RE: [xsl] Building a hierarchy based on siblings

Subject: RE: [xsl] Building a hierarchy based on siblings
From: Ryan Graham <Ryan.Graham@xxxxxxxxxxxxx>
Date: Fri, 11 Jun 2004 12:59:04 -0700
Thank you so much for the guidance -- you filled the knowledge gap on this
one!  I created a modified version based on what you posted below.  It
integrates seamlessly into my existing stylesheet and provides the correct
output :)

<xsl:template match="para[not(starts-with(., '* '))]">
	<para>
		<xsl:apply-templates/>
	</para>
</xsl:template>
	
<xsl:template match="para[starts-with(., '* ') and
not(preceding-sibling::para[1][starts-with(.,'* ')])]">
	<xsl:variable name="followingParagraph"
select="following-sibling::para[not(starts-with(., '* '))][1]"/>
	<itemizedlist mark="bullet">
		<xsl:apply-templates select=". |
following-sibling::para[starts-with(.,'* ') and not(preceding-sibling::para
= $followingParagraph)]" mode="list"/>
	</itemizedlist>
</xsl:template>
	
<xsl:template match="para[starts-with(.,'* ') and
preceding-sibling::para[1][starts-with(.,'* ')]] "/>
	
<xsl:template match="para" mode="list">
	<listitem>
		<xsl:apply-templates select="text() | *"/>
	</listitem>
</xsl:template>
	
<xsl:template match="text()[starts-with(., '* ')]">
	<xsl:value-of select="substring-after(., '* ')"/>
</xsl:template>

Thanks again!
-Ryan

-----Original Message-----
From: M. David Peterson [mailto:m.david@xxxxxxxxxx] 
Sent: Thursday, June 10, 2004 9:34 PM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: RE: [xsl] Building a hierarchy based on siblings


Hey Ryan,

Kind of an interesting situation that has several possible solutions. But
from your comments it seems that getting a good grasp of how to properly use
recursion to build nested output is something you are in seek of.  So I gave
you an example that uses as much recursion as possible to help you in your
quest :)

I was going to try and comment it a bit but I actually just realized the
time and I need to head out for a bit so I can attend a buddy of mines
bachelor party.  But I don't plan on drinking too much so when I return Ill
look to see if you had any questions.  Otherwise, enjoy!  I hope you find
this helpful in better understanding how you can use recursion in multiple
ways in XSLT.

Best of luck!

<M:D/>

Heres the XSLT:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
  <xsl:element name="sect1">
    <xsl:apply-templates select="sect1/para[not(starts-with(.,'* '))]"
mode="normal"/>
  </xsl:element>
</xsl:template>
<xsl:template match="para" mode="normal"><!-- no pun intended.  I like
X-Files but not so much I name my attribute values to match the shows focus
;) --> <xsl:variable name="followingParagraph"
select="following-sibling::para[not(starts-with(., '*'))][1]"/>
  <xsl:copy-of select="."/>
  <xsl:if test="following-sibling::para[1][starts-with(., '* ')]">
    <xsl:element name="itemizedlist">
      <xsl:apply-templates
select="following-sibling::para[starts-with(.,'* ') and
not(preceding-sibling::para = $followingParagraph)]" mode="buildList"/>
    </xsl:element>
  </xsl:if>
</xsl:template>
<xsl:template match="para" mode="buildList">
  <xsl:element name="listitem">
    <xsl:element name="para">
      <xsl:apply-templates select="text() | *"/>
    </xsl:element>
  </xsl:element>
</xsl:template>
<xsl:template match="text()[starts-with(., '* ')]">
  <xsl:value-of select="substring-after(., '* ')"/> </xsl:template>
<xsl:template match="text() | *">
  <xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>

Heres the transformed result of the XML you gave as an example:

<?xml version="1.0" encoding="UTF-8"?>
<sect1>
  <para>First Paragraph</para>
  <itemizedlist>
    <listitem>
      <para>First list item</para>
    </listitem>
    <listitem>
      <para>Second list item</para>
    </listitem>
    <listitem>
      <para>Third list item <emphasis>emphasized text</emphasis>
      </para>
    </listitem>
  </itemizedlist>
  <para>Second Paragraph</para>
  <para>Third paragraph</para>
  <itemizedlist>
    <listitem>
      <para>First list item #2</para>
    </listitem>
    <listitem>
      <para>Second list item #2</para>
    </listitem>
  </itemizedlist>
  <para>Another paragraph</para>
</sect1>

Enjoy!  I'm off to get a little loopy 8$ but will return before to long so
don't hesitate to ask questions...  Worst case Ill get back to you in the
morning.

-----Original Message-----
From: Ryan Graham [mailto:Ryan.Graham@xxxxxxxxxxxxx] 
Sent: Thursday, June 10, 2004 7:35 PM
To: 'xsl-list@xxxxxxxxxxxxxxxxxxxxxx'
Subject: [xsl] Building a hierarchy based on siblings

Hi all,

I am having some trouble getting the correct template matches written to
build a hierarchy.

I have similar to the following input XML:

<sect1>
	<para>First Paragraph</para>
	<para>* First list item</para>
	<para>* Second list item</para>
	<para>* Third list item <emphasis>emphasized text</emphasis></para>
	<para>Second Paragraph</para>
	<para>Third paragraph</para>
	<!-- Start new list-->
	<para>* First list item #2</para>
	<para>* Second list item #2</para>
	<para>Another paragraph</para>
</sect1>

I would like to put all para nodes that begin with "* " into a list
structure and cut off the first two characters when processed, to get the
following output:

<para>First Paragraph</para>
<itemizedlist>
	<listitem>
		<para>First list item</para>
	</listitem>
	<listitem>	
		<para>Second list item</para>
	</listitem>
	<listitem>
		<para>Third list item <emphasis>emphasized
text</emphasis></para>
	<listitem>
</itemizedlist>
<para>Second Paragraph</para>
<para>Third paragraph</para>
<itemizedlist>
	<listitem>
		<para>First list item #2</para>
	</listitem>
	<listitem>	
		<para>Second list item #2</para>
	</listitem>
</itemizedlist>
<para>Another paragraph</para>

I was using this to get the <listitems> built:

<xsl:template match="para">
  <xsl:choose>
    <xsl:when test="substring(node()[1], 0, 3) = '* '>
      <itemizedlist>
	  <listitem>
	  <xsl:value-of select="substring(node()[1], 3)"/>
	  <xsl:apply-templates select="node()[position() > 1]"/>
	  </listitem>
	</itemizedlist>
    </xsl:when>
    <xsl:otherwise>
      <para>
	  <xsl:apply-templates/>
	</para>
    </xsl:otherwise>
</xsl:template>

But with this match, every <para> that starts with "* " becomes its own
itemizedlist with one listitem.  Is there an efficient way to do this using
a recursive template, without creating duplicate output?

I appreciate any and all suggestions!

Thanks,
Ryan

--+------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe@xxxxxxxxxxxxxxxxxxxxxx>
--+--


--+------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe@xxxxxxxxxxxxxxxxxxxxxx>
--+--

Current Thread