xs:string as element() in XQuery - xpath

Let's assume I have a XML files, one like this:
<SampleF>
<FirstNode AAA="Something" BBB="Something"></FirstNode>
<SecondNode CCC="Random" DDD="Random"></SecondNode>
</SampleF>
And second one like this:
<SampleF2>
<FirstNode>
<AAA>Something</AAA>
<BBB>Random</BBB>
</FirstNode>
</SampleF2>
And I would like to obtain from both (AAA="Something"/Something) of them as element(). How do I convert it? When in first case I get xs:string and in the second document-node().
I made something like this for the first example but I'm 100% certain there is a better way of doing this
declare function getElementFirstExample($message) as element() {
let $name := "AAA"
let $value := $message/*:SampleF1/*:FirstNode/#AAA
return element {$name} {"$value"}
};
Thank you in advance for your help and advices.

As I understand, you want the value of <FirstNodes/>s AAA attribute or child element, no matter whether it is in the element or attribute.
Use an alternative axis step for accessing both the attribute and element, and data(...) to retrieve the string value.
data(//FirstNode/(#AAA, AAA))
Putting this together for your function and explicit use case:
declare function getElementFirstExample($message) as element() {
let $name := "AAA"
let $value := data($message/*:SampleF1/*:FirstNode/(#AAA, *:AAA))
return element {$name} {"$value"}
};

Related

XQuery - How can I count and save informations in a map

I want to safe IDs in a map. If the ID occurs again, I want to set the count ($value) from 1 to 2 and so on.
Following you find my code:
declare namespace functx = "http://www.functx.com";
declare variable $idMap := map{};
declare function functx:uniqueID ($entityID as xs:string) as xs:integer {
let $idMap := map:merge(($idMap, if(not(map:contains($idMap, $entityID))) then map:entry($entityID, 1) else map:entry(entityID, map:get($idMap, $entityID)+1)))
return map:get($idMap, $entityID)
};
declare variable $map := map:merge((
map:entry("Sheff", "85246525"),
map:entry("Peter", "85246454"),
map:entry("Marcel", "85246525"),
map:entry("Lion", "85244565"),
map:entry("Klaus", "85241234")
));
map:for-each($map,
function($key, $value) {
functx:uniqueID($value)
}
)
Result:
1
1
1
1
1
Expected Result
1
1
2 (: Because it is the second time, that 85246525 occurs. :)
1
1
Edited 23.03.2020 - 17:45:
I have a complex xquery, which functions. But the target system need unique IDs per line. I have a map, which hold my information like the upper one. I need to add something behind the IDs like (001, 002, 003) to have different IDs.
Best practice would be, that only douplicate IDs get a added number.
Do you understand or what do you need more from me?
One way to construct a new map with an "index" added to duplicated values is to use grouping:
map:merge(
for $key in map:keys($map)
group by $value := $map($key)
for $group-key at $pos in $key
return map:entry($group-key, $value || '-' || format-integer($pos, '000'))
)
At https://xqueryfiddle.liberty-development.net/6qVSgeT that gives
{
"Peter": "85246454-001",
"Marcel": "85246525-001",
"Sheff": "85246525-002",
"Lion": "85244565-001",
"Klaus": "85241234-001"
}
You've written your code as if $idMap is mutable, and as if calling functx:uniqueID() has the side-effect of modifying the map. That isn't going to work in a functional language.
You need a completely different approach; and to help you with that, we need to look at the problem you are trying to solve, not at your existing approach to a solution.

eXist-DB / XQuery StringValue cannot be cast to AnyURIValue (using compression:zip)

In eXist 4.4/XQuery 3.1, I am building a function to compress a number of xml files into a zip using compression:zip.
I have one function which collects all the URIs for the documents to be compressed, schedule:get-document-uris-for-zip(xmlid as xs:string). This function returns lists of documents like the following:
/db/apps/deheresi/data/MS609-0001.xml
/db/apps/deheresi/data/MS609-0002.xml
/db/apps/deheresi/data/MS609-0003.xml
/db/apps/deheresi/data/MS609-0004.xml
/db/apps/deheresi/data/MS609-0005.xml
/db/apps/deheresi/data/MS609-0006.xml
/db/apps/deheresi/data/MS609-0007.xml
/db/apps/deheresi/data/MS609-0008.xml
/db/apps/deheresi/data/MS609-0009.xml
/db/apps/deheresi/data/MS609-0010.xml
This function is called by the compression function as follows
declare function schedule:create-zip-by-batch()
{
let $batch := doc(concat($globalvar:URIdocuments,"document_collections.xml"))
for $entry in $batch//collection[#compile="y"]
let $zipobject := compression:zip(schedule:get-document-uris-for-zip($entry/string(#xml:id)),false())
let $zipstore := xmldb:store("/db/apps/deheresi/documents",
"MS609_tei.zip",
$zipobject)
return $zipstore
};
This is throwing a cast error as follows, but I can't identify how to resolve this...
org.exist.xquery.value.StringValue cannot be cast to org.exist.xquery.value.AnyURIValue
Many thanks in advance.
Edit - I'm adding here the part of the function schedule:get-document-uris-for-zip(xmlid as xs:string) which outputs the list of URIs. The URIs are built through string concatenation:
(: get names of documents which meet criteria :)
let $list := xmldb:get-child-resources("/db/apps/deheresi/data")[starts-with(., $y/string(#filename)) and ends-with(., $y/string(#ext))]
(: create URI for each document :)
return
for $n in $list
return concat("/db/apps/deheresi/data/",$n)
You're right to find this function a bit confusing. The (eXist-specific) compression:zip() function $sources parameter is typed as if it is quite flexible way, as xs:anyType()+. But really it is quite strict about the two types of item it accepts: a sequence of URIs (i.e., of type xs:anyURI), or a sequence of <entry> elements:
<entry name="filename.ext"
type="collection|uri|binary|xml|text"
method="deflate|store"
>data</entry>
See https://exist-db.org/exist/apps/fundocs/view.html?uri=http://exist-db.org/xquery/compression#zip.2.
The problem with your code is that you are passing strings in your $sources parameter, and have not cast these strings as xs:anyURI.
Here is sample working code:
xquery version "3.1";
let $prepare :=
(
xmldb:create-collection("/db", "test"),
xmldb:store("/db/test", "test.xml", <test/>)
)
let $zip := compression:zip("/db/test/test.xml" cast as xs:anyURI, false())
return
xmldb:store("/db/test", "test.zip", $zip)

Passing a subpath as a variable (type of node) or either eval() an string containing a relative XPATH

I want to succed with following functionality, and it works if you harcoded it:
declare function local:sort($collection as node()*, $filter as xs:SUBPATH?) as node()*{
for $element in $collection
order by
if ($filter) then ($element/$filter) (: OR SOME KIND OF fn:eval($filter) IF WE DEFINED $filter AS AN xs:string :)
else ($element/name()) (: Default :)
descending
return $element
};
And it could be called like that:
for $element in local:sort(doc('data')/Data/*,'/#myAttr')
return $element
or
for $element in local:sort(doc('data')/Data/*,'/subnode/subnode/name()')
return $element
or
for $element in local:sort(doc('data')/Data/*,()) (: This is the default of function = own elmentĀ“s name :)
return $element
Mx problem is passing the subpath. Either I need to know some kind of way to send a relative XPATH as an argument and type of node, or I need some kind of eval to pass from xs:string to a runtime valid code
Any help?
You could consider (a) generating a query in which the filter is hard-coded, or (b) using an eval() function specific to your XQuery vendor, or (c) using XQuery 3.0 higher-order functions if your chosen XQuery engine supports them yet.
I ended up doing the following, thanks to this idea How can I solve this autoncremental var case in XQUERY 1.0 FLOWR?:
(: This is a workaround solution as xquery:eval() is not working with var bindings until BaseX 7.3 version :)
(: It evals $context node + literal subpath with a pattern of '(/)lit/lit/lit/#attr' or '(/)lit/lit/lit' representing (/)lit/lit/lit/name(). If subpath null, it returns $context/name().:)
declare function u:eval-path($context as node()*, $subnodes as xs:string*) as item()* {
if(empty($subnodes)) then $context/name()
else(
if (count($subnodes) eq 1) then ( (: Last Element :)
if (starts-with($subnodes[1],'#')) then $context/#*[name()=substring-after($subnodes[1],'#')]
else $context/*[name()=$subnodes[1]]/name()
)
else if ($subnodes[1] eq '') then u:eval-path($context, $subnodes[position() gt 1])
else u:eval-path($context/*[name()=$subnodes[1]],$subnodes[position() gt 1])
)
};
(: Sorts the given collection by given criteria, which should be a pattern '(/)lit/lit/lit/#attr' or '(/)lit/lit/lit' representing (/)lit/lit/lit/name() :)
(: If criteria is null, everything is ordered by $elements/name(). Theres no way to filter intermediate nodes as in /lit/*[name()='X']/lit :)
declare function u:sort($collection as node()*, $criteria as xs:string?) as node()*{
for $element in $collection
order by u:eval-path($element,tokenize($criteria,'/'))
ascending
return $element
};

string as xpath selector

I have a function that takes the result set as element and a string as args and I want to use this string as a selector inside the function.
function abc ($results as element()*, $element as xs:string)
{
for $g in distinct-values($results//*[name() = $element]) (: $results//genre :)
let $c := //$results[? = $g] (: //$results[genre=$g] :)
}
what should be in place of '?' in variable '$c'
function abc ($results as element()*, $element as xs:string)
{
for $g in distinct-values($results//*[name() = $element]) (: $results//genre :)
let $c := //$results[? = $g] (: //$results[genre=$g] :)
}
what should be in place of '?' in
variable '$c'
It is a syntax error to write:
//$results
This question is rather vague, but it seems to me that you want to group the elements, contained in $results according to their genre (or whatever element name defined by $element -- BTW this name sucks (an element isn't a string) -- better use $elementName).
The other concerning fact is that you check the complete subtrees topped by each of the elements in $results -- this means that they may have multiple genre (or whatever) descendents.
To finalize my guessing spree, it seems to me that you want this:
function grouping-Keys ($elems as element()*, $subElementName as xs:string) as element()*
{
for $g in distinct-values($elems//*[name() = $subElementName ])
return
$elems[//*[name() = $subElementName ] = $g][1]
}
If it is known that $subElementName is a name of a child that any of the elements in $elems have, then the above should be better written as:
function grouping-Keys ($elems as element()*, $childElementName as xs:string) as element()*
{
for $g in distinct-values($elems/*[name() = $childElementName ])
return
$elems[/*[name() = $subElementName ] = $g][1]
}
Both of the above functions return one element per a (distinct value of) genre (or whatever). If it is known (guaranteed) that every element in $elems has exactly one child (descendent) named genre (or whatever), then the results of these functions are not redundant.

Prototype: how to dynamically construct selector?

I am having a little bit of difficulty passing a variable into a selector in prototype. I would like to be able to pass a variable into the select string, so that one function can work for many of the same kind.
At the moment, this is what I would basically like to do:
function myFunct(var)
{
$(var + 'add_form').hide() //so inde the brackets would be ('#product1 #add_form') for example.
}
Be able to pass 'var' into the function that would pass it to the selector, so that I can hide a pattern that is the same for many on the page.
Any ideas for a path to follow would be greatly appreciated.
You're on the right track! Couple things:
var is a JavaScript keyword (source), don't use it to name a variable
if you're querying an element by id (such as #add_form) you don't need to add any container element as you're doing
If you're querying an element by class, you need to use the $$ function, not the $ function
You need to iterate over the wrapped set to call your method
whitespace is significant in css selectors, so make sure to include those in your selector construction to tell Prototype to search within your parent container:
function myFunct(parent) {
$$(parent + ' .add_form').invoke('hide')
}
myFunct('#someparent'); // hides .add_form inside #someparent
That should work... just rename var to something else.
function myFunct(yourVar)
{
$$('#' + yourVar + ' .add_form').each(function(s){ s.hide(); }); // yourVar being the id of the container element
}
I've put a '.' in front of add_form because you can't use multiple elements with same ID, make it a class.

Resources