4

还有一个关于使用 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
4

5 回答 5

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>
于 2014-06-13T13:12:48.677 回答
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>
于 2014-06-14T02:59:54.600 回答
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>

不推荐效率。

于 2014-06-14T04:31:14.237 回答
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>
于 2014-06-13T13:05:32.853 回答
1

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

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

我们将该名称填充到命名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
于 2014-06-14T00:44:06.897 回答