Looping and recursion based on parameter passed - xslt

I have an XML organized like below-
<section name="Parent 1 Text here" ID="1" >
<section name="Child 1 Text here" ID="11">
</section>
<section name="Child 2 Text here" ID="12">
<section name="GrandChild Text here" ID="121" >
</section>
</section>
</section>
<section name="Parent 2 Text here" ID="2" >
<section name="Child 1 Text here" ID="22">
</section>
<section name="Child 2 Text here" ID="23">
<section name="GrandChild Text here" ID="232" >
</section>
</section>
</section>
I have to produce the below output XML -
<section name="Parent 1 Text here" ID="1" >
<section name="Child 2 Text here" ID="12">
<section name="GrandChild Text here" ID="121" >
</section>
</section>
</section>
<section name="Parent 2 Text here" ID="2" >
<section name="Child 2 Text here" ID="23">
</section>
</section>
I have to achive above using XSLT 1.0 transformation. I was planning to pass a comma separated string as a parameter with value= "1,12,121,2,23"
My question- How to loop the comma separated parameter in XSLT 1.0 ?
Is there a simpler way to achieve the above. Please remember I have to do this in XSLT 1.0
Your help is appreciated.

Recursion is not necessarily the solution. Basically your comma-seperated string can be applied as a filter:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="filter">1,12,121,2,23</xsl:param>
<xsl:template match="section">
<xsl:if test="contains(concat(',', $filter, ','), concat(',', #ID, ','))">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- copy the rest -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Here is an alternative to Jan Willem B's approach. I don't claim that it is either better or worse. I present it for two reasons:
You may want to see what a recursive approach would look like
It behaves differently for some input data (although it behaves the same for your example data). Specifically, if your filter includes two nodes that are both descendants of the same node, this stylesheet will output two nested data structures, each with one leaf node, while Jan Willem B's answer outputs one nested structure with two leaf nodes. Don't know which you want.
For my stylesheet, you would pass a filter listing only the leaf nodes that you want. It will find the ancestors. I have assumed for this that your root node is called <doc>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:param name="descendant-ids" select="'121,23'"/>
<xsl:template match="/">
<doc>
<xsl:call-template name="recurse-ids">
<xsl:with-param name="ids" select="concat($descendant-ids,',')"/>
</xsl:call-template>
</doc>
</xsl:template>
<xsl:template name="recurse-ids">
<xsl:param name="ids"/>
<xsl:variable name="id" select="substring-before($ids,',')"/>
<xsl:variable name="remaining-ids" select="substring-after($ids,',')"/>
<xsl:apply-templates select="/doc/section">
<xsl:with-param name="id" select="$id"/>
</xsl:apply-templates>
<xsl:if test="$remaining-ids">
<xsl:call-template name="recurse-ids">
<xsl:with-param name="ids" select="$remaining-ids"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="section">
<xsl:param name="id"/>
<xsl:if test="starts-with($id,#ID)">
<xsl:copy>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="id" select="$id"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

Selecting the last (closest) element with specific child only with XSLT

