XPath 1.0 select distinct attribute of siblings

你说的曾经没有我的故事 提交于 2019-12-24 03:24:24

问题


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>

回答1:


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



回答2:


This short (13 lines) and simple transformation -- just one template, no hardcoded strings:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text" />
 <xsl:strip-space elements="*"/>

 <xsl:template match="t/*[*/@ATTR3]">
     <xsl:value-of select="concat(name(), ':')"/>
     <xsl:for-each select="*/@ATTR3[not(. = ../preceding-sibling::*/@ATTR3)]">
       <xsl:value-of select="concat(' ', .)"/>
     </xsl:for-each>
     <xsl:text> </xsl:text>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML (wrapped into a single top element to become a well-formed) document:

<t>
    <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>
</t>

produces the wanted, correct result:

PANELS: 31 ZONES: 31 


来源:https://stackoverflow.com/questions/11363678/xpath-1-0-select-distinct-attribute-of-siblings

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!