XML content, XSLT, element node

Element Content

1. what is the content of an element node
2. Numeric value of a element
3. A problem with embedded templates
4. Matching specific elements
5. How to check for an empty element content.
6. Looping between two element values
7. Breaking up is hard to do
8. Applying a named template to arbitrary content
9. Remove certain elements from input
10. Why are element contents being passed through?

1.

what is the content of an element node

Tony Graham

The spec says that 'The *value* of an element node is the string that results from concatenating all characters that are descendants of the element node in the order in which they occur in the document.' so, if 'The children of an element node are the element nodes, comment nodes, processing instruction nodes and text nodes for its content.' what is meant by 'content' then? is there a hierarchical or coordinating relationship between them or are they merely associated nodes?


Yes, there is a hierarchical relationship.  Try the following
stylesheet on a small file.  For each node (except namespace nodes),
the output shows the type of the node and its value, then the
templates are applied to its children.  The borders around the nodes
demonstrate the containment.

------------------------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<!-- $Id: node-type.xsl,v 1.1 1999-06-14 12:32:53-04 tkg Exp $ -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
     <xsl:output method="html">

  <xsl:template match="@*">
    <div style="border: 1.5pt solid aqua; padding: 3pt">
      <p><strong>Attribute: 
            <xsl:value-of select="local-part()"/></strong></p>
      <blockquote style="background-color: silver">
	<!-- The spans are necessary because empty attributes
	  count as attribute nodes, but Internet Explorer, at least,
	  doesn't cope very well with blockquotes containing just a
	  line break -->
	<span style="color: silver">|</span>
            <xsl:value-of select="."/>
               <span style="color: silver">|</span>
      </blockquote>
      <xsl:apply-templates/>
    </div>
  </xsl:template>

  <xsl:template match="/">
    <div style="border: 1.5pt solid red; padding: 3pt">
      <p><strong>Root</strong></p>
      <blockquote style="background-color: silver">
	<xsl:value-of select="."/>
      </blockquote>
      <xsl:apply-templates/>
    </div>
  </xsl:template>

  <xsl:template match="*">
    <div style="border: 1.5pt solid blue; padding: 3pt">
      <p><strong>Element: 
       <xsl:value-of select="local-part()"/></strong></p>
      <blockquote style="background-color: silver">
	<!-- The spans are necessary because empty elements
	  count as element nodes, but Internet Explorer, at least,
	  doesn't cope very well with blockquotes containing nothing -->
	<span style="color: silver">|</span>
           <xsl:value-of select="."/>
             <span style="color: silver">|</span>
      </blockquote>
      <xsl:apply-templates select="@*"/>
      <xsl:apply-templates/>
    </div>
  </xsl:template>

  <xsl:template match="text()">
    <div style="border: 1.5pt solid purple; padding: 3pt">
      <p><strong>Text</strong></p>
      <blockquote style="background-color: silver">
	<!-- The spans are necessary because line breaks between an
	  end-tag and the next start-tag count as text nodes, but
	  Internet Explorer, at least, doesn't cope very well with
	  blockquotes containing just a line break -->
	<span style="color: silver">|</span>
          <xsl:value-of select="."/>
            <span style="color: silver">|</span>
      </blockquote>
      <!-- This won't produce any output -->
      <xsl:apply-templates/>
    </div>
  </xsl:template>

  <xsl:template match="comment()">
    <div style="border: 1.5pt solid fuchsia; padding: 3pt">
      <p><strong>Comment</strong></p>
      <blockquote style="background-color: silver">
	<xsl:value-of select="."/>
      </blockquote>
      <xsl:apply-templates/>
    </div>
  </xsl:template>

  <xsl:template match="pi()">
    <div style="border: 1.5pt solid teal; padding: 3pt">
      <p><strong>Processing Instruction: 
             <xsl:value-of select="local-part()"/>
                    </strong></p>
      <blockquote style="background-color: silver">
	<xsl:value-of select="."/>
      </blockquote>
      <xsl:apply-templates/>
    </div>
    <xsl:apply-templates/>
  </xsl:template>