I need to check all elements to see if there is a preceding element (not always a sibling) that has any PI element. If so, I need to assign an attribute to all following elements UNTIL the next PI element is found.
Example XML Input:
<chapter id = "1">
<section>
<heading>some text here</heading>
<p>some text here<?Test1?></p>
</section>
<section>
<heading>some text here</heading>
<p>some text here<?Test2?></p>
</section>
</chapter>
<chapter id="2">
<section>
<p></p>
</section>
</chapter>
In this example, the chapter element with the id = "2" should get an attribute named "test2" and then stop (so it should not also get "test1".
What I have now:
<xsl:key name="test1PIs" match="*[not(self::main|self::title)]/processing-instruction('test1')" use="following::*/#id"/>
<xsl:key name="test2PIs" match="*[not(self::main|self::title)]/processing-instruction('test2')"
use="following::*/#id"/>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:if test="key('test1PIs',#id)">
<xsl:attribute name="test1"/>
</xsl:if>
<xsl:if test="key('test2PIs',#id)">
<xsl:attribute name="test2"/>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
What I end up with:
<chapter id="2" test1="" test2="">
**child elements here**
</chapter>
What I really want:
<chapter id="2" test2="">
**child elements here**
</chapter>
#Michael Kay's response got me where I needed to be. In final:
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:variable name="piParent"
select="preceding::processing-instruction()[1]"/>
<xsl:if test="$piParent">
<xsl:attribute name="processPI">
<xsl:value-of select="name($piParent)"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
It works by assigning an attribute to all elements following a specific PI until it reaches another PI.
I think you want something like:
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:variable name="piParent"
select="preceding::*[processing-instruction()][1]"/>
<xsl:if test="$piParent">
<xsl:attribute name="name($piParent/processing-instruction())"/>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
However, I'm guessing a little because you haven't specified the task very clearly.

finding preceding-sibling and processing in same node

Need to transform following XML snippet into DITA using XSLT. My requirements are:
1. All the tags comes before "orderedlist" should be wrapped under "context" node.
2. All the tags comes after "orderedlist" should be in "result".
3. All the "include" tags should be wrapped under their preceding sibling nodes.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<procedure>
<para>This is first para</para>
<para>This is second para</para>
<include>This is include</include>
<orderedlist>
<listitem>this is list item</listitem>
<include>This is include</include>
<listitem>this is list item <include>this is include</include></listitem>
</orderedlist>
<observation>this is observation</observation>
<para>this is result para <include>this is include</include></para>
<include>This is include</include>
</procedure>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<task>
<context>
<p>This is first para</p>
<p>This is second para <included type='tag'>This is include</included>
</p>
</context>
<ol>
<li>this is list item <included type='tag'>This is include</included>
</li>
<li>this is list item <included type='tag'>this is include</included></li>
</ol>
<result>
<observation>this is observation</observation>
<p>this is result para <included type='tag'>this is include</included><included type='tag'>this is include</included>
</p>
</result>
</task>
My XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="procedure">
<task>
<context>
<xsl:apply-templates select="*[parent::procedure][following-sibling::orderedlist]"/>
</context>
<xsl:apply-templates select="orderedlist"/>
<result>
<xsl:apply-templates select="*[parent::procedure][preceding-sibling::orderedlist]"/>
</result>
</task>
</xsl:template>
<xsl:template match="para">
<p>
<xsl:apply-templates/>
<include>
<xsl:apply-templates select="following-sibling::include"/>
</include>
</p>
</xsl:template>
<!-- rest of the template goes here -->
<xsl:template match="listitem">
<li>
<xsl:apply-templates/>
<include>
<xsl:apply-templates select="following-sibling::include"/>
</include>
</li>
</xsl:template>
</xsl:stylesheet>
Any pointer will be a great help. Thanks.
The first thing to note is you can simplify the following expression:
<xsl:apply-templates select="*[parent::procedure][following-sibling::orderedlist]"/>
You don't need the [parent::procedure] here, because you are already positioned on a procedure element, so so you know if you select any child element, it will have that as a parent!
<xsl:apply-templates select="*[following-sibling::orderedlist]"/>
However, you might need to add an clause to ensure you don't output the include elements at this point, as you will need special code to handle them being included later
<xsl:template match="include" />
To handle the include elements, it might be worth defining a key, so you can group them by the first most proceding non-include element, like so
<xsl:key name="include" match="include" use="generate-id(preceding-sibling::*[not(self::include)][1])"/>
Then, when matching an element such as para or listitem, you can then get the include elements to include, just like this:
<xsl:copy-of select="key('include', generate-id())"/>
Note I am not sure how you want to handle multipe include elements for a single element, but in my example, it will output them separately as opposing to merging them:
Here is the full XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="include" match="include" use="generate-id(preceding-sibling::*[not(self::include)][1])"/>
<xsl:template match="procedure">
<task>
<context>
<xsl:apply-templates select="*[following-sibling::orderedlist]"/>
</context>
<xsl:apply-templates select="orderedlist"/>
<result>
<xsl:apply-templates select="*[preceding-sibling::orderedlist]"/>
</result>
</task>
</xsl:template>
<xsl:template match="orderedlist">
<ol>
<xsl:apply-templates />
</ol>
</xsl:template>
<xsl:template match="para">
<p>
<xsl:apply-templates/>
<xsl:copy-of select="key('include', generate-id())" />
</p>
</xsl:template>
<xsl:template match="listitem">
<li>
<xsl:apply-templates/>
<xsl:copy-of select="key('include', generate-id())" />
</li>
</xsl:template>
<xsl:template match="include" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<task>
<context>
<p>This is first para</p>
<p>This is second para<include>This is include</include></p>
</context>
<ol>
<li>this is list item<include>This is include</include></li>
<li>this is list item</li>
</ol>
<result>
<observation>this is observation</observation>
<p>this is result para<include>This is include</include></p>
</result>
</task>
Give this a try:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()" name="Copy">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
<xsl:call-template name="Include" />
</xsl:copy>
</xsl:template>
<xsl:template match="procedure">
<task>
<context>
<xsl:apply-templates select="*[following-sibling::orderedlist]"/>
</context>
<xsl:apply-templates select="orderedlist"/>
<result>
<xsl:apply-templates select="*[preceding-sibling::orderedlist]"/>
</result>
</task>
</xsl:template>
<xsl:template match="para">
<p>
<xsl:apply-templates/>
<xsl:call-template name="Include" />
</p>
</xsl:template>
<!-- rest of the template goes here -->
<xsl:template match="listitem">
<li>
<xsl:apply-templates/>
<xsl:call-template name="Include" />
</li>
</xsl:template>
<xsl:template name="Include">
<xsl:apply-templates
select="following-sibling::include[
generate-id(preceding-sibling::*[not(self::include)][1]) =
generate-id(current())]"
mode="performIncludes"/>
</xsl:template>
<xsl:template match="include" />
<xsl:template match="include" mode="performIncludes">
<xsl:call-template name="Copy" />
</xsl:template>
</xsl:stylesheet>
Output when run on your sample input:
<task>
<context>
<p>This is first para</p>
<p>This is second para<include>This is include</include></p>
</context>
<orderedlist>
<li>this is list item<include>This is include</include></li>
<li>this is list item</li>
</orderedlist>
<result>
<observation>this is observation</observation>
<p>this is result para<include>This is include</include></p>
</result>
</task>

