Grouping

1. Grouping, two levels.
2. Grouping
3. Sorting and Grouping
4. every expression
5. Grouping in XSLT2
6. Grouping in XSLT2
7. Grouping in xslt 2
8. Grouping; group-starting-with
9. Grouping by value
10. Grouping
11. How to find the deepest node?
12. Grouping
13. for-each Grouping
14. Selective Grouping
15. Grouping.
16. Grouping, xslt 2.0
17. Grouping, a novel use of grouping-key
18. Grouping by depth
19. group adjacent
20. Grouping by alpha key
21. Grouping in 2.0
22. for-each-group, counting ancestor paths
23. group-adjacent use
24. Working with pairs of siblings
25. Group by element name
26. List all unique element and attribute names
27. If i have a node like

1.

Grouping, two levels.

Jeni Tennison




I'm having a tough time grouping on an attribute where I need to get
only unique values of the domain attribute for each unique technology.
The problem is that the technology element is unbounded and repeats as a
child of the unbounded product element.  Looking at the XML below we see
that tech 1 belongs to both Product 1 and Product 2 and has a domain
attribute of xyz and abc.  Whenever I group and output the values to get
each unique domain I always get repeating domain values.  So grouping on
Tech 1 I would get 2 values for domain=xyz and 2 values for domain=abc
(since the domain has these values in both Product 1 and Product 2).

I've been trying to set the key as <xsl:key name="domain4tech"
match="technology/@domain" use="technology"/> but this has proved
useless.  Any suggestions?

Here's an example of the output I'm looking for.

Technology 1
domain = xyz
domain = abc

Technology 2
domain = xyz
domain = abc
domain = zzz
domain = xxx

Technology 3
domain = xyz
domain = aaa

- -------------------------------
Here's the XML

<document>
   <products>
  <product>Product1
   <technology domain="xyz">tech1</technology>
   <technology domain="abc">tech1</technology>
   <technology domain="xyz">tech2</technology>
   <technology domain="xxx">tech2</technology>
         <technology domain="xyz">tech3</technology>
      <product>
<product>Product2
   <technology domain="xyz">tech1</technology>
   <technology domain="abc">tech1</technology>
   <technology domain="zzz">tech2</technology>
   <technology domain="xxx">tech2</technology>
         <technology domain="aaa">tech3</technology>
      <product>
    <products>
<document>

I think that the reason you're running into difficulties is because this is actually a two-level grouping problem. The first level of grouping is grouping all the technology elements by their value (e.g. tech1, tech2, tech3). The second level of grouping is only required to get rid of the duplicates - you need to group the technology elements for each particular technology by their domain attribute.

In XSLT 2.0 terms, the grouping would look like:

  <xsl:for-each-group select="product/technology"
                      group-by=".">
    <xsl:sort select="." />
    <xsl:value-of select="." />
    <xsl:for-each-group select="current-group()"
                        group-by="@domain">
      domain = <xsl:value-of select="@domain" />
    </xsl:for-each-group>
  </xsl:for-each-group>

or, alternatively:

  <xsl:for-each-group select="product/technology"
                      group-by=".">
    <xsl:sort select="." />
    <xsl:value-of select="." />
    <xsl:for-each select="distinct-values(current-group()/@domain)">
      domain = <xsl:value-of select="@domain" />
    </xsl:for-each>
  </xsl:for-each-group>

2.

Grouping

Michael Kay

You'll find that this is a specific example showing how to use the new grouping facilities in http://www.w3.org/TR/xslt20 - we included it in our list of cases studies specifically because it's hard to to in XSLT 1.0.

Using Saxon 7.0, you can do

<xsl:for-each-group group-adjacent="@bullet">
  <xsl:choose>
    <xsl:when test="@bullet=0">
      <xsl:apply-templates select="current-group()" mode="p"/>
    </xsl:when>
    <xsl:when test="@bullet=1">
    <ul>
      <xsl:apply-templates select="current-group()" mode="li"/>
    </ul>
  </xsl:choose>
</xsl:for-each-group>

3.

Sorting and Grouping

Jeni Tennison



> I have the following XML:
> <a>
>     <b>4</b>
>     <b>9</b>
>     <b>6</b>
>     <b>1</b>
>     <b>8</b>
>     <b>6</b>
>     <b>4</b>
>     <b>7</b>
> </a>
>
> I am trying to generate the following ouput:
> <a>
>     <b rank="1">1</b>
>     <b rank="2">4</b>
>     <b rank="2">4</b>
>     <b rank="3">6</b>
>     <b rank="3">6</b>
>     <b rank="4">7</b>
>     <b rank="5">8</b>
>     <b rank="6">9</b>
> </a>

This resolves to

<xsl:template match="a">
  <a>
    <xsl:for-each-group select="b" group-by="." />
      <xsl:sort select="." />
      <xsl:variable name="rank" select="position()" />
      <xsl:for-each select="current-group()">
        <b rank="{$rank}">
          <xsl:value-of select="." />
        </b>
      </xsl:for-each>
    </xsl:for-each-group>
  </a>
</xsl:template>

4.

every expression

Evan Lenz


>I want to identify all elements that share some attribute 

While the magical expression you want cannot be expressed in XPath 1.0, it can easily be expressed in XPath 2.0:

document('2.xml')/outsidedata/b
    [every $att in @* satisfies current()/@*
         [.=$att and node-name(.)=node-name($att)]]

The "every" expression is called a universal quantifier[1] and enables you to test that a condition applies for *every* node in a given sequence. This is something that could not be done in general in XPath 1.0.

The node-name() function[2] returns an "expanded QName" type that consists of a local/URI pair that can in turn be compared with other expanded names. This ensures that your code is robust in the face of namespace-qualified attributes without having to write [local-name(.)=local-name($att) and namespace-uri(.)=namespace-uri($att)]. This is a welcome shorthand indeed!

Except for the node-name() function, I'm happy to report that a stylesheet with this expression (using name() instead) is executed as expected by Saxon 7.1. Below is the stylesheet I ran against your two files:

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="text"/>

  <xsl:template match="/">
    <xsl:apply-templates select="/mydata/a"/>
  </xsl:template>

  <xsl:template match="/mydata/a">
    <xsl:for-each select="document('2.xml')/outsidedata/b
                             [every $att in @* satisfies current()/@*
                                    [.=$att and name(.)=name($att)]]">
      <xsl:text>[</xsl:text>
      <xsl:value-of select="."/>
      <xsl:text>]</xsl:text>
      <xsl:if test="position() != last()">
        <xsl:text>
</xsl:text>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

Invocation and output:

$ java net.sf.saxon.Transform 1.xml test.xsl
[PPP]
[QQQ]
[RRR]
$

With your (corrected) reformulation of the original problem, I believe the expression only needs slight modification (@* trades places with current()/@*):

document('2.xml')/outsidedata/b
    [every $att in current()/@* satisfies @*
         [.=$att and node-name(.)=node-name($att)]]

Hope you found this helpful, or at least interesting!

[1] [1] [2] [2]

5.

Grouping in XSLT2

Mike Kay



>  <p>
>  <el att='a'>x</el>
>  <el att='b'>x</el>
>  <el att='c'>x</el>
>  <el att='d'>y</el>
>  </p>


> <xsl:template match='p'>
> <xsl:for-each-group select="e1" group-by=".">
>   <e1 name="{.}">
>     <xsl:for-each-group select="current-group()" group-by="@att">
>       <xsl:value-of select="concat(@att, ' ')"/>
>     </xsl:for-each>
>   </e1>
> </xsl:for-each>
> </xsl:template>

