Individual values ​​with XSLT 1.0 when XPath has multiple criteria

Another question about getting different values ​​using XSLT 1.0. Here is a stupid, compiled example that should illustrate my problem.

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

Now let me say that I would like to list all the years when films were created that are either comedies or director B. I use the following stylesheet:

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

This gives me the following result:

year1994year1994year1995

I have not yet found a solution to get the various values ​​that will work here. For example, an name(.) != name(following-sibling::*)exception is thrown when used year1994.

In my real case, I have a complex XML and XPath structure with many criteria that select multiple nodes from which I need to get the output of different node names.

: michael.hor257k , xsl:. :

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

, , , . :

<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

​​ Muenchian grouping - , , .

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

:

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

, xpath-. , , - XSLT ( ).

for-each , XPATH /moviesByYear/*[name(.) != name(following-sibling::*)] , node -set 1 . , , .

We use this name in the parameter for the named template foo, which uses this name to select all the relevant elements, but only that year, and then select 1 of them /moviesByYear/*[name(.) = $year][movie/genre='Comedy' or movie/director='B'][1]. If we find 1 matching element, we print the name.

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

This template, when launched against your test date, displays the result:

year1994year1995
+1
source

Source: https://habr.com/ru/post/1544476/


All Articles