Can I DRY this XSLT for nested categories?

XSLT available is 1.0.
The XML and XSLT below is for building a dropdown navigation menu for nested categories. The level of categories may vary.
Sample XML:
<data>
<categories-nav>
<section id="11" handle="categories-1">Categories 1</section>
<entry id="65">
<name handle="air-rifles">Air Rifles</name>
<subcategories field-id="50" subsection-id="12" items="2">
<item id="66" quantity="1">
<name handle="rifles">Rifles</name>
<active>Yes</active>
<subcategories field-id="57" subsection-id="13" items="2">
<item id="67" quantity="1">
<name handle="b2-series">B2 Series</name>
<active>Yes</active>
</item>
<item id="112" quantity="1">
<name handle="junior-supergrade">Junior Supergrade</name>
<active>Yes</active>
</item>
</subcategories>
</item>
<item id="111" quantity="1">
<name handle="accessories">Accessories</name>
<active>Yes</active>
<subcategories field-id="57" subsection-id="13" items="0" />
</item>
</subcategories>
</entry>
<entry id="118">
<name handle="pistols">Pistols</name>
</entry>
<entry id="58">
<name handle="bb-softair-guns">BB Softair Guns</name>
</entry>
</categories-nav>
</data>
My current XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="categories-nav-entries" mode="navigation">
<li class="{name/#handle}-{#id}">
<xsl:value-of select="name"/>
<xsl:apply-templates select="subcategories" mode="navigation"/>
</li>
</xsl:template>
<!-- level 1 -->
<xsl:template match="/data/categories-nav" mode="navigation">
<ul>
<xsl:apply-templates select="entry" mode="navigation"/>
</ul>
</xsl:template>
<xsl:template match="/data/categories-nav/entry" mode="navigation">
<xsl:call-template name="categories-nav-entries" mode="navigation"/>
</xsl:template>
<!-- level 2 -->
<xsl:template match="/data/categories-nav/entry/subcategories" mode="navigation">
<ul>
<xsl:apply-templates select="item" mode="navigation"/>
</ul>
</xsl:template>
<xsl:template match="/data/categories-nav/entry/subcategories/item" mode="navigation">
<xsl:call-template name="categories-nav-entries" mode="navigation"/>
</xsl:template>
<!-- level 3 -->
<xsl:template match="/data/categories-nav/entry/subcategories/item/subcategories" mode="navigation">
<ul>
<xsl:apply-templates select="item" mode="navigation"/>
</ul>
</xsl:template>
<xsl:template match="/data/categories-nav/entry/subcategories/item/subcategories/item" mode="navigation">
<xsl:call-template name="categories-nav-entries" mode="navigation"/>
</xsl:template>
</xsl:stylesheet>
The only difference between the matches for the three different levels is repeated subcategories and item nodes.
I've successfully got the lis in their own named template, but is there a way I can avoid matching the three levels separately?
Also, it seems that because I'm using a mode on the initial match, I also have to use that mode on all subsequent matches - is that correct?
Edit: here's what I came up with after using relative paths as #michael's answer:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="categories-nav-list">
<li class="{name/#handle}">
<xsl:value-of select="name"/>
<xsl:apply-templates select="subcategories[#items > 0]"/>
</li>
</xsl:template>
<xsl:template match="categories-nav">
<ul>
<xsl:apply-templates select="entry"/>
</ul>
</xsl:template>
<xsl:template match="categories-nav/entry">
<xsl:call-template name="categories-nav-list"/>
</xsl:template>
<xsl:template match="categories-nav//subcategories[item/active='Yes']">
<ul>
<xsl:apply-templates select="item"/>
</ul>
</xsl:template>
<xsl:template match="categories-nav//subcategories/item[active='Yes']" priority="1">
<xsl:call-template name="categories-nav-list"/>
</xsl:template>
<xsl:template match="categories-nav//subcategories/item" priority="0"/>
</xsl:stylesheet>
You don't need to put absolute paths in each #match attribute.
Why not something like this:
<xsl:template match="subcategories">
<ul>
<xsl:apply-templates select="item"/>
</ul>
</xsl:template>
<xsl:template match="item">
<xsl:call-template name="categories-nav-entries"/>
</xsl:template>

