coder
coder

Reputation: 4313

how to increment per node set in xslt

I have this xml:

<root>
    <row>
        <number>1001461</number>
        <unit>CAN</unit>
    </row>
    <row>
        <number>1001462</number>
        <unit>KG</unit>
    </row>
</root>

My Xslt:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
    <xsl:output method="xml" indent="yes"/>
    <xsl:param name="formid" select="60202" />


  <xsl:template match="@* | node()">
      <DocumentElement>
          <xsl:apply-templates>
            <xsl:with-param name="primarykey" select="position()"/>
          </xsl:apply-templates>
      </DocumentElement>
    </xsl:template>

  <xsl:template match="row" name="trow">
    <xsl:param name="primarykey"/>
    <xsl:for-each select="*">
      <SaveDataTable>
        <KeyName>
          <xsl:value-of select="name()"/>
        </KeyName>
        <KeyValue>
          <xsl:value-of select="."/>
        </KeyValue>
        <PrimaryKey>
          <xsl:value-of select="concat('-',$primarykey)"/>
        </PrimaryKey>
        <FormId>
          <xsl:value-of select="$formid"/>
        </FormId>
      </SaveDataTable>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Expected output: (note that the first two <PrimaryKey> has -1 and the next two has -2, that's how I need)

<DocumentElement>
    <SaveDataTable>
        <KeyName>number</KeyName>
        <KeyValue>1001461</KeyValue>
        <PrimaryKey>-1</PrimaryKey>
        <FormId>60202</FormId>
    </SaveDataTable>
    <SaveDataTable>
        <KeyName>unit</KeyName>
        <KeyValue>CAN</KeyValue>
        <PrimaryKey>-1</PrimaryKey>
        <FormId>60202</FormId>
    </SaveDataTable>
    <SaveDataTable>
        <KeyName>number</KeyName>
        <KeyValue>1001462</KeyValue>
        <PrimaryKey>-2</PrimaryKey>
        <FormId>60202</FormId>
    </SaveDataTable>
    <SaveDataTable>
        <KeyName>unit</KeyName>
        <KeyValue>KG</KeyValue>
        <PrimaryKey>-2</PrimaryKey>
        <FormId>60202</FormId>
    </SaveDataTable>
</DocumentElement>

Basically for every <row>...</row> the <PrimaryKey>-1</PrimaryKey> should decrement by 1, but it's not happening. All the <PrimaryKey> elements have a value of -1 instead of -1, -2 and so on.

===========================================

Update: I kinda get it to work but I'm not sure if it is efficient.

Working xslt (may need improvement):

<xsl:stylesheet version="1.0" exclude-result-prefixes="msxsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
    <xsl:output method="xml" indent="yes"/>
    <xsl:param name="formid" select="60202"/>
    <xsl:template match="@* | node()">
        <DocumentElement>
            <xsl:for-each select="row">
                <xsl:call-template name="trow">
                    <xsl:with-param name="primarykey" select="position()"/>
                </xsl:call-template>
            </xsl:for-each>
        </DocumentElement>
    </xsl:template>
    <xsl:template name="trow">
        <xsl:param name="primarykey"/>
        <xsl:for-each select="*">
            <SaveDataTable>
                <KeyName>
                    <xsl:value-of select="name()"/>
                </KeyName>
                <KeyValue>
                    <xsl:value-of select="."/>
                </KeyValue>
                <PrimaryKey>
                    <xsl:value-of select="concat('-',$primarykey)"/>
                </PrimaryKey>
                <FormId>
                    <xsl:value-of select="$formid"/>
                </FormId>
            </SaveDataTable>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 2

Views: 1276

Answers (3)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243599

Push style solution -- similar to that of C. M. Sperberg-McQueen, but probably slightly more efficient as no xsl:number is calculated and the position is passed as a parameter.

Another difference is that we don't need to specify the priority attribute on any template.

Also, we don't multiply by -1.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

        <xsl:param name="pFormid" select="60202"/>

        <xsl:template match="/*">
            <DocumentElement>
                <xsl:apply-templates/>
            </DocumentElement>
        </xsl:template>

        <xsl:template match="row">
            <xsl:apply-templates select="*">
             <xsl:with-param name="pPos" select="position()"/>
            </xsl:apply-templates>
        </xsl:template>

        <xsl:template match="row/*">
          <xsl:param name="pPos"/>

            <SaveDataTable>
                    <KeyName>
                        <xsl:value-of select="name()"/>
                    </KeyName>
                    <KeyValue>
                        <xsl:value-of select="."/>
                    </KeyValue>
                    <PrimaryKey>
                        <xsl:value-of select="-$pPos"/>
                    </PrimaryKey>
                    <FormId>
                        <xsl:value-of select="$pFormid"/>
                    </FormId>
                </SaveDataTable>
        </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<root>
    <row>
        <number>1001461</number>
        <unit>CAN</unit>
    </row>
    <row>
        <number>1001462</number>
        <unit>KG</unit>
    </row>
</root>

the wanted, correct result is produced:

<DocumentElement>
   <SaveDataTable>
      <KeyName>number</KeyName>
      <KeyValue>1001461</KeyValue>
      <PrimaryKey>-1</PrimaryKey>
      <FormId>60202</FormId>
   </SaveDataTable>
   <SaveDataTable>
      <KeyName>unit</KeyName>
      <KeyValue>CAN</KeyValue>
      <PrimaryKey>-1</PrimaryKey>
      <FormId>60202</FormId>
   </SaveDataTable>
   <SaveDataTable>
      <KeyName>number</KeyName>
      <KeyValue>1001462</KeyValue>
      <PrimaryKey>-2</PrimaryKey>
      <FormId>60202</FormId>
   </SaveDataTable>
   <SaveDataTable>
      <KeyName>unit</KeyName>
      <KeyValue>KG</KeyValue>
      <PrimaryKey>-2</PrimaryKey>
      <FormId>60202</FormId>
   </SaveDataTable>
</DocumentElement>

Upvotes: 0

C. M. Sperberg-McQueen
C. M. Sperberg-McQueen

Reputation: 25054

Another approach (in a 'push' rather than a 'pull' style) would break the processing up into templates:

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

    <xsl:output method="xml" indent="yes"/>
    <xsl:param name="formid" select="60202"/>

Wrap the results for the entire document in DocumentElement.

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

For each row, get its positional number. We use xsl:number rather than position() because we don't want to count text nodes.

    <xsl:template match="row" priority="10">
      <xsl:variable name="pos">
        <xsl:number count="row" level="single"/>
      </xsl:variable>
      <SaveDataTable>
        <xsl:apply-templates>
          <xsl:with-param name="pkey" select="-1 * $pos"/>
        </xsl:apply-templates>
      </SaveDataTable>
    </xsl:template>

On each child of a row element, emit a SaveDataTable element containing key name, key value, primary key, and form ID.

    <xsl:template match="row/*">
      <xsl:param name="pkey"/>
      <KeyName>
        <xsl:value-of select="name()"/>
      </KeyName>
      <KeyValue>
        <xsl:value-of select="."/>
      </KeyValue>
      <PrimaryKey>
        <xsl:value-of select="$pkey"/>
      </PrimaryKey>
      <FormId>
        <xsl:value-of select="$formid"/>
      </FormId>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 2

Kirill Polishchuk
Kirill Polishchuk

Reputation: 56222

You can try this solution, but I think it is less efficient than yours:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:param name="formid" select="60202"/>

  <xsl:template match="/root">
    <DocumentElement>
      <xsl:apply-templates select="row/*"/>
    </DocumentElement>
  </xsl:template>

  <xsl:template match="*">
    <SaveDataTable>
      <KeyName>
        <xsl:value-of select="name()"/>
      </KeyName>
      <KeyValue>
        <xsl:value-of select="."/>
      </KeyValue>
      <PrimaryKey>
        <xsl:value-of select="-1 * (count(../preceding-sibling::row) + 1)"/>
      </PrimaryKey>
      <FormId>
        <xsl:value-of select="$formid"/>
      </FormId>
    </SaveDataTable>
  </xsl:template>
</xsl:stylesheet>

Upvotes: 0

Related Questions