View RSS Feed

Development Team Blog

Practical XML namespaces

Rate this Entry
Now that you've read the new namespace primer in the Visual DataFlex documentation, and perhaps also read John's article about namespaces (you have read them, right?), you're probably thinking, good, but how do I really use namespaces when creating my XML document?

First, you should use AddElementNS and so on, all those methods ending with NS. As John mentioned, you'll notice that you specify the namespace uri all the time, because that's the real namespace identifier (not the prefix). I usually create a string variable to store the namespace uri, so I won't have to repeat the uri over and over.

You can define a default namespace, and skip namespace prefixes. Whether you use prefixes or a default namespace declaration makes no real difference when interpreting the document (you always use the namespace uri associated with the prefix or default namespace). You typically pick whatever method is easier for the specific document. If you need to use multiple namespaces, it's often better to use prefixes. You can also use a default namespace declaration for the namespace that's most frequently used, and prefixes for the other namespaces. I'll show both methods.

Code:
Procedure TestIt
    Handle hDoc hElem
    String sNs1
    
    Get Create (RefClass(cXMLDOMDocument)) to hDoc
    Move "http://example.com/one" to sNs1

    Get CreateDocumentElementNS of hDoc sNs1 "outer" to hElem

    Showln (psXML(hDoc))
    Send Destroy to hDoc
End_Procedure
The above will output the following:
Code:
<outer xmlns="http://example.com/one"/>
Easy, huh? Note that we didn't even create the namespace declaration manually. When you use the namespace-aware methods to add elements and attributes, it will automatically take care of the namespace declarations so you don't have to think about it, reducing the chances of errors.

The above is using a default namespace, i.e. no prefix. What if we want to use a prefix?
Code:
Procedure TestIt
    Handle hDoc hElem
    String sNs1
    
    Get Create (RefClass(cXMLDOMDocument)) to hDoc
    Move "http://example.com/one" to sNs1
    
    Get CreateDocumentElementNS of hDoc sNs1 "myPrefix:outer" to hElem

    Showln (psXML(hDoc))
    Send Destroy to hDoc
End_Procedure
All we have to do is specify the prefix we would like to use with the qualified element name. It's as easy as that. The output is now:
Code:
<myPrefix:outer xmlns:myPrefix="http://example.com/one"/>
Note that the prefix was used as we expected in the element name, and the automatic namespace declaration was also adjusted automatically. It all "just works" because we're using the namespace-aware methods.

Let's add a child element:
Code:
Procedure TestIt
    Handle hDoc hElem
    String sNs1
    
    Get Create (RefClass(cXMLDOMDocument)) to hDoc
    Move "http://example.com/one" to sNs1
    
    Get CreateDocumentElementNS of hDoc sNs1 "one:outer" to hElem
    Send AddElementNS to hElem sNs1 "one:inner" ""

    Showln (psXML(hDoc))
    Send Destroy to hDoc
End_Procedure
I've reformatted the output with linebreaks to make it easier to read:
Code:
<one:outer xmlns:one="http://example.com/one">
    <one:inner/>
</one:outer>
Now if we want the inner element to be in a different namespace:
Code:
Procedure TestIt
    Handle hDoc hElem
    String sNs1 sNs2
    
    Get Create (RefClass(cXMLDOMDocument)) to hDoc
    Move "http://example.com/one" to sNs1
    Move "http://example.com/two" to sNs2
    
    Get CreateDocumentElementNS of hDoc sNs1 "one:outer" to hElem
    Send AddElementNS to hElem sNs2 "two:inner" ""

    Showln (psXML(hDoc))
    Send Destroy to hDoc
End_Procedure
The output is now:
Code:
<one:outer xmlns:one="http://example.com/one">
    <two:inner xmlns:two="http://example.com/two"/>
</one:outer>
Notice how the namespace declarations are automatically maintained for both elements and prefixes. Go back and look at the previous example with just one namespace again, notice how it automatically added the namespace declaration for the outer node, and the inner node just uses the inherited declaration instead of creating its own, all very neat. There's however a situation where the output is correct but not as neat as one might expect. Watch what happens when we create another child node.

Code:
Procedure TestIt
    Handle hDoc hElem
    String sNs1 sNs2
    
    Get Create (RefClass(cXMLDOMDocument)) to hDoc
    Move "http://example.com/one" to sNs1
    Move "http://example.com/two" to sNs2
    
    Get CreateDocumentElementNS of hDoc sNs1 "one:outer" to hElem
    Send AddElementNS to hElem sNs2 "two:inner" ""
    Send AddElementNS to hElem sNs2 "two:inner" ""

    Showln (psXML(hDoc))
    Send Destroy to hDoc
End_Procedure
Code:
<one:outer xmlns:one="http://example.com/one">
    <two:inner xmlns:two="http://example.com/two"/>
    <two:inner xmlns:two="http://example.com/two"/>
</one:outer>
See how it duplicated that second namespace declaration. The reason for this difference is due to the scope of a namespace declaration. In the previous example the child element didn't duplicate the namespace declaration, but in the last example the sibling element did. The scope of a namespace declaration is the element where it was declared, including all its child elements. So with a sibling element, the previous namespace declaration is already out of scope, therefore it must create another namespace declaration. This is obviously all valid, and absolutely correct, it's just not as neat as it could be.

What we'd like is the following output:
Code:
<one:outer xmlns:one="http://example.com/one"
           xmlns:two="http://example.com/two">
    <two:inner/>
    <two:inner/>
</one:outer>
Unfortunately there's no real universally sanctioned DOM solution to that problem. Using AddAttribute to add the namespace declaration at the parent element usually works, and until there's an official DOM solution available, it's the best we've got. Note that in many cases it's not necessary, and it's recommended that you prefer to use AddElementNS to let it create the namespace declarations automatically whenever possible.

So the unofficial solution to push up the declaration to the parent element is:
Code:
Procedure TestIt
    Handle hDoc hElem
    String sNs1 sNs2
    
    Get Create (RefClass(cXMLDOMDocument)) to hDoc
    Move "http://example.com/one" to sNs1
    Move "http://example.com/two" to sNs2
    
    Get CreateDocumentElementNS of hDoc sNs1 "one:outer" to hElem
    Send AddAttribute to hElem "xmlns:two" sNs2
    Send AddElementNS to hElem sNs2 "two:inner" ""
    Send AddElementNS to hElem sNs2 "two:inner" ""
    Showln (psXML(hDoc))
    Send Destroy to hDoc
End_Procedure
And the result is:
Code:
<one:outer xmlns:one="http://example.com/one"
           xmlns:two="http://example.com/two">
    <two:inner/>
    <two:inner/>
</one:outer>
If you design a new XML document, my recommendation is to always use the namespace aware methods, and always use namespaces in your documents. I consider the non-namespace aware methods to be available only for backwards compatibility with older existing document structures that are not using namespaces. (And even then you can actually use the namespace aware methods with "" as the namespace uri.)

So if you start with a new document design, set up a namespace, and use a default namespace declaration for the top element, exactly as illustrated in the first example in this article. There's really no reason not to use namespaces when designing new documents, as there are very few (if any) XML processors still in use today that cannot handle namespaces. And using namespaces gives you so many advantages, such as automatic version detection with different namespaces, which is used internally in the Studio for ddclasslist.xml etc.

Now for the quiz of the week. With the information from this article and the answer to the previous quiz, you should now be able to answer the following question: Why is it necessary to use psSelectionNamespaces together with FindNode?

Comments