tags:

views:

187

answers:

3

This is a slightly version of other question posted here: http://stackoverflow.com/questions/2888880/xslt-change-node-inner-text

Imagine i use XSLT to transform the document:

<a>
  <b/>
  <c/>
</a>

into this:

<a>
  <b/>
  <c/>
  Hello world
</a>

In this case i can't use neither the

<xsl:strip-space elements="*"/> 

element or the [normalize-space() != ''] predicate since there is no text in the place where i need to put new text. Any ideas? Thanks.

A: 

edit: fixed my fail to put proper syntax in.

<xsl:template match='a'>
   <xsl:copy-of select="." />
   <xsl:text>Hello World</xsl:text>
</xsl:template>
<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
Mark Schultheiss
@Mark: This is structurally invalid XSLT, please fix.
Tomalak
woops, sorry for the typo
Mark Schultheiss
+1  A: 

This transformation inserts the desired text (for generality) after the element named a7:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="a7">
   <xsl:call-template name="identity"/>
   <xsl:text>Hello world</xsl:text>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document:

<a>
  <a1/>
  <a2/>
  .....
  <a7/>
  <a8/>
</a>

the desired result is produced:

<a>
    <a1/>
    <a2/>
  .....
    <a7/>Hello world
    <a8/>
</a>

Do note:

  1. The use of the identity rule for copying every node of the source XML document.

  2. The overriding of the identity rule by a specific template that carries out the insertion of the new text.

  3. How the identity rule is both applied (on every node) and called by name (for a specific need).

Dimitre Novatchev
Dimitre, is there any benefit in calling the identity template explicitly instead of using `<xsl:copy-of>`?
Tomalak
@tomalak: Not in this example, but if there are (future) requirements for multiple edits then this solution may need to be updated to invoke the identity template at this point. So, calling the identity template gives us more extensibility for free.
Dimitre Novatchev
+1  A: 

Here is what I would do:

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <!-- identity template to copy everything unless otherwise noted -->
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <!-- match the first text node directly following a <c> element -->
  <xsl:template match="text()[preceding-sibling::node()[1][self::c]]">
    <!-- ...and change its contents -->
    <xsl:text>Hello world</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Note that text nodes contain "surrounding" whitespace - in the sample XML in the question the matched text node is whitespace only, which is why the above works. It will stop to work as soon as the input document looks like this:

<a><b/><c/></a>

because here is no text node following <c>. So if this is too brittle for your use case, an alternative would be:

<!-- <c> nodes get a new adjacent text node -->
<xsl:template match="c">
  <xsl:copy-of select="." />
  <xsl:text>Hello world</xsl:text>
</xsl:template>

<!-- make sure to remove the first text node directly following a <c> node-->
<xsl:template match="text()[preceding-sibling::node()[1][self::c]]" />

In any case, stuff like the above makes clear why intermixing of text nodes and element nodes is best avoided. This is not always possible (see XHTML). But when you have the chance and the XML is supposed to be purely a container for structural data, staying clear of mixed content makes your life easier.

Tomalak
Any of your solutions produce the error: Only 'child' and 'attribute' axes are allowed in a pattern outside predicates. c/ -->following-sibling::<-- node()[1][self::text()]
Nabo
@nabo: Oh, my bad. I've corrected the match pattern.
Tomalak