Access attribute from within current-grouping-key() xslt

▼魔方 西西 提交于 2019-12-23 01:15:13

问题


I have some xml with thousands of movie elements which can all have 1 or MORE director and writer elements, if a director or writer has the same name as another, as seen here, I add an @differentiator of birthyear, if they are NOT the same person

<mediaList>
        <movie id="1603934" dateCreated="2014-08-11">
          <title>Night at the Museum</title>
          <director>Shawn Levy</director>
          <LCSpecialTopics>Comedy</LCSpecialTopics>
          <writer>Robert Ben Garant</writer>
          <writer differentiator="1970">Thomas Lennon</writer>
          <language>English</language>
          <year>2006</year>
       </movie>

      <movie lastModified="2014-08-30" id="1123629" dateCreated="2014-08-04">
          <title type="foreign" xml:lang="cmn">Qiúgǎng Wèishì</title>
          <title>Warriors of Qiugang</title>
          <director>Ruby Yang</director>
          <LCSpecialTopics>Documentary films</LCSpecialTopics>
          <writer differentiator="1951">Thomas Lennon</writer>
          <language>Chinese</language>
          <year>2010</year>
       </movie>
</mediaList>

my current XSLT to transform this into a nice readable table of

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:functx="http://www.functx.com" version="2.0">
    <xsl:output method="html" indent="yes"/>

    <xsl:function name="functx:substring-after-last-match" as="xs:string"
        xmlns:functx="http://www.functx.com">
        <xsl:param name="arg" as="xs:string?"/>
        <xsl:param name="regex" as="xs:string"/>

        <xsl:sequence select="
            replace($arg,concat('^.*',$regex),'')
            "/>

    </xsl:function>

    <xsl:template match="mediaList">
        <html>
            <head>
                <link rel="stylesheet" type="text/css" href="CssJs/tableMedia.css"/>
                <title>Media Table</title>
            </head>
            <body>
                <table>
                    <thead>
                        <tr>
                            <th>#</th>
                            <th>Person</th>
                            <th>Movies</th>
                        </tr>
                    </thead>
                    <tbody>
                        <xsl:for-each-group select="movie" group-by="writer | director">
                            <xsl:sort
                                select="functx:substring-after-last-match(current-grouping-key(),'\s')"/>
                            <xsl:sort select="current-grouping-key()"/>
                            <xsl:variable name="person" select="current-grouping-key()"/>

                            <tr>
                                <td>
                                    <xsl:value-of select="position()"/>
                                </td>
                                <td>
                                    <p>
                                        <a id="{current-grouping-key()}">
                                            <xsl:value-of select="$person"/>
                                        </a>
                                    </p>
                                </td>
                                <td>
                                    <xsl:if test="current-group()[writer = $person]">
                                        <h3>Writer</h3>
                                    </xsl:if>
                                    <xsl:apply-templates
                                        select="current-group()[writer = $person]/title[not(@type)]"/>
                                    <xsl:if test="current-group()[director = $person]">
                                        <h3>Director</h3>
                                    </xsl:if>
                                    <xsl:apply-templates
                                        select="current-group()[director = $person]/title[not(@type)]"
                                    />
                                </td>
                            </tr>
                        </xsl:for-each-group>
                    </tbody>
                </table>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="title">
        <p>
            <xsl:value-of select="."/>
            <xsl:apply-templates select="../title[@type]"/>
        </p>
    </xsl:template>

    <xsl:template match="title[@type]">
        <span>
            <xsl:value-of select="concat(' (',.,')')"/>
        </span>
    </xsl:template>            
</xsl:stylesheet>

The problem is that I can't access the @differentiator after the group-by, I receive an error of "Required item type of first operand of '/' is node(); supplied value has item type xs:anyAtomicType. The expression can succeed only if the supplied value is an empty sequence."

from reading similar problems, i think there must be a way to perform this IN the group-by using concat() but because each movie element may have MORE than 1 writer/director I can't simply do

group-bye="concat(writer[@differentiator], writer/@differentiator) | writer | director">

