I'm trying to match the first bar element that occurs as a descendant of a foo element in an xsl match pattern and struggling. Initial attempt:
<xsl:template match="//foo//bar[1]">
...
</xsl:template>
fails because there are several bar elements that match. So:
<xsl:template match="(//foo//bar)[1]">
...
</xsl:template>
but that fails to compile.
Tricky. I don't know how efficient or otherwise this would be, but you could turn the pattern on its head and move the logic into a predicate (which is allowed to use axes other than child, attribute and //):
<xsl:template match="foo//bar[not(preceding::bar/ancestor::foo)]">
(any bar inside a foo provided there isn't another bar-inside-a-foo before it). Alternatively you could try a key trick similar to the way Muenchian grouping works, which may be more efficient
<!-- trick key - all matching nodes will end up with the same key value - all
we care about is finding whether a particular node is the first such node
in the document or not. -->
<xsl:key name="fooBar" match="foo//bar" use="1" />
<xsl:template match="foo//bar[generate-id() = generate-id(key('fooBar', 1)[1])]">
You cannot do this with match expressions. In fact, you can do this with match expressions, just not in every XSLT processor, as it seems. See comments.
I'd use an <xsl:if>.
<xsl:template match="foo//bar">
<xsl:if test="generate-id() = generate-id(ancestor::foo[1]//bar)">
<!-- ... -->
</xsl:if>
</xsl:template>
This ensures that only the first descendant <bar> per <foo> (!) is processed any further.
NB: When given a node-set, generate-id() returns the ID of the first node in the set.
Alternative solution is based on the "rule of thumb" use advanced XPATH with "select" not when you "match". Match XML with templates simply by name like foo, not even //foo.
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar>bar1<bar>bar2</bar></bar>
<bar>bar3</bar>
</foo>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="foo">
<xsl:apply-templates select="descendant::bar[1]"/>
</xsl:template>
<xsl:template match="bar">
<!--only the first bar was selected --><xsl:value-of select="text()"/>
</xsl:template>
</xsl:stylesheet>
Related
Say I have XML data like this:
<root>
<subs>
<sub>
<values>
<value attribute="a">1</value>
<value attribute="a">2</value>
<value attribute="c">3</value>
<value attribute="c">4</value>
</values>
</sub>
<subOther>
<otherValues attribute="c">
<otherValue attribute="a">1</value>
<otherValue attribute="a">2</value>
<otherValue attribute="b">3</value>
<otherValue attribute="a">4</value>
</otherValues>
</subOther>
</subs>
</root>
I am trying to create an XSLT template that matches all the nodes in the path to /root/subs/subOther/otherValues/otherValue[attribute="b"].
So far, this is the closest I have gotten:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<!--IDENTITY TEMPLATE -->
<xsl:template match="#*|node()">
<xsl:apply-templates select="node()" />
</xsl:template>
<xsl:template match="//*[ancestor-or-self::[#attribute='b']]">
<xsl:copy>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
But that throws an error saying there is an unexpected token [. I have tried several combinations but they either don't match anything at all, match too much (i.e. everything), or they throw some sort of error.
Edit: I updated the example and expected to be a little more clear. Also note that this is a highly-simplified XML. In my actual file the attribute in question can be at any leaf node on any valid element for that level, so I have to use a more generic path using * and unknown paths with //. So, for instance, one of the value elements could be the one with attribute="b" and it would trigger the same result.
Edit 2: The expected result is to select the nodes that have a path that lead to any left-child w/ an attribute that is equal to a specific value. In my XSD schema there's a total of about 100 possible leaf nodes spread all over the place. The use case is that the attribute in question marks which data elements have had changes, and I need to basically create a "diff" where the full file is whittled down to only nodes where the results are only those items that have changed and their parents. In the small example above, attrubute="b" is the indication I need to copy that node, and thus I would expect this exact result:
<root> <!-- Copied because part of the path -->
<subs> <!-- Copied because part of the path -->
<sub> <!-- Copied because part of the path -->
<values> <!-- Copied because part of the path -->
<value attribute="b">3</value> <!-- Copied because it matches the attribute -->
</values>
</sub>
</subs>
</root>
I hope that makes better sense. Also, I fixed the typo on the xsl:stylesheet being self-closing.
It looks like you have changed the identity template to ignore elements (the change will also drop attributes and text nodes), and added a template to copy the elements you need.
I think you need to reverse your logic. Instead of thinking about things you want to copy, think of it as removing things you don't want to copy.
So, you have the identity template to do the generic copying of elements, and have a second template to remove the things you don't want (the elements which don't have a "b" attribute either on its self or its descendants).
Try this XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<!--IDENTITY TEMPLATE -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(descendant-or-self::*[#attribute = 'b'])]" />
</xsl:stylesheet>
See it in action at http://xsltfiddle.liberty-development.net/ncntCS6
I have an XML:
<foo>
<bar>some text</bar>
</foo>
And I'm generating an HTML from it using XSTL, and looking for an Xpath (or some XSTL method, don't know) that gives me the whole content of foo. To illustrate my problem,
<xsl:value-of select="foo"/>
as expected, delivers only
some text
. But is there anything I can do to get
<bar>some text</bar>
? So to treat it as if the bar tags would be just ordinary strings.
Well, there's certainly no way of treating <tag> as a string, because XSLT sees the output of the XML parser, which is a tree of nodes: the tags have long since disappeared by the time XSLT swings into action.
But you can of course copy the element node as a whole, rather than just extracting its string value. Simply use <xsl:copy-of> in place of <xsl:value-of>.
You can use Copy
HTML
<foo>
<bar>some text</bar>
</foo>
XSL
<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="bar">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output
<bar>some text</bar>
I am using XSLT 1.0, and using xsltproc on OS X Yosemite.
The source content is HTML; the target content is XML.
The issue is a fairly common one. I want all "uninteresting"
nodes simply to be discarded from the output. I've seen catch-all
directives like this:
<xsl:template match="node()|script"/>
<xsl:template match="*">
<xsl:apply-templates/>
</xsl:template>
This is close to what I need. But unfortunately, it's too strong when I need to add another template that visits one of the text nodes caught by node(). For example, suppose I added this template:
<xsl:template match="a/div[#class='location']/br">
<xsl:text> </xsl:text>
</xsl:template>
which simply replaces certain <br/> elements with spaces.
Well, node() precludes this latter template from taking effect,
because the relevant text node containing the line-break is discarded
already!
Well, to correct the issue, here's what I have done in lieu of the catch-all node():
<xsl:template match="html/head|div[#id='banner_parent']|button|ul|div[#id='feed_title']|span|div[#class='submit_event']|script"/>
But this is precisely the problem: I am now piecing together a template
whose matching criteria is likely to be error-prone when the source
content changes.
Is there a simpler directive that would accomplish the same thing? I'm aiming for something like this:
<xsl:template match="node()[not(locations)]|script"/>
Thanks.
If i understood correctly, you want only some nodes in the output and the rest you dont care abour, in this example I try to catch only li elements and throw the rest away.. not sure if this is what you want though http://xsltransform.net/gWmuiKk
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" doctype-public="XSLT-compat" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<!-- Lets pretend li is interesting for you -->
<xsl:template match="li">
<xsl:text>Interesting Node Only!
</xsl:text>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
</xsl:transform>
I've done a bit of XPath in C++ and C# applications, but this is my first time really using it directly in XSLT. I have an XML file that is formatted like this:
<topLevel>
<foo>
<bar>prefix1_Taxi</bar>
...
</foo>
<foo>
<bar>prefix1_SchoolBus</bar>
...
</foo>
...
<foo>
<bar>prefix2_Taxi</bar>
...
</foo>
<foo>
<bar>prefix2_SchoolBus</bar>
...
</foo>
</topLevel>
First, I want to select only the <foo> elements that have a <bar> element that starts with "prefix1_." This appears to work:
<xsl:for-each select="foo[bar[starts-with(., 'prefix1_')]]">
<!-- Style and format the values from child elements of the "prefix1_" <foo> -->
</xsl:for-each>
From inside the for-each block, I then want to select the <foo> element that contains the corresponding "prefix2_" element. I then want to pull data out of each as I see fit.
For example, when the for-each has selected "prefix1_Taxi", I want to then select the foo element that contains the "prefix2_Taxi" bar element.
<xsl:for-each select="foo[bar[starts-with(., 'prefix1_')]]">
<!-- Retrieve the corresponding "prefix2_" foo -->
<!-- Style and format the values from child elements of the "prefix1_" <foo> -->
<!-- Style and format the values from child elements of the "prefix2_" <foo> -->
</xsl:for-each>
Unfortunately, I have no idea how to go about this. In a traditional program I would do something like the following pseudocode:
String buf = barElement.Name.Replace("prefix1_", String.Empty);
XmlNode otherFoo = document.SelectSingleNode("foo[bar[" + buf + "]]");
However XSLT obviously works with a different paradigm for retrieving values and manipulating data, so I'm trying to break out of my old mode of thinking.
Using what I've gathered from some googling on XSLT, I came up with something pretty ugly:
Select the foo element containing a bar that starts with some text:
foo[bar[starts-with(., ...)
Replace the "prefix1_" in our current <bar> element:
replace(<xsl:value-of select='bar'/>, 'prefix1_', '')
This yields a pretty ugly mess:
<xsl:value-of select="foo[bar[starts-with(., replace(<xsl:value-of select='bar'/>, 'prefix1_', ''))]]" />
I'm also pretty sure that the <xsl:value-of> element isn't correct.
How do I go about this? I suspect that I'm missing some core concepts of how to express this concept in XSLT. I'm slogging through the w3.org page on XSLT but I still need much more study and practice.
This XSL stylesheet should give you some flexibility about what you do with the foo elements that contain "prefix2".
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<!-- Matches the top level element and selects the children with prefix1 text. -->
<xsl:template match="topLevel">
<xsl:copy>
<xsl:apply-templates select="foo[bar[starts-with(., 'prefix1_')]]"/>
</xsl:copy>
</xsl:template>
<!-- We're at a foo element with prefix1 text. Select the siblings of the
same vehicle type, with prefix2 text. -->
<xsl:template match="foo[bar[starts-with(., 'prefix1_')]]">
<xsl:variable name="vehicle" select="substring-after(bar, 'prefix1_')"/>
<xsl:apply-templates select="../foo[bar = concat('prefix2_', $vehicle)]"/>
</xsl:template>
<!-- In this template, you can do whatever you want with the foo element containing
the prefix2 vehicles. This example just copies the element and its children. -->
<xsl:template match="foo[bar[starts-with(., 'prefix2_')]]">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
<xsl:for-each select="foo[bar[starts-with(., 'prefix1_')]]">
<xsl:variable name="new-text" as=xs:string" select="replace(bar/text(), '1', '2')" />
<xsl:value-of select="concat(../foo/bar[$new-text]/text(), ' OR DO SOMETHING MORE USEFUL THAN APPENDING TEXT')" />
</xsl:for-each>
You are on the foo element containing the "prefix1" text within the loop, and then change to the other bar element's text and operate on it, whatever you want to do.
Remark: You might need to add xmlns:xs="http://www.w3.org/2001/XMLSchema" to your XSLT root element for the xs:stringin my sample to work. And probably it would also work without the intermediate variable, but I think it makes the code slightly more readable.
I want to write an XSLT template that matches all elements of one namespace except one element. For example I want to match all elements foo:*, but not foo:bar.
Is that possible to define this in a selector or do I have to write an xsl:if condition within the xsl:template (and how can I test the local name of the element)?
To do this, you can just have a template that matches foo:bar that does nothing with it like so:
<xsl:template match="foo:bar" />
To match other foo elements, you can use a more general template
The XSLT processor should match the more specific template first, and so foo:bar will be ignored, and all other foo elements matched by the other template.
So, for example, given this input XML
<foo:root xmlns:foo="foo.com">
<foo:bar>No match</foo:bar>
<foo:pie>Match</foo:pie>
</foo:root>
When you apply the following XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:foo="foo.com">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="foo:bar" />
<xsl:template match="foo:*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If you wanted to do different processing on foo:bar, just add code to the relevant template.
The following is output, without any sign of foo:bar
<foo:root xmlns:foo="foo.com">
<foo:pie>Match</foo:pie>
</foo:root>
XSLT 1.0:
<xsl:template match="foo:*[not(local-name()='bar')]">
<!--do stuff-->
</xsl:template>
XSLT 2.0:
<xsl:template match="foo:*[. except self::foo:bar]">
<!--do stuff-->
</xsl:template>