Dynamic Path in XQUERY - xquery

This is my xml-file:
<?xml version="1.0" encoding="UTF-8"?>
<QQ:Envelope xmlns:QQ="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<RR:ABCInfo xmlns:RR="http://abc.test.de/abc/SOAP-Header/1.0">
<RR:Version>2.2.2.2</RR:Version>
<RR:BuildRevision>3333</RR:BuildRevision>
<RR:BuildTimestamp>2019-01-01T00:00:00.000+02:00</RR:BuildTimestamp>
<RR:Start>2019-01-01T10:10:10.101+02:00</RR:Start>
<RR:End>2019-01-01T11:11:11.111+02:00</RR:End>
<RR:Something>2.222 sek.</RR:Something>
<RR:Anything/>
</RR:ABCInfo>
<work:WorkContext xmlns:work="http://test.com/">1234567890abcdefghijklmnopqrstuvwxyz</work:WorkContext>
</SOAP-ENV:Header>
<QQ:Body>
<TT:testA xmlns:TT="http://abc.test.de/XYZ/2.0.1" xmlns:RR="http://abc.test.de/abc/abcdefgh/1.0">
<TT:testB>
<TT:testC>
<TT:testD>
<TT:testE id="1234567" quellID="09876543">
<TT:data>urn:de:abc:test:whatever</TT:data>
<TT:changeDate>2019-02-02T02:02:02.020+02:00</TT:changeDate>
<TT:part1 listURI="urn:de:abc:codeliste:555" listVersionID="V12">
<code>555_777</code>
<name>Fischers Fritze</name>
</TT:part1>
<TT:piece2>Frische Fische fischen</TT:piece2>
<TT:begin>
<TT:date>20191231</TT:date>
</TT:begin>
</TT:testE>
</TT:testD>
</TT:testC>
</TT:testB>
</TT:testA>
</QQ:Body>
</QQ:Envelope>
I have a XQuery, where I have to return XML. The first element in the returning XML is "result". The other elements in the returning XML should be dynamically created.
I get 2 sequences from outside, though I have made 2 fix Sequences in the following example to test it.
In Sequence No 1 I get the names for the other elements.
In Sequence No 2 I get the related path to the element names in Sequence 1.
I open the XML file an read a path (there might be several elements, though in my example is only one.
Then I want to process this result in a loop and return the dynamic elements.
If I access the path with a fix value (variable $c in the following code) I get the correct value, but then I must know the elements in Sequence 1 and the path in Sequence 2.
If I concatenate the path then I get the value from all elements.
This is my XQuery Code:
declare namespace TT="http://abc.test.de/XYZ/2.0.1";
declare namespace QQ="http://schemas.xmlsoap.org/soap/envelope/";
declare function local:getValue($path) as xs:string {
if (fn:exists($path)) then
(
data($path)
) else (
""
)
};
let $a := ('part1', 'piece2', 'beginDate')
let $b := ('TT:part1/name','TT:piece2', 'TT:begin/TT:date')
for $x in doc("Test.XML")/QQ:Envelope/QQ:Body/TT:testA/TT:testB/TT:testC/TT:testD/TT:testE
return <result>
{
for $item at $ind in $a
let $c := local:getValue($x/TT:part1/name)
let $d := local:getValue($x || concat("/", $b[$ind]))
return element { $item } {$c, " --- ", $d}
}
</result>
Is there a possibility to access the path dynamically?
Thank you in advance.

http://www.xqueryfunctions.com/xq/functx_dynamic-path.html could help - at least did it help ME ;)
The functx:dynamic-path function dynamically evaluates a simple path expression. The function only supports element names and attribute names preceded by #, separated by single slashes. The names can optionally be prefixed, but they must use the same prefix that is used in the input document. It does not support predicates, other axes, or other node kinds. Note that most processors have an extension function that evaluates path expressions dynamically in a much more complete way.

Related

How do I get output for a XQuery in MarkLogic in a one line output?

Will elaborate - when I execute the following command :
let $value := xdmp:forest-status(
xdmp:forest-open-replica(
xdmp:database-forests(xdmp:database("Documents"))))
return $value
Above query returns a lot of information about the database "Documents" forest, like - forest-id, host-id, etc.
I only require that it should return only the "state" of my forest. How do I do that?
Use XPath to select what you want to return.
let $value := xdmp:forest-status(
xdmp:forest-open-replica(
xdmp:database-forests(xdmp:database("Documents"))))
return $value/*:state/text()
Also, no need for a FLWOR you could make it a one-liner:
xdmp:forest-status(
xdmp:forest-open-replica(
xdmp:database-forests(xdmp:database("Documents"))))/*:state/text()
Or you may find that using the arrow operator makes things easier to read instead of nested function calls and tons of parenthesis wrapping them:
(xdmp:database("Documents")
=> xdmp:database-forests()
=> xdmp:forest-open-replica()
=> xdmp:forest-status()
)/*:state/text()
The XML elements in the response are in the http://marklogic.com/xdmp/status/forest namespace. So, you would either need to declare the namespace (i.e. declare namespace f = "http://marklogic.com/xdmp/status/forest";) and use the prefix in your XPath (i.e. f:state), or just use the wildcard as I have done *:state

how to change the XML structure using XQuery

I have a XML file containing Employees Name and the Job done by them.
The structure of the XML file is -
<Employee>AAA#A#B#C#D</Employee>
<Employee>BBB#A#B#C#D</Employee>
<Employee>CCC#A#B#C#D</Employee>
<Employee>DDD#A#B#C#D</Employee>
There are thousands of records and I have to change structure to -
<Employee>
<Name>AAA</Name>
<Jobs>
<Job>A</Job>
<Job>B</Job>
<Job>C</Job>
<Job>D</Job>
</Jobs>
</Employee>
How to get this done using XQuery in BaseX ?
3 XQuery functions, substring-before, substring-after and tokenize are used to get
the required output.
substring-before is used to get the Name.
Similarly, the substring-after is used to get the Job portion.
Then the tokenize function, is used to split the Jobs.
let $data :=
<E>
<Employee>AAA#A#B#C#D</Employee>
<Employee>BBB#A#B#C#D</Employee>
<Employee>CCC#A#B#C#D</Employee>
<Employee>DDD#A#B#C#D</Employee>
</E>
for $x in $data/Employee
return
<Employee>
{<Name>{substring-before($x,"#")}</Name>}
{<Jobs>{
for $tag in tokenize(substring-after($x,"#"),'#')
return
<Job>{$tag}</Job>
}</Jobs>
}</Employee>
HTH...
Tokenizing the string is probably easier and faster. tokenize($string, $pattern) splits $string using the regular expression $pattern, head($seq) returns the first value of a sequence and tail($seq) all but the first. You could also use positional predicates of course, but these functions are easier to read.
for $employee in //Employee
let $tokens := tokenize($employee, '[##]')
return element Employee {
element Name { head($tokens) },
element Jobs {
for $job in tail($tokens)
return element Job { $job }
}
}

XQuery fn:deep-equal - comparing text from XPath with string literal

I'm trying to use XQuery function fn:deep-equal to compare sections of XML documents and I'm getting unexpected behaviour. When comparing XPath value with string literal, function returns false.
For example following code
let $doc :=
<root>
<child><message>Hello</message></child>
</root>
let $message := <message>Hello</message>
let $value := $doc/child/message/text()
let $compareDirectly := fn:deep-equal($value, "Hello") (: -> false :)
let $compareAsString := fn:deep-equal(fn:concat($value, ""), "Hello") (: -> true :)
let $comparePath := fn:deep-equal($value, $message/text()) (: -> true :)
return
<results>
<value>{$value}</value>
<directly>{$compareDirectly}</directly>
<asString>{$compareAsString}</asString>
<path>{$comparePath}</path>
</results>
Executed using Saxon, XQuery program generates following XML
<?xml version="1.0" encoding="UTF-8"?>
<results>
<value>Hello</value>
<directly>false</directly>
<asString>true</asString>
<path>true</path>
</results>
I'd expect $compareDirectly to be true (same as two other examples), but fn:deep-equal does not seem to work as I would intuitively expect. I'm wondering whether this is correct behaviour.
Is there any better wah how to compare two XML nodes?
I'm lookig for some generic solution which could be used for both XML snippets (like values of $doc or $message in example) and also for this special case with string literal.
From the spec:
To be deep-equal, they must contain items that are pairwise deep-equal; and for two items to be deep-equal, they must either be atomic values that compare equal, or nodes of the same kind, with the same name, whose children are deep-equal.
So this is why it doesn't return true when comparing a text node to an atomic type. In your other two examples you are comparing 2 string atomic types. It looks as if you don't need deep-equal, which compares nodes recursively. If that's the case, then you can just compare the strings:
$doc/child/message/string() eq $message/string()
=> true()
If there are other requirements, then you may need to update your example to demonstrate those more clearly.

To remove the node but keep the value inside intact through XQuery

I have a content.xml modelled as below
<root>
<childnode>
Some text here
</childnode>
</root>
I am trying to remove the <childnode> and update the content.xml with only the value of it
so the output looks like
<root>
Some Text here
</root>
I wrote a function to perform this but anytime I run it it gives me error as "unexpected token: modify". I was thinking of a way to accomplish this without using functx functions.
xquery version "1.0";
declare namespace request="http://exist-db.org/xquery/request";
declare namespace file="http://exist-db.org/xquery/file";
declare namespace system="http://exist-db.org/xquery/system";
declare namespace util="http://exist-db.org/xquery/util";
declare namespace response="http://exist-db.org/xquery/response";
declare function local:contentUpdate() {
let $root := collection('/lib/repository/content')//root/childNode
let $rmChild := for $child in $root
modify
(
return rename node $child as ''
)
};
local:updateTitle()
Thanks in advance
There are multiple problems with your query:
Updating functions must be declared as updating.
You're calling another function than you defined (probably you didn't notice as there still have been syntax errors).
Rename node expects some element (or processing instruction, attribute) as target, the empty string is not allowed.
At least BaseX doesn't allow updating statements when defining code as XQuery 1.0. Maybe exist doesn't care about this, try adding it if you need to know.
You do not want to rename, but replace all <childnode />s with its contents, use replace node.
This code fixes all these problems:
declare updating function local:contentUpdate() {
let $root := collection('/lib/repository/content')
return
for $i in $root//childnode
return
replace node $i with $i/data()
};
local:contentUpdate()
eXist-db's XQuery Update syntax is documented at http://exist-db.org/exist/update_ext.xml. Note that this syntax predates the release of the XQuery Update Facility 1.0, so the syntax is different and remains unique to eXist-db.
The way to do what you want in eXist-db is as follows:
xquery version "1.0";
declare function local:contentUpdate() {
let $root := doc('/db/lib/repository/content/content.xml')/root
return
update value $root with $root/string()
};
local:contentUpdate()
The primary changes, compared to your original code, are:
Inserted the eXist-db syntax for your update
Prepended '/db' to your collection name, as /db is the root of the database in eXist-db; replaced the collection() call with a doc() call, since you stated you were operating on a single file, content.xml
Changed //root to /root, since "root" is the root element, so the // (descendant-or-self) axis is extraneous
Replaced updateTitle() with the actual name of the function, contentUpdate
Removed the extraneous namespace declarations
For more on why I used $root/string(), see http://community.marklogic.com/blog/text-is-a-code-smell.

XQuery - problem with recursive function

Im new on this project and am going to write, what i thought was a simple thing. A recursive function that writes nested xml elements in x levels (denoted by a variable). So far I have come up with this, but keeps getting a compile error. Please note that i have to generate new xml , not query existing xml:
xquery version "1.0";
declare function local:PrintTest($amount)
{
<test>
{
let $counter := 0
if ($counter <= $amount )
then local:PrintTest($counter)
else return
$counter := $counter +1
}
</test>
};
local:PrintPerson(3)
My error is:
File Untitled1.xquery: XQuery transformation failed
XQuery Execution Error!
Unexpected token - " ($counter <= $amount ) t"
I never understood xquery, and cant quite see why this is not working (is it just me or are there amazingly few resources on the Internet concerning XQuery?)
You have written this function in a procedural manner, XQuery is a functional language.
Each function body can only be a single expression; it looks like you are trying to write statements (which do not exist in XQuery).
Firstly, your let expression must be followed by a return keyword.
return is only used as part of a FLWOR expression, a function always evaluates to a value. As you have written it return is equivalent to /return and so will return a node called return.
The line $counter := $counter + 1 is not valid XQuery at all. You can only set a variable like this with a let expression, and in this case it would create a new variable called counter which replaced the old one, that would be in scope only in the return expression of the variable.
The correct way to do what you are trying to do is to reduce the value of $argument each time the function recurses, and stop when you hit 0.
declare function local:Test($amount)
{
if ($amount == 0)
then ()
else
<test>
{
local:Test($amount - 1)
}
</test>
};
local:Test(3)
Note that I have changed the name of the function to Test. The name "PrintTest" was misleading, as this implies that the function does something (namely, printing). The function in fact just returns a node, it does not do any printing. In a purely functional langauge (which XQuery is quite close to) a function never has any side effects, it merely returns a value (or in this case a node).
The line $counter := $counter + 1 is valid XQuery Scripting.

Resources