XQuery constructs string object with sequential ancestor node names - xquery

a.xml:
<execution xmlns="http://www.example.org">
<header>
<messageId>FX123</messageId>
</header>
<isCorrection>false</isCorrection>
<trade>
<tradeHeader>
<partyTradeIdentifier>
<partyReference href="ptyA"/>
<tradeId>12345</tradeId>
</partyTradeIdentifier>
<tradeDate>2019-12-21</tradeDate>
</tradeHeader>
<fxTargetKnockoutForward>
<target>
<accumulationRegion>
<lowerBound>
<condition>AtOrAbove</condition>
</lowerBound>
<upperBound>
<condition>Below</condition>
</upperBound>
</accumulationRegion>
<accumulationRegion>
<lowerBound>
<condition>AtOrAbove</condition>
<initialValue>1.1000</initialValue>
</lowerBound>
<multiplier>2</multiplier>
</accumulationRegion>
<knockoutLevel>
<amount>
<currency>CAD</currency>
<amount>100000.00</amount>
</amount>
<targetStyle>Exact</targetStyle>
</knockoutLevel>
</target>
<expirySchedule>
<adjustedDate>2019-12-23</adjustedDate>
<adjustedDate>2020-01-27</adjustedDate>
<adjustedDate>2020-02-25</adjustedDate>
<adjustedDate>2020-03-26</adjustedDate>
</expirySchedule>
<settlementSchedule>
<dateAdjustments>
<businessDayConvention>FOLLOWING</businessDayConvention>
<businessCenters>
<businessCenter>CATO</businessCenter>
<businessCenter>USNY</businessCenter>
</businessCenters>
</dateAdjustments>
<adjustedDate>2019-12-24</adjustedDate>
<adjustedDate>2020-01-28</adjustedDate>
<adjustedDate>2020-02-26</adjustedDate>
<adjustedDate>2020-03-27</adjustedDate>
</settlementSchedule>
<fixingInformationSource>
<rateSource>Reuters</rateSource>
<rateSourcePage>WMRSPOT09</rateSourcePage>
</fixingInformationSource>
</fxTargetKnockoutForward>
</trade>
</execution>
Logic: I pass in-memory XML (a.xml) and targeted element (“trade”) as parameters -> the function local:array-qname evaluates all of this element’s descendants -> Whenever the descendant’s node name is the same as its sibling’s node name, it is considered a candidate -> the function walks backwards to retrieve all of its ancestor node name (except the root node) up to the passed element (“trade”) level.
The desired result: string array objects, of each object contains all of the candidate's sequential ancestor node names and its own node name. The expected result is:
( ("trade","fxTargetKnockoutForward","target","accumulationRegion"),
("trade","fxTargetKnockoutForward","expirySchedule","adjustedDate"),
("trade","fxTargetKnockoutForward","settlementSchedule","dateAdjustments","businessCenters","businessCenter"),
("trade","fxTargetKnockoutForward","settlementSchedule","adjustedDate") )
The library module is:
xquery version "1.0-ml";
declare function local:array-qname(
$doc as node()*,
$element as xs:string
) as xs:string*
{
let $e := $doc//*[name() = $element]
for $d in $e/descendant::*[name() = name(following-sibling::*[1])],
$a in $d/ancestor::*[not(name() = name($doc/*))]/name(.)
return
for $_ in $a
return
<a>
( {xs:QName($a)},{xs:QName(local-name($d))} )
</a>
};
let $doc := doc("a.xml")
return
local:array-qname($doc, "trade")
But it goes awry:
(trade,fxTargetKnockoutForward,target,accumulationRegion),
(trade,fxTargetKnockoutForward,expirySchedule,adjustedDate),
(trade,fxTargetKnockoutForward,settlementSchedule,dateAdjustments,businessCenters,businessCenter),
(trade,fxTargetKnockoutForward,settlementSchedule,adjustedDate),
How can I get my module work?

The following solution is compliant with the requirement…
declare function local:array-qname(
$doc as document-node(),
$name as xs:string
) {
for $e in $doc//*[name() = $name]
for $d in $e/descendant::*[name() = name(following-sibling::*[1])]
return <a>{
for $name in $d/ancestor-or-self::*[not(. << $e)]
return node-name($name)
}</a>
};
let $doc := doc('a.xml')
return local:array-qname($doc, 'trade')
…but it differs from the expected output as it yields duplicate paths. If duplicates are to be avoided, and if a string representation is sufficient, distinct-values can be used:
distinct-values(
for $e in $doc//*[name() = $name]
for $d in $e/descendant::*[name() = name(following-sibling::*[1])]
return string-join($d/ancestor-or-self::*[not(. << $e)]/name(), ' ')
)

With
declare variable $element-name as xs:QName external := QName('http://www.example.org', 'trade');
let $base := //*[node-name() = $element-name]
for $d in $base//*[node-name() = following-sibling::*[1]/node-name()]
return
'('
|| $element-name
|| ': ('
|| ($d/ancestor-or-self::* except $d/ancestor::*[node-name() = $element-name]/ancestor-or-self::*)/node-name() => string-join(', ')
|| '))'
I get
(trade: (fxTargetKnockoutForward, target, accumulationRegion))
(trade: (fxTargetKnockoutForward, expirySchedule, adjustedDate))
(trade: (fxTargetKnockoutForward, expirySchedule, adjustedDate))
(trade: (fxTargetKnockoutForward, expirySchedule, adjustedDate))
(trade: (fxTargetKnockoutForward, settlementSchedule, dateAdjustments, businessCenters, businessCenter))
(trade: (fxTargetKnockoutForward, settlementSchedule, adjustedDate))
(trade: (fxTargetKnockoutForward, settlementSchedule, adjustedDate))
(trade: (fxTargetKnockoutForward, settlementSchedule, adjustedDate))
I am not sure from your description "Whenever the descendant’s node name is the same as its sibling’s node name, it is considered a candidate -> the function walks backwards to retrieve all of its ancestor node name (except the root node) up to the passed element (“trade”) level." why duplicate adjustedDate are not in your desired output as it seems the samples contains various elements of that name that meet the condition.

Related

Compare Master data with elements present in the XML

<MasterData>
<Name>AA</Name>
<EmpId>123</EmpId>
<AccountNo>111</AccountNo>
<IFSC>ABC</IFSC>
<AccountData>
<AccountNo>111</AccountNo>
<IFSC>ABC</IFSC>
</AccountData>
<AccountData>
<AccountNo>222</AccountNo>
<IFSC>DEF</IFSC>
</AccountData>
</MasterData>
I have an xml like this in my database,I have a requirement to check the combination of AccountNo+IFSC present in the MasterData(not under the AccountData section) and compare with all documents present in the collection and check whether its matching to the data present in the AccountData section,If its matching identify the URI of the document.
First identify the unique combination of AccountNo+IFSC from Masterdata section and then check whether this combination present under any of the AccountData section, there are more elements in this xml other than AccountNo and IFSC
If you had range indexes on the AccountNo and IFSC elements, then you could:
retrieve the set of values from AccountNo, IFSC, and a cts:uri-reference() with cts:value-tuples().
create a map using a composite key with the AccountNo and IFSC values and the URIs as the values for those map entries
prune any entry that only has one URI associated
return the map that will have the set of URIs corresponding to each combo of AccountNo and IFSC value
Something like this:
let $accountNumber-IFSC :=
cts:value-tuples(
(
cts:element-reference(xs:QName("AccountNo")),
cts:element-reference(xs:QName("IFSC")),
cts:uri-reference()
)
)
let $map := map:new()
let $_create_map_value_to_uris := for $co-occurrence in $accountNumber-IFSC
let $key := concat($co-occurrence[1], " ", $co-occurrence[2])
let $value := (map:get($map, $key), $co-occurrence[3])
return map:put($map, $key, $value)
let $_prune_single_uri :=
for $key in map:keys($map)
let $value := map:get($map, $key)
where not(tail($value))
return
map:put($map, $key, ())
return
$map
If you just wanted the list of URIs, you can invert the map: -$map and return it's keys: return map:keys(-$map)
If you had a range-index on the EmpId you could pivot on that instead of the document URIs.
Using the Optic API functions, you can do something similar with element-range indexes:
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";
op:from-lexicons(
map:entry("AccountNo", cts:element-reference(xs:QName("AccountNo")))
=> map:with("IFSC", cts:element-reference(xs:QName("IFSC")))
=> map:with("URI", cts:uri-reference())
)
=> op:group-by(
("IFSC", "AccountNo"),
(
op:group-concat("URIs", "URI", map:entry("separator", ", ")),
op:count("count", op:col("URI"))
)
)
=> op:where(op:gt(op:col("count"), 1))
=> op:result()

Checking absence of attribute with cts:query

I have an XML fragment where I want to have different queries based in the existence of the id attribute:
<author order="1"
id="99999999"
initials="A."
given-names="Able"
surname="Baker"
fullname="Able Baker"/>
I have tried:
let $first-query := if ($first)
then cts:or-query((
cts:element-attribute-word-match(xs:QName("author"), xs:QName("given-names"), $first || "*", ("collation=http://marklogic.com/collation/codepoint")),
cts:element-attribute-word-match(xs:QName("author"), xs:QName("initials"), $first || "*", ("collation=http://marklogic.com/collation/codepoint"))
))
else ()
let $last-query := if ($last)
then cts:element-attribute-word-match(xs:QName("author"), xs:QName("surname"), $last || "*", ("collation=http://marklogic.com/collation/codepoint"))
else ()
let $author-no-id-query :=
cts:and-query((
cts:not-query(
cts:element-attribute-value-query(xs:QName("author"), xs:QName("id"), "*")
),
$first-query,
$last-query
))
let $query := cts:element-query(xs:QName("author"),
cts:or-query(($author-no-id-query, $author-id-query
)))
If the id exists, then a different query takes place and a match against the id occurs. How do I detect an absence of an attribute in MarkLogic?
I have inserted two test documents into the database:
xdmp:document-insert('/example.xml', <author order="1"
id="99999999"
initials="A."
given-names="Able"
surname="Baker"
fullname="Able Baker"/>)
xdmp:document-insert('/example2.xml', <author order="1"
initials="A."
given-names="Able"
surname="Baker"
fullname="Able Baker"/>)
And run the following query against these documents:
cts:search(fn:doc(),
cts:element-query(xs:QName('author'), cts:and-query((
cts:not-query(cts:element-attribute-value-query(xs:QName('author'), xs:QName('id'), '*', ("wildcarded")))
)
)))
This search only matches the document where the ID attribute does not exist.

XQuery with if condition in for loop

I have written xquery to return results in normal way.
let $results := //data:data
return
<result>
{
for $i in $results
return
<documentInformation>
<id>{data($i/DATA:ID)}</id>
<status>{data($i/#status)}</status>
<title>{data($i/data:title)}</title>
<displayName>{data($i/DATA:DISPLAYNAME)}</displayName>
</documentInformation>
}
</result>
Now, I have to filter out the results in for loop with some condition like
(pseudo logic)
if id = 'abc' and status ="closed"
then skip the row
else add row.
I have tried several ways. but could not run the query..
Try this:
<result>
{
for $i in //data:data
where fn:not($i/DATA:ID = 'abc' and $i/#status = "closed")
return
<documentInformation>
<id>{data($i/DATA:ID)}</id>
<status>{data($i/#status)}</status>
<title>{data($i/data:title)}</title>
<displayName>{data($i/DATA:DISPLAYNAME)}</displayName>
</documentInformation>
}
</result>
Note that the XPath //data:data may have a lot of work to do, but that's a separate matter.
You Can also use if condition instead of where
<result>
{
for $i in //data:data
return
if($i/DATA:ID != 'abc' and $i/#status != "closed")
then
(
<documentInformation>
<id>{data($i/DATA:ID)}</id>
<status>{data($i/#status)}</status>
<title>{data($i/data:title)}</title>
<displayName>{data($i/DATA:DISPLAYNAME)}</displayName>
</documentInformation>
)
else ()
}
</result>

wrapping child node content in xquery

I have what I hope is a really simple question, but I'm a novice at xquery and I can't make this work:
I have the following bit of xml:
<collation>1<num>12</num> 2<num>12</num> ||
I<num>8</num>-V<num>8</num>, 1 flyleaf</collation>
That I need to transform so that becomes the content of a new node, like so:
<note displayLabel="Collation: Notes">1(12) 2(12) || I(8)-V(8), 1 flyleaf<note>
I am using the following xquery code to attempt to do this:
<note displayLabel="Collation:Notes">{for $t in doc("collation.xml")//collation,
$h in distinct-values($t)
return
????
</note>
The problem is that I can either display all of the content (so without the parentheses) using data($t), or I can display just the things I want to be in parentheses (the information in the tags) using data($t/num) but I can't figure out how to display both with the items in tags wrapped in parentheses. I'm sure it's a really simple answer but I can't find it.
This is a good job for recursion:
declare function local:render(
$n as node()
) as node()?
{
typeswitch($n)
case element(num) return text{concat('(', $n, ')')}
case element(collation) return
<note displayLabel="Collation: Notes">{
for $n in $n/node()
return local:render($n)
}</note>
case element() return element { node-name($n) } {
for $n in $n/node()
return local:render($n)
}
default return $n
};
local:render(
<collation>1<num>12</num> 2<num>12</num> || I<num>8</num>-V<num>8</num>, 1 flyleaf</collation>)
=>
<note displayLabel="Collation: Notes">1(12) 2(12) || I(8)-V(8), 1 flyleaf</note>

How to convert text inside a node into child node using xquery update?

I have a xml document like
<root>
<first>
First Level
<second>
second level
<third>
Third Level
</third>
</second>
<second2>
another second level
</second2>
</first>
</root>
How to convert this document with all nodes, that means if a node contains text and child node convert text into a child node (let's say childtext) using xquery-update
<root>
<first>
<childtext>First Level</childtext>
<second>
<childtext>second level</childtext>
<third>
Third Level
</third>
</second>
<second2>
another second level
</second2>
</first>
</root>
And here is what I tried:
let $a :=
<root>
<first>
First Level
<second>
second level
<third>
Third Level
</third>
</second>
<second2>
another second level
</second2>
</first>
</root>
return
copy $i := $a
modify (
for $x in $i/descendant-or-self::*
return (
if($x/text() and exists($x/*)) then (
insert node <childtext>
{$x/text()}
</childtext> as first into $x
(: here should be some code to delete the text only:)
) else ()
)
)
return $i
I could not delete the text which has sibling node.
As you want to replace an element, you should simply use the replace construct, instead of inserting the new element and deleting the old one. Seems much simpler to me:
copy $i := $a
modify (
for $x in $i/descendant-or-self::*[exists(*)]/text()
return replace node $x with <childtext>{$x}</childtext>
)
return $i
The modified solution is from #dirkk here:
copy $i := $a
modify (
for $x in $i/descendant-or-self::*
return (
if($x/text() and exists($x/*)) then (
if(count($x/text())=1) then (
replace node $x/text() with <child> {$x/text()}</child>
) else (
for $j at $pos in 1 to count($x/text())
return
replace node $x/text()[$pos] with <child>{$x/text()[$pos]}</child>
)
) else ()
)
)
return $i
Thank you Once again.

Resources