Search code examples
xsltxslt-2.0

How to recursively join all texts of all preceding siblings?


This is my XML:

<?xml version="1.0"?>
<foo>
  <a>
   first
   <b>x</b>
   <c>y</c>
  </a>
  <b>second</b>
  <bar>hey</bar>
</foo>

This is my XSL:

<?xml version="1.0"?>
<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:template match="/foo">
    <xsl:apply-templates select="bar" />
  </xsl:template>
  <xsl:template match="bar">
    [<xsl:value-of select="preceding-sibling::text()"/>]
  </xsl:template>
</xsl:stylesheet>

I'm expecting this:

[first x y second]

However, it doesn't work as expected. Simply put, I'm interested to join all texts (recursively) of all siblings of the current node.


Solution

  • The siblings which you select are not themselves text nodes, therefore you cannot use the text() node test immediately after the preceding-sibiling:: axis. Instead, you must select text descendants of these siblings.

    You probably also want to omit whitespace-only texts such as the one between the <b> and <c> elements:

    [<xsl:for-each select="preceding-sibling::*//text()
      [normalize-space(.)!='']"> <!-- filter out whitespace-only -->
      <xsl:if test="position()>1">
        <xsl:text> </xsl:text>
      </xsl:if>
      <xsl:value-of select="normalize-space(.)"/>
    </xsl:for-each>]
    

    If you want the texts separated by one space, you must insert that explicitly and remove trailing and leading whitespace with the normalize-space function.

    (XSLT 1.0)