XSLT: A sequence of more than one item is not allowed as the first argument of concat()

xpath, xslt, xslt-2.0

Solution

First of all, the pipe symbol in Xpath isn't the "or" you are thinking of. What it does it combines two separate querys and returns them as one node-set. So what is happening, each of your queries for chapters and parts is returning one element (the @id attribute of the first node), and then its combined returning a node-set with two nodes. Hence why `concat` is having issues.

You could do:

(ancestor::div[@class='chapter'] | ancestor::div[@class='part'])[1]/@id

But it would be wrong!! As this will get the first in the combined series, but it won't merge them into the correct document order. So it would always get a chapter if available before getting a part.

What you need instead is this:

ancestor::div[@class='chapter' or @class='part'][1]/@id

I'm not sure why you are using `concat` on something you've already `concat`ed. As an aside if you are `concat`ing with a string and making an attribute, you can just surround it in curly braces to make the XSLT shorter, as in the below:

<xsl:template match="a[@class='page']" mode="pagelist">
    <xsl:variable name="html" select="ancestor::div[@class='chapter' or @class='part'][1]/@id"/>
    <xsl:variable name="pagenumber" select="substring-after(@id,'-')"/>
    <li>
        <a href="{$html}.xhtml#page{$pagenumber}">
            <xsl:value-of select="$pagenumber"/>
        </a>
    </li>
</xsl:template>

edit: If you wanted to continue using `xsl:attribute` elements you could use concat like in the following template:

<xsl:template match="a[@class='page']" mode="pagelist">
    <xsl:variable name="html" select="ancestor::div[@class='chapter' or @class='part'][1]/@id"/>
    <xsl:variable name="pagenumber" select="substring-after(@id,'-')"/>
    <li>
        <a>
            <xsl:attribute name="href">
                <xsl:value-of select="concat($html,'.xhtml#page',$pagenumber)"/>
            </xsl:attribute>
            <xsl:value-of select="$pagenumber"/>
        </a>
    </li>
</xsl:template>

Its a little bit more verbose, which is why I prefer inline attribute values.

Problem

I am trying to generate a pagelist which consists of a concatenation of the id of various divs (@chapter)|(@part). My problem stems from nested structures: @part can contain any number of @chapter. I only want to select the id of the last ancestor of a page number in document order. Below is a snippet of the XML: ``` <text> <div class="part" id="s9781483352947.i870"> <a class="page" id="pbr-31"/> <h1 class="title">Part 1</h1> <p>This is a paragraph.</p> <p>This is a paragraph.</p> <div class="chapter" id="s9781483352947.n3.i884"> <a class="page" id="pbr-34"/> <h1 class="title">Chapter 1</h1> <p>This is a paragraph</p> </div> </div> </text> ``` And below is my XSLT: ``` <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0"> <xsl:template match="/"> <xsl:element name="nav"> <xsl:element name="h1"> <xsl:value-of select="'Pages'"/> </xsl:element> <xsl:element name="ol"> <xsl:apply-templates mode="pagelist" select="//a[@class='page']"/> </xsl:element> </xsl:element> </xsl:template> <xsl:template match="a[@class='page']" mode="pagelist"> <xsl:variable name="html" select="concat(ancestor::div[@class='chapter'][1]/@id | ancestor::div[@class='part'][1]/@id,'.xhtml#page')"/> <xsl:variable name="pagenumber" select="substring-after(@id,'-')"/> <xsl:element name="li"> <xsl:element name="a"> <xsl:attribute name="href" select="concat($html,$pagenumber)"/> <xsl:value-of select="substring-after(@id,'-')"/> </xsl:element> </xsl:element> </xsl:template> </xsl:stylesheet> ``` However, I keep getting the error message: A sequence of more than one item is not allowed as the first argument of concat(). Obviously my XPath is selecting multiple strings in the first argument due to the @part and @chapters. How do I limit the selection to the first ancestor of a class='page'? This is the desired ouput for the above sample: ``` <nav> <h1>Pages</h1> <ol> <li> <a href="s9781483352947.i870.xhtml#page31">31</a> </li> <li> <a href="s9781483352947.n3.i884.xhtml#page34">34</a> </li> </ol> </nav> ```

Original source