fauve
fauve

Reputation: 253

How to Apply the Same XSLT Template to Both XML Elements and Dynamically Created Elements?

I am working with XSLT 2.0 and trying to apply the same transformation process to both elements from the input XML and elements dynamically created within the XSLT.

Input XML:

<?xml version="1.0" encoding="UTF-8"?>
<document>
    <h2>General Introduction</h2>
    <h3>Project Overview</h3>
    <h3>Goals and Challenges</h3>
    <h2>Installation</h2>
    <h3>Initial Configuration</h3>
    <h4>Configuration File</h4>
    <h3>Deployment</h3>
    <h4>On a Local Server</h4>
    <h4>On a Remote Server</h4>
    <h2>Usage</h2>
    <h3>Basic Commands</h3>
    <h3>Advanced Options</h3>
</document>

XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/document">
        <html>
            <body>
                <xsl:apply-templates/>
                <h3>Dynamic title (was not in the XML)</h3>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="h2 | h3 | h4">
        <xsl:element name="{name()}">
            ¶<xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

Issue

The transformation correctly applies to <h2>, <h3>, and <h4> elements from the original XML. However, the dynamically created element:

<h3>Dynamic title (was not in the XML)</h3>

is not processed by the template that matches h2 | h3 | h4.

The question

How can I ensure that both XML elements and dynamically created elements undergo the same transformation process? Is there a way to reapply templates to generated elements within XSLT?

Upvotes: 0

Views: 21

Answers (2)

Michael Kay
Michael Kay

Reputation: 163587

As an alternative to Martin's solution, which does two processing passes over the entire document, you could locally process the dynamic element like this:

     <xsl:template match="/document">
        <html>
            <body>
                <xsl:apply-templates/>
                <xsl:variable name="temp">
                   <h3>Dynamic title (was not in the XML)</h3>
                </xsl:variable>
                <xsl:apply-templates select="$temp"/>
            </body>
        </html>
    </xsl:template>

Upvotes: 2

Martin Honnen
Martin Honnen

Reputation: 167716

Use a variable as a temporary result and probably better different modes to separate the processing steps:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

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

    <xsl:variable name="result1">
      <xsl:apply-templates mode="step1"/>
    </xsl:variable>
    
    <xsl:template match="/">
      <xsl:apply-templates select="$result1" mode="step2"/>
    </xsl:template>
    
    <xsl:template mode="step1" match="/document">
        <html>
            <body>
                <xsl:apply-templates mode="#current"/>
                <h3>Dynamic title (was not in the XML)</h3>
            </body>
        </html>
    </xsl:template>

    <xsl:template mode="step2" match="h2 | h3 | h4">
        <xsl:element name="{name()}">
            ¶<xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

I have used XSLT 3's xsl:mode declaration to set up the default identity transformation for the used modes as all supported versions of Saxon are XSLT 3 processors; if you still work with a version of Saxon that only supports XSLT 2 you could use

<xsl:template match="@* | node()" mode="step1 step2">
  <xsl:copy>
    <xsl:apply-templates select="@* | node()" mode="#current"/>
  </xsl:copy>
</xsl:template>

instead.

Upvotes: 1

Related Questions