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

Re: [xsl] Concatenating a nodeset (set of attributes)


Subject: Re: [xsl] Concatenating a nodeset (set of attributes)
From: Geert Josten <Geert.Josten@xxxxxxxxxxx>
Date: Mon, 10 Oct 2005 02:02:38 +0200

Hi Ragulf,

I realized after sending my answer that it contained various flaws. You will have to divide by
larger numbers (powers of 100 or 1000) to cope with more child STRUC elements. But dividing by
larger numbers cannot be done endlessly. I think the precision of real numbers usually ends around 1
divided by 2 billion. So you either can handle more depth or more children, not both to a reasonable
extend. Next to this, number() and format-number() of an empty node-set returns 'NaN'. So, sorting
on numbers might look smart, but is actually a lot more complicated...

But your format-number brings me to the following idea. Why not use it to construct a string that
can be used to sort alphanumerically:

      <xsl:sort select="concat(format-number(@pos, '000'),
                          translate(format-number(STRUC/@pos, '000'), 'Na', '00'),
                          translate(format-number(STRUC/STRUC/@pos, '000'), 'Na', '00'))"
                data-type="text" />

The 'NaN's are less problematic here. Just convert the characters to zeros and alphanumerical
ordering will not be disturbed. This however still limits the nesting level to the amount of levels
you anticipated. If you want to do better (in XSLT 1.0?), you will have to do two steps.

In the first step you add the string that is constructed with the select expression of the sort as
an attribute to the top-most STRUC element, extending the sort-key as long as the nesting continues,
 and using a format-string with more zeros if more child elements have to be supported.

      <xsl:attribute name="sort-key">
        <xsl:value-of select="format-number(@pos, '000')" />
        <!-- recurse over nested levels -->
        <xsl:apply-templates select="STRUC" mode="add-sort-key" />
      </xsl:attribute>

...

  <xsl:template match="STRUC/STRUC" mode="add-sort-key">
    <xsl:value-of select="format-number(@pos, '000')" />
    <xsl:apply-templates select="STRUC" mode="add-sort-key" />
  </xsl:template>

In the second step you simply sort on that attribute:

          <xsl:apply-templates select="STRUC" mode="remove-sort-key">
            <xsl:sort select="@sort-key" data-type="number" />
          </xsl:apply-templates>

(removing the attribute again if it disturbs further processing)

You could use the node-set() extension function that is supported by most parsers to perform the two
passes in one stylesheet, if you like. Simply gather the result of the first pass in a variable and
pass the variable as an argument to the node-set() function and pass the result to another
apply-templates:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:exsl="http://exslt.org/common">

...

      <xsl:variable name="rtf">
        <xsl:apply-templates select="STRUC" mode="add-sort-key" />
      </xsl:variable>

      <xsl:apply-templates select="exsl:node-set($rtf)/STRUC" mode="remove-sort-key">
        <xsl:sort select="@sort-key" data-type="number" />
      </xsl:apply-templates>

Note, I'm using the exslt namespace for lesser dependence on the actual parser.

Just a last remark. It would be much easier to solve this problem if the STRUC elements were nested
as a tree. Something like:

<TOC>
  <STRUC pos="2">
    <STRUC pos="1" />
    <STRUC pos="12" />
  </STRUC>
  <STRUC pos="3">
    <STRUC pos="4" />
    <STRUC pos="4">
      <STRUC pos="1" />
    </STRUC>
  </STRUC>
</TOC>

HTH,
Geert


Current Thread
Keywords