</xsl:stylesheet>

            

2.

Numeric value of a element

Michel Goossens

Q: Expansion
Is it possible to have anything like this:

<xsl:template match="/one/two[. > 10]">
  ...
</xsl:template>

I want match an element which contain a number 
and I want to match it only
if it's value is higher than a certain number.
How can I do?

The following XSL stylesheet

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Tranform"     version="1.0">
<xsl:template match="/one/two[. < 10]">
<xsl:value-of select="."/><xsl:text> < 10</xsl:text>
</xsl:template>
<xsl:template match="/one/two[. = 10]">
<xsl:value-of select="."/><xsl:text> = 10</xsl:text>
</xsl:template>
<xsl:template match="/one/two[. > 10]">
<xsl:value-of select="."/><xsl:text> > 10</xsl:text>
</xsl:template>
</xsl:stylesheet>

on the following XML data

<one>
<two>9</two>
<two>10</two>
<two>11</two>
</one>

produces the following output

9 < 10
10 = 10
11 > 10

It is a little unfortunate that one has to use < in the comparison on line 3 (otherwise xt displays a "character not allowed" message). On the other hand, the > operator (gt) by itself seems to be accepted all right as a literal, without needing the > entity reference.

To which David Carlisle added:

If you don't like writing . < 10 you can write 10 >= . which means the same thing, or you can write . < 10 into an XML (rather than ascii) editor, which will know that it needs to quote the < character.

3.

A problem with embedded templates

David Carlisle


Q: expansion If I have XML like this:
<title>This is some <italics>text</italics> in some XML</title>

Can I use XSL to produce HTML like this:
<h1>This is some <i>text</i> in some XML</h1>



<xsl:template match="title">
 <h1><xsl:apply-templates/></h1>
</xsl:template>

<xsl:template match="italics">
 <i><xsl:apply-templates/></i>
</xsl:template>

            

4.

Matching specific elements

David Carlisle




Given:

<foo>
 <bar>1</bar>
 <bar>1</bar>
</foo>

<foo>
 <bar>
   <bar>3</bar><bar>4</bar>
 </bar>
</foo>
      
            

I want to write a template which treats the second case separately from the first.

It depends a bit what you want to do, do you want to just be in a recursive template matching situation, and have different templates fire for those two cases, or do you want to be sat at a foo node and query if there is a bar/bar child, in which case do something different.

In the second case you could have

<xsl:template match="foo">
<xsl:choose>
<xsl:when test="bar/bar">
 <xsl:text> case 2</xsl:text>
 <xsl:apply-templates select="bar/bar"/>
</xsl:when>
<xsl:otherwise>
 <xsl:text> case 1</xsl:text>
 <xsl:apply-templates select="bar"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
            

In the former case you just want different templates like

<xsl:template match="foo">
 <xsl:apply-templates/>
</xsl:template>


<xsl:template match="bar/bar" priority="3">
 I'm a bar with a bar parent
 <xsl:apply-templates/>
</xsl:template>

<xsl:template match="bar[bar]" priority="2">
 I'm a bar with bar children (and not a bar parent)
 <xsl:apply-templates/>
</xsl:template>


<xsl:template match="bar">
 I'm a bar that's all alone
 <xsl:apply-templates/>
</xsl:template>

            

5.

How to check for an empty element content.

Mike Brown

If you are using an XSLT 1.0 implementation,

<xsl:if test="string(DESCRIPTION)">

string() will return false if DESCRIPTION has an empty
string value. If you want a whitespace-only DESCRIPTION to
also be considered empty, use normalize-space(DESCRIPTION)
instead.

            

6.

Looping between two element values

Michael Kay



