Xquery replace an element value that has a prefix - xquery

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.

Related

Scope and statically known namespaces in XQuery

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)
}
};

How to use dynamic namespace with type switch?

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
};

Typescript reflection - required parameters & default values

In short: is there a way to know if a typescript parameter is required and/or has a default value?
Longer version:
Say I have the following file:
//Foo.ts
class Bar {
foo(required:string,defaultValue:number=0,optional?:boolean) {
...
}
}
I would like to know of each of the parameters:
the name
the type
is it required?
does it have a default value?
I have succesfully used method decorators with the TypeScript reflection API to get the types of the parameters, I've used this method to get their names, but so far I have not found a way to know if a variable is required and/or has a default value.
I know the typescript compiler itself can be used from within typescript. So I'm wondering if there is a way to use the parse tree of the compiler to see if a parameter is required and/or has a default value?
How would that work?
If you want to do this from scratch...
On a high level, one way of doing it is to:
Figure out how to get the SourceFile node using the compiler api of your file. That requires a bit of an explanation in itself.
From there, use the api's forEachChild function to loop over all the nodes in the file and find the node with a kind of SyntaxKind.ClassDeclaration and .name property with text Bar.
Then loop over all the children of the class by again using the api's forEachChild function and get the ones that has the kind SyntaxKind.MethodDeclaration and .name property with text foo.
To get the parameters, you will need to loop over the method node's parameters property.
Then for each parameter node, to get the name you can call .getText() on the .name property.
You can tell if the parameter is optional by doing:
const parameterDeclaration = parameterNode as ts.ParameterDeclaration;
const isOptional = parameterDeclaration.questionToken != null || parameterDeclaration.initializer != null || parameterDeclaration.dotDotDotToken != null;
Or you could use the TypeChecker's isOptionalParameter method.
To get its default expression, you will just have to check the initializer property:
propertyDeclaration.initializer;
To get the type use the TypeChecker's getTypeOfSymbolAtLocation method and pass in the symbol of the node... that gets a little bit complicated so I won't bother explaining it (think about how it's different with union types and such).
Don't do it from scratch...
I've created a wrapper around the TypeScript compiler api. Just use this code with ts-simple-ast (edit: Previously this talked about my old ts-type-info library, but ts-simple-ast is much better):
import { Project } from "ts-morph";
// read more about setup here:
// https://ts-morph.com/setup/adding-source-files
const project = new Project({ tsConfigFilePath: "tsconfig.json" });
const sourceFile = project.getSourceFileOrThrow("src/Foo.ts");
const method = sourceFile.getClassOrThrow("Bar").getInstanceMethodOrThrow("foo");
Once you have the method, it's very straightforward to get all the information you need from its parameters:
console.log(method.getName()); // foo
for (const param of method.getParameters()) {
console.log(param.getName());
console.log(param.getType().getText());
console.log(param.isOptional());
console.log(param.getInitializer() != null);
}

Copying namespaces in XQuery

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>

function return type issue

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 /> }
};

Resources