xsl-list
[Top] [All Lists]

Re: [xsl] Problems grouping nested items within a completely flat structure

2014-08-07 18:10:47
Hi Frank,

well yes I had to make some assumptions to write the code and one was that
your sources would not change. :) I also assumed that 'Body text' could be
the trigger to start a group. But as far as I understand now also
'Bulleted text' can. I guess the tricky part here is that there is no
explicit para element that tells you where a list starts and that the
first 'Bulleted text' of a group is the initial item of the group and part
of the list at the same time which means you have to use modes. So now I
determine the beginning of a list as follows: the preceding sibling of the
first 'Bulleted text' may not be 'Bulleted text', 'Bullet sub' or 'Note'
which should be the same as: has to be 'Chapter' or 'Body text'. You
mentioned more types of para elements - maybe you could provide some
samples. Could you also tell whether 'Note' can be placed anywhere or is
it always part of a list. Anyhow here is the modified version which still
just takes the sources of your first post into account.

Regards,
Heiko


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="2.0">
  <xsl:output method="xml" encoding="UTF-8"/>

  <xsl:template match="/textflow">
    <book>
      <xsl:for-each-group select="para" group-starting-with="para[@pgftag
eq 'Chapter']">
        <xsl:apply-templates select="."/>
      </xsl:for-each-group>
    </book>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Chapter']">
    <chapter>
      <title>
        <xsl:apply-templates select="*"/>
      </title>
      <xsl:for-each-group group-starting-with="para[@pgftag eq 'Body
text']| para[@pgftag eq 'Bulleted
text'][preceding-sibling::para[1][not(@pgftag = ('Bulleted text',
'Bullet sub', 'Note'))]]" select="current-group() except .">
        <xsl:apply-templates select="." mode="trigger"/>
      </xsl:for-each-group>
    </chapter>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Body text']" mode="trigger">
    <p>
      <xsl:apply-templates select="*"/>
    </p>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Bulleted text']" mode="trigger">
    <ul>
      <xsl:for-each-group select="current-group()"
group-starting-with="para[@pgftag = ('Bulleted text', 'Note')]">
        <xsl:apply-templates select="." mode="listitem"/>
      </xsl:for-each-group>
    </ul>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Bulleted text']" mode="listitem">
    <li>
      <xsl:apply-templates select="*"/>
    </li>
    <xsl:if test="count(current-group()) &gt; 1">
      <ul>
        <xsl:for-each-group select="current-group() except ."
group-starting-with="para[@pgftag eq 'Bullet sub']">
          <xsl:apply-templates select="."/>
        </xsl:for-each-group>
      </ul>
    </xsl:if>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Bullet sub']">
    <li>
      <xsl:apply-templates select="*"/>
    </li>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Note']" mode="listitem">
    <note>
      <xsl:apply-templates select="*"/>
    </note>
  </xsl:template>

  <xsl:template match="paraline|xref">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="render">
    <xsl:choose>
      <xsl:when test="@charformat eq 'Emphasis'">
        <em>
          <xsl:value-of select="."/>
        </em>
      </xsl:when>
      <xsl:when test="@charformat eq 'Bold'">
        <b>
          <xsl:value-of select="."/>
        </b>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="."/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!--next template for testing purposes only-->
  <xsl:template match="*">
    <bah/>
  </xsl:template>

</xsl:stylesheet>




Heiko,

thanks for your approach. While this helped me to grasp the grouping
concept in XSLT 2.0 a little bit better (but only a little bit), I noticed
that your solution is specifically tailored to the exact sequence of nodes
that I used in the example file. But, unfortunately, life doesn't work
that way:-) If, e.g. I put the first para[@pgftag='Body text'] down to
right after the note, the stylesheet yields:

<book>
   <chapter>
      <title>Introduction</title>
      <p>Display the online help as follows:</p>
      <ul>
         <li>Display the online help as follows:</li>
         <ul>
            <li>To view the help for a panel, press the help (PF1)
key.</li>
         </ul>
      </ul>
      <p>This chapter expains...</p>
      <ul>
         <li>To view the help for an input field or select a parameter
from a pop-up window, press PF1.</li>
         <li>Check relevant sections of <em>XXXX</em>.</li>
         <li>Visit our web site to get...</li>
      </ul>
[...]

Clearly not what is intended. So what is the right strategy to solve this
in a generic way? Sure, you'd want to group all sibling elements to
chapter as long as they are not indicating a new chapter or a subdivision.
But then? You cannot go for a group like

      <xsl:for-each-group group-starting-with="para[@pgftag eq 'Body
text']" select="current-group() except .">
        <p>
          <xsl:apply-templates select="*"/>
        </p>
        <ul>
          <xsl:apply-templates select="."/>
        </ul>
      </xsl:for-each-group>

