msxsl:node-set() and position() are not working together - xslt

I've got an xsl:variable that contains a pre-processed set (DoesNotContainChildElement). I think msxsl:node-set() is adding a root element so position is always 1. But what I need it the top 15 elements.
<xsl:variable name="Summary">
<xsl:for-each select="msxsl:node-set($DoesNotContainChildElement)">
<xsl:if test="position() < 16">
<xsl:copy-of select="."></xsl:copy-of>
</xsl:if>
</xsl:for-each>

No, the function msxsl:node-set does not add any root node, it is simply that with XSLT 1.0 a sample like
<xsl:variable name="rtf1">
<item>1</item>
<item>2</item>
<item>3</item>
</xsl:variable>
creates a result tree fragment and "A result tree fragment is treated equivalently to a node-set that contains just a single root node". So in the sample above we have a result tree fragment with a single root node containing three item child elements.
Applying the msxsl:node-set(rtf1) extension function then gives you a node-set instead of a result tree fragment where now the node-set contains a single root node with three item child elements. Thus if you want to access the item elements you need msxsl:node-set($rtf1)/* or more general msxsl:node-set($rtf1)/node() to access all child nodes.

You may try msxsl:node-set($DoesNotContainChildElement)/*. If it's true that msxsl:node-set() adds a root node, then adding /* to your path will iterate over the children, where you can test for position.
Alternatively you could simply use <xsl:apply-templates select="msxsl:node-set($DoesNotContainChildElement)/*" mode="testposition"/> and <xsl:template match="*" mode="testposition">….

There's only one variable $DoesNotContainChildElement so a for-each will only yield value 1 for position().
You can check that by running the following:
<xsl:for-each select="$x">
<pos1><xsl:value-of select="position()"/></pos1>
</xsl:for-each>
<xsl:for-each select="$x/*">
<pos2><xsl:value-of select="position()"/></pos2>
</xsl:for-each>
Where x is a (node-set type) variable.
The result will look something like
<pos1>1</pos1>
<pos2>1</pos2>
<pos2>2</pos2>
<pos2>3</pos2>
<pos2>...</pos2>
Adding <xsl:copy-of select="."/> will result in the output of the entire variable contents in case of the first for-each above, whereas for the second for-each it will result in the output of each sub-element of the variable one-by-one.
The second form is the one to use if you wish to output only selected sub-elements.
The same holds when you first apply the node-set function to change an rtf into a node-set.

Related

Constructing, not selecting, XSL node set variable

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.

Change text of elements identified by dynamic XPath

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.

What does this do? <xsl:apply-templates select="."/> and <xsl:apply-templates select="*|#*"/>

I am very new to XSL and I am confused about what the select inside the following pieces of code will select.
<xsl:apply-templates select="."/>
<xsl:apply-templates select="*|#*"/>
Any ideas?
Thanks
Check out the Abbreviated Syntax section of XPath 2.0.
In the <xsl:apply-templates select="."/> example, . evaluates to the context item. In most cases, this is the same as the node currently being processed. So this example will select the context node.
In the <xsl:apply-templates select="*|#*"/> example, * will select all the child elements of the context node. #* will select all the attributes of the context node. | is the union operator. So this example will select all the child elements of the context node and also all the attributes of the context node.
<xsl:apply-templates select="."/> is frequently used to apply further processing to the context node.
<xsl:apply-templates select="*|#*"/> is frequently used to process all the child elements of the current node and its attributes. It’s often used when you’re done handling an element and want to hand off its child elements/attributes to any other templates that apply.
<xsl:apply-templates select="."/>
processes the content of the current node! the dot . indicates content.. if the current node doesn't have childnodes but has data instead (ex: <foo>Sample Data</foo>) then the parser processes the data Sample Data
<xsl:apply-templates select="#*|*"/>
processes the attribute and child nodes or the data under the current node.. difference is .. this one takes care of all the attribute of the context node..
I use the word process instead of copy, because .. apply-template unlike copy-of and value-of evaluates other templates, for example along with above code if I have one more template like below:
<xsl:template match="text()[.='Sample Data']"/>
then it will drop the text from your output XML. Where as copy-of select="node_name" and value-of select="node-name" copy the data despite of being this template in our XSL file..

identifying the null element in XSLT which has no value even in child elements

checking for an Null element
<image><a><img src="abcd"/></a></image>
XSLT template:
<xsl:if test="image!=''">
IMAGE HAS TEXT OR INNER ELEMENT
</xsl:if>
I am getting blank output though the "image" element has child elements. and Ideally it is not null.
I have to check the condition that it should have value or the child elements. The child elements can be empty.
How to rectify this.
Thank you
Use:
image[string() or node()]
This evaluates to true() only if there is at least one image child of the current node, such that its string value is non-empty, or it has children (or both).
This can be simplified just to:
image[node()]
taking into account that in order to have string value, an element must have a text node descendant in its sub-tree.
If you want the string value of image (if any) to be not all-whitespace, modify the first of the above XPath expressions to:
image[normalize-space() or node()]
Use <xsl:if test="image/node()">...</xsl:if> to check whether the image element has any kind of child node or <xsl:if test="image/*">...</xsl:if> to test whether the image element has at least one child element.
You can use this to check for any child nodes (text, elements etc)
<xsl:template match="image">
<xsl:if test="node()">
IMAGE HAS TEXT OR INNER ELEMENT
</xsl:if>
</xsl:template>
Or you can be more specific:
<xsl:template match="image">
<xsl:if test="a | text()">
IMAGE HAS TEXT OR INNER ELEMENT
</xsl:if>
</xsl:template>

How to filter node list based on the contents of another node list

I'd like to use XSLT to filter a node list based on the contents of another node list. Specifically, I'd like to filter a node list such that elements with identical id attributes are eliminated from the resulting node list. Priority should be given to one of the two node lists.
The way I originally imagined implementing this was to do something like this:
<xsl:variable name="filteredList1" select="$list1[not($list2[#id_from_list1 = #id_from_list2])]"/>
The problem is that the context node changes in the predicate for $list2, so I don't have access to attribute #id_from_list1. Due to these scoping constraints, it's not clear to me how I would be able to refer to an attribute from the outer node list using nested predicates in this fashion.
To get around the issue of the context node, I've tried to create a solution involving a for-each loop, like the following:
<xsl:variable name="filteredList1">
<xsl:for-each select="$list1">
<xsl:variable name="id_from_list1" select="#id_from_list1"/>
<xsl:if test="not($list2[#id_from_list2 = $id_from_list1])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
But this doesn't work correctly. It's also not clear to me how it fails... Using the above technique, filteredList1 has a length of 1, but appears to be empty. It's strange behaviour, and anyhow, I feel there must be a more elegant approach.
I'd appreciate any guidance anyone can offer. Thanks.
Use this XPath one-liner:
$vList1[not(#id = $vList2/#id)]
As far as I am aware using $var[] syntax doesn't work. What works is: expr1/[expr2 = $var], and func1($var).
What you can do is simply embed the expression that yields $list2 in the if test:
<xsl:for-each select="$list1">
<xsl:variable name="id" select="#id_from_list1"/>
<xsl:if test="not(expr2[#id_from_list2 = $id ])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
<xsl:copy-of select="$list2"/>
Substitute expr2 with actual expression.