XSLT 2.0 How to tokenize values of multiple elements and correlate them together to form the result

StackOverflow https://stackoverflow.com/questions/21603814

  •  07-10-2022
  •  | 
  •  

Question

I just began to work on XSLT. My input and expected output are as follows and the XSLT is also given below. I'm now facing 2 issues

  1. As you could see I'm assigning the variable name dynamically and it throws the following exception net.sf.saxon.trans.XPathException: Required item type of the context item for the attribute axis is node(); supplied value has item type xs:string
  2. When I set the variable name to some static string, I don't receive the exception but I don't see the expected output i.e. I'm wondering how do I map the tokenized strings

Input

<students>
  <field name="id">1,2,3</field>
  <field name="name">a,b,c</field>
 </students>

Expected Output

<students>
<student>
    <id>1</id>
    <name>a</name>
</student>
<student>
    <id>2</id>
    <name>b</name>
</student>
<student>
    <id>3</id>
    <name>c</name>
</student>
</students>

XSLT

<xsl:stylesheet version="2.0" 
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output indent="yes" method="xml"/>
<xsl:strip-space elements="*"/>

<xsl:template match="/" name="main">

    <xsl:for-each select="students/field">
        <xsl:for-each select="tokenize(.,',')">
            <xsl:element name="{@name}">
                <xsl:value-of select="."/>
            </xsl:element>
        </xsl:for-each>
    </xsl:for-each>                     
</xsl:template>
</xsl:stylesheet>
Was it helpful?

Solution

I am not sure why the stylesheet you show uses XSLT 1.0 since you tagged the question with XSLT 2.0 and tokenize is a 2.0 function.

Also, your expected output contains a typo I think. Perhaps this is what you meant to write:

<student>
  <id>1</id>
  <name>a</name>
</student>
<student>
  <id>2</id>
  <name>b</name>
</student>

Why you get an exception

In XSLT, expressions depend heavily on the context. A line like the following:

<xsl:element name="{@name}">

depends on the context in the sense that it retrieves the value of the name attribute of either the current template match or the current item in an xsl:for-each.

In your case, the context is this:

<xsl:for-each select="tokenize(.,',')">

So, the context for {@name} is a tokenized string - that of course does not have any attributes.

Stylesheet

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

   <xsl:output indent="yes" method="xml"/>
   <xsl:strip-space elements="*"/>

   <xsl:template match="/students">
      <xsl:copy>
         <xsl:apply-templates/> 
      </xsl:copy>
   </xsl:template>

   <xsl:template match="field[@name='id']">
      <xsl:variable name="match" select="."/>
      <xsl:for-each select="tokenize(.,',')">
         <xsl:variable name="id-pos" select="position()"/>
         <student>
            <id>
               <xsl:value-of select="."/>
            </id>
            <name>
               <xsl:value-of select="tokenize($match/following-sibling::field[@name='name'],',')[position() = $id-pos]"/>
            </name>
         </student>
      </xsl:for-each>
   </xsl:template>

   <xsl:template match="text()"/>

</xsl:stylesheet>

Output

<?xml version="1.0" encoding="UTF-8"?>
<students>
   <student>
      <id>1</id>
      <name>a</name>
   </student>
   <student>
      <id>2</id>
      <name>b</name>
   </student>
   <student>
      <id>3</id>
      <name>c</name>
   </student>
</students>

OTHER TIPS

Assuming you don't know the number and names of the field elements then the following is more generic (and avoids tokenizing values multiple times):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

<xsl:output indent="yes"/>

<xsl:template match="students">
  <xsl:copy>
    <xsl:variable name="fields">
      <fields>
        <xsl:variable name="fields" select="field"/>
        <xsl:for-each select="field">
          <xsl:variable name="field" select="."/>
          <xsl:element name="{@name}s">
            <xsl:for-each select="tokenize(., ',')">
              <xsl:element name="{$field/@name}">
                <xsl:value-of select="."/>
              </xsl:element>
            </xsl:for-each>
          </xsl:element>
        </xsl:for-each>
      </fields>
    </xsl:variable>
    <xsl:for-each select="$fields/fields/*[1]/*">
      <xsl:variable name="pos" select="position()"/>
      <student>
        <xsl:copy-of select="$fields/fields/*/*[$pos]"/>
      </student>
    </xsl:for-each>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top