Preserving priority of operands in one-liner Xpath 1.0 - xslt

I can't really formulate it properly, better with example.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar id="someId" class="someClass"/>
<buz class="someClass" id="someId"/>
<ololo class="someClass"/>
<test id="someId"/>
</foo>
XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*/*">
<xsl:value-of select="#id | #class"/><xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
The result:
someId
someClass
someClass
someId
What I need
I need that "priority" of attributes remained as stated in my xpath expression.
So, if we call #id | #class an expression with two operands, I need that the attributes would be taken not in a document order, but in the order of how two operands were specified in the expression.
So, the result should be:
someId
someId
someClass
someId
#class should be taken only if #id is not present.
I know, that it can be done with conditional logic, but I'm really interested in a short solution, because it's common and used as attribute value template.
It might be obvious and I am missing The Elegant One.
Do note that I'm speaking in terms of XPath 1.0.

Use:
#id | #class[not(../#id)]
This XPath expression selects always one node: #id if it exists, and only if #id doesn't exist then #class.
So this transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="text"/>
<xsl:template match="foo/*">
<xsl:value-of select="#id | #class[not(../#id)]"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<foo>
<bar id="someId" class="someClass"/>
<buz class="someClass" id="someId"/>
<ololo class="someClass"/>
<test id="someId"/>
</foo>
produces the wanted, correct results:
someId
someId
someClass
someId

Related

Sequence concatenation or union when using except operator

I am trying to understand the difference (or lack thereof) between using sequence concatenation or sequence union in this case, so as an example :
Input XML:
<?xml version="1.0" encoding="utf-8" ?>
<document>
<someElement a="1" b="2" c="3" d="4"/>
</document>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="someElement">
<xsl:copy>
<xsl:copy-of select="#* except (#c, #d)"/>
</xsl:copy>
<xsl:copy>
<xsl:copy-of select="#* except (#c | #d)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<document>
<someElement a="1" b="2"/>
<someElement a="1" b="2"/>
</document>
So I was using xsl:copy with the except operator, as explained on page 261 or Dr. Kay's XSLT and XPath 4th ed. book. There the comma is used to build the sequence.
I also tried with the uninon operator and got the same result.
On page 537, the operators are defined as :
, : Sequence concatenation
| union : Union of two sequences considered as sets of nodes
So when using either, is there a difference?
For the except operator, which is a "set" operator, like union, | and like intersect, it makes no difference as https://www.w3.org/TR/xpath-31/#combining_seq states: "All these operators eliminate duplicate nodes from their result sequences based on node identity. The resulting sequence is returned in document order".
When you construct a sequence using the comma operator, the operands are concatenated in the order that you list them, and the sequence may contain duplicate items.
OTOH, when you use the union operator, items are listed in document order and duplicate nodes are eliminated.
Here is a more illustrative example:
XML
<root>
<alpha/>
<bravo/>
<charlie/>
</root>
XSLT 2.0
<xsl:stylesheet version="2.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="/root">
<xsl:copy>
<comma>
<xsl:copy-of select="bravo, alpha, charlie, bravo"/>
</comma>
<union>
<xsl:copy-of select="bravo | alpha | charlie | bravo"/>
</union>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<comma>
<bravo/>
<alpha/>
<charlie/>
<bravo/>
</comma>
<union>
<alpha/>
<bravo/>
<charlie/>
</union>
</root>
Added:
In my answer above I chose to ignore the part about the except operator and concentrate on the differences between the comma and union operators when constructing a sequence.
As the other answers pointed out, when the except operator is added to the constructing expression, these differences are effectively wiped out.
To demonstrate this in a rather extreme way, the instruction:
<except>
<xsl:copy-of select="(bravo, alpha, charlie, bravo) except()"/>
</except>
when used in the above example, will produce:
<except>
<alpha/>
<bravo/>
<charlie/>
</except>
The expression X except Y selects every node that is in X and is not in Y.
The expressions (P|Q) and (P,Q) deliver the same nodes, though the results may be in a different order and the second form may include duplicates. But since both forms include the same nodes, they are interchangeable when used on the right-hand side of "except".

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>

Find node containing the most recent date when date needs converted to valid date format

I need an xPath to be used in a global variable which will select the 'Policy' node with the most recent dateTime (2014-12-02-04:00). Unfortanately the Time delimeter is a dash instead of 'T' so I can't use max() straight away. If I try to use substring or translate to remove the dashes and colon to simply compare numbers I get the error which states that there cannot be more that one sequence in those functions.
Is there a way to evaluate PolicyEffectiveDate from the root node when it is in 2014-12-02-04:00 format?
/Policies/PolicySummary/Policy[2]/PolicyEffectiveDate
XSLT 2.0 is OK. Also, note that I don't have control over the XML format. Thanks.
Given sample XML of:
<?xml version="1.0" encoding="UTF-8"?>
<Policies>
<PolicySummary>
<Policy>
<PolicyNumber>123</PolicyNumber>
<PolicyEffectiveDate>2014-06-01-04:00</PolicyEffectiveDate>
</Policy>
<Policy>
<PolicyNumber>1234</PolicyNumber>
<PolicyEffectiveDate>2014-12-02-04:00</PolicyEffectiveDate>
</Policy>
<Policy>
<PolicyNumber>12345</PolicyNumber>
<PolicyEffectiveDate>2014-08-02-04:00</PolicyEffectiveDate>
</Policy>
</PolicySummary>
</Policies>
You can simply sort the policies by their "dates" as text. For example:
<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:strip-space elements="*"/>
<xsl:template match="/">
<xsl:for-each select="Policies/PolicySummary/Policy">
<xsl:sort select="PolicyEffectiveDate" data-type="text" order="descending"/>
<xsl:if test="position()=1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
will return:
<?xml version="1.0" encoding="utf-8"?>
<Policy>
<PolicyNumber>1234</PolicyNumber>
<PolicyEffectiveDate>2014-12-02-04:00</PolicyEffectiveDate>
</Policy>
in your example.

Can I check condition in template match in XSLT?

I want to check variable in template match, is it possible?
like:
<xsl:template match="*:Item and $MODE='PURCHASE'">
So template should check variable $MODE='PURCHASE' as well
Not in XSLT 1.0.
In XSLT 2.0 one can have variable references -- in the predicates of the template match pattern.
For example:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="MODE" select="'PURCHASE'"/>
<xsl:template match="*:Item[$MODE='PURCHASE']">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on this XML document:
<t xmlns:x="some:x">
<x:Item>someText</x:Item>
</t>
the wanted, correct result is produced:
someText

template match - how to specify OR conditon

I want to specify a match expression in a template that will get invoked on multiole namespaces of element:
<xsl:template match="*[namespace-uri()='abc.com' or namespace-uri()='def.com']">
...
</xsl:template>
But this does not seem to work. It only gets invoked if left side of or expression is true.
The usual approach to work with namespaces is to declare them e.g.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:abc="http://example.com/abc"
xmlns:def="http://example.com/def"
exclude-result-prefixes="abc def">
<xsl:template match="abc:* | def:*">...</xsl:template>
...
</xsl:stylesheet>
That being said, I don't see anything wrong with your or predicate expression, other than that you haven't provided any input you use it with.
<xsl:template match="*[namespace-uri()='abc.com' or namespace-uri()='def.com']">
...
</xsl:template>
But this does not seem to work.
This is correct code.
So, the problem is in the code that you haven't shown to us. Please, also provide a simple XML document so that everyone could apply the provided XSLT code to the provided XML document and repro the problem.
Here is a demonstration that the "suspected" code is correct:
<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="*[namespace-uri()='def.com' or namespace-uri()='abc.com']">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on this XML document:
<a>
<b:b xmlns:b="abc.com">
<c/>
</b:b>
<f/>
<d:d xmlns:d="def.com">
<e/>
</d:d>
</a>
the wanted, correct result is produced:
<b:b xmlns:b="abc.com">
<c/>
</b:b>
<d:d xmlns:d="def.com">
<e/>
</d:d>