How to write XML path expression for the following code? - xpath

Write an expression that selects all the items ISBN and TITLE that their return
is “3/12/2017”
Code -
<itemlist>
<item>
<title>
The Bonfire of the Vanities
</title>
<type>Book</type>
<authors>
<author>Wolfe, Tom</author>
</authors>
<subjects>
<subject>New York</subject>
<subject>Race Relations</subject>
</subjects>
<isbn>0374115370</isbn>
<location>Adult</location>
<collection>Fiction</collection>
<status return="3/12/2017">Checked Out</status>
</item>
</itemlist>

//itemlist/item[status/#return='3/12/2017']/(isbn|title)
Find item elements whose status element child has return attribute that is "3/12/2017", then take those items' children that are isbn or title elements.

Related

Getting a node value depending on an another value at the same level

For each "item" node in the following XML structure, I want to select the corresponding "title" (the text nodes are located at the same level as the item nodes, I can't modify it).
The link between those two nodes will be the "ref" node which is a kind of primary key between the "item" and "title" trees.
Is it possible in XPath ?
I think it should be something like this: //root/item/../title[ref/text()=??????]/label
An example :
<root>
<item>
<ref>ITEM001</ref>
</item>
<item>
<ref>ITEM002</ref>
</item>
<item>
<ref>ITEM003</ref>
</item>
<item>
<ref>ITEM004</ref>
</item>
<title>
<ref>ITEM002</ref>
<label>Hello world!</label>
</title>
<title>
<ref>ITEM003</ref>
<label>Goodbye world!</label>
</title>
<title>
<ref>ITEM007</ref>
<label>This is a test!</label>
</title>
<title>
<ref>ITEM0010</ref>
<label>No this a question!</label>
</title>
</root>
The result would be:
ITEM001: empty
ITEM002: Hello world!
ITEM003: Goodbye world!
ITEM004: empty
Thanks in advance for your help.
I assume if you follow below steps you would get you desired output.
Step 1: Iterate through all the Items tag and capture all in an array.
Step 2: Using a loop on array use the below XPath to find the respective label value.
//title[contains(.,'')]/label.
Step 3: If you find an matching element then get the text of the label to display on console else display empty.

xpath multiple scope : select data from multiple trees

Problem : select data based on node which is in another part of the tree
How to select data in rows of column with label = "status"?
Data should be "data2" from /result/rows/items/item/c/items/item/v
and selection should be based on label='status' i.e. /result/cols/items/item/label=status
In the XML below "status" is column number 2, but it may change to column number 1, so the according XPath should return data of column no.1
<result>
<cols>
<items>
<item>
<id>c1</id>
<label>result</label>
<type>string</type>
</item>
<item>
<id>c2</id>
<label>status</label>
<type>string</type>
</item>
<item>
<id>c3</id>
<label>message</label>
<type>string</type>
</item>
</items>
</cols>
<rows>
<items>
<item>
<c>
<items>
<item>
<v>data1</v>
</item>
<item>
<v>data2</v>
</item>
<item>
<v />
</item>
</items>
</c>
</item>
</items>
</rows>
</result>
Your description is not very clear to understand.
I got it like this:
There is a node which indicates the column. The label of the column is "status". You get this label with
/result/cols/items/item/label[text()='status']
But that's not what you want. First, you want to find out at which position that column is. You get that position with
count(/result/cols/items/item[label/text()='status']/preceding-sibling::*)+1
But that's still not what you want. Based on that information, you want to select the actual data within rows. You get a row with
/result/rows/items/item/c/items/item[2]/v/text()
But you don't always want the second column of the row, you want the row based on the column index determined earlier. So you need to combine both:
/result/rows/items/item/c/items/item[count(/result/cols/items/item[label/text()='status']/preceding-sibling::*)+1]/v/text()
The last expression does not contain any hard coded indexes and uses only the column header text "status" to determine where the data is. In your example, it returns data2. If you change the column header text to "result", it gives you data1.
I'm not sure what you are asking for. But if you are looking for an Expression, which will get the "type" text for all labels with the text "status"
//label[text()='status']/following-sibling::type

Ruby + Nokogiri + Xpath navigate Node_Set

<Item id="item0">
<Links>
<FirstLink id="link1" target="one"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content</String>
</Data>
</Item>
<Item id="item1">
<Links>
<FirstLink id="link1" target="two"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content</String>
</Data>
</Item>
I have created a Nokogiri-NodeSet with this structure, i.e. a list of items with links and data children.
How can I filter any items that don't match a certain value in the 'target'-attribute of <FirstLink>?
Actually, what I want in the end is to extract the <Data><String>-Content of every <Item> that matches a certain value in it's <FirstLink> "Target"-Attribute.
I've tried several approaches already but I'm at a loss as to how to identify an element by an attribute of it's grandchild, then extracting the content of this grandchild's parent's sibling, X(.
We can build up an XPath expression to do this. Assuming we are starting from the whole XML document, rather than the node-set you already have, something like
//Item
will select all <Item> elements (I’m guessing you already have something like that to get this node-set).
Next, to select only those <Item> elements which have <Links><FirstLink> where FirstLink has a target attribute value of one:
//Item[Links/FirstLink[#target='one']]
and finally to select the Data/String children of those nodes:
//Item[Links/FirstLink[#target='one']]/Data/String
So with Nokogiri you could use something like this (where doc is your parsed document):
doc.xpath("//Item[Links/FirstLink[#target='one']]/Data/String")
or if you want to use the node-set you already have you can use a relative expression:
nodeset.xpath("self::Item[Links/FirstLink[#target='one']]/Data/String")
I completely didn't understand what your goal is. But using a guess, I am trying to show you, how to proceed in this case :
require 'nokogiri'
doc = Nokogiri::XML <<-xml
<Item id="item0">
<Links>
<FirstLink id="link1" target="one"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content1</String>
</Data>
</Item>
<Item id="item1">
<Links>
<FirstLink id="link1" target="two"/>
<SecondLink id="link2" target="two"/>
</Links>
<Data>
<String>content2</String>
</Data>
</Item>
xml
#xpath method with the expression "//Item", will select all the Item nodes. Then those Item nodes will be passed to the #reject method to select only those nodes, that has a node called Links having the target attribute value is "one". If any of the links, either FirstLink or SecondLink has the target attribute value "one", for that nodes grandparent node Item will be selected.
node.at("//Links/FirstLink")['target'] will give you the string say "one" which is a value of target attribute of the node, FirstLink of first Item nodes , then "two" from the second Item node. The part ['any vaue'] in node.at("//Links/FirstLink")['target']['any vaue'] is a call to the String#[] method.
Remember below approach will give you the flexibility of the use regular expression too.
nodeset = doc.xpath("//Item").reject do |node|
node.at("//Links/FirstLink")['target']['any vaue']
end
Now nodeset contains only the required Item nodes. Now I use #map, passing each item node inside it to collect the content of the String node. Then #at method with an expression //Data/String, will select the String node. Then #text, will give you the content of each String node.
nodeset.map { |n| n.at('//Data/String').text } # => ["content1"]

Selecting a XML node with LINQ, and modifying

I've got the following XML:
<Config>
<Book>
<Name> Book Name #1 </Name>
<Available In>
<Country>US</Country>
<Country>Canada</Country>
</Available In>
</Book>
</Config>
I need to find all instances of Book which are available in a specific country, and then introduce a node underneath "Available In". My selection statement fails anytime I add the where statement:
XElement xmlFile = XElement.Load(xmlFileLocation);
var q = (from c in xmlFile.Elements(“Book”)
where c.Elements(Country).Value == "Canada"
select c;
.Value can't be resolved, and toString give me the entire subnode in stringform. I need to select all books in a particular country so that I can then update them all to include a new locale node, ex:
<Config>
<Book>
<Name> Book Name #1 </Name>
<Available In>
<Country>US</Country>
<Country>Canada</Country>
</Available In>
<LocaleIDs>
<LocalID> 3066 </LocaleID>
<LocaleIDs>
</Book>
</Config>
Thanks for your help!
You're trying to use Value on the result of calling Elements which returns a sequence of elements. That's not going to work - it doesn't make any sense. You want to call it on a single element at a time.
Additionally, you're trying to look for direct children of Book, which ignores the Available In element, which isn't even a valid element name...
I suspect you want something like:
var query = xmlFile.Elements("Book")
.Where(x => x.Descendants("Country")
.Any(x => (string) x == "Canada"));
In other words, find Book elements where any of the descendant Country elements has a text value of "Canada".
You'll still need to fix your XML to use valid element names though...

Retrieve from a particular level in xml tree

I have the decision tree structure as such like the below ,
<?xml version="1.0" encoding="utf-8" ?>
<root>
outlook
<item>
sunny
<root>
humidity
<item>
high
<leaf>no</leaf>
</item>
<item>
normal
<leaf>yes</leaf>
</item>
</root>
</item>
<item>
overcast
<leaf>yes</leaf>
</item>
<item>
rain
<root>
wind
<item>
weak
<leaf>yes</leaf>
</item>
<item>
strong
<leaf>no</leaf>
</item>
</root>
</item>
</root>
I wanted a LINQ query which will display ,
Outlook : Sunny , Overcast , Rain
That is , root value plus the tree's first level of children's value. Again selecting a particular item like ,
Sunny
It must iteratively give its successive root value and root's first level of children. That is ,
Humidity : High , Normal
And finally arrive at the decision , YES or NO.
Am working on the same but some inputs would help me further.
Could you try the following:
var doc = XDocument.Parse(xml);
foreach (var r in doc.Descendants("root"))
{
var values = r.Elements("item").Select(s => (s.FirstNode as XText).Value.Trim()).ToList();
string.Concat((r.FirstNode as XText).Value.Trim(), ": ", string.Join(",", values)).Dump("decision");
}
This is output I get:
decision
outlook: sunny,overcast,rain
decision
humidity: high,normal
decision
wind: weak,strong
I used LinqPad, hence the Dump method call which spits the output to the LinqPad console. Hope this helps.

Resources