XSLT transformation xml List item with sub list - xslt

will appreciate your help. i have xml as following:
<ul>
<li>list item 1 </li>
<li>List Item 2
<ul>
<li>List item 2.1</li>
<li>List Item 2.2</li>
</ul>
</li>
<li>List Item 3 </li>
</ul>
The output should be as following:
<list>
<item>
<paragraph>list item 1 </paragraph>
</item>
<item>
<paragraph>List Item 2 </paragraph>
<list>
<item>
<paragraph>List<emphasis> item</emphasis> 2.1 </paragraph>
</item>
<item>
<paragraph>List Item 2.2 </paragraph>
</item>
</list>
</item>
<item>
<paragraph>List Item 3 </paragraph>
</item>
</list>
I am using xlst version 3.0 as following:
<xsl:template match="ul">
<xsl:choose>
<xsl:when test="name(..)='li'">
<xsl:text disable-output-escaping="yes"></paragraph></xsl:text>
<list>
<xsl:apply-templates/>
</list>
</xsl:when>
<xsl:otherwise>
<list>
<xsl:apply-templates/>
</list></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="li">
<item>
<xsl:text disable-output-escaping="yes"><paragraph></xsl:text>
<xsl:apply-templates/>
<xsl:text disable-output-escaping="yes"></paragraph></xsl:text>
</item>
</xsl:template>
I am getting the output almost as i would like but with extra closing paragraph element (</paragraph>) as following:
<list>
<item><paragraph>list item 1 </paragraph></item>
<item><paragraph>List Item 2 </paragraph><list>
<item><paragraph>List item 2.1 </paragraph></item>
<item><paragraph>List Item 2.2 </paragraph></item>
</list>
</paragraph></item>
<item><paragraph>List Item 3 </paragraph></item>
</list>

You should avoid using disable-output-escaping in this way. It is really not good practise, as you have seen.
What you can do, for your given XML, is use a template that matches text() and then wrap the paragraph around that:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="xml" indent="yes""/>
<xsl:strip-space elements="*" />
<xsl:template match="ul">
<list>
<xsl:apply-templates/>
</list>
</xsl:template>
<xsl:template match="li">
<item>
<xsl:apply-templates/>
</item>
</xsl:template>
<xsl:template match="text()">
<paragraph>
<xsl:value-of select="normalize-space()" />
</paragraph>
</xsl:template>
</xsl:stylesheet>
Alternatively, if you could have mark-up in your text, like <li>list <b>item 1</b> </li>, and you wanted output like <paragraph>list<b>item 1</b></paragraph>, then you could make use of xsl:for-each-group.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="ul">
<list>
<xsl:apply-templates/>
</list>
</xsl:template>
<xsl:template match="li">
<item>
<xsl:for-each-group select="node()" group-ending-with="ul">
<paragraph>
<xsl:apply-templates select="current-group()[not(self::ul)]" />
</paragraph>
<xsl:apply-templates select="current-group()[self::ul]" />
</xsl:for-each-group>
</item>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="normalize-space()" />
</xsl:template>
</xsl:stylesheet>

Related

Explanation on why certain nodes show up

All-
Very new to XSLT. I could use some help to understand some basic.
the xml
<root>
<Article>
<Bullettext>10,00 </Bullettext>
<Bullettext>8,00 </Bullettext>
</Article>
<Article>
<something>some text</something>
</Article>
<Article>
<Corpsdetexte>Bulgaria</Corpsdetexte>
<Bullettext>15,0 </Bullettext>
<Bullettext>10,0 </Bullettext>
</Article>
<Article>
<Corpsdetexte>Somaialia</Corpsdetexte>
<bunk>Test</bunk>
<Bullettext>15,1</Bullettext>
<Bullettext>10,2</Bullettext>
<Bullettext>20,3</Bullettext>
<Bullettext>25,4</Bullettext>
<Bullettext>30,5 </Bullettext>
</Article>
</root>
XSLT 1
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<!-- copy all elements as is -->
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<!-- process only the first bullet text element under this template -->
<xsl:template match="Bullettext[not(preceding-sibling::*[1][name()='Bullettext'])]">
<!-- find the element that we want to stop at -->
<xsl:variable name="stop" select="./following-sibling::*[name() != 'Bullettext'][1]"/>
<LIST>
<xsl:choose>
<!-- first, the simple case: there's no element we have to stop at -->
<xsl:when test="not($stop)">
<xsl:apply-templates select="." mode="item"/>
<xsl:apply-templates select="./following-sibling::Bullettext" mode="item"/>
</xsl:when>
<!-- is this required -->
<!-- transform all elements between the start and stop index into items -->
<xsl:otherwise>
<xsl:variable name="start_index" select="count(preceding-sibling::*) + 1"/>
<xsl:variable name="stop_index" select="count($stop/preceding-sibling::*)"/>
<xsl:apply-templates select="../*[position() >= $start_index
and position() <= $stop_index]"
mode="item"/>
</xsl:otherwise>
</xsl:choose>
</LIST>
</xsl:template>
<xsl:template match="Bullettext" />
<xsl:template match="Bullettext" mode="item">
<ITEM>
<xsl:value-of select="."/>
</ITEM>
</xsl:template>
</xsl:stylesheet>
XSLT 2 _ Notice the missing 'otherwise in the choose.
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<!-- copy all elements as is -->
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<!-- process only the first bullet text element under this template -->
<xsl:template match="Bullettext[not(preceding-sibling::*[1][name()='Bullettext'])]">
<!-- find the element that we want to stop at -->
<xsl:variable name="stop" select="./following-sibling::*[name() != 'Bullettext'][1]"/>
<LIST>
<xsl:choose>
<!-- first, the simple case: there's no element we have to stop at -->
<xsl:when test="not($stop)">
<xsl:apply-templates select="." mode="item"/>
<xsl:apply-templates select="./following-sibling::Bullettext" mode="item"/>
</xsl:when>
<!-- is this required -->
<!-- transform all elements between the start and stop index into items -->
</xsl:choose>
</LIST>
</xsl:template>
<xsl:template match="Bullettext" />
<xsl:template match="Bullettext" mode="item">
<ITEM>
<xsl:value-of select="."/>
</ITEM>
</xsl:template>
</xsl:stylesheet>
Both give the same result
<root>
<Article>
<LIST>
<ITEM>10,00 </ITEM>
<ITEM>8,00 </ITEM>
</LIST>
</Article>
<Article>
<something>some text</something>
</Article>
<Article>
<Corpsdetexte>Bulgaria</Corpsdetexte>
<LIST>
<ITEM>15,0 </ITEM>
<ITEM>10,0 </ITEM>
</LIST>
</Article>
<Article>
<Corpsdetexte>Somaialia</Corpsdetexte>
<bunk>Test</bunk>
<LIST>
<ITEM>15,1 </ITEM>
<ITEM>10,2 </ITEM>
<ITEM>20,3 </ITEM>
<ITEM>25,4 </ITEM>
<ITEM>30,5 </ITEM>
</LIST>
</Article>
</root>
What is the value of otherwise in the example. And why does it work without otherwise?
Note: this was an answer to question posed. Sorry do not have a link
Your input does not contain any Bullettext nodes. Therefore, the three templates matching Bullettext are never applied and it makes no difference what they contain.
However, with an input such as:
<root>
<Article>
<Bullettext>alpha</Bullettext>
<Bullettext>bravo</Bullettext>
<text>charlie</text>
<Bullettext>delta</Bullettext>
</Article>
</root>
you will see a difference: the first XSLT will return:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<Article>
<LIST>
<ITEM>alpha</ITEM>
<ITEM>bravo</ITEM>
</LIST>
<text>charlie</text>
<LIST>
<ITEM>delta</ITEM>
</LIST>
</Article>
</root>
while the second one will produce only:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<Article>
<LIST/>
<text>charlie</text>
<LIST>
<ITEM>delta</ITEM>
</LIST>
</Article>
</root>

Transform to nest items within one list when source contains flat tagging

I have the following XML file:
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525" href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526" href="#s9781452281988.n39.i3312">Interpersonal conflict</a></li>
And I want the output to be the following:
<item>See also
<list rend="runon">
<item><term>Emotion</term></item>
<item><term>Interpersonal conflict</term></item>
</list>
</item>
Basically if I have multiple a[#class='term-ref'], the first instance should start the list rend="runon" and subsequent a[#class='term-ref'] should be included as item/term within the list.
The below was my try, but it is not working as I had hoped, and is closing the list before the second item/term (elements which are also not being output):
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="2.0">
<xsl:template match="li">
<xsl:element name="item">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="a[#class='term-ref'][1]">
<xsl:element name="list">
<xsl:attribute name="rend" select="'runon'"/>
<xsl:element name="item">
<xsl:element name="term">
<xsl:apply-templates/>
</xsl:element>
</xsl:element>
<xsl:if test="a[#class='term-ref'][position() >1]">
<xsl:element name="item">
<xsl:element name="term">
<xsl:apply-templates/>
</xsl:element>
</xsl:element>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="translate(., '.,;', '')"/>
</xsl:template>
</xsl:stylesheet>
On the source, XML, the above stylesheet produces this output:
<item>See also
<list rend="runon">
<item><term>Emotion</term></item>
</list>
Interpersonal conflict</item>
Which is incorrect.
What am i doing wrong?
This short transformation (almost completely "push style", with no conditional instructions, no xsl:element and no unnecessary function calls like translate() or replace()):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="li">
<item><xsl:apply-templates/></item>
</xsl:template>
<xsl:template match="a[#class='term-ref'][1]">
<list rend="runon">
<xsl:apply-templates mode="group"
select="../a[#class='term-ref']"/>
</list>
</xsl:template>
<xsl:template match="a[#class='term-ref']" mode="group">
<item><term><xsl:apply-templates/></term></item>
</xsl:template>
<xsl:template match="a[#class='term-ref']|li/text()" priority="-1"/>
</xsl:stylesheet>
when applied on the provided XML document -- which is well-formed:
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525"
href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526"
href="#s9781452281988.n39.i3312">Interpersonal conflict.</a>.
</li>
produces the wanted, correct result:
<item>See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict.</term>
</item>
</list>
</item>
This should work...
XML Input (well-formed)
<doc>
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525" href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526" href="#s9781452281988.n39.i3312">Interpersonal conflict.</a>.
</li>
</doc>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="li">
<item>
<xsl:apply-templates select="i/text()"/>
<xsl:if test="a">
<list rend="runon">
<xsl:apply-templates select="a"/>
</list>
</xsl:if>
</item>
</xsl:template>
<xsl:template match="a">
<item><term><xsl:apply-templates select="node()"/></term></item>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="replace(.,'[.,;]','')"/>
</xsl:template>
</xsl:stylesheet>
Output
<doc>
<item>See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict</term>
</item>
</list>
</item>
</doc>
This should do what you are looking to do:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="li">
<xsl:element name="item">
<xsl:apply-templates select="node()" />
<xsl:apply-templates select="." mode="items" />
</xsl:element>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="normalize-space(translate(., '.,;', ''))"/>
</xsl:template>
<xsl:template match="a[#class = 'term-ref']" />
<xsl:template match="node()" mode="items" />
<xsl:template match="li" mode="items">
<xsl:apply-templates mode="items" />
</xsl:template>
<xsl:template match="li[count(a[#class = 'term-ref']) > 1]" mode="items">
<list rend="runon">
<xsl:apply-templates select="a[#class = 'term-ref']" mode="items" />
</list>
</xsl:template>
<xsl:template match="a[#class = 'term-ref']" mode="items">
<item>
<term>
<xsl:value-of select="."/>
</term>
</item>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, this produces:
<item>
See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict</term>
</item>
</list>
</item>
When run on an input file with just one a.term-ref, this produces:
<item>
See also<item>
<term>Interpersonal conflict</term>
</item>
</item>

Flat XML into tree with XSLT. Show one branch only

I have flat xml structure, i need to convert into hierarchy. With the help of stackoverflow I was able to do it.
Question: Is it possible to show only one branch using the same flat structure?
Here is my xml and xsl files:
XML
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="Stack.xsl"?>
<Items>
<Item>
<Id>1</Id>
<ParentId>0</ParentId>
<Name>1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>2</Id>
<ParentId>1</ParentId>
<Name>1.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>3</Id>
<ParentId>1</ParentId>
<Name>1.2</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>4</Id>
<ParentId>1</ParentId>
<Name>1.3</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>5</Id>
<ParentId>1</ParentId>
<Name>1.4</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>6</Id>
<ParentId>0</ParentId>
<Name>2</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>7</Id>
<ParentId>6</ParentId>
<Name>2.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>8</Id>
<ParentId>6</ParentId>
<Name>2.2</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>9</Id>
<ParentId>0</ParentId>
<Name>3</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>10</Id>
<ParentId>3</ParentId>
<Name>1.2.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>11</Id>
<ParentId>8</ParentId>
<Name>2.2.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>11</Id>
<ParentId>5</ParentId>
<Name>1.4.1</Name>
<SortOrder>0</SortOrder>
</Item>
</Items>
XSL
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:param name="SelectedId" select="'10'"/>
<xsl:key name="ChildNodes" match="/Items/Item" use="ParentId"/>
<xsl:template match="Items">
<ul>
<xsl:apply-templates select="Item[ParentId = 0]" />
</ul>
</xsl:template>
<xsl:template match="Item">
<li>
<xsl:choose>
<xsl:when test="Id = $SelectedId">
<b><xsl:value-of select="Name" /></b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="Name" />
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="Descendants" select="key ('ChildNodes', Id)" />
<xsl:if test="count ($Descendants) > 0">
<ul>
<xsl:apply-templates select="$Descendants" />
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
Current output I have:
1
1.1
1.2
1.2.1
1.3
1.4
1.4.1
2
2.1
2.2
2.2.1
3
Desireable result example:
1
1.1
1.2
1.2.1
1.3
1.4
2
3
One way to do this is to make use of node-set function, which will require the use of an extension namespace in XSLT.
What you could do is that instead of outputing the Descendants variable directly as currently:
<ul>
<xsl:apply-templates select="$Descendants"/>
</ul>
You instead store the results in a variable
<xsl:variable name="list">
<ul>
<xsl:apply-templates select="$Descendants"/>
</ul>
</xsl:variable>
Then you can convert this 'result tree fragment' into a node-set, which you can then check for whether the selected element (held in a b element) exists. If so, you can then output it
<xsl:if test="exsl:node-set($list)//li[b]">
<xsl:copy-of select="$list"/>
</xsl:if>
Here is the full XSLT
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="exsl">
<xsl:output method="html"/>
<xsl:param name="SelectedId" select="'10'"/>
<xsl:key name="ChildNodes" match="/Items/Item" use="ParentId"/>
<xsl:template match="Items">
<ul>
<xsl:apply-templates select="Item[ParentId = 0]"/>
</ul>
</xsl:template>
<xsl:template match="Item">
<li>
<xsl:choose>
<xsl:when test="Id = $SelectedId">
<b>
<xsl:value-of select="Name"/>
</b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="Name"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="Descendants" select="key ('ChildNodes', Id)"/>
<xsl:if test="count ($Descendants) > 0">
<xsl:variable name="list">
<ul>
<xsl:apply-templates select="$Descendants"/>
</ul>
</xsl:variable>
<xsl:if test="exsl:node-set($list)//li[b]">
<xsl:copy-of select="$list"/>
</xsl:if>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<ul>
<li>1
<ul>
<li>1.1</li>
<li>1.2
<ul>
<li>
<b>1.2.1</b>
</li>
</ul></li>
<li>1.3</li>
<li>1.4</li>
</ul></li>
<li>2</li>
<li>3</li>
</ul>
Note, because I am using Microsoft XML here, the extension namespace is "urn:schemas-microsoft-com:xslt". For other processors, you will probably have to use "http://exslt.org/common"

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>

xslt pattern match transformation

How can I transform the following XML with XSLT from this:
<root>
<list>
<item label="21(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
to this:
<root>
<list label="21">
<item label="(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
So, if there is a number before a parenthesis on the label attribute of the first item, that number needs to be aded as the value of the label attribute for the parent list item.
The pattern to match the attribute would be something like:
/(\d+)\([^\)]+\)/
As mentioned by Nikolaus you can use the substring-before and substring-after XPath functions. A sample XSL transformation would look like this:
<?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="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="list">
<list>
<xsl:variable name="prefix" select="substring-before(./item/#label, '(')" />
<xsl:if test="$prefix != '' and number($prefix)">
<xsl:attribute name="label">
<xsl:value-of select="substring-before(./item/#label, '(')"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates />
</list>
</xsl:template>
<xsl:template match="item">
<item>
<xsl:attribute name="label">
<xsl:variable name="prefix" select="substring-before(#label, '(')" />
<xsl:choose>
<xsl:when test="$prefix != '' and number($prefix)">
<xsl:value-of select="concat('(', substring-after(#label, '('))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#label"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates />
</item>
</xsl:template>
</xsl:stylesheet>
you can use the xslt function substring-before to get the substring befor '('
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[1][boolean(number(substring-before(#label,'(')))]">
<xsl:attribute name="label">
<xsl:value-of select="substring-before(#label,'(')"/>
</xsl:attribute>
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="item[1]/#label[boolean(number(substring-before(.,'(')))]">
<xsl:attribute name="label">
<xsl:value-of select="concat('(',substring-after(.,'('))"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<list label="21">
<item label="(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
Edit: Compact predicate.
Edit 2: Test number before parentesis. Explicity strip white space only nodes.
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"list[number(substring-before(item[1]/#label, '('))
=
number(substring-before(item[1]/#label, '('))
]">
<list label="{substring-before(item[1]/#label, '(')}">
<xsl:apply-templates select="node()|#*"/>
</list>
</xsl:template>
<xsl:template match=
"item[1]/#label[number(substring-before(., '('))
=
number(substring-before(., '('))
]">
<xsl:attribute name="label">
<xsl:value-of select="concat('(',substring-after(.,'('))"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<list>
<item label="21(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
produces the wanted, correct result:
<root>
<list label="21">
<item label="(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>