Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How to get value of previous node in sorted order, not document order 1

Status
Not open for further replies.

TonyMarston

Programmer
Dec 8, 1999
20
0
0
GB
I wish to process my XML data in a particular oder, so I am using the following code:
Code:
  <xsl:apply-templates select="SYSTEM/TABLE/OCC">
    <xsl:sort select="FIELD3"/>
    <xsl:sort select="FIELD4"/>
  </xsl:apply-templates>
Within the template I wish to obtain a value from the previous node so I can emulate "on change of value do so-and-so", so I use the following code:
Code:
<xsl:template match="SYSTEM/TABLE/OCC">

  <xsl:variable name="position" select="position()" />
  <xsl:variable name="prev">
    <xsl:if test="$position > 1">
      <xsl:value-of select="/SYSTEM/TABLE/OCC[$position -1]/FIELD3" />
    </xsl:if>
  </xsl:variable>

  ....

</xsl:template>
The problem is that $position-1 will only give me the previous node in the original unsorted sequence, not the sorted sequence.

In a 'conventional' programming language I could save the current value in a variable so that I could test it the next time around, but even this simple act does not appear to be possible in XSLT.

Does anyone have any ideas?
 
I think the only way you are going to be able to do it is to sort the nodes into a variable and select the position()-1 from the corresponding node.

I can't actually get this working as my variables are not "node sets" and I can't be bothered to sort it out (I think i need xslt 2 or sommit). But basically from what I know it goes something like this:

Code:
<xsl:template name="test" match="item">
		<xsl:variable name="SortedItems">
			<xsl:for-each select="//item">
				<xsl:sort select="id"/>
				<xsl:copy-of select="."/>
			</xsl:for-each>
		</xsl:variable>
		<xsl:variable name="prev">
			<xsl:if test="position() > 1">
				<xsl:value-of select="$SortedItems/item[position()-1]" />
			</xsl:if>
		</xsl:variable>

I have not tested this, as I say.

Interestingly, just using preceding-sibling::node() does not work either. You would have thought it would...

Matt
matt


 
flumpy is right, you'll need to use a variable:
Code:
<xsl:variable name="SortedItems">
  <xsl:for-each select="SYSTEM/TABLE/OCC">
    <xsl:sort select="FIELD3"/>
    <xsl:sort select="FIELD4"/>
    <xsl:copy-of select="."/>
  </xsl:for-each>
</xsl:variable>
<xsl:for-each select="$SortedItems/OCC">
  <xsl:variable name="position" select="position()"/>
  <xsl:variable name="prev">
    <xsl:if test="position() > 1">
      <xsl:value-of select="$SortedItems/OCC[$position - 1]"/>
    </xsl:if>
  </xsl:variable>
</xsl:for-each>
 
Tony,

Did you get the code to work? I have a similar requirement, but could not get the advice above to work. I am open to any thoughts and experience in comparing values at different positions in the for-each loop.

Thanks very much.
 
The code works. Post your code, and we might be able to see where you are going wrong.

Jon
 
After a lot of playing around I eventually got it to work as follows:

(1) Declare the following namespaces to deal with the 'node-set' extension:
Code:
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform"[/URL] version="1.0"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                xmlns:saxon="[URL unfurl="true"]http://icl.com/saxon"[/URL] exclude-result-prefixes="msxsl saxon"
>

(2) Create a variable to contain the sorted nodes:
Code:
  <xsl:variable name="SortedItems">
    <xsl:for-each select="/SYSTEM/TABLE/OCC">
      <xsl:sort select="FIELD2"/>
      <xsl:sort select="FIELD3"/>
      <xsl:copy-of select="."/>
    </xsl:for-each>
  </xsl:variable>

(3) Pass the sorted nodes to another template (this was the tricky part as it needs to find out how to deal with the sorted node-set):
Code:
  <xsl:choose>
    <xsl:when test="system-property('xsl:version') >= 1.1">
      <xsl:apply-templates select="$SortedItems/OCC"/>
    </xsl:when>
          
    <xsl:when test="function-available('saxon:node-set')">
      <xsl:apply-templates select="saxon:node-set($SortedItems)/OCC"/>
    </xsl:when>

    <xsl:when test="function-available('msxsl:node-set')">
      <xsl:apply-templates select="msxsl:node-set($SortedItems)/OCC"/>
    </xsl:when>

    <xsl:otherwise>
      <xsl:message terminate="yes">
        ERROR: Processor is unknown or doesn't support node-set extension function.
      </xsl:message>
    </xsl:otherwise>
  </xsl:choose>

