XSLT preceding-sibling limitation and workaround

10,228

As described in the W3C specification:

the preceding-sibling axis contains all the preceding siblings of the context node

What it does not say is that it works in reverse! You need to add a [position()=1] predicate to the end of your XPath select like this:

<?xml version="1.0" encoding="UTF-8"?>
<x:stylesheet version="1.0"
  xmlns:x="http://www.w3.org/1999/XSL/Transform"
  exclude-result-prefixes="x">
<x:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>   
<x:template match="CMNUALGPARENTCHILD">
  <table>
    <x:for-each select="UIDPARENT">
      <tr>
        <td>current <x:value-of select="."/></td>
        <td>previous <x:value-of select="preceding-sibling::UIDPARENT[position()=1]"/></td>
        <td>next <x:value-of select="following-sibling::UIDPARENT[position()=1]"/></td>
      </tr>
    </x:for-each>
 </table>
</x:template>
</x:stylesheet>

I couldn't get the XSL in your question to work. But this works with the XML in your question for me.

Edit: Adding in XML from original question for reference

<CMNUALGPARENTCHILD>
    <UIDPARENT>21</UIDPARENT>
    <UIDPARENT>21</UIDPARENT>
    <UIDPARENT>21</UIDPARENT>
    <UIDPARENT>81</UIDPARENT>
    <UIDPARENT>81</UIDPARENT>
    <UIDPARENT>81</UIDPARENT>
</CMNUALGPARENTCHILD>
Share:
10,228
nacho
Author by

nacho

Updated on June 04, 2022

