CTS and xPath both necessary? - xpath

I have the following CTS search query:
cts:search(/parent,
cts:and-query((
cts:element-attribute-value-query(xs:QName('parent'), xs:QName('attr'), 'value'),
cts:element-attribute-value-query(xs:QName('child'), xs:QName('attr-1'), 'value-2'),
cts:element-attribute-value-query(xs:QName('child'), xs:QName('attr-2'), 'value-3')
))
)/child[#attr-1 eq 'value-2' and #attr-2 eq "value-3"]
(: Returns /parent/child elements matching criteria :)
I have some qualifiers on the parent, as well as qualifiers on the children. The end result I want is just the children though. In order to do that, as you can see from above, I have to:
Search for documents that match parent criteria + children criteria
After getting that document, filter out the children by the same criteria logic as above
This works, but it seems really dumb that I must have the same logic in the cts:query as I do on the xPath for the children. The logic is duplicated unnecessarily.
Is there a way that I can do this all in the cts:query, and not have to have additional xPath expressions, as in the sample above?
This is similar to what I want, but it doesn't work for the problem specified in the comments:
cts:search(/parent/child,
cts:and-query((
cts:element-attribute-value-query(xs:QName('parent'), xs:QName('attr'), 'value'), (: The problem is this line... I can't filter by the parent, as it is above the scope of my first parameter (/parent/rule) :)
cts:element-attribute-value-query(xs:QName('child'), xs:QName('attr-1'), 'value-2'),
cts:element-attribute-value-query(xs:QName('child'), xs:QName('attr-2'), 'value-3')
))
)

You can still query the parent, even if your search is across the child:
cts:search(/parent/child,
cts:and-query((
cts:element-attribute-value-query(xs:QName('parent'), xs:QName('attr'), 'value'),
cts:element-attribute-value-query(xs:QName('child'), xs:QName('attr-1'), 'value-2'),
cts:element-attribute-value-query(xs:QName('child'), xs:QName('attr-2'), 'value-3')
))
)
This will run as a filtered search, but since you were manually doing the filtering anyway, the performance should be roughly equivalent.
Update:
I tested this and the above assertion is wrong. I thought it was right, but evidently cts:search filtering will filter results that don't match on the searchable expression exactly. A parent would be outside of the scope of the searchable expression.
Ideally, you would break up your documents on the child element, but you can at least remove the overlapping queries and XPath like this:
cts:search(/parent/child,
cts:and-query((
cts:element-attribute-value-query(xs:QName('child'), xs:QName('attr-1'), 'value-2'),
cts:element-attribute-value-query(xs:QName('child'), xs:QName('attr-2'), 'value-3')
))
)[parent::parent/#attr = 'value']

wst gave you the answer. However, this all comes up from needing filtering. In MarkLogic, the idea is that one document should reflect one 'record'. Is it possible to refactor your documents to avoid the need for filtering in the fist place?

Related

How to select a specific category value in this Xpath expression

I have a feed here. I'm trying to create an XPath expression that returns items that have a category equal to Bananas. Due to the limitations in my XML parser, I can't use namespaces directly to select items.
The expression /rss/channel/item//*[name()='itunes:category'] returns this:
Element='<itunes:category
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
text="Apples"/>'
Element='<itunes:category
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
text="Bananas"/>'
...
And /rss/channel/item//*[name()='itunes:category']/#text returns this:
Attribute='text=Apples'
Attribute='text=Bananas'
...
But I can't figure out how to limit the response to just a single category (e.g., Bananas)?
I want some kind of expression like this:
/rss/channel/item//*[name()='itunes:category' and contains(., 'Bananas')]
But this doesn't work. It's not syntactically valid. What would be the right XPath expression syntax to just return Bananas?
Do you just mean to filter by attributes of item child, but still return item node?
/rss/channel/item/*[name()='itunes:category' and contains(#text,'Apples')]/parent::item
or simplier
/rss/channel/item[*[name()='itunes:category' and #text='Apples']]
I used Apples in example because using your example xml file there is 0 results for Bananas.

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

xpath expression to read value based on value of sibling

I've below xml and would like to read the value of 'Value' tag whose Name matches 'test2'. I'm using the below xpath , but did not work. Can someone help.
/*[ local-name()='OutputData']/*[ local-name()='OutputDataItem']/*[ local-name()='Name'][normalize-space(.) = 'test2']//*[local-name()='Value']/text()
<get:OutputData>
<get:OutputDataItem>
<get:Name>test1</get:Name>
<get:Value/>
</get:OutputDataItem>
<get:OutputDataItem>
<get:Name>test2</get:Name>
<get:Value>B5B4</get:Value>
</get:OutputDataItem>
<get:OutputDataItem>
<get:Name>test3</get:Name>
<get:Value/>
</get:OutputDataItem>
<get:OutputDataItem>
<get:Name>OP_VCscEncrptCd_VAR</get:Name>
<get:Value/>
</get:OutputDataItem>
</get:OutputData>
Thanks
You were close, but because the get:name and get:value are siblings, you need to adjust your XPath a little.
Your XPath was attempting to address get:value elements that were descendants of get:name, rather than as siblings. Move the criteria that is filtering the get:name into a predicate, then step down into the get:value:
/*[ local-name()='OutputData']/*[ local-name()='OutputDataItem']
[*[ local-name()='Name'][normalize-space(.) = 'test2']]/*[local-name()='Value']/text()
You could also combine the criteria of the predicate filter on the get:name and use an and:
/*[ local-name()='OutputData']/*[ local-name()='OutputDataItem']
[*[ local-name()='Name' and normalize-space(.) = 'test2']]/*[local-name()='Value']/text()
This should work I think:
//*[local-name()="get:Name" and text()="test2"]/following-sibling::*[local-name()="get:Value"]/text()

MarkLogic: how to search for an XPath using cts:uris

Is it possible to search for a uri whose document contains a certain XPath using cts:uris()? I thought it may be quicker than returning uris from a cts:search. Here is what I have currently:
declare function local:xpath-search($collection) {
for $i in cts:search(//a/b, cts:and-query((cts:collection-query($collection)) ))[1] return fn:base-uri($i)
} ;
Is there a quicker way to return documents that contain a match to the XPath //a/b, using cts:uris()?
You can use cts:element-query() to construct a cts:query that functions similar to the XPath expression //a/b searching for documents that have a elements that have b element descendants. It isn't exactly the same, and might give you some false positives, because it is really more akin to //a//b, but might be acceptable and can be used with cts:uris().
xquery version "1.0-ml";
declare function local:xpath-search($collection) {
cts:uris("", (),
cts:and-query((
cts:collection-query($collection),
cts:element-query(xs:QName("a"),
cts:element-query(xs:QName("b"), cts:and-query(()) ) ) )) )
};

How to construct an xpath returning all of a set of elements

Considering theses xpath expressions :
//*[#id="searchResults"]/div[1]/div[1]/h2/span
//*[#id="searchResults"]/div[3]/div[1]/h2/span
//*[#id="searchResults"]/div[5]/div[1]/h2/span
For your info the div inside search result's class is article searchResult and the one inside article searchResult is header.
I am not sure how to construct an xpath matching all three of the above elements. Is there a tool or a how to guide for that?
Thanks
Use position function
//*[#id="searchResults"]/div[position()=1 or position()=3 or position()=5]/div[1]/h2/span
If, by 'all', you mean all div in even position index, then you can use mod operator to check :
//*[#id="searchResults"]/div[position() mod 2 = 1]/div[1]/h2/span
but if 'all' literally means all, then you don't need index to return all matched elements :
//*[#id="searchResults"]/div/div[1]/h2/span

Resources