(4) Process the sorted nodes with code to detect change of key value:
Code:
<xsl:template match="OCC">

  <xsl:variable name="position" select="position()" />
  <xsl:variable name="prev">
    <xsl:if test="$position > 1">
      <!-- store value of previous node in the sorted sequence -->
      <xsl:value-of select="/OCC[$position -1]/FIELD3"/>
    </xsl:if>
  </xsl:variable>

  <xsl:choose>
    <xsl:when test="FIELD3 != $prev">
      <!-- value has changed -->
    </xsl:when>
    <xsl:otherwise>
      <!-- value has not changed -->
    </xsl:otherwise>
  </xsl:choose>

</xsl:template>

Easy, isn't it? (once you know how)
 
Tony and Jon, your kindness is without peer. Thank you for your quick response.

I found a solution that uses the Msxml2 active x object and two xsl templates to create a unique set of values and then sort them and display them in an html document.

Tony, I am such a greenhorn that your solution raises several questions. I am going to post them, but please do not feel obligated to answer. But I'm am intrigued by your solution, and wonder if it is faster than mine. Here's the questions:

1) Is all of your xsl in a single file?

2) About the use of the SortedItems variable. It looks like the structure of your xml document is something like this:

<SYSTEM>
<TABLE>
<OCC>
<FIELD2>text node</FIELD2>
<FIELD3>text node</FIELD3>
</OCC>
</TABLE>
</SYSTEM>

and you are sorting FIELD2 and FIELD3. When you call your SortedItems variable, the node set is $SortedItems/OCC. Doesn't the variable already contain the OCC node? If so, why do you have to specify the OCC node in the expression? Is it because the OCC nodes are siblings, and have to be present in the expression so the [$position - 1] location expression will work?

3) When creating populating the SortedNodes variable, what does the xsl:copy-of command do?

4) What do either the saxon node-set or msxsl node-set functions do to your $SortedNode/OCC node set?

5)And finally a comment. In my xml document, the nodes I am working with are not siblings. Rather, they are very distant cousins, at the same level down from ancestors that are siblings, so I suspect that the idea of [position() - 1] will not work. Here's an example of one of the siblings:

<ID value="1">
<PBG value="CPI">
<Division value="AL">
<KPU value="AL">
<Technology value="PVD">
<Upper_Technology_Node value="<None>">
<Platform_Name value="ENDURA">
<Application_Area value="Al IC">
<Process_Step value="Al Fill">
<Process_Detail value="Barrier/ Al/ Arc-TiN">
<Primary_Customer_Interest_Code value="Metallization">
<Secondary_Customer_Interest_Code value="Al">
[Red]<Applied_Product_Name value="Endura Al Fill PVD">[/Red]
<Release_Status value="Customer Release">
<Refurb_Available value="<None>">
<Demo_Status value="Demo">
<Wafer_Size value="200mm">
<Device_Area value="<None>">
<Device_Type value="<None>">
<Lower_Technology_Node value="<None>">
<Official_Product value="Applied Endura Al PVD">
[red]<Actively_Marketed value="Yes" />[/red]
</Official_Product>
</Lower_Technology_Node>
</Device_Type>
</Device_Area>
</Wafer_Size>
</Demo_Status>
</Refurb_Available>
</Release_Status>
</Applied_Product_Name>
</Secondary_Customer_Interest_Code>
</Primary_Customer_Interest_Code>
</Process_Detail>
</Process_Step>
</Application_Area>
</Platform_Name>
</Upper_Technology_Node>
</Technology>
</KPU>
</Division>
</PBG>
</ID>


The node I am interested in (Applied_Product_Name) is 13 levels down from its ancestor, and the filtering value (Actively_Marketed) is 22 levels down. The Applied_Productd_Name is not unique to the document, so I need to create a set of unique values and then sort them alphabetically. Here's the xsl template that creates the unique values:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="
<xsl:eek:utput method="xml" version="1.0" encoding="iso-8859-1" indent="yes" />

<xsl:template match="/">
<data>

<xsl:for-each select="//Applied_Product_Name/@value[not(. = preceding::*/@value)][//Actively_Marketed[@value='Yes']]">

<xsl:variable name="thisName" select="." />

<ele>
<xsl:value-of select="$thisName" />
</ele>

</xsl:for-each>
</data>

</xsl:template>
</xsl:stylesheet>

This creates an xml document that is then sent to an xsl template to be sorted. (The created xml document moves the desired value from an attribute of the Applied_Product_Name node to a text node of the ele node). The transformation below sorts and outputs the desired values into an html form select field:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="
<xsl:eek:utput method="html" />

<xsl:template match="/">

<form>
<select>

<xsl:for-each select="//ele">

<xsl:sort select="." />

<xsl:variable name="thisName" select="." />

<option value="$thisName"><xsl:value-of select="." /></option>

</xsl:for-each>

</select>
</form>

