Shed Simas
Shed Simas

Reputation: 15

XSL key matching one or more child nodes

I have a complex XML file structured by book title. Something like this, but with hundreds of books and sometimes many authors per book.

<Book>
    <Title>Ken Lum</Title>
    <Author>
        <GivenName>Grant</GivenName>
        <Surname>Arnold</Surname>
    </Author>
</Book>
<Book>
    <Title>Shore, Forest and Beyond</Title>
    <Author>
        <GivenName>Ian M.</GivenName>
        <Surname>Thom</Surname>
    </Author>
    <Author>
        <GivenName>Grant</GivenName>
        <Surname>Arnold</Surname>
    </Author>
</Book>

What I need to output is an alphabetized list of authors, and then a list of every book they worked on, also alphabetized, something like:

Arnold, Grant — Ken Lum; Shore, Forest and Beyond

Thom, Ian M. — Shore, Forest and Beyond

I have a version of the code working fairly well, but it is very slow, so I'm trying to optimize my approach. I recently learned of the Muenchian method of grouping from another user here and I'm trying to apply that.

The part I'm specifically stuck on right now is getting the list of titles per author. This is what I have right now:

<xsl:key name="books-by-author" match="Book"
         use="concat(Author/GivenName, Contributor/Surname)" />
…
<xsl:template match="Author">
    …
    <xsl:apply-templates mode="ByAuthor" select=
                                "key('books-by-author',
                                     concat(GivenName, Surname)
                                     )">
        <xsl:sort select="Title/TitleText"/>
    </xsl:apply-templates>
</template>

But it seems that this is only matching Books where the Author is the first one listed, like:

Arnold, Grant — Ken Lum

Thom, Ian M. — Shore, Forest and Beyond

I figure the xsl:key is only using the first Author element, rather than checking every author. Is it possible to check every Author like that? Or is there a better approach?

Upvotes: 0

Views: 254

Answers (1)

michael.hor257k
michael.hor257k

Reputation: 117165

I would suggest you look at this way:

XML

<Books>
    <Book>
        <Title>Ken Lum</Title>
        <Author>
            <GivenName>Grant</GivenName>
            <Surname>Arnold</Surname>
        </Author>
    </Book>
    <Book>
        <Title>Shore, Forest and Beyond</Title>
        <Author>
            <GivenName>Ian M.</GivenName>
            <Surname>Thom</Surname>
        </Author>
        <Author>
            <GivenName>Grant</GivenName>
            <Surname>Arnold</Surname>
        </Author>
    </Book>
</Books>

XSLT 1.0

<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:key name="author" match="Author" use="concat(Surname, ', ', GivenName)" />

<xsl:template match="/Books">   
    <Authors>
        <!-- for each unique author -->
        <xsl:for-each select="Book/Author[count(. | key('author', concat(Surname, ', ', GivenName))[1]) = 1]">
            <xsl:sort select="Surname"/>
            <xsl:sort select="GivenName"/>
            <Author>
                <!-- author's details-->
                <xsl:copy-of select="Surname | GivenName"/>
                <!-- list author's books -->
                <Books>
                    <xsl:for-each select="key('author', concat(Surname, ', ', GivenName))/parent::Book">
                        <xsl:sort select="Title"/>
                        <xsl:copy-of select="Title"/>
                    </xsl:for-each> 
                </Books>
            </Author>
        </xsl:for-each>
    </Authors>
</xsl:template>

</xsl:stylesheet>

Result

<?xml version="1.0" encoding="UTF-8"?>
<Authors>
  <Author>
    <GivenName>Grant</GivenName>
    <Surname>Arnold</Surname>
    <Books>
      <Title>Ken Lum</Title>
      <Title>Shore, Forest and Beyond</Title>
    </Books>
  </Author>
  <Author>
    <GivenName>Ian M.</GivenName>
    <Surname>Thom</Surname>
    <Books>
      <Title>Shore, Forest and Beyond</Title>
    </Books>
  </Author>
</Authors>

Upvotes: 1

Related Questions