Home > Miscellaneous, XQuery > XML Namespaces 101

XML Namespaces 101

Back to basics with a focus on XML namespaces.
A lot of people still struggle to use and reference namespaces correctly in XML-related functions, and most often try random combinations until it works correctly.
Hopefully, this post will clear a few things up :)

 

1. What is a namespace?

A namespace is not some exotic object but just one out of the two parts that form a node name.
In the XML Object Model, the node name of an element or attribute is composed of :

  • a namespace URI, i.e. the namespace name
  • a local name

If the namespace uri is absent (null), the node is said to be in no namespace.

 

2. Default namespaces and prefixes

In an XML document or fragment, a namespace can be defined in two ways :

  • namespace binding (prefix) declaration : xmlns:prefix="my-namespace-1"

    The scope is the element where it appears and all its descendant elements and attributes, unless it is redefined using another declaration (e.g. xmlns:prefix="my-namespace-2").
    A binding declaration applies to all qualified (i.e. prefixed) in-scope elements and attributes.

  • default namespace declaration : xmlns="my-default-ns"

    The scope is the element where it appears and all its descendants, unless it is redefined using another declaration (e.g. xmlns="new-default-ns") or undefined using an empty declaration (xmlns="").
    A default namespace declaration applies to all unqualified in-scope elements, but it does not apply to attributes.

Let’s consider a simple example :

<root xmlns="my-uri-1" xmlns:ns0="my-uri-2" ns0:version="1.0">
  <header>Hello</header>
  <ns0:item1 id="1">Some value</ns0:item1>
  <ns0:item2 xmlns:ns0="my-uri-3" xmlns="">
    <item3/>
  </ns0:item2>
</root>
  • The namespace of “root” element is “my-uri-1” because it is in the scope of the default namespace declaration and the name is unqualified.
  • The namespace of “version” attribute is “my-uri-2” because it is in the scope of the “ns0” binding and the name is qualified.
  • The namespace of “header” element is “my-uri-1” because it is in the scope of the default namespace declaration and the name is unqualified.
  • The namespace of “item1” element is “my-uri-2” because it is in the scope of the “ns0” binding and the name is qualified.
  • The “id” attribute is in no namespace, because the name is unqualified.
  • The namespace of “item2” element is “my-uri-3” because, although it is qualified with the “ns0” prefix, the initial binding to “my-uri-2” has been locally redefined to “my-uri-3”.
    From this point on, this new binding will now apply to all qualified descendant elements and attributes.
    This situation is seldom encountered though, using distinct prefixes for distinct namespaces is preferred and of course less confusing.
  • The “item3” element is in no namespace : although the name is unqualified, it is not in the scope of the initial default namespace declaration because its parent element (“item2”) has undefined it.

Checking using XMLFlattenDOM table function :

SQL> select *
  2  from table(
  3         xmlflattendom(
  4           xmlparse(document
  5  '<root xmlns="my-uri-1" xmlns:ns0="my-uri-2" ns0:version="1.0">
  6    <header>Hello</header>
  7    <ns0:item1 id="1">Some value</ns0:item1>
  8    <ns0:item2 xmlns:ns0="my-uri-3" xmlns="">
  9      <item3/>
 10    </ns0:item2>
 11  </root>'
 12           )
 13         )
 14       )
 15  ;
 
   NODE_ID NODE_NAME  NODE_TYPE  PARENT_NODE_ID NODE_VALUE  NAMESPACE_URI
---------- ---------- ---------- -------------- ----------- --------------
         1 root       element                               my-uri-1
         2 version    attribute               1 1.0         my-uri-2
         3 header     element                 1             my-uri-1
         4            text                    3 Hello       
         5 item1      element                 1             my-uri-2
         6 id         attribute               5 1           
         7            text                    5 Some value  
         8 item2      element                 1             my-uri-3
         9 item3      element                 8             

 

3. Using namespaces with XQuery

Given the above sample document, let’s say we want to extract “item3” element.