desired output (snippet)would look like:

        <tr>
           <td>Some Number</td>
           <td>
              <p><a id="Thomas Lennon1951">Thomas Lennon (1951)</a></p>
           </td>
           <td>
              <h3>Writer</h3>
              <p>Warriors of Qiugang<span> (Qiúgǎng Wèishì)</span></p>
           </td>
        </tr>
        <tr>
           <td>Some Number</td>
           <td>
              <p><a id="Thomas Lennon1970">Thomas Lennon (1970)</a></p>
           </td>
           <td>
              <h3>Writer</h3>
              <p>Night at the Museum</p>
           </td>
        </tr>

回答1:


Elaborating on the brief answer given by Martin. Concatenate the string value of writer and a potential differentiator attribute and hand it to for-each-group as the "group-by" value.

Then, two variables $person and $year separate the two parts of the grouping key again. This only solves the problem for writer elements, not for director.

As an aside note, usually IDs cannot contain whitespace. If your id attribute is a "true" ID, you should strip any whitespace in the ID value.

Also, use exclude-prefixes="#all" to prevent unused namespaces from appearing in the output HTML.

Stylesheet

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="#all"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:functx="http://www.functx.com" version="2.0">
    <xsl:output method="html" indent="yes"/>

    <xsl:function name="functx:substring-after-last-match" as="xs:string"
        xmlns:functx="http://www.functx.com">
        <xsl:param name="arg" as="xs:string?"/>
        <xsl:param name="regex" as="xs:string"/>

        <xsl:sequence select="
            replace($arg,concat('^.*',$regex),'')
            "/>

    </xsl:function>

    <xsl:template match="mediaList">
        <html>
            <head>
                <link rel="stylesheet" type="text/css" href="CssJs/tableMedia.css"/>
                <title>Media Table</title>
            </head>
            <body>
                <table>
                    <thead>
                        <tr>
                            <th>#</th>
                            <th>Person</th>
                            <th>Movies</th>
                        </tr>
                    </thead>
                    <tbody>
                        <xsl:for-each-group select="movie" group-by="writer/concat(@differentiator, '_', .), director">
                            <xsl:sort
                                select="functx:substring-after-last-match(current-grouping-key(),'\s')"/>
                            <xsl:sort select="current-grouping-key()"/>

                           <xsl:variable name="person" select="if (contains(current-grouping-key(),'_')) then substring-after(current-grouping-key(),'_') else current-grouping-key()"/>
                            <xsl:variable name="year" select="if (contains(current-grouping-key(),'_')) then substring-before(current-grouping-key(),'_') else ''"/>

                            <tr>
                                <td>
                                    <xsl:value-of select="position()"/>
                                </td>
                                <td>
                                    <p>
                                        <a id="{concat(replace($person,' ',''),$year)}">
                                            <xsl:value-of select="if ($year) then concat($person,' (',$year,')') else $person"/>
                                        </a>
                                    </p>
                                </td>
                                <td>
                                    <xsl:if test="current-group()[writer = $person]">
                                        <h3>Writer</h3>
                                    </xsl:if>
                                    <xsl:apply-templates
                                        select="current-group()[writer = $person]/title[not(@type)]"/>
                                    <xsl:if test="current-group()[director = $person]">
                                        <h3>Director</h3>
                                    </xsl:if>
                                    <xsl:apply-templates
                                        select="current-group()[director = $person]/title[not(@type)]"
                                    />
                                </td>
                            </tr>
                        </xsl:for-each-group>
                    </tbody>
                </table>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="title">
        <p>
            <xsl:value-of select="."/>
            <xsl:apply-templates select="../title[@type]"/>
        </p>
    </xsl:template>

    <xsl:template match="title[@type]">
        <span>
            <xsl:value-of select="concat(' (',.,')')"/>
        </span>
    </xsl:template>            
</xsl:stylesheet>

XML Input

I used the following input, with more than one movie written by Thomas Lennon (1970).

