XPATH/Xquery navigating a sequence of strings - xpath

I have a collection of xml documents, each with an xml:id. For example:
<doc xml:id="TC0001">some content<doc>
<doc xml:id="TC0002">some content<doc>
<doc xml:id="TC0003">some content<doc>
<doc xml:id="TC0004">some content<doc>
<doc xml:id="TC0005">some content<doc>
I have a function which gets a subset of (maximum) 3 xml:ids in a sequence, potential returning the xml:id before and after of a given xml:id. So, in simple terms, calling this function (XPATH within XQuery):
let $myid = "TC0002"
return doc-id-list($myid)
returns the sequence: TC0001, TC0002, TC0003
And this:
let $myid = "TC0001"
return doc-id-list($myid)
correctly returns the sequence of two xml:ids: TC0001, TC0002
All of this works fine. Now I need to process the results, with each xml:id treated differently based on whether it is 'before' $myid, $myid itself, or 'after' $myid.
I'm stuck trying to build a solution that navigates the sequence with the following logic:
if there is an id 'before' $myid in the sequence, do X with that id
if there is an id 'after' $myid in the sequence, do Y with that id
Because this eventually outputs HTML, I am looking for something which allows me a solution that doesn't require many nested if-then-else statements.
I should note that the xml:ids can have gaps, so I can't simply increment the value.
Many thanks for any ideas.

It would be nice to know what your real requirement is, and whether forming the sequence in doc-id-list() actually contributes to it.
My instinct would be to do something like
let $target := doc[#xml-id=$myId]
return (process-prior($target/preceding-sibling::doc[1]),
process-target($target),
process-next($target/following-sibling::doc[1]))

Related

IMPORTXML To Return a Value from dividendhistory.org

I want to return the value of "Yield:" from a series of stocks from a URL dividenhistory.org, for example HDIF into a Google sheet using IMPORTXML, where F7 represents the user supplied ticker.
=IMPORTXML("https://dividendhistory.org/payout/TSX/"&F7, "/html/body/div/div[2]/div[2]/p[4]")
The problem with the above is the yield value is not always located in the same paragraph, depending on the ticker. It also returns with the word "Yield:" as part of the value.
I believe I should be using the XPATH parameter which should find and return the yield value only, but I am lost. I am open to all suggestions!
I tried with a few of the tickers there, and this should work. For example:
=IMPORTXML("https://dividendhistory.org/payout/ctas/", "//p[contains(.,'Yield')]/text()")
Output:
Yield: 1.05%
Obviously, you can change 'ctas' for any user input.
Try this and see if it works on all tickers.
EDIT:
To get only the number 1.05, you need to split the result and output the 2nd part:
=index(split(IMPORTXML("https://dividendhistory.org/payout/ctas/", "//p[contains(.,'Yield')]/text()"), ": "),2)
Output:
0.0105

Assign value to a variable using || operator with if condition in Ruby

I am trying to write logic for a search query. There are many different conditions with different parameters. One parameter sent from form is code. So there are code values in two different tables: competitions and responses. What I need is to check the params[:code] value first in competitions table and if it does not exist then check in responses table. If it does not exist in either table then it should return nil. I am trying to write it in a single if statement. The code I tried is below:
competitions = Competition.includes(:event, :responses)
if params[:code].present?
competitions = (competitions.where(code: params[:code])) ||
(competitions.joins(:responses).where(responses: { code: params[:code] }))
The above code checks only the value of competitions.where(code: params[:code]). If that value is [], then it is not evaluating the second condition. What changes should I do to make the above code work as per the requirements mentioned above?
competitions.where(code: params[:code]) returns a Relation object which is always truthy.
Luckily enough, it implements #presence method, returning either the value if it’s not blank, or nil. So, this should work:
competitions.where(code: params[:code]).presence || ...

Need XPath and XQuery query

I'm working on Xpath/Xquery to return values of multiple child nodes based on a sibling node value in a single query. My XML looks like this
<FilterResults>
<FilterResult>
<ID>535</ID>
<Analysis>
<Name>ZZZZ</Name>
<Identifier>asdfg</Identifier>
<Result>High</Result>
<Score>0</Score>
</Analysis>
<Analysis>
<Name>XXXX</Name>
<Identifier>qwerty</Identifier>
<Result>Medium</Result>
<Score>0</Score>
</Analysis>
</FilterResult>
<FilterResult>
<ID>745</ID>
<Analysis>
<Name>XXXX</Name>
<Identifier>xyz</Identifier>
<Result>Critical</Result>
<Score>0</Score>
</Analysis>
<Analysis>
<Name>YYYY</Name>
<Identifier>qwerty</Identifier>
<Result>Medium</Result>
<Score>0</Score>
</Analysis>
</FilterResult>
</FilterResults>
I need to get values of Score and Identifier based on Name value. I'm currently trying with below query but not working as desired
fn:string-join((
for $Identifier in fn:distinct-values(FilterResults/FilterResult/Analysis[Name="XXXX"])
return fn:string-join((//Identifier,//Score),'-')),',')
The output i'm looking for is this
qwerty-0,xyz-0
Your question suggests some fundamental misunderstandings about XQuery, generally. It's hard to explain everything in a single answer, but 1) that is not how distinct-values works (it returns string values, not nodes), and 2) the double slash selections in your return statement are returning everything because they are not constrained by anything. The XPath you use inside the distinct-values call is very close, however.
Instead of calling distinct-values, you can assign the Analysis results of that XPath to a variable, iterate over them, and generate concatenated strings. Then use string-join to comma separate the full sequence. Note that in the return statement, the variable $a is used to concat only one pair of values at a time.
string-join(
let $analyses := FilterResults/FilterResult/Analysis[Name="XXXX"]
for $a in $analyses
return $a/concat(Identifier, '-', Score),
',')
=> qwerty-0,xyz-0

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.

How to get node value using variable node name?

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]

Resources