XPATH Axes - Issues - xslt

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>

Related

How to merge two documents without overwriting elements in original using XSLT 1.0?

I am a new to XSLT and I need to insert an xslt script into a third party software, which uses XSLT 1.0 to transform xml document.
My task is to take document A.xml and insert each element from document B.xml, but only if the text in A is not yet existing. The output should be generated as document C.xml.
Example A.xml:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Name>SCHAME.table_name</Name>
<Location>oracle:TNS_1</Location>
<Citation>
<Title>Title 1</Title>
<Description/>
</Citation>
<metadataDate>20170418</metadataDate>
</Table>
</metadata>
Example B.xml:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Citation>
<Title>Template Title</Title>
<Abstract>Template Abstract</Abstract>
<Description>Template Description</Description>
</Citation>
<MetadataDate>20160131</MetadataDate>
</Table>
</metadata>
The expected output of C.xml is:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Name>SCHAME.table_name</Name>
<Location>oracle:TNS_1</Location>
<Citation>
<Title>Title 1</Title>
<Abstract>Template Abstract</Abstract>
<Description>Template Description</Description>
</Citation>
<metadataDate>20170418</metadataDate>
</Table>
</metadata>
Three things are important:
B can contain elements, which are not present in A but must be copied to C (eg metadata/Table/Citation/Abstract)
Elements of A with text must not be overwritten in C with text from B. (eg metadata/Table/Citation/Title). Then again empty elements in A must be filled with text from B (eg metadata/Table/Citation/Description)
The xml is just a sample, there are more than hundred different tags in my real xml files, these are just samples depicting my problem. So any solution to my problem has to be applicable on more tags than the ones living in my sample xml.
I do not need a running solution, any hints how to solve this for a XSLT beginner would be nice.
The problem here is how to identify corresponding elements in both files. I assume that each element appears at most once, so we can identify corresponding elements simply by their element name.
My solution follows the idea of John Bollinger:
Create a template.xml file containing all possible elements in the desired output structure.
The XSL script runs through all elements of the template.
It lookups the values of this element in files A and B.
If value A is not empty, take it, else take the value from B.
This is the template I used:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Name/>
<Location/>
<Citation>
<Title />
<Abstract />
<Description/>
</Citation>
<metadataDate/>
</Table>
</metadata>
The following XSL has the above template as input:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<!-- Parameters -->
<xsl:param name="aFile" select="'a.xml'" />
<xsl:param name="bFile" select="'b.xml'" />
<!-- Variables -->
<xsl:variable name="aDoc" select="document( $aFile, . )"/>
<xsl:variable name="bDoc" select="document( $bFile, . )"/>
<!-- Locate elements by name in both files -->
<xsl:key name="elementsByName" match="*" use="name()" />
<!-- Root-Template -->
<xsl:template match="/">
<xsl:comment>
<xsl:value-of select="concat( 'Merge of ', $aFile, ' with ', $bFile )" />
</xsl:comment>
<xsl:apply-templates />
</xsl:template>
<!-- Merge all elements -->
<xsl:template match="*">
<xsl:variable name="elemName" select="name()" />
<xsl:variable name="aValue" select="$aDoc/key('elementsByName', $elemName)/text()" />
<xsl:variable name="bValue" select="$bDoc/key('elementsByName', $elemName)/text()" />
<xsl:copy>
<xsl:choose>
<xsl:when test="$aValue != ''">
<xsl:value-of select="$aValue" />
</xsl:when>
<xsl:when test="$bValue != ''">
<xsl:value-of select="$bValue" />
</xsl:when>
</xsl:choose>
<xsl:apply-templates select="*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The result of the transformation is:
<?xml version="1.0" encoding="UTF-8"?>
<!--Merge of a.xml with b.xml-->
<metadata>
<Table>
<Name>SCHAME.table_name</Name>
<Location>oracle:TNS_1</Location>
<Citation>
<Title>Title 1</Title>
<Abstract>Template Abstract</Abstract>
<Description>Template Description</Description>
</Citation>
<metadataDate>20170418</metadataDate>
</Table>
</metadata>
The only "trick" in this code is the use of xsl:key and the corresponding key() function. This allows us to find the corresponding elements (same name) in both files A and B.
This script copies all elements from the template to the output file C.
To change this behaviour, simple move the xsl:copy instructions inside of the xsl:when.
I try to give an improved answer separately to avoid confusion with the helpful comments there.
In addition to the "generic" approach there (match element names), we will now have specific templates as well.
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<!-- Parameters -->
<xsl:param name="aFile" select="'a.xml'" />
<xsl:param name="bFile" select="'b.xml'" />
<!-- Variables -->
<xsl:variable name="aDoc" select="document( $aFile, . )"/>
<xsl:variable name="bDoc" select="document( $bFile, . )"/>
<!-- Locate elements by name in both files -->
<xsl:key name="elementsByName" match="*" use="name()" />
<!-- Root-Template -->
<xsl:template match="/">
<xsl:comment>
<xsl:value-of select="concat( 'Merge of ', $aFile, ' with ', $bFile )" />
</xsl:comment>
<xsl:apply-templates />
</xsl:template>
<!-- Merge specific elements -->
<xsl:template match="metadata/Table/Description">
<xsl:call-template name="mergeElement">
<xsl:with-param name="aValue" select="$aDoc/metadata/Table/Description/text()" />
<xsl:with-param name="bValue" select="$bDoc/metadata/Table/Description/text()" />
</xsl:call-template>
</xsl:template>
<xsl:template match="metadata/Table/Citation/Description">
<xsl:call-template name="mergeElement">
<xsl:with-param name="aValue" select="$aDoc/metadata/Table/Citation/Description/text()" />
<xsl:with-param name="bValue" select="$bDoc/metadata/Table/Citation/Description/text()" />
</xsl:call-template>
</xsl:template>
<!-- Merge unique elements -->
<xsl:template match="*" priority="-10">
<xsl:variable name="elemName" select="name()" />
<xsl:call-template name="mergeElement">
<xsl:with-param name="aValue" select="$aDoc/key('elementsByName', $elemName)/text()" />
<xsl:with-param name="bValue" select="$bDoc/key('elementsByName', $elemName)/text()" />
</xsl:call-template>
</xsl:template>
<!-- Use A or B -->
<xsl:template name="mergeElement">
<xsl:param name="aValue" />
<xsl:param name="bValue" />
<xsl:copy>
<xsl:choose>
<xsl:when test="$aValue != ''">
<xsl:value-of select="$aValue" />
</xsl:when>
<xsl:when test="$bValue != ''">
<xsl:value-of select="$bValue" />
</xsl:when>
</xsl:choose>
<xsl:apply-templates select="*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
For the test, I changed the template and A and B and added another Description element directly inside of Table:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Name />
<Description/> <!-- NEW: not unique element name -->
<Location/>
<Citation>
<Title />
<Abstract />
<Description/>
</Citation>
<metadataDate/>
</Table>
</metadata>
File A:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Name>SCHAME.table_name</Name>
<Location>oracle:TNS_1</Location>
<Description>Table description A</Description> <!-- NEW -->
<Citation>
<Title>Title 1</Title>
<Description/>
</Citation>
<metadataDate>20170418</metadataDate>
</Table>
</metadata>
File B:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Description>Table description B</Description> <!-- NEW -->
<Citation>
<Title>Template Title</Title>
<Abstract>Template Abstract</Abstract>
<Description>Template Description</Description>
</Citation>
<MetadataDate>20160131</MetadataDate>
</Table>
</metadata>
Generated file C:
<?xml version="1.0" encoding="UTF-8"?>
<!--Merge of a.xml with b.xml-->
<metadata>
<Table>
<Name>SCHAME.table_name</Name>
<Description>Table description A</Description> <!-- NEW -->
<Location>oracle:TNS_1</Location>
<Citation>
<Title>Title 1</Title>
<Abstract>Template Abstract</Abstract>
<Description>Template Description</Description>
</Citation>
<metadataDate>20170418</metadataDate>
</Table>
</metadata>
Obviosly, this is not a generic solution.
But if the number of non-unique elements is low compared to the total number, you will benefit from the generical template. Only the non-unique elements must be "styled" individually.

