Can I check condition in template match in XSLT? - 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

Related

XSLT - Get String between commas

How can I get the value 'four' in XSLT?
<root>
<entry>(one,two,three,four,five,six)</entry>
</root>
Thanks in advance.
You didn't specify the XSLT version, so I assume version 2.0.
I also assume that word four is only a "marker", stating from which place
take the result string (between the 3rd and 4th comma).
To get the fragment you want, you can:
Use tokenize function to "cut" the whole content of entry
into pieces, using a comma as the cutting pattern.
Take the fourth element of the result array.
This expression can be used e.g. in a template matching entry.
So the example script can look like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="entry">
<xsl:copy>
<xsl:value-of select="tokenize(., ',')[4]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
For your input XML it gives:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<entry>four</entry>
</root>

How to create template to match based upon an XSLT parameter

I'm trying to create a standard-use XSLT that will perform a given task based upon a user-provided XPATH expression as an XSLT parameter.
That is, I need something like this:
<xsl:template match="$paramContainingXPATH">
<!-- perform the task on the node(s) in the given xpath -->
</xsl:template>
For example, suppose I have some XML:
<xml>
<nodeA>whatever</nodeA>
<nodeB>whatever</nodeB>
<nodeC>whatever</nodeC>
<nodeD>whatever</nodeD>
<nodeE>whatever</nodeE>
</xml>
The XSLT needs to transform just a node or nodes matching a provided XPATH expression. So, if the xslt parameter is "/xml/nodeC", it processes nodeC. If the xslt parameter is "*[local-name() = 'nodeC' or local-name() = 'nodeE']", it processes nodeC and nodeE.
This should work for absolutely any XML message. That is, the XSLT cannot have any direct knowledge of the content of the XML. So, it could be a raw XML, or a SOAP Envelope.
I was guessing I might need to grab all the nodes matching the xpath, and then looping over them calling a named template, and using the standard identity template for all other nodes.
All advice is appreciated.
If you really need that feature with XSLT 1.0 or 2.0 then I think you should consider writing one stylesheet that takes that string parameter with the XPath expression and then simply generates the code of a second stylesheet where the XPath expression is used as a match pattern and the other needed templates like the identity template are included statically. Dynamic XPath evaluation is only available in XSLT 3.0 or in earlier versions as a proprietary extension mechanism.
You cannot match a template using a parameter - but you can traverse the tree and compare the path of each node with the given path. Here's a simple example:
XSLT 1.0
<?xml version="1.0" encoding="UTF-8"?>
<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:param name="path" select="'/world/America/USA/California'"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="*"/>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="path-to-me">
<xsl:for-each select="ancestor-or-self::node()">
<xsl:value-of select="name()" />
<xsl:if test="position()!=last()">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$path=$path-to-me">
<xsl:call-template name="action"/>
</xsl:if>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template name="action">
<return>
<xsl:value-of select="." />
</return>
</xsl:template>
</xsl:stylesheet>
Applied to a slightly more ambitious test input of:
<world>
<Europe>
<Germany>1</Germany>
<France>2</France>
<Italy>3</Italy>
</Europe>
<America>
<USA>
<NewYork>4</NewYork>
<California>5</California>
</USA>
<Canada>6</Canada>
</America>
</world>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<return>5</return>
</root>
This could be made more efficient by passing the accumulated path as a parameter of the recursive template, so that each node needs only to add its own name to the chain.
Note:
The given path must be absolute;
Predicates (including positional predicates) and attributes are not implemented in this. They probably could be, with a bit more effort;
Namespaces are ignored (I don't see how you could pass an XPath as a parameter and include namespaces anyway).
If your processor supports an evaluate() extension function, you could forgo the calculated text path and test for intersection instead.
Edit:
Here's an example using EXSLT dyn:evaluate() and set:intersection():
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dyn="http://exslt.org/dynamic"
xmlns:set="http://exslt.org/sets"
extension-element-prefixes="dyn set">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="path" select="'/world/America/USA/California'"/>
<xsl:variable name="path-set" select="dyn:evaluate($path)" />
<xsl:template match="/">
<root>
<xsl:apply-templates select="*"/>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:if test="set:intersection(. , $path-set)">
<xsl:call-template name="action"/>
</xsl:if>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template name="action">
<return>
<xsl:value-of select="." />
</return>
</xsl:template>
</xsl:stylesheet>
Note that this will also work with with paths like:
/world/America/USA/*[2]
//California
and many others that the text comparison method could not accommodate.
I'm sending the element name as a param to the XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="xml"/>
<xsl:param name="user"/>
<xsl:template match="/">
<xsl:call-template name="generic" />
</xsl:template>
<xsl:template name="generic">
<count><xsl:value-of select="count(.//*[local-name()=$user])"/></count>
</xsl:template>
</xsl:stylesheet>
I hope this could help!

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>

Preserving priority of operands in one-liner Xpath 1.0

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

xslt apply-templates selects all remaining textnodes

I have this simplified xml:
<?xml version="1.0" encoding="UTF-8"?>
<a>
<b>
<c>
<d>1</d>
<e>2</e>
</c>
</b>
<f>
<g>3</g>
</f>
</a>
This is the xslt i try to apply:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="a">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="b">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="c">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="d">
</xsl:template>
</xsl:stylesheet>
When I apply this sheet, I get output 2 3, which are the remaining textnodes. I've read about the built-in templates which get applied if it can't find a matching template, but in this case, it should find a template?
What is going on?
Edit:
In this case, i would expect to see nothing, because the templates are empty. But i get 2 3 in stead.
When you do <xsl:template match="d">, you tell the processor to ignore all nodes under <d>.
All other nodes are processed with default rules, including the text() one, which is to print the text.
That's why you see 23, and not 1.
Start from the root:
<xsl:template match="/a">
And specify either a mode (so that the default template does not get called, because it does not find a template for e, f and g) or define your own * template which does nothing at the end of the stylesheet:
<xsl:template match="*"/>