xsl-list
[Top] [All Lists]

RE: empty element as an end-marker

2003-03-28 02:41:37
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(_dot_)H(_dot_)Kay(_at_)ntlworld(_dot_)com
work: Michael(_dot_)Kay(_at_)softwareag(_dot_)com 



-----Original Message-----
From: owner-xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com 
[mailto:owner-xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com] On Behalf Of 
Vincent Neyt
Sent: 28 March 2003 00:09
To: XSL-List(_at_)lists(_dot_)mulberrytech(_dot_)com
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