xmlxsltxslt-1.0cxml

Concatenate using XSLT v1.0 in XML to XML transformation


I am doing an XSLT stylesheet to transform from XML to XML, the layouts are very different and my case is causing a headache. I can only use XSLT 1.0 and I'm not finding the way to do it.

Input File

<?xml version="1.0"?>
<Root>
  <ParentNode>
    <Node>
      <Id>1</Id>
      <Date>2019-02-01</Date>
      <ReferenceLine>1</ReferenceLine>
    </Node>
    <Node>
      <Id>2</Id>
      <Date>2019-02-01</Date>
      <ReferenceLine>1</ReferenceLine>
    </Node>
    <Node>
      <Id>3</Id>
      <Date>2019-02-02</Date>
      <ReferenceLine>2</ReferenceLine>
    </Node>
  </ParentNode>
</Root>

Output File

<Lines>
  <Line>
    <LineNum>1</LineNum>
    <Node>1 - 2</Node>
  </Line>
 <Line>
    <LineNum>2</LineNum>
    <Node>3</Node>
 </Line>
</Lines>

So what I need is to concatenate in the Output all the nodes that appears with reference to the line. While I can have multiple ocurrences of Node in the Input file, in the output file I can only have one ocurrence inside the Line node.


Solution

  • You can achieve with this the XSLT-1.0 method Muenchian Grouping. If you search on SO, you'll find a lot of examples. Applying this method, your stylesheet could look like this.

    This stylesheet concatenates all the Ids separated by a -.

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:key name="nd" match="Node" use="ReferenceLine" />   
    
    <xsl:template match="/Root/ParentNode">
        <Lines>
            <xsl:for-each select="Node[generate-id() = generate-id(key('nd',ReferenceLine)[1])]">
                <Line>
                    <LineNum><xsl:value-of select="Id" /></LineNum>
                    <Node>
                        <xsl:for-each select="key('nd',ReferenceLine)">
                            <xsl:sort order="ascending" select="Id" />
                            <xsl:value-of select="Id" />
                            <xsl:if test="position() != last()">
                                <xsl:text> - </xsl:text>
                            </xsl:if>
                        </xsl:for-each>
                    </Node>
                </Line>
            </xsl:for-each>
        </Lines>
    </xsl:template>
    
    </xsl:stylesheet>
    

    If you only want to get a range as the result, replace the inner for-each with the following:

    ...
    <xsl:for-each select="key('nd',ReferenceLine)">
        <xsl:sort order="ascending" select="Id" />
        <xsl:if test="position() = 1">
            <xsl:value-of select="Id" />
        </xsl:if>
        <xsl:if test="position() = last() and position() != 1">
            <xsl:value-of select="concat(' - ',Id)" />
        </xsl:if>
    </xsl:for-each>
    ...
    

    But be aware that this range would ignore gaps and only use the lowest and the highest value.