> The outer grouping groups on element content, 
>    outputs the name of the outer group.

Within <xsl:for-each-group> there is a context node, this node is the first e1 element in the current group. So name="{.}" outputs the string value of this first e1 element: "x" for the first group, "y" for the second. Since you were grouping by ".", this is actually the value that all members of the group have in common, so any element in the group would have been OK, but "." actually selects the first one.


>  The inner grouping takes each of these 'sequences' (is that the right
> word?)
>  and groups them by attribute value.
>  then outputs each these values.

That's right. The first time round the outer loop, current-group() selects the three "x" element, the second time round, it selects the "y" element.

The inner xsl:for-each-group could equally have been written:

<xsl:for-each select="distinct-values(current-group()/@att)"> since you only want to select the distinct values of the attribute, not the group of nodes associated with each distinct value.


or even 
 <xsl:value-of 
  select="distinct-values(current-group()/@att)"  separator=" "/>

6.

Grouping in XSLT2

Mike Kay


> I want to be able to convert XML code like

> <XMLCODE> 
> <a/><b/><c/><d/>
  <a/><b/><c/><d/>
  <a/><b/><c/><d/>
  <a/><b/><c/><d/>
> </XMLCODE>

> to the following XML:

> <NEWXMLCODE>
> <a>
>  <b/>
>  <c/>
>  <d/>
> </a>
> <a>
>  <b/>
>  <c/>
>  <d/>
> </a>
> <a>
>  <b/>
>  <c/>
>  <d/>
> </a>
> </NEWXMLCODE>

This is a kind of grouping problem which I call "positional grouping".

In XSLT 2.0 you can do

<xsl:for-each-group select="*" group-starting-with="a">
  <a>
   <xsl:copy-of select="current-group() except ."/>
  </a>
</xsl:for-each-group>

7.

Grouping in xslt 2

Mike Kay



> I am having some issues with understanding the for-each-group 
> mechanism 
> in XSLT 2.0
> and how I can apply it to my particular situation. Michael 
> Kay yesterday 
> pointed me at some positional grouping information which may 
> appear to 
> offer a solution. But i can't get my head around how to manage the 
> nesting aspects. That is having to output
> an opening <PART> tag and then managing all the child tags 
> and then coming back somehow to put in the closing </PART> 
> tag. I am using XSLT 
> 2.0 Saxon 7.5 and I would really like some help on  this - as 
> I am a bit 
> stuck.

> ===Problem Context =============================================

> I need to iterate through each paragraph in the Source XML 
> document (below) and depending on what the paragraph style is 
> i need to output some markup & content whilst keeping the 
> nesting (PARTS, contain DIVISIONS, contain SUBDIVISIONS 
> contain REGULATIONS contain SUB-REGULATIONS etc.) 
> intact

I think that what you need here is a nested set of for-each-groups, one for each level of the output tree. In this case it is quite deeply nested:

<xsl:template match="Document"
<xsl:for-each-group select="Paragraph" 
                    group-starting-with="*[@StyleName='PART']">
 <Part Category="PART">
   <xsl:copy-of select="child::node()"/>
   <xsl:for-each-group select="current-group() except ."
                       group-starting-with="*[@StyleName='DIVISION']">
     <Part Category="DIVISION">
       <xsl:copy-of select="child::node()"/>
       <xsl:for-each-group select="current-group() except ."
 
group-starting-with="*[@StyleName='SUBDIVISION']">

and so on.

It might be possible to make the code neater by making it table-driven:

<xsl:template match="Document"
<xsl:for-each-group select="Paragraph" 
                    group-starting-with="*[@StyleName='PART']">
  <xsl:apply-templates select="."/>
</xsl:template>

<xsl:template match="Paragraph">
  <Part Category="{@StyleName}">
    <xsl:copy-of select="child::node()"/>
    <xsl:for-each-group select="current-group() except ."
       group-starting-with="*[@StyleName=f:child(@StyleName)]">
      <xsl:apply-templates select="."/>
    </
  </
</

<xsl:function name="f:child" as="xs:string">
  <xsl:param name="level" as="xs:string">
  <xsl:choose>
  <xsl:when test="$level='PART'">DIVISION</xsl:when>
  <xsl:when test="$level='DIVISION'">SUBDIVISION</xsl:when>
  etc
  
</
       

=== Source XML Document ========================================

<Document>
     <Paragraph StyleName="PART">..................</Paragraph>
     <Paragraph StyleName="DIVISION">..............</Paragraph>
     <Paragraph StyleName="SUBDIVISION">...........</Paragraph>
     <Paragraph StyleName="REGULATION">............</Paragraph>
     <Paragraph StyleName="SUB-REGULATION">........</Paragraph>
     <Paragraph StyleName="PARAGRAPH">.............</Paragraph>
     <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph>
     <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph>
     <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph>
     <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph>
     <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph>
     <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph>
     <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph>
     <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph>
     <Paragraph StyleName="PARAGRAPH">.............</Paragraph>
     <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph>
     <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph>
     <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph>
     <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph>
     <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph>
     <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph>
     <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph>
     <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph>
     <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph>
     <Paragraph StyleName="SUB-REGULATION">........</Paragraph>
     <Paragraph StyleName="SUB-REGULATION">........</Paragraph>
     <Paragraph StyleName="SUB-REGULATION">........</Paragraph>
     <Paragraph StyleName="SUB-REGULATION">........</Paragraph>
     <Paragraph StyleName="NOTE">..................</Paragraph>
 </Document>

> === Required Output ======================

> <Regulation>
>     <Part Category="PART">
>         <Part Category = "DIVISION">
>             <Part Category = "SUBDIVISION">
>                 <Article>
>                     <Sub-Article>
>                         <Paragraph>
>                             <Sub-Paragraph/>
>                             <Sub-Paragraph>
>                                 <Sub-Sub-Paragraph>
>                                 <Sub-Sub-Paragraph>
>                             <Sub-Paragraph/>
>                             <Sub-Paragraph/>
>                             <Sub-Paragraph>
>                                 <Sub-Sub-Paragraph/>
>                                 <Sub-Sub-Paragraph/>
>                             </Sub-Paragraph>
>                         </Paragraph>
>                     </Sub-Article>
>                </Article>
>             </Part>
>         </Part>
>     </Part>
> </Regulation>
> 

8.

Grouping; group-starting-with

Jeni Tennison



<x>
<y id="1"/>
  <b id="1"/>
  <b id="2"/>
<y id="2"/>
  <b id="3"/>
<y id="3"/>
  <b id="4"/>
  <b id="5"/>
  <b id="6"/>
</x>

how do I select only <b>'s that follow particular <y>'s; to then create

<x>
<y id="1"><b id="1"/><b id="2"/></y>
<y id="2"><b id="3"/></y>
<y id="3"><b id="4"/><b id="5"/><b id="6"/></y> </x>

Jeni responds

In XSLT 2.0, you use group-starting-with to say that you want to group the child elements of <x> in the order that they appear, with each group starting with the <y> element. The contents of the group can be found using current-group() -- the first element in the resulting sequence will be the <y> element, and the rest will be <b> elements.

  <xsl:for-each-group select="/x/*"
                      group-starting-with="y">
    <y id="{@id}">
      <xsl:copy-of select="current-group()[self::b]" />
    </y>
  </xsl:for-each-group>

9.

Grouping by value

David Carlisle


