I have a couple of nodes that contrain track data and I want to concatenate them all in one string that becomes a new variable $tracks.
<xsl:template match="tracks">
<xsl:variable name="trackArtist">
<xsl:apply-templates select="/artists/item/#artist" />
</xsl:variable>
<xsl:value-of select="item/concat(#unit, '|', #track, '|null|', #text, '|', $trackArtist, '|null|null|')" />
</xsl:template>
So this works, it concatenates them all. But I had one space at every end of a node, and I tried a lot of things normalize-space, string-join etc. But then I found out the extra space is just generated by each node that's processed.
<tracks>
<item text="SILENCE" track="1" unit="1"/>
<item text="HAPPINESS" track="2" unit="1"/>
<item text="DREAM" track="3" unit="1"/>
</tracks>
Result (notice the space behind '|null|null|':
1|1|null|SILENCE|FOURPLAY|null|null| 1|2|null|HAPPINESS|FOURPLAY|null|null| 1|3|null|DREAM|FOURPLAY|null|null|
How can I concatenate the data from a node PLUS all the other nodes?
Okay, figured it out with a little help from a colleague.
Since it's a nodelist that you're editing, it will apply the standard built-in way of handling, thus adding a space after each node. So by adding a string-join() you can manipulate this. This also fixes the issue of having a trailing (unnecessary) pipe.
<xsl:value-of select="string-join(item/concat(#unit, '|', #track, '|null|', #text, '|', $trackArtist, '|null|null'), '|')" />
Related
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>
If I have an XSL that creates output like this simple/rough example:
<Parent1>
<ABC><xsl:value-of select="SomeValue1"/></ABC>
<DEF><xsl:value-of select="SomeValue2"/></DEF>
<GHI><xsl:value-of select="SomeValue3"/></GHI>
...
<YZ><xsl:value-of select="SomeValue9"/></YZ>
</Parent1>
... within this same XSL, how can I count how many children the XSL will produce?
You can generate your content into a variable, count the children in the variable, and then emit the content of the variable:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<xsl:variable name="temp-results">
<Parent1>
<ABC><xsl:value-of select="SomeValue1"/></ABC>
<DEF><xsl:value-of select="SomeValue2"/></DEF>
<GHI><xsl:value-of select="SomeValue3"/></GHI>
...
<YZ><xsl:value-of select="SomeValue9"/></YZ>
</Parent1>
</xsl:variable>
<xsl:text>Number of children:</xsl:text>
<xsl:value-of select="count($temp-results/Parent1/*)"/>
<xsl:sequence select="$temp-results"/>
</xsl:template>
</xsl:stylesheet>
One possibility is wrapping the whole output process in a variable and then count its descendants.
So, for example, you can use the following XSLT code
<xsl:template match="/Parent1">
<xsl:variable name="output">
<ABC><xsl:value-of select="SomeValue1"/><ZZZ>Some Grandchild</ZZZ></ABC>
<DEF><xsl:value-of select="SomeValue2"/></DEF>
<GHI><xsl:value-of select="SomeValue3"/></GHI>
...
<YZ><xsl:value-of select="SomeValue9"/></YZ>
</xsl:variable>
<xsl:value-of select="concat('Outputting ', count($output/descendant::*), ' elements.
')" />
<xsl:copy-of select="$output" />
</xsl:template>
Its output is
Outputting 5 elements.
<ABC>
<ZZZ>Some Grandchild</ZZZ>
</ABC>
<DEF/>
<GHI/>
...
<YZ/>
This code accomplishes three things:
First it generates the result and puts it into the variable
It counts all the
children (child::* axis) or
descendants (descendant::* axis) as in the example above
of the elements in the variable
It copies the variable to the output stream
This approach can even be nested - meaning that it can be applied several times, one after another.
Your choices are:
(a) find a way of computing the result as a function of the input
(b) capture the output in a variable and run a second phase of processing against that variable.
(c) a blend of the above: compute some intermediate result in a variable, and use that variable as input to both processes.
In the example you've given, the first approach works perfectly well; but I guess your real problem is more complex than that, otherwise you wouldn't be asking.
I wish to construct an XSL node set variable using a contained for-each loop. It is important that the constructed node set is the original (a selected) node set, not a copy.
Here is a much simplified version of my problem (which could of course be solved with a select, but that's not the point of the question). I've used the <name> node to test that the constructed node set variable is in fact in the original tree and not a copy.
XSL version 1.0, processor is msxsl.
Non-working XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="entryNodes" select="msxsl:node-set($entries)"/>
<xsl:for-each select="$entryNodes">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XML input:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<name>X</name>
<entry>1</entry>
<entry>2</entry>
</root>
Wanted output:
X1X2
Actual output:
12
Of course the (or a) problem is the copy-of, but I can't work out a way around this.
There isn't a "way around it" in XSLT 1.0 - it's exactly how this is supposed to work. When you have a variable that is declared with content rather than with a select then that content is a result tree fragment consisting of newly-created nodes (even if those nodes are a copy of nodes from the original tree). If you want to refer to the original nodes attached to the original tree then you must declare the variable using select. A better question would be to detail the actual problem and ask how you could write a suitable select expression to find the nodes you want without needing to use for-each - most uses of xsl:if or xsl:choose can be replaced with suitably constructed predicates, maybe involving judicious use of xsl:key, etc.
In XSLT 2.0 it's much more flexible. There's no distinction between node sets and result tree fragments, and the content of an xsl:variable is treated as a generic "sequence constructor" which can give you new nodes if you construct or copy them:
<xsl:variable name="example" as="node()*">
<xsl:copy-of select="//entry" />
</xsl:variable>
or the original nodes if you use xsl:sequence:
<xsl:variable name="example" as="node()*">
<xsl:sequence select="//entry" />
</xsl:variable>
I wish to construct an XSL node set variable using a contained
for-each loop.
I have no idea what that means.
It is important that the constructed node set is the original (a
selected) node set, not a copy.
This part I think I understand a little better. It seems you need to replace:
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
with:
<xsl:variable name="entries" select="//entry"/>
or, preferably:
<xsl:variable name="entries" select="root/entry"/>
The resulting variable is a node-set of the original entry nodes, so you can do simply:
<xsl:for-each select="$entries">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
to get your expected result.
Of course, you could do the same thing by operating directly on the original nodes, in their original context - without requiring the variable.
In response to the comments you've made:
We obviously need a better example here, but I think I am getting a vague idea of where you want to go with this. But there are a few things you must understand first:
1.
In order to construct a variable which contains a node-set of nodes in their original context, you must use select. This does not place any limits whatsoever on what you can select. You can do your selection all at once, or in stages, or even in a loop (here I mean a real loop). You can combine the intermediate selections you have made in any way sets can be combined: union, intersection, or difference. But you must use select in all these steps, otherwise you will end up with a set of new nodes, no longer having the context they did in the source tree.
IOW, the only difference between using copy and select is that the former creates new nodes, which is precisely what you wish to avoid.
2.
xsl:for-each is not a loop. It has no hierarchy or chronology. All the nodes are processed in parallel, and there is no way to use the result of previous iteration in the current one - because no iteration is "previous" to another.
If you try to use xsl:for-each in order to add each of n processed nodes to a pre-existing node-set, you will end up with n results, each containing the pre-existing node-set joined with one of the processed nodes.
3.
I think you'll find the XPath language is quite powerful, and allows you to select the nodes you want without having to go through the complicated loops you hint at.
It might help if you showed us a problem that can't be trivially solved in XSLT 1.0. You can't solve your problem the way you are asking for: there is no equivalent of xsl:sequence in XSLT 1.0. But the problem you have shown us can be solved without such a construct. So please explain why you need what you are asking for.
I have an XML with 2 XML fragments, 1st one is a fragment where the new values must be applied (which can have pretty complex elements) like
... some static parents
<a:element1>
<a:subelement tag="someString">
<a:s1>a</a:s1>
</a:subelement>
</a:element1>
<a:element2>b</a:element2>
<a:element3>c</a:element3>
... lots of other elements like the above ones
and 2nd fragment that has XPaths generated from the first XML and a new value, like
<field>
<xpath>/Parent/element1/subelement[#tag="someString"]/s1</xpath>
<newValue>1</newValue>
</field>
<field>
<xpath>/Parent/element2</xpath>
<newValue>2</newValue>
</field>
We might not have new values to apply for all the elements in the first fragment.
I'm struggling to make an XSLT transformation that should apply the new values to the places indicated by the XPaths.
The output should be:
... some static parents
<a:element1>
<a:subelement tag="someString">
<a:s1>1</a:s1>
</a:subelement>
</a:element1>
<a:element2>2</a:element2>
... lots of other elements like the above ones
I have access to xalan:evaluate to evaluate the dynamic xpath. I'm trying different solutions, I will write them here when they will start to make sense.
Any ideas of approaches are well received. Thanks
Oki, I found out how, and I will write the answer here maybe someone sometime will need this:
<xsl:template match="/">
<!-- static parents -->
<a:Root>
<xsl:apply-templates select="/a:Root/a:Parent" />
</a:Root>
</xsl:template>
<xsl:template match="#*|*|text()">
<xsl:variable name="x" select="generate-id(../.)" />
<xsl:variable name="y" select="//field[generate-id(xalan:evaluate(xpath)) = $x]" />
<xsl:choose>
<xsl:when test="$y">
<xsl:value-of select="$y/newValue" />
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|*|text()" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
And to explain the transformation:
I'm writing down part that is static and then call apply-templates on the fragment I'm interested in, that has a liquid structure.
Then I'm using a slightly modified identity transformation that copies everything from source to target (starting from the /a:Root/a:Parent fragment), except when we position ourselves on the text I'm interested in changing.
The text() I'm interested in will have as parent (../.) the element referred by an xpath string found in the second fragment. Variable x means, in the context of the when, this element.
Variable y finds a field element that has as child an xpath element that if evaluated using xalan will refer to the same element that the x variable relates to.
Now I used generate-id() in order to compare the physical elements, otherwise it would have compared by the toString of the element (which is wrong). If variable y doesn't exist, it means that I have no xpath element for this element that could have changed, and I'm leaving it alone. If the y variable exists, I can get from it the newValue and I'm currently positioned on the element which text I want to update.
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.