Xpath get distinct nodes using preceding-sibling - xpath

I need to get distinct values //name() withount distinct-values(//*/name())
I tried do like this, but its dosent work.
//*/name()[.!=//preceding-sibling::*]
How can i repair it?

Using XPath 1.0, to get the distinct values
For name attribute,
/*/*[not(#name = preceding::*/#name)]
For node name,
/*/*[not(name() = preceding::*/name())]
My Sample XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<friend1 name="abc"/>
<friend2 name="def"/>
<friend3 name="abc"/>
<friend1 name="abcd"/>
<friend5 name="abcd"/>
<friend6 name="xyz"/>
<friend8 name="789"/>
<friend0 name="pqr"/>
<friend9 name="lmn"/>
<friend2 name="lmn"/>
<friend5 name="123"/>
<friend7 name="456"/>
<friend12 name="789"/>
</root>

Related

Select only the first element 'libro' that don't have the attribute 'paginas'

Given the attached xml file:
<?xml version="1.0" encoding="UTF-8"?>
<biblioteca>
<libro paginas="100">
<titulo>Los bandidos de la playa</titulo>
<autor>Rosario Lopez</autor>
<isbn>1231-123-123-2233</isbn>
<precio>123</precio>
<fechaPublicacion año="1920"/>
</libro>
<libro paginas="200">
<titulo>Indagaciones publicas</titulo>
<autor sexo="M">Aurora Laspitas</autor>
<isbn>1231-222-3333-4444</isbn>
<precio>40</precio>
<fechaPublicacion año="2000"/>
</libro>
<libro>
<titulo>libro barato</titulo>
<autor sexo="H">Cipriano Lopez</autor>
<isbn>1231-2343 32333333</isbn>
<precio>10</precio>
<fechaPublicacion año="1978"/>
</libro>
<libro>
<titulo>libro de ayuda</titulo>
<autor sexo="H">Zacarias Sanchez</autor>
<isbn>1231-2343 32333333</isbn>
<precio>10</precio>
<fechaPublicacion año="1999"/>
</libro>
</biblioteca>
I want to select only the first element 'libro' that doesn't have the attribute 'paginas'.
Here is my try that doesn't work:
/biblioteca/libro[not(#paginas)]/../libro[1]
Thanks
You can use this XPath-1.0 expression:
/biblioteca/libro[not(#paginas)][1]

Ruby nokogiri attribute selector in XML file

this is the xml file:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:putResponse
xmlns:ns1="urn:DmsManagerClient">
<result xsi:type="xsd:string">
<?xml version="1.0" encoding="ISO-8859-1"?>
<MESSAGE ID="11c73b9e-687c-4300-baba-b743c26f7c83" TYPE="CUSDMS">
<DELIVERY>
<FROM>
<SENDER>0072000</SENDER>
<SERVICE>eService</SERVICE>
<DATE>2019-03-08T12:27:25</DATE>
</FROM>
<TO>
<DEALER DEALERCODE="0072000" MARKETCODE="1000"/>
</TO>
</DELIVERY>
<CONTENT>
<dms:ComplexResponse ErrorCode="430" ErrorDescription="null : PrivacyUE Mancante" Return="false"
xmlns:dms="http://dmsmanagerservice">
<dms:Element Name="DMSVERSION">2.7</dms:Element>
</dms:ComplexResponse>
</CONTENT>
</MESSAGE>
</result>
</ns1:putResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I am coding with Ruby and I used Nokogiri and the method xpath to extrapole the "CONTENT" of the file
this is the code:
def extrapolate_error(xml)
doc = Nokogiri::XML(File.open(xml))
doc.xpath('//CONTENT')
end
and this is the result:
[#<Nokogiri::XML::Element:0x1c5ba78 name="CONTENT" children=[
#<Nokogiri::XML::Text:0x1c5b940 "\n">,
#<Nokogiri::XML::Element:0x1c5b8bc name="ComplexResponse" namespace=#<Nokogiri::XML::Namespace:0x1c5b88c prefix="dms" href="http://dmsmanagerservice">
attributes=[
#<Nokogiri::XML::Attr:0x1c5b874 name="ErrorCode" value="430">,
#<Nokogiri::XML::Attr:0x1c5b868 name="ErrorDescription" value="null : PrivacyUE Mancante">,
#<Nokogiri::XML::Attr:0x1c5b85c name="Return" value="false">]
children=[#<Nokogiri::XML::Text:0x1c5b118 "\n">,
#<Nokogiri::XML::Element:0x1c5b094 name="Element" namespace=#<Nokogiri::XML::Namespace:0x1c5b88c prefix="dms" href="http://dmsmanagerservice">
attributes=[#<Nokogiri::XML::Attr:0x1c5b058 name="Name" value="DMSVERSION">]
children=[#<Nokogiri::XML::Text:0x1c5abe4 "2.7">]>,
#<Nokogiri::XML::Text:0x1c5aaac "\n">]>,
#<Nokogiri::XML::Text:0x1c5a974 "\n">]>]
Now I need to enter in it and select some attributes.
In the specific I need this:
name="ErrorCode" value="430"
name="ErrorDescription" value="null : PrivacyUE Mancante"
I do not know how to procceed. Can you help me?
The following should work for you assuming the dms namespace is always the same
doc.xpath('//CONTENT/dms:ComplexResponse', dms: 'http://dmsmanagerservice')
.xpath('#ErrorCode | #ErrorDescription')
.each_with_object({}) do |e,obj|
obj[e.name] = e.text
end
#=> {"ErrorCode"=>"430", "ErrorDescription"=>"null : PrivacyUE Mancante"}
You already understand how you got to //CONTENT so from there we use dms:ComplexResponse to navigate deeper but since this is namespaced we have to provide the namespace reference e.g. dms: 'http://dmsmanagerservice'.
Then we select the attributes we are interested in #ErrorCode and #ErrorDescription.
In XPath the pipe | means UNION (think AND) so we want to select both.
Then we are just building a Hash using the name as the key and the text as the value.
XPath Cheatsheet - Useful resource if you need additional reference
Update
You asked about conditionals so this is what I would propose
ndoc = Nokogiri::XML(doc)
namespaces = ndoc.collect_namespaces
response = ndoc.xpath("//CONTENT/dms:ComplexResponse", namespaces)
if response.xpath("self::node()[#ErrorCode != '' and #ErrorDescription != '']").any?
response.xpath("#ErrorCode | #ErrorDescription")
.each_with_object({}) do |e,obj|
obj[e.name] = e.text
end
else
response.xpath('dms:Element/#Name | dms:Element/text()',namespaces)
.each_slice(2)
.map {|s| s.map(&:text)}.to_h
end
This checks to see if there is an ErrorCode and and ErrorDescription if so then Hash as originally proposed. If Not then it returns all the dms:Elements as a Hash so {"DMSVERSION"=>"2.7"} in this case Functional Example

How to use following in Xpath to get siblings in a Tag

I have following Structure: I am trying to build a robust method to extract the elements of FT1_19_0 of the FT1_19 Tag in the order they appear. However
in my results the elements are rearranged. How can i get my result in correct order.
//*/FT1_19/FT1_19_0[contains(../FT1_19_2,'I10') and
not(.=../following::FT1_19/FT1_19_0)]
The Result(Rearranged)
X50.0XXA
M76.891
M17.11
M23.303
<?xml version="1.0" encoding="UTF-8"?>
<root>
<FT1>
<FT1_1>1</FT1_1>
<FT1_4>20180920130000</FT1_4>
<FT1_5>20180924110101</FT1_5>
<FT1_6>CG</FT1_6>
<FT1_7>99203</FT1_7>
<FT1_9/>
<FT1_10>1.00</FT1_10>
<FT1_13>NPI</FT1_13>
<FT1_16>
<FT1_16_1>Gavin, Matthew, MD</FT1_16_1>
<FT1_16_3>22</FT1_16_3>
</FT1_16>
<FT1_19 NO="1">
<FT1_19_0>M76.891</FT1_19_0>
<FT1_19_2>I10</FT1_19_2>
</FT1_19>
<FT1_19 NO="2">
<FT1_19_0>M17.11</FT1_19_0>
<FT1_19_2>I10</FT1_19_2>
</FT1_19>
<FT1_19 NO="3">
<FT1_19_0>M23.303</FT1_19_0>
<FT1_19_2>I10</FT1_19_2>
</FT1_19>
<FT1_19 NO="4">
<FT1_19_0>X50.0XXA</FT1_19_0>
<FT1_19_2>I10</FT1_19_2>
</FT1_19>
</FT1>
</root>
Use this if you are using java:
List<WebElement> list = driver.findElements(By.xpath("//ft1_19//following::ft1_19_0"));
for(WebElement we:list) {
System.out.println(we.getText());
}

Can't address XML attribute thought XPath in Ruby (using Nokogiri)

I'm trying to filter xml file to get nodes with certain attribute. I can successfully filter by node (ex. \top_manager), but when I try \\top_manager[#salary='great'] I get nothing.
<?xml version= "1.0"?>
<employee xmlns="http://www.w3schools.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="employee.xsd">
<top_manager>
<ceo salary="great" respect="enormous" type="extra">
<fname>
Vasya
</fname>
<lname>
Pypkin
</lname>
<hire_date>
19
</hire_date>
<descr>
Big boss
</descr>
</ceo>
<cio salary="big" respect="great" type="intro">
<fname>
Petr
</fname>
<lname>
Pypkin
</lname>
<hire_date>
25
</hire_date>
<descr>
Resposible for information security
</descr>
</cio>
</top_manager>
......
How I need to correct this code to get what I need?
require 'nokogiri'
f = File.open("employee.xml")
doc = Nokogiri::XML(f)
doc.xpath("//top_manager[#salary='great']").each do |node|
puts node.text
end
thank you.
That's because salary is not attribute of <top_manager> element, it is the attribute of <top_manager>'s children elements :
//xmlns:top_manager[*[#salary='great']]
Above XPath select <top_manager> element having any of it's child element has attribute salary equals "great". Or if you meant to select the children (the <ceo> element in this case) :
//xmlns:top_manager/*[#salary='great']

NSXMLDocument, nodesForXPath with namespaces

I want to get a set of elements from a xml-file, but as soon the the elements involve namespaces, it fails.
This is a fragment of the xml file:
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
version="1.0" creator="Groundspeak Pocket Query"
xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd http://www.groundspeak.com/cache/1/0 http://www.groundspeak.com/cache/1/0/cache.xsd"
xmlns="http://www.topografix.com/GPX/1/0">
<name>My Finds Pocket Query</name>
<desc>Geocache file generated by Groundspeak</desc>
<author>Groundspeak</author>
<email>contact#groundspeak.com</email>
<time>2010-09-15T16:18:55.9846906Z</time>
<keywords>cache, geocache, groundspeak</keywords>
<bounds minlat="41.89687" minlon="5.561883" maxlat="70.669967" maxlon="25.74735" />
<wpt lat="62.244933" lon="25.74735">
<time>2010-01-11T08:00:00Z</time>
<name>GC22W1T</name>
<desc>Kadonneet ja karanneet by ooti, Traditional Cache (1.5/2)</desc>
<url>http://www.geocaching.com/seek/cache_details.aspx?guid=4af28fe9-401b-44df-b058-5fd5399fc083</url>
<urlname>Kadonneet ja karanneet</urlname>
<sym>Geocache Found</sym>
<type>Geocache|Traditional Cache</type>
<groundspeak:cache id="1521507" available="True" archived="False" xmlns:groundspeak="http://www.groundspeak.com/cache/1/0">
<groundspeak:name>Kadonneet ja karanneet</groundspeak:name>
<groundspeak:placed_by>ooti</groundspeak:placed_by>
<groundspeak:owner id="816431">ooti</groundspeak:owner>
<groundspeak:type>Traditional Cache</groundspeak:type>
<groundspeak:container>Small</groundspeak:container>
<groundspeak:difficulty>1.5</groundspeak:difficulty>
<groundspeak:terrain>2</groundspeak:terrain>
<groundspeak:country>Finland</groundspeak:country>
<groundspeak:state>
</groundspeak:state>
<groundspeak:short_description html="True">
</groundspeak:short_description>
<groundspeak:encoded_hints>
</groundspeak:encoded_hints>
<groundspeak:travelbugs />
</groundspeak:cache>
</wpt>
</gpx>
I want to get all the grounspeak:cache elements, but neither //groundspeak:cache nor //cache seems to return anything.
NSArray *caches = [self.xml nodesForXPath:#"//cache" error:&error];
Any clue?
Edit: Are there any cocoa-based software out there, where I can load my xml and test different xpaths? I'm quite new to objective-c and cocoa, so it would be nice to check that it is really my xpath that is wrong..
This //cache means: a descendant element under no namespace (or empty namespace)
Your groundspeak:cache element is under a namespace URI http://www.groundspeak.com/cache/1/0.
So, if you can't declare a namespace-prefix binding (I think you can't with cocoa...), you could use this XPath expression:
//*[namespace-uri()='http://www.groundspeak.com/cache/1/0' and
local-name()='cache']
If you don't want to be so strict about namespace...
//*[local-name()='cache']
But this last is a bad practice, because you could end up selecting wrong nodes, and because when dealing with XML, your tool should support namespaces.
As proof, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:copy-of select="//*[namespace-uri() =
'http://www.groundspeak.com/cache/1/0' and
local-name() = 'cache']"/>
</xsl:template>
</xsl:stylesheet>
Output:
<groundspeak:cache id="1521507" available="True" archived="False"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.topografix.com/GPX/1/0"
xmlns:groundspeak="http://www.groundspeak.com/cache/1/0">
<groundspeak:name>Kadonneet ja karanneet</groundspeak:name>
<groundspeak:placed_by>ooti</groundspeak:placed_by>
<groundspeak:owner id="816431">ooti</groundspeak:owner>
<groundspeak:type>Traditional Cache</groundspeak:type>
<groundspeak:container>Small</groundspeak:container>
<groundspeak:difficulty>1.5</groundspeak:difficulty>
<groundspeak:terrain>2</groundspeak:terrain>
<groundspeak:country>Finland</groundspeak:country>
<groundspeak:state></groundspeak:state>
<groundspeak:short_description html="True"></groundspeak:short_description>
<groundspeak:encoded_hints></groundspeak:encoded_hints>
<groundspeak:travelbugs />
</groundspeak:cache>
You need to add a new namespace attribute to the root node of your document, defining a prefix that you can use when querying the children:
NSXMLDocument *xmldoc = ...
NSXMLElement *namespace = [NSXMLElement namespaceWithName:#"mns" stringValue:#"http://mynamespaceurl.com/mynamespace"];
[xmldoc.rootElement addNamespace:namespace];
then when you query things later, you can use that prefix to refer to the namespace:
NSArray * caches = [xmldoc.rootElement nodesForXPath:#"//mns:caches" error:&error];
//groundspeak:cache should work. You might need a namespace-uri setting as well

Resources