>In numeric style citations, the in-text markers look like [1]. 
>If one has multiple references, then, you'd get [1, 2, 3].

>So how can I get [1-3] or (in some cases) [1-3, 5]?

range.xml

<a>
<r><x>1</x><x>3</x><x>5</x><x>7</x></r>
<r><x>1</x><x>2</x><x>3</x><x>4</x></r>
<r><x>1</x><x>2</x><x>3</x><x>8</x></r>
</a>

range.xsl

<xsl:stylesheet
   version="2.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="r">
<xsl:text>
[</xsl:text>
<xsl:for-each-group select="x" 
         group-adjacent="
           if (position()=1 or 
            .=preceding-sibling::x[1]+1) 
           then -1 
           else position() "> 
  <xsl:value-of select="."/> 
  <xsl:if test="current-group()[2]"> 
    <xsl:text>-</xsl:text> 
    <xsl:value-of select="current-group()[last()]"/>
  </xsl:if>
  <xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each-group>
<xsl:text>]</xsl:text>
</xsl:template>



</xsl:stylesheet>

$ java -jar ../saxon8/saxon8.jar  range.xml range.xsl <?xml version="1.0" encoding="UTF-8"?>


[1,3,5,7]

[1-4]

[1-3,8]

10.

Grouping

Michael Kay

Given this XML file:

<?xml version="1.0" encoding="UTF-8"?>
<tables>
  <table name="table1">
    <column name="col1"/>
    <column name="col2"/>
    <column name="col3"/>
    <column name="col4"/>
  </table>
  <table name="table2">
    <column name="col1"/>
    <column name="col2"/>
    <column name="col5"/>
    <column name="col6"/>
  </table>
  <table name="table3">
    <column name="col1"/>
    <column name="col2"/>
    <column name="col7"/>
    <column name="col8"/>
  </table>
</tables>

Find the columns shared by all tables, by column name.

The solution (in XSL 2.0):

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

  <xsl:template match="/">
    <commonColumns>
      <xsl:apply-templates/>
    </commonColumns>
  </xsl:template>

  <xsl:template match="tables">
    <xsl:for-each-group select="table/column" group-by="@name">
      <xsl:variable name="name" select="@name"/>
      <xsl:if
test="count(/tables/table/column[@name=$name])=count(/tables/table)">
        <columnName><xsl:value-of select="@name"/></columnName>
      </xsl:if>
    </xsl:for-each-group>
  </xsl:template>

</xsl:stylesheet>

Produces the following output with Saxon 8:

<?xml version="1.0" encoding="UTF-8"?>
<commonColumns>
  <columnName>col1</columnName>
  <columnName>col2</columnName>
</commonColumns>

Jay Bryant

My suggestion would be to replace this:

   <xsl:for-each-group select="table/column" group-by="@name">
>       <xsl:variable name="name" select="@name"/>
>       <xsl:if
> test="count(/tables/table/column[@name=$name])=count(/tables/table)">
>         <columnName><xsl:value-of select="@name"/></columnName>
>       </xsl:if>
>     </xsl:for-each-group>

by this:

  <xsl:for-each-group select="table/column" group-by="@name">
    <xsl:if test="count(current-group())=count(/tables/table)">
        <columnName><xsl:value-of
select="current-grouping-key()"/></columnName>
    </xsl:if>
  </xsl:for-each-group>

You could also move count(/tables/table) outside the loop into a variable.

Note that the algorithm only works if a column name can appear in a table at most once.

Michael Kay


> I  also thought about the one-such-name-to-a-table problem,

For your particular application the source data obviously satisfies this constraint, but the constraint needs to be stated in case someone tries to apply the same algorithm to a different situation where the constraint doesn't hold.

It's not hard to generalize the solution: instead of

   count(current-group()) = $number-of-tables

you want something like

   count(current-group()/parent::table) = $number-of-tables

relying on the fact that "/" eliminates duplicates.


    then when processing col5, count(current-group()) will be 2, but
    count(current-group()/parent::table) will be 1, because 
    both columns in the group have the same parent element.

Thanks Mike. Now I see it. xslt 2.0 stylesheet below demonstrates it.

regards DaveP

  <xsl:variable name="table-count" select="count(/tables/table)"/>

 <xsl:template match="tables">
    <xsl:for-each-group select="table/column" group-by="@name">
    <xsl:if test="count(current-group())=$table-count">
        <columnName><xsl:value-of
select="current-grouping-key()"/></columnName>
    </xsl:if>


      <p><i>count(current-group())<xsl:value-of 
                 select="count(current-group()) "/></i></p>
      <p><i>count(current-group())/parent::table <xsl:value-of 
                 select="count(current-group()/parent::table) "/></i></p>
  </xsl:for-each-group>
   
  </xsl:template>

11.

How to find the deepest node?

Mike Kay



> I have the following XML-file (see below) and I need to find the 
> deepest node of 'title'.
> The resultfile should only contain the titles 'Text 02' and 'Text 03', 
> because they are in this case the deepest nodes. The next XML-file I 
> receive may contain more nested levels of items and whatever the level 
> of nesting I only need to find the deepest titles.
> I know I have to do something with recurse, but I don't know how to 
> start.


> <?xml version="1.0" encoding="UTF-8"?> <document>
>   <item>
>     <item>
>       <title>Text 01</title>
>     </item>
>     <item>
>       <item>
>         <title>Text 02</title>
>       </item>
>     </item>
>   </item>
>   <item>
>     <item>
>       <item>
>         <title>Text 03</title>
>       </item>
>     </item>
>   </item>
>   <item>
>     <item>
>       <title>Text 04</title>
>     </item>
>   </item>
> </document>

> Resultfile should contain:

> <?xml version="1.0" encoding="UTF-8"?> <document>
>   <item>
>     <item>
>       <item>
>         <title>Text 02</title>
>       </item>
>     </item>
>   </item>
>   <item>
>     <item>
>       <item>
>         <title>Text 03</title>
>       </item>
>     </item>
>   </item>
> </document>

<xsl:for-each-group select="//title"
  group-by="count(ancestor::*)">
  <xsl:sort select="current-grouping-key()">
  <xsl:if test="position()=last()">
   <xsl:copy-of select="current-group()"/>
  </
</

12.

Grouping

David Carlisle and Dimitre Novatchev.



> Group the following XML into 2 periods.  The periods 
> are arbitrary, but for this example they happen to be:
> Period 1:  1 - 12
> Period 2:  14 - 30
> I.e. non-overlapping ranges.
>
> Expected Result:
> <result>
>  <period begins="1" ends="12">
>    <B period_begin="1" period_end="5"/>
>    <B period_begin="2" period_end="7"/>
>    <B period_begin="3" period_end="10"/>
>    <B period_begin="4" period_end="12"/>  </period>  <period 
> begins="14" ends="30">
>    <B period_begin="14" period_end="16"/>
>    <B period_begin="16" period_end="20"/>
>    <B period_begin="16" period_end="30"/>  </period> </result>

using Dimitre's test file (which has sorted input) here's a simplish pure xslt1 solution, no node set or other extensions.

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


<xsl:output indent="yes"/>

<xsl:template match="A">
<result>
<xsl:apply-templates select="B[1]"/>
</result>
</xsl:template>

