Search code examples
xsltxpath-1.0

XPath 1.0 select distinct attribute of siblings


I have hunted around, but haven't been able to get any of the ideas I've found to work.

These are a couple of nodes I have in an xml file (that is generated from a db)

<PANELS>        
<PANEL ATTR1="7"  ATTR2="37" ATTR3="31"/>
<PANEL ATTR1="8"  ATTR2="37" ATTR3="31"/>
<PANEL ATTR1="8A" ATTR2="37" ATTR3="31"/>
</PANELS>
<ZONES>
<ZONE ATTR1="7"  ATTR2="37" ATTR3="31" />
<ZONE ATTR1="8"  ATTR2="37" ATTR3="31" />
<ZONE ATTR1="8A" ATTR2="37" ATTR3="31" />
</ZONES>

I want to be able to select the distinct ATTR3 from each of these.

Currently, this works for the first one //PANELS/PANEL[not(@ATTR3 = (preceding::*/@ATTR3))] and returns the expected result for '31'

But when I try to do the same for the second one, it returns nothing (I want it to return '31' again) //ZONES/ZONE[not(@ATTR3 = (preceding::*/@ATTR3))]

I understand that the second one is not working because the value of ATTR3 is the same for all of them, but how do I get the distinct attribute value per node?

(This is being used as the predicate for a for-each that I am using to display each distinct value)

This is being used like this, one of these for-each for ZONES and one for PANELS

<xsl:for-each select="//PANELS/PANEL[not(@ATTR3 = (preceding::*/@ATTR3))]">
<xsl:sort select="@ATTR3"/>
<xsl:value-of select="@ATTR3" />
<xsl:if test="position()!=last()">, </xsl:if>
</xsl:for-each>

I would like it to return

PANELS: 31

ZONES: 31

I have tried using preceding-sibling instead of preceding, but then I get

PANELS: 31, 31

ZONES: 31

Each one is in a template like this:

    <xsl:template match="//HEADER/ZONES" >              
    <fo:block font-size="10pt">
        <fo:table  table-layout="fixed" > 
            <fo:table-column column-width="proportional-column-width(1)"/>
            <fo:table-column column-width="proportional-column-width(7)"/>
            <fo:table-body>
                <fo:table-row>
                    <fo:table-cell  border-bottom="none">
                        <fo:block font-weight="bold">
                            <xsl:text>Zones:</xsl:text>
                        </fo:block>
                    </fo:table-cell >                       
                    <fo:table-cell>
                        <fo:block>
                            <xsl:for-each select="//HEADER/ZONES/ZONE[not(@ATTR3 = (preceding-sibling::*/@ATTR3))]">
                                <xsl:sort select="@ATTR3"/>
                                <xsl:value-of select="@ATTR3" />
                                <xsl:if test="position()!=last()">, </xsl:if>
                            </xsl:for-each>
                        </fo:block>
                    </fo:table-cell>
                </fo:table-row>             
            </fo:table-body>
        </fo:table>
    </fo:block>
    </xsl:template>

Solution

  • The following XSLT uses preceding-siblings instead of preceding and thus produces the correct number of repetitions of the 31:

    <?xml version="1.0" encoding="UTF-8"?>
        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
        <xsl:output method="text"/>
    
        <xsl:template match="//HEADER/ZONES">
            ZONES:
            <xsl:for-each select="//ZONES/ZONE[not(@ATTR3 = (preceding-sibling::*/@ATTR3))]">
                <xsl:sort select="@ATTR3"/>
                <xsl:value-of select="@ATTR3" />
                <xsl:if test="position()!=last()">, </xsl:if>
            </xsl:for-each>
        </xsl:template>
    
        <xsl:template match="//HEADER/PANELS">
            PANELS:
            <xsl:for-each select="//PANELS/PANEL[not(@ATTR3 = (preceding-sibling::*/@ATTR3))]">
                <xsl:sort select="@ATTR3"/>
                <xsl:value-of select="@ATTR3" />
                <xsl:if test="position()!=last()">, </xsl:if>
            </xsl:for-each>
        </xsl:template>
    
    </xsl:stylesheet>
    

    The output for this document:

    <HEADER>
        <PANELS>
            <PANEL ATTR1="7"  ATTR2="37" ATTR3="31"/>
            <PANEL ATTR1="8"  ATTR2="37" ATTR3="31"/>
            <PANEL ATTR1="8A" ATTR2="37" ATTR3="31"/>
        </PANELS>
        <ZONES>
            <ZONE ATTR1="7"  ATTR2="37" ATTR3="31" />
            <ZONE ATTR1="8"  ATTR2="37" ATTR3="31" />
            <ZONE ATTR1="8A" ATTR2="37" ATTR3="31" />
        </ZONES>
    </HEADER>
    

    is as follows:

        ZONES:
        31
        PANELS:
        31