How to get the last element of a sequence in XPath? - ruby

In Ruby we can access an array with negative numbers like array[-1] to get the last object in the array. How do I do this using XPath?
I can't do this:
result = node.xpath('.//ROOT/TAG[-1]/KEY_NAME')
I found a solution here on Stack Overflow, but that is a query that just changes the upper limit to get elements. This could return one last item or last item and prevous.
What if I want to get only the prevous element like array[-2] in Ruby?

You can access the last element in XPath using last() in a predicate.
node.xpath('.//ROOT/TAG[last()]/KEY_NAME')
And use [last()-1] for the second-to-last position.

Related

In Ruby is there a way to get the index of an item in an array that consists of structs?

With a normal array, I can use the arrayname.find_index('whatimlookingfor') to get the position within the array.
I can't figure out how to do this when the elements of the array are Struct's.
Scenario: I have a struct that consists of an ID and the Filename. In one function I need to find within that array the ID of a different file than the one I'm currently processing. I know the other filename, so what I was hoping that I could do something like:
arrayname.filename.find_index(parsedfilename)
But this obviously fails. Without iterating through the entire array is there a way to quickly reference the index of where the match happens? Or am I out of luck because the array is a collection of structs?
index (same as find_index) takes a block in which you can code up any true/false logic for your finder. To find the index of the first item whose filename does not match parsedfilename...
found_index = items.index { |item| item.filename != parsedfilename }
Many methods which work with Arrays and Enumerables also take blocks.

How to grab Index instead of relative position using xpath

Given the following xml:
<randomName>
<otherName>
<a>item1</a>
<a>item2</a>
<a>item3</a>
</otherName>
<lastName>
<a>item4</a>
<a>item5</a>
</lastName>
</randomName>
Running: '//a' Gives me an array of all 5 "a" elements, however '//a[1]' does not give me the first of those five elements (item1). It instead gives me an array containing (item1 and item 4).
I believe this is because they are both position 1 relatively. How can I grab any a element by its overall index?
I would like to be able to use a variable "x" to get itemX.
You can wrap it in parenthesis so it knows to apply the index to the entire result set
(//a)[1]

How can I select the second last item in a xpath query?

I'm new to xpath and I understand how to get a range of values in xpath:
/bookstore/book[position()>=2 and position()<=10]
but in my case, I need to get above 2 and one less then the total(so if there's 10 then I need 9, or if there's 5, I need up to the 4th spot). I'm applying my code to different pages and the number of entries is not always the same.
In python, I could do something like book[2:-2], but I'm unsure if I can do this within xpath.
You can use last() which represents the last item in the context:
/bookstore/book[position()>=2 and position() <= (last() - 1)]
In my case this was working for me to get last but one element
/bookstore/book[position() = (last() - 1)]

Need some explanation about getting max in XPath

I'm kinda new to XPath and I've found that to get the max attribute number I can use the next statement: //Book[not(#id > //Book/#id) and it works quite well.
I just can't understand why does it return max id instead of min id, because it looks like I'm checking whether id of a node greater than any other nodes ids and then return a Book where it's not.
I'm probably stupid, but, please, someone, explain :)
You're not querying for maximum values, but for minimum values. Your query
//Book[not(#id > //Book/#id)
could be translated to natural language as "Find all books, which do not have an #id that is larger than any other book's #id". You probably want to use
//Book[not(#id < //Book/#id)
For arbitrary input you might have wanted to use <= instead, so it only returns a single maximum value (or none if it is shared). As #ids must be unique, this does not matter here.
Be aware that //Book[#id > //Book/#id] is not equal to the query above, although math would suggest so. XPath's comparison operators adhere to a kind of set-semantics: if any value on the left side is larger than any value on the right side, the predicate would be true; thus it would include all books but the one with minimum #id value.
Besides XPath 1.0 your function is correct, in XPath 2.0:
/Books/Book[id = max(../Book/id)]
The math:max function returns the maximum value of the nodes passed as the argument. The maximum value is defined as follows. The node set passed as an argument is sorted in descending order as it would be by xsl:sort with a data type of number. The maximum is the result of converting the string value of the first node in this sorted list to a number using the number function.
If the node set is empty, or if the result of converting the string values of any of the nodes to a number is NaN, then NaN is returned.
The math:max template returns a result tree fragment whose string value is the result of turning the number returned by the function into a string.

How to count the elements of an array inside an array?

I have a method which returns the number of hotels from a webpage:
hotel_count = self.getHotelsList.values
The output of this method is:
[["hotel_0", "hotel_1", "hotel_2", "hotel_3", "hotel_4", "hotel_5", "hotel_6", "hotel_7", "hotel_8", "hotel_9", "hotel_10", "hotel_11", "hotel_12", "hotel_13", "hotel_14", "hotel_15", "hotel_16", "hotel_17", "hotel_18", "hotel_19", "hotel_20", "hotel_21", "hotel_22", "hotel_23", "hotel_24", "hotel_25", "hotel_26", "hotel_27", "hotel_28", "hotel_29", "hotel_30", "hotel_31", "hotel_32", "hotel_33", "hotel_34", "hotel_35", "hotel_36", "hotel_37", "hotel_38", "hotel_39", "hotel_40"]]
I want to know the length of this array, but if I write
hotel_count = self.getHotelsList.values.length
The length is 1. How can I get a length of 41, which is the one I'm expecting?
Thanks
The array you are showing is nested inside another array. So the outer array is of length 1, the inner array is what you want.
To get it you have to first get the first element of the outer array using [0] or first
testList[0].length
testList.first.length
I am not sure why your getHotelsList method returns a nested array, it doesn't appear to need it.
hotel_count = getHotelsList.values.first.length
You can also do it with [0], but first is faster.
Two notes:
You don't need self at the beginning.
It is a bad habit to use camel case for method names in Ruby. it should better be get_hotels_list.
You could convert that into a single array with flatten:
hotel_count = self.getHotelsList.values.flatten.size

Resources