Practical XML namespaces
by
, 22-Dec-2009 at 08:00 AM (6714 Views)
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.
The above will output the following: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
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.Code:<outer xmlns="http://example.com/one"/>
The above is using a default namespace, i.e. no prefix. What if we want to use a prefix?
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: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
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.Code:<myPrefix:outer xmlns:myPrefix="http://example.com/one"/>
Let's add a child element:
I've reformatted the output with linebreaks to make it easier to read: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
Now if we want the inner element to be in a different namespace:Code:<one:outer xmlns:one="http://example.com/one"> <one:inner/> </one:outer>
The output is now: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
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:<one:outer xmlns:one="http://example.com/one"> <two:inner xmlns:two="http://example.com/two"/> </one:outer>
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_ProcedureSee 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.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>
What we'd like is the following output:
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.Code:<one:outer xmlns:one="http://example.com/one" xmlns:two="http://example.com/two"> <two:inner/> <two:inner/> </one:outer>
So the unofficial solution to push up the declaration to the parent element is:
And the result 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
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.)Code:<one:outer xmlns:one="http://example.com/one" xmlns:two="http://example.com/two"> <two:inner/> <two:inner/> </one:outer>
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?