when you don't know what first child element you have. Instead, I'd
imagine templates that can match single paras and don't need no know about
their context (like para[@pgftag='Body text'] or para[@pgftag='Note']. But
as soon as you have list structures I currently don't see a solution that
goes beyond grouping consecutive list items and if something like a note
drops in, you have to regard the following list item as the first item of
a new list.
And here I'm back to square one. I cannot e.g. use

      <xsl:for-each-group group-starting-with="para[@pgftag eq 'Bullet
text']" select="current-group() except .">

within the chapter template, since I don't know if and when such a list
can be expected, so to me it looks more like:

  <xsl:template match="para[@pgftag='Chapter']">
        <chapter><title><xsl:apply-templates/></title>
          <xsl:apply-templates select="all siblings that are in the same
chapter until a subdivision comes up"/>
        </chapter>
   </xsl:template>

   <xsl:template match="para[@pgftag='Bulleted text'][1]">
      <ul>
         <xsl:for-each-group select="following-sibling::*"
group-adjacent="name()">
        <xsl:apply-templates select="." mode="listitem"/>
          </xsl:for-each-group>
      </ul>
   </xsl:template>

But Saxon doesn't like that for-each-group and complains: "the only axes
allowed in a pattern are the child and attribute axes".
To complicate matters, subdivisions like '1st Section', ..., '4th Section'
may be nested, so a chapter may consist either of simple content, as shown
here, or some overview, followed by one or more subsections...
From what I've seen so far, I cannot deduce a generic solution to my
problem. Probably I'm still missing something elementary here...

Thanks so far,
Frank


-----Original Message-----
From: Heiko Niemann kontakt(_at_)heiko-niemann(_dot_)de
[mailto:xsl-list-service(_at_)lists(_dot_)mulberrytech(_dot_)com]
Sent: Mittwoch, 6. August 2014 18:09
To: xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
Subject: Re: [xsl] Problems grouping nested items within a completely flat
structure

Hi,

this should get you close to the desired result. Just add more templates
necessary.

Regards,
Heiko



<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="2.0">
  <xsl:output method="xml" encoding="UTF-8"/>

  <xsl:template match="/textflow">
    <book>
      <xsl:for-each-group select="para" group-starting-with="para[@pgftag
eq 'Chapter']">
        <xsl:apply-templates select="."/>
      </xsl:for-each-group>
    </book>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Chapter']">
    <chapter>
      <title>
        <xsl:apply-templates select="*"/>
      </title>
      <xsl:for-each-group group-starting-with="para[@pgftag eq 'Body
text']" select="current-group() except .">
        <p>
          <xsl:apply-templates select="*"/>
        </p>
        <ul>
          <xsl:apply-templates select="."/>
        </ul>
      </xsl:for-each-group>
    </chapter>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Body text']">
    <xsl:for-each-group group-starting-with="para[@pgftag = ('Bulleted
text', 'Note')]" select="current-group() except .">
      <xsl:apply-templates select="."/>
    </xsl:for-each-group>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Bulleted text']">
    <li>
      <xsl:apply-templates select="*"/>
    </li>
    <xsl:if test="count(current-group()) &gt; 1">
      <ul>
        <xsl:for-each-group select="current-group() except ."
group-starting-with="para[@pgftag eq 'Bullet sub']">
          <xsl:apply-templates select="."/>
        </xsl:for-each-group>
      </ul>
    </xsl:if>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Bullet sub']">
    <li>
      <xsl:apply-templates select="*"/>
    </li>
  </xsl:template>

  <xsl:template match="para[@pgftag eq 'Note']">
    <note>
      <xsl:apply-templates select="*"/>
    </note>
  </xsl:template>

  <xsl:template match="paraline|xref">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="render">
    <xsl:choose>
      <xsl:when test="@charformat eq 'Emphasis'">
        <em>
          <xsl:value-of select="."/>
        </em>
      </xsl:when>
      <xsl:when test="@charformat eq 'Bold'">
        <b>
          <xsl:value-of select="."/>
        </b>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="."/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!--next template for testing purposes only-->
  <xsl:template match="*">
    <bah/>
  </xsl:template>

</xsl:stylesheet>




Hi.

While there is a lot of information about grouping available, I still
have problems applying it to my particular case of a document-centric
XML file.
Obviously I haven�t yet understood it fully.

This is my (redacted) source. Please excuse its length, but this
better illustrates my problem. I use Saxon 9HE, but I am open to both,
XSL 1.0 or
2.0 solutions.

      <textflow tftag=�A�>
         <para pgftag=�Chapter�>
            <paraline>Introduction</paraline>
         </para>
         <para pgftag="Body text">
            <paraline>This chapter expains...</paraline>
         </para>
         <para pgftag="Bulleted text">
            <paraline>Display the online help as follows:</paraline>
         </para>
         <para pgftag="Bullet sub">
            <paraline>To view the help for a panel, press the help
(PF1) key.</paraline>
         </para>
         <para pgftag="Bullet sub">
            <paraline>To view the help for an input field or select a
parameter from a pop-up window, </paraline>
            <paraline>press PF1.</paraline>
         </para>
         <para pgftag="Note">
            <paraline>If you do not specify a required parameter, or