> <tscmeta>
>   <frequency code="ANNUAL" />
>   <date year="1994" month="1" day="1" />
>   <date year="1998" month="12" day="31" />
> </tscmeta>
> 
> Desired output: 
> <table><tr><td>1994</td><td
  >1995</td><td>1996</td><td>1997</td
> ><td>1998</td>
> </tr></table>

Today it has to be recursion. The principle is:

template do-range
  param from
  param to
  if $from<=$to
     <td><value-of $from></td>
     call-template do-range
        with-param from = $from+1
        with-param to = $to
     /call
  /if
/template

7.

Breaking up is hard to do

Mike Brown



> I'm trying to set an opening tag attribute based on the value 
> of my input; simple enough except that my closing tag needs
> to be outside of my select template.

Nope, it needs to be inside the template :)

Usually this question is asked in regards to constructing HTML tables, when one wants to end a row with </tr> and start a new one with <tr> after having generated some number of <td>...</td> cells in between.

Stop thinking about tags and start thinking about nodes.

The answer in your case is easy. Try this:

<xsl:template match="/">
  <Transaction>
    <EnrollmentRequest>
       <xsl:attribute name="servicetype">
         <xsl:choose>
           <xsl:when 
        test="Transaction/LIN/LIN03='EL'">ELECTRIC</xsl:when>
           <xsl:otherwise>GAS</xsl:otherwise>
         </xsl:choose>
       </xsl:attribute>
       <!-- ... other stuff ... -->
    </EnrollmentRequest>
  </Transaction>
</xsl:template>

Mike Kay adds:

You need to change mindset. XSLT doesn't output tags, it outputs trees. I found this took months to get used to, but you have to do it. You need to find a way of constructing your stylesheet so that a single element in the stylesheet outputs a single element in the result tree, you can't output the start tag from one place and the end tag from another, because you can't have half a node on your tree. Unless of course you resort to dirty tricks, otherwise known as disable-output-escaping: but avoid that if you can.

8.

Applying a named template to arbitrary content

Steve Tinney and Mike Kay


> I'm trying to make a generic template which will, in effect, 
> simply insert stuff before and after the content.  In one case, I 
> want to do it to apply font styling parameters etc. where needed:

This is hard to do in XSLT 1.0, but quite a few people have asked for it, so maybe evaluating the name of the template to call will come in a future version of the language.

For now, one approach is that of Mike Kay---you can check the archive for details, but the meat of it is this:

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

<xsl:template name="xyz" match="xsl:template[@name='xyz']">
  <xsl:message>Hello xyz.</xsl:message>
</xsl:template>

<xsl:template match="/">
  <xsl:variable name="tname" select="'xyz'"/>
  <xsl:apply-templates
     select="document('')/*/xsl:template[@name=$tname]"/>
</xsl:template>
</xsl:stylesheet>

The other approach is to rethink your problems and solutions. For example, much of what you seem to want to do could probably also be achieved by use of xsl:attribute-set, or increased use of CSS in combination with 'class' attributes on your HTML output.

9.

Remove certain elements from input

Jeni Tennison


>I'm having parent element which can have many childrens (in source). But the
>result side (in DTD) there are fewer possible elements. So, solution I'm
>gonna do is to put those 'illegal' (in result side) elements childrens of
>para element(s).

You can think of this as a grouping problem and apply a grouping solution to it. You're grouping all the illegal elements and text within a 'para' element. As with all grouping problems, you have to ask yourself what is unique about these particular nodes that puts them in a group with each other?

The answer in cases like this is the identity of some following node. In your example:

<entry>
  <para>sometext</para>
  <jibii>sometext</jibiii>
  sometext without tags - illegal
  <zzz>illegal element in result</zzz>
  <xxx>another illegal elem</xxx>
  <jibii>this elem is good</jibii>
  <xxx>another illegal</xxx>
</entry>

'sometext without tags - illegal' and the following 'zzz' and 'xxx' elements all have the same preceding legal element (<jibii>sometext</jibiii>) and the same following legal element (<jibii>this elem is good</jibii>). So you can use this fact to group the nodes together.

