XSLT 2.0 / XPATH - choose-when testing node - xslt

In XPATH under XSLT 2.0, I am unclear as to why an xsl:choose/xsl:when #test isn't working.
When I run this template testing for the element tei:del[#rend='expunctus'], the test DOES NOT return the result:
<xsl:template match="tei:del[#rend='expunctus'] |
tei:gap |
tei:sic |
tei:supplied[#reason='added'] |
tei:surplus[#reason='repeated' or #reason='surplus'] |
tei:unclear">
<xsl:choose>
<xsl:when test="tei:del[#rend='expunctus']">
[<xsl:text>EXPUNCTUS</xsl:text>]
</xsl:when>
</xsl:template>
When I run this template with just the attribute #rend='expunctus' as the test, the test DOES return the result:
<xsl:template match="tei:del[#rend='expunctus'] |
tei:gap |
tei:sic |
tei:supplied[#reason='added'] |
tei:surplus[#reason='repeated' or #reason='surplus'] |
tei:unclear">
<xsl:choose>
<xsl:when test="#rend='expunctus'">
[<xsl:text>EXPUNCTUS</xsl:text>]
</xsl:when>
</xsl:template>
Is this because of the current node already selected?
I prefer to test against the element, not just the attribute, to eliminate possible ambiguity.
Thanks.

Yes, it is because of the current node selected.
Your template matches tei:del[#rend='expunctus'] (amongst other things), so when you do <xsl:when test="tei:del[#rend='expunctus']"> this is relative to the node you have matched, so it is looking for another tei:del as a child node of the current node.
What you probably need to do is this...
<xsl:when test="self::tei:del[#rend='expunctus']">
Alternatively, consider using separate templates for each possible node and putting any shared code in a named template.

Related

xsl 3.0: How to process certain child elements first in xsl:apply-templates, then the remainder (overriding document order)

Assume my xml input is a MFMATR element with a few child elements, such as: TRLIST, INTRO, and SBLIST -- in that document order. I am converting to HTML.
I have a template that matches on the MFMATR element, and wants to run xsl:apply-templates on the 3 child elements, but I want INTRO to be processed first (listed first in the HTML). The other two (TRLIST and SBLIST) should keep their relative document order, as long as INTRO comes before both of them.
So I'd like to run <xsl:apply-templates select="INTRO, *"> but not have INTRO matched twice. (Using this syntax with xsl 3.0 causes dupes for me.) I also don't want to explicitly list every tag in the select expression, so unknown tags will still be processed.
A 2nd real life example is this: <xsl:apply-templates select="TITLE, CHGDESC, *"/>. Again, right now that is causing dupes I don't want.
I am using Saxon.
So I'd like to run <xsl:apply-templates select="INTRO, *"> but not have INTRO matched twice
Try:
<xsl:apply-templates select="INTRO, * except INTRO">
This seems to work. If someone has a better answer, let me know and I will change it.
There is no DRY violation here -- no repeated element names or variable names. I want it to look clean at all the call sites I will have.
It seems idiomatic to me since the function was pulled from w3's own website!
<xsl:template match="MFMATR">
<!-- Process INTRO first, no matter where it appears -->
<xsl:variable name="nodes" select="INTRO, *"/>
<xsl:apply-templates select="kp:distinct_nodes_stable($nodes)"/>
</xsl:template>
<xsl:template match="INTRO">
<xsl:variable name="nodes" select="TITLE, CHGDESC, *"/>
<xsl:apply-templates select="kp:distinct_nodes_stable($nodes)"/>
</xsl:template>
<!-- Discard duplicate elements in $seq, but keep their ordering -->
<!-- Adapted from https://www.w3.org/TR/xpath-functions/#func-distinct-nodes-stable -->
<xsl:function name="kp:distinct_nodes_stable" as="node()*">
<xsl:param name="seq" as="node()*"/>
<xsl:sequence select="fold-left($seq, (),
function($foundSoFar as node()*, $this as node()) as node()* {
if ($foundSoFar intersect $this)
then $foundSoFar
else ($foundSoFar, $this)
}) "/>
</xsl:function>

XSLT 2.0 / XPATH testing for parent element in specific position

In XSLT 2.0 and XPATH, within <xsl:template match="lb">, I am testing for a variety of different case where each case produces different HTML output (using xsl:choose/xsl:when).
I want to test for the following situation, where lb is the very first node of any sort inside seg element:
<seg><lb break="n"/>text</seg>
By contrast, these tests would fail:
<seg>text<lb break="n"/>text</seg>
<seg><foo/><lb break="n"/>text</seg>
I've tried combining parentand position() but it's not testing correctly.
Many thanks.
Having a template matching lb and then using xsl:choose/when in my view can be solved more elegantly and compact with precise match patterns e.g. xsl:template match="seg/node()[1][self::lb]" would match any first child node of a seg parent where the child is an lb element. For other conditions you would set up different templates with different match patterns.
But you can use . is parent::seg/node()[1] inside the xsl:template match="lb" to write it the other way around if needed.
/seg/child::node()[1]/name() = 'lb'
check if first child is named "lb"
You can simply test if there are no preceding siblings:
<xsl:when test="not(preceding-sibling::node())">
Do note that node() includes comments and processing instructions too, not just elements and text.
Alternatively, if you have a template matching seg where you do something like this...
<xsl:template match="seg">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
Then, because <xsl:apply-templates /> is short for <xsl:apply-templates select="node()" /> you could use position() in your template
<xsl:when test="position() = 1">
This would not work if the "seg" template did <xsl:apply-templates select="lb" /> though.
See http://xsltfiddle.liberty-development.net/nc4NzRd for an example of the tests in action.

XSLT xsl:sequence. What is it good for..?

I know the following question is a little bit of beginners but I need your help to understand a basic concept.
I would like to say first that I'm a XSLT programmer for 3 years and yet there are some new and quite basics things I've been learning here I never knew (In my job anyone learns how to program alone, there is no course involved).
My question is:
What is the usage of xsl:sequence?
I have been using xsl:copy-of in order to copy node as is, xsl:apply-templates in order to modifiy nodes I selected and value-of for simple text.
I never had the necessity using xsl:sequence. I would appreciate if someone can show me an example of xsl:sequence usage which is preferred or cannot be achieved without the ones I noted above.
One more thing, I have read about the xsl:sequence definition of course, but I couldn't infer how it is useful.
<xsl:sequence> on an atomic value (or sequence of atomic values) is the same as <xsl:copy-of> both just return a copy of their input. The difference comes when you consider nodes.
If $n is a single element node, eg as defined by something like
<xsl:variable name="n" select="/html"/>
Then
<xsl:copy-of select="$n"/>
Returns a copy of the node, it has the same name and child structure but it is a new node with a new identity (and no parent).
<xsl:sequence select="$n"/>
Returns the node $n, The node returned has the same parent as $n and is equal to it by the is Xpath operator.
The difference is almost entirely masked in traditional (XSLT 1 style) template usage as you never get access to the result of either operation the result of the constructor is implicitly copied to the output tree so the fact that xsl:sequence doesn't make a copy is masked.
<xsl:template match="a">
<x>
<xsl:sequence select="$n"/>
</x>
</xsl:template>
is the same as
<xsl:template match="a">
<x>
<xsl:copy-of select="$n"/>
</x>
</xsl:template>
Both make a new element node and copy the result of the content as children of the new node x.
However the difference is quickly seen if you use functions.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="data:,f">
<xsl:variable name="s">
<x>hello</x>
</xsl:variable>
<xsl:template name="main">
::
:: <xsl:value-of select="$s/x is f:s($s/x)"/>
:: <xsl:value-of select="$s/x is f:c($s/x)"/>
::
:: <xsl:value-of select="count(f:s($s/x)/..)"/>
:: <xsl:value-of select="count(f:c($s/x)/..)"/>
::
</xsl:template>
<xsl:function name="f:s">
<xsl:param name="x"/>
<xsl:sequence select="$x"/>
</xsl:function>
<xsl:function name="f:c">
<xsl:param name="x"/>
<xsl:copy-of select="$x"/>
</xsl:function>
</xsl:stylesheet>
Produces
$ saxon9 -it main seq.xsl
<?xml version="1.0" encoding="UTF-8"?>
::
:: true
:: false
::
:: 1
:: 0
::
Here the results of xsl:sequence and xsl:copy-of are radically different.
The most common use case for xsl:sequence is to return a result from xsl:function.
<xsl:function name="f:get-customers">
<xsl:sequence select="$input-doc//customer"/>
</xsl:function>
But it can also be handy in other contexts, for example
<xsl:variable name="x" as="element()*">
<xsl:choose>
<xsl:when test="$something">
<xsl:sequence select="//customer"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="//supplier"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
The key thing here is that it returns references to the original nodes, it doesn't make new copies.
Well to return a value of a certain type you use xsl:sequence as xsl:value-of despite its name always creates a text node (since XSLT 1.0).
So in a function body you use
<xsl:sequence select="42"/>
to return an xs:integer value, you would use
<xsl:sequence select="'foo'"/>
to return an xs:string value and
<xsl:sequence select="xs:date('2013-01-16')"/>
to return an xs:date value and so on. Of course you can also return sequences with e.g. <xsl:sequence select="1, 2, 3"/>.
You wouldn't want to create a text node or even an element node in these cases in my view as it is inefficient.
So that is my take, with the new schema based type system of XSLT and XPath 2.0 a way is needed to return or pass around values of these types and a new construct was needed.
[edit]Michael Kay says in his "XSLT 2.0 and XPath 2.0 programmer's reference" about xsl:sequence: "This innocent looking instruction introduced in XSLT 2.0 has far reaching effects on the capability of the XSLT language, because it means that XSLT instructions and sequence constructors (and hence functions and templates) become capable of returning any value allowed by the XPath data model. Without it, XSLT instructions could only be used to create new nodes in a result tree, but with it, they can also return atomic values and references to existing nodes.".
Another use is to create a tag only if it has a child. An example is required :
<a>
<b>node b</b>
<c>node c</c>
</a>
Somewhere in your XSLT :
<xsl:variable name="foo">
<xsl:if select="b"><d>Got a "b" node</d></xsl:if>
<xsl:if select="c"><d>Got a "c" node</d></xsl:if>
</xsl:variable>
<xsl:if test="$foo/node()">
<wrapper><xsl:sequence select="$foo"/></wrapper>
</xsl:if>
You may see the demo here : http://xsltransform.net/eiZQaFz
It is way better than testing each tag like this :
<xsl:if test="a|b">...</xsl:if>
Because you would end up editing it in two places. Also the processing speed would depend on which tags are in your imput. If it is the last one from your test, the engine will test the presence of everyone before. As $foo/node() is an idioms for "is there a child element ?", the engine can optimize it. Doing so, you ease the life of everyone.

XSLT1.0: Multiple values in choose-when in the right part of equation?

Is there any way to put more then one value in the right part of equation?
<xsl:choose>
<xsl:when test="Properties/LabeledProperty[Label='Category Code']/Value = 600,605,610">
This code above returns:
XPath error : Invalid expression
Properties/LabeledProperty[Label='Category Code']/Value = 600,605,610
^
compilation error: file adsml2adpay.xsl line 107 element when
The reason I don't want to use 'OR' is because there are around 20 numbers should be in the right part for each 'when' test.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:config="http://tempuri.org/config"
exclude-result-prefixes="config"
>
<config:categories>
<value>600</value>
<value>605</value>
<value>610</value>
</config:categories>
<xsl:variable
name = "vCategories"
select = "document('')/*/config:categories/value"
/>
<xsl:key
name = "kPropertyByLabel"
match = "Properties/LabeledProperty/Value"
use = "../Label"
/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="key('kPropertyByLabel', 'Category Code') = $vCategories">
<!-- ... -->
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This works because the XPath = operator compares every node from the left hand side to every node on the right hand side when working on node sets (comparable to an SQL INNER JOIN).
Hence all you need to do is create a node set from your individual values. Using a temporary namespace you can do that right in your XSLT file.
Also note that I've introduced an <xsl:key> to make selecting property values by their label more efficient.
EDIT: You could also create an external config.xml file and do this:
<xsl:variable name="vConfig" select="document('config.xml')" />
<!-- ... -->
<xsl:when test="key('kPropertyByLabel', 'Category Code') = $vConfig/categories/value">
With XSLT 2.0 the concept of sequences has been added. It's simpler to do the same thing there:
<xsl:when test="key('kPropertyByLabel', 'Category Code') = tokenize('600,605,610', ',')">
This means you could easily pass in the string '600,605,610' as an external parameter.

convert <xsl:variable> string value , to the value which I can select with <xsl:value-of select>

I have a variable from which I need to dynamically generate nodes
<xsl:template match="banner_discount_1 | banner_discount_2 | banner_discount_3">
<xsl:variable name="link">banner_discount_<xsl:value-of select="substring-after(name(.) ,'banner_discount_')" />_link</xsl:variable>
<xsl:value-of select="$link" />
</xsl:template>
<xsl:value-of> selects the string, but I want to be able to select the node which name matches the name of a variable.
In my case the node looks something like this:
<banner_discount_1_link />
<banner_discount_2_link />
...
Here is the xml I'm using
<banner_discount_1> 12 </banner_discount_1>
<banner_discount_2> 21 </banner_discount_2>
<banner_discount_3> 32 </banner_discount_3>
<banner_discount_1_link> link1 </banner_discount_1_link>
<banner_discount_2_link> link2 </banner_discount_2_link>
<banner_discount_3_link> link3 </banner_discount_3_link>
#MartinHonnen is on the right track, but you need to set the selection context as well.
Since you're in a template that's selecting the banner_discount_ nodes, that is your context. From your XML sample, it looks like the nodes you want to select are siblings, so this should work:
<xsl:value-of select="../*[local-name() = $link]"/>
It is preferable to target the nodes directly, but if they could be anywhere in the document, then you may resort to
<xsl:value-of select="//*[local-name() = $link]"/>
This is a last resort because it is potentially O(n) with respect to the number of nodes in the document.
Use <xsl:value-of select="*[local-name() = $link]"/>. If that does not help then consider to show a sample of the XML.