Xquery get the children nodes by passing parent value at run time - xpath

I need to pass a value at run time get all the childen elements.
For example this is my XML:
<person>
<details1>
<name>jack</name>
<age>26</age>
</details1>
<details2>
<name>john</name>
<age>48</age>
</details2>
</person>
And my query:
let $y as xs:string := "details1"
let $x := fn:doc(cts:uri-match('*person.xml'))
return $x/$y
So here I am expecting the result as
<details1>
<name>jack</name>
<age>26</age>
</details1>
but it returns the same name as that of $y i.e. "details1"
or if I query this way
let $y as xs:string := "details1"
let $x := fn:doc(cts:uri-match('*person.xml'))
return $x//$y
the result would be "details1" for 12 times
I am new to XQuery please help me in solving the issue

It seems like you attempt to use a string $y as a node step. However, in XPath/XQuery a path step is different from a string, so you can't simple add a string as path step. Instead, you could look for all descendent elements with the requirement that they do have the same name, i.e.:
let $y as xs:string := "details1"
let $x := fn:doc(cts:uri-match('*person.xml'))
return $x/*[name(.) = $y]

As drkk said, you have to look at the name of the element. But using name() is dangerous. It returns the lexical name of the element, which might be ns:elem if the element has a prefix. And the prefix can be different in every document, even for the same namespace. So either you match only by local name:
let $y as xs:string := 'details1'
let $x := fn:doc(cts:uri-match('*person.xml'))
return
$x/*[local-name(.) eq $y]
Or better, match a QName (note the node-name() usage):
let $y as xs:string := xs:QName('details1')
let $x := fn:doc(cts:uri-match('*person.xml'))
return
$x/*[node-name(.) eq $y]
and if the element name is within a namespace (note the difference between xs:QName() and fn:QName()):
let $y as xs:string := fn:QName('namespace-uri', 'details1')
let $x := fn:doc(cts:uri-match('*person.xml'))
return
$x/*[node-name(.) eq $y]

One alternative is to use xdmp:value() to evaluate the XPath dynamically:
let $y as xs:string := "details1"
let $x := fn:doc(cts:uri-match('*person.xml'))
return xdmp:value("$x/" || $y)
If $x is a large data set, that approach can have performance benefits by removing the need to get the element name for every document in the set.
For more detail, see:
http://docs.marklogic.com/xdmp:value?q=xdmp:value&v=8.0&api=true
Hoping that helps,

Related

XQuery: How to convert string into XPath rules

The following is a simple XQuery that would run an xpath against an htm file and return how many matching tags were found. The xpath rules are in variable $rule, but I would like to use the string variable $rule-unused instead. How can I convert this string into a node-set and have it run against my document similar to the $document//$rule ?
xquery version "3.1";
declare namespace persons="http://www.test.com/test/profiles";
declare variable $uri as xs:string := "/db/apps/Persons/profile.htm";
declare variable $rule-unused as xs:string := "//persons:relationship";
let $document := doc($uri)
let $rule := //persons:relationship
let $all-lines := $document//$rule
return
<test>
{
count( $all-lines )
}
</test>

xQuery group output by category

I have another little problem with xQuery/xPath. For my homework i need to output the product range by branch and product category, where the output should only contain those branches (filiale), that have products (produkt and prodInSortiment) of every category (kategorie).
For the XML data please see the URL provided in the code.
While this gives me the right amount of products for each branch, i need to group it further by product category:
declare context item := doc("https://etutor.dke.uni-linz.ac.at/etutor/XML?id=1");
let $filialen := //filiale,
$sortiment := //prodInsortiment,
$produkte := //produkt,
$kategorien := distinct-values(//produkt/kategorie)
for $f in $filialen
let $s := $f//prodInSortiment
let $scat := distinct-values($produkte[#ean = $s/ean]/kategorie)
let $filsort := $produkte[#ean = $s/ean]/#ean
let $catcount := count($scat)
where $catcount = count(distinct-values($kategorien))
return
<filiale filialeNr="{$f/#filNr}">
{for $fs in $filsort return <ean>{$fs}</ean>
}
</filiale>
This is my approach of grouping it further, but it returns every product for every category(kategorie) in every branch(filiale):
declare context item := doc("https://etutor.dke.uni-linz.ac.at/etutor/XML?id=1");
let $filialen := //filiale,
$sortiment := //prodInsortiment,
$produkte := //produkt,
$kategorien := distinct-values(//produkt/kategorie)
for $f in $filialen
let $s := $f//prodInSortiment
let $scat := distinct-values($produkte[#ean = $s/ean]/kategorie)
let $filsort := $produkte[#ean = $s/ean]/#ean
let $catcount := count($scat)
where $catcount = count(distinct-values($kategorien))
return
<filiale filialeNr="{$f/#filNr}">
{for $cat in $scat return
<prodGruppe val = "{$cat}">
{for $fs in $filsort return <ean>{$fs}</ean>
}
</prodGruppe>
}
</filiale>
The result should look like this (this is the correct output for filiale "1"):
<filiale filialeNr="1">
<prodGruppe val="Pflege">
<ean>0-666-4567-2-22</ean>
<ean>0-777-4997-2-43</ean>
<ean>0-456-4887-3-22</ean>
<ean>0-55-48567-16-2</ean>
</prodGruppe>
<prodGruppe val="Ersatz">
<ean>1-626-7767-2-99</ean>
<ean>1-256-7700-2-00</ean>
<ean>1-333-7788-2-31</ean>
<ean>2-446-7240-9-15</ean>
<ean>9-396-7510-9-00</ean>
</prodGruppe>
<prodGruppe val="Audio">
<ean>7-2881-760-3-70</ean>
<ean>5-2671-955-5-55</ean>
<ean>1-4444-652-8-88</ean>
<ean>3-1111-654-3-99</ean>
</prodGruppe>
<prodGruppe val="Sonstiges">
<ean>6-581-1766-3-45</ean>
<ean>6-231-4777-3-15</ean>
<ean>4-1161-730-3-88</ean>
<ean>0-4381-880-7-00</ean>
<ean>5-6661-000-0-00</ean>
</prodGruppe>
Since there is an upcoming mid-term exam, I'm not just asking for a solution, I'm also interested in possible simpler ways to achieve the correct output.
Thanks in advance for any helpful information!
Assuming you have XQuery 3's group-by clause (https://www.w3.org/TR/xquery-31/#id-group-by) you can rewrite the last return to
return
<filiale filialeNr="{$f/#filNr}">
{
for $prod in $s
group by $cat := $produkte[#ean = $prod/ean]/kategorie
return
<prodGruppe val = "{$cat}">
{
$prod/ean
}
</prodGruppe>
}
</filiale>
I think.

Specman e: When colon equal sign ":=" should be used?

I saw in some Specman e code example the use of := (colon-equal sign), e.g.:
var regs_type := rf_manager.get_exact_subtype_of_instance(graphics_regs);
When and why should we use := ?
Thank you for your help.
The := means declare a variable of the type the expression on the right returns and assign it to that value. Basically, in your example, the function get_exact_subtype_of_instance(...) returns a value of type rf_struct. The regs_type variable will be declared to that type.
This code is equivalent to (but shorter than):
var regs_type : rf_struct = rf_manager.get_exact_subtype_of_instance(graphics_regs);
This syntax is particularly helpful when casting:
var foo := some_struct.as_a(FOO some_struct_type);

Xquery returning list of maps rather than map

I have the following function
declare private function local:get-map() as map:map*
{
let $map := map:map()
for $chart in xdmp:directory("/charts/")
for $panel in $chart/delphi:chart/delphi:panels/delphi:panel
for $dataline in $panel/delphi:datalines/delphi:dataline
let $datasetHref := $dataline/delphi:link[#rel="dataset"]/#href
let $axisId := $dataline/delphi:dimensions/delphi:dimension[#field="y"]/#axis
let $label := $panel/delphi:axes[#dimension="y"]/delphi:axis[#id=$axisId]/#label
let $l := map:get ($map, $datasetHref)
let $updateMap := if (fn:exists ($l)) then () else map:put ($map, $datasetHref, $label)
return $map
};
I have been forced to declare the return type as map:map* because for some reason $map is an array of maps rather than a map. The array contains many items, where each item contains the same map that I need. So when I call this method I use take the first item. Problem is this is not exactly elegant. What I don't understand is why do I get multiple copies of the same map in an array. I expected the code to return a single map. How do I rewrite this to solve the issue?
It's returning a sequence of maps, because each iteration of each of the "for"s returns it. Try this:
declare private function local:get-map() as map:map
{
let $map := map:map()
let $populate :=
for $chart in xdmp:directory("/charts/")
for $panel in $chart/delphi:chart/delphi:panels/delphi:panel
for $dataline in $panel/delphi:datalines/delphi:dataline
let $datasetHref := $dataline/delphi:link[#rel="dataset"]/#href
let $axisId := $dataline/delphi:dimensions/delphi:dimension[#field="y"]/#axis
let $label := $panel/delphi:axes[#dimension="y"]/delphi:axis[#id=$axisId]/#label
let $l := map:get ($map, $datasetHref)
return if (fn:exists ($l)) then () else map:put ($map, $datasetHref, $label)
return $map
};
This does the FLWOR statement in its own let, then returns the map.

string in xpath

I have a string and I want to use it as a selector in xpath to select a node with name as value of the string.
declare variable $get_count := <count><comedy>1</comedy></count>;
(: $string = "comedy" :)
let $value = $get_count/$string (: but this doesn't return anything)
How shall i do it?
let $value = $get_count/$string (: but this doesn't return anything)
Use:
declare variable $get_count := <count><comedy>1</comedy></count>;
declare variable $string := "comedy";
let $value := $get_count/*[name()=$string]
return
$value
When this is applied on any XML document (not used), the wanted, correct result is produced:
<comedy>1</comedy>

Resources