tohuwawohu
tohuwawohu

Reputation: 13618

How to use last() inside xsl:matching-substring?

Is there a way to determine if a xsl:matching-substring is the last() matching substring?

Example data:

<data>
    <value>1 A 1 2 B 2 1 C 3</value>
</data>

Example XSLT:

<?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" exclude-result-prefixes="xs" version="2.0">
    <xsl:output media-type="xhtml" encoding="UTF-8" />
    <xsl:template match="/">
        <html xmlns="http://www.w3.org/1999/xhtml">
            <head>
                <title>xsl:matching-substring problem</title>
            </head>
            <body>
                <h1>xsl:matching-substring problem</h1>
                <xsl:apply-templates/>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="//value">
        <xsl:element name="p" namespace="http://www.w3.org/1999/xhtml">
            <xsl:analyze-string select="." regex="\p{{Nd}}\s\p{{Lu}}\s\p{{Nd}}" flags="i">
                <xsl:matching-substring>
                    <xsl:value-of select="."/>
                    <xsl:if test="not(last())">
                        <xsl:element name="br" namespace="http://www.w3.org/1999/xhtml" />
                    </xsl:if />
                </xsl:matching-substring>
            </xsl:analyze-string>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

Result:

<?xml version="1.0" encoding="UTF-8"?><html xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <meta http-equiv="Content-Type" content="xhtml; charset=UTF-8" />
      <title>xsl:matching-substring problem</title>
   </head>
   <body>
      <h1>xsl:matching-substring problem</h1>
      <p>1 A 12 B 21 C 3</p>
   </body>
</html>

So, it seems last() is true also for the first and the second matching substring (in fact, last() seems to return the position of the last character of the matching substring).

Without the xsl:if, i get:

<?xml version="1.0" encoding="UTF-8"?><html xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <meta http-equiv="Content-Type" content="xhtml; charset=UTF-8" />
      <title>xsl:matching-substring problem</title>
   </head>
   <body>
      <h1>xsl:matching-substring problem</h1>
      <p>1 A 1<br />2 B 2<br />1 C 3<br /></p>
   </body>
</html>

But i would prefer <p>1 A 1<br />2 B 2<br />1 C 3</p>. Is there a way to achieve this using XSLT 2.0? (Im using Saxon HE as xslt engine).

Upvotes: 1

Views: 508

Answers (2)

Michael Kay
Michael Kay

Reputation: 163360

@Joel_M.Lamsen has identified a bug in your code, but his solution isn't correct.

The spec says this: In processing each substring, the contents of the substring will be the context item (as a value of type xs:string); the position of the substring within the sequence of matching and non-matching substrings will be the context position; and the number of matching and non-matching substrings will be the context size.

So (position()=last()) tests whether you are processing the last substring, which may be either a matching or a non-matching substring. If you are in xsl:matching-substring, processing the last matching substring, then (position()=last()) will return false if there is a non-matching substring still to come.

Since you are not interested in the non-matching substrings, a better solution is to use tokenize():

<xsl:template match="value">
    <xhtml:p>
       <xsl:for-each select="tokenize(., '\p{{Nd}}\s\p{{Lu}}\s\p{{Nd}}', 'i')">
          <xsl:if test="position() ne 1"><xhtml:br/></xsl:if> 
          <xsl:value-of select="."/>
        </xsl:for-each>
   </xhtml:p>
</xsl:template>

Note also some other stylistic improvements I have made to your code:

  • The pattern //value should almost certainly be just "value"
  • Replacing xsl:element by literal result elements
  • Instead of inserting a separator after every item except the last, insert it before every item except the first. Testing whether an item is the last involves look-ahead, which can be inefficient; testing whether it is the first is much simpler.

I would also be inclined to avoid the 'i' flag in the regex; instead replace \p{Lu} by some larger category.

Upvotes: 3

Joel M. Lamsen
Joel M. Lamsen

Reputation: 7173

try

<xsl:if test="position()!=last()">

instead

Upvotes: 1

Related Questions