Element () vs. node () in XQuery

Can someone tell me the exact difference between the node() and element() types in XQuery? The documentation states that element() is a node element, and node() is any node, so if I understand it correctly, element() is a subset of node() .

The fact is that I have an XQuery function as follows:

 declare function local:myFunction($arg1 as element()) as element() { let $value := data($arg1/subelement) etc... }; 

Now I want to call a function with a parameter that is received by another function, say functionX (which I do not control):

 let $parameter := someNamespace:functionX() return local:myFunction($parameter) 

The problem is that functionX returns node() , so it will not allow me to pass $parameter directly. I tried changing the type of my function to take node() instead of element() , but then I did not seem to read any data. $value just empty.

Is there a way to convert a node element to an element, or am I just missing something?

EDIT: As far as I can tell, the problem is the part where I am trying to get a $arg1/subelement using $arg1/subelement . Obviously, you can do this if $arg1 is element() but not node() .

UPDATE: I tested the example provided by Dimitre below, and it really works great with both Saxon and eXist DB (this is what I use as the XQuery engine). The problem actually arises with the request:get-data() function from eXist DB. This function receives the data provided by the POST request when using eXist via REST, parses it as XML, and returns it as node() . But for some reason, when I pass data to another function, XQuery does not recognize it as a valid element() , although it does. If I extract it manually (i.e., copy the output and paste it into the source code), assign it to a variable and pass it to my function, everything will be fine. But if I pass it directly, it gives me a runtime error (and really does not pass the instance of test).

I need to be able to either ignore this type check or β€œorder” data to element() .

+4
source share
3 answers

data() returns an empty value for an element only because the type of the node() argument sounds like an error to me. Which XQuery processor are you using?

It sounds like you need to calm the static type check you can do using the treat as expression. I do not think that a dynamic test using instance of will be sufficient.

Try the following:

 let $parameter := someNamespace:functionX() treat as element() return local:myFunction($parameter) 

Quote from the 4th edition of Michael Kay magnum opus: "The treat as statement essentially tells the system that you know what type of runtime will be, and you want some kind of check to be delayed until the runtime because you are" sure that your code is correct. "(p. 679)

UPDATE: I think this is actually not true, since treat as is just a statement. It does not change the annotation type node() , which also means an incorrect statement and does not help you. Hmm ... I really need cast as , but this only works for atomic types. I’m probably at a dead end. Maybe you should change the XQuery engines. :-) I will report if I think about something else. Also, I'm curious to see if the Dimitre solution works for you.

UPDATE # 2: I came back here before. Can I back off again? ;-) Now my theory is that treat as will work based on the fact that node() interpreted as a union of various specific annotations of type node, and not as a runtime type annotation itself (see Note "in the " Element Types " section of XQuery formal semantics .) At run time, the annotation of the type will be element() . Use treat as to ensure that this is true. Now I wait with bated breath: will this work for you?

EXPLANATORY APP: Assuming this works, that’s why. node() - type of union. Actual elements at run time are never annotated with node() . "An element type is an atomic type, an element type, an attribute type, a document type node, a text type node, a comment type node, or a type of processing instruction." 1 Note that node() not in this list. Thus, your XQuery engine does not complain that the element is of type node() ; rather, he complains that he does not know what type will be ( node() means that it may turn out to be attribute() , element() , text() , comment() , processing-instruction() or document-node() ) . Why should he know? Because you are saying elsewhere that it is an element (in your function signature). This is not enough to narrow it down to one of six possibilities. Checking a static type means that at compile time you must ensure that the types will match (element with element, in this case). treat as used to narrow a static type from a generic type ( node() ) to a more specific type ( element() ). It does not change the dynamic type. cast as , on the other hand, is used to convert an element from one type to another, changing both static and dynamic types (for example, xs: string to xs: boolean). Does it make sense that cast as can only be used with atomic values ​​(and not with nodes), because for this you need to convert the attribute to an element (etc.)? And there is no such thing as converting a node() element to an element() , because there is no such thing as a node() element. node() exists only as a static union type. The moral of the story? Avoid XQuery processors that use static type checking. (Sorry for the bold conclusion, I feel that I deserve the right. :-))

NEW ANSWER BASED ON UPDATE INFORMATION : it looks like static type checking is a red herring (big thick). I believe that you are not actually dealing with an element, but a document node , which is the invisible root of a node that contains a top-level element (document element) in the XPath data model view of a well-formed XML document.

Thus, the tree is modeled as follows:

  [document-node] | <docElement> | <subelement> 

and not so:

  <docElement> | <subelement> 

I assumed that you were passing the <docElement> node. But if I'm right, you actually passed the node document (its parent). Since the node document is invisible, its serialization (what you copied and pasted) is indistinguishable from the node element, and this difference was lost when you pasted what is now interpreted as the bare element constructor in your XQuery. (To create a node document in XQuery, you must wrap the constructor of the document{ ... } element document{ ... } .)

The instance of test fails because the node is not an element, but a node document. (This is not node() as such because there is no such thing, see explanation above.)

Also, this explains why data() returns empty when you tried to get the child <subelement> node document (after weakening the type of the function argument to node() ). The first view of the tree above shows that <subelement> not a child of the node document; so it returns an empty sequence.

Now for the solution. Before passing the (document node) parameter, get its child (document element) by adding /* (or /element() , which is equivalent) as follows:

 let $parameter := someNamespace:functionX()/* return local:myFunction($parameter) 

Alternatively, let your function take a node document and update the argument you pass to data ():

 declare function local:myFunction($arg1 as document-node()) as element() { let $value := data($arg1/*/subelement) etc... }; 

Finally, it looks like a description of an eXist request: the get-data () function is fully consistent with this explanation. It says: "If this is not a binary document, we try to parse it as XML and return the node () document." (highlighted by me)

Thanks for the adventure. This turned out to be a common XPath gotcha (document node awareness), but I learned a few things from our tour of static type checking.

+7
source

This works fine using Saxon 9.3 :

 declare namespace my = "my:my"; declare namespace their = "their:their"; declare function my:fun($arg1 as element()) as element() { $arg1/a }; declare function their:fun2($arg1 as node()) as node() { $arg1 }; my:fun(their:fun2(/*) ) 

when the above code is applied to the following XML document :

 <t> <a/> </t> 

the correct result is created without error messages :

 <a/> 

Update

The following should work even with the most punctual XQuery implementation for type checking:

 declare namespace my = "my:my"; declare namespace their = "their:their"; declare function my:fun($arg1 as element()) as element() { $arg1/a }; declare function their:fun2($arg1 as node()) as node() { $arg1 }; let $vRes := their:fun2(/*) (: this prevents our code from runtime crash :) return if($vRes instance of element()) then (: and this assures the static type-checker that the type is element() :) my:fun(their:fun2(/*) treat as element()) else() 
+2
source

node() is an element, attribute, processing instruction, text node, etc.

But data() converts the result to a string that is not one of them; This is a primitive type.

You might want to try item() , which should match.

See 2.5.4.2 . Matching ItemType and Item in the W3C XQuery Specification.

Although not shown in your code example, I assume that you are actually returning a value (for example, the $value are working with) from local:myFunction .

+1
source

Source: https://habr.com/ru/post/1386954/


All Articles