1

I am new to XSLT. I am trying to parse the result in XML 1.0 and generate:

Keyword A,Keyword B,Keyword C,Keyword D

but the separator is not shown. I just got the result

Keyword A Keyword B Keyword C Keyword D 

My XML and XSLT code are as below:

Product.xml

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="style.xsl" ?>
<products>
    <product id="1" name="My Product">
        <keywords>
            <keyword>
                Keyword A
            </keyword>
            <keyword>
                Keyword B
            </keyword>
            <keyword>
                Keyword C
            </keyword>
            <keyword>
                Keyword D
            </keyword>
        </keywords>
    </product>
</products>

style.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <html>
            <body>      
            <xsl:for-each select="products/product">
                <xsl:for-each select="keywords">
                    <xsl:choose>
                        <xsl:when test="position()=last()">
                            <xsl:value-of select="current()"/>
                        </xsl:when>                 
                        <xsl:otherwise>
                            <xsl:value-of select="current()"/>
                            <xsl:text>&#44;</xsl:text>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:for-each>
            </xsl:for-each>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

I have already look at if-else statement in XSLT but I cannot find the answer.

Community
  • 1
  • 1
Sun Maung Oo
  • 183
  • 2
  • 11

2 Answers2

1

Change

<xsl:for-each select="keywords">

to

<xsl:for-each select="keywords/keyword">

Otherwise, you're iterating over the single keywords element, falling into the first xsl:when because the one and only position is equal to the last position, and outputting the string value of keyword, which will be concatenation of all of the string values of the children of keyword without any commas.

kjhughes
  • 106,133
  • 27
  • 181
  • 240
1

Note that in XSLT 2.0 you can do

<xsl:for-each select="products/product">
  <xsl:value-of select="keywords/keyword" separator=","/>
</xsl:for-each>

and if you do have to insert separators by hand, then to avoid unnecessary lookahead it is best to put them before every element except the first, rather than after every element except the last: so

<xsl:for-each select="products/product/keywords/keyword">
    <xsl:if test="position() != 1">,</xsl:if>
    <xsl:value-of select="."/>
</xsl:for-each>
Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • Can you explain the "unnecessary lookahead" part? According to the XPath specification, "*the context consists of ... a pair of non-zero positive integers (the context position and the context size)*". Why would referring to one be any different from the other? – michael.hor257k Feb 10 '16 at 11:32
  • Many processors will attempt to avoid building an in-memory list representing the result of the `select` expression, and instead use a mapping operation where each input item is read and processed in turn (often called pipelined or streamed evaluation). Using `last()` will in general disrupt the pipeline; if the processor needs to know how many items there are in the sequence, then it needs to read them all before it can process any of them. (cont'd) – Michael Kay Feb 10 '16 at 11:52
  • A smart processor like Saxon will recognize `position()=last()` as a special case, which can be evaluated with a single-item lookahead (there's no need to know the actual value of `last()`, only that it isn't equal to `position()`). But in general, the `last()` function should be treated as expensive. – Michael Kay Feb 10 '16 at 11:52
  • An even smarter processor might recognize the whole loop with a `position()=last()` conditional, and replace it with the loop in the form I wrote it: but that's beyond even Saxon's current optimization skills. – Michael Kay Feb 10 '16 at 11:57
  • Thank you for explaining. "*the `last()` function should be treated as expensive.*" That's a difficult concept to adjust to. It certainly does not follow from the precepts - in fact, it conflicts with what I quoted above. – michael.hor257k Feb 10 '16 at 22:00
  • BTW: http://stackoverflow.com/questions/10954091/delimeting-the-output-for-xslt-in-non-trivial-cases/10956713#10956713 -- And what about: http://stackoverflow.com/questions/19133038/xslt-1-0-max-value-of-a-date-node/19134272#19134272 ? Should you sort in descending order and take the first value instead? – michael.hor257k Feb 10 '16 at 22:05
  • Yes, it's only in the last couple of years I've been advocating the inversion of `position()=last()` for inserting separators. That's partly because of the focus on streaming in the last couple of years, and partly because although many common uses of `last()` get optimized in Saxon, some don't, and it can be difficult for users to predict. – Michael Kay Feb 10 '16 at 22:59
  • How do we know this is not processor-specific? – michael.hor257k Feb 11 '16 at 01:46