Re: [xsl] Finding list items in XHTML

Subject: Re: [xsl] Finding list items in XHTML
From: Chris Loschen <loschen@xxxxxxxxxxxxx>
Date: Thu, 14 Nov 2002 16:39:54 -0500
Thanks again -- I've got it all working correctly now (I think -- I'll run it on a few more test cases before I'm fully confident).

This was a great primer in grouping and making hierarchy out of flat files. I'm starting to see uses for "mode" I hadn't before as well.

I gather from Michael Kay's book that I can also use modes to bring my three passes into a single stylesheet -- right now I've got
three separate stylesheets and I've wrapped the whole procedure into a Perl script. I may want the Perl wrapper anyway so I can
catch some errors for people who run the script later on, so that's OK, but another day I'll no doubt want to explore using modes
more elegantly.


Thanks for all your wonderful help -- I really appreciate it.

At 12:40 PM 11/14/02, you wrote:
Hi Chris,

Okay, easy things first. Your current test looks like:

<xsl:if test="following-sibling::oeb:sublist[1]">

but it doesn't work. Translated into English, the XPath reads "the first following sibling sublist", which gives a hint as to the problem: the test is true if there is a (first) following sibling sublist. Which there always is until you get to the end, hence your results.

Try:

<xsl:if test="following-sibling::*[1][self::oeb:sublist]">

which translates as "the first following sibling element, when it is (itself) an oeb:sublist". That is, the node set returned by this expression will contain a node only when the next sibling is a sublist. What you want. (Try this and see, your results should look better.)

Okay, next thing. The technique you're adopting is basically

(a) test whether an <ol> is needed. It's needed when the next sibling is a sublist. (We just did that.)
(b) if so, insert the <ol>
(c) inside the <ol>, collect the sublist elements we need, transforming each into an <li>


Where (c) is getting nasty is in the algorithm to collect all the sublists. This can be done in XPath. The thing is (and it takes some experience to see this), the XPath you will need will be pretty nasty, and essentially requires "keying" the sublists to the list they belong in (which is what your variable identifying the paragraph is doing) ... leading us to keys as a clean way to implement, leading us to the solution you rejected as too complex! :->

Rather than undo all the logic here and re-engineer the grouping-by-keys solution, since this looks good so far, let's just keep going. I'm going to break down step (c):

(c) inside the <ol>
    (c.1) if it's a sublist, include the first following sibling
    (c.2) repeat (c.1) until the first following sibling is not a sublist

We can do this with a template, which we'll put into a special mode so as not to confuse ourselves (since this isn't ordinary tree-traversal anymore):

<xsl:if test="following-sibling::*[1][self::oeb:sublist]">
   <!-- we test to see whether we need an <ol> -->
   <ol>
     <!-- now we do our first sublist in our special mode -->
     <xsl:apply-templates mode="listitem"
       select="following-sibling::*[1][self::oeb:sublist]"/>
   </ol>
</xsl:if>

and now our template:

