Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

using XSLT to modify HTML, got stuck on a tricky bit

Status
Not open for further replies.

Normunds

Programmer
Feb 7, 2006
3
0
0
CH
Hi folks, I need to use XSLT to do a few modifications to HTML when document gets saved. It all goes fine, except I got stuck on one requirement. The indent level of bulleted list may not be more than 2. So I have embedded lists:
[tt]<ul>
<li>bull0</li>
<li>bull1
<ul>
<li>bull1.1</li>
<li>here goes level 3
<ul>
<li>bull1.2.1</li>
</ul>
</li>
<li>bull1.2</li>
</ul>
</li>
</ul>[/tt]
and the task would be to move bullet points of level 3 and deeper back to level 2:
...
[tt]<li>bull1.1</li>
<li>here goes level 3</li>
<li>bull1.2.1</li>
<li>bull1.2</li>[/tt]
...
My best attempt was to use:
[tt] <xsl:template match="ul/li/ul/li/ul">
<xsl:text disable-output-escaping="yes">&lt;/li&gt;</xsl:text>
<xsl:apply-templates select="node()" />
<xsl:text disable-output-escaping="yes">&lt;li&gt;</xsl:text>
</xsl:template>[/tt]
this removes UL tags themselves and closes the list item preceding any 3rd level UL element and opens a LI tag after, so that it produces balanced tags. Nearly OK, but 1) it produces empty tag after the embedded list and 2) it leaves trailing space in LI element preceding the removed UL tag, so that I get:
...
[tt]<li>bull1.1</li>
<li>here goes level 3
</li>
<li>bull1.2.1</li>
<li></li>
<li>bull1.2</li>[/tt]
...
It's not as bad as on the next save I will remove the empty LI tag using:
[tt] <xsl:template match="li">
<xsl:if test="node()!=''">
<xsl:element name="li">
<xsl:attribute name="class">myclass</xsl:attribute>
<xsl:apply-templates select="node()" />
</xsl:element>
</xsl:if>
</xsl:template>[/tt]
and as the users must not create the 2+ level indented lists anyway, they will have to put up with this.

Still I cannot figure out how to avoid leaving trailing space at the end of the list item preceding UL (also the text may contain other tags); and I'd prefer to handle this cleanly (without creating empty elements). Or could I process the HTML produced once more in one go in attempt to clean it up? I doubt. Any ideas?

BTW I tried to loop the preceding LI element:
[tt] <xsl:template match="ul/li/ul/li">
<xsl:for-each select="*">
...
</xsl:for-each>[/tt]
</xsl:template>
but then I loose any plain text that precedes <ul>.
 
Think something like this would be easier. Basically match everything as usual except if the list has a parent 3 levels up.

<xsl:template match="/">
<xsl:apply-templates select="ul" />
</xsl:template>

<xsl:template match="ul">
<ul>
<xsl:apply-templates select="li" />
</ul>
</xsl:template>

<xsl:template match="ul[name(parent::*/parent::*/parent::*/parent::*)='ul']">
<xsl:apply-templates select="li" />
</xsl:template>

<xsl:template match="li">
<li>
<xsl:value-of select="node()" />
<xsl:apply-templates select="ul" />
</li>
</xsl:template>
 
Thanks, I already tried something like this; I think a bit simpler, but it does not give the result I expect. It only gets UL content. Here is what I get using your code:
[tt]<li>bull1.1</li>
<li>here goes level 3
<li>bull1.2.1</li>
</li>
<li>bull1.2</li>[/tt]
This is not really right -- list item with text "here goes level 3" does not get closed; and below is the corresponding closing tag. Basically I get included LI tags. That's why I tried this workaround with inserting closing/opening tags.

In my first attempt I used the syntax below to arrive at about the same result:
[tt]<xsl:template match="ul/li/ul/li/ul">
<xsl:apply-templates select="node()"/>
</xsl:template>[/tt]
 
Yes i see what you mean. How about something like this?

<xsl:template match="/">
<xsl:apply-templates select="ul" />
</xsl:template>

<xsl:template match="ul">
<ul>
<xsl:apply-templates select="li" />
</ul>
</xsl:template>

<xsl:template match="ul[name(parent::*/parent::*/parent::*/parent::*)='ul']">
<xsl:apply-templates select="li" />
</xsl:template>

<xsl:template match="li">
<li>
<xsl:value-of select="node()" />
<xsl:apply-templates select="ul" />
</li>
</xsl:template>

<xsl:template match="li[child::* and parent::*/parent::*/parent::*]">
<li><xsl:value-of select="node()" /></li>
<xsl:apply-templates select="ul" />
</xsl:template>
 
Great, much better. Still if the list item that precededes 3rd level UL contains other tags (B, FONT, etc) it looses them (I also got a similar result within my previous attempts). I added a template to process other tags and tried to change your last template to process LI content:
[tt]<xsl:template match="li[child::* and parent::*/parent::*/parent::*]">
<li><xsl:apply-templates /></li>
<xsl:apply-templates select="ul" />
</xsl:template>[/tt]
but then understandably I got ul tags processed twice. OK, then I added 2 new templates:
[tt]<xsl:template match="*" mode="noul">
<xsl:element name="{name()}">
<xsl:apply-templates mode="noul" />
</xsl:element>
</xsl:template>
<xsl:template match="ul" mode="noul"></xsl:template>
[/tt]
and changed the previous template to process only "noul" mode inside LI element:
[tt]<xsl:template match="li[child::* and parent::*/parent::*/parent::*]">
<li><xsl:apply-templates mode="noul" /></li>
<xsl:apply-templates select="ul" />
</xsl:template>[/tt]

and finally added the line:
[tt]<xsl:strip-space elements="li" />[/tt]
to get rid of the space the above procedure added to the list element preceding 3rd level UL.
Excellent, thanks so much for your help MrGrey; seems I'll be able to use this :) I think I've learned a thing or two trying to get this done...
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top