Re: [xsl] Nodes get new IDs in function?

Subject: Re: [xsl] Nodes get new IDs in function?
From: Martynas Jusevicius <martynas.jusevicius@xxxxxxxxx>
Date: Fri, 9 Apr 2010 11:16:48 +0200
Thanks all for pointing out the xsl:copy-of, now it works like a charm.

As for the grouping approach, I'm considering it, but I can't see why
it's so much better? Performance wise? But it's using recursion as
well?
I think after I introduced parent-heading() function, my solution
became quite elegant. It's also convenient to have this function in
other situations.

Another question is, what is the desired output. You start at level 1,
what if there are no h1 elements?
Also, in your solution <li class="level4"><p>Attribution</p></li> gets
nested in an additional <ul>, which I think is unnecessary. In my
version, it gets flattened to the same level as <li class="level3">.

I'm using the same approach not only for <ul> list in XHTML, but NCX
table of contents as well. In Adobe Digital Editions, all top-level
headings are collapsed by default (children not shown until clicked),
so having empty nesting elements is not really a good idea.

Martynas

On Fri, Apr 9, 2010 at 3:29 AM, Imsieke, Gerrit, le-tex
<gerrit.imsieke@xxxxxxxxx> wrote:
> As Ken Holman (and I, http://markmail.org/message/4kneb3v2y6d4jlur)
recently
> suggested: this problem is probably better solved by a grouping approach.
>
> XSLT:
> ======8<----------------------------------
> <?xml version="1.0" encoding="utf-8"?>
> <xsl:stylesheet
>  xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
>  xmlns:xs="http://www.w3.org/2001/XMLSchema";
>  xmlns:my="URN:my"
>  version="2.0"
>  exclude-result-prefixes="my xs"
>  >
>
>  <xsl:output method="xml" indent="yes" />
>
>  <xsl:template match="/">
>    <xsl:apply-templates/>
>  </xsl:template>
>
>  <xsl:template match="body">
>    <xsl:copy>
>      <xsl:sequence select="my:hierarchize(*,1)" />
>    </xsl:copy>
>  </xsl:template>
>
>  <xsl:function name="my:hierarchize" as="element(*)*">
>    <xsl:param name="nodes" as="element(*)*" />
>    <xsl:param name="outline-level" as="xs:double?" />
>    <xsl:choose>
>      <xsl:when test="empty($outline-level)">
> <!-- no output for non-headings, as we are creating the ToC: -->
> <!--         <xsl:sequence select="$nodes" /> -->
>      </xsl:when>
>      <xsl:otherwise>
>        <ul>
>          <xsl:for-each-group select="$nodes"
>            group-starting-with="*[matches(name(), '^h\d$')]
>                                  [my:hlevel(.) = $outline-level]">
>            <xsl:choose>
>              <xsl:when test="my:hlevel(current-group()[1])
>                              = $outline-level">
>                <li class="level{$outline-level}">
>                  <p>
>                    <xsl:copy-of select="current-group()[1]/node()" />
>                  </p>
>                  <xsl:variable
>                    name="new-nodes"
>                    select="current-group()[position() gt 1]"
>                    as="node()*" />
>                  <xsl:sequence select="my:hierarchize(
>                                          $new-nodes,
>                                          min(
>                                            for $n in $new-nodes[
>                                              matches(name(), '^h\d$')
>                                            ]
>                                              return my:hlevel($n)
>                                          )
>                                        )" />
>                </li>
>              </xsl:when>
>              <xsl:otherwise>
>                <xsl:sequence select="my:hierarchize(
>                                        current-group(),
>                                        min(
>                                          for $n in current-group()[
>                                            matches(name(), '^h\d$')
>                                          ]
>                                            return my:hlevel($n)
>                                        )
>                                      )" />
>              </xsl:otherwise>
>            </xsl:choose>
>          </xsl:for-each-group>
>        </ul>
>      </xsl:otherwise>
>    </xsl:choose>
>  </xsl:function>
>
>  <xsl:function name="my:hlevel" as="xs:double">
>    <xsl:param name="elt" as="element(*)" />
>    <xsl:value-of select="number(replace(name($elt), '^h(\d)$', '$1'))" />
>  </xsl:function>
>
> </xsl:stylesheet>
> ======8<----------------------------------
>
>
> Input:
> ======8<----------------------------------
> <?xml version="1.0" encoding="utf-8"?>
> <body>
> <h1 class="Heading_20_1 ">Free Culture - How Big Media Uses Technology
> and the Law to Lock Down Culture and Control Creativity,Lawrence
> Lessig</h1>
> <h4 class="Heading_20_4 ">Attribution</h4>
> <p>...</p>
> <h3 class="Heading_20_3 ">Preface</h3>
> <h4 class="Heading_20_4 ">[Preface]</h4>
> <p>...</p>
> <h3 class="Heading_20_3 ">INTRODUCTION</h3>
> <p>...</p>
> </body>
> ======8<----------------------------------
>
>
> Output:
> ======8<----------------------------------
> <?xml version="1.0" encoding="UTF-8"?>
> <body>
>   <ul>
>      <li class="level1">
>         <p>Free Culture - How Big Media Uses Technology
> and the Law to Lock Down Culture and Control Creativity,Lawrence
> Lessig</p>
>         <ul>
>            <ul>
>               <li class="level4">
>                  <p>Attribution</p>
>               </li>
>            </ul>
>            <li class="level3">
>               <p>Preface</p>
>               <ul>
>                  <li class="level4">
>                     <p>[Preface]</p>
>                  </li>
>               </ul>
>            </li>
>            <li class="level3">
>               <p>INTRODUCTION</p>
>            </li>
>         </ul>
>      </li>
>   </ul>
> </body>
> ======8<----------------------------------
>
>
> De rien.
>
> Gerrit
>
>
>
> On 09.04.2010 02:15, Martynas Jusevicius wrote:
>>
>> Hey list,
>>
>> still struggling with the level gap-infested ToC, but I think I
>> introduced a couple of functions, I'm now on the finish line:
>>
>> <xsl:function name="o2e:heading-level" as="xs:integer">
>>     <xsl:param name="heading" as="element()"/>
>>     <xsl:value-of select="number(substring-after(local-name($heading),
>> 'h'))"/>
>> </xsl:function>
>>
>> <xsl:function name="o2e:parent-heading" as="element()*">
>>     <xsl:param name="heading" as="element()"/>
>>     <xsl:copy-of select="$heading/preceding-sibling::h:*[self::h:h1 or
>> self::h:h2 or self::h:h3 or self::h:h4 or self::h:h5 or
>> self::h:h6][o2e:heading-level(.)&lt;
>> o2e:heading-level($heading)][1]"/>
>> </xsl:function>
>>
>> The problem is, it seems that nodes returned from the parent-heading()
>> function get different IDs than identical nodes in the source
>> document. Is this the specified behavior, i.e. functions construct new
>> nodes, which get new IDs?
>>
>> For that reason this expression won't work:
>>
>> following-sibling::h:*[self::h:h1 or self::h:h2 or self::h:h3 or
>> self::h:h4 or self::h:h5 or self::h:h6][o2e:parent-heading(.) is
>> current()]
>>
>> Because the IDs won't match in the 'is' operator, even though I can
>> see the nodes are (look) identical. If I change the operator to '=' it
>> starts giving the expected results, but it is not good enough, as
>> there can be several heading elements with the same string value. Is
>> it possible to retain the original ID value somehow?
>>
>> As for the source document, it could be any XHTML with any number and
>> order of headings, for example:
>>
>> <h1 class="Heading_20_1 ">Free Culture - How Big Media Uses Technology
>> and the Law to Lock Down Culture and Control Creativity,Lawrence
>> Lessig</h1>
>> <h4 class="Heading_20_4 ">Attribution</h4>
>> <p>...</p>
>> <h3 class="Heading_20_3 ">Preface</h3>
>> <h4 class="Heading_20_4 ">[Preface]</h4>
>> <p>...</p>
>> <h3 class="Heading_20_3 ">INTRODUCTION</h3>
>> <p>...</p>
>>
>> and so on
>>
>> Martynas
>> odt2epub.com
>>
>
> --
> Gerrit Imsieke
> Geschdftsf|hrer / Managing Director
> le-tex publishing services GmbH
> Weissenfelser Str. 84, 04229 Leipzig, Germany
> Phone +49 341 355356 110, Fax +49 341 355356 510
> gerrit.imsieke@xxxxxxxxx, http://www.le-tex.de
>
> Registergericht / Commercial Register: Amtsgericht Leipzig
> Registernummer / Registration Number: HRB 24930
>
> Geschdftsf|hrer: Gerrit Imsieke, Svea Jelonek,
> Thomas Schmidt, Dr. Reinhard Vvckler

Current Thread