As usual I'll use the Muenchian Method and define a key:

<xsl:key name="illegal-nodes"
         match="xxx | zzz | entry/text()[normalize-space(.)]"
         use="generate-id(following-sibling::*[name() = 'para' or
                                               name() = 'jibii'])" />

The key matches on the illegal nodes that you know about - change this expression to match any illegal nodes - note that I've selected only that text that actually has some non-whitespace content. The key uses as a value the unique id of the first legal element (a 'para' or a 'jibii') that follows the matched illegal node. You could put something more complex there in order to match other legal nodes.

Thus, within a template that matches on a legal element, you can use:

  <xsl:variable name="preceding-illegal-nodes"
                select="key('illegal-nodes', generate-id())" />
  <xsl:if test="$preceding-illegal-nodes">
    <para><xsl:copy-of select="$preceding-illegal-nodes" /></para>
  </xsl:if>

The variable $preceding-illegal-nodes holds the illegal nodes that precede the current legal element, identifying them through it's unique identifier. If there are such nodes, a copy of them is placed within a 'para' element.

You also need to make sure to copy any illegal nodes that come at the end of the entry, so within the 'entry'-matching template similarly have:

    <xsl:variable name="ending-illegal-nodes"
                  select="key('illegal-nodes', '')" />
    <xsl:if test="$ending-illegal-nodes">
      <para><xsl:copy-of select="$ending-illegal-nodes" /></para>
    </xsl:if>

The key value of '' gets all those nodes that were given a key value that was the result of calling generate-id() on an empty node set.

With those in place, you just need to be sure that you're only applying templates to the legal nodes. The final stylesheet is:

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

<xsl:key name="illegal-nodes"
         match="xxx | zzz | entry/text()[normalize-space(.)]"
         use="generate-id(following-sibling::*[name() = 'para' or
                                               name() = 'jibii'])" />

<xsl:template match="entry">
  <entry>
    <xsl:apply-templates select="para | jibii" />
    <xsl:variable name="ending-illegal-nodes"
                  select="key('illegal-nodes', '')" />
    <xsl:if test="$ending-illegal-nodes">
      <para><xsl:copy-of select="$ending-illegal-nodes" /></para>
    </xsl:if>
  </entry>
</xsl:template>

<xsl:template match="*">
  <xsl:variable name="preceding-illegal-nodes"
                select="key('illegal-nodes', generate-id())" />
  <xsl:if test="$preceding-illegal-nodes">
    <para><xsl:copy-of select="$preceding-illegal-nodes" /></para>
  </xsl:if>
  <xsl:copy-of select="."/>
</xsl:template>

</xsl:stylesheet>

This works in SAXON and XALAN

10.

Why are element contents being passed through?

Michael Kay



I have an xml source doc which I want to turn into an xhtml document. 
What is happening is that the 
contents of the elements in the xml document are turning up in the 
output and I can't understand why. I have simplified the xml and the xsl 
until there is almost nothing happening, and the element contents still 
come through.

What can I do?

If you don't specify a template rule for a particular element, then the default template rule kicks in. The default template rule for elements copies the content to the output. If this isn't what you want, you can override it:


<xsl:template match="*"/>

Read about the "default rules" or "built-in template rules".

The XSLT 1.0 spec (W3C) says:

"There is a built-in template rule to allow recursive processing to continue in the absence of a successful pattern match by an explicit template rule in the stylesheet"

and

"There is also a built-in template rule for text and attribute nodes that copies text through:

<xsl:template match="text()|@*">
  <xsl:value-of select="."/>
</xsl:template>

"Most probably you have an xsl:apply-templates in your code against a node-set some of whose nodes are not matched by templates in your code. Then the built-in templates are instantiated/applied by the XSLT processor. This is done in a recursive manner until text nodes are reached, at which point the built-in template rule (for text and attribute nodes) above copies the contents of the text nodes to the output.