ruby search for name and value in nokogiri object - ruby

The code below will get me all nodes with name=visible, like this node:
<property name="visible" value="false"/>
vis = #noko_obj.search("property[name=visible]")
...regardless of what value is. However, what if I want the nodes whose name="visible" AND whose value="true"?
thanks

Just add another attribute selector:
vis_true = #noko_obj.search('property[name=visible][value=true]')
The order of the attribute selectors doesn't matter so you could also do this:
vis_true = #noko_obj.search('property[value=true][name=visible]')

Related

XPath 1.0 to find a sibling of an attribute node whose name is based on the attribute, but has a suffix

Given the following Xml:
<Root><Foo Bar="" Bar_Baz="12" /></Root>
Is there an XPath statement (using version 1.0 functions only) that can return Root/Foo/#Bar where there exists some sibling attribute starting with Bar (determined by context), and ending in _Baz, where that node has the value 12?
Bar should be anonymous - the XPath shouldn't care what it's called - but whatever it is called, if it is returned or not should be determined by whether X_Baz exists, and has the value of 12.
I was looking into something like:
//#*[sibling::#*[concat(local-name(), '_Baz') = '12']
But fairly obviously, this would just compare the text Bar_Baz to 12, not the value of that sibling attribute.
I'm making use of this using the .Net XmlDocument class, meaning I'm limited to Microsoft's XPath 1.0 implementation, so please don't make use of subsequent versions of the spec!
EDIT: Per the comment requesting a more diverse set of examples, see below:
<Root>
<Item Foo="" Foo_Baz="12">Yes - #Foo_Baz is 12, and #Foo exists</Item>
<Item Bar="" Bar_Baz="12">Yes - #Bar_Baz is 12, and #Bar exists</Item>
<Item Foo="" Foo_Baz="1">No - Foo_Baz != 12<Item>
<Item Baz="" Foo_Baz="12">No - No #Foo to return</Item>
<Item Foo_Baz="12">No - No #Foo to return</Item>
<Item Foo="" Foo_Haz="12">No - No #Foo_Baz node to check the value of</Item>
</Root>
Edit 2:
Looking at the first couple of answers proposed, I think there is something I haven't been clear on: the names, Foo or Bar, are unknown. The only things that are known are:
There are one or more attributes with a suffix _Baz that has the value 12
They may have siblings whose entire name is whatever came before the suffice
If they do, then that sibling is the node I want to match, provided the _Baz attribute has the value of 12
Another option :
//item[substring-after(local-name(./#*[last()]),"_")="baz" and ./#*[last()]="12"][local-name(./#*[1])=substring-before(local-name(./#*[last()]),"_")]
Shortest form :
//item[#foo or #bar][#bar_baz="12" or #foo_baz="12"]
EDIT : Massive and horrible XPath here, but it should work. It supports up to 5 attributes per item and regardless the position of these attributes inside each item tag.
//item[contains(local-name(#*[1]),"_baz") and #*[1]=12][local-name(#*[1])=substring-before(local-name(#*[1]),"_")]|//item[contains(local-name(#*[1]),"_baz") and #*[1]=12][local-name(#*[3])=substring-before(local-name(#*[1]),"_")]|//item[contains(local-name(#*[1]),"_baz") and #*[1]=12][local-name(#*[4])=substring-before(local-name(#*[1]),"_")]|//item[contains(local-name(#*[1]),"_baz") and #*[1]=12][local-name(#*[5])=substring-before(local-name(#*[1]),"_")]|//item[contains(local-name(#*[2]),"_baz") and #*[2]=12][local-name(#*[1])=substring-before(local-name(#*[2]),"_")]|//item[contains(local-name(#*[2]),"_baz") and #*[2]=12][local-name(#*[3])=substring-before(local-name(#*[2]),"_")]|//item[contains(local-name(#*[2]),"_baz") and #*[2]=12][local-name(#*[4])=substring-before(local-name(#*[2]),"_")]|//item[contains(local-name(#*[2]),"_baz") and #*[2]=12][local-name(#*[5])=substring-before(local-name(#*[2]),"_")]|//item[contains(local-name(#*[3]),"_baz") and #*[3]=12][local-name(#*[1])=substring-before(local-name(#*[3]),"_")]|//item[contains(local-name(#*[3]),"_baz") and #*[3]=12][local-name(#*[3])=substring-before(local-name(#*[3]),"_")]|//item[contains(local-name(#*[3]),"_baz") and #*[3]=12][local-name(#*[4])=substring-before(local-name(#*[3]),"_")]|//item[contains(local-name(#*[3]),"_baz") and #*[3]=12][local-name(#*[5])=substring-before(local-name(#*[3]),"_")]|//item[contains(local-name(#*[4]),"_baz") and #*[4]=12][local-name(#*[1])=substring-before(local-name(#*[4]),"_")]|//item[contains(local-name(#*[4]),"_baz") and #*[4]=12][local-name(#*[3])=substring-before(local-name(#*[4]),"_")]|//item[contains(local-name(#*[4]),"_baz") and #*[4]=12][local-name(#*[4])=substring-before(local-name(#*[4]),"_")]|//item[contains(local-name(#*[4]),"_baz") and #*[4]=12][local-name(#*[5])=substring-before(local-name(#*[4]),"_")]|//item[contains(local-name(#*[5]),"_baz") and #*[5]=12][local-name(#*[1])=substring-before(local-name(#*[5]),"_")]|//item[contains(local-name(#*[5]),"_baz") and #*[5]=12][local-name(#*[3])=substring-before(local-name(#*[5]),"_")]|//item[contains(local-name(#*[5]),"_baz") and #*[5]=12][local-name(#*[4])=substring-before(local-name(#*[5]),"_")]|//item[contains(local-name(#*[5]),"_baz") and #*[5]=12][local-name(#*[5])=substring-before(local-name(#*[5]),"_")]
Working sample (4 nodes selected) :
Strictly in terms of xpath, this expression
//Item[attribute::*[contains(local-name(), '_Baz')]='12'][attribute::*[local-name()='Foo'] | attribute::*[local-name()='Bar']]
should get you your desired output.