<xsl:template match="B">
 <xsl:param name="b" select="@period_begin"/>  
 <xsl:param name="e" select="@period_end"/>  
 <xsl:param name="g" select="/.."/> 
 <xsl:variable name="e2" select="@period_end[. &gt; $e]|$e[. &gt;= current()/@period_end]"/> 
  <xsl:choose> 
    <xsl:when test="../B[@period_begin &lt;=$e2 and @period_end &gt; $e2]">  
     <xsl:apply-templates select="following-sibling::B[1]">
  <xsl:with-param name="b" select="$b"/>
  <xsl:with-param name="e" select="$e2"/>
  <xsl:with-param name="g" select="$g|."/>  </xsl:apply-templates> </xsl:when>
  <xsl:otherwise>
  <period begins="{$b}" ends="{$e2}">
    <xsl:copy-of select="$g|."/>
  </period>
 <xsl:apply-templates select="following-sibling::B[1]"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>


</xsl:stylesheet>

With input

<A>
        <B period_begin="1" period_end="5"/>
        <B period_begin="2" period_end="7"/>
        <B period_begin="3" period_end="10"/>
        <B period_begin="4" period_end="12"/>
        <B period_begin="14" period_end="16"/>
        <B period_begin="16" period_end="20"/>
        <B period_begin="16" period_end="30"/>
        <B period_begin="32" period_end="33"/>
        <B period_begin="33" period_end="38"/>
</A>

Produces this output

<?xml version="1.0" encoding="utf-8"?>
<result>
   <period begins="1" ends="12">
      <B period_begin="1" period_end="5"/>
      <B period_begin="2" period_end="7"/>
      <B period_begin="3" period_end="10"/>
      <B period_begin="4" period_end="12"/>
   </period>
   <period begins="14" ends="30">
      <B period_begin="14" period_end="16"/>
      <B period_begin="16" period_end="20"/>
      <B period_begin="16" period_end="30"/>
   </period>
   <period begins="32" ends="38">
      <B period_begin="32" period_end="33"/>
      <B period_begin="33" period_end="38"/>
   </period>
</result>

and from Dimitre

An XSLT 2.0 solution, but using f:foldl(.) This can be re-written 1:1 in XSLT 1.0 + FXSL for XSLT 1.0.

The reason I'm posting this is because it resembles very much the "functional tokenizer" (see for example: xslt archive) and your problem can be called something like: "interval tokenization".

I still cannot fully assimilate the meaning and implications of this striking similarity but it seems to prove that there's law, order and elegance in the world of functional programming.

Here's the transformation:

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:foldl-func="foldl-func"
xmlns:f="http://fxsl.sf.net/"
exclude-result-prefixes="f foldl-func"
>

   <xsl:import href="../f/func-foldl.xsl"/>
   
   <xsl:output omit-xml-declaration="yes" indent="yes"/>

<!--
    This transformation must be applied to:  
        ../data/periods.xml                  
-->
   <xsl:variable name="vFoldlFun" as="element()">
           <foldl-func:foldl-func/>
   </xsl:variable>
   
   <xsl:variable name="vA0" as="element()+">
     <period start="0" end="0"/>
   </xsl:variable>

    <xsl:template match="/">
      <xsl:sequence select="f:foldl($vFoldlFun, $vA0, /*/* )[position() > 1]"/>
    </xsl:template>
    
    <xsl:template match="foldl-func:*" as="element()+"
     mode="f:FXSL">
       <xsl:param name="arg1"/>
       <xsl:param name="arg2"/>
       
       <xsl:variable name="vLastPeriod" select="$arg1[last()]"/>
         
       <xsl:choose>
         <xsl:when test=
            "number($arg2/@period_begin) > number($vLastPeriod/@end)">
           <xsl:sequence select="$arg1"/>
           <period start="{$arg2/@period_begin}" end="{$arg2/@period_end}"/>
         </xsl:when>
         <xsl:otherwise>
           <xsl:sequence select="$arg1[not(. is $vLastPeriod)]"/>
           <xsl:choose>
             <xsl:when test="number($arg2/@period_end) > number($vLastPeriod/@end)">
               <period start="{$vLastPeriod/@start}" end="{$arg2/@period_end}"/>
             </xsl:when>
             <xsl:otherwise>
               <xsl:sequence select="$vLastPeriod"/>
             </xsl:otherwise>
           </xsl:choose>
         </xsl:otherwise>
       </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

When applied on the same source xml document:

<A>
        <B period_begin="1" period_end="5"/>
        <B period_begin="2" period_end="7"/>
        <B period_begin="3" period_end="10"/>
        <B period_begin="4" period_end="12"/>
        <B period_begin="14" period_end="16"/>
        <B period_begin="16" period_end="20"/>
        <B period_begin="16" period_end="30"/>
        <B period_begin="32" period_end="33"/>
        <B period_begin="33" period_end="38"/>
</A>

it produces the wanted result:

<period start="1" end="12"/>
<period start="14" end="30"/>
<period start="32" end="38"/>

13.

for-each Grouping

Mike Kay, David Carlisle



I want the html output will look like this:

USA
Group     Name        id
AAA       Adel          12345
AAA       Barry         12346
AAA       Carl           12347
BBB       Dave          12345
BBB       Ethel          12346
BBB       Fred           12347

EUR
Group    Name         id
CCC      George       24567
CCC      Harold        23458
CCC      Jennifer      23459



from this Sample XML file
<report>
      <column>name</column>
      <column>country</column>
      <column>group</column>
      <table>
              <date>07-13-2006
                      <group type="AAA">111111
                              <entry>
                                      <name>Adel</name>
                                      <country>USA</country>
                                      <id>12345</id>
                              </entry>
                              <entry>
                                      <name>Barry</name>
                                      <country>USA</country>
                                      <id>12346</id>
                              </entry>
                              <entry>
                                      <name>Carl</name>
                                      <country>USA</country>
                                      <id>12347</id>
                              </entry>
                      </group>
                      <group type="BBB">111111
                              <entry>
                                      <name>Dave</name>
                                      <country>USA</country>
                                      <id>12345</id>
                              </entry>
                              <entry>
                                      <name>Ethel</name>
                                      <country>USA</country>
                                      <id>12346</id>
                              </entry>
                              <entry>
                                      <name>Fred</name>
                                      <country>USA</country>
                                      <id>12347</id>
                              </entry>
                      </group>
                      <group type="CCC">111111
                              <entry>
                                      <name>George</name>
                                      <country>EUR</country>
                                      <id>24567</id>
                              </entry>
                              <entry>
                                      <name>Harold</name>
                                      <country>EUR</country>
                                      <id>23458</id>
                              </entry>
                              <entry>
                                      <name>Jennifer</name>
                                      <country>EUR</country>
                                      <id>23459</id>
                              </entry>
                      </group>
              </date>
      </table>
</report>

This is a classic grouping problem which is best tackled using xsl:for-each-group in XSLT 2.0,

 <xsl:for-each-group select="//entry" group-adjacent="country">
    <h2><xsl:value-of select="current-grouping-key()"/></h2>
    <table>
    <thead>...</thead>
    <tbody>
      <xsl:for-each select="current-group()">
        <tr>
         <td><xsl:value-of select="../@type"/></td>
         <td><xsl:value-of select="name"/></td>
         <td><xsl:value-of select="id"/></td>

David C comments

Others have given solutions but let me just observe (given a recent thread about mutable variables) that the solutions follow the natural human language description rather than a solution that you'd program in an imperative programming language.

If you were describing the problem to a person you'd say that you want to group the items by country and put a country heading at the top of each group.

You wouldn't (or at least I wouldn't) introduce the notion of state and some temporary variable holding the last seen country and say that for each item you need to compare the current country with the value of the variable stored at the last item.

