tags:

views:

41

answers:

2

Trying to convert a plain text document into a html document using xslt, I am struggling with unordered lists.

I have:

<item>some text</item>
<item>- a list item</item>
<item>- another list item</item>
<item>more plain text</item>
<item>more and more plain text</item>
<item>- yet another list item</item>
<item>even more plain text</item>

What I want:

<p>some text</p>
<ul>
    <li>a list item</li>
    <li>another list item</li>
</ul>
<p>more plain text</p>
<p>more and more plain text</p>
<ul>
    <li>yet another list item</li>
</ul>
<p>even more plain text</p>

I was looking at the Muenchian grouping but it would combine all list items into one group and all the plain text items into another. Then I tried to do select only items which preceding elements first char is different from its first char. But when I try to combine everything, I still get all the li in one ul.

Do you have any hints for me?

+2  A: 

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kFollowing"
   match="item[contains(., 'list')]
          [preceding-sibling::item[1][contains(.,'list')]]"
   use="generate-id(preceding-sibling::item
                      [not(contains(.,'list'))]
                      [1]
                        /following-sibling::item[1]
                      )"/>

 <xsl:template match="item[contains(.,'list')]
              [preceding-sibling::item[1][not(contains(.,'list'))]]">

  <ul>
   <xsl:apply-templates mode="list"
        select=".|key('kFollowing',generate-id())"/>
  </ul>
 </xsl:template>

 <xsl:template match="item" mode="list">
  <li><xsl:value-of select="."/></li>
 </xsl:template>

 <xsl:template match="item[not(contains(.,'list'))]">
  <p><xsl:value-of select="."/></p>
 </xsl:template>

 <xsl:template match="item[contains(.,'list')]
              [preceding-sibling::item[1][contains(.,'list')]]"/>
</xsl:stylesheet>

when applied on the provided XML document (corrected from severely malformed into a well-formed XML document):

<t>
 <item>some text</item>
 <item>- a list item</item>
 <item>- another list item</item>
 <item>more plain text</item>
 <item>more and more plain text</item>
 <item>- yet another list item</item>
 <item>even more plain text</item>
</t>

produces the wanted, correct result:

<p>some text</p>
<ul>
   <li>- a list item</li>
   <li>- another list item</li>
</ul>
<p>more plain text</p>
<p>more and more plain text</p>
<ul>
   <li>- yet another list item</li>
</ul>
<p>even more plain text</p>
Dimitre Novatchev
We have a small misunderstanding I wanted to distinguish by the leading '-'. But this is no problem since I can easily replace the contains(.,'list') with starts-with(.,'- ').
Jan
But I have another bigger problem. The node set that I want to process is in a variable. I can do a <xsl:apply-templates select="exslt:node-set($items)/item"/>.But how do I put my data in the xsl:key?
Jan
@Jan, This question becomes too long and confusing after your comments. For the sake of clarity, please, just ask a new question just for how to apply keys to an XML document that is in a variable -- this is easy to answer and it would be more valuable to everybody if this useful knowledge isn't buried inside another question. Please, notify me when you have asked the new question, so that I would reply soon.
Dimitre Novatchev
+1  A: 

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="node()">
        <xsl:apply-templates select="node()[1]|following-sibling::node()[1]"/>
    </xsl:template>
    <xsl:template match="item">
        <p>
            <xsl:value-of select="."/>
        </p>
        <xsl:apply-templates select="following-sibling::node()[1]"/>
    </xsl:template>
    <xsl:template match="item[starts-with(.,'- ')]">
        <ul>
            <xsl:call-template name="open"/>
        </ul>
        <xsl:apply-templates
             select="following-sibling::node()
                        [not(self::item[starts-with(.,'- ')])][1]"/>
    </xsl:template>
    <xsl:template match="node()" mode="open"/>
    <xsl:template match="item[starts-with(.,'- ')]" mode="open" name="open">
        <li>
            <xsl:value-of select="substring-after(.,'- ')"/>
        </li>
        <xsl:apply-templates select="following-sibling::node()[1]" mode="open"/>
    </xsl:template>
</xsl:stylesheet>

Output:

<p>some text</p>
<ul>
    <li>a list item</li>
    <li>another list item</li>
</ul>
<p>more plain text</p>
<p>more and more plain text</p>
<ul>
    <li>yet another list item</li>
</ul>
<p>even more plain text</p>

Note: This is like wrapping adjacents. Ussing fine grained traversal.

Alejandro
To be honest I do not completely understand your solution.Having my node set in a variable I used <xsl:apply-templates select="exslt:node-set($items)/item"/> to run your solution.The result starts good, but all the elements are processed multiple times.Maybe I should understand the solution completely. The templates process their text and actively call their following-sibling. But then the following-sibling will be still in the node set, or not?
Jan
@Jan: Use `exslt:node-set($items)/item[1]`. The fine grained traversal process node by node: firstly the first child, and secondly the first following sibling.
Alejandro