XQuery: How to convert string into XPath rules - xpath

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>

Related

Go Template compare if string ends in or contains another string

The eq function allows for comparing if two strings are equal
{{if eq .Name "MyName"}}
Is there a way to test if a string ends in (or contains) another string?
Use a function map containing the relevant string functions.
funcs := map[string]any{
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix}
tmpl := `{{if hasSuffix . ".txt"}}yes!{{end}}`
t := template.Must(template.New("").Funcs(funcs).Parse(tmpl))
t.Execute(os.Stdout, "example.txt") // writes yes! to standard out
Run the example on the playground.
Some applications that use Go templates as a feature (Hugo and Helm are examples) provide these functions by default.
(h/t to mkopriva).

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

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,

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.

Iterate through map in Go text template

I have a map of values that looks like this:
vals := map[string]interface{}{"foo": 1, "bar": 2, "baz": 7}
data := map[string]interface{}{"bat": "obj", "values": vals}
What should my template look like to generate the following string (note the correct comma usage)?
SET obj.foo=1, obj.bar=2, obj.baz=7
I started with this as my template:
SET {{range $i, $v := .values}} {{.bat}}.{{$i}}={{$v}},{{end}}
But that just prints out
SET
And even if that did work, the commas would be incorrect. I then tried to use a custom function to format the map, but I couldn't get the template to ever call my function. None of the following seemed to work:
SET {{.MyFunction .values}}
SET {{call .MyFunction .values}}
SET {{call MyFunction .values}}
when MyFunction was defined as:
func MyFunction(data map[string]interface{}) string {
fmt.PrintLn('i was called!')
return "foo"
}
And I'm executing the templates using a helper function that looks like this:
func useTemplate(name string, data interface{}) string {
out := new(bytes.Buffer)
templates[name].Execute(out, data)
return string(out.Bytes())
}
Thanks!
This will get you pretty close:
SET {{range $key, $value := $.values}}{{$.bat}}.{{$key}}={{$value}} {{end}}
rendering as:
SET obj.bar=2 obj.baz=7 obj.foo=1
Unfortunately, I don't think there's any simple way to have the commas added in between the values due to how the range action iterates on maps (there's no numeric index). That said, the template packages were meant to be easily extensible so you can have less logic in your templates and more logic in Go itself, so it's easy enough to code a helper function in Go and make it available to your templates.
If you're happy to go that extra mile, then the template becomes much simpler, and also more efficient. The function can look like this:
func commaJoin(prefix string, m map[string]interface{}) string {
var buf bytes.Buffer
first := true
for k, v := range m {
if !first {
buf.WriteString(", ")
}
first = false
buf.WriteString(prefix)
buf.WriteByte('.')
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(fmt.Sprint(v))
}
return buf.String()
}
and your template would look like:
SET {{$.values | commaJoin $.bat}}
Here is a working example with this logic:
http://play.golang.org/p/5lFUpFCzZm

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