Here’s the general methodology to get the expression right :

  1. write down the raw expression, starting from the root : /root/item2/item3
  2. determine to which namespace (if any) each traversed node belongs :
    • root : my-uri-1
    • item2 : my-uri-3
    • item3 : <no namespace>
  3. declare the above two namespaces in the expression using prefix bindings :
    declare namespace ns1 = "my-uri-1";
    declare namespace ns2 = "my-uri-3";
    
  4. complete the expression by qualifying node names with the declared prefixes :
    declare namespace ns1 = "my-uri-1";
    declare namespace ns2 = "my-uri-3";
    /ns1:root/ns2:item2/item3

A couple of notes :

Prefix bindings we declare in the expression don’t have to be the same as the XML document.
For clarity, it may be better to use the same ones, sometimes it’s just not possible, as in my example where “ns0” is used twice in the document with different target namespaces.

It’s also tempting to declare ‘my-uri-1’ as default to mirror what is done in the document.
However, it’s not possible here because it would place “item3” in that namespace as the name is not qualified.

Another example?

Extracting the “header” element :

  1. Path : /root/header
  2. Namespaces :
    • root : my-uri-1
    • header : my-uri-1

    Since all traversed nodes belong to the same namespace, we can use a default namespace.

  3. Declaration :
    declare default element namespace "my-uri-1";
  4. Final expression :
    declare default element namespace "my-uri-1";
    /root/header
    

 

4. Using namespaces with SQL/XML functions XMLExists, XMLQuery and XMLTable

With both XMLExists and XMLQuery, we must declare the namespace bindings in the XQuery expression prolog, for example :

with sample_data (xmldoc) as (
  select xmlparse(document
'<root xmlns="my-uri-1" xmlns:ns0="my-uri-2" ns0:version="1.0">
  <header>Hello</header>
  <ns0:item1 id="1">Some value</ns0:item1>
  <ns0:item2 xmlns:ns0="my-uri-3" xmlns="">
    <item3/>
  </ns0:item2>
</root>'
         )
  from dual
)
select xmlserialize(content
         xmlquery(
          'declare namespace ns1 = "my-uri-1"; (: :)
           declare namespace ns2 = "my-uri-3"; (: :)
           /ns1:root/ns2:item2/item3'
           passing t.xmldoc
           returning content
         )
       )
from sample_data t ;

XQuery comments after each declaration are there so that the semicolon is not interpreted as a SQL statement terminator.
Depends on the SQL client tool used to run the query. More info @ http://docs.oracle.com/database/121/ADXDB/xdb04cre.htm#ADXDB5139

Though namespaces may be declared the same, XMLTable provides a more user-friendly way of declaring namespaces via the XMLNamespaces() clause.
The scope of namespaces declared using XMLNamespaces() is the whole XMLTable context, not only the main XQuery expression. That means declarations apply to the PATH expressions in the COLUMNS clause as well.

 

5. Wrap-up

One final example, to conclude :

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
                xmlns="http://xmlns.example.org/Main"
                xmlns:mta="http://xmlns.example.org/MetaData">  
  <soap:Body>
    <Result>
      <ResultDataSets xmlns="http://xmlns.example.org/DataSets" 
                      xmlns:x="http://xmlns.example.org/DataSet/Data">
        <ResultDataSet>
          <x:Row>
            <x:Col mta:type="c1">1</x:Col>
            <x:Col mta:type="c2">ABC</x:Col>
            <x:Col mta:type="c3">2016-01-01</x:Col>
          </x:Row>
          <x:Row>
            <x:Col mta:type="c1">2</x:Col>
            <x:Col mta:type="c2">DEF</x:Col>
            <x:Col mta:type="c3">2016-01-02</x:Col>
          </x:Row>
          <x:Row>
            <x:Col mta:type="c1">3</x:Col>
            <x:Col mta:type="c2">GHI</x:Col>
            <x:Col mta:type="c3">2016-01-03</x:Col>
          </x:Row>
        </ResultDataSet>
      </ResultDataSets>
    </Result>
  </soap:Body>  
</soap:Envelope>

Say we want to get the data in relational format, like this :

        C1 C2         C3