</xsl:template>
</xsl:stylesheet>

The html file below uses the Msxml2.DOMDocument Active X Object methods to do the labor:

<html>
<head>
<title>iMark q_tax.cfm</title>
</head>

<script language="javascript">

function init(){

// load xml file into dom object
var xmlSrc = new ActiveXObject("Msxml2.DOMDocument.3.0");
xmlSrc.async = false;
xmlSrc.load("../iMarketingTaxonomy/application.xml");

// load the xsl file that returns unique values
xslSrc = new ActiveXObject("Msxml2.DOMDocument.3.0");
xslSrc.async = false;
xslSrc.load("../iMarketingTaxonomy/uniqueValues.xsl");

// transform the xml file into a unique set of desired values
var tmpStr = xmlSrc.transformNode(xslSrc);

// load the unique value string into the xml DOMDocument object
xmlSrc.loadXML(tmpStr);

// load the xsl file that returns sorted values as an html form select field
xslSrc.load("../iMarketingTaxonomy/sortedValues.xsl");

// transform the unique values xml into the sorted values
tmpStr = xmlSrc.transformNode(xslSrc);

// write the unique, sorted html select field to the div
resTree.innerHTML = tmpStr

}

</script>

</head>

<body onload="init();">

<div id="resTree"></div>

</body>

</html>

So, that's the long-winded picture. I don't expect a response to this lengthy post, but am interested in any comments from your experience, which is much greater than mine. Thank you very much.

Bill
 
My quick replies:-

(1) Yes, all the code resides in a sungle XSL stylesheet. I only quoted the relevant snippets.

(2) Yes, the variable does contain the OCC node, but it also contains other nodes, so I have to specify which one to process. The variable contains a tree structure just like the main document, but sorted into a different order.

(3) It copies nodes from the main document into the variable.

(4) The ability to process tree fragments was not built into XSLT version 1.0 (it is/will be in version 2.0) so I have to use one of the available extensions. This code tries to find out which extension is available in the current XSLT processor.
 
Tony,

Thanks very much. I will apply your code and see if it is a faster solution. Mine takes several seconds to do the transformations.

Much regard,
Bill
 
Tony,

Could you please post your complete xsl file? I have done some searching, but am having difficulty working with more than one template in the xsl file.

Thanks very much.

Bill
 
