XSLT: add node inner text

2019-07-20 06:16发布

This is a slightly version of other question posted here: 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.

标签: xslt
3条回答
Juvenile、少年°
2楼-- · 2019-07-20 06:44

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>
查看更多
Viruses.
3楼-- · 2019-07-20 06:54

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">
 <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).

查看更多
Anthone
4楼-- · 2019-07-20 07:09

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.

查看更多
登录 后发表回答