Filter elements by attribute values - xslt

I'd like to filter all elements with a duplicate attribute value.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<elements>
<element pos="1"/>
<element pos="2"/>
<element pos="2"/>
<element pos="3"/>
</elements>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<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:output indent="yes"/>
<xsl:template match="/elements/element">
<xsl:if test="#pos != preceding-sibling::element/#pos">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
<xsl:template match="text() | #*"/>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<element pos="2"/>
<element pos="2"/>
<element pos="3"/>
Output (should be):
<?xml version="1.0" encoding="UTF-8"?>
<element pos="1"/>
<element pos="2"/>
<element pos="3"/>
What am I doing wrong? I don't get it. :-(

I would probably use an XSLT key to group the elements and identify duplicates. Perhaps something like this:
<xsl:key name="elements-by-pos" match="element" use="#pos"/>
<xsl:template match="elements/element">
<xsl:copy-of select="
.[generate-id(.) = generate-id(key('elements-by-pos', #pos)[1])]
"/>
</xsl:template>
But as to why your code doesn't work as you expect, I think it's because this XPath expression doesn't mean what you think it does:
#pos != preceding-sibling::element/#pos
This expression does not mean "are there no previous #pos which are equal to the current #pos?"; rather it means "are there any previous #pos which are unequal to the current #pos?"
You are using the != operator to compare a single #pos attribute with the set of #pos attributes which appear earlier in the document. The operator will return a true value if any member of the set satisfies the condition, i.e. if there is at least one preceding #pos attribute which is not equal to the current #pos. See the section on Boolean expressions in the XPath 1.0 spec for details.
Walking through your example:
For the first #pos (whose value is 1), there are no preceding
#pos attributes, so there are none which are unequal to the current
#pos.
For the second #pos (2) there is a previous #pos whose
value is unequal (i.e. the first #pos, whose value is 1).
The third #pos (whose value is also 2) will be copied because the first #pos 1 again satisfies the != condition.
The final #pos (3) appears because any of the
previous #pos attributes will satisfy the condition.
If you were to change that expression to not(#pos = preceding-sibling::element/#pos) then you would see the behaviour you want.

Related

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

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

XSLT Filter comparing two values in same sub node

I need to use XSLT (version 1 unfortunately..) am trying to filter out certain nodes via a comparison of two properties in the sub node.
Here is the XML:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
</SOAP-ENV:Header>
<SOAP-ENV:Body xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<QueriesResponse xmlns="http://schemas.Movies.com/Movies">
<Films xmlns="http://schemas.Movies.com/Movies">
<Film>
<FilmPostings>
<FilmPosting>
<FilmPostingDates>
<FilmPostDate>2017-01-04T19:44:25.9530000-05:00</FilmPostDate>
<FilmActiveDate>2017-01-04T19:44:25.9530000-05:00</FilmActiveDate>
</FilmPostingDates>
</FilmPosting>
</FilmPostings>
</Film>
<Film>
<FilmPostings>
<FilmPosting>
<FilmPostingDates>
<FilmPostDate>2017-01-04T19:50:06.3830000-05:00</FilmPostDate>
<FilmActiveDate>2017-01-04T19:50:06.3100000-05:00</FilmActiveDate>
</FilmPostingDates>
</FilmPosting>
</FilmPostings>
</Film>
<Film>
<FilmPostings>
<FilmPosting>
<FilmPostingDates>
<FilmPostDate>2016-12-05T18:03:14.9830000-05:00</FilmPostDate>
<FilmActiveDate>2017-01-02T00:16:52.7570000-05:00</FilmActiveDate>
</FilmPostingDates>
</FilmPosting>
</FilmPostings>
</Film>
</Films>
</QueriesResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
And here is my transform:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://schemas.Movies.com/Movies"
xmlns:m="http://schemas.Movies.com/Movies"
exclude-result-prefixes="m"
>
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<!-- standard identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- This is mean to compare PostDate and ActiveDate, matching if different. It doesn't match any nodes in XML (but should match the final one). -->
<xsl:template match="m:Film[m:FilmPostings/m:FilmPosting/m:FilmPostingDates/m:FilmPostDate[1] [.!= m:FilmPostings/m:FilmPosting/m:FilmPostingDates/m:FilmActiveDate[1]]]">
</xsl:template>
<!-- This code does match the hard coded value (the second node). -->
<!--<xsl:template match="m:Film[m:FilmPostings/m:FilmPosting/m:FilmPostingDates/m:FilmPostDate[1] [.!= '2017-01-04T19:50:06.3830000-05:00']]">
</xsl:template>-->
</xsl:stylesheet>
So, you'll see in the commented-out bit that I can do a match with hard-coded values, so I'm obviously in the right area - I know that the code will exclude a whole Film node if it finds the match. But it's comparing the two node values that doesn't work.
I've tried all kinds of variations on the right side of the comparison, but it doesn't seem able to pick up the value of the ActiveDate.
To remove Film elements where FilmPostDate and FilmActiveDate you can actually nest the conditions in the match attribute
<xsl:template
match="m:Film[m:FilmPostings/m:FilmPosting/m:FilmPostingDates[m:FilmPostDate = m:FilmActiveDate]]" />
This assumes only one set of FilmPostDate and FilmActiveDate elements per Film. If there were more than one set, you can try remove Film elements where all occurrences are the same (or rather, then are no occurrences that were different).
<xsl:template
match="m:Film[not(m:FilmPostings/m:FilmPosting/m:FilmPostingDates[m:FilmPostDate != m:FilmActiveDate])]" />

XSLT for-each always takes only one element

I have XML which contains multiple elements:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<element>
<ip>192.168.188.101</ip>
</element>
<element>
<ip>192.168.188.100</ip>
</element>
</data>
I want to make it to this structure:
<SYNCDW>
<CIDWSet>
<CI>
<CINUM>192.168.188.101</CINUM>
</CI>
<CI>
<CINUM>192.168.188.100</CINUM>
</CI>
</CIDWSet>
</SYNCDW>
But always one element is processed, the first one, although I have for-each.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<SyncCIDW xmlns="http://www.ibm.com/maximo">
<xsl:attribute name="xsi:schemaLocation" namespace="http://www.w3.org/2001/XMLSchema-instance">http://www.ibm.com/maximo</xsl:attribute>
<CIDWSet>
<xsl:for-each select="*[local-name()='data' and namespace-uri()='']/*[local-name()='element' and namespace-uri()='']">
<CI>
<CINUM>
<xsl:value-of select="string(*[local-name()='data' and namespace-uri()='']/*[local-name()='element' and namespace-uri()='']/*[local-name()='ip' and namespace-uri()=''])"/>
</CINUM>
</CI>
</xsl:for-each>
</CIDWSet>
</SyncCIDW>
</xsl:template>
</xsl:stylesheet>
Why I am not getting processed all other elements but only the first one?
Thank you in advance for help
A couple of things:
Inside of the xsl:for-each, the context switches to the element that you are iterating over (in this case, /data/element), so to select the ip element your XPath is relative from the /data/element that you are "standing on" and would simply be ip. The way you had it, it would be looking for /data/element/data/element/ip inside of the xsl:for-each and would not produce any values inside of the <CINUM>.
You can simplify your XPath expressions. If the elements you are addressing are not bound to a namespace, rather than a generic match on any element and a predicate matching the local-name() and namespace-uri()='', just use the simplified XPath data/element.
If you are creating a statically known attribute xsi:schemaLocation with a statically known value, just use the literal declaration inside of the SyncCIDW element literal.
If you are using xsl:value-of it will yield the string value of the selected node. There is no need for the string() function.
Changes applied to your stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<SyncCIDW xmlns="http://www.ibm.com/maximo"
xsi:schemaLocation="http://www.ibm.com/maximo">
<CIDWSet>
<xsl:for-each select="data/element">
<CI>
<CINUM>
<xsl:value-of select="ip"/>
</CINUM>
</CI>
</xsl:for-each>
</CIDWSet>
</SyncCIDW>
</xsl:template>
</xsl:stylesheet>

Two questions about XSLT

I have now two questions about xslt.
First, I want to describe all of elements which without any attribute. Like
<element id="p0" attribute="a1"/>
<element id="p1"/>
<element id="p2"/>
I need a group of elements without attribute="a1", so just need p1 and p2. With XSLT, should I write <xsl:if test="element[#attribute]=''">? Because when I test it, I found it doesn't work. So please help me.
The second is, I want to make the output result not in the same line. Like
right:
t11
t22
t33
wrong:
t11t22t33
which XSLT word should I write? Thanks a bunch.
All relative paths in an xsl:template are evaluated against the node that was matched by the template. So <xsl:if test="element[#attribute]=''"> is testing for the presence of a child of the current node called element that matches your condition. If this test is located in a template that matches element nodes, then it's not going to work. You should use . to refer to the current node.
The boolean expression element[#attribute]='' is looking for an element node that has an attribute called attribute and that is itself empty. It doesn't test anything about the content of the attribute attribute.
Basically, you need to understand the following template:
<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="element">
<xsl:text>element position=[</xsl:text>
<xsl:value-of select="count(preceding-sibling::element) + 1"/>
<xsl:text>] / </xsl:text>
<xsl:choose>
<xsl:when test=".[not(#attribute)]">
<xsl:text>no #attribute</xsl:text>
</xsl:when>
<xsl:when test=".[#attribute='']">
<xsl:text>empty #attribute</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>non-empty #attribute</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Applied to this test document:
<root>
<element id="p0" attribute="a1">one</element>
<element id="p1" attribute="test"></element>
<element id="p1" attribute=""></element>
<element id="p2">three</element>
</root>
element position=[1] / non-empty #attribute
element position=[2] / non-empty #attribute
element position=[3] / empty #attribute
element position=[4] / no #attribute
This template also answers your second question about newlines.
With this stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="no" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:for-each select="root/element[not(#attribute='a1')]">
<xsl:value-of select="#id"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied to this input:
<root>
<element id="p0" attribute="a1"/>
<element id="p1"/>
<element id="p2"/>
</root>
outputs:
p1
p2

XSLT stylesheet empty paired tags required instead of self-closing tags for null values

I have an input which can sometimes have value and sometimes not. Like value1=ABC or value1=""
During my xsl transformation I have my code with the following line
<element name="test"><xsl:value-of select="$value1"/><element>
The output of the above code when value is present is
<element name="test">ABC</element>
When the value is not present, the output is
<element name="test"/>
Now, I want it to look like
<element name="test"></element>
instead of
<element name="test"/>
Is it possible to get the required output?
If yes, then how to do it?
Try adding <xsl:output method="html"/>.
Example...
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:variable name="test1"/>
<xsl:variable name="test2" select="'value'"/>
<element name="test1"><xsl:value-of select="$test1"/></element>
<element name="test2"><xsl:value-of select="$test2"/></element>
</xsl:template>
</xsl:stylesheet>
produces (when applied to any XML instance):
<element name="test1"></element>
<element name="test2">value</element>