<mediaList>
        <movie id="1603934" dateCreated="2014-08-11">
          <title>Night at the Museum</title>
          <director>Shawn Levy</director>
          <LCSpecialTopics>Comedy</LCSpecialTopics>
          <writer>Robert Ben Garant</writer>
          <writer differentiator="1970">Thomas Lennon</writer>
          <language>English</language>
          <year>2006</year>
       </movie>
        <movie id="1603934" dateCreated="2014-08-11">
          <title>Second movie</title>
          <director>Frodo Baggins</director>
          <LCSpecialTopics>Comedy</LCSpecialTopics>
          <writer differentiator="1970">Thomas Lennon</writer>
          <language>English</language>
          <year>2006</year>
       </movie>

      <movie lastModified="2014-08-30" id="1123629" dateCreated="2014-08-04">
          <title type="foreign" xml:lang="cmn">Qiúgǎng Wèishì</title>
          <title>Warriors of Qiugang</title>
          <director>Ruby Yang</director>
          <LCSpecialTopics>Documentary films</LCSpecialTopics>
          <writer differentiator="1951">Thomas Lennon</writer>
          <language>Chinese</language>
          <year>2010</year>
       </movie>
</mediaList>

XML Output

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <link rel="stylesheet" type="text/css" href="CssJs/tableMedia.css">
      <title>Media Table</title>
   </head>
   <body>
      <table>
         <thead>
            <tr>
               <th>#</th>
               <th>Person</th>
               <th>Movies</th>
            </tr>
         </thead>
         <tbody>
            <tr>
               <td>1</td>
               <td>
                  <p><a id="FrodoBaggins">Frodo Baggins</a></p>
               </td>
               <td>
                  <h3>Director</h3>
                  <p>Second movie</p>
               </td>
            </tr>
            <tr>
               <td>2</td>
               <td>
                  <p><a id="RobertBenGarant">Robert Ben Garant</a></p>
               </td>
               <td>
                  <h3>Writer</h3>
                  <p>Night at the Museum</p>
               </td>
            </tr>
            <tr>
               <td>3</td>
               <td>
                  <p><a id="ThomasLennon1951">Thomas Lennon (1951)</a></p>
               </td>
               <td>
                  <h3>Writer</h3>
                  <p>Warriors of Qiugang<span> (Qiúgǎng Wèishì)</span></p>
               </td>
            </tr>
            <tr>
               <td>4</td>
               <td>
                  <p><a id="ThomasLennon1970">Thomas Lennon (1970)</a></p>
               </td>
               <td>
                  <h3>Writer</h3>
                  <p>Night at the Museum</p>
                  <p>Second movie</p>
               </td>
            </tr>
            <tr>
               <td>5</td>
               <td>
                  <p><a id="ShawnLevy">Shawn Levy</a></p>
               </td>
               <td>
                  <h3>Director</h3>
                  <p>Night at the Museum</p>
               </td>
            </tr>
            <tr>
               <td>6</td>
               <td>
                  <p><a id="RubyYang">Ruby Yang</a></p>
               </td>
               <td>
                  <h3>Director</h3>
                  <p>Warriors of Qiugang<span> (Qiúgǎng Wèishì)</span></p>
               </td>
            </tr>
         </tbody>
      </table>
   </body>
</html>

EDIT As a response to your comment:

What IS that comma doing though in the group-by rather than a |?

That's an excellent follow-up question! The | operator returns the union of two sets of nodes, with duplicates removed and in document order. Its operands must necessarily be sets of nodes. But the first part of your group-by value is not a set of nodes, it's just a string (the concat() function returns a string). On the other hand, the operands of the comma operator , can be any kind of sequences, also ones of type xs:string.

In short, you have to use , rather than | because of the kinds of operands those operators accept. It has no bearing on the grouping whatsoever. When all sequences that contribute to the grouping key are sets of nodes, the two operators can be used interchangeably, save for potential changes in ordering.




回答2:


You can use group-by="writer/concat(@differentiator, '+', .), director".



来源:https://stackoverflow.com/questions/25585987/access-attribute-from-within-current-grouping-key-xslt

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!