This is the mail archive of the xsl-list@mulberrytech.com mailing list .


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: <P><H1></H1></P> to <P></P><H1></H1>


Rainer,

>I'm trying to convert something like
><P>Text1<H1>Head1</H1>Text2<H1>Head2</H1>Text3</P>
>to 
><P>Text1</P>
><H1>Head1</H1>
><P>Text2</P>
><H1>Head2</H1>
><P>Text3</P>

I'll talk through your solution, which will hopefully show you why it's
going wrong, and then give you an alternative approach.

>I tried something like 
><xsl:template match="P">
> <xsl:for-each match=".">
>  <xsl:chose>
>   <xsl:when test="H1">
>    <H1><xsl:apply-templates></H1>
>   </xsl:when>
>   <xsl:otherwise>
>    <P><xsl:apply-templates></P>
>   </xsl:otherwise>
>  <xsl:chose>
> </xsl:for-each>
></xsl:template>

There are three errors that your XSLT processor should have picked up on:
1. the 'match' attribute on the xsl:for-each should be a 'select' attribute
2. the 'xsl:chose' element should be called 'xsl:choose'
3. the xsl:apply-templates elements should be closed
4. the xsl:choose end tag is missing a '/'

After making those changes, what this template says is:

When you find a 'P' element, then for each current node, if it contains an
H1 element, then create an H1 element and make its contents the result of
applying templates to the children of this element, and if not then make a
P element with the contents being result of applyign templates to the
children of this element.

There are a few things wrong with that.  Firstly, you probably want to
iterate over the *contents* of the P element, not the P element itself, in
other words:

  <xsl:for-each select="node()">
    ...
  </xsl:for-each>

Secondly, within this xsl:for-each, you want to test whether the current
node (which is either a text node or an H1 element) *is* an H1 element.
The test="H1" tests whether the current node has an H1 element *as a
child*.  You should use:

  <xsl:when test="self::H1">
    ...
  </xsl:when>

instead.

Finally, within the xsl:for-each, if the node is a text node (e.g.
'Text1'), then rather than applying templates to its children (it doesn't
have any), you want to get its value:

  <xsl:otherwise>
    <xsl:value-of select="." />
  </xsl:otherwise>

If you make these changes, you get:

<xsl:template match="P">
 <xsl:for-each select="node()">
  <xsl:choose>
   <xsl:when test="self::H1">
    <H1><xsl:apply-templates /></H1>
   </xsl:when>
   <xsl:otherwise>
    <P><xsl:value-of select="." /></P>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:for-each>
</xsl:template>

which creates the desired output in Saxon and Xalan.

However, an iterative approach is generally not the best approach to take
when you're faced with mixed content.  Rather than thinking in terms of
iterating over the contents of the P element, you can let the processor
fall back on built in rules that apply templates to its content
automatically.  Then you can construct rules (templates) that tell the
processor what to do when it comes across, within a P element, either an H1
or a text() node, or anything else.

So, you can use the following template to say 'whenever there's a text()
node within a P element, create a P element to hold its content':

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

And the following template to say 'whenever there's an H1 element within a
P element, create an H1 element with the content being the result of
applying templates to its content':

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

These two templates give the same result as the one above in both Saxon and
Xalan.

I hope that this helps,

Jeni

Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]