Default namespace handling in 11.2.5

In UniVerse 11.2 the XML libraries used by UV were updated. This updating appears to have broken the namespace handling when adding new nodes to a DOM with a default namespace.

The problem

If an XML document has a default namespace, such as this:

<TheBeginning xmlns="http://www.wordpress.com/webpath/20160222">
</TheBeginning>

the addition of nodes that do not explicitly reference a namespace produces an unexpected result:

<TheBeginning xmlns="http://www.wordpress.com/webpath/20160222">
  <Header xmlns=""/>
</TheBeginning>

The Header node should look like this:

 <TheBeginning xmlns="http://www.wordpress.com/webpath/20160222">
  <Header/>
</TheBeginning>

My thinking is that under the 11.2 XML module, the XDOMCreateNode() and XDOMAddChild() functions are expecting you to know exactly what you are doing. So when you add a node that has no namespace to a DOM that has a default the new node is added with no namespace.

The work-around

There is a way to work with the 11.2 XDOM functions to get nodes to have the same default namespace as the DOM to which they are added. It involves changing how the node name is structured.

The code used to create the Header node was:

$include UNIVERSE.INCLUDE XML.H
nsPATH="http://www.wordpress.com/webpath/20160222"
rootXML = '<TheBeginning '
rootXML := ' xmlns="':nsPATH:'"'
rootXML := '></TheBeginning>'
xmlStatus = XDOMOpen(rootXML,XML.FROM.STRING,domHandle)
if (xmlStatus = XML.ERROR) then
   gosub HANDLE.ERROR
