DOM for Web Services, Part 2
by Faheem Khan
|
Pages: 1, 2, 3
Working with namespaces
W3C DOM Level 2 does not have any method to find the
namespaceURI associated with a particular prefix. The
generic Node interface in W3C DOM has a property named
namespaceURI, which tells the namespace associated with a
particular node, but it only looks for the namespace URI in the
particular node and does not perform any programmatic lookup
(e.g. check the namespaces declared with parents and grandparents of a
particular node).
The IXMLDOMNode interface in MSXML has a property
named namespaceURI, which dynamically finds the namespace
URI associated with a particular node. But we would like to stick with
standard DOM, therefore we will not use this property.
Take a look at the recursive method named
getNamespaceURI() in Listing 2. This function takes an
element node and a namespace prefix as
parameters and returns the namespace URI associated with the
prefix. If you pass null as namespace prefix as the
second parameter then the getNamespaceURI() method will
return the default namespace of the element node. It
starts from the element node and progressively looks for the namespace
URIs associated with the parent, grandparent, etc. The method stops
its search when it has either found the required namespace URI or it
hits the document element.
The definitionsNamespacePrefix =
getPrefix(definitionsElement.nodeName) line reads the namespace
prefix associated with the root definitions element. The
getPrefix() method takes a qualified name of a node and
returns the namespace prefix.
After reading the prefix, we pass it to the
getNamespaceURI() method, which returns the namespace URI
associated with the prefix. We have stored the namespace URI value in
a variable named definitionsNamespaceURI. Once we have
the namespace URI, we can simply compare it to the WSDL namespace by
using an definitionsNamespaceURI !=
"http://schemas.xmlsoap.org/wsdl/" condition.
Reading the local name of an element node
The generic Node interface in W3C DOM contains an
attribute named localName. The localName
property always returns the local part of the qualified name. The
definitionsElement.localName property call will return
"definitions" for both Listings 1 and 4.
Unfortunately MSXML 4 does not implement the localName
property. Instead MSXML implements its own baseName
property, which returns the local part of the qualified name. As our
focus is only on the standard W3C features, we are trying to avoid the
use of Microsoft-specific methods for WSDL processing.
Listing 2 includes a method
named getLocalName(), which takes a complete qualified
name and returns the local part of the qualified name. In order to
check the name of the root element, we have checked getLocalName
(definitionsElement.nodeName) != "definitions" condition. The
definitionsElement.nodeName returns the complete
qualified name, which is passed as a parameter to the
getLocalName() method. The getLocalName()
method returns the local part of the qualified name.
Getting the children of an element
We have checked that the root element of the WSDL file is
definitions and the namespace URI associated with the
definitions element is
http://schemas.xmlsoap.org/wsdl/. The next step is to
check the service child element of the definitions
element.
The IXMLDOMElement interface has a standard W3C DOM
method named getElementsByTagName(), which takes a
tag name as a parameter. This method searches all
descendant (child, grandchild, grand grand child etc.) nodes of the
element node and returns a list of those nodes whose tag names match
the tag name that you passed to the
getElementsByTagName() method call.
The getElementsByTagName() works on qualified names. This
means that the method will include an element in the list only if both
namespace prefix and the local name of an element match with the tag
name that you provided to the method call. That's why this method is
not suitable for our purpose, as we don't want to assume that the
author of the WSDL file will choose a particular prefix for the WSDL
namespace.
The Element interface in W3C DOM contains another method
named getElementsByTagNameNS(). This method takes two
parameters namely, a namespace URI and the local
name of an element. This method searches all descendant nodes
of the element node object with matching namespace URI and local
names. This method would have worked perfect for our purpose, however
unfortunately MSXML does not implement this method.
Therefore, in order to find all the service elements that are direct
children of the definitions root element, we have to
write some simple logic. In Listing
2, we have first called the
definitionsElement.childNodes property. The
childNodes property belongs to the
IXMLDOMNode interface and returns a list of all child
nodes of an element.
The list of child nodes is in the form of an object that exposes an
IXMLDOMNodeList interface. The
IXMLDOMNodeList interface corresponds to the
NodeList interface in W3C DOM.
The IXMLDOMNodeList interface provides methods to iterate
through the list to find element matching certain criteria. So we have
to run a loop and check all nodes with node type
"element" and with local name "service". The
for-if combination block after the
definitionsElement.childNodes line in Listing 2 performs this job.
This for-if combination block does not only check the
local name of a node, it also checks the type of a node. Let's see why
and how.
Checking the type of a node
It is not necessarily the case that the list of child nodes that the
childNodes property returns are of "element"
type. They may be of other types of nodes as well (e.g. text nodes,
comment nodes, etc.). Therefore, in addition to checking that the
local name of a child node is "service", we also have to
check that the type of the child node is "element".
When we find the first node whose local name is
"service", and type of the node is
"element", we quit the loop.
In order to check the type of a node, we have used the
nodeType property of the IXMLDOMNode
interface. This is a standard W3C DOM property, which returns an
integer value of a node. W3C DOM has defined integer values for 12
different types of nodes. Some of the commonly used types of nodes
include the following:
| Type of node | Integer value |
| Element node | 1 |
| Attribute node | 2 |
| Text node | 3 |
| Processing instruction node | 7 |
| Comment node | 8 |
| Document node | 9 |
So we can simply call the nodeType property of any node
and compare it to an integer value in order to check what type of node
it is. The for-if combination block brings all the nodes
of type "element" and local names "service".
We have passed each of the service elements to a method named
getServiceTable(). The getServiceTable()
method will process the service element and author a complete HTML
table representing the presentation logic of the service
element. Therefore, we will get one complete HTML table for each
service element in the WSDL file. Let's see how the
getServiceTable() method works.
Authoring tables
The first thing that the getServiceTable() method in Listing 2 does is that it creates a
new table object. The
document.createElement("table") method creates a new
table element. The next few lines of code in this method set
parameters related to the visual appearance of the table (border,
background color, cell padding, etc.). The result of these lines is an
empty table element as shown below:
<table border="6" bgColor="#F0F8FF" cellPadding="4" cellSpacing="5" borderColor="#D2B48C"/>
After creating the table object, we need to create a
tbody element. The var tBody =
document.createElement("tbody") authors a new standalone table
body. You will now attach the newly created table
body to the table element that you have already
authored. The table.appendChild(tBody) method call
performs this job. Now the HTML table looks like the following:
<table border="6" bgColor="#F0F8FF" cellPadding="4" cellSpacing="5" borderColor="#D2B48C">
<tbody/>
</table>
Next, we need to author all the child elements of the tbody
element. Look at the tbody.appendChild(createRow("Name of
service", serviceElement.getAttribute("name"))) line. This line
of code performs three important things that we need to discuss
separately:
- Reads the
nameattribute value of theserviceelement. - Creates a new row of an HTML table.
- Appends the newly created row to the
tbodystructure.
Let's discuss these three things in detail before proceeding further.
Reading an attribute value
Notice form Listing 1 that the service element has a name
attribute, whose value is "CityPortal". This is the name
of the web service that we are trying to transform into an HTML
file. We would like to read the value of the name
attribute and include the value in the HTML code.
The serviceElement.getAttribute("name") method call
returns the name attribute value of the
service element. The getAttribute() method
is a standard W3C DOM method that belongs to the
IXMLDOMElement interface.
Creating a single row of an HTML Table
Listing 2 includes a method named
createRow(), which takes two strings as parameters and
returns a single row of an HTML table, with the two strings set as
textual content inside two columns of the row.
For example, if you pass "string1" and
"string2" as the two parameters to the
createRow() method call, the createRow()
method will return the following structure:
<tr><td><b>string1</b></td><td>string2</td></tr>
Therefore, when we make the createRow("Name of service",
serviceElement.getAttribute("name")) method call, we are
passing "Name of Service" string as the first string and
the actual name of the service (CityPortal) as the second
string. The resulting HTML that the createRow() method
will author and return is shown below:
<tr><td><b>Name of Service</b></td><td>CityPortal</td></tr>
Let's see how the createRow() method works.
Creating text nodes and adding textual content to an element
Inside the createRow() method, we have created a table
row (tr) element, a table cell (td) and a
b element using the document.createElement()
method.
After creating the three HTML elements, we have called the
document.createTextNode() method to create a new
text node.
The creation of text nodes is not different from the
creation of new elements that we have already discussed. You create a
new text node using the document.createTextNode() method
and pass the text of the new node along with the
createTextNode() method call. Next you call the
appendChild() method of the element to which you want to
attach the text node and pass the newly created text node as a
parameter to the appendChild() method call.
After creating a text node, we first call the
appendChild() method of the bold node to attach the text
node to the bold element, thus effectively wrapping the label content
inside a bold element, as shown below:
<b>Name of Service</b>
Next we call the appendChild() method of the
cell node to attach the complete b node to
the cell node:
<td><b>Name of Service</b></td>
Next we call the appendChild() method of the row
(tr) node to attach the complete cell (td
structure) to the row node:
<tr><td><b>Name of Service</b></td></tr>
We have authored one cell of a row. The next few lines of the
createRow() method author the second cell in a similar
manner and append the second cell to the same row. The complete row
that the createRow() method authors looks like the
following:
<tr><td><b>Name of Service</b></td><td>CityPortal</td></tr>
The last line of the createRow() method returns the
completed row node back to the calling application.
Inserting child nodes
Notice that in the createRow() method, we have called the
appendChild() method of the row node twice,
that is, once for each cell of the row. The cells appear in the same
sequence that you used to call the appendChild()
method. The appendChild() method always inserts a new row
as the last child of the parent.
What if you want to append a child before another child that
you have already appended to a parent element node? The
IXMLDOMNode interface has a method named
insertBefore(), which takes two parameters: the
name to be inserted and the node before
which the new node is to be inserted. For example, the
insertBefore() method call in the following code will
insert row1 before row2:
cell.appendChild(row2);
cell.insertBefore(row1, row2);
If you pass null as the second parameter to the
insertBefore() method, the new incoming node will be
inserted as the last child of the parent node. Therefore,
cell.insertBefore(lastRow, null) and
cell.appendChild(lastRow) have identical effects.