Search code examples
xslt-2.0xpath-2.0

XSLT 2.0: Overriding nodes with grandchild nodes


I'm trying to find a way to replace a node with one that has the same name deeper down in the tree. For example, with the following input:

<root>
    <foo>
        <a>1</a>
        <b>2</b>
        <c>3</c>
        <bar>
            <a>100</a>
            <c>5000</c>
        </bar>
    </foo>
</root>

I'd like to produce something like this:

<root>
    <foo>
        <a>100</a>
        <b>2</b>
        <c>5000</c>
    </foo>
</root>

I need to be able to replace any number of nodes, and I'd also like to figure out the list dynamically, rather than spell out all the possibilities because there's a chance that things will change in the future. One other requirement is that order of the parent nodes must remain intact. (To be specific, my final output is going to be a CSV file so the columns need to line up with the headers.)

This is my first attempt at learning XSLT and I'm totally stumped on this one! Any help would be greatly appreciated. I'm using XSLT 2.0, BTW.

Thanks, Mark


Solution

  • I apologize for the nasty SO bug which doesn't indent the formatted code!

    They can't fix this for months...

    This transformation:

    <xsl:stylesheet version="2.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:xs="http://www.w3.org/2001/XMLSchema">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <xsl:template match="node()|@*">
      <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
     </xsl:template>
    
     <xsl:template match=
     "*[*]
           [every $leaf in .//*[not(*)]
          satisfies
            name($leaf) = preceding::*/name()
           ]
     "/>
    
     <xsl:template match=
      "*[not(*) and name() = following::*/name()]">
    
      <xsl:sequence select=
      "following::*[name() = name(current())][1]"/>
     </xsl:template>
    </xsl:stylesheet>
    

    when applied on the provided XML document:

    <root>
        <foo>
            <a>1</a>
            <b>2</b>
            <c>3</c>
            <bar>
                <a>100</a>
                <c>5000</c>
            </bar>
        </foo>
    </root>
    

    produces the wanted, correct result:

    <root>
       <foo>
          <a>100</a>
          <b>2</b>
          <c>5000</c>
       </foo>
    </root>