The human-oriented description does not introduce any variables at all, and the XSLT solutions typically don't use any variables either. This is almost always the case when someone more familiar with imperative programming languages has problems with the fact that xsl variables "don't change". Usually it's not that you don't need to change the value of a variable, it's that you don't need a variable at all.

14.

Selective Grouping

Various


> I have problem with some "strange" type of data which i have
> to convert to a hierarchical xml structure.
>
> My source is a huge xml file which represents a decimal
> classification. It contains so called documents, where each
> document represents one node of the classification.
> Furthermore each documents shows the direct parents of a
> node. It's a structure like this (example taken from
> http://www.udcc.org):
> ...
> <document>
>     <tag1>3</tag1>
>     <tag1a>Social Sciences</tag1a>
> </document>
> <document>
>     <tag1>3</tag1>
>     <tag1a>Social Sciences</tag1a>
>     <tag2>32</tag2>
>     <tag2a>Politics</tag2a>
> </document>
> <document>
>     <tag1>3</tag1>
>     <tag1a>Social Sciences</tag1a>
>     <tag2>32</tag2>
>     <tag2a>Politics</tag2a>
>     <tag3>326</tag3>
>     <tag3a>Slavery</tag3a>
> </document>
> ...
> As you can see there is no hierarchical information in it
> instead of the names and the sequence of the tags. In my real
> data i have up to 9 levels, but not every time. My result
> should look like this (or something similar):
> ...
> <node id="3" name="Social Science">
>    <node id="32" name="Politics">
>       <node id="326" name="Slavery"/>
>    </node>
> </node>
> ...
> I have simply no idea what to start with to archive this
> result. I guess the first step would be to get rid of all
> those redundant content, but i don't know how. And i even
> can't figure out how to build the hierachichal structure the
> same time.
>
> Has anyone a good starting point for this?

The key to this is to do a depth-first recursive traversal of the tree by starting with the root, and for each node processing its children using xsl:apply-templates, but with one key difference: you need to select the logical root and the logical children, rather than the XML root and the XML children.

I'm not sure how you identify the logical root in your structure. The logical children of a node N are those nodes that have a child element equal (in name and value) to every child element of N: that is in XSLT 2.0:

