Elej
Elej

Reputation: 65

Copy XML and rename some of its nodes and nested nodes

I need to copy everything to new XML except some elements which I need to rename.

All "child" nodes which are childs of "node1" I need to rename to "children" and "child" nodes which are childs of "node2" I need to rename to "kid".

Perfect solution will be using

match="/root/node1/descendant::child"

But as I found in Michael Kay response in topic http://comments.gmane.org/gmane.text.xml.saxon.help/14956

A pattern of the form /descendant::m__id[1] is not legal in either XSLT 1.0 or XSLT 2.0, though it becomes legal in XSLT 3.0.

Do You have any suggestions how to do that in XSLT 2.0 without doing what I made below? Because I don't know how many nesting there will be exactly.

Here is my xml example

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <node1>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
    </node1>
    <node2>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
    </node2>
</root>

And XSL:

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

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

    <xsl:template match="/root/node1/child">
        <xsl:element name="children">
            <xsl:apply-templates select="@*|node()"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="/root/node1/child/subnode/child">
        <xsl:element name="children">
            <xsl:apply-templates select="@*|node()"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="/root/node2/child">
        <xsl:element name="kid">
            <xsl:apply-templates select="@*|node()"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="/root/node2/child/subnode/child">
        <xsl:element name="kid">
            <xsl:apply-templates select="@*|node()"/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

Upvotes: 1

Views: 695

Answers (3)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243579

This solution doesn't use any predicates or axes at all, has the shortest match patterns, and thus is more efficient than one that uses predicates.

It also produces the expected correct result when <node1> has descendents or ancestors <node2> -- something which the remaining solutions don't handle at all.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

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

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

  <xsl:template match="child" mode="node1">
    <children>
      <xsl:apply-templates select="node()|@*" mode="#current"/>
    </children>
  </xsl:template>
  <xsl:template match="child" mode="node2">
    <kid>
      <xsl:apply-templates select="node()|@*" mode="#current"/>
    </kid>
  </xsl:template>
</xsl:stylesheet>

When applied on the provided XML document:

<root>
    <node1>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
    </node1>
    <node2>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
    </node2>
</root>

the wanted, correct result is produced:

<root>
      <node1>
            <children>
                  <subnode>
                        <children/>
                  </subnode>
      </children>
            <children>
                  <subnode>
                        <children/>
                  </subnode>
      </children>
      </node1>
      <node2>
            <kid>
                  <subnode>
                        <kid/>
                  </subnode>
      </kid>
            <kid>
                  <subnode>
                        <kid/>
                  </subnode>
      </kid>
      </node2>
</root>

When the same transformation is applied on this XML document, where <node1> and <node2> are descendents or ancestors of each other:

<root>
    <node1>
        <child>
            <subnode>
              <node2>
                  <child></child>
                  <node1>
                    <child/>
                  </node1>            
              </node2>
            </subnode>
        </child>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
    </node1>
    <node2>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
        <node1>
            <child>
                <subnode>
                    <child></child>
                </subnode>
            </child>
        </node1>
        <child>
            <subnode>
                <child></child>
            </subnode>
        </child>
    </node2>
</root>

again the correct and wanted result (the replacement-name of <child> is determined by its closest ancestor that is either <node1> or <node2>) is produced:

<root>
      <node1>
            <children>
                  <subnode>
                     <node2>
                           <kid/>
                           <node1>
                              <children/>
                           </node1>           
                     </node2>
                  </subnode>
            </children>
            <children>
                  <subnode>
                        <children/>
                  </subnode>
            </children>
      </node1>
      <node2>
            <kid>
                  <subnode>
                        <kid/>
                  </subnode>
            </kid>
            <node1>
                  <children>
                        <subnode>
                              <children/>
                        </subnode>
                  </children>
            </node1>
            <kid>
                  <subnode>
                        <kid/>
                  </subnode>
            </kid>
      </node2>
</root>

Upvotes: 1

Martin Honnen
Martin Honnen

Reputation: 167716

You can use //child in a pattern:

<xsl:template match="/root/node1//child">
    <xsl:element name="children">
        <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
</xsl:template>


<xsl:template match="/root/node2//child">
    <xsl:element name="kid">
        <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
</xsl:template>

I would use literal result elements instead of xsl:element:

<xsl:template match="/root/node1//child">
    <children>
        <xsl:apply-templates select="@*|node()"/>
    </children>
</xsl:template>


<xsl:template match="/root/node2//child">
    <kid>
        <xsl:apply-templates select="@*|node()"/>
    </kid>
</xsl:template>

Upvotes: 1

michael.hor257k
michael.hor257k

Reputation: 117140

Would this work for you?

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

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

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

<xsl:template match="child[ancestor::node2]">
    <kid>
        <xsl:apply-templates select="@*|node()"/>
    </kid>
</xsl:template>

</xsl:stylesheet>

Upvotes: 1

Related Questions