[XSL-LIST Mailing List Archive Home] [By Thread] [By Date]

RE: [xsl] empty element as an end-marker


Subject: RE: [xsl] empty element as an end-marker
From: "Michael Kay" <mhk@xxxxxxxxx>
Date: Fri, 28 Mar 2003 09:41:37 -0000

These problems are slightly tricky, and the exact details of the
solution depend on the exact nature of the problem.

A typical example of the problem is to convert

<p>
line1<br/>
line2<br/>
linen
</p>

to

<p>
<line>line1</line>
<line>line2</line>
<line>linen</line>
</p>

The best way to tackle this, provided that it doesn't exceed the
recusion depth allowed by most processors (say 1000), is to use
apply-templates recursing along the following-sibling axis. In simple
cases the following works:

<xsl:template match="p">
<p>
  <xsl:apply-templates select="child::text()[1]" mode="along"/>
</p>
</xsl:template>

<xsl:template match="text()" mode="along">
<line><xsl:value-of select="."/></line>
<xsl:apply-templates select="following-sibling::text()[1]"
mode="along"/>
</xsl:template>

Unfortunately this isn't very robust. Some processors (wrongly) split
text nodes when entity references are encountered, and all (rightly)
split them when comments are encountered; also your text might have
other elements such as <i> embedded in it.

So a more robust technique is to match the br elements, and each time
you hit a br, copy all the preceding siblings whose next br element is
this one. This can be done as follows:

<xsl:template match="br" mode="along">
<xsl:copy-of select="preceding-sibling::node()
    [generate-id(following-sibling::br[1])=generate-id(current())]"/>
<xsl:apply-templates select="following-sibling::br[1]"/>
</xsl:template>

Then you have to mop up the last group of elements (the ones that have
no following br element) which you can do in the parent template.

This has O(n^n) performance and it can recurse deeply, so it isn't
ideal. A more efficient solution is to treat it as a grouping problem.
The sibling elements between two br elements constitute a group, the
grouping key for this group is the generate-id() of the most recent br
(or p) element. So you can use Muenchian grouping with the grouping key:

<xsl:key name="k" match="p/node()" 
use="concat(generate-id(..), generate-id(preceding-sibling::br[1]"/>

It's easier in XSLT 2.0. If you can use Saxon 7.4, try:

<xsl:for-each-group select="child::node()" group-starting-with="br">
<line><xsl:copy-of select="current-group()"/></line>
</xsl:for-each-group>

Michael Kay
Software AG
home: Michael.H.Kay@xxxxxxxxxxxx
work: Michael.Kay@xxxxxxxxxxxxxx 



> -----Original Message-----
> From: owner-xsl-list@xxxxxxxxxxxxxxxxxxxxxx 
> [mailto:owner-xsl-list@xxxxxxxxxxxxxxxxxxxxxx] On Behalf Of 
> Vincent Neyt
> Sent: 28 March 2003 00:09
> To: XSL-List@xxxxxxxxxxxxxxxxxxxxxx
> Subject: [xsl] empty element as an end-marker
> 
> 
> Hello,
> 
> my problem is easily explained. This is the simple xml:
> 
> <p>some text some text <pb/>more text more text</p>
> 
> In my (html) output I need to process and reproduce the 'some 
> text' (ie. up to the <pb/>) without the 'more text'. (Of 
> course I also need to output just the 'more text' in another 
> instance.) I'm having some trouble with this. Also the 'some 
> text' contains tags which need to be processed by the stylesheet.
> 
> Sorry if this problem has been solved before, I searched the 
> archives and FAQ's using 'empty elements' and didn't find 
> anything. Thanx in advance, Vincent
> 
> 
> 
>  XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
> 


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



Current Thread
Keywords