Search code examples
xmlxslt-2.0

How do you convert and append sequence numbers of same named elements to attributes


I have this source XML.

<?xml version="1.0" encoding="UTF-8"?>
<toplevel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <address>
        <line>address line a</line>
        <line>address line b</line>
        <line>address line c</line>
        <line>address line d</line>
    </address>
</toplevel>

as described here

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="toplevel">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="address"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="address">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="4" ref="line"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="line" type="xs:string"/>
</xs:schema>

and i would like to convert it to this format using SAXON-HE 10.6.

<?xml version="1.0" encoding="UTF-8"?>
<toplevel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <address line1="address line a" line2="address line b" line3="address line c" line4="address line d"/>
</toplevel>

This is what i have for the transform XLT file but it only works if the lines in the source xml are numbered which unfortunately they aren't. I have tried adding a xsl:for-each and a variable but i can't determine how to append a sequential number to the line attributes. Please note the max number of lines will always be 4 so perhaps it could be hard-coded, but i don't know the syntax. The conversion can easily be done in a programming language but I would really like to know the answer to this using a SAXON-HE 10.6 transform.

thank you!

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">
    <xsl:output method="xml" indent="yes" />
    <xsl:strip-space elements="*"/>
    
    <xsl:template match="/">
        <xsl:text>&#xA;</xsl:text>
        <xsl:apply-templates mode="copy" select="."/>
        
    </xsl:template>
    
    <xsl:template match="* | text() | @*" mode="copy">
        <xsl:copy>
            
            <xsl:apply-templates mode="copy" select="@*"/>
            <xsl:apply-templates mode="copy"/>
        </xsl:copy>
        
    </xsl:template>
    
    <xsl:template match="*"/>
    
    <xsl:template match="toplevel" mode="copy">  
        <xsl:copy>            
            <xsl:apply-templates mode="copy" select="address"/> 
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="address" mode="copy">
        <xsl:copy>
            <xsl:apply-templates mode="convert-to-attr" select="*"/>            
        </xsl:copy> 
    </xsl:template>
    
    <xsl:template match="*" mode="convert-to-attr">
        <xsl:attribute name="{name()}" select="text()"/>
    </xsl:template>
</xsl:stylesheet>

Solution

  • If you're using Saxon 10 you should probably use XSLT 3.0 rather than 2.0. That lets you use an xsl:mode element to copy elements, rather than write your own identity templates.

    Here I've used the position() function to produce the numbering of the line attribute names. This function returns the index (starting from 1) of the context item in the current sequence. In the for-each, the select attribute selects the 4 line child elements, the value of position() will take the values 1 to 4 in each iteration of the for-each's sequence constructor (i.e. the xsl:attribute statement):

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="3.0">
        
        <xsl:mode on-no-match="shallow-copy"/>
    
        <xsl:template match="address">
          <xsl:copy>
            <xsl:for-each select="line">
              <xsl:attribute name="line{position()}" select="text()"/>
            </xsl:for-each>
          </xsl:copy>
        </xsl:template>
        
    </xsl:stylesheet>
    

    Result:

    <toplevel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <address line1="address line a" line2="address line b" line3="address line c" line4="address line d"/>
    </toplevel>