简体   繁体   中英

missing first element in for-each loop with preceding-sibling in xslt

I have a Problem in grouping xml entrys via attributes using XSLT.

Here is my source xml:

<chron>
<chronEntry type="education" order="1" blockorder="1">
    <foo>bar</foo>
</chronEntry>
    <chronEntry type="education" order="2" blockorder="1">
<foo>bar</foo>
    </chronEntry>
<chronEntry type="education" order="3" blockorder="1">
    <foo>bar</foo>
</chronEntry>
<chronEntry type="communityservice" order="1" blockorder="2">
    <foo>bar</foo>
</chronEntry>
<chronEntry type="experience" order="1" blockorder="3">
    <foo>bar</foo>
</chronEntry>
<chronEntry type="experience" order="2" blockorder="3">
    <foo>bar</foo>
</chronEntry>
<chronEntry type="experience" order="3" blockorder="3">
    <foo>bar</foo>
</chronEntry>
<chronEntry type="experience" order="4" blockorder="3">
    <foo>bar</foo>
</chronEntry>
</chron>

What i want to get is a list of all available values of the attribute "type". In this case it should be: - education - communityservice - experience

I tryed it like this:

<xsl:for-each select="/foobar/chron/chronEntry">
            <xsl:sort select="@blockorder"/>
                <xsl:if test ="@blockorder != preceding-sibling::chronEntry[1]/@blockorder">
                    <fo:table-row>
                        <fo:table-cell>
                            <fo:block><xsl:value-of select="@type"/></fo:block>
                        </fo:table-cell>
                    </fo:table-row>
                </xsl:if>
            </xsl:for-each>

what I get is: - communityservice - experience

I'm missing "education" (the first one)

What can I do to get it?

Thaks for your help!

Greetz

Dave

The problem is that although you are creating a sorted node-list, the preceding-sibling:: (or any axis) only can be used to express relations between nodes in a document (not in a node-list).

Therefore, preceding-sibling:: chronEntry[1] selects the first preceding sibling chronEntry` of the context node in the current document -- not in the sorted node-list.

Solution :

  1. In XSLT 1.0 capture the result of the xsl:for-each in a variable. As this is of the infamous RTF type, you have to convert it to a regular tree, using an xxx:node-set() extension function supported by the XSLT 1.0 processor in use. Then, within this regular tree, the axes, including preceding-sibling:: , have the wanted meaning.

  2. Recommended solution. Use Muenchian grouping:

like this:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kType" match="@type" use="."/>

 <xsl:template match=
  "chronEntry
    [generate-id(@type)
    =
     generate-id(key('kType', @type)[1])
    ]">
     <xsl:value-of select="concat(@type, ' ')"/>
 </xsl:template>
 <xsl:template match="text()"/>
</xsl:stylesheet>

when this transformation is applied on the provided XML document :

<chron>
    <chronEntry type="education" order="1" blockorder="1">
        <foo>bar</foo>
    </chronEntry>
    <chronEntry type="education" order="2" blockorder="1">
        <foo>bar</foo>
    </chronEntry>
    <chronEntry type="education" order="3" blockorder="1">
        <foo>bar</foo>
    </chronEntry>
    <chronEntry type="communityservice" order="1" blockorder="2">
        <foo>bar</foo>
    </chronEntry>
    <chronEntry type="experience" order="1" blockorder="3">
        <foo>bar</foo>
    </chronEntry>
    <chronEntry type="experience" order="2" blockorder="3">
        <foo>bar</foo>
    </chronEntry>
    <chronEntry type="experience" order="3" blockorder="3">
        <foo>bar</foo>
    </chronEntry>
    <chronEntry type="experience" order="4" blockorder="3">
        <foo>bar</foo>
    </chronEntry>
</chron>

the wanted, correct result is produced :

education communityservice experience 

Do you realy need the xsl:sort? or did you use it for grouping? if it is so, you can just delete the xsl:sort and correct your xsl:if-test:

        <xsl:for-each select="/foobar/chron/chronEntry">
            <xsl:if test ="not(@blockorder = preceding-sibling::chronEntry/@blockorder)">
                <fo:table-row>
                    <fo:table-cell>
                        <fo:block><xsl:value-of select="@type"/></fo:block>
                    </fo:table-cell>
                </fo:table-row>
            </xsl:if>
        </xsl:for-each>

If you need the xsl:sort, you can use this:

        <xsl:variable name="types" select="/foobar/chron/chronEntry[not(preceding-sibling::*/@blockorder=@blockorder)]"/>
        <xsl:for-each select="$types">
            <xsl:sort select="@blockorder"/>
            <fo:table-row>
                <fo:table-cell>
                    <fo:block>
                        <xsl:value-of select="@type"/>
                    </fo:block>
                </fo:table-cell>
            </fo:table-row>
        </xsl:for-each>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM