Using x-path " | " operator - xpath

I can choose several paths using .//div/h1/text() | .//div/h2/text(). However I would like to know if there's a way of doing it without explicitly writing out the part that is common for both path's - in this case .//div/ - every time?

As for shortcuts, with XPath 2.0 you can shorten e.g. //div/h1 | //div/h2 to e.g. //div/(h1 | h2) but that syntax is not allowed in XPath 1.0. And I think XPath 3.0 will introduce a let clause to define variables. So there I think you can do e.g. let $r := /html/body/div[3]/table[2]/tbody/tr[5] return ($r/span | $r/a).
Or for your corrected sample with XPath 2.0 you can shorten .//div/h1/text() | .//div/h2/text() to .//div/(h1/text() | h2/text()). But with XPath 1.0 all you can do is use .//div/*[self::h1 | self::h2]/text().

Use:
.//div/*[self::h1 or self::h2]/text()
In Xpath 2.0 one can use:
.//div/(h1|h2)/text()

.//div/*[(local-name() = 'h1') or (local-name() = 'h2')]/text()
should do the trick

Related

Can I condense an XPath expression that checks for conditional values of an attribute?

I have the following XML payload:
<fizz>
<buzz class="foo">
<whatever/>
</buzz>
</fizz>
The value of the /fizz/buzz[#class]/#class attribute can be foo, bar or whistlefeather. I'm trying to write an efficient XPath expression that covers all three scenarios. The best I have is:
/fizz/buzz[#class]/#class = 'foo' |
/fizz/buzz[#class]/#class = 'bar' |
/fizz/buzz[#class]/#class = 'whistlefeather'
Is there some "shorthand" way to make this more condense/efficient (less verbose)?
Using this (all xpath version) :
/fizz/buzz[#class='foo' or #class='bar' or #class='whistlefeather']
Using xpath >=2 :
/fizz/buzz[#class=("foo", "bar", "whistlefeather")]
Correcting the answer from #GillesQuenot:
Using any XPath version:
/fizz/buzz[#class='foo' or #class='bar' or #class='whistlefeather']
Using XPath 2.0 or later:
/fizz/buzz[#class=("foo", "bar", "whistlefeather")]
(Note, this returns the selected buzz elements. It's unclear what you actually want the expression to return.)

Sitecore item multilistfield XPATH builder

I'm trying to count with XPATH Builder in Sitecore, the number of items which have more than 5 values in a multilist field.
I cannot count the number of "|" from raw values, so I can say I am stuck.
Any info will be helpful.
Thank you.
It's been a long time since I used XPath in Sitecore - so I may have forgotten something important - but:
Sadly, I don't think this is possible. XPath Builder doesn't really run proper XPath. It understands a subset of things that would evaluate correctly in a full XPath parser.
One of the things it can't do (on the v8-initial-release instance I have to hand) is be able to process XPath that returns things that are not Sitecore Items. A query like count(/sitecore/content/*) should return a number - but if you try to run that using either the Sitecore Query syntax, or the XPath syntax options you get an error:
If you could run such a query, then your answer would be based on an expression like this, to perform the count of GUIDs referenced by a specific field:
string-length( translate(/yourNodePath/#yourFieldName, "abcdefg1234567890{}-", "") ) + 1
(Typed from memory, as I can't run a test - so may not be entirely correct)
The translate() function replaces any character in the first string with the relevant character in the second. Hence (if I've typed it correctly) that expression should remove all your GUIDs and just leave the pipe-separator characters. Hence one plus the length of the remaining string is your answer for each Item you need to process.
But, as I say, I don't think you can actually run that from Query Builder...
These days, people tend to use Sitecore PowerShell Extensions to write ad-hoc queries like this. It's much more flexible and powerful - so if you can use that, I'd recommend it.
Edited to add: This question got a bit stuck in my head - so if you are able to use PowerShell, here's how you might do it:
Assuming you have declared where you're searching, what MultiList field you're querying, and what number of selections Items must exceed:
$root = "/sitecore/content/Root"
$field = "MultiListField"
$targetNumber = 3
then the "easy to read" code might look like this:
foreach($item in Get-ChildItem $root)
{
$currentField = Get-ItemField $item -ReturnType Field -Name $field
if($currentField)
{
$count = $currentField.Value.Split('|').Count
if($count -gt $targetNumber)
{
$item.Paths.Path
}
}
}
It iterates the children of the root item you specified, and gets the contents of your field. If that field name had a value, it then splits that into GUIDs and counts them. If the result of that count is greater than your threshold it returns the item's URI.
You can get the same answer out of a (harder to read) one-liner, which would look something like:
Get-ChildItem $root | Select-Object Paths, #{ Name="FieldCount"; Expression={ Get-ItemField $_ -ReturnType Field -Name $field | % { $_.Value.Split('|').Count } } } | Where-Object { $_.FieldCount -gt $targetNumber } | % { $_.Paths.Path }
(Not sure if that's the best way to write that - I'm no expert at PowerShell syntax - but it gives the same results as far as I can see)

Selenium Webdriver + Ruby regex: Can I use regex with find_element?

I am trying to click an element that changes per each order like so
edit_div_123
edit_div_124
edit_div_xxx
xxx = any three numbers
I have tried using regex like so:
#driver.find_element(:css, "#edit_order_#{\d*} > div.submit > button[name=\"commit\"]").click
#driver.find_element(:xpath, "//*[(#id = "edit_order_#{\d*}")]//button").click
Is this possible? Any other ways of doing this?
You cannot use Regexp, like the other answers have indicated.
Instead, you can use a nifty CSS Selector trick:
#driver.find_element(:css, "[id^=\"edit_order_\"] > div.submit > button[name=\"commit\"]").click
Using:
^= indicates to find the element with the value beginning with your criteria.
*= says the criteria should be found anywhere within the element's value
$= indicates to find the element with with your criteria at the end of the value.
~= allows you to find the element based on a single criteria when the actual value has multiple space-seperated list of values.
Take a look at http://net.tutsplus.com/tutorials/html-css-techniques/the-30-css-selectors-you-must-memorize/ for some more info on other neat CSS tricks you should add to your utility belt!
You have no provided any html fragment that you are working on. Hence my answer is just based on the limited inputs provided your question.
I don't think WebDriver APIs support regex for locating elements. However, you can achieve what you want using just plain XPath as follows:
//*[starts-with(#id, 'edit_div_')]//button
Explanation: Above xpath will try to search all <button> nodes present under all elements whose id attribute starts with string edit_div_
In short, you can use starts-with() xpath function in order to match element with id format as edit_div_ followed by any number of characters
No, you can not.
But you should do something like this:
function hasClass(element, className) {
var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
return re.test(element.className);
}
This worked for me
#driver.find_element(:xpath, "//a[contains(#href, 'person')]").click

XPath OR operator for different nodes

How can I do with XPath:
//bookstore/book/title or //bookstore/city/zipcode/title
Just //title won't work because I also have //bookstore/magazine/title
p.s. I saw a lot of or examples but mainly with attributes or single node structure.
All title nodes with zipcode or book node as parent:
Version 1:
//title[parent::zipcode|parent::book]
Version 2:
//bookstore/book/title|//bookstore/city/zipcode/title
Version 3: (results are sorted based on source data rather than the order of book then zipcode)
//title[../../../*[book] or ../../../../*[city/zipcode]]
or - used within true/false - a Boolean operator in xpath
| - a Union operator in xpath that appends the query to the right of the operator to the result set from the left query.
If you want to select only one of two nodes with union operator, you can use this solution:
(//bookstore/book/title | //bookstore/city/zipcode/title)[1]
It the element has two xpath. Then you can write two xpaths like below:
xpath1 | xpath2
Eg:
//input[#name="username"] | //input[#id="wm_login-username"]

Is there a DRYer XPath expression for union?

This works nicely for finding button-like HTML elements, (purposely simplified):
//button[text()='Buy']
| //input[#type='submit' and #value='Buy']
| //a/img[#title='Buy']
Now I need to constrain this to a context. For example, the Buy button that appears inside a labeled box:
//legend[text()='Flubber']
And this works, (.. gets us to the containing fieldset):
//legend[text()='Flubber']/..//button[text()='Buy']
| //legend[text()='Flubber']/..//input[#type='submit' and #value='Buy']
| //legend[text()='Flubber']/..//a/img[#title='Buy']
But is there any way to simplify this? Sadly, this sort of thing doesn't work:
//legend[text()='Flubber']/..//(
button[text()='Buy']
| input[#type='submit' and #value='Buy']
| a/img[#title='Buy'])
(Note that this is for XPath within the browser, so XSLT solutions will not help.)
Combine multiple conditions in a single predicate:
//legend[text()='Flubber']/..//*[self::button[text()='Buy'] or
self::input[#type='submit' and #value='Buy'] or
self::img[#title='Buy'][parent::a]]
In English:
Select all descendants of the parent (or the parent itself)
for any legend element having the
text "Flubber" that are any of 1) a button
element having the text "Buy" or 2) an
input element having an attribute
type whose value is "submit" and an
attribute named value whose value is
"Buy" or 3) an img having an
attribute named title with a value
of "Buy" and whose parent is an a
element.
From comments:
Adjusting slightly to obtain the A
rather than the IMG:
self::a[img[#title='Buy']]. (Now if
only 'Buy' could be reduced
Use this XPath 1.0 expression:
//legend[text() = 'Flubber']/..
//*[
self::button/text()
| self::input[#type = 'submit']/#value
| self::a/img/#title
= 'Buy'
]
EDIT: I didn't see the parent accessor. Other way in one direction only:
//*[legend[text() = 'Flubber']]
//*[
self::button/text()
| self::input[#type = 'submit']/#value
| self::a/img/#title
= 'Buy'
]

Resources