xsl-list
[Top] [All Lists]

RE: Wrapping consecutive similar elements inside a new pare nt element

2003-02-14 05:12:26
Hi Graham,

What you want to do is select all the <p> elements which start with span,
but whose immediate preceding siblings does not start with span. This
selects the first element in the list. You then output the ul and handle the
first list item. You then need to step down the following elements, each
time handling them if they are another list item, and quitting the recursion
if they are not. So:

<!-- This template matches the a p with first child of span whose first
preceding sibling is not a p with a first child of span, i.e. the first item
in a list. -->
<xsl:template
match="p[*[1]/self::span][not(preceding-sibling::*[1][self::p][*[1]/self::sp
an])]">
 <!-- We output the ul element and process the first item -->
 <ul>
  <li><xsl:apply-templates/></li>
  <!-- We then call a template to check the following element (note we are
still "within" the ul) -->
  <xsl:call-template name="followingItem"/>
 </ul>
</xsl:template>

<xsl:template name="followingItem">
 <!-- This now moves the current node to the first following sibling IF it
is a p AND it has a span (i.e. it is a list item) -->
 <xsl:for-each select="following-sibling::*[1][self::p][*[1]/self::span]">
  <!-- Output the item, and call the template again. Note that when the
condition fails (there is an ordinary p) the for-each of this template will
not return a node and so the recursion will be exited as the recursive call
is within the for-each -->
  <li><xsl:apply-templates/></li>
  <xsl:call-template name="followingItem"/>
 </xsl:for-each>
</xsl:template>

<xsl:template match="p">
 <!-- Standard para -->
 <p><xsl:apply-templates/></p>
</xsl:template>

<xsl:template
match="p[*[1]/self::span][preceding-sibling::*[1][self::p][*[1]/self::span]]
"/>
<!-- This prevents the subsequent list items being processed a second time
outside of the ul -->

Note that starts-with() only checks the text value of a node. If you want to
check the first element child of a node use *[1]/self::myName as above (not
myName[1], which returns the first myName whether or not it is the first
child). Note also that I have assumed that a paragraph whose first child is
span is always a list item (i.e. I have omitted the test for "-") for
(relative) clarity of the XPaths. If you want to test for that as well,
change the [*[1]/self::span] predicates to
[starts-with(*[1]/self::span),'-')] wherever they occur.


Hope that helps,

Stuart

-----Original Message-----
From: Graham Hannington [mailto:Ghannington(_at_)csl(_dot_)com]
Sent: 14 February 2003 11:10
To: xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
Subject: [xsl] Wrapping consecutive similar elements inside a 
new parent
element


I'm cleaning up HTML exported from Word documents.  First, I 
run Tidy again
the exported HTML, to convert it to well-formed XHTML, then I 
transform it
with my own XSLT stylesheet.

I need to convert (this is slightly simplified):


<p>Some paragraph that does not begin with a span element whose first
character is a hyphen.</p>
<p><span>-</span>List item</p>
<p><span>-</span>List item</p>
<p><span>-</span>List item</p>
<p><span>-</span>List item</p>
<p>Some paragraph that does not begin with a span element whose first
character is a hyphen.</p>
<p><span>-</span>List item</p>
<p><span>-</span>List item</p>
<p><span>-</span>List item</p>
<p><span>-</span>List item</p>
<p>Some paragraph that does not begin with a span element whose first
character is a hyphen.</p>


into this:


<p>Some paragraph that does not begin with a span element whose first
character is a hyphen.</p>
<ul>
<li>List item</li>
<li>List item</li>
<li>List item</li>
<li>List item</li>
</ul>
<p>Some paragraph that does not begin with a span element whose first
character is a hyphen.</p>
<ul>
<li>List item</li>
<li>List item</li>
<li>List item</li>
<li>List item</li>
</ul>
<p>Some paragraph that does not begin with a span element whose first
character is a hyphen.</p>


I'm afraid my attempts at this have been truly pitiful:


      <xsl:template match="p[starts-with(span[1], '-')]">

              <xsl:if
test="not(starts-with(preceding-sibling::p[1]/span[1], '-'))">
                      <xsl:text>&lt;ul&gt;</xsl:text>
              </xsl:if>

              <li>
                      <xsl:apply-templates />
              </li>

              <xsl:if
test="not(starts-with(following-sibling::p[1]/span[1], '-'))">
                      <xsl:text>&lt;/ul&gt;</xsl:text>
              </xsl:if>
      </xsl:template>


I know, I know: those <xsl:text>&lt;ul&gt;</xsl:text> bits 
are a joke, they
don't produce the <ul> parent element.

I've tried writing a template that uses <xsl:for-each>, but 
that ends up
grabbing all following-sibling <p> elements that begin with 
<span>-</span>.
I don't know how to stop the <xsl:for-each> at the first 
non-matching <p>
element.

Can anyone throw me an XSLT pearl?

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


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



<Prev in Thread] Current Thread [Next in Thread>