Home > Software design >  Alter text() while preserving children of element using XSLT
Alter text() while preserving children of element using XSLT

Time:08-03

Considering the following example:

<root>
 <a>foo
  <b>bar</b>
 </a>
</root>

How can I change the text() of <a> to "fee" while preserving the child element <b> with all its children, attributes, etc.?

Using <xsl:value-of select="."/>, only the text() is processed, correct? Using <xsl:apply-templates/>, I cannot alter the text(), e.g. via replace function. So how can I change both. Note that my actual use case is more complex, with multiple children and the text() not necessarily in the first position.

Thank you!

CodePudding user response:

You can define templates which match text nodes, not just elements. You could make a template which matches text nodes which are children of a elements, e.g.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="a/text()">
     <xsl:value-of select="replace(., 'foo', 'fee')"/>
  </xsl:template>

</xsl:stylesheet>

Result:

<root>
 <a>fee
  <b>bar</b>
 </a>
</root>

By the way, you don't mention what version of XSLT you're running though you mention the replace function which implies you're version 2 or later. That stylesheet is version 3.0 but the only thing not supported in XSLT 2 is the mode element which is effectively just an identity template.

To answer your question about <xsl:value-of select="."/>; what happens is that xsl:value-of converts the current node (.) to a string. If the current node is an element (like your a element), then its text value is the result of concatenating all its descendant text nodes, not just the child text nodes; i.e. it equals string-join(.//text()), and not string-join(./text()).

CodePudding user response:

You can use this XSLT-1.0 template in combination with the identity template:

<xsl:template match="a">
  <xsl:copy><xsl:apply-templates select="@*" />fee
    <xsl:apply-templates select="node()[not(self::text()[1])]" />  
  </xsl:copy>
</xsl:template>

This should replace the first text() node of the <a> element.

  • Related