Is it possible to count the following TRUE nodes (with different names) with XPath in the XML below?
<my:templateBase>
<my:executive>true</my:executive>
<my:seniorManager>false</my:seniorManager>
<my:manager>false</my:manager>
<my:supervisor>false</my:supervisor>
<my:directReport>false</my:directReport>
</my:templateBase>
I can't seem to work out the XPATH, e.g. count(templateBase/*[?=true()]).
Find all elements which contain the text "true" and count them.
count(//my:templateBase/*[text() = 'true'])
Make sure to correctly register the namespace or use * instead of my as wildcard namespace, for example
count(//*:templateBase/*[text() = 'true'])
I do not know Infopath, maybe it also just omits namespaces.
If you also want to search subnodes for the text "true", use
count(//my:templateBase//*[text() = 'true'])
The xpath is
count(//my:templateBase/*[. = 'true']
Please be aware that you abviously have some namespace my in your XML. Maybe you have to make your processor aware of this namespaces. You can also use a wildcard, i.e. *:templateBase
Related
I have the following XML -
<d><m:properties xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
<d:AllTexts/>
<d:BomFlag/>
<d:OrderNumber>9489</d:OrderNumber>
<d:LineNumber>000000</d:LineNumber>
<d:VcFlag>Y</d:VcFlag>
<d:PricingFlag/>
<d:TextType>H</d:TextType>
<d:TextId>ZC01</d:TextId>
<d:TextLineNo>1</d:TextLineNo>
<d:TextLine>ecom header text 1</d:TextLine>
and trying to retrieve the TextLine nodelist as based on TextId = ZC01 -
<TextLine>ecom header text1</TextLine>
when I applied the xpath as --> //m:properties[d:TextId = 'ZC01']/d:TextLine
I get the output as -
<d:TextLine xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">ecom header text 1</d:TextLine>
how can I remove the prefix and namespace? I tried using local-name(), but that didn't work
May be used it wrong way.
Thank you for your help!
Thanks
Sugata
XPath is a selection language: it can only retrieve nodes that are actually there, it can't change them in any way. If the selected element has a prefix and namespace in the original, then it will have a prefix and namespace in the result.
However, you need to distinguish what the XPath selects (a node) from the way it the result is displayed. This depends on the application that is evaluating the XPath. The two popular ways of displaying a node selected by an XPath expression are (a) by serialising the node as XML (which is what we see in your case), and (b) by showing a path to the selected node, such as /d/m:properties/d:TextLine. You haven't told us how you are evaluating the XPath expression or displaying its result, and you may have options here.
But perhaps you should consider XSLT or XQuery, which (unlike XPath) allow you to construct new XML that differs from your original.
If I want to grab a currencies rate, say "USD", given a certain time, say "2015-02-09", how would I go about doing this?
I tried the following:
/gesmes:Envelope/def:Cube/def:Cube[#time="2014-11-19"]/def:Cube[#currency="USD"]/#rate
Though I suppose due a lack of understanding this is wrong, well at least, I know it is wrong because Nokogiri does not run it.
http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml
EDIT:
I'm going to go ahead and guess that I am not correctly using Nokogiri and XPath.
#doc = Nokogiri::XML(File.open("exchange_data.xml"))
#values = #doc.xpath('XPATH HERE')
#values.each {|i| puts i}
I have read the tutorial, and managed to get it working for other xml files, but this one seems harder to crack.
require 'nokogiri'
doc = Nokogiri::XML(File.open("xml4.xml"))
target_date = "2015-02-09"
target_currency = 'USD'
xpaths = [
"//gesmes:Envelope",
"/xmlns:Cube",
"/xmlns:Cube[#time='#{target_date}']",
"/xmlns:Cube[#currency='#{target_currency}']",
]
xpath = xpaths.join
target_cube = doc.at_xpath(xpath)
puts target_cube.attribute('rate')
--output:--
1.1297
Response to comment:
Your root tag:
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01"
xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
...declares two namespaces with xmlns, which stands for xml namespace. The namespace:
xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01"
declares that any child tag whose name is prefixed by gesmes, e.g.:
<gesmes:subject>
...
</gesmes:subject>
will actually have a tag name that incorporates the specified url into the tag name, something like this:
<http://www.gesmes.org/xml/2002-08-01:subject>
...
</http://www.gesmes.org/xml/2002-08-01:subject>
The reason you would want to use a namespace is to create a unique name for the Cube tag, so that it doesn't clash with another xml document's Cube tag.
The second namespace declaration:
xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref"
is a default namespace declaration. It declares that any child tag that does not specify a prefix will have the specified url incorporated into its tag name. So a tag like this:
<Cube>
...
</Cube>
becomes something like this:
<http://www.ecb.int/vocabulary/2002-08-01/eurofxref:Cube>
...
</http://www.ecb.int/vocabulary/2002-08-01/eurofxref:Cube>
However, it would be unwieldy to have to write a tag name like that in your xpaths, so in place of the url you instead use the shortcut xmlns:
/xmlns:Cube
This might be due to the namespaces in this document:
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
To test this hypothesis, apply the following XPath expression:
/*[local-name() = 'Envelope']/*[local-name() = 'Cube']/*[local-name() = 'Cube'][#time="2014-11-19"]/*[local-name() = 'Cube'][#currency="USD"]/#rate
and let me know what you get. If you are otherwise correctly using XPath, you should end up with:
rate="1.2535"
If not, you are not using the XPath facilities of Nokogiri correctly, and then you'd really need to show all of your Ruby code to get help.
EDIT
Responding to a comment:
I look forward to seeing some examples added to your answer, so that I can learn something new about xml namespaces. – 7stud
7stud already gave the correct answer, I'll only add info I think is missing from this answer.
Explicit namespaces
First of all, if a namespace URI is explicitly present on an element, the correct syntax uses curly brackets, both for a prefixed and default namespace:
<{http://www.gesmes.org/xml/2002-08-01}subject>
Internally, this is how namespaces could be represented on elements (although some applications have other ways to associate elements with namespaces). Prefixes and default namespaces are there to simplify this process.
Namespaces in Nokogiri
Prefixes (gesmes:) do not have any inherent meaning. They can be associated with an arbitrary namespace URI and every document can use gesmes: to mean something different. Namespace declarations are not available to an XPath engine per se - usually, if you'd like to use a prefix in an XPath expression, you need to declare this namespace again for the XPath processor.
Yet, Nokogiri tries to simplify namespace handling for you by redeclaring namespace declarations found on the root element of the input document. This is important because it allows you to reuse the prefixes declared on the root element of the input without actually declaring the namespace. For default namespaces declared on the root element that do not have a prefix, Nokogiri has defined a special syntax:
xmlns:Cube
Namespaces that are present in the document, but declared on an element other than the root element:
<root>
<child xmlns:gesmes="http://other.com"/>
</root>
must be explicitly declared in Nokogiri:
#doc.xpath('//other:Cube', 'other' => 'http://other.com/')
What's wrong with your original code?
Your code:
/gesmes:Envelope/def:Cube/def:Cube[#time="2014-11-19"]/def:Cube[#currency="USD"]/#rate
does not work because you are using an unknown prefix def:. This prefix is not declared on the root element of the input, and neither did you declare it with Nokogiri. The Cube elements are in the default namespace, and, as we have seen, the correct way to address them is
/gesmes:Envelope/xmlns:Cube
and so on, 7stud gave you the correct answer.
Am automating things using Selenium. Need your help to handle Dynamic Xpath as below:
Driver.findElement(By.xpath("//[#id='INQ_2985']/div[2]/tr/td/div/div[3]/div")).click();
As above INQ_2985 changes to 2986,2987,2988 etc during each run
HTML CODE:
< div> class="context-menu-item-inner" style="background-image:url(../images/productSmall.png);">Tender Assignment < /div>
Tried different combinations as below but with no success:
// Driver.findElement(By.name("//input[#name='Tender Assignment']")).click();
// Driver.findElement(By.className("context-menu-item-inner")).click();`
Can you help me on this.
you can try using contains() or starts-with() in xpath,
above xpath can be rewritten as follows,
Driver.findElement(By.xpath("//*[starts-with(#id,'INQ')]/div[2]/tr/td/div/div[3]/div")).click();
if you can post more of your html, we can help improve your xpath..
moreover using such long xpath's is not recommended, this may cause your test to fail more often
for example,if a "new table data or div" is added to the UI, above xpath will no longer be valid
you should try and use id, class or other attributes to get closer to the element your trying to find
i personally recommend using cssSelectors over xpath
you can use many methods,
use implicity wait;
driver.findElement(By.xpath("//*[contains(#id,'select2-result-label-535')]").click();
driver.findElement(By.xpath("//*[contains(text(), 'select2-result-label-535')]").click();
Good to use Regular expression
driver.findElement(By.xpath("//*[contains(#id,'INQ_')]")
Note: If you have single ID with name starts from INQ_ then you can take action on the element . If a bunch of ID then you can extract as a List<WebElements> and then match with the specific text of the element ( element.getText().trim() =="Linked Text" and if it matched then take action. You can follow other logic to traverse and match.
you can use css -
div.context-menu-item-inner
Use this xpath:
driver.findElement(By.cssSelector("div.context-menu-item-inner").click();
The best choice is using full xpath instead of id which you can get easily via firebug.
e.g.
/html/body/div[3]/div[3]/div[2]/div/div[2]/div[1]/div/div[1]
if your xpath is varying
Ex: "//*[#id='msg500']" , "//*[#id='msg501']", "//*[#id='msg502']" and so on...
Then use this code in script:
for (int i=0;i<=9;i++) {
String mpath= "//*[#id='msg50"+i+"']";
driver.findElement(By.xpath(mpath)).click();
}
This may be a silly question, but is it possible to make a query using XPath without specifying the element name?
Normally I would write something like
//ElementName[#id = "some_id"]
But the thing is I have many (about 40) different element types with an id attribute and I want to be able to return any of them if the id fits. But I don't want to make this call for each type individually. Is it possible to search all of them at once, regardless of the name?
I am using this in an XQuery script, if that offers any help.
use * instead of name //*[#id = "some_id"]
It might be more efficient to look directly at the #id elements - //* will work, but will initially return every node in the document and then filter!
That may not matter in a small document, of course. but here's an alternative:
//#id[.="some_id"]/..
I'm trying to use an xpath expression to select a node-set in an xml document with different namespaces defined.
The xml looks something like this:
<?POSTEN SND="SE00317644000" REC="5566420989" MSGTYPE="EPIX"?>
<ns:Msg xmlns:ns="http://www.noventus.se/epix1/genericheader.xsd">
<GenericHeader>
<SubsysId>1</SubsysId>
<SubsysType>30003</SubsysType>
<SendDateTime>2009-08-13T14:28:15</SendDateTime>
</GenericHeader>
<m:OrderStatus xmlns:m="http://www.noventus.se/epix1/orderstatus.xsd">
<Header>
<OrderSystemId>Soda SE</OrderSystemId>
<OrderNo>20090811</OrderNo>
<Status>0</Status>
</Header>
<Lines>...
I want to select only "Msg"-nodes that has the "OrderStatus" child and therefore I want to use the following xpath expression: /Msg[count('OrderStatus') > 0] but this won't work since I get an error message saying: "Namespace Manager or XsltContext needed. This query has a prefix, variable, or user-defined function".
So I think I want to use an expression that looks something like this: /*[local-name()='Msg'][count('OrderStatus') > 0] but that doesn't seem to work.. any ideas?
Br,
Andreas
I want to use the following xpath
expression:
/Msg[count('OrderStatus')[ 0]
but this won't work since I get an error message saying: "Namespace
Manager or XsltContext needed.
This is a FAQ.
In XPath a unprefixed name is always considered to belong in "no namespace".
However, the elements you want to select are in fact in the "http://www.noventus.se/epix1/genericheader.xsd"
namespace.
You have two possible ways to write your XPath expression:
Use the facilities of the hosting language to associate prefixes to all different namespaces to which names from the expression belong. You haven't indicated what is the hosting language in this concrete case, so I can't help you with this. A C# example can be found here.
If you have associated the prefix "xxx" to the namespace "http://www.noventus.se/epix1/genericheader.xsd" and the prefix "yyy" to the namespace "http://www.noventus.se/epix1/orderstatus.xsd", then your Expression can be written as:
/xxx:Msg[yyy:OrderStatus]
:2: If you don't want to use any prefixes at all, an XPath expression can still be constructed, however it will not be too readable:
/*[local-name() = 'Msg' and *[local-name() = 'OrderStatus']]
Finally, do note:
In order to test if an element x has a child y it isn't necessary to test for a positive count(y). Just use: x[y]
Xpath positions are 1-based. This means that NodeSetExpression[0] never selects a node. You want: NodeSetExpression[1]