Looking at the available axes in XSLT I had to find out that there is no sibling axis which would be the union of preceding-sibling and following-sibling. To me this is a little surprising since I already wrote one answer (XSLT issue...CSV issue.?) in which this axis would have been helpful (although I only have about 10 answers so far). Of course, it is obvious that you can always solve the problem by using the union. So this axis is not really required. But it would be very handy every once in a while and like all the other axes IMHO it would make the code more readable and easier to maintain.
Does anybody know why this axis was left out? Is there maybe a non-obvious reason for this?
By the way: I found at least one issue on StackExchange with a warning about a potential performance degrade using the preceding-sibling and following-sibling axes. But I assume this is true for all the axes containing a substantial portion of the XML tree is used in a nested way. So the reason for omission could not have been due to performance.
Since there has been no activity with this question for a while I would like to answer it myself. Picking up one thought in the comments, it is, of course, hard to retrospectively say why the people responsible of the XSLT 1.0 specification omitted the sibling axis.
One of the most conclusive reasons could have been related to the comments by #JLRiche and #MichaelKay: axis are supposed to go into a specific direction with respect to the reference node and it may be difficult to determine what the direction for sibling would be.
In order to investigate this a little further I set up a test XSLT and a test input XML to check how the axes work (see further below) and in particular what the order of the nodes in the axes are. The result was surprising to me:
The preceding-sibling axes does not start at the node closest to the reference node but with node closest to the start of the document.
The following-sibling does start at the reference node.
This would actually allow to define
sibling := preceding-sibling | following-sibling
with the nodes in this set being continuously iterated from the beginning of the document to the end. There would be no "jump".
The suggested alternative
../node except .
also works well and yields the same set in the same ordering. However, looking at an unfamiliar XSLT I would assume that a sibling axis would explain the logic better than using the parent-children construct.
Interestingly, the fact that axes do not start at the node closest to the reference node but at the node closest the beginning of the document also applies to preceding and ancestor so for example ancester::node[1] does not return the parent of the node but the root node.
The original motivation for me to ask the question was related to not having to repeat a lengthy CONDITION imposed on the attributes of the nodes, e.g. I did not want to write
preceding-sibling::node[CONDITION] | following-sibling::node[CONDITION]
However, since the expression above can be rewritten as
(preceding-sibling::node | following-sibling::node)[CONDITION]
the disadvantage of having to use two axes instead of a sibling axis is not as bad as thought. Of course, in XSLT 2.0 this also works for
(../node except .)[CONDITION]
So, to answer my question: I don't think there is a good reason not to define a sibling axis. I guess nobody thought of it. :-)
Test Setup
This XML test input
<?xml version="1.0" encoding="ISO-8859-1"?>
<node id="1">
<node id="2">
<node id="3">
<node id="4"/>
<node id="5"/>
<node id="6"/>
</node>
<node id="7">
<node id="8"/>
<node id="9"/>
<node id="10"/>
</node>
<node id="11">
<node id="12"/>
<node id="13"/>
<node id="14"/>
</node>
</node>
<node id="15">
<node id="16">
<node id="17"/>
<node id="18"/>
<node id="19"/>
</node>
<node id="20">
<node id="21"/>
<node id="22"/>
<node id="23"/>
</node>
<node id="24">
<node id="25"/>
<node id="26"/>
<node id="27"/>
</node>
</node>
<node id="28">
<node id="29">
<node id="30"/>
<node id="31"/>
<node id="32"/>
</node>
<node id="33" value="A">
<node id="34"/>
<node id="35"/>
<node id="36"/>
</node>
<node id="37">
<node id="38"/>
<node id="39"/>
<node id="40"/>
</node>
<node id="41">
<node id="42"/>
<node id="43"/>
<node id="44"/>
</node>
<node id="45" value="A">
<node id="46"/>
<node id="47"/>
<node id="48"/>
</node>
</node>
</node>
using this XSLT 2.0 sheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:variable name="id" select="'37'"/>
<xsl:template name="dump">
<xsl:text> </xsl:text>
<xsl:value-of select="#id"/>
</xsl:template>
<xsl:template match="//node[#id = $id]">
<xsl:text>preceding siblings: </xsl:text>
<xsl:for-each select="preceding-sibling::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
following siblings: </xsl:text>
<xsl:for-each select="following-sibling::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
preceding and following siblings: </xsl:text>
<xsl:for-each select="preceding-sibling::node | following-sibling::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
preceding and following siblings with value A: </xsl:text>
<xsl:for-each select="(preceding-sibling::node | following-sibling::node)[#value = 'A']">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
following siblings: </xsl:text>
<xsl:for-each select="following-sibling::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
parent's children: </xsl:text>
<xsl:for-each select="../node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
parent's children except self: </xsl:text>
<xsl:for-each select="../node except .">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
parent's children except self with value A: </xsl:text>
<xsl:for-each select="(../node except .)[#value = 'A']">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
ancestors: </xsl:text>
<xsl:for-each select="ancestor::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
immediate ancestor: </xsl:text>
<xsl:for-each select="(ancestor::node)[1]">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
ancestors or self: </xsl:text>
<xsl:for-each select="ancestor-or-self::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
descendants: </xsl:text>
<xsl:for-each select="descendant::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
descendants or self: </xsl:text>
<xsl:for-each select="descendant-or-self::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
preceding: </xsl:text>
<xsl:for-each select="preceding::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
following: </xsl:text>
<xsl:for-each select="following::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
will yield this output
preceding siblings: 29 33
following siblings: 41 45
preceding and following siblings: 29 33 41 45
preceding and following siblings with value A: 33 45
following siblings: 41 45
parent's children: 29 33 37 41 45
parent's children except self: 29 33 41 45
parent's children except self with value A: 33 45
ancestors: 1 28
immediate ancestor: 1
ancestors or self: 1 28 37
descendants: 38 39 40
descendants or self: 37 38 39 40
preceding: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 29 30 31 32 33 34 35 36
following: 41 42 43 44 45 46 47 48
Related
Suppose we have the following source xml.
<Data Key="SS_001PG"
OC:DataId="001PG"
OC:UniqueIdentifier="01-003"
OC:Status="available"
OC:DateOfBirth="2010-06-29"
OC:Sex="m">
<Event EventOID="123"
OC:EventLocation="we"
OC:StartDate="2010-07-12"
OC:Status="started"
OC:Age="0"
EventRepeatKey="1"></Event>
<Event EventOID="123"
OC:StartDate="2010-07-14"
OC:Status="started"
OC:Age="0"
EventRepeatKey="2"></Event>
</Data>
<Data Key="SS_1"
OC:DataId="1"
OC:UniqueIdentifier="1"
OC:Status="available"
OC:DateOfBirth="2010-07-14"
OC:Sex="m">
<Event EventOID="123"
OC:StartDate="2010-07-16"
OC:EndDate="2010-07-14"
OC:Status="started"
OC:Age="-1"
EventRepeatKey="1"></Event>
</Data>
We have the following xslt code to process it.
<xsl:variable name="repeatedEvents" select="//Event[#EventOID='123']"/>
<xsl:for-each select="$repeatedEvents">
<xsl:sort select="#EventRepeatKey" data-type="number"/>
<xsl:variable name="prevIndex" select="position()-1"/>
<xsl:variable name="prevEvent"
select="$repeatedEvents[position()=$prevIndex]"/>
<xsl:choose>
<xsl:when test="position()=1">
<xsl:value-of select="#EventRepeatKey"/>
</xsl:when>
<xsl:otherwise>
<xsl:if test="$prevEvent/#EventRepeatKey != #EventRepeatKey">
<xsl:value-of select="#EventRepeatKey"/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
Now, as you can see, we are selecting all Events having the same EventOID and then sort the elements using EventRepeatkey. So, after the sorting, the Event under the second Data gets in between the Events of the first Data. Inside the loop, while the second element is being processed, We can access the first element using previous index, but when the third element is being processed, We can’t access the second element using the previous index. Is this because the second element is in a lower position in the tree than the third element? Any suggestion how we could solve the problem?
Can someone help?
It seems that you want to perform grouping.
Here is a simple use of the Muenchian method for grouping:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kEvByRepK" match="Event[#EventOID='123']"
use="#EventRepeatKey"/>
<xsl:template match=
"Event[#EventOID='123'
and
generate-id()
=
generate-id(key('kEvByRepK', #EventRepeatKey)[1])
]">
<xsl:value-of select="#EventRepeatKey"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
when this transformation is performed on the following XML document (wrapping the provided non-well-formed fragment):
<t xmlns:OC="my:OC" >
<Data Key="SS_001PG" OC:DataId="001PG" OC:UniqueIdentifier="01-003"
OC:Status="available" OC:DateOfBirth="2010-06-29" OC:Sex="m">
<Event EventOID="123" OC:EventLocation="we" OC:StartDate="2010-07-12"
OC:Status="started" OC:Age="0" EventRepeatKey="1"/>
<Event EventOID="123" OC:StartDate="2010-07-14" OC:Status="started"
OC:Age="0"
EventRepeatKey="2"/>
</Data>
<Data Key="SS_1" OC:DataId="1" OC:UniqueIdentifier="1" OC:Status="available"
OC:DateOfBirth="2010-07-14" OC:Sex="m">
<Event EventOID="123" OC:StartDate="2010-07-16" OC:EndDate="2010-07-14"
OC:Status="started" OC:Age="-1" EventRepeatKey="1"/>
</Data>
</t>
the wanted, correct result is produced:
1
2
Explanation: Read about the Muenchian method for grouping.
I have an xml document that contains some "Item" elements with ids. I want to make a list of the unique Item ids. The Item elements are not in a list though - they can be at any depth within the xml document - for example:
<Node>
<Node>
<Item id="1"/>
<Item id="2"/>
</Node>
<Node>
<Item id="1"/>
<Node>
<Item id="3"/>
</Node>
</Node>
<Item id="2"/>
</Node>
I would like the output 1,2,3 (or a similar representation). If this can be done with a single xpath then even better!
I have seen examples of this for lists of sibling elements, but not for a general xml tree structure. I'm also restricted to using xslt 1.0 methods. Thanks!
Selecting all unique items with a single XPath expression (without indexing, beware of performance issues):
//Item[not(#id = preceding::Item/#id)]
Try this (using Muenchian grouping):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="item-id" match="Item" use="#id" />
<xsl:template match="/Node">
<xsl:for-each select="//Item[count(. | key('item-id', #id)[1]) = 1]">
<xsl:value-of select="#id" />,
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Not sure if this is what you mean, but just in case.
In the html
<xsl:apply-templates select="item"/>
The template.
<xsl:template match="id">
<p>
<xsl:value-of select="#id"/> -
<xsl:value-of select="."/>
</p>
</xsl:template>
Regarding Umbraco XSLT version 1.
I have aprox. 150 news items in XML. Lets say like this (all is pseudocode until I get more familiar with this xml/xslt):
<news>
<data alias=date>2008-10-20</data>
</news>
<news>
<data alias=date>2009-11-25</data>
</news><news>
<data alias=date>2009-11-20</data>
</news> etc. etc....
I would like to run through the XML and create html-output as a news archive. Something like (tags not important):
2008
Jan
Feb
...
2009
Jan
Feb
Mar
etc. etc.
I can only come up with a nested for-each (pseudocode):
var year_counter = 2002
var month_counter = 1
<xsl:for-each select="./data [#alias = 'date']=year_counter">
<xsl:for-each select="./data [#alias = 'date']=month_counter">
<xsl:value-of select="data [#alias = 'date']>
"...if month_counter==12 end, else month_counter++ ..."
</xsl:for-each>
"... year_counter ++ ..."
</xsl:for-each>
But a programmer pointet out that looping through 10 years will give 120 loops and that is bad coding. Since I think Umbraco caches the result I am not so concerned, plus in this case there will be a max. of 150 records.
Any clues on how to sort and output many news items and group them in year and group each year in months?
Br. Anders
For the following solution I used this XML file:
<root>
<news>
<data alias="date">2008-10-20</data>
</news>
<news>
<data alias="date">2009-11-25</data>
</news>
<news>
<data alias="date">2009-11-20</data>
</news>
<news>
<data alias="date">2009-03-20</data>
</news>
<news>
<data alias="date">2008-01-20</data>
</news>
</root>
and this XSLT 1.0 transformation:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cfg="http://tempuri.org/config"
exclude-result-prefixes="cfg"
>
<xsl:output method="xml" encoding="utf-8" />
<!-- index news by their "yyyy" value (first 4 chars) -->
<xsl:key
name="kNewsByY"
match="news"
use="substring(data[#alias='date'], 1, 4)"
/>
<!-- index news by their "yyyy-mm" value (first 7 chars) -->
<xsl:key
name="kNewsByYM"
match="news"
use="substring(data[#alias='date'], 1, 7)"
/>
<!-- translation table (month number to name) -->
<config xmlns="http://tempuri.org/config">
<months>
<month id="01" name="Jan" />
<month id="02" name="Feb" />
<month id="03" name="Mar" />
<month id="04" name="Apr" />
<month id="05" name="May" />
<month id="06" name="Jun" />
<month id="07" name="Jul" />
<month id="08" name="Aug" />
<month id="09" name="Sep" />
<month id="10" name="Oct" />
<month id="11" name="Nov" />
<month id="12" name="Dec" />
</months>
</config>
<xsl:template match="root">
<xsl:copy>
<!-- group news by "yyyy" -->
<xsl:apply-templates mode="year" select="
news[
generate-id()
=
generate-id(key('kNewsByY', substring(data[#alias='date'], 1, 4))[1])
]
">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- year groups will be enclosed in a <year> element -->
<xsl:template match="news" mode="year">
<xsl:variable name="y" select="substring(data[#alias='date'], 1, 4)" />
<year num="{$y}">
<!-- group this year's news by "yyyy-mm" -->
<xsl:apply-templates mode="month" select="
key('kNewsByY', $y)[
generate-id()
=
generate-id(key('kNewsByYM', substring(data[#alias='date'], 1, 7))[1])
]
">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</year>
</xsl:template>
<!-- month groups will be enclosed in a <month> element -->
<xsl:template match="news" mode="month">
<xsl:variable name="ym" select="substring(data[#alias='date'], 1, 7)" />
<xsl:variable name="m" select="substring-after($ym, '-')" />
<!-- select the label of the current month from the config -->
<xsl:variable name="label" select="document('')/*/cfg:config/cfg:months/cfg:month[#id = $m]/#name" />
<month num="{$m}" label="{$label}">
<!-- process news of the current "yyyy-mm" group -->
<xsl:apply-templates select="key('kNewsByYM', $ym)">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</month>
</xsl:template>
<!-- for the sake of this example, news elements will just be copied -->
<xsl:template match="news">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
When the transformation is applied, the following output is produced:
<root>
<year num="2009">
<month num="11" label="Nov">
<news>
<data alias="date">2009-11-25</data>
</news>
<news>
<data alias="date">2009-11-20</data>
</news>
</month>
<month num="03" label="Mar">
<news>
<data alias="date">2009-03-20</data>
</news>
</month>
</year>
<year num="2008">
<month num="10" label="Oct">
<news>
<data alias="date">2008-10-20</data>
</news>
</month>
<month num="01" label="Jan">
<news>
<data alias="date">2008-01-20</data>
</news>
</month>
</year>
</root>
It has the right structure already, you can adapt actual appearance to your own needs.
The solution is a two-phase Muenchian grouping approach. In the first phase, news items are grouped by year, in the second phase by year-month.
Please refer to my explanation of <xsl:key> and key() over here. You don't need to read the other question, though it is a similar problem. Just read the lower part of my answer.
What you need is the so-called Muenchian Grouping method, which addresses exactly this problem/pattern for XSLT.
Basically, it groups by finding unique keys and looping over the entries contained in the key being used.
in addition to lucero, check out Xsl grouping duplicates problem for avoiding problems with month names being removes
You can't do month_counter++ in XSLT, it's not a procedural language and it's not how XSLT works. So, it's kind of pointless to worry about this being inefficient if this does not work this way.
This looks like a major pain in the neck in XSLT. My XSLT is not fresh enough to try and actually implement it. But here are two ways:
1)
use xsl:key to extract all unique years-
then iterate through these years. For each year do
use xsl:key to extract all months
For each month do
2) (seems easier, if it works.)
sort them by date, save sorted array in variable
iterate this variable (it's important that variable holds sorted array)
each time look at preceding-sibling. If its year/month not equal to the current element, write the appropriate header
3) Forget XSLT, use a real programming language.
I have an XML document based what Excel produces when saving as "XML Spreadsheet 2003 (*.xml)".
The spreadsheet itself contains a header section with a hierarchy of labels:
| A B C D E F G H I
-+-----------------------------------------------------
1| a1 a2
2| a11 a12 a13 a21 a22
3| a111 a112 a121 a122 a131 a132 a221 a222
This hierarchy is present on all sheets in the workbook, and looks more or less the same everywhere.
Excel XML works exactly like ordinary HTML tables. (<row>s that contain <cell>s). I have been able to transform everything into such a tree structure:
<node title="a1" col="1">
<node title="a11" col="1">
<node title="a111" col="1"/>
<node title="a112" col="2"/>
</node>
<node title="a12" col="3">
<node title="a121" col="3" />
<node title="a122" col="4" />
</node>
<!-- and so on -->
</node>
But here is the complication:
there is more than one worksheet, so there is a tree for each of them
the hierarchy may be slightly different on each sheet, the trees will not be equal (for example, sheet 2 may have "a113", while the others don't)
tree depth is not explicitly limited
the labels however are meant to be the same across all sheets, which means they can be used for grouping
I'd like to merge these separate trees into one that looks like this:
<node title="a1">
<col on="sheet1">1</col>
<col on="sheet2">1</col>
<node title="a11">
<col on="sheet1">1</col>
<col on="sheet2">1</col>
<node title="a111">
<col on="sheet1">1</col>
<col on="sheet2">1</col>
</node>
<node title="a112">
<col on="sheet1">2</col>
<col on="sheet2">2</col>
</node>
<node title="a113"><!-- different here -->
<col on="sheet2">3</col>
</node>
</node>
<node title="a12">
<col on="sheet1">3</col>
<col on="sheet2">4</col>
<node title="a121">
<col on="sheet1">3</col>
<col on="sheet2">4</col>
</node>
<node title="a122">
<col on="sheet1">4</col>
<col on="sheet2">5</col>
</node>
</node>
<!-- and so on -->
</node>
Ideally I'd like to be able to do the merge before I even build the three structure from the Excel XML (if you get me started on this, it'd be great). But since I have no idea how I would do this, a merge after the trees have been built (i.e.: the situation described above) will be fine.
Thanks for your time. :)
Here is one possible solution in XSLT 1.0:
<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="/*">
<t>
<xsl:apply-templates
select="node[#title='a1'][1]">
<xsl:with-param name="pOther"
select="node[#title='a1'][2]"/>
</xsl:apply-templates>
</t>
</xsl:template>
<xsl:template match="node">
<xsl:param name="pOther"/>
<node title="{#title}">
<col on="sheet1">
<xsl:value-of select="#col"/>
</col>
<xsl:choose>
<xsl:when test="not($pOther)">
<xsl:apply-templates mode="copy">
<xsl:with-param name="pSheet" select="'sheet1'"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<col on="sheet2">
<xsl:value-of select="$pOther/#col"/>
</col>
<xsl:for-each select=
"node[#title = $pOther/node/#title]">
<xsl:apply-templates select=".">
<xsl:with-param name="pOther" select=
"$pOther/node[#title = current()/#title]"/>
</xsl:apply-templates>
</xsl:for-each>
<xsl:apply-templates mode="copy" select=
"node[not(#title = $pOther/node/#title)]">
<xsl:with-param name="pSheet" select="'sheet1'"/>
</xsl:apply-templates>
<xsl:apply-templates mode="copy" select=
"$pOther/node[not(#title = current()/node/#title)]">
<xsl:with-param name="pSheet" select="'sheet2'"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</node>
</xsl:template>
<xsl:template match="node" mode="copy">
<xsl:param name="pSheet"/>
<node title="{#title}">
<col on="{$pSheet}">
<xsl:value-of select="#col"/>
</col>
<xsl:apply-templates select="node" mode="copy">
<xsl:with-param name="pSheet" select="$pSheet"/>
</xsl:apply-templates>
</node>
</xsl:template>
</xsl:stylesheet>
When the above transformation is applied on this XML document (the concatenation of the two XML documents under a common top node -- left as an exercise for the reader :) ):
<t>
<node title="a1" col="1">
<node title="a11" col="1">
<node title="a111" col="1"/>
<node title="a112" col="2"/>
</node>
<node title="a12" col="3">
<node title="a121" col="3" />
<node title="a122" col="4" />
</node>
<!-- and so on -->
</node>
<node title="a1" col="1">
<node title="a11" col="1">
<node title="a111" col="1"/>
<node title="a112" col="2"/>
<node title="a113" col="3"/>
</node>
<node title="a12" col="4">
<node title="a121" col="4" />
<node title="a122" col="5" />
</node>
<!-- and so on -->
</node>
</t>
The wanted result is produced:
<t>
<node title="a1">
<col on="sheet1">1</col>
<col on="sheet2">1</col>
<node title="a11">
<col on="sheet1">1</col>
<col on="sheet2">1</col>
<node title="a111">
<col on="sheet1">1</col>
<col on="sheet2">1</col>
</node>
<node title="a112">
<col on="sheet1">2</col>
<col on="sheet2">2</col>
</node>
<node title="a113">
<col on="sheet2">3</col>
</node>
</node>
<node title="a12">
<col on="sheet1">3</col>
<col on="sheet2">4</col>
<node title="a121">
<col on="sheet1">3</col>
<col on="sheet2">4</col>
</node>
<node title="a122">
<col on="sheet1">4</col>
<col on="sheet2">5</col>
</node>
</node>
</node>
</t>
Do note the following:
We suppose that both top node elements have "a1" as the value of their title attribute. This can easily be generalized.
The template matching node has a parameter named pOther, which is the corresponding element named node from the other document. This template is applied - to only if $pOther exists.
When no corresponding element named node exists, another template, also matching node, but in mode copy is applied. This template has a parameter named pSheet, the value of which is the sheet name (string) this element belongs to.
How about a callable template taking the sheet number as a parameter, which examines the input and returns the correct "col" node if it appears in that sheet's XML, and nothing if it doesn't. At each node, call it once for each sheet.
To merge the trees, maybe a template that looks for all children of the current node in any sheet, and recurses on itself for each of them.
Sorry no sample code, I find writing XSLT to be pretty slow, probably because I don't do it often. So I may well have missed something crucial. But putting it all together would give something like:
get the title of "/node". With that title:
search all sheets for this title, emitting the "col" node for each
search all sheets for children of nodes with this title (discarding duplicates)
recurse on each of those titles.
Here are some snippets for removing duplicates in various ways:
http://www.dpawson.co.uk/xsl/sect2/N2696.html
Reading multiple documents is processor-dependent, but if all else fails a bit of cut-and-pastery with any old scripting language would probably do, provided that you know they'll all have the same encoding, don't use conflicting ids, and so on.
i use a minimalist MVC framework, where the PHP controler hands the DOM model to the XSLT view (c.f. okapi).
in order to build a navigation tree, i used nested sets in MYSQL. this way, i end up with a model XML that looks as follows:
<tree>
<node>
<name>root</name>
<depth>0</depth>
</node>
<node>
<name>TELEVISIONS</name>
<depth>1</depth>
</node>
<node>
<name>TUBE</name>
<depth>2</depth>
</node>
<node>
<name>LCD</name>
<depth>2</depth>
</node>
<node>
<name>PLASMA</name>
<depth>2</depth>
</node>
<node>
<name>PORTABLE ELECTRONICS</name>
<depth>1</depth>
</node>
<node>
<name>MP3 PLAYERS</name>
<depth>2</depth>
</node>
<node>
<name>FLASH</name>
<depth>3</depth>
</node>
<node>
<name>CD PLAYERS</name>
<depth>2</depth>
</node>
<node>
<name>2 WAY RADIOS</name>
<depth>2</depth>
</node>
</tree>
which represents the following structure:
root
TELEVISIONS
TUBE
LCD
PLASMA
PORTABLE ELECTRONICS
MP3 PLAYERS
FLASH
CD PLAYERS
2 WAY RADIOS
How can I convert this flat XML list to a nested HTML list using XSLT?
PS: this is the example tree from the Managing Hierarchical Data in MySQL.
That form of flat list is very hard to work with in xslt, as you need to find the position of the next grouping, etc. Can you use different xml? For example, with the flat xml:
<?xml version="1.0" encoding="utf-8" ?>
<tree>
<node key="0">root</node>
<node key="1" parent="0">TELEVISIONS</node>
<node key="2" parent="1">TUBE</node>
<node key="3" parent="1">LCD</node>
<node key="4" parent="1">PLASMA</node>
<node key="5" parent="0">PORTABLE ELECTRONICS</node>
<node key="6" parent="5">MP3 PLAYERS</node>
<node key="7" parent="6">FLASH</node>
<node key="8" parent="5">CD PLAYERS</node>
<node key="9" parent="5">2 WAY RADIOS</node>
</tree>
It becomes trivial to do (very efficiently):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="nodeChildren" match="/tree/node" use="#parent"/>
<xsl:template match="tree">
<ul>
<xsl:apply-templates select="node[not(#parent)]"/>
</ul>
</xsl:template>
<xsl:template match="node">
<li>
<xsl:value-of select="."/>
<ul>
<xsl:apply-templates select="key('nodeChildren',#key)"/>
</ul>
</li>
</xsl:template>
</xsl:stylesheet>
Is that an option?
Of course, if you build the xml as a hierarchy it is even easier ;-p
In XSLT 2.0 it would be rather easy with the new grouping functions.
In XSLT 1.0 it's a little more complicated but this works:
<xsl:template match="/tree">
<xhtml>
<head/>
<body>
<ul>
<xsl:apply-templates select="node[depth='0']"/>
</ul>
</body>
</xhtml>
</xsl:template>
<xsl:template match="node">
<xsl:variable name="thisNodeId" select="generate-id(.)"/>
<xsl:variable name="depth" select="depth"/>
<xsl:variable name="descendants">
<xsl:apply-templates select="following-sibling::node[depth = $depth + 1][preceding-sibling::node[depth = $depth][1]/generate-id() = $thisNodeId]"/>
</xsl:variable>
<li>
<xsl:value-of select="name"/>
</li>
<xsl:if test="$descendants/*">
<ul>
<xsl:copy-of select="$descendants"/>
</ul>
</xsl:if>
</xsl:template>
The heart of the matter is the long and ugly "descendants" variable, which looks for nodes after the current node that have a "depth" child greater than the current depth, but are not after another node that would have the same depth as the current depth (because if they were, they would be children of that node instead of the current one).
BTW there is an error in your example result: "FLASH" should be a child of "MP3 PLAYERS" and not a sibling.
EDIT
In fact (as mentionned in the comments), in "pure" XSLT 1.0 this does not work for two reasons: the path expression uses generate-id() incorrectly, and one cannot use a "result tree fragment" in a path expression.
Here is a correct XSLT 1.0 version of the "node" template (successfully tested with Saxon 6.5) that does not use EXSLT nor XSLT 1.1:
<xsl:template match="node">
<xsl:variable name="thisNodeId" select="generate-id(.)"/>
<xsl:variable name="depth" select="depth"/>
<xsl:variable name="descendants">
<xsl:apply-templates select="following-sibling::node[depth = $depth + 1][generate-id(preceding-sibling::node[depth = $depth][1]) = $thisNodeId]"/>
</xsl:variable>
<xsl:variable name="descendantsNb">
<xsl:value-of select="count(following-sibling::node[depth = $depth + 1][generate-id(preceding-sibling::node[depth = $depth][1]) = $thisNodeId])"/>
</xsl:variable>
<li>
<xsl:value-of select="name"/>
</li>
<xsl:if test="$descendantsNb > 0">
<ul>
<xsl:copy-of select="$descendants"/>
</ul>
</xsl:if>
</xsl:template>
Of course, one should factor the path expression that is repeated, but without the ability to turn "result tree fragments" into XML that can actually be processed, I don't know if it's possible? (writing a custom function would do the trick of course, but then it's much simpler to use EXSLT)
Bottom line: use XSLT 1.1 or EXSLT if you can!
2nd Edit
In order to avoid to repeat the path expression, you can also forget the test altogether, which will simply result in some empty that you can either leave in the result or post-process to eliminate.
very helpful!
one suggestion is moving the < ul > inside the template would remove the empty ul.
<xsl:template match="tree">
<xsl:apply-templates select="node[not(#parent)]"/>
</xsl:template>
<xsl:template match="node">
<ul>
<li>
<xsl:value-of select="."/>
<xsl:apply-templates select="key('nodeChildren',#key)"/>
</li>
</ul>
</xsl:template>
</xsl:stylesheet>
You haven't actually said what you'd like the html output to look like, but I can tell you that from an XSLT point of view going from a flat structure to a tree is going to be complex and expensive if you're also basing this on the position of items in the tree and their relation to siblings.
It would be far better to supply a <parent> attribute/node than the <depth>.