How can we update a value in xquery other than node-replace? I have an element called title which is empty , I should be updating the value as “submitted”.
Sample file:
<books>
<title></title>
</books>
I assume you are asking for an alternative to xdmp:node-replace() because this XML doc is an in-memory object and not (yet) a persisted document?
You could perform a recursive descent typeswitch
xquery version "1.0-ml";
(: This is the recursive typeswitch function :)
declare function local:transform($nodes as node()*) as node()*
{
for $n in $nodes return
typeswitch ($n)
case text() return $n
case attribute() return $n
case comment() return $n
case processing-instruction() return $n
case element (title) return <title>submitted</title>
default return element {$n/name()} {local:transform($n/(#*|node()))}
};
let $books :=
<books foo="x"><!--test--><?PITarget PIContent?>
<title></title>
</books>
return
local:transform($books)
, but I find that to be tedious and don't like for general purpose transformations if you wind up needing more complex transformation scenarios.
For more complicated transformations of in-memory XML structures in XQuery, then I would look to use Ryan Dew's XQuery XML Memory Operations library:
import module namespace mem = "http://maxdewpoint.blogspot.com/memory-operations"
at "/memory-operations.xqy";
let $books :=
<books>
<title></title>
</books>
return
mem:copy(fn:root($books)) ! (
mem:replace(., $books/title, element title {"submitted"}),
mem:execute(.)
)
When transforming XML (whether in-memory or documents in the database), I prefer XSLT. You could use either xdmp:xslt-invoke() (with an installed stylesheet) or with xdmp:xslt-eval():
xquery version "1.0-ml";
let $xslt :=
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="title">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:text>submitted</xsl:text>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
let $books :=
<books>
<title></title>
</books>
return xdmp:xslt-eval($xslt, $books)
Related
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.
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/*)
I can't seem to get this element in my xml to be an xdmp:function.
Here is the xml:
<xml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<function xsi:type="xdmp:function" xmlns:xdmp="http://marklogic.com/xdmp">xdmp:function(xs:QName("fn:empty"))</function>
</xml>
As you can see I've tried to put the type in the xml that didnt work.
I've tried to use cast as that also didnt work. I've tried wrapping the element in xdmp:function and that also doesnt work.
I'm able to put the function into a map and get it out. like this:
let $function := xdmp:function(xs:QName("fn:empty"))
let $xml :=
<xml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<function xsi:type="xdmp:function" xmlns:xdmp="http://marklogic.com/xdmp">{$function}</function>
</xml>
let $map := map:map()
let $put := map:put($map, 'function', $function)
let $mapFunction := map:get($map, 'function')
let $applyMapFunction := xdmp:apply($mapFunction, "something")
(:
let $xmlFunction := $xml/function
let $applyXmlFunction := xdmp:apply($xmlFunction, "something")
:)
return $xml
<xml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<function xsi:type="xdmp:function" xmlns:xdmp="http://marklogic.com/xdmp">xdmp:function(xs:QName("fn:empty"))</function>
</xml>
However I dont want to have to make a map just to cover the item into a xdmp:function.
Is there another way to do this?
You have to eval the "serialized" function to extract it from a string or XML context:
xdmp:eval($xml/function/fn:string())
Alternately, you can use xdmp:value, which is a more limited eval function (it uses the same execution context) and therefore protects against some types of code injection:
xdmp:value($xml/function/fn:string())
BaseX is complaining about a nested query of mine. I do not understand why it cannot return multiple lines like it did in the first query. The error says, "Expecting }, found >" and the > it is referring to is the > after name under trips. It works fine if the } is after the close-bracket for id, but obviously, that's not what I want. Here is the query:
for $u in doc("export.xml")/database/USERS/tuple
return
<user>
<login>{$u/USERNAME/text()}</login>
<email></email>
<name></name>
<affiliation></affiliation>
<friend></friend>
<trip>
{for $t in doc("export.xml")/database/TRIPS/tuple
where $t/ADMIN/text() = $u/USERNAME/text()
return
<id> {$t/ID/text()} </id>
<name> {$t/NAME/text()} </name> (: Error is here with <name> :)
<feature> {$t/FEATURE/text()} </feature>
<privacyFlag> {$t/PRIVACY/text() </privacyFlag>)
}
</trip>
</user>
If you want to return multiple items, you need to encapsulate them in a sequence ($item1, $item2, ..., $itemnN). In your case:
for $t in doc("export.xml")/database/TRIPS/tuple
where $t/ADMIN/text() = $u/USERNAME/text()
return (
<id> {$t/ID/text()} </id>,
<name> {$t/NAME/text()} </name>,
<feature> {$t/FEATURE/text()} </feature>,
<privacyFlag> {$t/PRIVACY/text() </privacyFlag>
)
But I'm unsure whether this will do what you expected, or if you actually want to create one element set per trip. Then, you'd also have a single trip element for result and are not required to return a sequence (this is also what's the case in the outer flwor-loop, here the <user/> element encapsulates to a single element):
for $t in doc("export.xml")/database/TRIPS/tuple
where $t/ADMIN/text() = $u/USERNAME/text()
return
<trip>
<id> {$t/ID/text()} </id>
<name> {$t/NAME/text()} </name>
<feature> {$t/FEATURE/text()} </feature>
<privacyFlag> {$t/PRIVACY/text() </privacyFlag>
</trip>
I am using the following XQuery code for selecting all .html documents within a collection
of exist-db. The script should create an XML document (serialized as JSON) with document URI and title (which is stored as the first H1 element).
However the the element remains empty. Why?
xquery version "3.0";
declare option exist:serialize "method=json media-type=text/javascript";
<result> {
let $data-collection := '/db/output'
for $doc in collection($data-collection)
where contains(base-uri($doc), '.html')
return
<item>
<url>{base-uri($doc)}</url>
<title>{$doc/h1/text()}</title>
</item>
}
</result>
I have simplified your lookup for HTML documents, but I think your h1 element is not the root of the document, instead I have assumed that it might be anywhere in the document so have used the descendant-or-self axis, e.g. // and as your HTML may be in a namespace I have used the *: prefix to indicate any namespace.
xquery version "3.0";
declare option exist:serialize "method=json media-type=text/javascript";
<result> {
let $data-collection := '/db/output'
for $doc in collection($data-collection)[ends-with(base-uri(.), '.html')]
return
<item>
<url>{base-uri($doc)}</url>
<title>{$doc//*:h1/text()}</title>
</item>
}
</result>