IronAces
IronAces

Reputation: 1883

Displaying child nodes

I currently have a simple markup which mostly represents HTML.

Below is a snippet of that

<li>Make this <b>Bold</b></li>

I can of course use <xsl:copy-of> to ensure that the <b> tag is passed through and is automatically displayed as bold, however I have a problem.

I am using another XSL which checks the markup against a repository of keywords or phrases and if they exist, links are created.

Below is my XSL

<xsl:template name="List" match="li">
  <li>
          <xsl:call-template name="markup">
            <xsl:with-param name="text" select="."/>
            <xsl:with-param name="phrases" select="document('../../documents/main/keywords.xml')/keywords/keyword"/>
            <xsl:with-param name="first-only" select="false()"/>
          </xsl:call-template>
  </li>
  </xsl:template>

This method prevents any child tags being passed through, however I am unsure as to how I can get around this.

Any help is greatly appreciated! Dan

Upvotes: 1

Views: 134

Answers (1)

Tomalak
Tomalak

Reputation: 338316

The problem is that your link-creating template does the wrong thing.

The right thing to do is using the identity template and creating a dedicated template for text node descendants of <li> elements.

Try this:

<xsl:variable 
  name="keywords"
  select="document('../../documents/main/keywords.xml')/keywords/keyword" 
/>

<!-- the identity template does copies of everything not matched otherwise -->
<xsl:template match="node() | @*">
  <xsl:copy>
    <xsl:apply-templates select="node() | @*" />
  </xsl:copy>
</xsl:template>

<!-- text nodes in <li> elements get special treatment -->
<xsl:template match="li//text()">
  <xsl:call-template name="markup">
    <xsl:with-param name="phrases" select="$keywords"/>
    <xsl:with-param name="first-only" select="false()"/>
  </xsl:call-template>
</xsl:template>

<!-- marks up the current node -->
<xsl:template name="markup">
  <xsl:with-param name="phrases" />
  <xsl:with-param name="first-only" select="true()" />

  <!-- you can use . here - no need to pass in a $text param -->
</xsl:template>

The identity template is the key to successfully solving problems like this one. It handles copying the <li> and the <b> transparently.

  1. It does a full traversal of the input, copying it unless a more specific template matches the current node.
  2. This means you only need to write templates for nodes you want to change. In this case matching text nodes works best, since they cannot have nested children.
  3. Avoid named templates like <xsl:template name="List">. This is "push style" XSLT, i.e. it's imperative and often leads to pretty clumsy results.
  4. The <xsl:template match="li//text()"> pulls nodes out of the stream and does something more complex than just copying them. This is "pull style" XSLT, i.e. template matching. It usually is easier to handle and produces cleaner XSLT code.
  5. You are not constrained to text nodes within <li> elements, of course. Just change the match expression to affect other nodes.

Say you want to turn all <b> nodes into <strong> without disrupting any other templates. With pull style, this is as easy as this:

<xsl:template match="b">
  <strong>
    <xsl:apply-templates select="node() | @*" />
  <strong>
</xsl:template>

Also note that the current node does not change when you do <xsl:call-template>. Therefore there is no need to pass in the current node to a called template.

Upvotes: 1

Related Questions