Here it is (for what it's worth):

Code:
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform"[/URL] version="1.0"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                xmlns:saxon="[URL unfurl="true"]http://icl.com/saxon"[/URL]
                exclude-result-prefixes="msxsl saxon"
                xmlns:USITEM="USITEM.DICT"
                xmlns:USMENU="USMENU.DICT">

<xsl:output method='xml'
            indent="yes"
            encoding="ISO-8859-1"
            omit-xml-declaration="yes"
            doctype-public = "-//W3C//DTD HTML 4.01 Transitional//EN"
            doctype-system = "[URL unfurl="true"]http://www.w3.org/TR/html4/loose.dtd"[/URL]
/>

<xsl:template match="/">

  <html>
    <head>
      <STYLE type="text/css">
      <![CDATA[
      <!--
        BODY { margin-left: 2em; margin-right: 2em; }
        tr.item { background-color: #99ff99; }
        td.code { background-color: #dddddd; }
      -->
      ]]>
      </STYLE>
    </head>
  <body>

    <!-- produce an index to all menu bars -->
    <table border="1">
      <thead>
        <tr><th>Library</th><th>Menu Bar</th></tr>
      </thead>

      <tfoot>
        <tr><th colspan="2">end of menu bars</th></tr>
      </tfoot>

      <tbody>
        <xsl:apply-templates select="UNIFACE/TABLE/OCC[USMENU:MENUTYPE='B']" mode="index1">
          <xsl:sort select="USMENU:UVAR"/>
          <xsl:sort select="USMENU:UMENU"/>
        </xsl:apply-templates>
      </tbody>

    </table>

    <!-- produce an index to all menus -->
    <table border="1">
      <thead>
        <tr><th>Library</th><th>Menu Bar</th><th>Title</th><th>Menu</th></tr>
      </thead>

      <tfoot>
        <tr><th colspan="4">end of menus</th></tr>
      </tfoot>

      <tbody>

        <xsl:variable name="SortedItems">
            <xsl:for-each select="UNIFACE/TABLE/OCC[USITEM:UCASCADE]">
                <xsl:sort select="USITEM:UVAR"/>
                <xsl:sort select="USITEM:UMENU"/>
                <xsl:sort select="USITEM:USEQ" data-type="number"/>
                <xsl:copy-of select="."/>
            </xsl:for-each>
        </xsl:variable>

        <xsl:choose>
          <xsl:when test="system-property('xsl:version') >= 1.1">
            <xsl:apply-templates select="$SortedItems/OCC" mode="index2"/>
          </xsl:when>
          
          <xsl:when test="function-available('saxon:node-set')">
            <xsl:apply-templates select="saxon:node-set($SortedItems)/OCC" mode="index2"/>
           </xsl:when>

          <xsl:when test="function-available('msxsl:node-set')">
            <xsl:apply-templates select="msxsl:node-set($SortedItems)/OCC" mode="index2"/>
          </xsl:when>

          <xsl:otherwise>
            <xsl:message terminate="yes">
              ERROR: Processor is unknown or doesn't support node-set extension function.
            </xsl:message>
          </xsl:otherwise>
        </xsl:choose>

      </tbody>
    </table>

    <!-- produce a list of all menu items -->
    <table border="1" width="100%">
      <colgroup width="10"/>
      <colgroup width="50"/>
      <colgroup width="1"/>
      <colgroup width="70%"/>
      <thead>
        <tr><th>Library</th><th>Menu</th><th>#</th><th>Menu Item</th></tr>
      </thead>

      <tfoot>
        <tr><th colspan="4">end of menu items</th></tr>
      </tfoot>

      <tbody>
        <xsl:apply-templates select="UNIFACE/TABLE/OCC[USITEM:UMENU]" mode="body">
          <xsl:sort select="USITEM:UVAR"/>
          <xsl:sort select="USITEM:UMENU"/>
          <xsl:sort select="USITEM:USEQ" data-type="number"/>
        </xsl:apply-templates>
      </tbody>

    </table>

  </body>
  </html>

</xsl:template>

<xsl:template match="OCC" mode="index1">

    <tr>
      <td><xsl:value-of select="USMENU:UVAR"/></td>
      <td><a href="#{USMENU:UMENU}"><xsl:value-of select="USMENU:UMENU"/></a></td>
    </tr>

</xsl:template>

<xsl:template match="OCC" mode="index2">

  <xsl:variable name="position" select="position()" />
  <xsl:variable name="prev">
    <xsl:if test="$position > 1">
      <xsl:value-of select="/OCC[$position -1]/USITEM:UMENU"/>
    </xsl:if>
  </xsl:variable>

    <tr>
      <xsl:choose>
      <xsl:when test="USITEM:UMENU != $prev">
        <td><xsl:value-of select="USITEM:UVAR"/></td>
        <td><a name="{USITEM:UMENU}"><xsl:value-of select="USITEM:UMENU"/></a></td>
      </xsl:when>
      <xsl:otherwise>
        <td>&#160;</td>
        <td>&#160;</td>
      </xsl:otherwise>
    </xsl:choose>
      <td><xsl:value-of select="USITEM:TITLE"/></td>
      <td><a href="#{USITEM:UCASCADE}"><xsl:value-of select="USITEM:UCASCADE"/></a></td>
    </tr>

  <xsl:variable name="cascade" select="USITEM:UCASCADE"/>
  <xsl:for-each select="//OCC[USITEM:UMENU=$cascade]">
    <tr>
      <td>&#160;</td>
      <td>(cascaded menu)</td>
      <td><xsl:value-of select="USITEM:TITLE"/></td>
      <td><a href="#{USITEM:UCASCADE}"><xsl:value-of select="USITEM:UCASCADE"/></a></td>
    </tr>
  </xsl:for-each>

</xsl:template>

<xsl:template match="OCC" mode="body">
   
      <xsl:if test="USITEM:UOPTTRIG">
        <tr class="item">
          <td><xsl:value-of select="USITEM:UVAR"/></td>
          <td><a name="{USITEM:UMENU}"><xsl:value-of select="USITEM:UMENU"/></a></td>
          <td><xsl:value-of select="USITEM:USEQ"/></td>
          <td><xsl:value-of select="USITEM:TITLE"/></td>
        </tr>
        <xsl:if test="USITEM:PRETRIG">
          <tr>
            <td colspan="4">Pre-display Trigger</td>
          </tr>
          <tr>
            <td colspan="4" class="code"><pre><xsl:value-of select="USITEM:PRETRIG"/></pre></td>
          </tr>
        </xsl:if>
        <tr>
          <td colspan="4">Option Trigger</td>
        </tr>
        <tr>
          <td colspan="4" class="code"><pre><xsl:value-of select="USITEM:UOPTTRIG"/></pre></td>
        </tr>

      </xsl:if>

</xsl:template>

</xsl:stylesheet>
 
Tony,

Getting a look at your xsl file helped a great deal. There is some tricky bits in there, at least for me as a beginner, and by working with some of your techniques in a sandbox environment I was finally able to develop a implementation that did what was needed, and was much faster than my original approach.

Thank you very much.

With much regard,
Bill
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top