<xsl:template match="document">
  <xsl:variable name="this" select="."/>
  <xsl:apply-templates select="//document[
     every $c in $this/* satisfies (some $d in ./* satisfies deep-equals($c,
$d))]"/>

DC writes

This is a grouping problem, in XSLT2 I think you just want

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>

  <xsl:strip-space elements="*"/>
  <xsl:output indent="yes"/>

  <xsl:template match="x" name="group">
    <xsl:param name="level" select="1"/>
    <xsl:param name="items" select="document"/>
    <xsl:for-each-group select="$items"
group-by="*[name()=concat('tag',$level)]">
      <node id="{current-grouping-key()}"
name="{*[name()=concat('tag',$level,'a')]}">
    <xsl:call-template name="group">
      <xsl:with-param name="level" select="$level+1"/>
      <xsl:with-param name="items" select="current-group()"/>
    </xsl:call-template>
      </node>
    </xsl:for-each-group>
  </xsl:template>
</xsl:stylesheet>
<x>
<document>
    <tag1>3</tag1>
    <tag1a>Social Sciences</tag1a>
</document>
<document>
    <tag1>3</tag1>
    <tag1a>Social Sciences</tag1a>
    <tag2>32</tag2>
    <tag2a>Politics</tag2a>
</document>
<document>
    <tag1>3</tag1>
    <tag1a>Social Sciences</tag1a>
    <tag2>32</tag2>
    <tag2a>Politics</tag2a>
    <tag3>326</tag3>
    <tag3a>Slavery</tag3a>
</document>
<document>
    <tag1>3</tag1>
    <tag1a>Social Sciences</tag1a>
    <tag2>33</tag2>
    <tag2a>Economics</tag2a>
</document>
</x>
$ saxon8 grp2.xml grp2.xsl
<?xml version="1.0" encoding="UTF-8"?>
<node id="3" name="Social Sciences">
   <node id="32" name="Politics">
      <node id="326" name="Slavery"/>
   </node>
   <node id="33" name="Economics"/>
</node>

15.

Grouping.

Michael Kay

How do I wrap the scenes and characters.







 <play>
  <scene>Scene 1</scene>
      <character>char 1</character>
        <line>blah blah blah</line>
        <line>blah blah blah</line>
        <line>blah blah blah</line>
      <character>char 2</character>
       <line>blah blah blah</line>
       <line>blah blah blah</line>
        <line>blah blah blah</line>
    <character>char 3</character>
       <line>blah blah blah</line>
       <line>blah blah blah</line>
       <line>blah blah blah</line>
 <scene>Scene 2</scene>...
     <character>char 1</character>
        <line>blah blah blah</line>
        <line>blah blah blah</line>
        <line>blah blah blah</line> ...
 </play>

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

<xsl:output method="xml" indent="yes" />

<xsl:template match="/play">
 <play>
   <xsl:apply-templates select="scene" />
 </play>
</xsl:template>

<xsl:template match="scene">
 <scene name="{.}">
   <xsl:apply-templates select="following-sibling::character[1]"/>
 </scene>
</xsl:template>

<xsl:template match="character">
   <character name="{.}">
       <xsl:apply-templates select="following-sibling::*[1][self::line]"/>
   </character>
   <xsl:apply-templates
select="following-sibling::*[self::character|self::scene][1][self::character
]"/>
</xsl:template>

<xsl:template match="line">
   <xsl:copy-of select="."/>
   <xsl:apply-templates select="following-sibling::*[1][self::line]"/>
</xsl:template>

</xsl:stylesheet>

I call the technique "sibling recursion". It's more concise than most recursive code, because it's self-terminating: you don't need to test explicitly whether there are more items to process, because apply-templates does that automatically for you.

16.

Grouping, xslt 2.0

David Carlisle


Given two XML fragments

<a>
  <b1/>
  <b2/>
  <c/>
</a>

and

<a>
  <b2/>
  <c/>
</a>

I would like to wrap <b1/><b2/> into a <b/> container, and the same
should happen to <b2/> when occurring alone. The other elements can be
assumed to remain unchanged.

I think you want

<xsl:for-each-group select="*" group-adjacent="starts-with(local-name(),'b')">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<b>
<xsl:copy-of select="current-group()"/>
</b>
<xsl:otherwise>
<xsl:copy-of select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>

17.

Grouping, a novel use of grouping-key

David Carlise



input.xml (I left blank lines to emphasize the variations):

<root>
  <flatsequence>
    <a>a</a>
    <b>b</b>
    <c>c</c>
    <d>d</d>
    <e>e</e>
    <f>f</f>

    <h>h</h>
    <i>i</i>
    <j>j</j>
  </flatsequence>
  <flatsequence>
    <a>a</a>

    <c>c</c>
    <d>d</d>
    <e>e</e>
    <f>f</f>
    <g>g</g>
    <h>h</h>
    <i>i</i>
    <j>j</j>
  </flatsequence>
</root>

desired output:

<root>
  <hierarchy>
    <a>a</a>
    <container1>
      <b>b</b>
      <c>c</c>
    </container1>
    <d>d</d>
    <e>e</e>
    <f>f</f>
    <container2>

      <h>h</h>
      <i>i</i>
    </container2>
    <j>j</j>
  </hierarchy>
  <hierarchy>
    <a>a</a>
    <container1>

      <c>c</c>
    </container1>
    <d>d</d>
    <e>e</e>
    <f>f</f>
    <container2>
      <g>g</g>
      <h>h</h>
      <i>i</i>
    </container2>
    <j>j</j>
  </hierarchy>
</root>

This is, I want to group certain element sequences (of which the first
element may be optional) that are occurring within a flat sequence into
some container, and group other element sequences within the same flat
sequence the same way into other containers, while leaving the rest
unchanged. The elements to group, however, are not easily identified
formally as their names are dissimilar, so I suppose I will have to
spell out their names.

I am stuck as far as
creating container2 in the same run is concerned:

Answer

>       <!-- how to match g, h and i as well within the same
>       "for-each-group"? -->

Two ways.

I used a grouping key of exists(self::b|self::c) so it only has two states to group or not to group so like that you can only group one thing at a time, but that's OK as you can have more than one pass.

  <xsl:template match="flatsequence">
 <hierarchy>
   <xsl:variable name="p1">
   <xsl:for-each-group
        select="*" group-adjacent="exists(self::b|self::c)"> ...
  </xsl:variable>
   <xsl:for-each-group
        select="$p1/*"
 group-adjacent="exists(self::h|self::h|self::i)"> ...
   </xsl:for-each-group>
    </hierarchy>
  </xsl:template>

Or you do it in one pass with a grouping key that has three states (and an xsl:choose with three branches.

  <xsl:template match="flatsequence">
 <hierarchy>
<xsl:for-each-group
        select="*" group-adjacent="if (exists(self::b|self::c)) then 1
                                   if (exists(self::h|self::i|self::i)) then 2
                                   else 0">
  <xsl:choose>
   <xsl:when test="current-grouping-key()=1">
     <group1><xsl:copy-of select="current-group()"/>...
   <xsl:when test="current-grouping-key()=2">
     <group2><xsl:copy-of select="current-group()"/>...
   <xsl:otherwise>
  <xsl:copy-of select="current-group()"/>..

18.

Grouping by depth

David Carlisle




I'm trying to take a flat db structure from a query and
transform it into a hierarchical xml structure.

This is the flat structure from the db:

<data>
 <row>
    <section_depth>1</section_depth>
    <section_title>Home</section_title>
 </row>
 <row>
    <section_depth>2</section_depth>
    <section_title>Events</section_title>
 </row>
 <row>
    <section_depth>3</section_depth>
    <section_title>Event 1</section_title>
 </row>
 <row>
    <section_depth>3</section_depth>
    <section_title>Event 2</section_title>
 </row>
 <row>
    <section_depth>2</section_depth>
    <section_title>Equipment</section_title>
 </row>
 <row>
    <section_depth>2</section_depth>
    <section_title>News</section_title>
 </row>
 <row>
    <section_depth>3</section_depth>
    <section_title>News 1</section_title>
 </row>
 <row>
    <section_depth>3</section_depth>
    <section_title>News 2</section_title>
 </row>
</data>

I'm looking to transform this with XSLT 2, into this using the section_depth
as the indentation, can always assume the flat structure will be in the
right order:

<site>
 <section>
    <title>Home</title>
    <section>
       <title>Events</title>
       <section>
          <title>Event 1</title>
       </section>
       <section>
          <title>Event 2</title>
       </section>
    </section>
    <section>
       <title>Equipment</title>
    </section>
    <section>
       <title>News</title>
       <section>
          <title>News 1</title>
       </section>
       <section>
          <title>News 2</title>
       </section>
    </section>
 </section>

I've played around with grouping but my head started to hurt.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="2.0">

<xsl:output indent="yes"/>

<xsl:template name=" group" match="data">
<xsl:param name="r" select="row"/>
<xsl:param name="d" select="1"/>
<xsl:for-each-group select="$r" group-starting-with="*[section_depth=$d]">
 <section>
   <title><xsl:value-of select="section_title"/></title>
   <xsl:call-template name="group">
     <xsl:with-param name="r" select="current-group()[section_depth&gt;$d]"/>
     <xsl:with-param name="d" select="$d+1"/>
   </xsl:call-template>
 </section>
</xsl:for-each-group>
</xsl:template>

</xsl:stylesheet>

$ saxon8 dep.xml dep.xsl

<?xml version="1.0" encoding="UTF-8"?>
<section>
  <title>Home</title>
  <section>
     <title>Events</title>
     <section>
        <title>Event 1</title>
     </section>
     <section>
        <title>Event 2</title>
     </section>
  </section>
  <section>
     <title>Equipment</title>
  </section>
  <section>
     <title>News</title>
     <section>
        <title>News 1</title>
     </section>
     <section>
        <title>News 2</title>
     </section>
  </section>
</section>

19.

group adjacent

David Carlisle



I want to take

<x>
<abc>1</abc>
<foo>bbb</foo>
<abc>b</abc>
<xyz>b</xyz>
<xyz>c</xyz>
<foo>bbb</foo>
<zzz/>
<hhh/>
<xyz>d</xyz>
</x>

and group adjacent runs of abc and xyz to produce

(using copy-of rather than apply-templates for simplicity)

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>

<xsl:output indent="yes"/>

 <xsl:template match="x">
   <x>
     <xsl:for-each-group select="*"
group-adjacent="exists(self::xyz|self::abc)">
   <xsl:choose>
     <xsl:when test="current-grouping-key()">
       <wrap>
         <xsl:copy-of select="current-group()"/>
       </wrap>
     </xsl:when>
     <xsl:otherwise>
       <xsl:copy-of select="current-group()"/>
     </xsl:otherwise>
   </xsl:choose>
     </xsl:for-each-group>
   </x>
 </xsl:template>
</xsl:stylesheet>

$ saxon8 wrap.xml wrap.xsl

<?xml version="1.0" encoding="UTF-8"?>
<x>
  <wrap>
     <abc>1</abc>
  </wrap>
  <foo>bbb</foo>
  <wrap>
     <abc>b</abc>
     <xyz>b</xyz>
     <xyz>c</xyz>
  </wrap>
  <foo>bbb</foo>
  <zzz/>
  <hhh/>
  <wrap>
     <xyz>d</xyz>
  </wrap>
</x>

20.

Grouping by alpha key

Dimitre Novatchev



I have a list of authors with a name element and a file-as attribute
(XPATH: /booklist/author/name/@file-as).  I currently build a Table of
Contents sorted by the author/name/@file-as attribute.  That works OK.
I want to add further markup (by putting authors in groups by the
first character of their last name).

I can get the first character of the last name, I've even created a
key that allows me to access the nodes of authors by the first
character in the last name (see the commented out section of the b.xsl
file included below).  However, I can not figure out how to get a list
of distinct characters (from the last names) so that I can iterate
over them and generate my list.

Here are stripped down example source files:

Exmaple author file: (file a.xml)

<?xml version="1.0" encoding="UTF-8"?>
<booklist>
  <author>
     <name file-as="Adams, Douglas">Douglas Adams</name>
  </author>
  <author>
     <name file-as="Anderson, Kevin J.">Kevin J. Anderson</name>
  </author>
  <author>
     <name file-as="Anthony, Piers">Piers Anthony</name>
  </author>
  <author>
     <name file-as="Archer, Jeffrey">Jeffrey Archer</name>
  </author>
  <author>
     <name file-as="Baldacci, David">David Baldacci</name>
  </author>
  <author>
     <name file-as="Ball, Margaret">Margaret Ball</name>
  </author>
  <author>
     <name file-as="Bradley, Marion Zimmer">Marion Zimmer Bradley</name>
  </author>
  <author>
     <name file-as="Carcaterra, Lorenzo">Lorenzo Carcaterra</name>
  </author>
  <author>
     <name file-as="Card, Orson Scott">Orson Scott Card</name>
  </author>
  <author>
     <name file-as="Chalker, Jack L.">Jack L. Chalker</name>
  </author>
  <author>
     <name file-as="Dahl, Roald">Roald Dahl</name>
  </author>
  <author>
     <name file-as="Daley, Brian">Brian Daley</name>
  </author>
  <author>
     <name file-as="Dann, Jack">Jack Dann</name>
  </author>
</booklist>

I want the first letter of their last names as the the main entry, like so

       A
               A authors
       B
              B authors
       etc..

The solution is as easy as this:

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
>
 <xsl:output method="text"/>

<xsl:template match="/">
  <xsl:for-each-group select="*/author"
    group-by="substring(name/@file-as,1,1)"
   >
    <xsl:text>&#xA;&#xA;</xsl:text>
    <xsl:value-of select="current-grouping-key()"/>
    <xsl:text>&#xA;</xsl:text>

    <xsl:for-each select="current-group()">
      <xsl:value-of select="name/@file-as"/>
      <xsl:text>&#xA;</xsl:text>
    </xsl:for-each>
  </xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

