2

I have an XSLT problem, I think I need to iterate on two for-each loops, maybe there is a better solution. I hope below example explains what my problem is about.

I have a following XML format:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <variables>var1 var2 var3 var4 var5</variables>
   <measurement no="1">
      <values>1 2 3 4 5</values>
   </measurement>
   <measurement no="2">
      <values>11 22 33 44 55</values>
   </measurement>
</root>

Expected output:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <record>
      <var1>1</var1>
      <var2>2</var2>
      <var3>3</var3>
      <var4>4</var4>
      <var5>5</var5>
   </record>
   <record>
      <var1>11</var1>
      <var2>22</var2>
      <var3>33</var3>
      <var4>44</var4>
      <var5>55</var5>
   </record>
</root>

A solution from Does xslt have split() function? worked for me partially, I managed to write following XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()" />
      </xsl:copy>
   </xsl:template>
   <xsl:template match="root/variables" name="tokenize">
      <xsl:param name="separator" select="' '" />
      <xsl:for-each select="tokenize(.,$separator)">
         <xsl:variable name="variable" select="normalize-space(.)" />
         <xsl:element name="{$variable}">xxx</xsl:element>
      </xsl:for-each>
   </xsl:template>
</xsl:stylesheet>

And the result was:

<root>
   <var1>xxx</var1>
   <var2>xxx</var2>
   <var3>xxx</var3>
   <var4>xxx</var4>
   <var5>xxx</var5>
   <measurement no="1">
      <values>1 2 3 4 5</values>
   </measurement>
   <measurement no="2">
      <values>11 22 33 44 55</values>
   </measurement>
</root>

However, I do not see any way this could operate on the values at the same time. I need to connect respective elements of variables and values together, in seperate record tags for each measurement. I will be garteful for any help!

fasola
  • 47
  • 3

2 Answers2

4

The little-known function for-each-pair() can be useful here.

<xsl:template match="measurement">
  <record>
    <xsl:sequence select="for-each-pair(
                           tokenize(/root/variables),
                           tokenize(values),
                           my:gen#2)"/>
  </record>
</xsl:template>

<xsl:function name="my:gen" as="element()">
   <xsl:param name="var" as="xs:string"/>
   <xsl:param name="content" as="xs:string"/>
   <xsl:element name="{$var}">{$content}</xsl:element>
</xsl:function>  

(This is XSLT 3.0 with expand-text="yes")

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • thank you! I did not know there is such a function! I am not sure whether I can use XSLT 3.0, however I will take a note for the future! – fasola Jun 16 '21 at 13:20
3

How about:

XSLT 2.0

<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:template match="/root">
    <xsl:copy>
        <xsl:variable name="var" select="tokenize(variables, ' ')" />
        <xsl:for-each select="measurement">
            <record>
                <xsl:for-each select="tokenize(values, ' ')">
                    <xsl:variable name="i" select="position()" />
                    <xsl:element name="{$var[$i]}">
                        <xsl:value-of select="."/>
                    </xsl:element>
                </xsl:for-each>
            </record>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Note that this assumes the number of values in each measurement equals the number of tokens in variables, and that each token in variables is a valid XML element name.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51