Is there an exclusive OR 'XOR' in XPath1.0 ?
Use this XPath 1.0 expression:
x and not(y) or y and not(x)
Always try to avoid the != operator, because it has an unexpected meaning/behavior when one or both of its arguments are node-sets.
In XSLT 2.0 or XQuery 1.0 one can write this as a function and then use just the function in any XPath expression. Below is an XSLT 2.0 function definition for xor and a small example of using this function:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="my:f">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select=
"for $x in (true(), false()),
$y in (true(), false())
return
('xor(', $x, ',', $y,') = ', f:xor($x, $y), '
')
"/>
</xsl:template>
<xsl:function name="f:xor">
<xsl:param name="pX" as="xs:boolean"/>
<xsl:param name="pY" as="xs:boolean"/>
<xsl:sequence select=
"$pX and not($pY) or $pY and not($pX)"/>
</xsl:function>
</xsl:stylesheet>
when this transformation is applied on any XML document (not used), the wanted, correct result is produced:
xor( true , true ) = false
xor( true , false ) = true
xor( false , true ) = true
xor( false , false ) = false
No but you can emulate it:
(a or b) and (a != b)
number($boolean_var) converts true() to 1 and false() to 0. (Note that true alone addresses a node!!)
boolean($numeric_var) converts 1 to true() and 0 to false().
Therefore, XOR can be accomplished by:
boolean((number($v1) + number($v2) + number($v3)) mod 2)
i.e. least-significant-bit addition using the mod 2 operator. Yes, XPATH is cumbersome.
Related
Here I want to count/Increment the repeated emp_no using for each.
and I just want to concatenate emp_no with duplicate no count..
Here is my XSLT style sheet.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array" exclude-result-prefixes="#all"
version="3.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="input"/>
<xsl:variable name="newline" select="'
'"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:variable name="input-as-map" select="parse-json($input)" as="map(*)"/>
<root>
<file1>
<xsl:for-each select="$input-as-map?file1?*">
<xsl:value-of select="$newline"/>
<xsl:value-of select="concat(?emp_no,',',count(?emp_no))"/>
<xsl:value-of select="$newline"/>
</xsl:for-each>
</file1>
</root>
</xsl:template>
</xsl:stylesheet>
input json:-
{
"file1": [
{
"emp_no": "0002"
},
{
"emp_no": "0009"
},
{
"emp_no": "0002"
},
{
"emp_no": "0009"
},
{
"emp_no": "0009"
}
]
}
Expected output:-
<root>
<file1>
0002,1
0009,1
0002,2
0009,2
0009,3
</file1>
</root>
Any suggestions also would be helpful.
One solution would be to replace the xsl:for-each with xsl:iterate, maintaining a parameter value containing a map from emp_no to an integer counter.
Another would be to convert the array to XML and use xsl:number.
Or you could use fn:fold-left(), again maintaining a map from emp_no to an integer counter as you step through the array.
None of them particularly easy, I'm afraid: perhaps someone else has a better idea.
Here is Mike's third suggestion spelled out:
<file>
<xsl:value-of
select="fold-left(
parse-json($json)?file1?*,
map{},
function($am, $m) {
let $c := head($am)($m?emp_no),
$new-am := map:put(head($am), $m?emp_no, if (empty($c)) then 1 else $c + 1)
return ($new-am, tail($am), $m?emp_no || ',' || $new-am($m?emp_no))
}
) => tail()"
separator="
"/>
</file>
This Java program makes use of a Ternary if, to map booleans to output strings: (a "*" for true, an empty string for false).
public class ternary {
public static void main(String[] args) {
boolean flags[]={true,false,true};
for (boolean f : flags) {
System.out.println(f?"*":"");
}
}
}
So the output is *, [empty], *.
I have an input XML document, something like:
<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="change.xsl"?>
<root>
<change flag="true"/>
<change flag="false"/>
<change flag="true"/>
</root>
And I have the following XSLT template which maps true to '*' and false to '' (it works):
<xsl:template match="change">
<xsl:variable name="flag" select="translate(substring(#flag,1,1),'tf','*')"/>
<xsl:value-of select="$flag"/>
</xsl:template>
Is there is more concise version of this ?
a) Can I automatically get a value of boolean true|false directly from the string 'true|false' ?
b) Is there an (xpath?) construct to map the boolean true|false to '*', '' ?
a) Can I automatically get a value of boolean true|false directly from
the string 'true|false' ?
b) Is there an (xpath?) construct to map the boolean true|false to
'*', '' ?
This is actually an XPath question.
I. XPath 1.0
There are more than one XPath expressions the evaluation of which produces the wanted result:
substring('*', 2 -(#flag = 'true'))
This also answers b) -- notice that the strings 'true' and 'false' aren't boolean values! The only two boolean values are true() and false() and they are not strings.
In the above expression we use the fact that in XPath 1.0 if a boolean is in a context where a number is needed, it is automatically converted to number. By definition:
number(true()) is 1
and
number(false()) is 0
Thus the second argument of the call to substring() above:
2 - (#flag = 'true')
is evaluated as 1 when #flag = 'true' and to 2 otherwise.
A more general XPath 1.0 expression that produces the string $s1 if $val is "x" and produces the string $s2 if $val is "y" :
concat(substring($s1, 1 div ($val = "x")),
substring($s2, 1 div ($val = "y"))
)
This produces the string $s1 when $val = "x", the string $s2 when $val = "y", and the empty string -- if none of these two conditions holds.
The above XPath 1.0 expression can be generalized to produce N different string results $s1, $2, ..., $sN exactly when $val is one of the values $v1, $v2, ..., $vN because the function concat() can have any number of arguments.
II. XPath 2.0 (XSLT 2.0)
'*'[current()/#flag = 'true']
And more generally, given 2*N atomic values $s1, $s2, ... $sN and $v1, $v2, ..., $vN, such that all $vi values are different, the result of evaluating this XPath 2.0 expression:
($s1, $s2, ..., $sN)[index-of(($v1, $v2, ..., $vN), $v)]
is $sK exactly when $v eq $vK.
You could utilise simple pattern matching in templates for this.
<xsl:template match="change[#flag = 'true']">
<xsl:template match="change">
So, the first one matches true entries, and the other one matches all other cases (which in your cases is just false
So, given the following stylesheet
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes"/>
<xsl:template match="change[#flag = 'true']">
<xsl:text>*
</xsl:text>
</xsl:template>
<xsl:template match="change">
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
*
*
I have this xslt :
<xsl:param name="total_articles" select="3" />
<xsl:param name="articles_per_page" select="3" />
<xsl:apply-templates select="dagboek/entry[position > $offset][position < $articles_per_page+$offset]" >
<xsl:with-param name="total_pages" tunnel="yes">
<xsl:choose>
<xsl:when test="$value="2005-09" and $page="1">8</xsl:when>
<xsl:otherwise>floor(number($total_articles)-1) div $articles_per_page +1</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:apply-templates>
But now I get this error.
How can I calculate the outcome of the calculation and put it into the param total_pages.
Roelof
Edit 1: WHat I try to achieve is that if it's not 2005-09 and page is not 1 then the totalpages is calculated out of total_articles and articles_per_page. The outcome has to be put into the param pages.
<xsl:when test="$value="2005-09" and $page="1">8</xsl:when>
This isn't even well-formed XML document. The problem is with the nested quotes.
Probably you meant:
<xsl:when test="$value='2005-09' and $page='1' ">8</xsl:when>
Another possible problem: Tunnel parameters are only available in XSLT 2.0. You seem to be using XSLT 1.0
Update:
The OP in a comment has modified/clarified his initial question:
WHat I try to achieve is that if it's not 2005-09 and page is not 1
then the totalpages is calculated out of total_articles and
articles_per_page. The outcome has to be put into the param pages.
This can simply be expressed as:
<xsl:with-param name="total_pages" select=
"8*($value='2005-09' and $page='1')
+
(floor(number($total_articles)-1) div $articles_per_page +1)
*
not($value='2005-09' and $page='1')
"/>
Explanation:
We are using the fact that in XPath 1.0 by definition number($someBoolean) is 1 if $someBoolean is true() and is 0 if $someBoolean is false().
Therefore the pseudocode:
if($vCond)
then $vNum1
else $vNum2
can be expressed with a single XPath expression:
$vNum1*$vCond + $vNum2*not($vCond )
whenever a boolean is an argument to an arithmetic operator, it is automatically converted to a number.
So, here's what happens at run-time:
Suppose $vCond is true(), therefore not($vCond) is false().
Because $vCond and not($vCond) are arguments to the * operator, they are converted to numbers, respectively 1 and 0:
...
$vNum1*1 + $vNum2*0
This is equivalent to:
...
$vNum1*1
Note:
The above equivalence rule can be further generalized to N mutually exclusive conditions $vCond1, $vCond2, ..., $vCondN, and corresponding N values: $val1, $val2, ..., $valN:
$val1*$vCond1 + $val2*$vCond2 + ... + $valN*$vCondN
is equal to $valK (k in {1,..., N}) exactly when $vCondK is true()
You have a badly formed attribute:
<xsl:when test="$value="2005-09" and $page="1">8</xsl:when>
It should probably look like:
<xsl:when test="$value='2005-09' and $page='1'">8</xsl:when>
Is it this line?
<xsl:when test="$value="2005-09" and $page="1">8</xsl:when>
You're nesting double quotes - that's not going to work. Use a combination of single and double quotes :
<xsl:when test='$value="2005-09" and $page=1'>8</xsl:when>
I have an xml like, values can be
<n1>value1</n1>
<n1>value1</n1>
<n1>value2</n1>
I need to check if all these values are same and if same I would need to assign it to another element. I am using XSLT v1.0.
Thanks,
Good question, +1.
Just use:
not(/*/n1[1] != /*/n1)
Assuming that all n1 elements are selected in a variable named $v, this can be expressed in just 14 characters-long XPath expression:
not($v[1] != $v)
Explanation 1:
By definition:
/*/n1[1] != /*/n1
is true() exactly if there exists a node in /*/n1 whose string value isn't equal to the string value of /*/n1[1]
The logical negation of this:
not(/*/n1[1] != /*/n1)
is true() iff no node in /*/n1 exists whose string value isn't equal to the string value of /*/n1[1] -- that is, if all nodes in /*/n1 have the same sting value.
Explanation 2:
This follows from a more general double negation law :
every x has property y
is equivalent to:
There is no x that doesn't have property y
Assume a document of this form:
<root>
<n1>value1</n1>
<n1>value1</n1>
<n1>value1</n1>
</root>
The following simple stylesheet determines whether every n1 element has the same value:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:value-of select="
concat('All same? ', count(/*/n1[.=/*/n1[1]])=count(/*/n1))"/>
</xsl:template>
</xsl:stylesheet>
Output:
All same? true
The key to this stylesheet is the expression:
count(/*/n1[.=/*/n1[1]])=count(/*/n1))
...which compares the count of the n1 elements whose value equals the value of the first n1 element to the count of all n1 elements. These counts will be equal only when every n1 node has the same value.
This can be made a little easier to read by first selecting all n1 into a variable named n:
count($n[.=$n[1]])=count($n)
Conditionally perform some action based on the result like this:
<xsl:template match="/">
<xsl:variable name="all" select="count(/*/n1[.=/*/n1[1]])=count(/*/n1)"/>
<xsl:if test="$all">All same</xsl:if>
<xsl:if test="not($all)">Not all same</xsl:if>
</xsl:template>
I needs an XSLT function to create key-value strings sequence ( 'key1_val1', 'key2_val2', 'key3_val3' ) from plain strings sequence ( 'key1', 'val1', 'key2', 'val2', 'key3', 'val3' ).
I was stopped on the following code and have no ideas to continue:
<xsl:function name="bx:generate-pairs" as="xs:string*">
<xsl:param name="seq" as="xs:string*"/>
<xsl:sequence select="for $key in $seq return ..."/>
</xsl:function>
XSLT processor Saxon 9.3
You are looking for
for $i in (1 to count($seq))[. mod 2 = 1] return concat($seq[$i], '_', $seq[$i + 1])