and when applied on the provided xml document it produces the wanted result:

A
Adams, Douglas
Anderson, Kevin J.
Anthony, Piers
Archer, Jeffrey

B
Baldacci, David
Ball, Margaret
Bradley, Marion Zimmer

C
Carcaterra, Lorenzo
Card, Orson Scott
Chalker, Jack L.

D
Dahl, Roald
Daley, Brian
Dann, Jack

Ken Holman offers the xslt 1.0 solution

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="1.0">
  <xsl:output method="text"/>

  <xsl:key name="alet" match="author"
           use="substring(name/@file-as,1,1)"/>

  <xsl:template match="/">
   <xsl:for-each select="*/author[generate-id(.)=
                    generate-id(key('alet',substring(name/@file-as,1,1))[1])]">
     <xsl:text>&#xA;&#xA;</xsl:text>
     <xsl:value-of select="substring(name/@file-as,1,1)"/>
     <xsl:text>&#xA;</xsl:text>

     <xsl:for-each select="key('alet',substring(name/@file-as,1,1))">
       <xsl:value-of select="name/@file-as"/>
       <xsl:text>&#xA;</xsl:text>
     </xsl:for-each>
   </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

21.

Grouping in 2.0

Mike Kay



I'm trying to group a
bunch of sibling items into groups of items that
start with the item that has a particular child with a particular value.

It would seem that the following does not work:

    <xsl:for-each-group select="/*/Item"
                        group-starting-with="RepresentationTerm[.='Code']">

Looking at XSLT 2.0, group-starting-with=
requires the population to all be nodes, and
there are a number of nodes that match the
pattern test supplied ... yet running the example
I am only getting a single group ... which I
suspect is just the first node in the population.

I'm missing something obvious ... could someone
please point me to the correct chapter and verse?

I have my example below of test data and
stylesheet

<?xml version="1.0" encoding="utf-8"?>
<Model>
   <Item>
      <DictionaryEntryName>Allowance Charge
Reason_ Code. Type</DictionaryEntryName>
      <RepresentationTerm>Code</RepresentationTerm>
   </Item>
   <Item>
      <DictionaryEntryName>Allowance Charge
Reason_ Code. Content</DictionaryEntryName>
   </Item>


   <xsl:template match="/">
          <All>
            <xsl:for-each-group select="/*/Item"
                                group-starting-with=
                            "RepresentationTerm[.='Code']">
              <Identification>
                <xsl:value-of select="DictionaryEntryName"/>
              </Identification>
            </xsl:for-each-group>
          </All>
</xsl:template>

The value of group-starting-with is a pattern that must match a node in the population. Since the population consists of Item elements, none of them match the pattern RepresentationTerm[.='Code']. Use

group-starting-with="Item[RepresentationTerm='Code']"

A bit of a gotcha - only this morning I found someone doing select="para" group-by="para/title". Oh for a context-free language!

22.

for-each-group, counting ancestor paths

Andrew Welch



>
> Hi - I'm trying to write a generic stylesheet to count occurrences of
> full paths to elements.  For example, I'd like input like this:
>
> <a>
>         <b>
>                 <x>
>                         <w>blah</w>
>                 </x>
>                 <y>bleh</y>
>         </b>
>         <b>
>                 <x>
>                         <w>blih</w>
>                 </x>
>                 <x>
>                         <w>bloh</w>
>                 </x>
>         </b>
>         <c>
>                 <w>blwh</w>
>                 <w>blyh</w>
>         </c>
> </a>
>
> To generate this output:
>
> /a - 1
> /a/b - 2
> /a/b/x - 3
> /a/b/x/w - 3
> /a/b/y - 1
> /a/c - 1
> /a/c/w - 2

This for-each-group

 <xsl:for-each-group
   select="//*/string-join(ancestor-or-self::*/name(), '/')"
   group-by=".">
   <xsl:sequence select="concat('&#xa;', ., ' - ', count(current-group()))"/>
 </xsl:for-each-group>

give this result:

a - 1
a/b - 2
a/b/x - 3
a/b/x/w - 3
a/b/y - 1
a/c - 1
a/c/w - 2

23.

group-adjacent use

David Carlisle



I am trying to figure out how to write my XSLT to get it to eliminate
redundant tagging of multiple <related-article> elements that I
currently have in my XML file.  The XML looks like this:

<related-article> [content 1] </related-article>
<related-article> [content 2] </related-article>
<related-article> [content 3] </related-article>

and I would like to end up with XML that looks like this:

<related-article> [content 1] [content 2] [content 3] </related-article>

do you mean there may be other elements in the same sequence, but you just want to group related-article?

In which case (if there are no sibling text nodes) something like

<xsl:for-each-group select="*"
group-adjacent="exists(self::related-article)">
  <xsl:choose>
    <xsl:when test="current-grouping-key()">
    <related-article><xsl:value-of
select="current-group()"/></related-article>
</xsl:when>
<xsl:otherwise>
  <xsl:copy-of select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>

24.

Working with pairs of siblings

Mike Kay



> I'm curious if there is a more elegant way to use a sequence 
> > expression to process two adjacent elements at a time.  With 
> > XSLT 1.0 I would have used recursion, but with XSLT/XPath 2.0 
> > I'm wondering if I can exploit sequences.  Below is something 
> > I've come up with so far.

> >       <xsl:for-each select="for $i in 1 to (count(*) idiv 2 +
> > (count(*) mod 2)) return *[($i*2)-1]">
> >         <pair>
> >           <xsl:copy-of select="." />
> >           <xsl:copy-of select="./following-sibling::*[1]" />
> >         </pair>
> >       </xsl:for-each>

It seems to me that you can either write code that assumes you're working with a sequence of siblings, or you can write code that works with an arbitrary sequence. For sibling elements, the following works just fine:

<xsl:for-each select="*[position() mod 2 = 1]">
  <pair>
    <xsl:copy-of select="., following-sibling::*[1]" />
  </pair>
</xsl:for-each>

If you want to work with an arbitrary sequence $SEQ (not necessarily siblings), try

<xsl:for-each select="1 to count($SEQ)[. mod 2 = 1]">
  <xsl:variable name="p" select="."/>
  <pair>
    <xsl:copy-of select="$SEQ[$p], $SEQ[$p+1]" />
  </pair>