---------- ---------- -----------
         1 ABC        01/01/2016
         2 DEF        02/01/2016
         3 GHI        03/01/2016

  1. Which nodes do we need to extract?

    Path to row items : /Envelope/Body/Result/ResultDataSets/ResultDataSet/Row
    Path to column items (relative to a row item) : Col[@type="..."]

  2. Namespaces traversed :
    Envelope       : http://schemas.xmlsoap.org/soap/envelope/
    Body           : http://schemas.xmlsoap.org/soap/envelope/
    Result         : http://xmlns.example.org/Main
    ResultDataSets : http://xmlns.example.org/DataSets
    ResultDataSet  : http://xmlns.example.org/DataSets
    Row            : http://xmlns.example.org/DataSet/Data
    
    Col            : http://xmlns.example.org/DataSet/Data
    @type          : http://xmlns.example.org/MetaData
    
  3. Now, let’s declare them all using the XMLNamespaces clause :

    I’ll choose to make one the default (e.g. 'http://xmlns.example.org/Main') so that we don’t have to qualify node belonging to that namespace :

    xmlnamespaces(
      'http://schemas.xmlsoap.org/soap/envelope/' as "s"
    , 'http://xmlns.example.org/MetaData' as "m"
    , 'http://xmlns.example.org/DataSets' as "ds"
    , 'http://xmlns.example.org/DataSet/Data' as "d"
    , default 'http://xmlns.example.org/Main'
    )
    

    Here again, I’ve decided to declare different prefixes than the ones originally used in the document. Only the URI they’re bound to matters.

  4. Now we can complete the row path by prefixing each node name (when required) : '/s:Envelope/s:Body/Result/ds:ResultDataSets/ds:ResultDataSet/d:Row'

    Likewise for the column path : 'd:Col[@m:type="..."]'

Final query :

SQL> with sample_data (xmldoc) as (
  2    select xmlparse(document
  3  '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  4                  xmlns="http://xmlns.example.org/Main"
  5                  xmlns:mta="http://xmlns.example.org/MetaData">
  6    <soap:Body>
  7      <Result>
  8        <ResultDataSets xmlns="http://xmlns.example.org/DataSets"
  9                        xmlns:x="http://xmlns.example.org/DataSet/Data">
 10          <ResultDataSet>
 11            <x:Row>
 12              <x:Col mta:type="c1">1</x:Col>
 13              <x:Col mta:type="c2">ABC</x:Col>
 14              <x:Col mta:type="c3">2016-01-01</x:Col>
 15            </x:Row>
 16            <x:Row>
 17              <x:Col mta:type="c1">2</x:Col>
 18              <x:Col mta:type="c2">DEF</x:Col>
 19              <x:Col mta:type="c3">2016-01-02</x:Col>
 20            </x:Row>
 21            <x:Row>
 22              <x:Col mta:type="c1">3</x:Col>
 23              <x:Col mta:type="c2">GHI</x:Col>
 24              <x:Col mta:type="c3">2016-01-03</x:Col>
 25            </x:Row>
 26          </ResultDataSet>
 27        </ResultDataSets>
 28      </Result>
 29    </soap:Body>
 30  </soap:Envelope>'
 31           )
 32    from dual
 33  )
 34  select x.*
 35  from sample_data t
 36     , xmltable(
 37         xmlnamespaces(
 38           'http://schemas.xmlsoap.org/soap/envelope/' as "s"
 39         , 'http://xmlns.example.org/MetaData' as "m"
 40         , 'http://xmlns.example.org/DataSets' as "ds"
 41         , 'http://xmlns.example.org/DataSet/Data' as "d"
 42         , default 'http://xmlns.example.org/Main'
 43         )
 44       , '/s:Envelope/s:Body/Result/ds:ResultDataSets/ds:ResultDataSet/d:Row'
 45         passing t.xmldoc
 46         columns c1  number       path 'd:Col[@m:type="c1"]'
 47               , c2  varchar2(10) path 'd:Col[@m:type="c2"]'
 48               , c3  date         path 'd:Col[@m:type="c3"]'
 49       ) x
 50  ;
 
        C1 C2         C3
---------- ---------- -----------
         1 ABC        01/01/2016
         2 DEF        02/01/2016
         3 GHI        03/01/2016

 

Advertisements
  1. gurupriyan
    August 23, 2016 at 04:10

    Nice Article. Thanks for Sharing.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s