Consider the following two xsl:value-of statements using XPath expressions testing the position of a node.
(1) This one, as I understand it, will return the value of the /ChildThree child of a /Parent where three things are independently true of that /Parent - (i) it has a /ChildOne with value "x"; AND (ii) it has a /ChildTwo with value "y"; AND (iii) it is the second or subsequent /Parent within its immediate ancestor:
<xsl:value-of select="Parent[ChildOne='x'][ChildTwo='y'][position()>=2]/ChildThree"/>
(2) This one, by contrast, will return the value of the /ChildThree child of a /Parent where that /Parent is the second or subsequent /Parent OF THOSE THAT HAVE BOTH a /ChildOne with value "x" AND a /ChildTwo with value "y":
<xsl:value-of select="(Parent[ChildOne='x'][ChildTwo='y'])[position()>=2]/ChildThree"/>
So far so good. However, what happens in the following example? Here, I'm trying to get the value of the /ChildThree child of any /Parent for which the first two criteria are concurrently true, but only precede it with a space if the /Parent is the second or subsequent within that subset (i.e., like example (2), above). How can I dictate how the position criterion within the xsl:if statement applies to the criteria in the xsl:for-if statement, when it is not part of the same XPath expression?
<xsl:for-each select="Parent[ChildOne='x'][ChildTwo='y']">
<xsl:if test="position()>=2">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:value-of select="ChildThree"/>
</xsl:for-each>
The two statements (1) and (2) are actually equivalent, and will return the same results.
position() is context-sensitive. It will give you the position of the current node within the set of nodes previously selected. So, when you have the expression Parent[ChildOne='x'][ChildTwo='y'] it is returning a set of nodes where two conditions is true. Doing Parent[ChildOne='x'][ChildTwo='y'][position()>=2] will give the second (or greater) node in this list.
The way you are describing how you think (1) works, would actually look like this.
<xsl:value-of select="Parent[position()>=2][ChildOne='x'][ChildTwo='y']/ChildThree"/>
One way to think of it, is to think of every condition in square brackets filtering what has come before.
Try it out on this XML, for example
<Parents>
<Parent>
<ChildOne>a</ChildOne>
<ChildTwo>b</ChildTwo>
<ChildThree>c</ChildThree>
</Parent>
<Parent>
<ChildOne>x</ChildOne>
<ChildTwo>y</ChildTwo>
<ChildThree>z1</ChildThree>
</Parent>
<Parent>
<ChildOne>x</ChildOne>
<ChildTwo>y</ChildTwo>
<ChildThree>z2</ChildThree>
</Parent>
</Parents>
Your statements (1) and (2) both return z2, but doing Parent[position()>=2][ChildOne='x'][ChildTwo='y']/ChildThree returns z1.
What this means is that your xsl:for-each should actually give you the results you expect.
Related
I would like to use XSL 2.0 (saxon9he.jar) to split data into groups by position.
In this sample, I try to split market products into bags with 4 items in each bag.
My testing indicates that position() is in the scope of the parent. Such that potato is position 2 as a child of the vegetable department, rather than position 5 in my selection of products.
I would like to base the groups on the position within the selection, not the position within the parent.
XML Dataset:
<market>
<department name="fruit">
<product>apple</product>
<product>banana</product>
<product>grape</product>
</department>
<department name="vegetable">
<product>carrot</product>
<product>potato</product>
<product>squash</product>
</department>
<department name="paper">
<product>plates</product>
<product>napkins</product>
<product>cups</product>
</department>
<department name="cloths">
<product>shirts</product>
<product>shorts</product>
<product>socks</product>
</department>
</market>
XSL Template:
<xsl:transform 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" exclude-result-prefixes="xs fn">
<xsl:output indent="no" method="text"/>
<!-- place 4 items in each bag -->
<xsl:template match="/">
<xsl:for-each-group select="/market/department/product"
group-ending-with="/market/department/product[position() mod 4 = 0]">
<xsl:variable name="file"
select="concat('bags/bag',position(),'.txt')"/>
<xsl:result-document href="{$file}">
<xsl:value-of select="position()"/>
<xsl:for-each select="current-group()">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
</xsl:transform>
Resulting bag1.txt
1applebananagrapecarrotpotatosquashplatesnapkinscupsshirtsshortssocks
Resulting bag2.txt
file does not exist!
Expected bag1.txt
1applebananagrapecarrot
Expected bag2.txt
2potatosquashplatesnapkins
My debugging conclusions:
It seems like position() is never 4 (each department only has 3 items)
If I change mod 4 to mod 2 I get multiple bags, and bag 1 contains 2 items. but all others but the last one contain 3 items.
each bag ends at the 2nd item of a department, all but the first bag include the last item of the previous department.
Resulting bag1.txt
1applebanana
Resulting bag1.txt
2grapecarrotpotato
Expected bag1.txt
1applebanana
Expected bag2.txt
2grapecarrot
This suggests to me that position() is related the the parent item, not to the selection.
I would like position() to be related to the selection.
From what I have researched, position() should be related to the selection.
Like is is described in the answer here:
Final hint: position() does not tell you the position of the node
within its parent. It tells you the position of the current node
relative to the list of nodes you are processing right now.
Find the position of an element within its parent with XSLT / XPath
There is mention here that pattern expressions differ in their interpretation of scope compared to select expressions. After reading it, I don't know how to change my use of the pattern expression to achieve the behavior I'm expecting.
Using for-each-group for high performance XSLT
based on the behavior I currently observe:
If I had 9 fruit, 4 vegetables and 20 paper products, and used mod 5
bag1 would contain the first 5 fruit products,
bag2 would contain the last 4 fruit + 4 vegetables + the first 5 paper products.
The current behavior is not the behavior I am looking for.
Try using group-adjacent here, instead of group-ending-with
<xsl:for-each-group select="/market/department/product"
group-adjacent="floor((position() - 1) div 4)">
Or this...
<xsl:for-each-group select="/market/department/product"
group-adjacent="ceiling(position() div 4)">
So, group the items based on the integer division by 4 of their position.
Tim C has already explained how to get the desired behavior; this is just a note to help you understand your error.
The position() function and the dynamic context
The position() function returns the position of an item within a given sequence whose identity is given by the context. The function often does return the position of an element among the children of its parent, but that is because in practice the rules for determining the dynamic context for the evaluation of XPath expressions often specify that the relevant sequence is the sequence of an element's child nodes. The position() function is not 'scoped' to the parent element as part of its definition.
The value of the position() function is the context position, which is defined as "the position of the context item within the sequence of items currently being processed". Like the context item, the context position (and the context size returned by last()) is part of the dynamic context within which an XPath expression is evaluated. In the evaluation of any non-atomic XPath expression, the dynamic context may be different for different subexpressions.
In particular, the XPath specification stipulates that "When an expression E1/E2 or E1[E2] is evaluated, each item in the sequence obtained by evaluating E1 becomes the context item in the inner focus for an evaluation of E2."
The expression in your group-ending-with attribute
In the expression /market/department/product[position() mod 4 = 0], the rule just quoted means that the expression product[position() mod 4 = 0] is evaluated separately for each item in the sequence /market/department'. That is, for eachdepartmentelement in that sequence, the expressionproduct[...]is evaluated. That right-hand expression in turn is equivalent tochild::product[...], so for each evaluation of the right-hand expression the sequence in question is the sequence of elements namedproductwhich are children of the currentdepartmentelement. Within the expressionproduct[position() mod 4 = 0], the same basic rule applies: the filter expression within square brackets is evaluated in the context given by the expressionproduct.
As a consequence, the context position (the value returned byposition()) is the position of the currentproductelement among its sibling elements. Since nodepartmentelement in the input has as many as four children, the value ofposition()` is never greater than three and each filter expression evaluates to false, so the expression as a whole evaluates to the empty sequence.
A similar expression with a different value
In the expression (/market/department/product)[position() mod 4 = 0], by contrast, the filter expression is evaluated in the context of the sequence of all product elements in the document (strictly speaking, those with the specified path, which in this case is all the product elements in the document). Product elements which are children of different department elements are lumped into the same sequence and then the predicate is applied once for each element. The value of position() ranges from 1 to 12 and the overall expression selects the products with values carrot, napkins, and socks.
You can't simply use the second expression in your group-ending-with attribute, because it's not allowed (the attribute value must be a pattern, not a general XPath expression). And even if you could, there are other problems in the template which would then require repair.
But you should clear your mind of the notion that position() always and only denotes the position of a node among the children of its parent.
A simple arithmetic example
It may help to consider some expressions that involve no nodes at all.
The expression
(1 to 100)
denotes the sequence of natural numbers from 1 to 100, inclusive. I'll call that S1. The expression
(1 to 100) [position() mod 4 eq 0]
filters out of S1 everything except the ones whose context positions are evenly divisible by 4, so it denotes the sequence (4, 8, ..., 96, 100). I'll call this S2. If we append another filter expression, its context is given by the sequence S2, not by S1. So
(1 to 100) [position() mod 4 eq 0] [position() gt 23]
returns the sequence consisting of the 24th and 25th entries in sequence S2, namely (96, 100).
I'm working on an XSLT stylesheet. I have a a node (node A) with a bunch of children, and I'm looping through another node's (node B) children. I'm trying to do something each time a child of node B is also a child of node A, so I have this code:
<xsl:if test="$prodbins/bin[./text()=/root/Line[1]/Element[6]/text()]">
But that doesn't work (the test fails; the expression returns false) even though the left and right side of the expression, when evaluated separately, are equal.
But when I do this:
<xsl:variable name="curbin" select="/root/Line[1]/Element[6]/text()"/>
<xsl:if test="$prodbins/bin[./text()=$curbin]">
The expression evaluates to true. Why do I have to use the $curbin variable to get the result I'm expecting?
Can you try <xsl:if test="$prodbins/bin[./text()=current()/root/Line[1]/Element[6]/text()]"> (notice current() function). The reason why it does not work in your original expression is that because you query a variable and / looks up the root node of the content of the variable and not the source document you are transforming. current() should return the context element for the template you are in.
My guess is that $prodbins/bin is a node(-set) belonging to a different document than the document that contains the nodes that are being compared to.
In the expression:
$prodbins/bin[./text()=/root/Line[1]/Element[6]/text()]
the subexpression
/root/Line[1]/Element[6]/text()
selects from the same document as the one that is the document from which the $prodbins/bin nodes are selected.
One way to specify successfully the wanted comparisson is:
<xsl:variable name="vDoc" select="/"/>
<xsl:if test="$prodbins/bin[./text()=$vDoc/root/Line[1]/Element[6]/text()]">
<xsl:apply-templates mode="block2sequence" select="NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][position()=1] and NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType][position()=1]"/>
why cant i use two conditions in above select condition, can any one suggest me
<xsl:apply-templates mode="block2"
select="NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][position()=1] "/>
why cant i use two conditions in above select condition
I guess this is to mean, "why can't the two conditions be specified in the same predicate?"
The answer is that the expression:
NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId and position() = 1]
isn't equivalent at all to the 1st expression above.
The first expression selects the first Table child of NewDataSet such that the string value of its CTD_CTD_PKG_ID child is equal to the string value of $PackageId. In this case we don't know which child (at which position) of NewDataSet will be selected -- any child that happens to be the first with the specified properties, will be selected.
On the other side, the latter expression selects the first Table child of NewDataSet only if the string value of its CTD_CTD_PKG_ID child is equal to the string value of $PackageId. In this case, if anything is selected, it would be the first Table child.
If you want an equivalent expression to the first one, that has only one predicate, one such expression is:
NewDataSet/Table
[CTD_CTD_PKG_ID =$PackageId
and
not(preceding-sibling::Table[CTD_CTD_PKG_ID =$PackageId ])
]
Update: The OP has published a code snippet:
<xsl:apply-templates mode="block2sequence" select=
"NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][position()=1]
and
NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType][position()=1]"/>
This code will cause an error thrown at compile time by the XSLT processor.
The value of the select attribute is a boolean (expr1 and expr2), however templates in XSLT 1.0 and XSLT 2.0 can only be applied on nodes. A boolean isn't a node -- hence the error.
Solution:
My first guess is that you want templates to be applied on both nodes. If this is so, then use:
<xsl:apply-templates mode="block2sequence" select=
"NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][1]
|
NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType][1]"/>
My second guess is that you want templates applied only on the first of the two nodes. If this is so, then use:
<xsl:apply-templates mode="block2sequence" select=
"(NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId]
|
NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType]
)
[1]
"/>
Notes:
Please, learn how to ask a question -- provide all relevant data and explain -- in the question, not in subsequent comments.
Did you know that [1] is equivalent to [position()=1] and is shorter?
You can use two conditions and your expression looks perfectly correct. If it is failing with an error, please tell us the error. If it is not selecting what you want, then (a) show us your source document, and (b) tell us what you want to be selected.
(You know, your question gives so little information, you don't give the impression that you really want an answer.)
I am having trouble to display the first matching value, like
<test>
<p>30</p>
<p>30{1{{23{45<p>
<p>23{34</p>
<p>30{1{98</p>
</test>
<test2>
<p1>text</p1>
</test2>
So i want to loop through the <test></test> and find the value of <p> node whose string length is greater than 2 and that contains 30. I want only the first value.
so i tired the following code
<xsl:variable name="var_test">
<xsl:for-each select="*/*/test()>
<xsl:if string-length(p/text())>2 and contains(p/text(),'30'))
<xsl:value-of select="xpath">
</xsl:variable>
the problem is the var_test is being null always.
if i try directly with out any variable
<xsl:for-each select="*/*/test()>
<xsl:if string-length(p/text())>2 and contains(p/text(),'30'))
<xsl:value-of select="xpath">
I am getting the following output
<p>30{1{23{4530{1{98</p>
but the desired output is
<p>0{1{23{45</p>
so how can i achieve this?
Instead of the for-each, use
<xsl:copy-of select="(*/*/test/p[string-length() > 2 and
contains(.,'30'))] )[1]" />
The [1] selects only the first matching <p>. (Updated: I changed the XPath above in response to #markusk's comment.)
The above will output that <p> element as well as its text content, as shown in your "desired output". If you actually want only the value of the <p>, that is, its text content, use <xsl:value-of> instead of <xsl:copy-of>.
Addendum:
The idea of breaking out of a loop does not apply to XSLT, because it is not a procedural language. In a <xsl:for-each> loop, the "first" instantiation (speaking in terms of document order, or sorted order) of the loop is not necessarily evaluated at a time chronologically before the "last" instantiation. They may be evaluated in any order, or in parallel, because they do not depend on each other. So trying to "break out of the loop", which is intended to cause "subsequent" instantiations of the loop not to be evaluated, cannot work: if it did, the outcome of later instantiations would be dependent on earlier instantiations, and parallel evaluation would be ruled out.
I am creating XSLT file.
I have one variable which take value from XML file.But it may happen that there is no reference in xml for the value and at that time XSL variable will return False/None(don't know).I want keep condition like,If there is no value for the variable use the default one.
How to do that ?
With the few details given in the question, the simplest test you can do is:
<xsl:if test="$var">
...
</xsl:if>
Or you might use xsl:choose if you want to provide output for the else-case:
<xsl:choose>
<xsl:when test="not($var)"> <!-- parameter has not been supplied -->
</xsl:when>
<xsl:otherwise> <!--parameter has been supplied --> </xsl:otherwise>
</xsl:choose>
The second example will also handle the case correctly that the variable or parameter has not been supplied with an actual value, i.e. it equals the empty string. This works because not('') returns true.
You haven't explained what you mean by "has no value". Here is a generic solution:
not($v) and not(string($v))
This expression evaluates to true() iff $v "has no value".
Both conditions need to be met, because a string $v defined as '0' has a value, but not($v) is true().
In XSLT 1.0 using a default can be achieved in different ways if the "value" is a node-set or if the value is a scalar (such as a string, a number or a boolean).
#Alejandro provided one way to get a default value if a variable that is supposed to contain a node-set is empty.
If the variable is supposed to contain a scalar, then the following expression returns its value (if it has a value) or (otherwise) the desired default:
concat($v, substring($default, 1 div (not($v) and not(string($v)))))
You can use string-length to check, if a variable called $reference for example contains anything.
<xsl:choose>
<xsl:when test="string-length($reference) > 0">
<xsl:value-of select="$reference" />
</xsl:when>
<xsl:otherwise>
<xsl:text>some default value</xsl:text>
</xsl:otherwise>
</xsl:choose>
If necessary use normalize-space, too.
First, all variables has values, because XSLT belongs to declarative paradigm: there is no asignation instruction, but when you declare the variable you are also declaring the expression for its value relationship.
If this value it's a node set data type (that looks from your question), then you should test for an empty node set in case nothing was selected. The efective boolean value for an empty node set is false. So, as #0xA3 has answered: test="$node-set".
You wrote:
If there is no value for the variable
use the default one. How to do that ?
Well, that depends on what kind of data type you are looking for.
Suppose the node set data type: if you want $node-set-1 value or $node-set-2 if $node-set-1 is empty, then use:
$node-set-1|$node-set-2[not($node-set-1)]
I tried a lot of solution from SO, my last solution was taken from #dimitre-novatchev, but that one also not working every time. Recently I found one more solution from random google search, thought to share with the community.
In order to check empty variable value, we can declare an empty variable and compare its value against test condition. Here is code snippet:
<xsl:variable name="empty_string"/>
<xsl:if test="testVariableValue != $empty_string">
...
</xsl:if>
Here testVariableValue hold the value of new variable to be tested for empty
scenario.
Hope it would help to test empty variable state.