Reversing order without xsl:sort

There is some problem in my xsl ,I do not know the reason
I want to use apply-templates to reverse the different sequences of XML without xsl:sort;
For example : the following is the input
<book title="XML">
<author first="P" />
<chapter title="A">
<section title="A.1" />
<section title="A.2">
<section title="A.2.1" />
<section title="A.2.2" />
</section>
<section title="A.3">
<section title="A.3.1" />
</section>
</chapter>
<chapter title="B">
<section title="B.1" />
<section title="B.2">
<section title="B.2.1" />
<section title="B.2.2" />
</section>
</chapter>
</book>
I want to get the output like this:this is my xsl.
<?xml version="1.0" encoding="UTF-8"?>
<book title="XML">
<author first="P"/>
<chapter title="A">
<section title="A.1">
<section title="A.3.1"/>
</section>
<section title="A.2">
<section title="A.2.2"/>
<section title="A.2.1"/>
</section>
<section title="A.1"/>
</chapter>
<chapter title="B">
<section title="B.2">
<section title="B.2.2"/>
<section title="B.2.1"/>
</section>
<section title="B.1"/>
</chapter>
</book>
Yes,the sections have been reversed but the chapters are not.
the following is my xsl ,there is some problem here ,could you help me to find it??
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent ="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="book">
<xsl:copy>
<xsl:sequence select="#title"/>
<xsl:sequence select="author"/>
<xsl:apply-templates select="chapter">
<xsl:with-param name="seq" select="section"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match ="chapter|section" as="element()">
<xsl:param name="seq" as="element(section)*"/>
<xsl:copy>
<xsl:sequence select="#title"/>
<xsl:if test="not(empty($seq))">
<xsl:apply-templates select="chapter">
<xsl:with-param name="seq" select="$seq"/>
</xsl:apply-templates>
<xsl:apply-templates select="$seq[1]"/>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:transform>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()[1]|#*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="section">
<xsl:apply-templates select="following-sibling::node()[1]"/>
<xsl:copy>
<xsl:apply-templates select="node()[1]|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<book title="XML">
<author first="P"></author>
<chapter title="A ">
<section title="A.3 ">
<section title="A.3.1"></section>
</section>
<section title="A.2">
<section title="A.2.2"></section>
<section title="A.2.1"></section>
</section>
<section title="A.1"></section>
</chapter>
<chapter title="B">
<section title="B.2">
<section title="B.2.2"></section>
<section title="B.2.1"></section>
</section>
<section title="B.1"></section>
</chapter>
</book>
How about this?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent ="yes"/>
<xsl:template match ="chapter|section">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:for-each select="*">
<xsl:sort select="position()" data-type="number" order="descending"/>
<xsl:apply-templates select="." />
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:transform>
A couple of things to note:
match="*" template: This provides a default behavior for elements that we just need to copy with attributes and then process the children. This replaces your "book" and "/" templates and doesn't make assumptions about what elements are in it. This means we now can focus on providing template(s) for elements that are not covered by default behavior.
for-each: This is where the magic happens by enumerating the children which we then sort in descending order based on position before processing them with apply templates.

