XSLT/Xpath Select elements based on the values of two attributes - xslt

I wish to return all <nodes> which have the tags with the attributes k=network & v=LU. Note LU could be part of a string. This XML just returns a list of <tag k="network" v="LU"/>
If there are any other improvements I can make, please note them.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="osm">
<xsl:copy>
<xsl:apply-templates select="node/tag[#k='network' and #v='LU']"/>
</xsl:copy>
</xsl:template>
Original XML:
<osm>
<node>
<tag k="railway" v="station"/>
<tag k="network" v="LU"/>
</node>
<node>
<tag k="railway" v="station"/>
<tag k="network" v="NR"/>
<tag k="operator" v="LU"/>
</node>
<node>
<tag k="railway" v="station"/>
<tag k="network" v="NR,LU"/>
<tag k="operator" v="LU"/>
</node>
<...snip...>
</osm>
Desired output
<osm>
<node>
<tag k="railway" v="station"/>
<tag k="network" v="LU"/>
</node>
<node>
<tag k="railway" v="station"/>
<tag k="network" v="NR,LU"/>
<tag k="operator" v="LU"/>
</node>
</osm>

Am I right in thinking that your requirement is to select and copy all <node> elements that have a child <tag> element with attribute #k='network' and an attribute #v which is a list of tokens including the token LU?
I would do this simply as
<xsl:template match="osm">
<osm>
<xsl:copy-of select="node[tag[#k='network'][contains(#v,'LU')]]"/>
</osm>
</xsl:template>
But the contains() test might need to be made more precise, for example if a value like v="LUCKY" can appear. With XSLT 2.0 I would use the predicate [tokenize(#v, ',')='LU']

Related

grouping with complex "selection"

This is the source XML:
<root>
<!-- a and b have the same date entries, c is different -->
<variant name="a">
<booking>
<date from="2017-01-01" to="2017-01-02" />
<date from="2017-01-04" to="2017-01-06" />
</booking>
</variant>
<variant name="b">
<booking>
<date from="2017-01-01" to="2017-01-02" />
<date from="2017-01-04" to="2017-01-06" />
</booking>
</variant>
<variant name="c">
<booking>
<date from="2017-04-06" to="2017-04-07" />
<date from="2017-04-07" to="2017-04-09" />
</booking>
</variant>
</root>
I'd like to group the three variants so that each variants with same #from and #to in each date should be grouped together.
My attempt is:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"></xsl:output>
<xsl:template match="root">
<variants>
<xsl:for-each-group select="for $i in variant return $i" group-by="booking/date/#from">
<group>
<xsl:attribute name="cgk" select="current-grouping-key()"/>
<xsl:copy-of select="current-group()"></xsl:copy-of>
</group>
</xsl:for-each-group>
</variants>
</xsl:template>
</xsl:stylesheet>
But this gives too many groups. (How) is this possible to achieve?
Using a composite key and XSLT 3.0 you could use
<xsl:template match="root">
<variants>
<xsl:for-each-group select="variant" group-by="booking/date/(#from, #to)" composite="yes">
<group key="{current-grouping-key()}">
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</variants>
</xsl:template>
which should group any variant elements together which have the same descendant date element sequence.
XSLT 3.0 is supported by Saxon 9.8 (any edition) or 9.7 (PE and EE) or a 2017 release of Altova XMLSpy/Raptor.
Using XSLT 2.0 you could concatenate all those date values with string-join():
<xsl:template match="root">
<variants>
<xsl:for-each-group select="variant" group-by="string-join(booking/date/(#from, #to), '|')">
<group key="{current-grouping-key()}">
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</variants>
</xsl:template>
Like the XSLT 3.0 solution, it only groups variant with the same sequence of date descendants, I am not sure whether that suffices or whether you might want to sort any date descendants first before computing the grouping key. In the XSLT 3 case you could do that easily with
<xsl:for-each-group select="variant" group-by="sort(booking/date, (), function($d) { xs:date($d/#from), xs:date($d/#to) })!(#from, #to)" composite="yes">
inline (although that leaves 9.8 HE behind as it does not support function expressions/higher order functions, so there you would need to move the sorting to your own user-defined xsl:function and in there use xsl:perform-sort).

XSL text output - trim blank lines and leading spaces

I have an XSLT that looks like this:
<?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" indent="no" encoding="utf-8" media-type="text/plain" />
<xsl:template match="/SOME/NODE">
<xsl:if test="./BLAH[foo]">
<xsl:value-of select="concat(#id, ',' , ./BLAH/bar/#id, ',' , ./blorb/text())"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The output looks something like this (it is going to be a CSV file):
1,2,3
4,456,22
90,5,some text
365,16,soasdkjasdjkasdf
9,43,more text
What I need is for it to be transformed into:
1,2,3
4,456,22
90,5,some text
365,16,soasdkjasdjkasdf
9,43,more text
The main problems are the blank lines (from nodes that do not match the IF condition) and the indentation. Is there any way to remove the blank lines and trim the indentation while preserving the line breaks after lines that are not blank?
I've tried using <xsl:strip-space elements="*"/>, but then the output looks like this:
1,2,3,4,456,22,90,5,some text,365,16,soasdkjasdjkasdf,9,43,more text
Which doesn't work since I need to have 3 values on each line.
As requested, a (heavily simplified) sample of the input:
<SOME>
<NODE>
<BLAH id="1">
<foo>The Foo</foo>
<bar id="2" />
<blorb> some text </blorb>
</BLAH>
</NODE>
<NODE>
<BLAH id="3">
<bar id="4" />
<blorb>some text that shouldn't be in output because there's no foo here</blorb>
</BLAH>
</NODE>
<NODE>
<BLAH id="5">
<foo>another Foo</foo>
<bar id="6" />
<blorb>some other text</blorb>
</BLAH>
</NODE>
</SOME>
I would suggest you approach it this way:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" />
<xsl:template match="/SOME">
<xsl:for-each select="NODE/BLAH[foo]">
<xsl:value-of select="#id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="bar/#id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="blorb"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Selecting the first and second node in XSLT

Given the following structure, how to copy the first and the second nodes with all their elements from the document based on the predicate in XSLT:
<list>
<slot>xx</slot>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
</list>
<list>
<slot>xx</slot>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
<data>
<name>xxx</name>
<age>xxx</age>
</data>
</list>
How to select the first and the second occurence of data (without the data element itself, only name, age) from the list, where the slot is equal to a different variable, i.e the first list has the slot=02, but I need the data from the second list, where the slot=01. But it does not really matter the order of the list by a slot as long as slot=$slotvariable.
I tried the following statement, but it did not produce any results:
<xsl:element name="{'Lastdata'}">
<xsl:copy-of select="list/data[position()=1 and slot = $slotvariable]" />
</xsl:element>
<xsl:element name="{'prevdata'}">
<xsl:copy-of select="list/data[position()=2 and slot = $slotvariable]" />
</xsl:element>
Any working suggestions would be appreciated
If I understood your question correctly, then:
<Lastdata>
<xsl:copy-of select="list[slot=$slotvariable]/data[1]/*" />
</Lastdata>
<prevdata>
<xsl:copy-of select="list[slot=$slotvariable]/data[2]/*" />
<prevdata>
Hints:
Don't use <xsl:element> unless you have a dynamic name based on an expression.
[1] is a shorthand for [position() = 1]
The following stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="slot" select="'slot1'"/>
<xsl:template match="/lists/list">
<xsl:copy-of select="data[../slot=$slot][position()<3]/*"/>
</xsl:template>
</xsl:stylesheet>
Applied to this source:
<lists>
<list>
<slot>slot1</slot>
<data>
<name>George</name>
<age>7</age>
</data>
<data>
<name>Bob</name>
<age>22</age>
</data>
<data>
<name>James</name>
<age>77</age>
</data>
</list>
<list>
<slot>slot2</slot>
<data>
<name>Wendy</name>
<age>25</age>
</data>
</list>
</lists>
Produces the following result:
<name>George</name>
<age>7</age>
<name>Bob</name>
<age>22</age>

Finding unique nodes with xslt

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>

How can I build a tree from a flat XML list using XSLT?

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>.