1
votes

Setting a default namespace with a direct element constructor is straightforward. Ex:

<map xmlns="http://www.w3.org/2013/XSL/json"/>

The above direct constructor outputs the exact same element, as expected. However, if I try to do the same with computed element and namespace constructors, I'm out of luck:

element {"map"} {
  namespace {""} { "http://www.w3.org/2013/XSL/json" }
}

The above throws a Duplicate namespace declaration: '' error in BaseX 8.2; and a XTDE0440: Cannot output a namespace node for the default namespace when the element is in no namespace in Saxon-HE 9.6.

There's no problem if I pass a prefix. The following works well:

element {"map"} {
  namespace { "e" } {"http://www.w3.org/2013/XSL/json"}
}

In the above case, neither BaseX nor Saxon complain. But I want to set the namespace as the default namespace.

Regarding computed namespace constructors, the XQuery 3.0 spec says:

If the constructor specifies a PrefixExpr, the prefix expression is evaluated as follows:

b. If the result is the empty sequence or a zero-length xs:string or xs:untypedAtomic value, the new namespace node has no name (such a namespace node represents a binding for the default namespace).

The spec also provides a similar example of a "computed namespace constructor with an empty prefix":

namespace { "" } {"http://a.example.com" }

Why then, the error messages? Apparently, the computed namespace constructor tries to re-declare a namespace already declared by the computed element constructor. On the other hand, the direct constructor would be directly initializing the element's namespace to my choice. But this is just my guess. My only certainty is my puzzlement.

In any case, is there a way to obtain with computed constructors the same result achievable with the direct constructor?

3

3 Answers

5
votes

The basic reason is that the name of an element (that is, the prefix, local, and uri parts of the name) are determined by the element constructor itself, and not by the dynamic content that is added to the element.

When you do this:

element {"map"} {
  namespace {""} { "http://www.w3.org/2013/XSL/json" }
}

You are creating an element with no prefix and no namespace, and then you are giving it a namespace node that binds the default namespace to something other than "no namespace"; you can't have it both ways. Adding a namespace dynamically to the content of an element will never change the element name.

2
votes

I haven't tested this in saxon or basex, but using XQuery3 in Marklogic, this works:

element {fn:QName("http://www.w3.org/2013/XSL/json", "map")} { }

However, that alone probably won't actually do what you want.

In XQuery, computed constructors do not inherit default namespaces from parent elements; the first part is always the (fully qualified) QName of the element. You can use a string there, but it's being coerced into a QName - if that string has no prefix then you're explicitly asking for an element with no namespace (unless you've declared a default element namespace, in which case, you're asking for that). This means that even if you set the default namespace on an element, any children for which you don't specify a default namespace in the constructor will explicitly set it back to "".

By far the simplest way to work with computed constructors is to declare your namespaces at the top of the file, and then use prefixes on every element name. This does lead to XML with prefixes on every element though. Personally, I like how obviously unambiguous that is, but many people find it overly verbose, especially in XML that uses only one namespace.

You can also use the construct I used above for every element, which will do more or less the same thing, but without using prefixes. If you do this, while you have to specify the namespace on every element where it's constructed in your code, the resulting XML should only specify it on the top-level element, with child elements inheriting it like you'd expect (although it's possible this will depend on your XQuery processor.)

Alternatively, you can declare the default element namespace and then only specify a namespace for elements that differ from that. Personally, I find this confusing and prone to hard to spot errors, though.

0
votes

I tried to add a comment but it wouldn't let me put in newlines.

As Will Goring mentioned in his answer, children elements do not inherit the default namespace from the parent. One way to make this easier is to declare the namespace and use that in the children elements.

declare namespace json = "http://www.w3.org/2013/XSL/json";

element {fn:QName("http://www.w3.org/2013/XSL/json", "map")} {
    element json:child { }
}

becomes

<map xmlns="http://www.w3.org/2013/XSL/json"><child/></map>

where child has the same default namespace.

It's equivalent to:

element {fn:QName("http://www.w3.org/2013/XSL/json", "map")} {
    element {fn:QName("http://www.w3.org/2013/XSL/json", "child")} { }
}