I have some XML that looks like this:
<container>
<type>01</type>
<text>one</text>
</container>
<container>
<type>02</type>
<text>two</text>
</container>
EDIT the order of the containers isn't fixed.
I'm using xPath (through ruby's nokogiri) to choose text from this document. I want to be able to take the text in the container with a type of 02, but take the text from the container with a type of 01 if that doesn't exist.
I can do
/container/type[text() = "02" or text() = "01"]/parent::container
Which will get me both elements, then I can use some ruby to sort and take the right one (as this would return the 01 element first), but this feels clumsy.
I've search stackoverflow and there's nothing immediately apparent which allows me to sort element output with simple xpath, but is there a way to command xpath to take an element, but fallback to another if it doesn't exist?
Cheers!
Not sure if you're going to like it, but it does what you want:
concat(substring(//container[type/text() = '02']/text,1,string-length(//container[type/text()='02'])*boolean(//container[type/text()='02']/text())),substring(//container[type/text() = '01']/text,1,string-length(//container[type/text()='01'])*number(boolean(//container[type/text()='01']/text())and not(boolean(//container[type/text()='02']/text())))))
I'll break it down in a sec...
OK, so this part:
concat(
substring(//container[type/text() = '02']/text,1,string-length(//container[type/text()='02'])*boolean(//container[type/text()='02']/text())),
Grabs the <text> from type=2 if it exists.
This part:
substring(//container[type/text() = '01']/text,1,string-length(//container[type/text()='01'])
Grabs the <text> from type=1, and only returns it if type=2 doesn't exist using this:
*number(boolean(//container[type/text()='01']/text())and not(boolean(//container[type/text()='02']/text())))))
Hope that helps clear it up, I know you were looking for something clean, but for what you wanted using XPath, it's a little messy.
If the order is fixed, you can select both and then take the last one. It will be 02, if there is only 02, and 02, if there is an 01 before it in the document...
(/container/type[text() = "02" or text() = "01"]/parent::container)[last()]
Or shorter, since the value of a node is its text and a comparison just looks for a matching pair:
(/container[type = ("01", "02")])[last()]
(at least that works in XPath 2, not sure if it is the same in XPath 1)
-
edit:
Actually it is pretty easy. You can check with //container[type = "02"], if there is a container with type 02, so you can take all 02 containers and all 01 containers, if there are not type 02 containers, leading to:
//container[type = "02" or (type = "01" and not(//container[type = "02"]))]
it'S quite slow through
(//container/type[.='02'], //container/type[.='01'])[1]/..
The outer brackets construct a sequence from the type element with text value 02, followed by the type with text value 01. The , operator omits any undefined values, so if one is missing, the sequence consists only of the other, and if both are missing, it's an empty sequence.
[1] selects the first item from that sequence
/.. is abbreviated syntax for parent::node(), which is equivalent to your parent::container in this case.
You might expect this to work:
//container/(type[.='02'],type[.='01'])[1]/..
but (in Saxon at least) this gives you both containers ; or this:
(//container/(type[.='02'],type[.='01']))[1]/..
but this gives you the 01 container, because the result of the brackets is both nodes in document order.
Related
I have this xml that contains certificates and its attributes. Here is the xml over which I am running the extraction query
<Certificates>
<CertificateAndChain>
<Certificate>
<FriendlyName />
<Thumbprint>EE8B375347FCADDC25547FDDF61866E07D5B6A71</Thumbprint>
<SerialNumber>256BB11DB5BE889E46EBCD85608DA849</SerialNumber>
<Version>3</Version>
<SignatureAlgorithm>sha1RSA</SignatureAlgorithm>
<Issuer>CN=certname</Issuer>
<Subject>CN=certname</Subject>
<NotAfter>2040-09-26T18:30:00Z</NotAfter>
<NotBefore>2016-09-27T18:30:00Z</NotBefore>
<IsVerified>false</IsVerified>
</Certificate>
.
.
.(continued)...
I have written another test over this xml that needs to extract thumbprints of all the certificates that are about to get expired in next 10 days.
As part of the test, , I have written the Extraction query as:
<XPath2ResponseParameter Name="TPName"><ExtractionQuery><![CDATA[string(//CertificateAndChain/Certificate[NotAfter[text() <= (current-dateTime() + xs:dayTimeDuration('P10D'))]]/Thumbprint)]]></ExtractionQuery></XPath2ResponseParameter>
The test is in xml file as well. However, when I run the test, I get this exception:
A sequence of more than one item is not allowed as the first argument of string().
This is probably happening because there are multiple certificate elements present in the xml. So, I couldn't figure out the way to extract all the thumbprints failing the mentioned condition(there could be more than 1 and I need to extract all of them)
Can anyone help me fix this?
You can invoke function in axis when using XPath 2.0. So, try to move string() function to the end of the inner XPath (formatted for readability) :
//CertificateAndChain
/Certificate[
NotAfter[
text() <= (current-dateTime() + xs:dayTimeDuration('P10D'))
]
]/Thumbprint
/string()
UPDATE :
Since CDATA can't receive multiple strings in this context, according to the error message in your first comment, then you need to combine those strings into one, presumably using string-join() :
string-join(
//CertificateAndChain
/Certificate[
NotAfter[
text() <= (current-dateTime() + xs:dayTimeDuration('P10D'))
]
]/Thumbprint
/string()
,' ')
I want to check in a xml if there is a node with the value "Hotel Hafen Hamburg".
But I get the error.
SimpleXMLElement::xpath(): Invalid predicate on line 25
You can view the xml here.
http://de.sourcepod.com/dkdtrb22-19748
Until now I have written the following code.
$apiUmgebungUrl = "xml.xml";
$xml_umgebung = simplexml_load_file($apiUmgebungUrl);
echo $nameexist = $xml_umgebung->xpath('boolean(//result/name[#Hotel Hafen Hamburg');
It seems that your parantheses and brackets do not close properly at the end of your XPath expression - it should end on ]).
Also, what is Hotel Hafen Hamburg? If it is an attribute called value, your value check should look like this:
[#value="Hotel Hafen Hamburg"]
You cannot just write # and then a value, without specifying where that value is supposed to be.
EDIT: Looking at the Xml document, it seems that Hotel Hafen Hamburg is supposed to be the text content of the <name> element. Therefore, try looking for a text node with that value rather than an attribute:
boolean(//result/name[text() = "Hotel Hafen Hamburg"])
My problem is the following :
I usually have those data:
<structures>
<structure id="10">
<code>XXX</code>
</structure>
</structures>
so the table I display (single columns : code) is ok.
But in some cases, the data is the result a a query with no content, so the data is:
<structures/>
resulting in my table not displaying + error.
I am trying to insert, in the case of an empty instance, a single node so that the data would look like:
<structures>
<structure id="0"/>
</structures>
I am trying something like that :
<xforms:action ev:event="xforms-submit-done">
<xforms:insert if="0 = count(instance('{./instance-name}')/root/node())" context="instance('{./instance-name}')/root/node()" origin="xforms:element('structure', '')" />
</xforms:action>
but no node inserted when I look at the data in the inspector in the page.
Any obvious thing I am doing wrong?
There seems to be erros in your XPath if and context expressions:
if="0 = count(instance('{./instance-name}')/root/node())"
context="instance('{./instance-name}')/root/node()"
You are a using curly brackets { and }, I assume to have the behavior of attribute value templates (AVTs). But the if and context expressions are already XPath expressions, so you cannot use AVTs in them. Try instead:
if="0 = count(instance(instance-name)/root/node())"
context="instance(instance-name)/root/node()"
Also, the instance-name path is relative to something which might not be clear when reading or writing the expression. I would suggest using an absolute path for example instance('foo')/instance-name to make things clearer.
You don't provide the structure of the other instances, so I can tell for sure, but you'll expression above suppose that they have the form:
<xf:instance id="foo">
<some-root-element>
<root>
<structure/>
</root>
<some-root-element>
</xf:instance>
I don't know if that's what you intend.
Finally, you could replace count(something) = 0, with empty(something).
I have an XML document like:
<data>
<item type="apple">
<misc>something</misc>
<appleValue>23</appleValue>
<misc2>something else</misc2>
</item>
<item type="banana">
<bananaValue>47</bananaValue>
<random>something</random>
</item>
</data>
I can get the items with doc("data.xml")/data/item but I need to get the text from the elements that end with Value. So I'd like to get "23" and "47", but I don't necessarily know the element names, meaning all I really know is there are elements that end in Value, I don't know if it's appleValue, bananaValue, etc. except that I could look at the type attribute and buildup a string.
let $type := (doc("data.xml")/data/item)[1]/#type
doc("data.xml")/data/item/$typeValue
...That last line is what I'm trying to get at, clearly that's not correct but I need to find elements whose name is known based on a variable (stored in a variable such as $type) and "Value".
Any ideas? I realize this variable element naming is strange/odd/bad...but that's the way it is and I have to deal with it.
I got it thanks to this post: Can XPath match on parts of an element's name?
doc("data.xml")/data/item/*[ends-with(name(), "Value")]
I would avoid using the name() function in favor of either node-name() or local-name(). The reason for this is that name() can give you different answers depending on what (and whether) namespace prefixes are used in the source. For example, the following three elements have the same exact name (QName):
<appleValue xmlns="http://example.com"/>
<x:appleValue xmlns:x="http://example.com"/>
<y:appleValue xmlns:y="http://example.com"/>
However, the name() function will give you a different answer for each one (appleValue, x:appleValue, and y:appleValue, respectively). So you're better off either ignoring the namespace by using local-name() (which returns the string appleValue for all three of the above cases) or explicitly specifying the namespace (even if it's empty, as Oliver showed), using node-name() (which returns a proper QName value, rather than a string). In this case, since you're not using namespaces (and since even if you added one later, the code will still work), I'd be slightly in favor of using local-name() as follows:
doc("data.xml")/data/item/*['Value' eq substring-after(local-name(),../#type)]
For elaboration on reasons to avoid the name() function (and exceptions), see "Perils of the name function".
You can access the name of the node using name(). XPath 1.0 does not have an "ends-with" function, but by using substring() and string-length() - 1 you can get there.
//item/*[ substring( name(), string-length(name() ) - 4 ) = 'Value']
A more precise way to implement this would be
for $item in doc("data.xml")/data/item
let $value-name := fn:QName('', concat($item/#type, 'Value'))
return $item/*[node-name() = $value-name]
How can I get H1,H2,H3 contents in one single xpath expression?
I know I could do this.
//html/body/h1/text()
//html/body/h2/text()
//html/body/h3/text()
and so on.
Use:
/html/body/*[self::h1 or self::h2 or self::h3]/text()
The following expression is incorrect:
//html/body/*[local-name() = "h1"
or local-name() = "h2"
or local-name() = "h3"]/text()
because it may select text nodes that are children of unwanted:h1, different:h2, someWeirdNamespace:h3.
Another recommendation: Always avoid using // when the structure of the XML document is statically known. Using // most often results in significant inefficiencies because it causes the complete document (sub)tree roted in the context node to be traversed.