缺少xslt中for-sibling的for-each循环中的第一个元素

时间:2012-04-10 12:15:05

标签: xml xslt foreach xsl-fo

我在使用XSLT通过属性对xml进行分组时遇到问题。

这是我的源码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>

我想得到的是属性“type”的所有可用值的列表。在这种情况下,它应该是: - 教育 - 社区服务 - 经验

我试着这样:

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

我得到的是: - 社区服务 - 经验

我缺少“教育”(第一个)

我能做些什么来获得它?

请求你的帮助!

格尔茨

戴夫

2 个答案:

答案 0 :(得分:1)

问题在于,尽管您正在创建排序节点列表,但preceding-sibling::(或任何轴)仅可用于表示文档中节点之间的关系(不在节点列表中)。

因此,当前文档中的上下文节点preceding-sibling:: chronEntry [1] selects the first preceding sibling chronEntry` - 不在已排序的节点列表中。

解决方案

  1. 在XSLT 1.0中,捕获变量中xsl:for-each的结果。由于这是臭名昭着的RTF类型,您必须使用正在使用的XSLT 1.0处理器支持的xxx:node-set()扩展函数将其转换为常规树。然后,在这个常规树中,包括preceding-sibling::在内的轴具有所需的含义。

  2. 推荐的解决方案。使用Muenchian分组:

  3. 像这样:

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

    将此转换应用于提供的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>
    

    产生了想要的正确结果

    education communityservice experience 
    

答案 1 :(得分:0)

你真的需要xsl:sort吗?或者你用它来分组?如果是这样,你可以删除xsl:sort并更正你的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>

如果您需要xsl:sort,可以使用:

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