Phillip B Oldham
Phillip B Oldham

Reputation: 19385

XSL: wrap all descendant text and elements?

Given the following XML document:

<dialog>
  <speech speaker="Robert">
    <line>"Once more into the breach", he said.</line>
    I can't believe him. I just <emphasis>can't</emphasis> believe him!
  </speech>
</dialog>

I'd like to try and capture eveything within speech that isn't in a line already and wrap it in a line, however I need to capture any other elements along with it (eg. the emphasis in the example above).

The result I'd like to achieve is:

<dialog>
  <speech speaker="Robert">
    <line>"Once more into the breach", he said.</line>
    <line>I can't believe him. I just <emphasis>can't</emphasis> believe him!</line>
  </speech>
</dialog>

I'm using libxslt and libxml, so I'm stuck with XSLT 1.0.

Upvotes: 0

Views: 149

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167571

One way to approach that in XSLT 1.0 is through sibling recursion, as outlined in the following example:

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

<xsl:output method="xml"/>

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

<xsl:template match="speech">
  <xsl:copy>
    <xsl:apply-templates select="@*"/>
    <xsl:apply-templates select="node()[1]"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="speech/line">
  <xsl:call-template name="identity"/>
  <xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>

<xsl:template match="speech/node()[not(self::line)
                                  and (not(preceding-sibling::node())
                                       or
                                       preceding-sibling::node()[1][self::line])]">
  <xsl:if test="normalize-space() or following-sibling::node()[1][not(self::line)]">
    <line>
      <xsl:call-template name="identity"/>
      <xsl:apply-templates select="following-sibling::node()[1][not(self::line)]"/>
    </line>
  </xsl:if>
  <xsl:apply-templates select="following-sibling::line[1]"/>
</xsl:template>

<xsl:template match="speech/node()[not(self::line)
                                   and preceding-sibling::node()[1][not(self::line)]]">
  <xsl:call-template name="identity"/>
  <xsl:apply-templates select="following-sibling::node()[1][not(self::line)]"/>
</xsl:template>

</xsl:stylesheet>

With that stylesheet an input sample like

<dialog>
  <speech speaker="Robert">
    <line>"Once more into the breach", he said.</line>
    I can't believe him. I just <emphasis>can't</emphasis> believe him!
  </speech>
  <speech speaker="Foo">This is a test.
    <line>This line is wrapped and should be copied unchanged.</line>
    <em>This</em> needs to be <it>wrapped</it>.
  </speech>
  <speech speaker="Bar">   <em>This</em> should be wrapped.
    <line>This line is wrapped and should be copied unchanged.</line>
    <it>Test</it>
  </speech>
</dialog>

is transformed into

<dialog>
  <speech speaker="Robert"><line>"Once more into the breach", he said.</line><line>
    I can't believe him. I just <emphasis>can't</emphasis> believe him!
  </line></speech>
  <speech speaker="Foo"><line>This is a test.
    </line><line>This line is wrapped and should be copied unchanged.</line><line>
    <em>This</em> needs to be <it>wrapped</it>.
  </line></speech>
  <speech speaker="Bar"><line>   <em>This</em> should be wrapped.
    </line><line>This line is wrapped and should be copied unchanged.</line><line>
    <it>Test</it>
  </line></speech>
</dialog>

Upvotes: 2

Related Questions