</xsl:for-each>

Alternatively of course there is

<xsl:for-each-group select="$SEQ" group-adjacent="(position()-1) idiv 2">
  <pair>
    <xsl:copy-of select="current-group()"/>
  </pair>
</xsl:for-each-group>

25.

Group by element name

Mike Kay



I need group by tags this xml for example. The problem is 
> that I dont know the name of the tags. Only I know the tag 
> "Book" and the ID "boo2". I dont know too the extension of de 
> levels of the subtrees, but the groupment must be make by the 
> tags that are writed in lowercase, and only group by tags 
> writed in upperecase if the tags are the same. (I dont 
> control all of the data). I need one generic template .


> <?xml version="1.0"?>
> <rdf:RDF   xmlns:foaf="http://xmlns.com/foaf/0.1/"
>      xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">

> <Book rdf:ID="boo2">
>     <address rdf:dataytpe="aaa">Almogavers 56</address>

>     <event>
>        <Inbook>
>           <month rdf:dataytpe="bbb">July</month>
>        </Inbook>
>     </event>
>     <event>
>        <Inbook>
>           <year rdf:dataytpe="ccc">2006</year>
>        </Inbook>
>     </event>

>     <proceedings>
>        <Proceedings>
>          <year rdf:dataytpe="ddd">2008</year>
>        </Proceedings>
>     </proceedings>
>     <proceedings>
>        <Misc>
>          <month rdf:dataytpe="ddd">May</month>
>        </Misc>
>     </proceedings>

>     <art>
>        <Manual>
>             <man>
>                 <Article>
>                     <month rdf:dataytpe="nnn">September</month>
>                 </Article>
>             </man>
>             <city rdf:dataytpe="ppp">September</city>
>        </Manual>
>     </art>
>     <art>
>        <Manual>
>             <man>
>                  <Article>
>                    <year rdf:dataytpe="eee">2004</year>
>                  </Article>
>             </man>
>        </Manual>
>     </art>


> </Book>

> </rdf:RDF>



> I want this result:

> <?xml version="1.0"?>
> <rdf:RDF   xmlns:foaf="http://xmlns.com/foaf/0.1/"
>      xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
> <Book rdf:ID="boo2">
>     <address rdf:dataytpe="aaa">Almogavers 56</address>

>     <event>
>        <Inbook>
>           <month rdf:dataytpe="bbb">July</month>
>           <year rdf:dataytpe="ccc">2006</year>
>        </Inbook>
>     </event>


>     <proceedings>
>        <Proceedings>
>          <year rdf:dataytpe="ddd">2008</year>
>        </Proceedings>
>        <Misc>
>          <month rdf:dataytpe="ddd">May</month>
>        </Misc>
>     </proceedings>

>     <art>
>        <Manual>
>             <man>
>                 <Article>
>                     <month rdf:dataytpe="nnn">September</month>
>                     <year rdf:dataytpe="eee">2004</year>
>                 </Article>
>             </man>
>             <city rdf:dataytpe="ppp">September</city>
>        </Manual>
>     </art>


> </Book>

> </rdf:RDF>

I think I would tackle it by first flattening the structure into something like this:

<Book rdf:ID="boo2">
   <address rdf:dataytpe="aaa" path="">Almogavers 56</address>
   <month rdf:dataytpe="bbb" path="event/Inbook">July</month>
   <year rdf:dataytpe="ccc" path="event/Inbook">2006</year>
   <year rdf:dataytpe="ddd" path="proceedings/Proceedings">2008</year>
   <month rdf:dataytpe="ddd" path="proceedings/Proceedings">May</month>

etc.

That part can be done by

<xsl:for-each select="//*[not(*)]">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:attribute name="path" select="string-join(ancestor::*/name(),
'/')"/>
  </xsl:copy>
</xsl:for-each>

Then you can construct the output with

<xsl:template match="/">
    <xsl:variable name='flattened'>
      <xsl:for-each select="//*[not(*)]">
        <xsl:copy>
          <xsl:copy-of select="@*"/>
          <xsl:attribute name="path" select="string-join(ancestor::*[not(position() = last())]/name(),
                                             '/')"/>
        </xsl:copy>
      </xsl:for-each>
    </xsl:variable>

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">

  <xsl:for-each-group select="$flattened/*" group-by="@path">
      <xsl:sequence select="f:nest(tokenize(@path, '/'), current-group())"/>
    </xsl:for-each-group>
  </rdf:RDF>


  </xsl:template>



<xsl:function name="f:nest" as="element()*">
  <xsl:param name="path" as="xs:string*"/>
  <xsl:param name="content" as="element()*"/>
  <xsl:choose>
    <xsl:when test="exists($path)">
      <xsl:element name="{$path[1]}">
         <xsl:sequence select="f:nest(remove($path, 1), $content)"/>
      </xsl:element>
    </xsl:when>
    <xsl:otherwise>
       <xsl:sequence select="$content"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:function>

  <xsl:template match="*">
  <xsl:message>
    *****<xsl:value-of select="name(..)"/>/{<xsl:value-of 
	  select="namespace-uri()"/>}<xsl:value-of select="name()"/>******
    </xsl:message>
</xsl:template>

Needs refinement if namespaces are involved.

26.

List all unique element and attribute names

Mike Kay, Andrew Welch





I need to list out all elements and attribute (unique) in a 
> text file for mapping with other XML file.

> I am able to get all the elements and attributes but I am 
> unable to achieve the uniqueness. Can any body help on this.

In XSLT 2.0 it's simply

distinct-values(//*/name())
distinct-values(//@*/name())

If you really need to do it with XSLT 1.0, eliminating duplicates is essentially the same problem as grouping, and you can use the Muenchian grouping approach.

The preceding-sibling grouping technique isn't going to work (a) because your nodes are not siblings of each other, and (b) because it only works where the grouping key is the string-value of the node, not where it is some other function of the node (here, it's name). Muenchian grouping works for any string-valued function of a node.

If you are stuck with 1.0 then you need to use a key:

<xsl:key name="names" match="*|@*" use="name()"/>

with

<xsl:for-each select="//*|//*/@*">
  <xsl:if test="generate-id(.) = generate-id(key('names', name(.))[1])">
    <xsl:value-of select="local-name()"/>

In 2.0 you can use distinct-values() or xsl:for-each-group, but it always good to learn this technique.

27.

If i have a node like

<node>1, 2, 3, 5-8, 9, 11, 15</node>

How can I make it as:

<node>1-3, 5-9, 11, 15</node>

Mike Kay

First parse the string and convert to a sequence of integers,

Then do grouping using a method first suggested by David Carlisle on this list:


<xsl:template match="/node">
<xsl:variable name='ls' as="item()*">
    <xsl:for-each select="tokenize(., ',\s+')">
      <xsl:choose>
        <xsl:when test="contains(., '-')">
          <xsl:sequence select="xs:integer(substring-before(., '-')) to 
                                xs:integer(substring-after(., '-'))"/>
</xsl:when>
<xsl:otherwise>
  <xsl:sequence select="xs:integer(.)"/>
</xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
</xsl:variable>



<xsl:for-each-group select="$ls" group-adjacent=". - position()">
  <xsl:choose>
    <xsl:when test="count(current-group()) gt 1">
      <xsl:value-of select="current-group()[1], current-group()[last()]" 
    separator="-"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="current-group()[1]"/>
    </xsl:otherwise>
  </xsl:choose>
<xsl:text>, </xsl:text>
</xsl:for-each-group>