<xsl:template match="oeb:sublist" mode="listitem">
  <!-- make our item and include what's inside this sublist -->
  <li>
    <xsl:apply-templates/>
  </li>
  <!-- now do the same for the next immediate sibling that's a sublist
       (if it's not, nothing will happen) -->
  <xsl:apply-templates mode="listitem"
     select="following-sibling::*[1][self::oeb:sublist]"/>
</xsl:template>

Notice what we have here is a recursive template that walks the tree forward one node at a time, as long as the next sibling is a sublist, making an item for each one it finds.

It's important for this to be in a mode since if we're not, this will clash with the default depth-first tree-traversal and we'll get many spurious list items as well as the ones we actually want. (We still need the regular template for sublists to suppress them.)

I hope this helps. Note: this code is untested (though I've successfully done this in the past).

Cheers,
Wendell


At 12:02 PM 11/14/2002, you wrote:
I seem to keep getting closer, but I'm not quite there yet, and I'm not sure what to do next.
I've now got a first pass which takes my sublist items and surrounds them with
<sublist>...</sublist>. So my input is like this:


***
<p class="hang-text-1"><span class="hang-text-2">&#10148; </span>Which of the following features of the Mighty Vac are important to you? (Check all
that apply.)</p>
<sublist>Extra-long hose</sublist>
<sublist>Extra-long cord</sublist>
<sublist>Extra-high horsepower</sublist>
<sublist>Square head</sublist>
<sublist>Other <i>(Please specify)</i> ______________________</sublist>
<p class="hang-text-12"><span class="hang-text-2">&#10148; </span>How would you rate the service you receive from Acme Industries? (Check one.)
Excellent</p>
<sublist>Good
Fair
Poor</sublist>
<p class="format-10">With closed-end questions, you must often give directions to the respondent (such as
&#8220;Check one&#8221; or &#8220;Please rate from one to five, with five being the best&#8221;).</p>
***


Yes, I know the second sublist didn't really come out right, but my input
isn't always perfect. That will have to be fixed downstream from me.

Now I'm trying to group the <sublist>s into an <ol> and to set up the <li>
for the main lists. In the third pass, I plan to group the <li>s for the main lists.


I read the FAQ page cited above, and tried to implement some of it, but frankly,
I'm not sure it all sunk in very well. I was able to make more sense of Michael
Kay's discussion of grouping adjacent nodes on p. 496 of his book (2nd ed.),
so that's the path I'm trying to follow. However, I'm still off-course somewhere.
Here's part of my stylesheet at present:


***
<xsl:template match="oeb:p[starts-with(.,'&#10148; ')]">
<li><xsl:apply-templates />
<xsl:if test="following-sibling::oeb:sublist[1]">
<xsl:text>&#xA;</xsl:text>
<ol>
<xsl:text>&#xA;</xsl:text>
<xsl:variable name="list-id" select="generate-id(preceding-sibling::p[1])" />
<xsl:for-each select="oeb:sublist[generate-id(preceding-sibling::p[1]) = $list-id]">
<li><xsl:value-of select="." /></li>
<xsl:text>&#xA;</xsl:text>
</xsl:for-each>
</ol>
<xsl:text>&#xA;</xsl:text>
</xsl:if>
</li>
</xsl:template>


<xsl:template match="oeb:sublist" />

<xsl:template match="oeb:span[.='&#10148; ']" />
***

What I'm trying to do is process the list items and the sublists of that list item if there are any.
So my intended output for the above snippet of text would be


***
<li>Which of the following features of the Mighty Vac are important to you? (Check all
that apply.)
<ol>
<li>Extra-long hose</li>
<li>Extra-long cord</li>
<li>Extra-high horsepower</li>
<li>Square head</li>
<li>Other <i>(Please specify)</i> ______________________</li>
</ol>
</li>
<li>How would you rate the service you receive from Acme Industries? (Check one.)
Excellent
<ol>
<li>Good
Fair
Poor</li>
</ol>
</li>
<p class="format-10">With closed-end questions, you must often give directions to the respondent (such as
&#8220;Check one&#8221; or &#8220;Please rate from one to five, with five being the best&#8221;).</p>
***


However, I have several problems. First, the <xsl:if> turns out to be true for EVERY occurrence where there
is a <sublist> anywhere further down in the document, so I get a lot of empty <ol> tags where I don't want them.
Those stop occurring after the last <sublist> in the document. I need to specify that the following-sibling is
immediately adjacent to the current node, but the [1] I inserted to try to do that seems to pick the next occurrence
of <sublist> but not the next node. So my <xsl:if> isn't right.


Next, the stylesheet doesn't insert the <sublist> items into the place where I expect them, so even when I do have
the <ol> tags in the right place, they don't have the content I was trying to set up. My actual output looks like this:


***
<li>Which of the following features of the Mighty Vac are important to you? (Check all
that apply.)
<ol>
</ol>
</li>






<li>How would you rate the service you receive from Acme Industries? (Check one.)
Excellent
<ol>
</ol>
</li>


<p class="format-10">With closed-end questions, you must often give directions to the respondent (such as
&#8220;Check one&#8221; or &#8220;Please rate from one to five, with five being the best&#8221;).</p>
***


I think I understand why the <xsl:if> isn't working, but I don't know how to fix it. I'm not sure why the second
problem is happening at all.


May I ask for a little more help? Thanks so much for all you've helped with so far!


======================================================================
Wendell Piez                            mailto:wapiez@xxxxxxxxxxxxxxxx
Mulberry Technologies, Inc.                http://www.mulberrytech.com
17 West Jefferson Street                    Direct Phone: 301/315-9635
Suite 207                                          Phone: 301/315-9631
Rockville, MD  20850                                 Fax: 301/315-8285
----------------------------------------------------------------------
  Mulberry Technologies: A Consultancy Specializing in SGML and XML
======================================================================


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

--Chris


----------------------------------------------------------------------------------------
Texterity ~ XML and PDF ePublishing Services
----------------------------------------------------------------------------------------
Chris Loschen, XML Developer
Texterity, Inc.
144 Turnpike Road
Southborough, MA 01772 USA
tel: +1.508.804.3033
fax: +1.508.804.3110
email: loschen@xxxxxxxxxxxxx
http://www.texterity.com/


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



Current Thread