I'm trying to construct a new element based on an old one, including copying in its children. To prevent each child from repeating the namespace definitions for each namespace in scope, I'm interested in copying the namespace declarations as well.
Namespace declarations aren't picked up as regular attributes through $element/#*.
If I restrict my code to only work with XQuery 3.0, I can do the following:
<new-element>
{
for $ns-prefix in in-scope-prefixes($element)
let $ns-uri := namespace-uri-for-prefix($ns-prefix, $element)
return namespace { $ns-prefix } { $ns-uri }
}
...
</new-element>
Is there a better way?
To clarify the problem a bit -- my original document looks like so:
<root xmlns:stuff="...">
<child name="foo"/>
<child name="bar"/>
</root>
When I copy children into a new document, I get the following:
<new-element>
<child xmlns:stuff="..." name="foo"/>
<child xmlns:stuff="..." name="bar"/>
</new-element>
...whereas it would make more sense to simply attach the xmlns:stuff declaration to <new-element>.
If you permit use of XQuery Update, you could use a transform expression (copy/modify/return) to copy the node, then modify the name.
In XQuery 3.0, the following should work if your implementation supports the namespace axis (which I believe is an optional feature):
<new-element>
{$element/namespace::*}
...
</new-element>
If you use programmatic constructors, I believe they take care of the namespace declarations in the way you need.
xquery version "1.0";
declare function local:transform($node)
{
typeswitch($node)
case element()
return element { fn:node-name($node) } {
$node/#*,
for $n in $node/*
return local:transform($n)
}
default return $node
};
let $node :=
<dmc:root xmlns:dmc="http://davidcassel.net/dmc">
<dmc:parent attr="value">
<stuff:child xmlns:stuff="http://davidcassel.net/stuff">some text</stuff:child>
</dmc:parent>
</dmc:root>
return local:transform($node)
Running that produces
<dmc:root xmlns:dmc="http://davidcassel.net/dmc">
<dmc:parent attr="value">
<stuff:child xmlns:stuff="http://davidcassel.net/stuff"/>
</dmc:parent>
</dmc:root>
Add cases to the typeswitch to make the structural changes you want. Does that accomplish what you're looking for?
Tested on MarkLogic 5 using standard XQuery 1.0.
I had a similar problem, and simply added a "fake" attribute to the parent node in the namespace, which causes it to pick up the declaration at that level.
<new-element stuff:something="anything">
...
</new-element>
Related
consider a library module ctx
xquery version "3.1";
module namespace ctx="module/ctx";
declare function ctx:resolve (
$ctx as function(xs:string) as xs:QName
) as function(xs:string, xs:integer) as function(*)? {
function ($name as xs:string, $arity as xs:integer) as function(*)? {
function-lookup($ctx($name), $arity)
}
};
and a library module a
xquery version "3.1";
module namespace a="module/a";
declare function a:f () { "a:f" };
And a main module
xquery version "3.1";
import module namespace a="module/a" at "a.xqm";
import module namespace ctx="module/ctx" at "ctx.xqm";
ctx:resolve(xs:QName(?))("a:f", 0)()
Is it safe to assume that the function reference returned by xs:QName(?) will keep the context in which the namespace a is declared so that the main module will output "a:f"?
This does work in eXist-db (tested on 5.3.0) but I am not sure if this code is portable to other XQuery 3.1 processors.
---- UPDATE ----
What does not work in eXist-db 5.3.0 is
import module namespace ctx="module/ctx" at "ctx.xqm";
declare namespace app = "app";
declare function app:f () { "app:f" };
ctx:resolve(xs:QName(?))("app:f", 0)()
It's an excellent question.
If you used a named function reference xs:QName#1, then you would find the answer in ยง3.1.6:
Furthermore, if the function returned by the evaluation of a
NamedFunctionRef has an implementation-dependent implementation, then
the implementation of this function is associated with the static
context of this NamedFunctionRef expression and with the dynamic
context in which the NamedFunctionRef is evaluated.
That's rather arcane language, but what it means is that the function you get back uses the static and dynamic context of the named function reference itself (and not the context at the point where the function is actually called).
For a partial function application xs:QName(?) the language is even more impenetrable:
Implementation: The implementation of F. If this is not an XQuery 3.1
expression then the new function's implementation is associated with a
static context and a dynamic context in one of two ways: if F's
implementation is already associated with contexts, then those are
used; otherwise, SC and DC are used.
I think the spec is assuming (without justification) that built in functions like xs:QName will have an implementation that is not an XQuery 3.1 expression, so the second sentence applies. I really don't know quite what it intends by the phrase "already associated with contexts" - perhaps it's concerned with the case where F is already a partially applied function. But in any case, I'm reasonably sure that the intent is that "SC and DC are used" - that is, xs:QName(?) works exactly like xs:QName#1, it uses the static and dynamic context at the point where the expression xs:QName(?) is evaluated.
You're quite right to be concerned that you can't assume that what existing products do is what the spec says must happen. But in this case, I think they are getting it right.
Is it safe to assume that the function reference returned by xs:QName(?) will keep the context in which the namespace a is declared so that the main module will output "a:f"?
Tested successfully with BaseX 10.3.
Had to fix ctx as it was deemed syntactically incorrect by BaseX (missing as <Type>) for the result of ctx:resolve(). Just added as function(*)? . Here is the corrected code:
xquery version "3.1";
module namespace ctx="module/ctx";
declare function ctx:resolve (
$ctx as function(xs:string) as xs:QName
) as function(xs:string, xs:integer) as function(*)? {
function ($name as xs:string, $arity as xs:integer) as function(*)? {
function-lookup($ctx($name), $arity)
}
};
declare function local:change($node)
{
typeswitch($node)
case element(add) return
local:do-something()
default return $node
};
let $test1 := <test xmlns="http:example.com/A">
<add>x1</add>
<b>x</b>
</test>
let $test2 := <test xmlns="http:example.com/B">
<add>x1</add>
<b>x</b>
</test>
In the typeswitch when element add, doing some further processing. Code is generic. The documents that will be sent for processing will have the same elements but namespace can be different like one in the example.
How to provide the namespace dynamically in case element.
If you declare
declare namespace A = "http:example.com/A";
declare namespace B= "http:example.com/B";
you should be able to use
case element(A:add) | element(B:add)
at least in standard XQuery (https://www.w3.org/TR/xquery-31/#prod-xquery31-SequenceTypeUnion), I don't know whether Marklogic supports it.
I don't think sequence types allow a namespace wild card in the form of e.g. element(*:add) so I am not sure what to suggest for your high number of namespaces, check whether you need typeswitch and sequence type matching or whether you can't simply select *:add in a step of a path expression or a predicate e.g. if ($node[self::*:add]) e.g. instead of
typeswitch($node)
case element(add) return
local:do-something()
...
you can use
if ($node[self::*:add])
then local:do-something()
else $node
One way to treat elements from different namespaces in the same way (as the same 'kind' of element), is put them into the same namespace, or into no namespace. Depending on what local:do-something() does, the typechecked copy might want to be empty, or to contain the original contents (as shown), or contain similarly namespace-stripped children.
declare function local:normalize-namespace($element){
element { local-name($element) } { $element/#*, $element/node() }
};
declare function local:change($node)
{
typeswitch(local:normalize-namespace($node))
case element(add) return
local:do-something()
default return $node
};
I'm a newbie with Xquery. An post already exists on this query but I'm having problems when the XML has a prefix as follows:
Source XML:
enter code here
<?xml version="1.0" encoding="UTF-8"?>
<so:category>
<bo:catid>1</bo:catid>
<bo:cattext>sport</bo:cattext>
</so:category>
Xquery to change value that was provided in another post:
declare namespace local = "http://example.org";
declare namespace bo = "http://example1.org";
declare function local:copy-replace($element as element()) {
if ($element/self::bo:catid)
then <bo:catid>test</bo:catid>
else element {node-name($element)}
{$element/#*,
for $child in $element/node()
return if ($child instance of element())
then local:copy-replace($child)
else $child
}
};
local:copy-replace(/*)
I have a prefix for elements in my XML document. So when I execute the query
I get the following error:
ERROR - The prefix "bo" for element "bo:catid" is not bound.
I'm not sure how to handle the prefix & have searched for related subject on the internet. However, I cannot resolve this issue with information provided & need to know what I'm doing wrong.
Your XML is well-formed according to the XML 1.0 recommendation, but it is not namespace-well-formed according to XML Namespaces 1.0 (because it uses namespace prefixes that are not bound to any namespace URI). Most tools in the XML eco-system will only handle namespace-well-formed XML, and this is an absolute requirement for using XQuery.
Declaring namespace prefixes in the query doesn't get away from the requirement to have namespace-well-formed input.
I am writing an XQuery that needs to check what version of XQuery is being run (eXist, Saxon, etc.). Is there a standard function that will return the system properties of an XQuery such as version, vendor etc?
I know there are some system specific calls such as eXist's
system:get-version()
but I am trying to find a standard function that would run on all platforms.
For example in XSLT we have:
Version:
<xsl:value-of select="system-property('xsl:version')" />
<br />
Vendor:
<xsl:value-of select="system-property('xsl:vendor')" />
<br />
Vendor URL:
<xsl:value-of select="system-property('xsl:vendor-url')" />
Are their similar functions for XQuery?
You can use the XQuery 3.0 function function-lookup to check the existence of implementation-specific functions to check for the processors. All XQuery 3.0 supporting processors should be able to process this.
declare function local:exist() as xs:boolean {
try {
if (not(empty(function-lookup(xs:QName('system:get-version'), 0))))
then true()
else false()
} catch * {
false()
}
};
(: works only on Saxon PE and EE, fails in HE since HE does not support XQuery 3.0. It would be nice if saxon had a version function. :)
declare function local:saxon() as xs:boolean {
try {
if (not(empty(function-lookup(xs:QName('saxon:parse'), 1))))
then true()
else false()
} catch * {
false()
}
};
declare function local:marklogic() as xs:boolean {
try {
if (not(empty(function-lookup(xs:QName('xdmp:xquery-version'), 0))))
then true()
else false()
} catch * {
false()
}
};
declare function local:basex() as xs:boolean {
try {
if (not(empty(function-lookup(xs:QName('prof:time'), 1))))
then true()
else false()
} catch * {
false()
}
};
declare function local:get-processor() as xs:string {
if (local:exist()) then "eXist"
else if (local:saxon()) then "Saxon"
else if (local:marklogic()) then "MarkLogic"
else if (local:basex()) then "BaseX"
else "Unknown"
};
local:get-processor()
Unfortunately, I was not able to make this more elegant using Higher-Order functions, as the implementation-specific functions were executed before the function is actually called. But I am sure this could also be written more elegant.
Tricky in the case of Saxon-HE because Saxon-HE only supports XQuery 1.0 and provides no vendor extensions. It does however provide a mechanism for creating user-defined extension functions, and you could use that to implement an interrogative function of your own design.
Or you could define an external global variable and initialize that from the application that runs the query, assuming the application is under your control.
There doesn't appear to be a function in Saxon, but eXist has system:get-version():
http://exist-db.org/exist/apps/fundocs/view.html?uri=http://exist-db.org/xquery/system&location=java:org.exist.xquery.functions.system.SystemModule
Also, although you're not using it, other SO readers may be interested to know that MarkLogic has xdmp:xquery-version():
http://docs.marklogic.com/7.0/xdmp:xquery-version
There is an inherent problem here that different versions of xquery simply wont complile if run by an incompatible version. You cant generally do a
#if version > 1
new stuff
#else
old stuff
#endif
Some vendor extensions supply version information and you can use an "eval()" type epression to get around this, but in pure XQuery there is no point I can think of that can used to do conditional logic that is compilation time valid on the current version yet use some feature on a different version.
According to the w3c
An ElementTest is used to match an element node by its name and/or type annotation. An ElementTest may take any of the following forms. In these forms, ElementName need not be present in the in-scope element declarations, but TypeName must be present in the in-scope schema types [err:XPST0008]. Note that substitution groups do not affect the semantics of ElementTest.
...
element(*, TypeName) matches an element node regardless of its name, if derives-from(AT, TypeName ) is true, where AT is the type annotation of the element node, and the nilled property of the node is false.
I have this function
import schema namespace cdm-base="http://cdm.basic.upc.com" at "file:///Workspace/peal/peal40/trunk/common/schema/cdm-basic.xsd";
declare function local:matchType(
$input as element()
) as element(*,cdm-base:ProductComponent..) {
<cdm-base:product xsi:type="cdm-base:ProductComponent" />
};
which while I am typing returns the error:
F [Saxon-EE XQuery 9.3.0.5] Required item type of result of function local:matchType() is element(*, ProductComponent); supplied value has item type element({http://cdm.basic.upc.com}product, {http://www.w3.org/2001/XMLSchema}untyped)
I may mistaken, but the type is actually cdm-base:ProductComponent and not untyped.
I don't get where the issue is...
I am Using Oxygen 13.0 with Saxon EE 9.3.0.5
Saxon is indeed correct here, all directly constructed ("inline") elements have the type xs:untyped (or xs:anyType if the construction mode is set to preserve).
The xsi:type element is meaningless until the element has been validated against your schemas. The easiest way to do this is to wrap the element in a validate expression:
import schema namespace cdm-base="http://cdm.basic.upc.com" at "file:///Workspace/peal/peal40/trunk/common/schema/cdm-basic.xsd";
declare function local:matchType(
$input as element())
as element(*,cdm-base:ProductComponent)
{
validate { <cdm-base:product xsi:type="cdm-base:ProductComponent" /> }
};
Note that in XQuery 3.0, if you do not actually need the xsi:type attribute then you can validate the element as a particular type:
import schema namespace cdm-base="http://cdm.basic.upc.com" at "file:///Workspace/peal/peal40/trunk/common/schema/cdm-basic.xsd";
declare function local:matchType(
$input as element())
as element(*,cdm-base:ProductComponent)
{
validate type cdm-base:ProductComponent { <cdm-base:product /> }
};