Comments

  • nacho
    nacho about 2 years

    I'm having problem with preceding-sibling. Is this really working?

    XSL looks something like this:

    <stored-procedure id="search-algorithm-parent-child">
        <xsl:stylesheet version="1.0" xmlns:spbc="urn:lsapps.spbcontext" xmlns:user="http://www.lodestarcorp.com" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
            <xsl:output omit-xml-declaration="yes"/>
            <xsl:template match="node()|/|@*">
                <query name="CMNUALGPARENTCHILD">
                    <sql>
                        SELECT 
                          parent.UIDALGLIBPARENTDTL as UIDPARENT,
                          parent.PARENTCHANNELID as VMCHANNELNUMBER, 
                          parent.UOMCODE as UOM, 
                          child.CHILDDCFLOW AS DCFLOW,
                        FROM
                          ##Q.NUALGLIBPARENTDTL parent,
                          ##Q.NUALGLIBCHILDDTL child 
                        WHERE 
                          child.UIDALGLIBPARENTDTL = parent.UIDALGLIBPARENTDTL
                          AND parent.UIDALGLIBRARY = '<xsl:value-of select="@UIDALGLIBRARY"/>'
                          ORDER BY UIDPARENT
                    </sql>
                </query>
            </xsl:template>
        </xsl:stylesheet>
    </stored-procedure>
    

    I dont't know how XML looks like when I pass above XSL to LsdbCommand in my asp like this:

    var xmlTable5 = LsdbCommand("LSDB.search-algorithm-parent-child", PrefixParams1.valueOf());
    

    I'm assuming that XML data will be something like this (ordered by UIDPARENT):

    <CMNUALGPARENTCHILD>
      <someNode>
        <UIDPARENT>21</UIDPARENT>
        <VMCHANNELNUMBER>123</VMCHANNELNUMBER>
        <UOM>5<UOM>
        <DCFLOW>R<DCFLOW>
      </someNode>
      <someNode>
        <UIDPARENT>21</UIDPARENT>
        <VMCHANNELNUMBER>123</VMCHANNELNUMBER>
        <UOM>5<UOM>
        <DCFLOW>R<DCFLOW>
      </someNode>
      ...
    </CMNUALGPARENTCHILD>
    

    For now, my concern is the UIDPARENT.. What I'm sure of is that result would have the following UIDPARENT..(in order)

    21
    21
    21
    81
    81
    81
    

    Now, in the asp, I call this

    tTable5 = ProcessXsl(xmlTable5, XmlFreeFile("../zAlgorithmLibrary/AlgorithmParentChild.xslt"));
    

    where AlgorithmParentChild.xslt is simply

    <x:stylesheet version="1.0"
      xmlns:x="http://www.w3.org/1999/XSL/Transform"
      xmlns:i="urn:ls-i18n-formatter"
      xmlns:ms="urn:schemas-microsoft-com:xslt"
      xmlns:user="http://www.lodestarcorp.com/user"
      exclude-result-prefixes="x i ms user">
    
    <x:param name="Portal">N</x:param>
    <x:param name="Include">ALL</x:param>
    <x:param name="OrderParams"></x:param>
    <x:param name="FromParam"></x:param>
    <x:param name="ROWPERPAGE">25</x:param>
    <x:param name="AllowEdit"></x:param>
    
    <x:output method="html" version="1.0" encoding="UTF-8" indent="yes"/> 
    <x:template match="/">
      <table class="FormTable">
        <x:for-each select="//CMNUALGPARENTCHILD">
          <tr>
            <td>current <x:value-of select="@UIDPARENT"/></td>
            <td>previous <x:value-of select="preceding-sibling::node()/@UIDPARENT"/></td> 
            <td>next <x:value-of select="following-sibling::node()/@UIDPARENT"/></td>
          </tr>
        </x:for-each>
      </table>
    </x:template>
    
    <x:include href="../controls/MultiSortImages.xslt"/>
    <x:include href="../controls/LockedColumns.xslt"/>
    </x:stylesheet>
    

    main table looks like this

    current 21 previous  next 21 
    current 21 previous 21 next 21 
    current 21 previous 21 next 81 
    current 81 previous 21 next 81 
    current 81 previous 21 next 81 
    current 81 previous 21 next  
    

    It seems correct on the first three result.. but how come preceding-element failed after the third iteration? It should return the following:

    current 21 previous  next 21 
    current 21 previous 21 next 21 
    current 21 previous 21 next 81 
    current 81 previous 21 next 81 
    current 81 previous 81 next 81 
    current 81 previous 81 next  
    

    following-sibling works fine.. are there any known limitations of preceding-sibling? and can you suggest any workaround?

    Note that I have also tried appending [position=1] or [position()] or simply [1] on preceding-sibling::node()/@UIDPARENT but it still have the same result..

    Please understand that I am new to ASP, XSLT, XSL.. and that I have only put snippets of code and not the entire code..

    I just want to understand how preceding-sibling works.. its syntax, everything about it..

  • Wayne
    Wayne about 13 years
    preceding-sibling::UIDPARENT[position()=1] or, more simply, preceding-sibling::UIDPARENT[1]
  • andyb
    andyb about 13 years
    @lwburk +1 yes that is cleaner. I was trying to explain the meaning behind the predicate as well :-)
  • nacho
    nacho about 13 years
    i have edited the question..please check.. thanks @andyb for your response.. but It still doesn't solve the problem.. please explain further..
  • andyb
    andyb about 13 years
    Please look at my XPath with more care. Your code is trying to select @UIDPARENT which means the attribute with name = UIDPARENT. In your XML the node is called UIDPARENT so you need to remove the @ symbol. My XPath works with the XML in the original question. You can't expect to edit the question and have an existing answer magically work.
  • nacho
    nacho about 13 years
    thanks again @andyb.. so the assumed XML output is incorect then.. i'm sorry, but i really don't know how the XML lokks like exactly.. I still would use @ since my XSLT only works with that.. and outputs NULL otherwise.. i'm now assuming that all the fields in the XSL query are all attributes and not node.. going back to preceding-sibling, could you further explain why following-sibling works and preceding-sibling does not (with or without position()=1)? It seemed to only point at the first node..
  • nacho
    nacho about 13 years
    I finally get it.. i used "preceding-sibling::node()[position=1]/@UIDPARENT"
  • andyb
    andyb about 13 years
    Your XPath is different to mine! Your XPath is selecting all the CMNUALGPARENTCHILD many times with the line <x:for-each select="//CMNUALGPARENTCHILD"> whereas mine matches the CMNUALGPARENTCHILD once with <x:template match="CMNUALGPARENTCHILD"> and then for-each UIDPARENT with <x:for-each select="UIDPARENT">. My XPath reads better and should perform better since //CMNUALGPARENTCHILD will make the entire document be loaded and parsed each time.
  • andyb
    andyb about 13 years
    There is no way that the code in your question produces the output you claim it does. UIDPARENT is a node name and @UIDPARENT is an attribute name. Your XML lists <UIDPARENT> nodes, not attributes.
  • Admin
    Admin about 13 years
    @andyb: You wrote "What it does not say is that it works in reverse!" Well, it doesn't. position() refers to proximity in the axis direction (something that is well declare in the spec)