XPATH Axes - Issues

XML structure:
<units>
<unit>
<lesson>
<name>Sample 1</name>
<sections>
<section type="IN">
<title> Sample Title 1 </title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 2</name>
<sections>
<section type="OF">
<title> Sample Title 2 </title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 3</name>
<sections>
<section type="IN">
<title> Sample Title 3</title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 4</name>
<sections>
<section type="AS">
<title> Sample Title 4</title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 5</name>
<sections>
<section type="IN">
<title> Sample Title 5</title>
</section>
</sections>
</lesson>
</unit>
</units>
My requirement is to get the values of title element and display as follows (Grouping similar data and display)
IN:
Sample Title 1
Sample Title 3
Sample Title 5
OF:
Sample Title 2
AS:
Sample Title 5
I have used following-sibling option to get the expected output. Since the XML structure is huge(I have pasted only the snippet), I cannot hard-code the path using ../../ and all in XSLT. Please help me in getting the expected output.
It's better to solve this using grouping than either of the sibling axes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:key name="bySectionType" match="section" use="#type" />
<xsl:template match="/">
<xsl:apply-templates select="units/unit/lesson/sections/section" />
</xsl:template>
<xsl:template match="section" />
<xsl:template
match="section[generate-id()=
generate-id(key('bySectionType', #type)[1])]">
<xsl:value-of select="concat(#type, ':
')" />
<xsl:apply-templates select="key('bySectionType', #type)" mode="out" />
</xsl:template>
<xsl:template match="section" mode="out">
<xsl:value-of select="concat(normalize-space(title), '
')" />
</xsl:template>
</xsl:stylesheet>
Output:
IN:
Sample Title 1
Sample Title 3
Sample Title 5
OF:
Sample Title 2
AS:
Sample Title 4
For completeness, the following stylesheet achieves the same result using the preceding and following axes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:apply-templates select="units/unit/lesson/sections/section" />
</xsl:template>
<xsl:template match="section" />
<xsl:template
match="section[not(preceding::section[#type=current()/#type])]">
<xsl:value-of select="concat(#type, ':
')" />
<xsl:apply-templates select=".|following::section[#type=current()/#type]"
mode="out" />
</xsl:template>
<xsl:template match="section" mode="out">
<xsl:value-of select="concat(normalize-space(title), '
')" />
</xsl:template>
</xsl:stylesheet>
This is a far less efficient solution. The normal way to solve this is with the Muenchian Method for grouping, as shown above.
Here is the solution in XSLT 2.0:
<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="//section" group-by="#type">
<xsl:value-of select="#type, ':
'" separator=""/>
<xsl:value-of select="current-group()/title" separator="
" />
<xsl:value-of select="'
'"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>