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.