How do I match on any node that itself or any child has an attribute with a value in XSLT Template? - xslt

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

Related

Replace xsi:nil=“true” with open and close tags

I need to do the following transformation in order to get a message pass through a integration broker which does not understand xsi:nil=“true”. I know that for strings having some thing like <abc></abc> is not same as <abc xsi:nil=“true”/> but I have no option.
My input XML:
<PART>
<LENGTH_UOM xsi:nil="1"/>
<WIDTH xsi:nil="1"/>
</PART>
Expected outcome:
<PART>
<LENGTH_UOM><LENGTH_UOM>
<WIDTH></WIDTH>
</PART>
Please let me know your suggestions.
To remove all xsi:nil attributes combine the identity template with an empty template matching xsi:nil.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://xsi.com">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="node()|#*"> <!-- identity template -->
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="#xsi:nil" /> <!-- empty template -->
</xsl:stylesheet>
If you only want to remove those whose value is true use the following empty template instead.
<xsl:template match="#xsi:nil[.='1' or .='true']" />
Concerning the opening and closing tag topic I suggest reading this SO question in which Martin Honnen states that (in the comments of the answer):
I am afraid whether an empty element is marked up as or or is not something that matters with XML and is usually not something you can control with XSLT processors.

XSLT: How to discard unwanted HTML nodes from source?

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>

XSLT: Match first descendant element

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>

Having trouble selecting properties with XSLT

I need to select Property1, and SubProperty2 and strip out any other properties. I need to make this future proof so that any new properties added to the xml won't break validation. iow's new fields have to be stripped by default.
<Root>
<Property1/>
<Property2/>
<Thing>
<SubProperty1/>
<SubProperty2/>
</Thing>
<VariousProperties/>
</Root>
so in my xslt I did this:
<xsl:template match="Property1">
<Property1>
<xsl:apply-templates/>
</Property1>
</xsl:template>
<xsl:template match="/Thing">
<SubProperty1>
<xsl:apply-templates select="SubProperty1" />
</SubProperty1>
</xsl:template>
<xsl:template match="*" />
The last line should strip anything I haven't defined to be selected.
This works to select my property1 but it always selects an empty node for SubProperty. The match on * seems to strip out the deeper object before my match on them can work.
I removed the match on * and it select my SubProperty with a value. So, how can I select the sub properties and still strip everything away that I am not using.
Thanks for any advise.
There are two problems:
<xsl:template match="*"/>
This ignores any element for which there isn't an overriding, more specific template.
Because there isn't a specific template for the top element Root it is ignored together with all of its subtree -- which is the complete document -- no output at all is produced.
The second problem is here:
<xsl:template match="/Thing">
This template matches the top element named Thing.
However in the provided document the top element is named Root. Therefore the above template doesn't match any node from the provided XML document and is never selected for execution. As the code inside its body is supposed to generate SubProperty1, no such output is generated.
Solution:
Change
<xsl:template match="*"/>
to:
<xsl:template match="text()"/>
And change
<xsl:template match="/Thing">
to
<xsl:template match="Thing">
The whole transformation becomes:
<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="Property1">
<Property1>
<xsl:apply-templates/>
</Property1>
</xsl:template>
<xsl:template match="Thing">
<SubProperty1>
<xsl:apply-templates select="SubProperty1" />
</SubProperty1>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
And when applied on the following XML document (as the provided is severely malformed it had to be fixed):
<Root>
<Property1/>
<Property2/>
<Thing>
<SubProperty1/>
<SubProperty2/>
</Thing>
<VariousProperties/>
</Root>
the result now is what is wanted:
<Property1/>
<SubProperty1/>

What's wrong with my <xsl:param>?

I have an xsl:param that I'm trying to use to do a template match on an attribute. By everything I've found here and on the Internet, I'm doing this correctly. However, my output is blank.
Here is my xslt
<xsl:param name="strm_name">main</xsl:param>
<xsl:template match="stream[#name='{$strm_name}']"></xsl:template>
If I hardcode the param call to "main", this works just fine.
Here is the XML tag I'm trying to match to..
<doc><stream name="main"></stream></doc>
Any help is much appreciated!
I see two issues:
You cannot use a variable or parameter reference in a match pattern in XSLT 1.0
You do not need the surrounding '{...}' when referencing your parameter in the predicate. (You're probably confusing this with an Attribute Value Template.) Use this instead: stream[#name=$strm_name]
A possible workaround for issue #1 is to select only those elements that meet the criteria controlled by your param. (You can reference a param in a select expression).
For example, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:param name="strm_name" select="'main'"/>
<xsl:template match="/">
<xsl:apply-templates select="/*/stream[#name=$strm_name]" />
</xsl:template>
<xsl:template match="stream">
<xsl:apply-templates />
<xsl:text>/</xsl:text>
</xsl:template>
</xsl:stylesheet>
Applied to this document:
<root>
<stream name="main">1</stream>
<stream name="other">2</stream>
<stream name="main">3</stream>
<stream name="main">4</stream>
<stream name="other">5</stream>
<stream name="other">6</stream>
</root>
...matches only the desired nodes. Output:
1/3/4/