DOM for Web Services, Part 3
by Faheem Khan
|
Pages: 1, 2
Working with DOM events
The DOM level 2 contains a separate specification for events, which can be very helpful in developing XML processing applications. This section demonstrates how to generate and handle DOM events in a Xerces application.
If you want to use DOM events in your Xerces applications, you need to
follow the DOM events architecture. The important components of the DOM
events architecture are three interfaces named EventTarget,
EventListener, and Event.
If a particular DOM implementation supports DOM events, all its nodes will
implement the EventTarget interface. Thus, you can cast any
object that implements the Node interface (or any interface
that extends the Node interface, such as the
Document interface) as an EventTarget object.
If a DOM implementation does not support DOM events and you try to cast
its nodes as EventTarget objects, your application will throw
exceptions as runtime. So you need a mechanism to verify that the DOM
implementation you are using supports DOM events before trying to cast a
node as an EventTarget object.
DOM Level 2 has an interface called DOMImplementation, whose
hasFeature() method can help you check whether a particular
DOM implementation supports a particular DOM feature. You can call the
getDOMImplementation() method of the DocumentBuilder object to get a
DOMImplementation object. You can then call the
hasFeature() method of the DOMImplementation
object to check whether it supports a particular DOM feature.
The hasFeature() method takes two parameters. The first
parameter specifies the name of the feature that you want to verify. The
DOM Level 2 Core specification defines the names of different DOM
features. The name of the events feature is "Events". The second parameter
defines a version number of the feature. For all DOM Level 2 features, you
will pass "2.0" as the second parameter.
Have a look at Listing
7, where we have tested the Events feature by calling the
DOMImplementation.hasFeature("Events","2.0") method, which
returns true (meaning Xerces supports the DOM Level 2 Events feature).
There are various types of events in DOM Level 2, e.g. mutation events, user interface events, mouse events, etc. The current Xerces implementation only supports mutation events. Mutation events are generated whenever a node gets mutated. For example, a mutation event can be generated whenever the value of an attribute in a DOM tree is changed.
Now let's see how you will use mutation events in Xerces. Have a look at Listing 7 and observe the following sequence:
- After verifying the support of Events feature, we have created a new
Documentobject and added one element node to the newly created document. We have then cast theDocumentobject as anEventTargetobject. As we have used theDocumentnode as an event target, any node in the document can generate an event for this target. - Next we have called the
addEventListener()method of theEventTargetinterface, which adds a listener to the event target (theDocumentobject). TheaddEventListener()method takes three parameters. The first parameter specifies the type of event you want to generate. There can be several types of mutation events. For example, theDOMAttrModifiedevent occurs whenever a DOM attribute value gets modified. (For a complete list of the possible types of DOM Level 2 mutation events, consult section 1.6.4 of the DOM Level 2 events specification.) The second parameter specifies an event handler object, and the third parameter specifies whether the user wants to initiate capture of an event. We don't want to use this feature, so we have passed "false" as the third parameter. - Finally, we have to write an event handler class (whose instance we
passed as the second parameter to the
addEventListerner()method). In order to write an event handler, you have to implement the DOM'sEventListenerinterface, which contains just one method namedhandleEvent(). ThehandleEvent()method of the event handler object will receive control whenever a mutation event occurs on theDocumentobject that you registered as an event target.
Notice from Listing
7 that we have written an inner class named
MyEventListener, which implements the
handleEvent() method. The handleEvent() method
takes just one parameter, which is an object that implements the
Event interface. The Event object carries
information about the event that occurred and which needs to be handled.
The handleEvent() method will normally call the
Event.getType() method to know the type of event that
occurred. You can check the type of mutation event that occurred and then
take an appropriate event handling action according to the event type that
occurred.
Notice that in the main() method of Listing
7, we have added two new attributes to the DOM Document
object after registering the event listener. Therefore, if you run the
class of Listing
7, the handleEvent() method of the
MyEventListener class will receive control twice (once for
each attribute added).
The concept of events is especially useful when you have a comprehensive XML application in which there are several DOM Documents with many nodes and each node has a possibility of being edited at several places in your business logic. In such cases, you can use the DOM events architecture. You will only have to write the event handling logic without worrying about calling the event handlers yourself. The DOM events framework will take care of calling the event handlers for you at appropriate time.
A range of DOM nodes and DOM document fragments
The concept of having a DOM range allows you to select a number of DOM nodes into a single range of nodes and then process the full range of nodes together in one go. For example, you can select a number of DOM nodes of a DOM document into a range and then import the range into another DOM document. This will import the whole range of nodes into the new document. Let's see how.
Have a look at Listing
8, which first checks whether the DOM implementation being used
supports the "Range" feature and then casts a Document object
as a DocumentRange object. This process is similar to what we
did earlier while trying to use the "Events" feature.
The DocumentRange interface exposes the
createRange() method that you can use to create a new
Range object. The Range interface exposes
methods that you need to use the DOM range feature.
A Range object represents a range of DOM nodes, which starts
at a starting point and ends at an ending point. You can move the starting
and ending points to position your range over the set of nodes of your
choice. In order to move the two points, you will need to use the
different methods of the Range interface.
When a range is initially created, both its starting and ending points are
positioned at the beginning of the document with which the range is
associated. Notice from Listing
8 that after creating a new range, we have created six elements i.e. a
wrapper (the root element) and its five children named e1,
e2, e3, e4, and e5.
Next we have called the setEndBefore() method of the
Range object, passing the e5 element as a
parameter along with the method call. This sets the end of the range
before the e5 element, which means now the range ends at the
e4 element. We have also called the setStartAfter() method of
the Range object and passed the e1 element as a
parameter. This sets the start of the range just after the e1 element,
which means the range now starts at the e2 element. Our range
now covers the e2, e3, and e4
elements.
We can now process the nodes in our range together in one go. For example,
we have called the cloneContents() method of the
Range object, which returns a DocumentFragment
object. A DocumentFragment interface is also part of DOM and
extends the Node interface. It is like a lightweight
document, similar to the Document interface, but with limited
features.
The DocumentFragment object that the
cloneContents() method returns contains a copy of each of the
nodes covered by our Range object. You can directly import
the DocumentFragment object into a new DOM document, just
like importing any other type of node. This will result in importing all
the nodes present in the DocumentFragment into the new
document.
For example, in Listing
8, after calling the cloneContents() method, we have
created a new DOM document, added a wrapper root element, and imported the
DocumentFragment into the newly created document. The newly
created document now contains copies of the e2,
e3, and e4 elements and looks like the XML file
of Listing
9.
Therefore, you can use the concept of DOM Range by first selecting the start and end positions of the range and then performing the operation of your choice on the range that you have selected.
The Load and Save module in DOM Level 3
DOM Level 3 includes a load and save module which provides a mechanism
for loading XML data into DOM Document objects and for
serializing DOM Document objects as XML data. Before the DOM
Level 3, there was no such mechanism in DOM. Therefore DOM implementations
used to build proprietary mechanisms for loading and saving.
The DOM Level 3 load and save module is currently under development for. It is not yet part of the standard Xerces download. So we will not demonstrate how to use the load and save module in Xerces. Instead we'll just describe the important interfaces in the load and save module of DOM Level 3.
The primary interface in the DOM load and save module is
DOMImplementationLS, which is meant to extend the features of
the DOMImplementation interface that we saw earlier. You can
check whether a particular DOM implementation supports load and save by
calling the hasFeature("LS","3.0") of any
DOMImplementation instance. In case the
DOMImplementation.hasFeature() method returns true, you can
cast the DOMImplementation object as a
DOMImplementationLS instance.
The DOMImplementation interface contains a method named
createLSInput(), which creates and returns an instance of the
LSInput interface. The LSInput object is capable
of encapsulating XML data in different forms, such as a textual string, a
character stream, or a byte stream. After creating the
LSInput interface, you can set XML data in one of the data
fields of the LSInput interface.
The load and save module also contains an LSParser interface,
which you can instantiate using the createLSParser() method
of the DOMImplementationLS interface. You can call the
parse() method of the LSParser object and pass
on the LSInput object to the parse() method. The
parse() method will return the DOM document representation of
the XML data that you set in the LSInput object, thus
completing the process of loading XML data into a DOM
Document object.
When you want to serialize a DOM document as XML data, you will use the
LSSerializer interface, which you can instantiate using the
createLSSerizlizer() method of the DOMImplementationLS
instance. You can then call the writeToString() method of the
LSSerizlizer object, which takes a DOM Node
(e.g. a Document node) and returns the string representation
of the input DOM Node.
When should I use DOM?
We have discussed many DOM features in this series of articles. We have demonstrated that DOM is a powerful API for low level XML authoring and processing. As web services have gained popularity, many higher level XML processing engines have emerged. These higher level engines normally target specific XML-based languages and schemas. For example, the Microsoft .NET framework contains easy to use features that enable WSDL and SOAP processing in web service applications. Similarly, the JWSDP also contains APIs for XML-based Remote Procedure Calls (RPC). Therefore, it is expected that many developers will prefer using higher-level schema-specific APIs rather than using DOM for low level XML processing.
The following are the two common scenarios where you will likely use DOM in your XML applications:
- New XML-based protocols are currently under development. Protocols for XML-based transactions are an example. As new protocols emerge, it will take a bit of time for corresponding higher level APIs and processing engines to appear and mature. DOM will help you in protocol-specific XML authoring and processing during this transient phase of XML-protocol development.
- In addition to protocol specific processing, you will also need DOM for the processing of application-specific XML data in SOAP applications. We have already discussed application-specific namespaces in SOAP messages at the start of the "Xerces for SOAP authoring" section. Many industry specific XML schemas for different requirements (such as invoices, work orders, purchase orders, shipping documentation, payment information, product catalogs, etc.) are expected to emerge and be layered over the SOAP framework. Therefore, it is likely that you will be using high-level protocol-specific engines for the processing of standard markup and low level XML APIs like DOM for the processing of industry-specific XML namespaces.
Summary
In this series of articles, I have explained the DOM architecture and demonstrated the use of DOM features in web service applications. I considered MSXML and Xerces, the two popular DOM implementations. I showed DOM working inside a JavaScript page on the client side and inside an ASP.NET page on the server side. And I discussed how to use the DOM features of Xerces in Java-XML applications.
Resources
|