element() vs. node() in XQuery

前端 未结 3 1199
猫巷女王i
猫巷女王i 2021-02-10 04:04

Can someone tell me the exact difference between node() and element() types in XQuery? The documentation states that element() is an eleme

3条回答
  •  别跟我提以往
    2021-02-10 04:27

    data() returning empty for an element just because the argument type is node() sounds like a bug to me. What XQuery processor are you using?

    It sounds like you need to placate static type checking, which you can do using a treat as expression. I don't believe a dynamic test using instance of will suffice.

    Try this:

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

    Quoting from the 4th edition of Michael Kay's magnum opus, "The treat as operator is essentially telling the system that you know what the runtime type is going to be, and you want any checking to be deferred until runtime, because you're confident that your code is correct." (p. 679)

    UPDATE: I think the above is actually wrong, since treat as is just an assertion. It doesn't change the type annotation node(), which means it's also a wrong assertion and doesn't help you. Hmmm... What I really want is cast as, but that only works for atomic types. I guess I'm stumped. Maybe you should change XQuery engines. :-) I'll report back if I think of something else. Also, I'm curious to find out if Dimitre's solution works for you.

    UPDATE #2: I had backpedaled here earlier. Can I backpedal again? ;-) Now my theory is that treat as will work based on the fact that node() is interpreted as a union of the various specific node type annotations, and not as a run-time type annotation itself (see the "Note" in the "Item types" section of the XQuery formal semantics.) At run time, the type annotation will be element(). Use treat as to guarantee to the type checker that this will be true. Now I wait on bated breath: does it work for you?

    EXPLANATORY ADDENDUM: Assuming this works, here's why. node() is a union type. Actual items at run time are never annotated with node(). "An item type is either an atomic type, an element type, an attribute type, a document node type, a text node type, a comment node type, or a processing instruction type."1 Notice that node() is not in that list. Thus, your XQuery engine isn't complaining that an item has type node(); rather it's complaining that it doesn't know what the type is going to be (node() means it could end up being attribute(), element(), text(), comment(), processing-instruction(), or document-node()). Why does it have to know? Because you're telling it elsewhere that it's an element (in your function's signature). It's not enough to narrow it down to one of the above six possibilities. Static type checking means that you have to guarantee—at compile time—that the types will match up (element with element, in this case). treat as is used to narrow down the static type from a general type (node()) to a more specific type (element()). It doesn't change the dynamic type. cast as, on the other hand, is used to convert an item from one type to another, changing both the static and dynamic types (e.g., xs:string to xs:boolean). It makes sense that cast as can only be used with atomic values (and not nodes), because what would it mean to convert an attribute to an element (etc.)? And there's no such thing as converting a node() item to an element() item, because there's no such thing as a node() item. node() only exists as a static union type. Moral of the story? Avoid XQuery processors that use static type checking. (Sorry for the snarky conclusion; I feel I've earned the right. :-) )

    NEW ANSWER BASED ON UPDATED INFORMATION: It sounds like static type checking is a red herring (a big fat one). I believe you are in fact not dealing with an element but a document node, which is the invisible root node that contains the top-level element (document element) in the XPath data model representation of a well-formed XML document.

    The tree is thus modeled like this:

          [document-node]
                 |
            
                 |
            
    

    and not like this:

            
                 |
            
    

    I had assumed you were passing the node. But if I'm right, you were actually passing the document node (its parent). Since the document node is invisible, its serialization (what you copied and pasted) is indistinguishable from an element node, and the distinction was lost when you pasted what is now interpreted as a bare element constructor in your XQuery. (To construct a document node in XQuery, you have to wrap the element constructor with document{ ... }.)

    The instance of test fails because the node is not an element but a document-node. (It's not a node() per se, because there's no such thing; see explanation above.)

    Also, this would explain why data() returns empty when you tried to get the child of the document node (after relaxing the function argument type to node()). The first tree representation above shows that is not a child of the document node; thus it returns the empty sequence.

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

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

    Alternatively, let your function take a document node 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 the description of eXist's request:get-data() function is perfectly consistent with this explanation. It says: "If its not a binary document, we attempt to parse it as XML and return a document-node()." (emphasis added)

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

提交回复
热议问题