enter an incorrect one, XXXX </paraline>
            <paraline>will prompt you for the correct
information.</paraline>
         </para>
         <para pgftag="Bulleted text">
            <paraline>Check relevant sections of <render
charformat="Emphasis">XXXX</render>.</paraline>
         </para>
         <para pgftag="Bulleted text">
            <paraline>Visit our web site to get...</paraline>
         </para>
         <para pgftag="Body text">
            <paraline>The topics covered are:</paraline>
         </para>
         <para pgftag="Bulleted text">
            <paraline>
               <xref srctext="55167: 1st Section: What is
XXX?"><render charformat="Bold">What is XXX?</render></xref>
            </paraline>
         </para>
         <para pgftag="Bulleted text">
            <paraline>
               <xref srctext="55167: 1st Section: How Does XXX
Work?"><render charformat="Bold"> How Does XXX Work?</render></xref>
            </paraline>
         </para>
         <para pgftag=�Chapter�>
            <paraline>Next chapter</paraline>
         </para>
     </textflow>

The idea is, quite obviously, grouping the relevant list items, so
you�d end up (ideally!) with something like e.g.:

<book>
  <chapter>
     <title>Introduction</title>
     <p>This chapter explains...</p>
     <ul>
        <li>Display the online help as follows:</li>
        <ul>
           <li>To view the help for a panel, press the help (PF1)
key.</li>
           <li>To view the help for an input field or select a
parameter from a pop-up window, press PF1.</li>
       </ul>
        <note> If you do not specify a required parameter, or enter an
incorrect one, XXXX will prompt you for the correct
information.</note>
        <li>Check relevant sections of <em>XXXX</em>.</li>
        <li>Visit our web site to get ...</li>
     </ul>
     <p> The topics covered are:</p>
     <ul>
        <li><b>What is XXX?</b></li>
        <li><b>How Does XXX Work?</b></li>
     </ul>
   </chapter>
  <chapter>
     <title>Next chapter</title>
  </chapter>
</book>


As you can see, the source is a completely flat, linear sequence from
which I have to establish every kind of structure. Therefore, I use
something like

<xsl:template match=�textflow�>
  <book><xsl:apply-templates/></book>
</xsl:template>

<xsl:template
match=�para[@pgftag=�Chapter�]�>
    <xsl:variable name="chapter-id" select="generate-id()"/>
    <chapter>
       <title><xsl:apply-templates/></title>
         <xsl:apply-templates
select="following-sibling::*[not(self::*[@pgftag='Chapter'])]

[generate-id(preceding-sibling::para[@pgftag='Chapter'][1])
= $chapter-id]"/>
    </chapter>
</xsl:template>

<xsl:template match=�para[@pgftag=�Bulleted
text�]�>...

That is, I can�t imagine having a single template matching
textflow
in which I apply <xsl:for-each-group> for all kinds of different paras.
Instead, I use Muenchian grouping (yep, starting with XSL 1.0, but now
I use 2.0), but ran into serious recursion trouble when fiddling with
nested chapter and list structures.
The other principal problem is how to decide when a structure has
ended, because all elements are on the same sibling axis. Now, a
chapter ends, when another <para pgftag=�Chapter�> or some
<para
pgftag=�Appendix�> appears. But there is no way to decide
when the
first bulleted list in the example really ends, since the list items
may include other elements such as notes or nested lists. You could
only use criteria such as ââ?¬Å?This list has ended, when the next
paragraph is e.g.
<para @pgftag=�Body text�> or <para
@pgftag=�Chapter�>
appears�.

Now, if anyone could point me in the right direction, I�d be
very
grateful, since it�s bugging me for some time now. And, please
apologize the length...

Thank you,
Frank


Software AG ââ?¬â?? Sitz/Registered office: UhlandstraÃ?Ÿe 12, 64297
Darmstadt, Germany ââ?¬â?? Registergericht/Commercial register:
Darmstadt
HRB 1562 - Vorstand/Management Board: Karl-Heinz Streibich
(Vorsitzender/Chairman), Dr. Wolfram Jost, Arnd Zinnhardt; -
Aufsichtsratsvorsitzender/Chairman of the Supervisory Board: Dr.
Andreas Bereczky - http://www.softwareag.com




Software AG â?? Sitz/Registered office: UhlandstraÃ?e 12, 64297 Darmstadt,
Germany â?? Registergericht/Commercial register: Darmstadt HRB 1562 -
Vorstand/Management Board: Karl-Heinz Streibich (Vorsitzender/Chairman),
Dr. Wolfram Jost, Arnd Zinnhardt; - Aufsichtsratsvorsitzender/Chairman of
the Supervisory Board: Dr. Andreas Bereczky - http://www.softwareag.com




--~----------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
EasyUnsubscribe: http://lists.mulberrytech.com/unsub/xsl-list/1167547
or by email: xsl-list-unsub(_at_)lists(_dot_)mulberrytech(_dot_)com
--~--

<Prev in Thread] Current Thread [Next in Thread>