recursive replacement - xquery

How to find all the empty string and replace it with 'empty' element in xquery without using xquery updating. I achieved this using xquery update but I want to get without using update.

This kind of transformation is easier in XSLT. In XSLT 3.0 you could write:
<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="*[.='']"><empty/></xsl:template>
</xsl:transform>
The reason this works is that "on-no-match='shallow-copy'" causes a recursive processing of the input tree, where if there is no explicit template rule for a particular element, the processor just copies the element and recurses down to process its children.
XQuery doesn't have a built-in primitive to do this, but you can achieve the same thing by writing it out explicitly. You need a recursive function:
declare function local:process($e as node()) as node() {
if ($e[self::element()])
then
if ($e='')
then <empty/>
else
element {node-name($e)} {
$e/#*,
$e/child::node()/local:process(.)
}
else (:text, comment, PI nodes:)
$e
};
local:process($in/*)

Related

How to return a existing document except an element using xquery in marklogic

I have a document I have written to ignore the element id from the document and return the document as below but throws error
let $ex := fn:doc("/name/docs")/* except $ex//sar:id return $ex
returns
Error: undefined variable $ex
You are attempting to reference the let variable within the let statement. You can't do that.
You could let a variable for the document, and then use it in a similar statement:
let $doc := fn:doc("/name/docs")
let $ex := $doc/* except $doc//sar:id
return $ex
Or you could use a simple mapping operator:
let $ex := fn:doc("/name/docs") ! (./* except .//sar:id)
return $ex
That will return a sequence of child elements from the document (but there is only one immediate child element for a doc, so unless it happens to be sar:id, that likely won't achieve what you are trying to do.
If you want to return a new document that has all of the content except for the sar:id element, then you could run it through an XSLT that is a modified identity transform with an empty template matching that element to exclude:
declare variable $XSLT :=
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:sar="sar-namespace-needs-to-be-updated-here">
<!--this generic template matches on, and copies all attributes and nodes by default-->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!--this empty template will ensure that the sar:id is dropped from the output-->
<xsl:template match="sar:id"/>
</xsl:stylesheet>;
xdmp:xslt-eval($XSLT, fn:doc("/name/docs"))
Otherwise you could run it through a recursive descent function with a typeswitch. I find XSLT shorter and easier to write, but that's a personal preference.

MarkLogic - How to insert element into XML

How to insert the node in XML.
let $a := <a><b>bbb</b></a>)
return
xdmp:node-insert-after(doc("/example.xml")/a/b, <c>ccc</c>);
Expected Output:
<a><c>ccc</c><b>bbb</b></a>
Please help to get the output.
You should be using xdmp:node-insert-before I believe in the following way:
xdmp:document-insert('/example.xml', <a><b>bbb</b></a>);
xdmp:node-insert-before(fn:doc('/example.xml')/a/b, <c>ccc</c>);
fn:doc('/example.xml');
(: returns <a><c>ccc</c><b>bbb</b></a> :)
Nodes are immutable, so in-memory mutation can only be done by creating a new copy.
The copy can use the unmodified contained nodes from the original:
declare function local:insert-after(
$prior as node(),
$inserted as node()+
) as element()
{
let $container := $prior/parent::element()
return element {fn:node-name($container)} {
$container/namespace::*,
$container/attribute(),
$prior/preceding-sibling::node(),
$prior,
$inserted,
$prior/following-sibling::node()
}
};
let $a := <a><b>bbb</b></a>
return local:insert-after($a//b, <c>ccc</c>)
Creating a copy in memory and then inserting the copy is faster than inserting and modifying a document in the database.
Depending on how many documents are inserted, the difference could be significant.
There are community libraries for copying with changes, but sometimes it's as easy to write a quick function (recursive where necessary).
You can use below code to insert the element into the XML:
xdmp:node-insert-child(fn:doc('directory URI'),element {fn:QName('http://yournamesapce','elementName') }{$elementValue})
Here we use fn:QName to remove addition of xmlns="" in added node.

Xquery - what is wrong here

I would like the node replace to act on the $person variable. What do I need to change?
The following code should change the name of the people in the sequence to X.
declare function local:ChangeName($person)
{
xdmp:node-replace($person//Name/text, text { "X" } )
<p>{$person}</p>
};
let $b := <Person>
<Name>B</Name>
<IsAnnoying>No</IsAnnoying>
</Person>
let $j := <Person>
<Name>J</Name>
<IsAnnoying>No</IsAnnoying>
</Person>
let $people := ($b, $j)
return $people ! local:ChangeName(.)
xdmp:node-replace() only operates on persisted documents, not on in-memory documents.
Your local:ChangeName() function could construct the Person and Name elements but copying the IsAnnoying element, as in:
declare function local:ChangeName($person)
{
<p>
<Person>
<Name>X</Name>
{$person//IsAnnoying}
</Person>
</p>
};
For more complex transformations, consider a recursive typeswitch or XSLT transform.
As Erik noted above, you can't apply the xdmp update functions to in-memory nodes. If you have a strong need to to do in-memory node updates, Ryan Grimm's memupdate library can come in handy. It basically does the grunt work of the recursive traversal for you. But beware, in-memory updates do not scale to large documents because it requires making a copy to do an update.
https://github.com/marklogic/commons/tree/master/memupdate
I don't have a ML instance running, so I couldn't test it (maybe there are more issues), but it has to be
$person//Name/text()
instead of
$person//Name/text
The Xpath:
$person//Name/text
should be:
$person//Name/text()
The XPath is looking to replace the text node, which doesn't exist.
Also note that the results of xdmp:node-replace will not be visible in the same transaction. To see the results you need to start a new transaction.

xquery beginner- how to use an if-then-else within a FLWOR statement

I want to extract some content from a web page's XML equivalent, using XQuery.
Here, I want to use if-then-else -- if a specific condition is met, then one value is returned by the xquery expression, otherwise a different value is returned.
This is the expression I have with me--
I am working to extract some content from a web page.
Given below is the Xquery code I am using-- I am using a XQuery library to parse through XML which is obtained by transforming a HTML web page into XML...after that I am extracting some specific content from that XML page...
declare variable $doc as node() external;
let $select_block := $doc//div[#class="randomclass" and contains(.,'MatchingText')]
for $select_link in $select_block/a
for $select_link_url in $select_link/#href
where contains($assignee_link_url,'inassignee')
return data($select_link)
Now how do I use if-then-else within this expression?
I tried to add an if-then immediately after 'return' keyword but I am getting error...
Basically, if some content is found for $select_block above, then data($select_link) should be returned, otherwise the static text 'Missing Value' should be returned.
What is the correct way of using if-then-else with the above xquery expression?
If I understand what you are trying to achieve correctly, then the following should work:
declare variable $doc as node() external;
let $select_block := $doc//div[#class="randomclass" and contains(.,'MatchingText')]
return
if ($select_block) then
for $select_link in $select_block/a
for $select_link_url in $select_link/#href
where contains($select_link_url,'inassignee')
return data($select_link)
else 'Missing Value'
You could simplify things a little bit and eliminate the nested for loops with predicate filters selecting the anchor elements:
declare variable $doc as node() external;
let $select_block := $doc//div[#class="randomclass" and contains(.,'MatchingText')]
return
if ($select_block) then
for $select_link in $select_block/a[#href[contains(., 'inassignee')]]
return data($select_link)
else 'Missing Value'

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.

Resources