Using xsl:key to store result of boolean expression - xpath

In my transformation there is an expression some elements are repeatedly tested against. To reduce redundancy I'd like to encapsulate this in an xsl:key like this (not working):
<xsl:key name="td-is-empty" match="td" use="not(./node()[normalize-space(.) or ./node()])" />
The expected behaviour is the key to yield a boolean value of true in case the expression is evaluated successfully and otherwise false. Then I'd like to use it as follows:
<xsl:template match="td[not(key('td-is-empty', .))]" />
Is this possible and in case yes, how?

I think with XSLT 1.0 a key value is always of type string so in your sample the key value with either be the string true or the string false. You could then call key('td-is-empty', 'true') to find all td element nodes for which the expression is true and key('td-is-empty', 'false') to find all td elements for which the expression is false.
You seem to want to do something differently with your key however, like storing the result of the use expression for each td node, based on node identity. I don't think that is how keys work in XSLT.
[edit]
You might however be able to express your requirement as
<xsl:template match="td[count(. | key('td-is-empty', 'false')) = count(key('td-is-empty', 'false'))]">...</xsl:template>
That matches those td elements which are a member of the set of elements found by key('td-is-empty', 'false').

Related

Is it possible in XPATH to find an element by attribute value, not by name?

For example I have an XML element:
<input id="optSmsCode" type="tel" name="otp" placeholder="SMS-code">
Suppose I know that somewhere there must be an attribute with otp value, but I don’t know in what attribute it can be, respectively, is it possible to have an XPath expression of type like this:
.//input[(contains(*, "otp")) or (contains(*, "ode"))]
Try it like this and see if it works:
one = '//input/#*[(contains(.,"otp") or contains(.,"ode"))]/..'
print(driver.find_elements_by_xpath(one))
Edit:
The contains() function has a required cardinality of first argument of either one or zero. In plain(ish) English, it means you can check only one element at a time to see if it contains the target string.
So, the expression above goes through each attribute of input separately (/#*), checks if the attribute value of that specific attribute contains within it the target string and - if target is found - goes up to the parent of that attribute (/..) which, in the case of an attribute, is the node itself (input).
This XPath expression selects all <input> elements that have some attribute, whose string value contains "otp" or "ode". Notice that there is no need to "go up to the parent ..."
//input[#*[contains(., 'otp') or contains(., 'ode')]]
If we know that "otp" or "ode" must be the whole value of the attribute (not just a substring of the value), then this expression is stricter and more efficient to evaluate:
//input[#*[. ='otp' or . = 'ode']]
In this latter case ("otp" or "ode" are the whole value of the attribute), if we have to compare against many values then an XPath expression of the above form will quickly become too long. There is a way to simplify such long expression and do just a single comparison:
//input[#*[contains('|s1|s2|s3|s4|s5|', concat('|', ., '|'))]]
The above expression selects all input elements in the document, that have at least one attribute whose value is one of the strings "s1", "s2", "s3", "s4" or "s5".

XPath Syntax - XSL 1.0

I'm trying to select all elements using XSL and XPath syntax where there is more than one pickup. I'd like to return the counter_name for each. Can someone please help me with the syntax? In this example there is only one counter_name with pickup locations, but there could be multiple locations where there are pickup counters.
XPATH
<xsl:value-of select="results/unique_locations/partner_location_ids[count(pickup) > 0]/counter_name" /><br/>
XML
<results>
<unique_locations>
<counter_name>Lake Buena Vista, FL</counter_name>
<is_airport>N</is_airport>
<partner_location_ids>
<pickup>
</pickup>
<dropoff>
<container>ZR-ORLS001</container>
<container>ET-ORLR062</container>
<container>HZ-ORLS011</container>
<container>HZ-ORLW015</container>
<container>AV-ORLR004</container>
</dropoff>
</partner_location_ids>
<counter_name>Orlando, FL</counter_name>
<is_airport>N</is_airport>
<partner_location_ids>
<pickup>
<container>ET-ORLC037</container>
<container>AV-ORLC021</container>
<container>ET-ORLC033</container>
<container>ET-ORLC035</container>
<container>HZ-ORLS007</container>
<container>HZ-ORLC004</container>
<container>HZ-ORLC002</container>
<container>ZR-ORLS002</container>
<container>BU-ORLE002</container>
<container>AV-ORLC019</container>
<container>ET-ORLR064</container>
<container>ET-ORLC001</container>
<container>ET-ORLR063</container>
<container>ET-ORLR061</container>
<container>HZ-ORLC011</container>
<container>HZ-ORLC054</container>
<container>HZ-ORLN003</container>
<container>HZ-ORLC007</container>
<container>HZ-ORLC005</container>
<container>ZA-ORLC002</container>
<container>ZA-ORLC003</container>
<container>ZA-ORLC001</container>
<container>AV-ORLC002</container>
<container>AV-ORLC001</container>
<container>BU-ORLS001</container>
<container>ET-ORLC012</container>
<container>AL-ORLR071</container>
<container>HZ-ORLC022</container>
<container>ET-ORLC051</container>
<container>HZ-ORLC025</container>
<container>HZ-ORLN018</container>
<container>HZ-ORLC017</container>
<container>AV-ORLN003</container>
<container>BU-ORLC002</container>
<container>BU-ORLC003</container>
<container>BU-ORLS006</container>
<container>ET-ORLC027</container>
<container>ET-ORLC022</container>
<container>AL-ORLR081</container>
<container>BU-ORLC005</container>
<container>HZ-ORLR029</container>
<container>HZ-ORLC032</container>
<container>HZ-ORLC031</container>
<container>HZ-ORLC030</container>
<container>ET-ORLC021</container>
</pickup>
<dropoff>
<container>HZ-ORLC003</container>
<container>ZA-ORLC004</container>
<container>BU-ORLW002</container>
<container>HZ-ORLC026</container>
<container>ZR-ORLC010</container>
<container>AL-ORLR073</container>
</dropoff>
</partner_location_ids>
</unique_locations>
Your XML structure is non-ideal, in that it appears to contain elements that are associated with each other by sequence, rather than exclusively by containment within the same element. But XPath can deal with that.
Supposing that the context node for evaluation of the XPath is the parent node of the <results> whose contents you are examining, it appears you want something along these lines:
results/unique_locations/partner_location_ids[pickup/*]/preceding-sibling::counter_name
Note in the first place the predicate: [pickup/*]. The expression within, interpreted in boolean context, evaluates to true if the expression matches any nodes. That's why we need pickup/*, not just pickup, to distinguish between <pickup> elements that contain child nodes and those that don't.
Additionally, observe the use of the preceding-sibling axis instead of the default child axis to step from each matching <partner_location_ids> to its corresponding (I think) <counter_name>.

How to refer to another instance in the iterate of the XForms action element?

I am using an XForms action along with iterate. The iterate selects a set (using XPath) of nodes and repeats the action for it.The problem is I have multiple conditions for selecting the node set.
There should not be a readOnly node.
Should not be part of the ignoreProperties list (this list is in another instance).
Code:
<xf:action ev:event="setValues" iterate="
instance('allProps')/props/prop[
not(readOnly) and
not(instance('ignoreProperties')/ignoredProperties/property[text() = name]
]
">
The first condition not(readOnly) works. But the second condition does not work. I feel there is some problem with the context of the XPath nodes.
How should I replace the second condition to achieve the result ?
The target XML is a simple ignoredProperties document:
<ignoredProperties>
<property>c_name</property>
<property>c_tel_no</property>
</ignoredProperties>
This should work:
<xf:action ev:event="setValues" iterate="
instance('allProps')/props/prop[
not(readOnly) and
not(name = instance('ignoreProperties')/ignoredProperties/property)
]
">
The = operator works against multiple nodes, returning all the ones that match. With not() you can express that you don't want a match.
Explicitly selecting .../property/text() will not be necessary.
There seems to be something wrong with your calls to instance(). If you have:
<xf:instance id="ignoredProperties">
<ignoredProperties>
<property>c_name</property>
<property>c_tel_no</property>
</ignoredProperties>
</xf:instance>
Then instance('ignoredProperties') returns the <ignoredProperties> element. So you should write:
<xf:action ev:event="setValues" iterate="
instance('allProps')/prop[
not(readOnly) and
not(instance('ignoreProperties')/property[text() = name])
]
">
This also assumes your allProps instance has a <props> root element.
Further, the second condition appears wrong, as already shown in another answer. Write instead:
not(name = instance('ignoreProperties')/property)
In XPath 2, you could clarify that your not() are testing on node existence by using empty() instead:
<xf:action ev:event="setValues" iterate="
instance('allProps')/prop[
empty(readOnly) and
not(name = instance('ignoreProperties')/property)
]
">

XPath: Default to "Master" node, select current node if specified (in third node)

My problem:
I need to select the value in the "Master_Node" only if the "Sub_Node_Checker" is set to "false".
If "Sub_Node_Checker" is set to "true" then the value must be set to "Sub_Node".
Current node is "Sub_Node"
I am using InfoPath 2010.
Here is my sample XML:
<my:myFields xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2013-07-02T14:58:05" xml:lang="en-us">
<my:Master_Node>123456</my:Master_Node>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
</my:Repeater>
</my:myFields>
Refer to the following forum thread to download my XSN template.
Here is the XPath that I have been attempting to use, to no avail (line breaks added for legibility):
//my:Master_Node[../my:Sub_Node_Checker = "false"]
|
../my:Sub_Node[../my:Sub_Node_Checker = "true"]
This does not seem to return anything whatsoever, and I'm not sure why.
The following question accompanies "Sub_Node_Checker" in my XML form: "Does the sub node differ from the master node?"
If the user selects "Yes" (true) then the Sub_Node field should be set to its own value.
If the user selects "No" (false) then the Sub_Node field should default to the Master_Node.
Edit & Additional XML
My Repeater section repeats (as per the name) and seems to cause additional chaos with the XPath selectors.
<my:myFields xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2013-07-02T14:58:05" xml:lang="en-us">
<my:Master_Node>123123</my:Master_Node>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
</my:Repeater>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
</my:Repeater>
</my:myFields>
A much cleaner solution would be to move the predicate to the <my:myFields/> element.
/my:myFields[my:Repeater/my:Sub_Node_Checker = "false"]/my:Master_Node
If you insist on your approach, you're missing a / to jump over the <my:Repeater/> element or reference that:
//my:Master_Node[..//my:Sub_Node_Checker = "false"]
//my:Master_Node[../my:Repeater/my:Sub_Node_Checker = "false"]
Relating to your xpath question you may try something like this.
(self::*[../my:Sub_Node_Checker = "true"]
|
//my:Master_Node)[last()]
Which should work if Master_Node is always before Sub_Node (in document order).
Ok, I got this one worked out a bit differently than my original approach. The following article on MSDN has an example of using the substring() function to return different values based on outside conditions. Also have to thank Hilary Stoupa at InfoPath Dev for helping me come to the solution.
I will also mention that I could not use the "current" node and had to create a third node which housed my XPath expression and evaluated the conditions to return the appropriate value.
Here is the source XML roughly as InfoPath might interpret it (note the "location" of the xpath expression in the 3rd repeater group - this is how InfoPath evaluates default values):
<my:myFields xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2013-07-02T14:58:05" xmlns:xd="http://schemas.microsoft.com/office/infopath/2003" xml:lang="en-us">
<my:Master_Node>123456</my:Master_Node>
<my:Repeater>
<my:Sub_Node_Checker>true</my:Sub_Node_Checker>
<my:Sub_Node>9870</my:Sub_Node>
<my:Sub_Node_Stored>9870</my:Sub_Node_Stored>
</my:Repeater>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
<my:Sub_Node_Stored>123456</my:Sub_Node_Stored>
</my:Repeater><
my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
<my:Sub_Node_Stored>concat(substring(../../my:Master_Node, 1, (../my:Sub_Node_Checker != "true") * string-length(../../my:Master_Node)), substring(../my:Sub_Node, 1, (../my:Sub_Node_Checker != "false") * string-length(../my:Sub_Node)))</my:Sub_Node_Stored>
</my:Repeater>
</my:myFields>
Note that the following XPath expression was instrumental in causing the appropriate node to be selected:
concat(substring(../../my:Master_Node, 1, (../my:Sub_Node_Checker != "true") * string-length(../../my:Master_Node)), substring(../my:Sub_Node, 1, (../my:Sub_Node_Checker != "false") * string-length(../my:Sub_Node)))
The substring function returns the number of characters from the target string as specified by the user. When a boolean value is evaluated inside the substring function (at the location given for number of characters to return) it returns either a "1" or a "0".
When multiplied by the length of the target string this boolean check causes any conditions that would exempt a particular target node from selection to return a length of "0" characters from that node. (0*X=0) This effectively allows for different default values within the node without the use of the | operator.

xquery full search text of children

Is it possible to do a search for a key words in an exist-db using xquery?
I've tried using
//foo//#val[. &= $param]
But this returns an error because this isn't supported with my version of exist-db (1.4.2)
What is the best way to do a search over a number of nodes?
<xml>
<foo #val='test1'>
<bar #val='test2'>
<thunk #val='test3'/>
</bar>
</foo>
So with my example XML, how can I do
let $result :=
if //xml/foo[contains(#val,$param)] or
//xml/foo/bar[contains(#val,$param)] or
//xml/foo/bar/thunk[contains(#val,$param)]
return $result
Either of these should work:
//foo//#val[contains(.,$param)]
//foo//#val[. eq $param]
However, there are obviously issues to consider when using contains() instead of equals. Also, if the paths will always be constrained as you describe in your example, and you are only checking to see if any of those are true (as opposed to actually getting all the elements), then this should be a faster and more efficient query:
((//xml/foo[#val eq $param])[1] or (//xml/foo/bar[#val eq $param])[1] or (//xml/foo/bar/thunk[#val eq $param])[1])
Untested, but the [1] should short-circuit the xpath evaluator after it gets its first result from the query, and the ORs should short-circuit the expression when any one of them returns a value.

Resources