[XSL-LIST Mailing List Archive Home] [By Thread] [By Date]

Re: Formatting elements


Subject: Re: Formatting elements
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Tue, 03 Oct 2000 20:48:48 +0100

Stuart,

>I have managed to generate to new tags from the name attribute but cannot
>get the <bulletlist> element generated. Can anyone help?

It would have been helpful if you'd included the XSLT that wasn't working
so we could see where you were having problems.  I'm guessing that your
input is a bit more complicated than just a title and a number of bullets,
and that you have other things mixed up that makes working out where the
bulletlist starts and stops quite difficult - something like:

<document>
<item name='title'><text>Title field</text></item>
<item name='bullet1'><text>some text for item 1</text></item>
<item name='bullet2'><text>some text for item 2</text></item>
<item name='bullet3'><text/></item>
<item name='bullet4'><text/></item>
<item name='bullet5'><text/></item>
<item name='bullet6'><text/></item>
<item name='para'><text>paragraph text</text></item>
<item name='bullet1'><text>some text for item 1</text></item>
<item name='bullet2'><text>some text for item 2</text></item>
<item name='bullet3'><text/></item>
<item name='bullet4'><text/></item>
</document>

which you want to be:

<document>
<title>Title field</title>
<bulletlist>
   <bullet>some text for item 1</bullet>
   <bullet>some text for item 2</bullet>
   <bullet></bullet>
   <bullet></bullet>
   <bullet></bullet>
   <bullet></bullet>
</bulletlist>
<para>paragraph text</para>
<bulletlist>
   <bullet>some text for item 1</bullet>
   <bullet>some text for item 2</bullet>
   <bullet></bullet>
   <bullet></bullet>
</bulletlist>
</document>

This generalisation turns the problem into a grouping problem - you need to
identify which items make up the bulletlist.  This is usually best
approached using the Muenchian method (i.e. keys) to group the relevant
nodes.  This problem is really interesting, though, because the
construction of the @names means that the Muenchian method is really hard
to work with (because you don't know how long the number at the end is
going to be).  I'm sure a Muenchian solution to this is possible, but just
thinking about constructing the key value made my head spin - David
Carlisle will doubtless be able to help :)

So I've used a different approach to the problem.  It's relatively easy to
work out whether an item is part of a list: if the last character of its
name is a number, then it's part of a list:

  boolean(number(substring(@name, string-length(@name), 1)))

Working out what number it is in the list is fairly straight-forward as
well: translate all the letters in the name to spaces and turn the result
into a number:

  <xsl:variable name="number"
     select="number(translate(@name, 'abcdefghijklmnopqrstuvwxyz', ' '))" />

If the $number is 1, then we've found the first item in a list; at this
point we can create the wrapping '*list' element:

  <xsl:if test="$number = 1">
    <xsl:variable name="name"
                  select="substring(@name, 1, string-length(@name) - 1)" />
      <xsl:element name="{$name}list">
        ...
      </xsl:element>
  </xsl:if>

Now the bit of logic that is a break from the normal method of grouping.
Usually this would involve collecting together all the members of the group
- all the items in the list - by finding all the items that have the same
following-sibling:: that marks the end of the list.  In this case, it's
very hard to do because we the @names change from item to item (albeit
retaining the same prefix).  Instead, we're going to step through them one
by one, passing the name of the new element (which is relatively easy to
work out for that first item in the list we can guarantee the number only
has one character in it) as a parameter.

The first item in the list is the first to have templates applied to it.
I'm using a mode here to distinguish between the normal processing of items
and the processing of items when they belong to a list.

  <xsl:apply-templates select="." mode="item">
    <xsl:with-param name="name" select="$name" />
  </xsl:apply-templates>

The template that this matches with checks whether the item starts with the
$name we're interested in, and if so creates an element with that name.
Then it applies templates in the same mode to *its* following sibling.
Again, the same template is applied, and if it's another member of the
list, it continues to process the next item and so on, effectively stepping
through the items one at a time.

To counter the situation where two lists are placed immediately one after
another, a second parameter ($first) is declared so that it is only true()
if the template is being applied to the first of a list.  If $first is not
true() and the @name of the item is the same as $name + '1', then the item
is ignored - in effect it is part of the next list, not this one.  The
complete templates are:

<xsl:template match="item" mode="list">
  <xsl:param name="name" />
  <xsl:param name="first" select="true()" />
  <xsl:if test="starts-with(@name, $name) and
                ($first or @name != concat($name, '1'))">
    <xsl:element name="{$name}"><xsl:value-of select="text" /></xsl:element>
    <xsl:apply-templates select="following-sibling::*[1]" mode="list">
      <xsl:with-param name="name" select="$name" />
      <xsl:with-param name="first" select="false()" />
    </xsl:apply-templates>
  </xsl:if>
</xsl:template>

<xsl:template match="item">
  <xsl:choose>
    <xsl:when
      test="boolean(number(substring(@name, string-length(@name), 1)))">
      <xsl:variable name="number"
        select="number(translate(@name, 'abcdefghijklmnopqrstuvwxyz', '
'))" />
      <xsl:if test="$number = 1">
        <xsl:variable name="name"
          select="substring(@name, 1, string-length(@name) - 1)" />
        <xsl:element name="{$name}list">
          <xsl:apply-templates select="." mode="list">
            <xsl:with-param name="name" select="$name" />
          </xsl:apply-templates>
        </xsl:element>
      </xsl:if>
    </xsl:when>
    <xsl:otherwise>
      <xsl:element name="{@name}"><xsl:value-of select="text" /></xsl:element>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

I hope that this helps.  It's certainly proved an interesting challenge.

Cheers,

Jeni

Jeni Tennison
http://www.jenitennison.com/


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



Current Thread
Keywords