XPath有多个条件时,XSLT 1.0的值不同

时间:2014-06-13 12:32:49

标签: xslt xpath xslt-1.0 distinct xpath-1.0

关于使用XSLT 1.0获取不同值的另一个问题。这是一个愚蠢的,成熟的例子,应该说明我的问题。

<?xml version="1.0" encoding="UTF-8"?>
<moviesByYear>
    <year1994>
        <movie>
            <genre>Action</genre>
            <director>A</director>
        </movie>
    </year1994>
    <year1994>
        <movie>
            <genre>Comedy</genre>
            <director>A</director>
        </movie>
    </year1994>
    <year1994>
        <movie>
            <genre>Drama</genre>
            <director>B</director>
        </movie>
    </year1994>
    <year1994>
        <movie>
            <genre>Thriller</genre>
            <director>C</director>
        </movie>
    </year1994>
    <year1995>
        <movie>
            <genre>Action</genre>
            <director>A</director>
        </movie>
    </year1995>
    <year1995>
        <movie>
            <genre>Comedy</genre>
            <director>C</director>
        </movie>
    </year1995>
    <year1996>
        <movie>
            <genre>Thriller</genre>
            <director>A</director>
        </movie>
    </year1996>
</moviesByYear>

现在让我们说我想列出制作喜剧或导演B导演的电影的所有年份。我使用以下样式表:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
    <xsl:output method="text" encoding="UTF-8" indent="no"/>
    <xsl:template match="/">
        <xsl:for-each select="/moviesByYear/*[movie/genre='Comedy' or movie/director='B']">
            <xsl:value-of select="name()"/>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

这给了我以下输出:

year1994year1994year1995

我还没有找到任何解决方案来获取可在此处使用的不同值。例如,使用name(.) != name(following-sibling::*)会导致year1994完全被排除。

在我的实际案例中,我有一个复杂的XML结构和一个带有许多标准的XPath,它们选择了许多节点,我需要从中获得不同节点名称的输出。

更新: michael.hor257k为此提供了一个优雅的解决方案,但使用它我遇到了xsl:key的问题。请允许我稍微改变一下情景:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <genres>
        <genre>Action</genre>
        <genre>Comedy</genre>
        <genre>Drama</genre>
        <genre>Thriller</genre>
    </genres>
    <moviesByYear>
        <year1994>
            <movie>
                <genre>Action</genre>
                <director>A</director>
            </movie>
        </year1994>
        <year1994>
            <movie>
                <genre>Comedy</genre>
                <director>A</director>
            </movie>
        </year1994>
        <year1994>
            <movie>
                <genre>Drama</genre>
                <director>B</director>
            </movie>
        </year1994>
        <year1994>
            <movie>
                <genre>Thriller</genre>
                <director>C</director>
            </movie>
        </year1994>
        <year1995>
            <movie>
                <genre>Action</genre>
                <director>A</director>
            </movie>
        </year1995>
        <year1995>
            <movie>
                <genre>Comedy</genre>
                <director>C</director>
            </movie>
        </year1995>
        <year1996>
            <movie>
                <genre>Thriller</genre>
                <director>A</director>
            </movie>
        </year1996>
    </moviesByYear>
</root>

现在让我们说我想要一个类型列表,每个类型列出制作该类型的电影的年份或由导演B.导演的电影。样式表:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="urn:schemas-microsoft-com:xslt"
extension-element-prefixes="exsl">
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>

<xsl:template match="/">
    <xsl:for-each select="/root/genres/genre">
        <xsl:call-template name="output">
            <xsl:with-param name="genre">
                <xsl:value-of select="."/>
            </xsl:with-param>
        </xsl:call-template>
    </xsl:for-each>
</xsl:template>

<xsl:param name="director" select="'B'"/>

<xsl:key name="year" match="year" use="." />

<xsl:template name="output">
    <xsl:param name="genre"/>

    <!-- first pass -->
    <xsl:variable name="years">
        <xsl:for-each select="/root/moviesByYear/*/movie[genre=$genre or director=$director]"> 
            <year><xsl:value-of select="local-name(..)"/></year>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="years-set" select="exsl:node-set($years)" />

    <!-- final pass -->
    <xsl:value-of select="concat($genre, ': ')"/> 
    <xsl:for-each select="$years-set/year[count(. | key('year', .)[1]) = 1]">
        <xsl:value-of select="."/>
    </xsl:for-each>
    <xsl:text>&#10;</xsl:text>

</xsl:template>

</xsl:stylesheet>

这会产生以下输出:

Action: year1994year1995
Comedy: 
Drama: 
Thriller: year1996

如您所见,每年只列出一次。期望的输出将是:

Action: year1994year1995
Comedy: year1994year1995
Drama: year1994
Thriller: year1994year1996

5 个答案:

答案 0 :(得分:3)

使用http://www.jenitennison.com/xslt/grouping/muenchian.xml

<xsl:key name="k1" match="moviesByYear/*[movie/genre='Comedy' or movie/director='B']" use="local-name()"/>

    <xsl:template match="/">
        <xsl:for-each select="/moviesByYear/*[movie/genre='Comedy' or movie/director='B'][generate-id() = generate-id(key('k1', local-name())[1])]">
            <xsl:if test="position() > 1"><xsl:text> </xsl:text></xsl:if>
            <xsl:value-of select="name()"/>
        </xsl:for-each>
    </xsl:template>

答案 1 :(得分:3)

这是Muenchian分组的不同实现 - 允许您参数化选择电影的标准。

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:param name="genre" select="'Comedy'"/>
<xsl:param name="director" select="'B'"/>

<xsl:key name="year" match="year" use="." />

