How to get node value using variable node name? - xpath

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]

Related

XQuery/Xpath referring to xml elements with no namespace, in a namespace environment

In Xquery 3.1 (under eXist-DB 4.7) I receive xml data like this, with no namespace:
<edit-request id="TC9999">
<title-collection>foocolltitle</title-collection>
<title-exempla>fooextitle</title-exempla>
<title-short>fooshorttitle</title-short>
</edit-request>
This is assigned to a variable $content and this statement:
let $collid := $content/edit-request/#id
...correctly returns: TC9999
Now, I need to actually transform all the data in $content into a TEI xml document.
I first need to get some info from an existing TEI file, so I assigned another variable:
let $oldcontent := doc(concat($globalvar:URIdata,$collid,"/",$collid,".xml"))
And then I create the new TEI document, referring to both $content and $oldcontent:
let $xml := <listBibl xmlns="http://www.tei-c.org/ns/1.0"
type="collection"
xml:id="{$collid}">
<bibl>
<idno type="old_sql_id">{$oldcontent//tei:idno[#type="old_sql_id"]/text()}</idno>
<title type="collection">{$content//title-exempla/text()}</title>
</bibl>
</listBibl>
The references to the TEI namespace in $oldcontent come through, but to my surprise the references to $content (no namespace) don't show up:
<listBibl xmlns="http://www.tei-c.org/ns/1.0"
type="collection"
xml:id="TC9999">
<bibl>
<idno type="old_sql_id">1</idno>
<title type="collection"/>
</bibl>
</listBibl>
The question is: how do I refer to the non-namespace elements in $content in the context of let $xml=...?
Nb: the Xquery document has a declaration at the top (as it is the principle namespace of virtually all the documents):
declare namespace tei = "http://www.tei-c.org/ns/1.0";
In essence you are asking how to write an XPath expression to select nodes in an empty namespace in a context where the default element namespace is non-empty. One of the most direct solutions is to use the "URI plus local-name syntax" for writing QNames. Here is an example:
xquery version "3.1";
let $x := <x><y>Jbrehr</y></x>
return
<p xmlns="foo">Hey there,
{ $x/Q{}y => string() }!</p>
If instead of $x/Q{}y the example had used the more common form of the path expression, $x/y, its result would have been an empty sequence, since the local name y used to select the <y> element specifies no namespace and thus inherits the foo element namespace from its context. By using the "URI plus local-name syntax", though, we are able to specify the empty namespace we are looking for.
For more information on this, see the XPath 3.1 specification's discussion of expanded QNames: https://www.w3.org/TR/xpath-31/#doc-xpath31-EQName.

How to refer to position when using xf:setvalue function with iterate

Considering this code example and this post
...
<xf:action>
<xf:setvalue
iterate="instance('fr-send-submission-params')/#*"
ref="."
value="event(name(context()))"/>
</xf:action>
...
How can refer to current iterated position? Like value="position()"
Can i use this position as variable to xpath expressions? Like ref="/AnotherElement[position()]"
The following works:
<xf:action iterate="instance('fr-send-submission-params')/#*">
<xf:var name="p" value="position()"/>
<xf:setvalue ref="." value="$p"/>
</xf:action>
I don't think you can get away just with xf:setvalue, because ref changes the evaluation context of the expression to a single item which means that position() returns 1 within value.
A warning as I see that you iterate on attributes: I don't think that attribute position is guaranteed to be consistent.
Update:
The following works if you have elements, but then you need to have knowledge of the items iterated within the xf:setvalue:
<xf:setvalue
event="DOMActivate"
iterate="value"
ref="."
value="count(preceding-sibling::value) + 1"/>
So I think that the option with an enclosing action is much clearer.

Xpath get multiple node-names

i am using xpath to get some node names from a xhtml / xml file.
I currently have this xpath:
/xhtml:html/xhtml:head/xforms:model/xforms:instance/form/*[starts-with(local-name(), 'section')]
That will get the nodes with a name like this:
section-1_s1_partners
section-2-s2_strategy
The result of the above xpath are the matched nodes, but i want to get for each match the full-node-name. When i use the name() function like
name(/xhtml:html/xhtml:head/xforms:model/xforms:instance/form/*[starts-with(local-name(), 'section')])
Then it only returns the first match, and i have no clue how to do it otherwise..
Any great ideas??
Thanks!
(the xhtml/xml: )
<xhtml:html ....>
<xhtml:head>
<xhtml:title>ASD-1</xhtml:title>
<xforms:model id="fr-form-model">
<xforms:instance id="fr-form-instance">
<form>
<section-1_s1_partners>
<control-304/>
<toggleForm>ASD</toggleForm>
<applicationid/>
<section-345>
<s1_kbPaAAr/>
<s1_kbDCCent/>
<s1_kbRAE/>
</section-345>
<section-s1_depDDFentGFress>
<address_search/>
<address_postcode/>
<address_address1/>
<address_address2/>
<address_address3/>
<address_city/>
</section-s1_departmentAddress>
<section-344>
<s1_companyPartner/>
<s1_companyRegistrationNumber/>
<s1_companyType/>
<s1_companySize/>
</section-344>
<section-s1_companyAddress>
<address_search/>
<address_postcode/>
<address_address1/>
<address_address2/>
<address_address3/>
<address_city/>
</section-s1_companyAddress>
<section-324>
<s1_plannedDate/>
<s1_workDescription/>
<s1_publicDescription/>
<s1_numberOfAssociates>1</s1_numberOfAssociates>
<s1_duration/>
<s1b_resubmissionYesNo/>
<s1_GAAGrogramNumber/>
</section-324>
</section-1_s1_partners>
<section-2-s2_strategy>
<control-4/>
<s2_memo_strategic/>
<s2_memo_problems/>
<s2_companyPosition/>
<s2_companyContribution/>
<s2_lackExpertise/>
<s2_essential/>
<s2_companySponsor/>
<s2_seekKnowledge/>
<s2_challenge/>
</section-2-s2_strategy>
The name function need one argument, it cannot take a node list. You have to iterate over the nodelist in the language you are using. For example, in xsh:
for /xhtml:html/xhtml:head/xforms:model/xforms:instance/form/*[starts-with(local-name(), 'section')]
echo name()

How to get H1,H2,H3,... using a single xpath expression

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.

Xpath: find an element value from a match of id attribute to id anchor

I would like to find the value of an element matched on id attribute for which I only have the ref - the bit with #, the anchor.
I am looking for the value of partyId:
< party id="partyA" >
< partyId >THEID< /partyId >
but to get there I only have the href from the following
< MyData >
< MyReference href="#partyA" />
Strip the # sign does not look good to me.
Any hints?
Because you haven't provided complete XML documents, I have to use // -- a practice I strongly recommend to avoid.
Suppose that
$vDataRef
is defined as
//MyData/MyReference/#href
and its string value is "#partyA", then one possible XPath expression that selects the wanted node is:
//party[#id=substring($vDataRef,2)]
In case the XML document has a DTD in which the id attribute of party is defined to be of type ID, then it is more convenient and efficient to use the standard XPath function id():
id(substring($vDataRef,2))
Assuming you have your ID as a variable already (lets say $myId), then try using:
//party[contains($myId, #id)]
The contains() function will look to see on each matching node whether or not the partyId attibute is in the value that you pass in.
Alternatively (as that could be considered 'ropey'), you can try:
//party[#id=substring($myId, 2, 1 div 0)]
the substring() function should be a little more precise.

Resources