I'm using BaseX, which supports XQuery 3.0.
Let's say I have an dataset like this, ordered on the value of <start>:
<element>
<start>1</start>
<end>2</end>
</element>
<element>
<start>2</start>
<end>4</end>
</element>
<element>
<start>5</start>
<end>6</end>
</element>
I want to connect these elements by their end and start values, and group connecting elements together:
<block>
<start>1</start>
<end>4</end>
</block>
<block>
<start>5</start>
<end>6</end>
</block>
In Oracle, we could do something like this with CONNECT BY PRIOR. How can we do this in XQuery?
You can implement this behavior using a tumbling window, which lets you group based on conditions over element spans. Sliding and tumbling windows require XQuery 3.0, which is supported by BaseX.
let $items := (
<element>
<start>1</start>
<end>2</end>
</element>,
<element>
<start>2</start>
<end>4</end>
</element>,
<element>
<start>5</start>
<end>6</end>
</element>
)
for tumbling window $window in $items
start $start when fn:true()
only end $end next $next when not(
$end/end eq $next/start
)
return <block>{
$start/start,
$end/end
}</block>
Related
I have a listPers.xml (TEI List containing persons, obviously ) . I want to write a function to update the listPers.xml
My function looks like this:
declare function app:addPerson($node as node(), $model as map(*)) {
let $person := "<person xml:id=""><persName><forename>Albert</forename><surname>Test</surname></persName></person>"
let $list := doc(concat($config:app-root, '/resources/listPers_test.xml'))
return
update insert $person into $list//tei:listPerson
};
And the listPerson.xml
looks more or less like a typical list with person-entries
I have a tei:header (here omitted) followed by
<text>
<body>
<listPerson xml:id="person">
<person xml:id="abbadie_jacques">
<persName ref="http://d-nb.info/gnd/100002307">
<forename>Jacques</forename>
<surname>Abbadie</surname>
</persName>
<note>Prediger der französisch-reformierten Gemeinde in <rs type="place" ref="#berlin">Berlin</rs>
</note>
</person>
</body>
</text>
</TEI>
(sorry for ruining indentions, it's just an excerpt )
I do not get an error, which means that my app:addPerson should be fine, right?
I want the listPers_test to look like this:
<text>
<body>
<listPerson xml:id="person">
<person xml:id="abbadie_jacques">
<persName ref="http://d-nb.info/gnd/100002307">
<forename>Jacques</forename>
<surname>Abbadie</surname>
</persName>
<note>Prediger der französisch-reformierten Gemeinde in <rs type="place" ref="#berlin">Berlin</rs>
</note>
</person>
<!-- here comes the output that I wish to have :-) -->
<person xml:id=""><persName><forename>Albert</forename><surname>Test</surname></persName></person>
</body>
</text>
</TEI>
In the long run, I aim for an html-form that allows users to input names etc., where ids are generated using sth like
to-lowercase(concat($surname, "_", $forename));
But I will not get into my questions regarding forms and xquery, as I have barely done a quick Google-trip regarding html forms and xquery!
Can anyone hint me at why I do not get the listPers_test.xml file updated with the second value? :-)
All the best and thanks in advance to everyone,
K
Alright, I have a solution for anyone interested in it:
My first snippet $person:= ... contains a STRING, not an element.Changing the line
let $person := "<person xml:id=""><persName><forename>Albert</forename><surname>Test</surname></persName></person>"
to this one actually solves the issue:
let $person := <tei:person xml:id=""><persName><forename>Albert</forename><surname>Test</surname></persName></tei:person>
I'm new to this and it's easy to get stuck in SQL mode.
I've prepared a small example to illustrate my problem.
<bigXML>
<Product>
<Attributes>
<Attribute>
<Descriptions>
<Description languageCode='DE'>Farbe</Description>
<Description languageCode='EN'>Color</Description>
</Descriptions>
<Value>0000ff</Value>
</Attribute>
<Attribute>
<Descriptions>
<Description languageCode='DE'>Länge</Description>
<Description languageCode='EN'>Length</Description>
</Descriptions>
<Value>2 mm</Value>
</Attribute>
<Attribute>
<Descriptions>
<Description languageCode='DE'>Name</Description>
<Description languageCode='EN'>Name</Description>
</Descriptions>
<Value>Circle</Value>
</Attribute>
</Attributes>
</Product>
</bigXML>
I want to get to the VALUE of 0000ff.
Here's my attempt:
<Reply>{for $i in //Product where $i/Attributes/Attribute/Descriptions/Description="Color" return $i/Attributes/Attribute/Value}</Reply>
It returns the values of all VALUE tags eventhough I specifically (maybe?) asked for the one where Description is Color.
Please tell me what part of the WHERE syntax I'm getting wrong.
Since you only fix the Product node in the for loop and then traverse down to the Attributes twice (once to check, and once to retrieve the Value), you cannot make sure that you only get values of Color attributes. Your query says: "For every Product that has one or more Color attributes, return all attribute values."
One easy fix is to just iterate over attributes instead of products:
<Reply>{
for $attr in //Product/Attributes/Attribute
where $attr/Descriptions/Description="Color"
return $attr/Value
}</Reply>
If you also need the product reference, you can use nested loops:
<Reply>{
for $i in //Product
for $attr in $i/Attributes/Attribute
where $attr/Descriptions/Description="Color"
return $attr/Value
}</Reply>
You can also replace the where with an XPath predicate and make the whole expression shorter:
<Reply>{
//Product/Attributes/Attribute[Descriptions/Description="Color"]/Value
}</Reply>
Try
//Attribute[Descriptions[Description='Color']]/Value
It selects 0000ff.
I want to insert a node using below code but if i will rerun the code i don't want my node to be repeated twice-
let $doc := fn:doc("abc.xml")
(: abc.xml looks like--
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root> :)
let $element := element Root{
element A{"A"},
element B{"B"},
element C{"C"},
element D{"D"},
element E{"E"}
}
let $doc := xdmp:node-insert-after(doc("abc.xml")//value4, $element)
return doc("abc.xml")
Whenever i am running this query my ELEMENT is getting inserted after value4 but i want that if i am running this multiple times then my element should get inserted only once and not multiple times.
Example- If i am running this 2 times-
Actual Output-
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<Root>
<A>A</A>
<B>B</B>
<C>C</C>
<D>D</D>
<E>E</E>
</Root>
<Root>
<A>A</A>
<B>B</B>
<C>C</C>
<D>D</D>
<E>E</E>
</Root>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root>
Expected Output-
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<Root>
<A>A</A>
<B>B</B>
<C>C</C>
<D>D</D>
<E>E</E>
</Root>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root>
Any Suggestions ??
Before inserting the node, check that it's not already there:
if (empty(child::Root)) then xdmp:update....
Note that you are using xdmp:node-insert-after -- this will do what the function name says (insert after) each time its called. As commented, you can conditionally call xdmp:node-update instead. Alternatively you can use xquery directly:
doc("file.xml" )/root ! <root>{ ./*[ . << ./value4] , $element , $a/value4, ./*[. >> ./value4] }</root>
Note: the "<<" operator compares "document order" (position not value)
I'm new to Xquery. I have a requirement of rewriting the API response into custom xml format.
Input file format:
<root> <_1>
<dataType>
<name>XVar(Osmo [mOsmol/kg])</name>
<term>M185</term>
<type>XVar</type>
</dataType>
<values>305</values>
<values>335</values> </_1> <_2>
<dataType>
<name>XVar(DO (2) [%])</name>
<term>M199</term>
<type>XVar</type>
</dataType>
<values>12</values>
<values>33</values>
</_2> <_3>
<dataType>
<name>Maturity</name>
<type>Maturity</type>
</dataType>
<values>0</values>
<values>0.73600054</values>
</_3> </root>
Expected output:
<element> <XVar(Osmo [mOsmol/kg]> 305</XVar(Osmo [mOsmol/kg]>
<XVar(Osmo [mOsmol/kg]> 335</XVar(Osmo [mOsmol/kg]> <XVar(DO (2)[%])>
12</XVar(DO (2) [%])> <XVar(DO (2) [%])>33 </XVar(DO (2) [%])>
<Maturity>0</Maturity> <Maturity>0.73600054</Maturity> </element>
no of nodes (dataType -> name) will vary in each input file and also
Values will be dynamics .
currently using the below code.
let $input:= /root for $i in $input//values
return <element>
<name>{$i/../dataType/name/text()}</name> <values>{$i/text()} </values>
</element>
but all data are coming in and . my requirement is to
keep the node name as {$i/../dataType/name/text()} as values should
be {$i/text()} -
for the input file sample ideally there should be three different
nodes and its values.
Can any one help me on this?
i am in the middle of creating xquery replace-value of node action in XQuery.
But seems this code is not working therefore the IF-ELSE statement is always going to else.
This is my code:
declare function local:replacing($contextData as element())
as element()*
{
copy $pipeline := $contextData/Handler/Data/*
modify(
if(not(empty(data($pipeline/Payload/sample/transactionType)))) then
replace value of node $pipeline/Payload/sample/transactionType with 'XXX' else (),
if(not(empty(data($pipeline/Payload/sample/revision)))) then
replace value of node $pipeline/Payload/sample/revision with 'XXX' else ()
)
return $pipeline
};
I try against this sample XML but the result is always not XXX when the field revision is having value. (this always goes to else statement)
let $result :=
<root>
<Handler>
<Data>
<root>
<Payload>
<sample>
<transactionType></transactionType>
<revision>123</revision>
<board>1</board>
<mission>1</mission>
<method>Manual</method>
<listOfBoard>
<board>
<type>small</type>
<amount>5054</amount>
<token>300</token>
</board>
</listOfBoard>
<pricing>300</pricing>
<playing>Wed</playing>
</sample>
</Payload>
</root>
</Data>
</Handler>
</root>
Current Result:
<root>
<Payload>
<sample>
<transactionType/>
<revision>123</revision>
<board>1</board>
<mission>1</mission>
<method>Manual</method>
<listOfBoard>
<board>
<type>small</type>
<amount>5054</amount>
<token>300</token>
</board>
</listOfBoard>
<pricing>300</pricing>
<playing>Wed</playing>
</sample>
</Payload>
</root>
Expected Result
<root>
<Payload>
<sample>
<transactionType/>
<revision>XXX</revision>
<board>1</board>
<mission>1</mission>
<method>Manual</method>
<listOfBoard>
<board>
<type>small</type>
<amount>5054</amount>
<token>300</token>
</board>
</listOfBoard>
<pricing>300</pricing>
<playing>Wed</playing>
</sample>
</Payload>
</root>
Any ideas for this?
Update
As recommended by Har below, i change the code into:
if(not(empty(data($pipeline/Payload/sample/transactionType)))) then
replace value of node $pipeline/Payload/sample/transactionType with 'XXX'
else (
if(not(empty(data($pipeline/Payload/sample/revision)))) then
replace value of node $pipeline/Payload/sample/revision with 'XXX' else ()
)
But seems the result is the same. It still goes to else () statement.
Any ideas?
Thank you before.
The problem was, empty() checks for empty sequence, so sequence containing one empty string is considered true by empty(). You can just pass data() result to if since empty has Effective Boolean Value of false :
if(data($pipeline/Payload/sample/transactionType)) then
replace value of node $pipeline/Payload/sample/transactionType with 'XXX' else (),
if(data($pipeline/Payload/sample/revision)) then
replace value of node $pipeline/Payload/sample/revision with 'XXX' else ()