关于使用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> </xsl:text>
</xsl:template>
</xsl:stylesheet>
这会产生以下输出:
Action: year1994year1995
Comedy:
Drama:
Thriller: year1996
如您所见,每年只列出一次。期望的输出将是:
Action: year1994year1995
Comedy: year1994year1995
Drama: year1994
Thriller: year1994year1996
答案 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>
注意:这两个添加的密钥仅用于提高效率 - 这里主要用途不需要它们。
第二个想法,我们可以一次完成所有这些,因此(希望)避免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