<xsl:template match="/">

    <!-- first pass -->
    <xsl:variable name="years">
        <xsl:for-each select="moviesByYear/*/movie[genre=$genre or director=$director]"> 
            <year><xsl:value-of select="local-name(..)"/></year>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="years-set" select="exsl:node-set($years)" />

    <!-- final pass -->
    <output>
        <xsl:for-each select="$years-set/year[count(. | key('year', .)[1]) = 1]">
            <xsl:copy-of select="."/>
        </xsl:for-each>
    </output>

</xsl:template>

</xsl:stylesheet>

当上述内容应用于您的示例输入时,结果为:

<?xml version="1.0" encoding="UTF-8"?>
<output>
   <year>year1994</year>
   <year>year1995</year>
</output>

编辑:

关于你修改过的输入,我相信我会这样做:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:param name="director" select="'B'"/>

<xsl:key name="movies-by-genre" match="movie" use="genre" />
<xsl:key name="movies-by-director" match="movie" use="director" />
<xsl:key name="year" match="year" use="." />

<xsl:template match="/">
    <output>
        <xsl:apply-templates select="root/genres/genre"/>
    </output>
</xsl:template>

<xsl:template match="genre">
    <!-- first pass -->
    <xsl:variable name="years">
        <xsl:for-each select="key('movies-by-genre', .) | key('movies-by-director', $director)"> 
            <year><xsl:value-of select="local-name(..)"/></year>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="years-set" select="exsl:node-set($years)" />
    <!-- final pass -->
    <genre name="{.}">
        <xsl:for-each select="$years-set/year[count(. | key('year', .)[1]) = 1]">
            <xsl:copy-of select="."/>
        </xsl:for-each>
    </genre>
</xsl:template>

</xsl:stylesheet>

结果如下:

<?xml version="1.0" encoding="UTF-8"?>
<output>
   <genre name="Action">
      <year>year1994</year>
      <year>year1995</year>
   </genre>
   <genre name="Comedy">
      <year>year1994</year>
      <year>year1995</year>
   </genre>
   <genre name="Drama">
      <year>year1994</year>
   </genre>
   <genre name="Thriller">
      <year>year1994</year>
      <year>year1996</year>
   </genre>
</output>

注意:这两个添加的密钥仅用于提高效率 - 这里主要用途不需要它们。


编辑2:

第二个想法,我们可以一次完成所有这些,因此(希望)避免Xalan和MSXSML处理变量的问题 - 但仍然使用Muenchian分组:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:param name="director" select="'B'"/>

<xsl:key name="year" match="moviesByYear/*" use="local-name()" />

<xsl:template match="/">
    <output>
        <xsl:apply-templates select="root/genres/genre"/>
    </output>
</xsl:template>

<xsl:template match="genre">
    <xsl:variable name="genre" select="." />
    <genre name="{$genre}">
        <xsl:for-each select="../../moviesByYear/* 
        [count(. | key('year', local-name())[1]) = 1]
        [key('year', local-name())/movie[genre=$genre or director=$director]]">
            <year>
                <xsl:value-of select="local-name()"/>
            </year>  
        </xsl:for-each>
    </genre>
</xsl:template>

</xsl:stylesheet>

答案 2 :(得分:3)

现在,仅仅因为有人说它无法完成,这里是一个XPath单行:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:param name="genre" select="'Comedy'"/>
<xsl:param name="director" select="'B'"/>

<xsl:template match="/">
    <output>
        <xsl:for-each select="//movie[genre=$genre or director=$director] 
        [not(local-name(..)=local-name(preceding::movie[genre=$genre or director=$director]/parent::*))]">
            <year>       
                <xsl:value-of select="local-name(..)"/>
            </year>  
        </xsl:for-each>
    </output>
</xsl:template>

</xsl:stylesheet>

不建议提高效率。

答案 3 :(得分:2)

这可能对您有用:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
    <xsl:for-each select="moviesByYear/*[movie/genre='Comedy' or movie/director='B'][not(name(.) = following-sibling::*[movie/genre='Comedy' or movie/director='B']/name(.))]">
        <xsl:value-of select="name()"/>
    </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

答案 4 :(得分:1)

我认为有一种方法可以用直接的xpath表达式做到这一点。我能看到的唯一方法是使用XSLT变量(或者在我的示例中使用模板参数)。

此代码正在执行的操作是在主模板中的for-each循环中,XPATH /moviesByYear/*[name(.) != name(following-sibling::*)]选择每个子年份的最后一个实例,以便节点集每年只包含1个元素。我们不关心这个元素是否符合我们的实际标准,我们只关心它的名称。

我们将参数中的名称填充到指定的foo模板中,该模板使用该名称选择所有匹配的元素,但仅选择那一年,然后选择第一个/moviesByYear/*[name(.) = $year][movie/genre='Comedy' or movie/director='B'][1]。如果我们找到1个匹配元素,我们输出名称。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
    <xsl:output method="text" encoding="UTF-8" indent="no"/>

<xsl:template match="/">
  <xsl:for-each select="/moviesByYear/*[name(.) != name(following-sibling::*)]">
    <xsl:call-template name="foo">
      <xsl:with-param name="year" select="name(.)"/>
    </xsl:call-template>
  </xsl:for-each>
</xsl:template>

<xsl:template name="foo">
 <xsl:param name="year" select="'year1994'" />
 <xsl:for-each select="/moviesByYear/*[name(.) = $year][movie/genre='Comedy' or movie/director='B'][1]">
            <xsl:value-of select="name(.)"/>
        </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

此模板在您的测试日期运行时会产生输出:

year1994year1995