Using XPATHs from file and adding relevant attribute

May be my XSL approach is wrong? please correct me the way to handle this situation
I want to grab XPATHs and Attrs from a mapping file, then use XPATH to match, and apply attributes to XML.
Here is my 3 inputs files:
mappings.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<map xpath="//title" class="title" others="moreToCome" />
<map xpath="//subtitle" class="subtitle" others="moreToCome" />
<map xpath="//p" class="p" others="moreToCome" />
</mappings>
Source.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title>title text</title>
<subtitle>subtitle text</subtitle>
<p>subtitle text</p>
</root>
StyleMapping.xsl
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="fMappings" select="document('mappings.xml')" />
<xsl:variable name="xpath"><xsl:text>justToMakeItGlobal</xsl:text></xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
<!-- loop thru map in mappings.xml -->
<xsl:for-each select="$fMappings//mappings/map">
<xsl:call-template name="dyn">
<xsl:with-param name="xpath" select="#xpath" />
<xsl:with-param name="class" select="#class" />
<xsl:with-param name="others" select="#others" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template match="$xpath" mode="dyn">
<xsl:param name="xpath"/>
<xsl:param name="class"/>
<xsl:param name="others"/>
<xsl:attribute name="class"><xsl:value-of select="$class" /></xsl:attribute>
<xsl:attribute name="others"><xsl:value-of select="$others" /></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This is what is planning to do, but i'm not getting the correct way of doing it in XSLT:
1. Read mappings.xml file
2. Loop thru each map tag
3. grab xpath and attr's
4. apply template match/select with above xpath
5. add attr's to above selected nodes
output.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title class="title" others="moreToCome">title text</title>
<subtitle class="subtitle" others="moreToCome">subtitle text</subtitle>
<p class="p" others="moreToCome">subtitle text</p>
</root>
I don't think you can have a template with a calculated match pattern. Let me suggest a different approach:
mappings.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<map elem="title" class="Title" others="moreToCome1" />
<map elem="subtitle" class="Subtitle" others="moreToCome2" />
<map elem="p" class="P" others="moreToCome3" />
</mappings>
stylesheet
<?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" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:variable name="mappings" select="document('mappings.xml')/mappings" />
<xsl:template match="*">
<xsl:variable name="elem" select="name()" />
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:if test="$elem = $mappings/map/#elem">
<xsl:attribute name="class">
<xsl:value-of select="$mappings/map[#elem=$elem]/#class"/>
</xsl:attribute>
<xsl:attribute name="others">
<xsl:value-of select="$mappings/map[#elem=$elem]/#others"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Testing with the following source XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title original="yes">title text</title>
<subtitle>subtitle text</subtitle>
<p>para text</p>
<nomatch attr="test">another text</nomatch>
</root>
results in:
<?xml version="1.0" encoding="utf-8"?>
<root>
<title original="yes" class="Title" others="moreToCome1">title text</title>
<subtitle class="Subtitle" others="moreToCome2">subtitle text</subtitle>
<p class="P" others="moreToCome3">para text</p>
<nomatch attr="test">another text</nomatch>
</root>

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.

Looping and recursion based on parameter passed

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>