简体   繁体   中英

Losing xml tags during transformation with xslt

I am having trouble while renaming a child node during a transformation of a xml file with the help of a .xsl stylesheet. The problem is, that only values are handled and not the tags. I want to have both.

Original before transformation:

<old>
 <ns2:Header>
  <EntityId>yxc</EntityId>
  <Application>11</Application>
  <Version>354</Version>
  <User>
    <Id>user1</Id>
  </User>
</ns2:Header>
 ....
 </old>

Expected result:

<new>
  <Header>
  <EntityId>yxc</EntityId>
  <Application>11</Application>
  <Version>354</Version>
  <User>
    <Id>user1</Id>
  </User>
 </Header>
 ...
</new>

What I got so far is a .xsl like this:

<?xml version="1.0" encoding="UTF-8"?>
   <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output indent="yes" method="xml"/>

    <xsl:template match="/">
      <new xmlns:ns2="http://example.com">

      <xsl:template match="//ns2:Header">
            <xsl:element name="Header">
                <xsl:apply-templates select="//ns2:Header" />
            </xsl:element>
        </xsl:template>
       .....
        </new>
     </xsl:template>
  </xsl:stylesheet>

After transformation, the child nodes of "Header" get lost and only the values are still there:

<new xmlns:ns2="http://example">
    <Header>
       yxc
       11
       354

        user1
     </Header>
     ...
</new>

I guess I am missing some expression around the "apply-templates" function. Any ideas?

Thanks!

You may not be loosing the node. If you are displaying transformed xml, in browser, using w3c xslt tester, then chances are that it might be just "UI" representation. Here is my code that works. Run the script below and click on transform.

this is my xsl, used in below test snippet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
    <xsl:template match="/">
      <new xmlns:ns2="http://example.com">
        <Header>
          <xsl:copy-of select="//*[local-name()='ns2:Header']/*" />
        </Header>
      </new>

    </xsl:template>

  </xsl:stylesheet>

Working example below:

 var parser = new DOMParser(); var xsltText = document.getElementById('xsl').value; var xslt = parser.parseFromString(xsltText, "text/xml"); var xmlText = document.getElementById('xml').value;; var xml = parser.parseFromString(xmlText, "text/xml"); document.getElementById('transform').onclick = function() { var xsltProcessor = new XSLTProcessor(); xsltProcessor.importStylesheet(xslt); var processed = xsltProcessor.transformToDocument(xml); var result = new XMLSerializer().serializeToString(processed); console.log(result); document.getElementById('result').value = result; } 
 XML to be transformed:<br/> <textarea id="xml" cols="70" rows="5"> <old> <ns2:Header> <EntityId>yxc</EntityId> <Application>11</Application> <Version>354</Version> <User> <Id>user1</Id> </User> </ns2:Header> </old> </textarea><br/>XSL:<br/> <textarea id="xsl" cols="70" rows="5"> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" /> <xsl:template match="/"> <new xmlns:ns2="http://example.com"> <Header> <xsl:copy-of select="//*[local-name()='ns2:Header']/*" /> </Header> </new> </xsl:template> </xsl:stylesheet> </textarea> <input type="button" id="transform" value="Transform!" /> <br/>Tranformed xml:<br/> <textarea id="result" cols="70" rows="5" readOnly="true"></textarea> 

The problem is that just applying a template without having defined one doesn't copy your selected nodes. There are some built-in templates that are executed in that case, one of which is this one:

<xsl:template match="text()|@*">
   <xsl:value-of select="."/>
</xsl:template>

This is why you see the text values in your output, but not their surrounding tags.

Part 1 - What is generally incorrect

If you use any namespace inside an XML file, it should be defined , eg in the main tag. So the main tag should be:

<old xmlns:ns2="http://example.com">

Another point to correct is that templates cannot be nested one within another.

Part 2 - Why your script failed

While looking at an XSLT script, it is not enough to read its content. Equally important is what has not been written, but is somehow included in the XSLT processor, namely the default template rules .

One of them is the default rule for elements:

<xsl:template match="* | /">
  <xsl:apply-templates/>
</xsl:template>

Remember that default select content for the above apply-templates is node() , which means that this default rule copies only the content of the source element, omitting both opening and closing tags.

Quite often it is not what we want. If we want to "replicate" the whole content of the source, the script should containt an identity template :

<xsl:template match="node()|@*">
  <xsl:copy>
    <xsl:apply-templates select="node() | @*"/>
  </xsl:copy>
</xsl:template>

As your script contains (probably) no such identity template, the XSLT processor applies its default rule, copying just the content .

Part 3 - How this task should be done

As I see, you want to do 2 changes:

  • Drop the namespace contained in the Header tag.
  • Change old tag name to new .

To drop any namespace, we have to replace the identity template with 2 following templates, dealing with elements and attributes:

<xsl:template match="*">
  <xsl:element name="{local-name()}">
    <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
</xsl:template>

<xsl:template match="@*">
  <xsl:attribute name="{local-name()}">
    <xsl:value-of select="."/>
  </xsl:attribute>
</xsl:template>

Note the usage of local-name() , which actually remove any source namespace.

And to change the element name from old to new , we need the following template:

<xsl:template match="old">
  <new>
    <xsl:apply-templates/>
  </new>
</xsl:template>

So the whole script can be written as follows:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="UTF-8" indent="yes" />
  <xsl:strip-space elements="*"/>

  <!-- Change element name -->
  <xsl:template match="old">
    <new>
      <xsl:apply-templates/>
    </new>
  </xsl:template>

  <!-- Copy elements w/o namespace -->
  <xsl:template match="*">
    <xsl:element name="{local-name()}">
      <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
  </xsl:template>

  <!-- Copy attributes w/o namespace -->
  <xsl:template match="@*">
    <xsl:attribute name="{local-name()}">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>

</xsl:transform>

I added strip-space to remove additional empty lines from the output.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM