Explanation on why certain nodes show up - xslt

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>

Related

Generate XML and JSON file using single XSLT

I already have an XSLT which takes XML input, transforms it and gives me XML output.
But is there a way where I can use same XSLT, and fetch the transformed XML output and convert it into JSON.
Here is a small and artificial example that you could use as your starting point:
Input XML
<root>
<item id="1">
<name>Alpha</name>
<value>101</value>
</item>
<item id="2">
<name>Bravo</name>
<value>2.25</value>
</item>
<item id="3">
<name>Charlie</name>
<value>33</value>
</item>
</root>
XSLT 2.0
<xsl:stylesheet 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:strip-space elements="*"/>
<xsl:template match="/">
<xsl:variable name="output-xml">
<xsl:apply-templates/>
</xsl:variable>
<!-- write XML to output file -->
<xsl:copy-of select="$output-xml"/>
<!-- produce a JSON file as additional output -->
<xsl:result-document href="json.txt" method="text">
<xsl:apply-templates select="$output-xml" mode="json"/>
</xsl:result-document>
</xsl:template>
<xsl:template match="root">
<array>
<xsl:apply-templates/>
</array>
</xsl:template>
<xsl:template match="item">
<object name="{name}">
<xsl:value-of select="value"/>
</object>
</xsl:template>
<xsl:template match="array" mode="json">
<!-- array -->
<xsl:text>[
</xsl:text>
<xsl:apply-templates mode="json"/>
<xsl:text>
]</xsl:text>
</xsl:template>
<xsl:template match="object" mode="json">
<!-- object -->
<xsl:text> {"</xsl:text>
<xsl:value-of select="#name"/>
<xsl:text>" : </xsl:text>
<xsl:value-of select="."/>
<xsl:text>}</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>,
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
XML Output
<?xml version="1.0" encoding="UTF-8"?>
<array>
<object name="Alpha">101</object>
<object name="Bravo">2.25</object>
<object name="Charlie">33</object>
</array>
Output file "json.txt"
[
{"Alpha" : 101},
{"Bravo" : 2.25},
{"Charlie" : 33}
]

XSLT transformation xml List item with sub list

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>

XSLT Add nested element to the last node based on the existing element

I have the following XML, the XML has multiple occurance of 'item' elements, and there will be single occurance of 'info' element
<?xml version="1.0" encoding="utf-8" ?>
<root xmlns="http://temo.com/tempe.xsd">
<di>
<md>2013-07-09T09:43:00</md>
</di>
<list>
<item>
<Name>test</Name>
<section block1="true">
<block1>
<move>1</move>
<info>
<item1>test item 1</item1>
<item2>false</item2>
<item3>1</item3>
</info>
</block1>
<block2>
...
</block2>
</section>
</item>
</list>
<option>
...
</option>
</root>
and I want to convert it to the below format, i.e., if 'move' element is present then add new elements in the last position of 'info' element should be created
<item4>
<item5>1</item5>
</item4>
<?xml version="1.0" encoding="utf-8" ?>
<root xmlns="http://temo.com/tempe.xsd">
<di>
<md>2013-07-09T09:43:00</md>
</di>
<list>
<item>
<Name>test</Name>
<section block1="true">
<block1>
<move>1</move>
<info>
<item1>test item 1</item1>
<item2>false</item2>
<item3>1</item3>
<item4>
<item5>1</item5>
</item4>
</info>
</block1>
<block2>
...
</block2>
</section>
</item>
<item>
...
</item>
</list>
<option>
...
</option>
</root>
I am using the following XSLT to convert to the above format
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/info/*[position()=last()]">
<xsl:copy>
<xsl:choose>
<xsl:when test="/section/block/move">
<!--Add new element item4-->
<xsl:element name="item4">
<xsl:element name="item5">
<xsl:value-of select="section/block/move"/>
</xsl:element>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="identity" />
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Could you please Help me to find the issue in the XSLT?
I am new to XSLT
Thank you :)
You have a default namespace declaration on the input XML and the output XML also needs that namespace for the elements to be inserted so you need
xmlns:tp="http://temo.com/tempe.xsd"
on your xsl:stylesheet root element, then you need to adapt your paths and patterns to use that prefix e.g. instead of info you need tp:info and instead of section/block/move you need tp:section/tp:block/tp:move and so on.
The paths in your XSLT sample seem wrong however you use /section/block/move but your root element is root so /section will never select anything, even if you add the namespace prefix. And your path has block, the input has block1.
Instead of trying to fix your code I wrote a new stylesheet, it is
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://temo.com/tempe.xsd"
xmlns:tp="http://temo.com/tempe.xsd"
exclude-result-prefixes="tp"
version="1.0">
<xsl:param name="to-insert" xml:space="preserve">
<item4>
<item5><xsl:value-of select="//tp:section/tp:block1/tp:move"/></item5>
</item4>
</xsl:param>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="tp:item[.//tp:move[. = 1]]//tp:info">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:copy-of select="$to-insert"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It outputs
<root xmlns="http://temo.com/tempe.xsd">
<di>
<md>2013-07-09T09:43:00</md>
</di>
<list>
<item>
<Name>test</Name>
<section block1="true">
<block1>
<move>1</move>
<info>
<item1>test item 1</item1>
<item2>false</item2>
<item3>1</item3>
<item4 xmlns:tp="http://temo.com/tempe.xsd">
<item5>1</item5>
</item4>
</info>
</block1>
<block2>
...
</block2>
</section>
</item>
</list>
<option>
...
</option>
</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>

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>