end else
   xmlStatus = XDOMLocateNode(domHandle, XDOM.CHILD, XDOM.FIRST.CHILD, XDOM.ELEMENT.NODE, rootNode)
   if (xmlStatus = XML.ERROR) then
      gosub HANDLE.ERROR
   end else
      name.NODE = "Header"
      map.NS = nsPATH
      xmlStatus = XDOMCreateNode( rootNode, name.NODE, "", XDOM.ELEMENT.NODE, headerNode)
      if xmlStatus = XML.SUCCESS then
         xmlStatus = XDOMAddChild( rootNode, "", map.NS, headerNode, XDOM.NODUP)
         if (xmlStatus = XML.ERROR) then
            gosub HANDLE.ERROR
         end
      end
      XmlStatus= XDOMWrite(domHandle, recXml, XML.TO.STRING)
      if (xmlStatus # XML.ERROR) then crt recXml else crt "failed to build XML!"
   end
end
STOP

HANDLE.ERROR:
CRT 'HANDLE.ERROR'
DEBUG
return

In order to add a node that uses the default name space the name.NODE variable needs to be defined much the same as when adding a node with a namespace prefix.

In Creating XML Elements with a namespace prefix I cover how to add nodes with a namespace prefix. The work-around is very similar, so we will work through adding the Footer node as an example.

There is no namespace prefix involved in this DOM, so the structure of the name.NODE variable looks like this:

:” {node_Name} “:” {namespace_PATH}

As we are dealing with the default namespace the map.NS variable, important for the namespace prefix handling, needs to be empty for this work-around to work. So the code to add the Footer node looks like this:

      name.NODE = ":Footer:":nsPATH
      map.NS = ""
      xmlStatus = XDOMCreateNode( rootNode, name.NODE, "", XDOM.ELEMENT.NODE, headerNode)
      if xmlStatus = XML.SUCCESS then
         xmlStatus = XDOMAddChild( rootNode, "", map.NS, headerNode, XDOM.NODUP)
         if (xmlStatus = XML.ERROR) then
            gosub HANDLE.ERROR
         end
      end

Insert this above the XDOMWrite() function in the above program, and the output should look like this on an 11.2.x database:

<TheBeginning xmlns="http://www.wordpress.com/webpath/20160222">
  <Header xmlns=""/>
  <Footer/>
</TheBeginning>

This is a UV11.2.x only Solution

Beware that his logic only works on UV 11.2.x. As the XML libraries integrated in other version of UV behave differently, this work-around is only functional on UV 11.2.x servers.

To get this example to work on multiple UV versions you will need to leverage the compiler directives to test the UV version when compiling the code. The alternative is to code in a test of the UV version at runtime, but as there is no BASIC function that returns the current UV version this is a bit more cumbersome.

*******************************************************************************
*
*       Program TEST_UV11XML_NAMESPACE
*
*****************************************************************************
$include UNIVERSE.INCLUDE XML.H
nsPATH="http://www.wordpress.com/webpath/20160222"
rootXML = '<TheBeginning '
rootXML := ' xmlns="':nsPATH:'"'
rootXML := '></TheBeginning>'
xmlStatus = XDOMOpen(rootXML,XML.FROM.STRING,domHandle)
if (xmlStatus = XML.ERROR) then
   gosub HANDLE.ERROR
end else
   xmlStatus = XDOMLocateNode(domHandle, XDOM.CHILD, XDOM.FIRST.CHILD, XDOM.ELEMENT.NODE, rootNode)
   if (xmlStatus = XML.ERROR) then
      gosub HANDLE.ERROR
   end else
      name.NODE = "Header"
      map.NS = nsPATH
      xmlStatus = XDOMCreateNode( rootNode, name.NODE, "", XDOM.ELEMENT.NODE, headerNode)
      if xmlStatus = XML.SUCCESS then
         xmlStatus = XDOMAddChild( rootNode, "", map.NS, headerNode, XDOM.NODUP)
         if (xmlStatus = XML.ERROR) then
            gosub HANDLE.ERROR
         end
      end
$IFDEF U2__UNIVERSEv11
      name.NODE = ":Footer:":nsPATH
      map.NS = ""
$ELSE
      name.NODE = "Footer"
$ENDIF      
      xmlStatus = XDOMCreateNode( rootNode, name.NODE, "", XDOM.ELEMENT.NODE, headerNode)
      if xmlStatus = XML.SUCCESS then
         xmlStatus = XDOMAddChild( rootNode, "", map.NS, headerNode, XDOM.NODUP)
         if (xmlStatus = XML.ERROR) then
            gosub HANDLE.ERROR
         end
      end
      XmlStatus= XDOMWrite(domHandle, recXml, XML.TO.STRING)
      if (xmlStatus # XML.ERROR) then crt recXml else crt "failed to build XML!"
   end
end
STOP
HANDLE.ERROR:
CRT 'HANDLE.ERROR'
DEBUG
return

 

 

Creating XML Elements with a namespace prefix

The Problem

Recently we had a need to create an XML document from UniVerse where the nodes needed to have a specific namespace prefix, like this:

<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
   <wsse:Username>myUID</wsse:Username>
</wsse:Security>

“That’s easy”, I thought and promptly set to creating many an example of it not being easy. Nothing we tried would create the desired output.

The Solution

Rocket U2 Support were very helpful when I explained the requirement. Their solution, whilst not being the most intuitive option, certainly achieves the desired result.

There are two things that need to be done to properly build XDOM elements with a namespace prefix:

  1. Adjust the name declaration passed in to XDOMCreateNode()
  2. Adjust the namespace map passed in to XDOMAddChild()

The change to the name declaration sets up the new XDOM node internally, so that when the node is added to the main XDOM object it is integrated correctly. The change to the namespace map ensures that the node being added is added correctly into the existing namespace definitions of the main XDOM object.

The name declaration for XDOMCreateNode()

Instead of simply defining the name of the element as “Username”, we need to define it using the following syntax:

{prefix} “:” {node_name} “:” {path}

For the example xml above, our components are:

{prefix} = “wsse
{node_name} = “Username
{path} = “http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd

Using these components, our new name declaration for our new XML element needs to be defined as:

wsse:Username:http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd

To see this in context, we would use create a node such as this with the following UV code snippet:

PREFIX = "wsse"
PATH = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
name.NODE = PREFIX: ":": "Username": ":": PATH
hstat = XDOMCreateNode( hd, name.NODE, "", XDOM.ELEMENT.NODE, nhd)

The namespace map for XDOMAddChild()

Now that we have created the node, we now need to add the node to our XML document. To properly add an element created with the above name definition, we need to supply a namespace map that will instruct UV to place the node into the right place within the XML document.

The namespace map that needs to be supplied to the XDOMAddChild() function needs to be defined using this syntax:

“xmlns:” {prefix} “=”

Simple really. So, using our components from above, to add our newly created node to an existing XML document we need to define our namespace map and supply it to XDOMAddChild():

snMAP = "xmlns:wsse="
hstat = XDOMAddChild( hd, "", map.NS, nhd, XDOM.NODUP)

An Example

This is an example program showing an implementation of the above solution:

$include UNIVERSE.INCLUDE XML.H
* The aim is to create the "Username" element in the following:
* <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
*    <wsse:Username>myUID</wsse:Username>
* </wsse:Security>

PREFIX = "wsse"
PATH = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
src.XML = '<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"></wsse:Security>'
if XDOMOpen(src.XML, XML.FROM.STRING, rdom) # XML.ERROR then

   * Show the basic XML
   str.XML = ""
   hstat = XDOMWrite( rdom, str.XML, XML.TO.STRING)
   crt "XML-Before:"
   crt "[":str.XML:"]"

   * Locate our starting position
   if XDOMLocate( rdom, PREFIX:":Security", "", hd) # XML.ERROR then

      * Add new node with namespace prefix
      name.NODE = PREFIX: ":": "UserName": ":": PATH
      hstat = XDOMCreateNode( hd, name.NODE, "", XDOM.ELEMENT.NODE, nhd)
      map.NS = "xmlns:": PREFIX: "="
      hstat = XDOMAddChild( hd, "", map.NS, nhd, XDOM.NODUP)

      * Add a text node with the value
      hstat = XDOMCreateNode( nhd, '', "myUID", XDOM.TEXT.NODE, thd)
      hstat = XDOMAddChild( nhd, "", map.NS, thd, XDOM.NODUP)

      * Show final XML
      str.XML = ""
      hstat = XDOMWrite( hd, str.XML, XML.TO.STRING)
      crt "XML-After:"
      crt "[":str.XML:"]"

      * Clean-up
      hstat = XDOMClose(thd)
      hstat = XDOMClose(nhd)
      hstat = XDOMClose(hd)
   end
   hstat = XDOMClose(rdom)
end

Example output

This example code produces the following output:

XML-Before:
[<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"/>]
XML-After:
[<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:Username>myUID</wsse:Username></wsse:Security>]

An easier way to build XML in UniVerse

The process of creating an XML document in UniVerse BASIC is quite laborious. It takes a number of steps to add a new element node, and similar steps to add attributes to a node.

The sequence is something like this:

  1. Create new XDOM.ELEMENT.NODE
  2. Add new node to existing XML document
  3. If the element is to have a value
    1. Create new XDOM.TEXT.NODE with the element node value
    2. Add text node to new element node
  4. If the element is to have attributes
    1. Create new XDOM.ATTR.NODE with attribute name and value
    2. Add attribute node to new element node
    3. Repeat for all required attributes

All this has create just on element. To create multiple elements requires performing the above sequence multiple times. This makes for a very messy and hard to maintain program.

An Easier way

Ordinarily, such a piece of logic would be placed into an external subroutine. That does work, but there is a way to provide a more intuitive solution: Functions!

UniVerse BASIC supports the creation and use of functions. By creating a function that performs this sequence, the process of building an XML document becomes a lot easier to code, and to read and maintain.

The Add_XMLElement function

The following code is a function that performs all the above steps:

function Add_XMLElement( domHandle, NODE.name, NODE.value, NODE.attr, NODE.ns, nodeHandle)

* This function adds a new element node to the domHandle XML doc.
* It also adds any attributes supplied.
*
* domHandle [IN]  Single Value
*   Handle to an XML node or document
*
* NODE.name [IN]  Single Value
*   Name of the element node to be added
*
* NODE.value [IN]  Single Value
*   The value of the new element node
*
* NODE.attr [IN]  Two attributes, with Multi values
*   Dynamic array of names and values to be used to define the
*   attributes on this node.
*   <1> Contains the attribute name
*   <2> Contains the attribute value
*
* NODE.ns [IN]  Single Value
*   The namespace to use for this node.
*
* nodeHandle [OUT]  Single Value
*   A handle to the added node
*
* Return Value
*   An XML return code: XML.SUCCESS, XML.ERROR, XML.INVALID.HANDLE

$include UNIVERSE.INCLUDE XML.H
xmlStatus = XML.SUCCESS

* Create the new ELEMENT node
xmlStatus = XDOMCreateNode( domHandle, NODE.name, "", XDOM.ELEMENT.NODE, nodeHandle)
if xmlStatus = XML.SUCCESS then
   xmlStatus = XDOMAddChild( domHandle, "", NODE.ns, nodeHandle, XDOM.NODUP)
end

if NODE.value # '' and xmlStatus = XML.SUCCESS then
   * If a value has been supplied, add a nameless child TEXT element
   xmlStatus = XDOMCreateNode(nodeHandle, '', NODE.value, XDOM.TEXT.NODE, newText)
   if xmlStatus = XML.SUCCESS then
      xmlStatus = XDOMAddChild( nodeHandle, "", NODE.ns, newText, XDOM.NODUP)
      xmlStatus = XDOMClose(newText)
   end
end

if NODE.attr # '' and xmlStatus = XML.SUCCESS then
   * If there are attribute to be added, do them now
   qty.ATTR = dcount( NODE.attr<1>, @VM)
   for num.ATTR = 1 to qty.ATTR while xmlStatus = XML.SUCCESS
      name.ATTR = NODE.attr<1,num.ATTR>
      if name.ATTR # '' then
         value.ATTR = NODE.attr<2,num.ATTR>
         xmlStatus = XDOMCreateNode( nodeHandle, name.ATTR, value.ATTR, XDOM.ATTR.NODE, attrHandle)
         if xmlStatus = XML.SUCCESS then
            xmlStatus = XDOMAddChild( nodeHandle, "", NODE.ns, attrHandle, XDOM.NODUP)
            xmlStatus = XDOMClose(attrHandle)
         end
      end
   next num.ATTR
end

return (xmlStatus)

Once compiled and cataloged, this function is available to be used.

A Working Example

This program uses the above function to create a sample XML document:

* manual build of xml using XDOM
$include UNIVERSE.INCLUDE XML.H
* Define the function we are going to use
deffun Add_XMLElement(a,b,c,d,e,f)

* Trying to build xml:
* <PurchaseHistory>
*   <PurchaseItem Ref="ABC123">Learning the Ropes</PurchaseItem>
*   <PurchaseItem Ref="CODE86">Getting Smarter</PurchaseItem>
*   <PurchaseItem Ref="CODE007" Security="High">Bonding the James Way</PurchaseItem>
* </PurchaseHistory>

* Create the doc
xmlStatus = XDOMCreateRoot(domHandle)

* Add the root element
xmlStatus = Add_XMLElement( domHandle, "PurchaseHistory", "", "", "", newnode)

* add the PurchaseItem elements
attrNode = "Ref": @AM: "ABC123"
xmlStatus = Add_XMLElement( newnode, "PurchaseItem", "Learning the Ropes", attrNode, "", subnode)
xmlStatus = XDOMClose(subnode)
attrNode = "Ref": @AM: "CODE86"
xmlStatus = Add_XMLElement( newnode, "PurchaseItem", "Getting Smarter", attrNode, "", subnode)
xmlStatus = XDOMClose(subnode)
attrNode = "Ref":@VM:"Security": @AM: "CODE007": @VM: "High"
xmlStatus = Add_XMLElement( newnode, "PurchaseItem", "Bonding the James Way", attrNode, "", subnode)
xmlStatus = XDOMClose(subnode)

* Output the XML
xmlStatus = XDOMWrite( domHandle, doc.XML, XML.TO.STRING)
crt "XML = "
crt "[":doc.XML:"]"

xmlStatus = XDOMClose(newnode)
xmlStatus = XDOMClose(domHandle)

As you can see, this is a much easier way of coding the manual build of XML, whilst maintaining the XDOM API-type function interface.

Compile and run this program, and it will produce the following output:

XML =
[<PurchaseHistory>
  <PurchaseItem Ref=”ABC123″>Learning the Ropes</PurchaseItem>
  <PurchaseItem Ref=”CODE86″>Getting Smarter</PurchaseItem>
  <PurchaseItem Ref=”CODE007″ Security=”High”>Bonding the James Way</PurchaseItem>
</PurchaseHistory>
]

Creating Processing Instruction nodes

The Processing Instruction nodes created via the XDOM API do NOT allow children to be added. This makes it impossible to add Attribute nodes to define the data for the node.

So, how does the data component of a Processing Instruction node get populated?

The XDOMCreateNode() function has a parameter for the nodeValue. This parameter is not used for all nodeType nodes.

Fortunately, it is used for XDOM.PROC.INST.NODE (Processing Instruction) nodes. Simply provide the data, formatted exactly as required for the node, in the nodeValue parameter.

Example

$include UNIVERSE.INCLUDE XML.H
XSL = 'cd_catalog.xsl'
theValue = 'type=':dquote('text/xsl'):' href=':dquote(XSL)
domHandle = ''
stat = XDOMCreateRoot(domHandle)
if stat # XML.ERROR then
   stat=XDOMCreateNode(domHandle, "xml-stylesheet", theValue, XDOM.PROC.INST.NODE, NEW.NODE)
   if stat # XML.ERROR then
      stat = XDOMAddChild(domHandle, "", "", NEW.NODE, XDOM.NODUP)
      if stat # XML.ERROR then
         doc.XML = ''
         stat = XDOMWrite( domHandle, doc.XML, XML.TO.STRING)
         if stat # XML.ERROR then
            crt "XML = [":doc.XML:"]"
         end
      end
   end
end
if stat then
   crt "BAD: STATUS() = ":stat
   stat = XMLGetError( xmlERR, xmlDESC)
   crt "[":xmlERR:"] ":xmlDESC
end

NOTE

The theValue defines the data portion of the Processing Instruction node. The XDOMCreateNode() function uses this to populate the node in the XML document.

Output

XML = [<?xml-stylesheet type=”text/xsl” href=”cd_catalog.xsl”?>
]

Traversing an XML node’s attributes

UV 10.2

The XDOM API in UniVerse provides the XDOMLocateNode() to traverse up and down, and iterate across the nodes in an XML document.

This works well, until you need to iterate through the attributes of an XML document. The XDOMLocateNode() will find the first child attribute, but there is no way to use it to iterate through all the attributes. This is because attributes are NOT child nodes of elements.

How to traverse a node’s attributes?

Using XPath 1.0 syntax and the XDOMEvaluate() function (which is in the U2 Basic Extensions manual – it is just not in the index!), it is possible to query the quantity of attributes on a node. It is then possible to query the name of each of the numbered attributes.

In the example below, the show.attribs subroutine uses two XPath 1.0 queries:

  1. The first (assigned to xpath.ATTR.COUNT) is passed to the XDOMEvaluate() function, and returns the quantity of attributes in the node.
  2. The second (assigned to xpath.ATTR.NODE) is passed to the XDOMEvaluate() function, and returns the name of the specified attribute number.

Once the name of the attribute is known, the XDOMGetAttribute() function returns a XDOM handle to the attribute node, and XDOMGetNodeValue() returns the value of the attribute node.

Example

$include UNIVERSE.INCLUDE XML.H  sample.XML =  '<?xml version="1.0" encoding="UTF-8"?>'
sample.XML := '<animals>'
sample.XML := '        <animal species="giraffe" size="large" horns="yes">'
sample.XML := '                <legs>4</legs>'
sample.XML := '                <color>tan/orange</color>'
sample.XML := '                <tail>Y</tail>'
sample.XML := '        </animal>'
sample.XML := '        <animal species="rhino" size="large" horns="yes" dangerlevel="extreme">'
sample.XML := '                <legs>4</legs>'
sample.XML := '                <color>mud</color>'
sample.XML := '                <tail>Y</tail>'
sample.XML := '        </animal>'
sample.XML := '        <animal species="rodent" size="tiny" cutenesslevel="medium">'
sample.XML := '                <legs>4</legs>'
sample.XML := '                <color>fawn</color>'
sample.XML := '                <tail>Y</tail>'
sample.XML := '        </animal>'
sample.XML := '</animals>'

nsMAP = ""
doc.ID = "Sample2.xml"
path.LOOK = "//animals"

stat = XDOMOpen(sample.XML,XML.FROM.STRING,dom.HANDLE)
if stat # XML.ERROR then
    stat = XDOMLocate(dom.HANDLE,path.LOOK,nsMAP,dom.ANIMALS)
    if stat # XML.ERROR then
       * Found the root node. Use XDomLocateNode to step down to first child
       stat = XDOMLocateNode( dom.ANIMALS, XDOM.CHILD, XDOM.FIRST.CHILD, XDOM.ELEMENT.NODE, dom.ANIMALS.CHILD)
       if stat # XML.ERROR then
          * Found first child element
          loop
             stat = XDOMGetNodeName(dom.ANIMALS.CHILD, name.NODE)
             if stat # XML.ERROR then
                crt "Name = ":squote(name.NODE)
                gosub show.attribs
             end
             * Find the next node
             nstat = XDOMLocateNode( dom.ANIMALS.CHILD, XDOM.NEXT.SIBLING, 1, XDOM.ELEMENT.NODE, dom.ANIMALS.CHILD)
          while stat # XML.ERROR and nstat # XML.ERROR do
          repeat
          cstat = XDOMClose(dom.ANIMALS.CHILD)
       end
       cstat = XDOMClose(dom.ANIMALS)
    end
    cstat = XDOMClose(dom.HANDLE)
end

if stat then
    crt "BAD: STATUS() = ":stat
    stat = XMLGetError( xmlERR, xmlDESC)
    crt "[":xmlERR:"] ":xmlDESC
end

stop

* This is where we show all the node's attributes
show.attribs:

qty.ATTRIB = 0

* Get the count of attributes for this node
xpath.ATTR.COUNT = "count(@*)"
stat = XDOMEvaluate( dom.ANIMALS.CHILD, xpath.ATTR.COUNT, nsMAP, qty.ATTRIB)
if stat # XML.ERROR and qty.ATTRIB then
    * There ARE attributes for this node
    for num.ATTRIB = 1 to qty.ATTRIB while stat # XML.ERROR
       * Use XPath 1.0 syntax to get the name of each attribute
       xpath.ATTR.NAME = "name(@*[position()=":num.ATTRIB:"])"
       name.ATTRIB = ''
       stat = XDOMEvaluate(dom.ANIMALS.CHILD,xpath.ATTR.NAME,nsMAP, name.ATTRIB)
       if stat # XML.ERROR then
          * Now we have the attirbute name, we can get the value
          crt "  ":name.ATTRIB:" = ":
          stat = XDOMGetAttribute(dom.ANIMALS.CHILD, name.ATTRIB, node.ATTRIB)
          if stat # XML.ERROR then
             * Got the attribute node, get the value
             stat = XDOMGetNodeValue(node.ATTRIB, val.ATTRIB)
             crt dquote(val.ATTRIB):
             cstat = XDOMClose(node.ATTRIB)
          end
          crt
       end
    next num.ATTRIB
end

return

Create XML with schema/namespace references via API

UV 10.2

Under UniVerse 10.2, there are no API functions that will add Namespace or Schema attributes to a node. This makes it more difficult, but not impossible, to create an XML document that contains Namespace or Schema references.
   

The Normal approach

To create a simple XML document via API calls is very straight forward: 
  • Create root DOM using XDOMCreateRoot()
  • Add root node using XDOMCreateNode() and XDOMAddChild()
  • Build XML using root node

The Steps needed

To create an XML document that contains Namespace or Schema references needs the following steps to be performed:

  • Create an XML string of the root node containing the Namespace and/or Schema reference
  • Create DOM using XDOMOpen() by supplying the XML string and using the XML.FROM.STRING location option.
  • Obtain a reference to the root node using XDOMLocate()
  • Build XML using root node    

Example

We need to create the following XML document:
<card xmlns=http://businesscard.org
      xsi:schemaLocation=http://businesscard.org businesscard.xsd”>
   <name>John Doe</name>
</card>

Step 1

Create the XML string containing the namespace and/or schema references:
   
$include UNIVERSE.INCLUDE XML.H
root.XML = ‘<card xmlns=http://businesscard.org
root.XML := ‘xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance&#8221; ‘
root.XML := ‘xsi:schemaLocation=http://businesscard.org businesscard.xsd”></card>

Step 2

Create DOM handle using the XML string:
   
xmlStat = XDOMOpen( root.XML, XML.FROM.STRING, root.DOM)

Step 3

Obtain a reference to the root node
   
xmlStat = XDOMLocate( root.DOM, “/a:card”, map.NS, card.NODE)

Step 4

The node card.NODE can now be used to build the rest of the XML document:
   
xmlStat = XDOMCreateNode( card.NODE, “name”, “”, XDOM.ELEMENT.NODE, name.NODE)
xmlStat = XDOMAddChild( card.NODE, “”, map.NS, name.NODE, XDOM.NODUP)
xmlStat = XDOMCreateNode( name.NODE, “”, “John Doe”, XDOM.TEXT.NODE, text.NODE)
xmlStat = XDOMAddChild( name.NODE, “”, map.NS, text.NODE, XDOM.NODUP)
xmlStat = XDOMWrite( root.DOM, final.XML, XML.TO.STRING)
CRT “XML = [“:final.XML:”]”
   
Output from the above example should be:
XML = [<card xmlns=”http://businesscard.org” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance&#8221; xsi:schemaLocation=”http://businesscard.org businesscard.xsd”><name>John Doe</name></card>
]    
 
The key is Steps 1-3, which result in an XML document that contains the necessary namespace and/or schema references.

TEST.XML.VAL

This program is a very handy tool I created to help understand what is happening with the XDOMValidate() function of UniVerse.
   
* TESTING XML VALIDATION
$include UNIVERSE.INCLUDE XML.H
   
b.CONTINUE = @TRUE
for num.STEP = 1 to 4 while b.CONTINUE
on num.STEP gosub GET.ARGS,
LOAD.XML,
VALIDATE.XML,
CLOSE.XML
next num.STEP
   
stop
   
   
GET.ARGS:
   
b.SHOW.SYNTAX = @FALSE
GET(ARG.) id.XML else b.SHOW.SYNTAX = @TRUE
GET(ARG.) id.XSD else b.SHOW.SYNTAX = @TRUE
GET(ARG.) type.SRC else type.SRC = “F” ;* Default to file
type.SRC = upcase(type.SRC)
if type.SRC = ” or index(“FSD”,type.SRC,1) = 0 then b.SHOW.SYNTAX = @TRUE
   
if b.SHOW.SYNTAX then
b.CONTINUE = @FALSE
crt “SYNTAX: TEST.XML.VAL <xml> <xsd> [<src>]”
crt
crt ” <xml> is the identity of the XML file to validate.”
crt ” <xsd> is the identity of the XSD used to validate the XML.”
crt ” <src> is a code indicating how to supply the XML and XSD to XDOMValidate().”
crt
crt ” Both <xml> and <src> parameters can use one of two formats:”
crt ” Simple: <itemID>”
crt ” ‘MyXml.xml’, ‘Astrictschema.xsd'”
crt ” Complex: <fileName>/<itemID>”
crt ” ‘XML.RULES/Another.xml’ ‘XMl.SCHEMAS/ASlackSchema.XSD'”
crt
crt ” The <src> parameter can be one of three values:”
crt ” F : Process FROM FILE (the default source)”
crt ” S : Process FROM STRING”
crt ” D : Process FROM DOM”
crt
end else
begin case
case type.SRC = “F”
SRC.XML = XML.FROM.FILE
 
case type.SRC = “S”
SRC.XML = XML.FROM.STRING
 
case type.SRC = “D”
SRC.XML = XML.FROM.DOM
 
end case
end
   
return
   
   
LOAD.XML:
   
begin case
case type.SRC = “F”
SRC.XML = XML.FROM.FILE
SRC.XSD = XML.FROM.FILE
the.XML = id.XML
the.XSD = id.XSD
 
case type.SRC = “S”
SRC.XML = XML.FROM.STRING
SRC.XSD = XML.FROM.STRING
   
* Open the xml write to string, close xml
stat = XDOMOpen(id.XML, XML.FROM.FILE, dom.XML)
if stat = XML.SUCCESS then
stat = XDOMWrite( dom.XML, the.XML, XML.TO.STRING)
if stat # XML.SUCCESS then
crt “XDOMWrite failed for “:squote(id.XML)
hstat = XMLGetError(code.ERR, desc.ERR)
crt “(result=”:stat:”) [“:code.ERR:”] “:desc.ERR
b.CONTINUE = @FALSE
end
hstat = XDOMClose(dom.XML)
end else
crt “XDOMOpen failed for “:squote(id.XML)
hstat = XMLGetError(code.ERR, desc.ERR)
crt “(result=”:stat:”) [“:code.ERR:”] “:desc.ERR
b.CONTINUE = @FALSE
end
 
* Open the xsd write to string, close xsd
stat = XDOMOpen(id.XSD, XML.FROM.FILE, dom.XSD)
if stat = XML.SUCCESS then
stat = XDOMWrite( dom.XSD, the.XSD, XML.TO.STRING)
if stat # XML.SUCCESS then
crt “XDOMWrite failed for “:squote(id.XSD)
hstat = XMLGetError(code.ERR, desc.ERR)
crt “(result=”:stat:”) [“:code.ERR:”] “:desc.ERR
b.CONTINUE = @FALSE
end
hstat = XDOMClose(dom.XSD)
end else
crt “XDOMOpen failed for “:squote(id.XSD)
hstat = XMLGetError(code.ERR, desc.ERR)
crt “(result=”:stat:”) [“:code.ERR:”] “:desc.ERR
b.CONTINUE = @FALSE
end
 
case type.SRC = “D”
* We need to open the XML and XSD as DOM objects and then validate
SRC.XML = XML.FROM.DOM
SRC.XSD = XML.FROM.FILE
the.XSD = id.XSD
 
stat = XDOMOpen(id.XML, XML.FROM.FILE, the.XML)
if stat # XML.SUCCESS then
crt “XDOMOpen failed for “:squote(id.XML)
hstat = XMLGetError(code.ERR, desc.ERR)
crt “(result=”:stat:”) [“:code.ERR:”] “:desc.ERR
b.CONTINUE = @FALSE
end
 
end case
   
return
   
   
VALIDATE.XML:
   
* The XML needs to have some reference to the XSD, otherwise it will always be valid!
   
stat = XDOMValidate( the.XML, SRC.XML, the.XSD, SRC.XSD)
if stat = XML.SUCCESS then
crt squote(id.XML):” is valid according to “:squote(id.XSD)
end else
hstat = XMLGetError(code.ERR, desc.ERR)
crt “(result=”:stat:”) [“:code.ERR:”] “:desc.ERR
end
   
return
   
   
CLOSE.XML:
   
if type.SRC = “D” then
stat = XDOMClose(the.XML)
stat = XDOMClose(the.XSD)
end
   
return

XPath and Namespaces

Namespace handling
UV 10.2 has some quirks when handling namespaces, particularly when processing XPath queries.
Default Namespace
If the XML document contains a default namespace, you need to provide a namespace map to associate the namespace with a prefix, then use the prefix in the XPath queries.
Example
include UNIVERSE.INCLUDE XML.H
xml = ‘<?xml version=”1.0″ ?>’
xml := ‘<TheSearchResults xmlns=”http://theresults >’
xml := ‘<response>’
xml := ‘<exception>Exception Message</exception>’
xml := ‘</response>’
xml := ‘</TheSearchResults>’
nsMAP = ‘xmlns:a=http://theresults’
xpathStr = ‘/a:TheSearchResults/a:response/a:exception’
if XDOMOpen(xml,XML.FROM.STRING,hd) # XML.ERROR then
   if XDOMLocate(hd,xpathStr,nsMAP,hn) # XML.ERROR then
      if not(XDOMGetnodename(hn,name)) and name[9] = ‘exception’ then
         crt ‘Located ‘:name
      end
   end
end
NOTE: The XML document makes use of a default namespace. The nsMAP defines the mapping of the default namespace to a prefix. The XDOMLocate function uses the nsMAP and an appropriate XPath query that uses the prefixes to correctly find the appropriate node.
Namespace Maps
The definition of a namespace map needs to exclude quotes from the URI. The quotes do not get correctly processed by UniVerse.
Example
$include UNIVERSE.INCLUDE XML.H
xml = ‘<?xml version=”1.0″ encoding=”UTF-8″?>’
xml := ‘<Notifications xmlns=”http://www.address.com/ChangeInCircumstance
xml := ‘<Header>’
xml := ‘<BatchId>ab1234</BatchId>’
xml := ‘</Header></Notifications>’
nsMAP = ‘xmlns:a=http://www.address.com/ChangeInCircumstance
nsMAP := ‘xmlns:person=http://www.govtalk.gov.uk/people/AddressAndPersonalDetails ‘
nsMAP := ‘xmlns:pdt=http://www.govtalk.gov.uk/people/PersonDescriptives ‘
nsMAP := ‘xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance’
XPATH = “/a:Notifications/a:Header/a:BatchId/text()
stat = XDOMOpen(xml,XML.FROM.STRING,hd)
if stat # XML.ERROR then
   stat = XDOMLocate(hd,XPATH,nsMAP,TEXTNODEHANDLE)
   if stat # XML.ERROR then
      stat = XDOMGetNodeValue(TEXTNODEHANDLE,NODEVALUE)
      if stat # XML.ERROR then
         CRT NODEVALUE
      end
   end
end
if stat then
   crt “BAD: STATUS() = “:stat
   stat = XMLGetError( xmlERR, xmlDESC)
   crt “[“:xmlERR:”] “:xmlDESC
end
NOTE: The nsMAP defines the prefix-to-namespace mapping without using double-quotes. This is different to how the namespaces are defined within the XML document

Correctly validate an XML document

UV 10.2

Using the XDOMValidate() function to confirm that a specific XML document is valid according to a specific XML schema requires the XML document to include a reference to the schema item to be used for the validation.

Without namespace

An XML document that is not using namespaces needs to have the attributes xmlns:xsi and xsi:noNamespaceSchemaLocation included in the opening root tag:
 
<Info xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance&#8221; xsi:noNamespaceSchemaLocation=“w3c_example.xsd”/>   

With Namespace

An XML document that is using namespaces needs to have the attributes xmlns:xsi and xsi:schemaLocation added to the tag that defines the namespaces:
 
                     xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance&#8221;
                     xsi:schemaLocation=http://databonnet.sellstuff.com/schema/ prodinfo.xsd”>
 
The xsi:schemaLocation attribute needs to reference the appropriate namespace and the appropriate item id of the schema to be used (separated by whitespace).

Schema Reference

The contents of either the xsi:noNamespaceSchemaLocation and xsi:schemaLocation attributes needs to reference the item ID of a schema item in the same location (file) as the XML being validated.
If it does not, then the validation stops with no errors (as it cannot locate the schema to use to perform the validation), and the XDOMValidate() function returns XML.SUCCESS.
BASIC example
$include UNIVERSE.INCLUDE XML.H
id.XML = “test.xml”
id.XSD = “test.xsd”
stat = XDOMValidate( id.XML, XML.FROM.FILE, id.XSD, XML.FROM.FILE)
if stat = XML.SUCCESS then
   crt squote(id.XML):” is valid according to “:squote(id.XSD)
end else
   hstat = XMLGetError(code.ERR, desc.ERR)
   crt “(stat=”:stat:”) [“:code.ERR:”] “:desc.ERR
end

NOTES:

The item named in id.XML must contain either xsi:noNamespaceSchemaLocation or xsi:schemaLocation as an attribute. As a consequence, the xmlns:xsi attribute is needed to define the xsi: prefix.
The schema item defined in these [NoNamespace]schemalocation attributes MUST exist in the same file as the item named in id.XML.
The item named in id.XSD must exist, but it is not used in the validation so it can be any item at all.
This is solely based on the XML.FROM.FILE source being used. Other source options may behave differently.

Explanation

When using the XDOMValidate() function using the XML.FROM.FILE source, the schema item defined in the [NoNamespace]schemalocation attribute is used as the sole source for the schema when validating. The function seems to ignore the contents of the schema item supplied. If the schema item from the xml document  is not found in the same file as the xml document, the schema validation cannot proceed. No errors are produced about the missing schema and because no schema is loaded the xml document is reported as valid (by virtue of a non-failure result from the XDOMValidate() function).