tags:

views:

1000

answers:

3

I need to use xsl/xpath (version 1.0) to do something special (for simplifying, say insert some dummy text) when the value of SupplierId changes. I need to handle 3 variations;

  1. Do something when on the first Order (the first occurence of SupplierId)
  2. Do somwthing when on OrderId O3 (SupplierId changed from S1 to S2)
  3. Do something when on the last Order (the last occurence of SupplierId)

.

<?xml version="1.0" encoding="utf-8"?>
<Orders>
    <Order>
     <OrderId>O1</OrderId>
     <SupplierId>S1</SupplierId>
    </Order>
    <Order>
     <OrderId>O2</OrderId>
     <SupplierId>S1</SupplierId>
    </Order>
    <Order>
     <OrderId>O3</OrderId>
     <SupplierId>S2</SupplierId>
    </Order>
    <Order>
     <OrderId>O4</OrderId>
     <SupplierId>S2</SupplierId>
    </Order>
    <Order>
     <OrderId>O5</OrderId>
     <SupplierId>S2</SupplierId>
    </Order>
</Orders>

I've tried using preceding-sibling, following-sibling, etc, but haven't found out of it yet. I'd appreciate any help on this newbie question.

Wally

A: 

Assuming a root element Orders, the XPath expressions matching each condition become:

a. first Order (the first occurence of SupplierId)

XPath 1.0 - /Orders/Order[SupplierId][1]
XPath 2.0 - /Orders/Order[exists(SupplierId)][1]

b. On OrderId O3 (SupplierId changed from S1 to S2)

/Orders/Order[OrderId = 'O3' and SupplierId = 'S2']

c. On the last Order (the last occurence of SupplierId)

/Orders/Order[SupplierId][last()]
Cerebrus
The OP is asking something else: How to detect when the SupplierId changes from the previous Order to the current one. Another note is that the provided XPath expressions (and the XPath 2.0 has additional problems!) select not the SupplierId of the first Order, but the first Order, that has a SupplierId
Dimitre Novatchev
@Dimitre: Thank you for your comment. As far as the selection of the Order node is concerned, it is completely intentional as per the stated requirement in the question(or atleast my interpretation of the meaning). To regressively test my expressions, I removed the SupplierId node from the first Order and the last Order node in the sample XML. As for the detection of node changes, I assumed that it would be done via code or the OP's own XSLT implementation.
Cerebrus
A: 

You can use recursion to step down the list of orders one by one, comparing the previous value to the current value as you go (the solution implies that document order is correct already, because it uses the following-sibling axis):

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

  <xsl:template match="/Orders">
    <xsl:apply-templates select="Order[1]" mode="watch_SupplierId" />
  </xsl:template>

  <xsl:template match="Order" mode="watch_SupplierId">
    <xsl:param name="PrevValue" select="''" />

    <xsl:if test="string(SupplierId) != $PrevValue">
      <xsl:call-template name="DoSomething" />
    </xsl:if>

    <xsl:variable name="next" select="following-sibling::Order[1]" />
    <xsl:choose>
      <xsl:when test="$next">
        <xsl:apply-templates select="$next" mode="watch_SupplierId">
          <xsl:with-param name="PrevValue" select="string(SupplierId)" />
        </xsl:apply-templates>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="concat('last Order found: ', OrderId, '&#10;')" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="DoSomething">
    <xsl:value-of select="concat(
      '@SupplierId changed to &quot;' 
      , SupplierId/text() 
      , '&quot; in Order '
      , OrderId
      , '&#10;'
    )" />
  </xsl:template>

</xsl:stylesheet>

Outputs:

@SupplierId changed to "S1" in Order O1
@SupplierId changed to "S2" in Order O3
last Order found: O5
Tomalak
Actually, no recursion is necessary to lolve this problem :)
Dimitre Novatchev
Actually, I anticipated that you would say that. :)
Tomalak
+1  A: 

This is one natural and easy solution:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="text"/>

    <xsl:template match="Order[1]">
      First OrderId = <xsl:text/>
      <xsl:value-of select="OrderId"/>
    </xsl:template>

    <xsl:template match="Order[last()]">

      Last OrderId = <xsl:text/>
      <xsl:value-of select="OrderId"/>
    </xsl:template>

    <xsl:template match=
      "Order[not(position() = 1)]
         [not(SupplierId 
             = 
             preceding-sibling::Order[1]/SupplierId
              )
        ]">

      Changes in Order OrderId = <xsl:text/>
      <xsl:value-of select="OrderId"/>
         SupplierId = <xsl:text/>
         <xsl:value-of select="SupplierId"/>
      Previous Order OrderId = <xsl:text/>
      <xsl:value-of select=
        "preceding-sibling::Order[1]/OrderId"/>
              SupplierId = <xsl:text/>
         <xsl:value-of select=
         "preceding-sibling::Order[1]/SupplierId"/>
    </xsl:template>

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

When this transformation is applied on the provided XML document:

<Orders>
    <Order>
     <OrderId>O1</OrderId>
     <SupplierId>S1</SupplierId>
    </Order>
    <Order>
     <OrderId>O2</OrderId>
     <SupplierId>S1</SupplierId>
    </Order>
    <Order>
     <OrderId>O3</OrderId>
     <SupplierId>S2</SupplierId>
    </Order>
    <Order>
     <OrderId>O4</OrderId>
     <SupplierId>S2</SupplierId>
    </Order>
    <Order>
     <OrderId>O5</OrderId>
     <SupplierId>S2</SupplierId>
    </Order>
</Orders>

the desired result is produced:

  First OrderId = O1

  Changes in Order OrderId = O3
     SupplierId = S2
  Previous Order OrderId = O2
          SupplierId = S1

  Last OrderId = O5
Dimitre Novatchev
+1, very nice. Is repeatedly accessing the "preceding-sibling" axis a potential performance problem?
Tomalak
@Tomalak Referencing the "preceding-sibling" axis is not a problem in this case. preceding-sibling::someThing[1] takes a constant time unless there is a terrible, non-optimizing XSLT processor.
Dimitre Novatchev
@Dimitre: Kindly see my response to your comment to my answer... Ah, whatever! ;-)
Cerebrus