How to get parent element with attribute using xpath

I have posted sample XML and expected output kindly help to get the result.
Sample XML
<root>
<A id="1">
<B id="2"/>
<C id="2"/>
</A>
</root>
Expected output:
<A id="1"/>
You can formulate this query in several ways:
Find elements that have a matching attribute, only ascending all the time:
//*[#id=1]
Find the attribute, then ascend a step:
//#id[.=1]/..
Use the fn:id($id) function, given the document is validated and the ID-attribute is defined as such:
/id('1')
I think it's not possible what you're after. There's no way of selecting a node without its children using XPATH (meaning that it'd always return the nodes B and C in your case)
You could achieve this using XQuery, I'm not sure if this is what you want but here's an example where you create a new node based on an existing node that's stored in the $doc variable.
declare variable $doc := <root><A id="1"><B id="2"/><C id="2"/></A></root>;
element {fn:node-name($doc/*)} {$doc/*/#*}
The above returns <A id="1"></A>.
is that what you are looking for?
//*[#id='1']/parent::* , similar to //*[#id='1']/../
if you want to verify that parent is root :
//*[#id='1']/parent::root
https://en.wikipedia.org/wiki/XPath
if you need not just parent - but previous element with some attribute: Read about Axis specifiers and use Axis "ancestor::" =)

Simplify specific XPath expression

I would like to know if the following XPath expression can be simplified:
//map[requester/#type='2' and requester/code]
Some test data:
<root>
<map>
<requester type="2">
<code>a</code>
<code>b</code>
</requester>
</map>
...
</root>
My objective is to get only map elements which have at least one requester with type attribute and value '2' and also have at least one code element.
For your use case, this is probably as simple as it could be. However, it doesn't match what you are describing doing.
Here you are selecting map elements where
There is a requester element with type attribute equal to 2
There is a requester element with a code element
The requester elements in (1) and (2) are not necessarily the same
For example, the map element in the following is selected:
<root>
<map>
<requester type="2"/>
<requester>
<code>a</code>
</requester>
</map>
</root>
If you want the elements in (1) and (2) to be the same, you should use (simplified slightly at the suggestion of kjhughes)
//map[requester[#type='2']/code]
Here we select all map elements which have a requester element which in turn has an attribute type with a value of 2 and a code element.

XPath Wildcard -- Any Node Name, Must have Specific Attribute Value

I am having difficulty figuring out an XPath query that would allow me to return nodes based on the value of the Program attribute in the example below. For example, I would like to be able to search all nodes for a value of the Program attribute = "011.pas". I tried /Items/*[Program="012.pas"] and also /Items/Item*[Program="01.pas"] but neither works. What is the correct expression?
<Items>
<Item0 Program="01.pas"></Item0>
<Item1 Program="011.pas"></Item1>
</Items>
The attribute is selected with #Program, the child elements of the Items element with /Items/*, so you want /Items/*[#Program = '011.pas'].
Try this :
/items/*[#Program='011.pas']

XPATH expression that Matches on the attribute value "true"

I have some XML like this:
<engine-set>
<engine host-ref="blah1.com">
<property name="foo" value="true"/>
<property name="bar" value="true"/>
</engine>
<engine host-ref="blah2.com">
<property name="foo" value="true"/>
<property name="bar" value="false"/>
</engine>
</engine-set>
I want to match on all engine elements that have a child node property with a name equal to "bar" and and value equal to "true". I'm finding the fact that "true" appears in my XML is causing my condition to always evaluate to true in an XPath expression. Is there a way around? I'm using Python and lxml.
EDIT:
My xpath expression is (that isn't working) is:
//engine[(property/#name='bar' and property/#value="true")]
Thanks,
I want to match on all engine elements
This is:
//engine
that have a child node property
Now this becomes:
//engine[property]
with a name equal to "bar"
Still more specific:
//engine[property[#name = 'bar']]
and and value equal to "true".
Finally:
//engine[property[#name = 'bar' and #value = 'true']]
So you're saying
//engine[property[#name='bar' and #value='true']]
gives you too many results? Because for me it gives just one.
What XPath expression did you try?
The following seems to work well in getting "blah1.com" but not "blah2.com":
//engine[property[#value="true"][#name="bar"]]
Remember that you need to encase your parameter test values in quotes.

Resources