Search code examples
xmlxsltpositionxslt-2.0

XSLT: Why aren't my variables not incrementing correctly with position()?


I have an incoming SOAP xml request that I need to transform into XSLT, like so:

<q0:Operation namespace="Provisioning" name="Create" modifier="Data">
 <q0:ParameterList>
  <q0:StringParameter name="Action">Create</q0:StringParameter>
  <q0:StructParameter name="Create">
   <q0:StringParameter name="UserID">1234567X</q0:StringParameter>
   <q0:StringParameter name="Device1">abcd567890</q0:StringParameter>
   <q0:StringParameter name="Contract1">Postpaid</q0:StringParameter>
   <q0:StringParameter name="Line1">990108024011900</q0:StringParameter>
   <q0:StringParameter name="Device2">efgh567890</q0:StringParameter>
   <q0:StringParameter name="Contract2">Postpaid</q0:StringParameter>
   <q0:StringParameter name="Line2">990104562499105</q0:StringParameter>
  </q0:StructParameter>
 </q0:ParameterList>
</q0:Operation>

My transformation XSLT looks something like this:

<xsl:for-each select="//q0:CommandRequestData/q0:Command/q0:Transaction/q0:Operation/q0:ParameterList/q0:StructParameter[@name='Create']">
 <xsl:for-each select="q0:StringParameter[@name='UserID']">
  <ins:Parameter name="USER_ID" value="{string()}" />
 </xsl:for-each>
 <xsl:for-each select="q0:StringParameter[@name!='UserID']">
  <xsl:choose>
   <xsl:when test = "starts-with(@name, 'Device')">
    <xsl:variable name="DeviceIndex" select="position()"/>
    <ins:Parameter name="DEVICE_ID_{$DeviceIndex}" value="{string()}" />
   </xsl:when>
   <xsl:when test = "starts-with(@name, 'Contract')">
    <xsl:variable name="ContractIndex" select="position()"/>
    <ins:Parameter name="CONTRACT_TYPE_{$ContractIndex}" value="{string()}" />
   </xsl:when>
   <xsl:when test = "starts-with(@name, 'Line')">
    <xsl:variable name="LineIndex" select="position()"/>
    <ins:Parameter name="LINE_ID_{$LineIndex}" value="{string()}" />
   </xsl:when>
   <xsl:otherwise>
    <ins:Parameter name="NA" value="NA" />
   </xsl:otherwise>
  </xsl:choose>
 </xsl:for-each>
</xsl:for-each>

However, for some reason the numbers in my result SOAP xml isn't incrementing properly, as if they're based on a single 'counter' variable instead of multiple 'counter' variables:

<q0:Operation namespace="Provisioning" name="Create" modifier="Data">
 <q0:ParameterList>
  <q0:StringParameter name="Action">Create</q0:StringParameter>
  <q0:StructParameter name="Create">
   <q0:StringParameter name="USER_ID">1234567X</q0:StringParameter>
   <q0:StringParameter name="DEVICE_ID_1">abcd567890</q0:StringParameter>
   <q0:StringParameter name="CONTRACT_TYPE_2">Postpaid</q0:StringParameter>
   <q0:StringParameter name="LINE_ID_3">990108024011900</q0:StringParameter>
   <q0:StringParameter name="DEVICE_ID_4">efgh567890</q0:StringParameter>
   <q0:StringParameter name="CONTRACT_TYPE_5">Postpaid</q0:StringParameter>
   <q0:StringParameter name="LINE_ID_6">990104562499105</q0:StringParameter>
  </q0:StructParameter>
 </q0:ParameterList>
</q0:Operation>

How do I fix this? Preferably without using templates, because I'm not familiar enough with those.

The outgoing SOAP xml result from the transformation should have renamed StringParameters with correct numbering, like so:

<q0:Operation namespace="Provisioning" name="Create" modifier="Data">
 <q0:ParameterList>
  <q0:StringParameter name="Action">Create</q0:StringParameter>
  <q0:StructParameter name="Create">
   <q0:StringParameter name="USER_ID">1234567X</q0:StringParameter>
   <q0:StringParameter name="DEVICE_ID_1">abcd567890</q0:StringParameter>
   <q0:StringParameter name="CONTRACT_TYPE_1">Postpaid</q0:StringParameter>
   <q0:StringParameter name="LINE_ID_1">990108024011900</q0:StringParameter>
   <q0:StringParameter name="DEVICE_ID_2">efgh567890</q0:StringParameter>
   <q0:StringParameter name="CONTRACT_TYPE_2">Postpaid</q0:StringParameter>
   <q0:StringParameter name="LINE_ID_2">990104562499105</q0:StringParameter>
  </q0:StructParameter>
 </q0:ParameterList>
</q0:Operation>

Solution

  • The value of position() is determined from the position of the current node in the current node-set (or, in XPath 2.0 terminology, the position of the context item within the sequence of items currently being processed). The current node-set is established by your instruction:

    <xsl:for-each select="q0:StringParameter[@name!='UserID']">
    

    and it includes all q0:StringParameter elements whose name is not "UserID".

    If you want a specific count for each type of name, try changing:

    <xsl:variable name="DeviceIndex" select="position()"/>
    

    to:

    <xsl:variable name="DeviceIndex">
        <xsl:number count="q0:StringParameter[starts-with(@name, 'Device')]"/>
    </xsl:variable>
    

    and so on for the other types.

    Or, since it seems the input already has the correct numbering, you could extract it by:

    <xsl:variable name="DeviceIndex" select="substring-after(@name, 'Device')"/>