Eric compares XML processing in both Java and C++ using the Apache Xerces XML parser.
July 01, 2004
URL:http://www.drdobbs.com/jvm/c-java-xml-processing/184401817
When it comes to data processing and string manipulation, C++ is a top performer. Therefore, it makes sense that XML processing and C++ belong together. Although most XML tools target Java programmers, there are tools for the C++ developer. In this article, I compare XML processing in both Java and C++ using the Apache Xerces XML parser (http://xml .apache.org/) for both Java and C++. Xerces supports XML parsing via the Simple API for XML (SAX) as well as the Document Object Model (DOM).
SAX provides an event-based paradigm for traversing XML. With SAX, the given XML is traversed in its entirety, from top to bottom, sequentially. As the XML is processed, methods are called on your class providing information for each node, its attributes, and data, in the order that they appear in the XML. With SAX, you must traverse an entire document, even if you are only interested in a portion of it.
DOM parsing provides an alternative to SAX, letting your code locate a specific node in an XML document while ignoring the rest. The DOM also supports creating XML documents within your code, which you can serialize as a file on disk.
Although these two features make DOM parsing seem much more appealing than SAX processing, they come with a price. DOM parsing typically uses more memory and time to process XML, as it must create an object hierarchy in memory for the entire XML document before your application can use it. If the XML you are parsing is particularly large, this may not be an option.
In most cases, however, DOM parsing is the most efficient and useful way of working with XML in an application. This is especially true if your application needs to create XML documents from its own generated data. For these reasons, I'll focus here on using the DOM parser in both C++ and Java.
For the purpose of illustration, I examine a scenario such as an off-site conference, where the organization hosting the conference would like to process attendees as they enter. Assume that for cost and convenience purposes, the organization plans to equip its greeters with wireless, hand-held PocketPC devices as opposed to notebook computers.
The mobile device must gather credit-card payment information for each attendee, and send that data to a server for processing. The server, written in Java, sends a response to the mobile device indicating whether the payment has gone through.
Your job is to choose the best architecture and tools to build the client application for the mobile device. One option is to write the client application in Java and use Remote Method Invocation (RMI) to communicate with the server. The second option is to use Visual Basic to build the application, as it makes building GUIs a breeze.
However, both scenarios introduce some problems. First, using Java to develop GUIs isn't ideal and requires a Java virtual machine (JVM) to run. The JVM might prove to be too resource intensive to run even a simple application on a mobile device. Second, using Visual Basic can also be resource intensive, and leaves little or no option to communicate with the Java server.
The third option is to use C++ to build the client application, which should yield excellent results in terms of size and performance. Using XML to communicate with the server should provide an ideal level of abstraction.
If this scenario sounds familiar, it may be because Microsoft recently held a developer conference where they used hand-held, wireless PocketPC devices to process registrants as they arrived at the conference. Although I don't know any of the details regarding the applications they ran on these devices, I assume the issues Microsoft had to tackle were similar to those presented here.
The solution presented here is based on the third option. The client application will be written in C++ to offer the best performance. The server is written in Java, and communicates with a payment-processing system over the Internet. Client-server communication is done through XML files written to a network drive accessible to both the client and server applications. Figure 1 illustrates the proposed solution.
The client application has a user interface (Figure 2) that lets users enter a customer's name, address, and credit-card information. The application is written as a Visual Studio 6.0 C++ application that can run on a Windows desktop computer. The main thread of the client application takes data from the user, creates an XML document from it, and serializes the document to a file. A child thread running in the background continuously checks a folder for an XML file containing the server response.
The server application is written in Java, has no user interface, and is intended to run on a server. A child thread is created that continuously checks a folder for payment-request XML files. When a file is found, it is processed using the Xerces Java DOM parser, and all of the payment-request data is extracted.
When a (simulated) credit-card response is received, an XML document is created that contains the customer's name and the payment approval or denial. Finally, the XML is serialized to a file.
When an XML document is parsed using a DOM parser, the output is an object hierarchy, or tree, in the form of a Document
object. This object serves as the root of the tree, which contains Node
objects that can be traversed recursively to inspect the contents of the XML.
Every object in the DOM tree implements the Node
interface and can be treated as a Node
object, including the root Document
object. Each Node
itself contains a list of child Node
objects, each representing an XML tag, attribute, or value, and so on. This allows you to write one method to recursively traverse the tree and process the contents of the entire XML document. Figure 3 is a UML class diagram for the Node
interface.
Every Node
in a DOM tree has a type, which can be queried; see Table 1. The first node in the tree is always a node of type DOCUMENT_NODE
, but the most common type encountered is ELELMENT_NODE
. This node type, and only some of the other common types, is discussed here.
Node Type | Description |
ATTRIBUTE_NODE | Represents an attribute of an XML element; for instance, <Score type="percent">100</Score > .
|
CDATA_SECTION_NODE | Represents data that is not to be interpreted as markup. |
COMMENT_NODE | Represents a comment within the XML; for instance, <!-- This is a comment --> . |
DOCUMENT_FRAGMENT_NODE | Represents a lightweight Document object. |
DOCUMENT_NODE | Represents the XML Document root. |
DOCUMENT_TYPE_NODE | Represents the XML document type attribute. |
ELEMENT_NODE | Represents an XML element tag; for instance, <LastName>Bruno</LastName> . |
ENTITY_NODE | Represents a basic piece of XML data. |
ENTITY_REFERENCE_NODE | Represents an XML entity reference, which is a substitution for another character; for instance, <command>java -cp <classpath&rt;</commmand> . |
NOTATION_NODE | Represents a DTD construct used to format an unparsed XML entity. |
PROCESSING_INSTRUCTION_NODE | Represents an instruction for parsing the XML; for instance, <?xml-styleshhet href=...> . |
TEXT_NODE | Represents the text content within an XML element or attribute; for instance, <LastName>Bruno</LastName> . |
The first element node in the tree is the root node of the parsed XML text. The name of an element node is the text located within the "<" and ">" characters in the XML. For the XML in Example 1, the root element node name is Customer
.
<Customer> <LastName>Bruno</LastName> <FirstName>Eric</FirstName> <CreditCard type="AMEX"> <Number>1234567890</Number> <Expiration>1/2005</Expiration> </CreditCard> </Customer>
Traversing the list of child nodes of an element yields the element's textual content, attributes, and other elementswith their children, and so onif they exist. The Customer
element node in Example 1 has three child nodes, LastName
, FirstName
, and CreditCard
, all of which are element nodes themselves.
The Customer
node does not contain any textual content, but the LastName
element node does. Textual contentin this case the text "Bruno"is identified as having the node type TEXT_NODE
. Nodes that represent XML attributes, such as the type attribute of the CreditCard
node, have the node type ATTRIBUTE_NODE
.
Element, text, and attribute nodes are typically the most useful nodes in the DOM tree to deal with. Refer to the DOM documentation that came with your DOM parser for a complete description of these node types and the others not discussed here.
Besides parsing existing XML files, the DOM lets you create new XML documents and serialize them to disk. This is useful when an application needs to generate an XML response to a request, or even the request itself. For instance, in my example, the PocketPC client application generates an XML request using the information entered in the edit boxes within the application GUI. The Java application parses the request XML and then generates the response XML based on the credit-card approval/denial. Before getting into the code details, first examine the general algorithm for the XML document creation with the DOM.
Building a DOM object hierarchy starts with a document factory object. In Java this is the DocumentBuilder
object, and the DOMImplementation
object in C++. Both objects let you either parse an existing document or create a new one from scratch. The result is a new Document
object, which is really a Node
object of type DOCUMENT_NODE
.
The Document
object is used to create Element
node objects, which can be added as children of other Element
nodes via the call appendChild
. You have a complete DOM hierarchy when your code has created all of the element, text, and attribute nodes it needs, and has added each as children of the proper parents. The DOM can then be serialized to disk using a DOM serializer object.
The C++ DOM API provides an implementation-independent starting point via the DOMImplementationRegistry
static class. A call to the getDOMImplementation
method on this class returns a DOMImplementation
object. Calling createDocument
on this object creates and returns a DOMDocument
object, which is subsequently used to build Node
objects for your DOM hierarchy. The XML namespace, DTD, and root node name are passed as parameters to this method.
Example 2 illustrates the method calls just described and begins the creation of a document representing the XML in Example 1. The result is a mostly empty DOM document; all that exists is a root node named Customer
, identified as the object rootElem
.
// Create a new XML DOM Document DOMImplementation* dom = DOMImplementationRegistry::getDOMImplementation("Core"); DOMDocument doc = dom->createDocument( NULL, // namespace URI. "Customer", // root element name NULL ); // DTD // Get the root element Node object DOMElement* rootElem = doc->getDocumentElement();
Adding a node to the document requires a series of method calls; see Example 3. First, a DOMElement
object is created via the method DOMDocument::createElement
, with the node name LastName
. Next, a DOMText
node is created by calling DOMDocument::createTextNode
with the text "Bruno". The text node is made a child of the new element object, and the element object is made a child of the document's root element.
// Create the LastName element node DOMElement* elem = doc->createElement( "LastName" ); // Create the textual content for the element DOMText* text = doc->createTextNode( "Bruno" ); // Make the text node a child of the element node elem->appendChild( text ); // Make the new element a child of the root node "Customer" rootElem->appendChild( elem ); // Get a writer object to serialize to disk DOMWriter* serializer = dom->createDOMWriter(); // Set the file name XMLFormatTarget* target = new LocalFileFormatTarget( "c:\\mydocument.xml" ); // Write the XML to the file serializer->writeNode(target, *doc);
Serialization of the XML is started by obtaining a DOMWriter
object by calling the DOMImplemention::createDOMWriter
method. Finally, the XML is written to a file by calling DOMWrite::writeNode
, passing the filename (as an XMLFileFormat
object) and the DOMDocument
object as parameters.
For the C++ client application, all of the C++ code for creating and serializing the requested XML is contained within the CClientXMLGen
class; see Listing One. The customer data is contained within an object of class Client
, but the code can easily be modified to use a class containing your own application data.
CClientXMLGen::CClientXMLGen() { try { XMLPlatformUtils::Initialize(); m_doc = NULL; } catch(const XMLException& e) { char *pMsg = XMLString::transcode(e.getMessage()); cerr << "Error during Xerces-c Initialization.\n" << " Exception message:" << pMsg; XMLString::release(&pMsg); } } CClientXMLGen::~CClientXMLGen() { delete m_doc; XMLPlatformUtils::Terminate(); } bool CClientXMLGen::CreateXMLDocument(Client* pClient) { try { // Create a new XML DOM Document DOMImplementation* dom = DOMImplementationRegistry ::getDOMImplementation(X("Core")); m_doc = dom->createDocument( NULL, // namespace X("Client"), // root element name NULL); // DTD. // Create root element and add the client // details as child nodes with text nodes DOMElement* rootElem = m_doc->getDocumentElement(); AddElem( rootElem, X("LastName"), X(pClient->GetLastName()) ); AddElem( rootElem, X("FirstName"), X(pClient->GetFirstName()) ); AddElem( rootElem, X("Street"), X(pClient->GetStreet()) ); AddElem( rootElem, X("City"), X(pClient->GetCity()) ); AddElem( rootElem, X("Zip"), X(pClient->GetZip()) ); AddElem( rootElem, X("State"), X(pClient->GetState()) ); // Create another element for the credit card information DOMElement* CreditCardElem = m_doc->createElement( X("CreditCard") ); rootElem->appendChild( CreditCardElem ); AddElem( CreditCardElem, X("Number"), X(pClient->GetCreditCard()->GetCardNumber()) ); AddElem( CreditCardElem, X("Type"), X(pClient->GetCreditCard()->GetCardType()) ); AddElem( CreditCardElem, X("Holder"), X(pClient->GetCreditCard()->GetCardHolder()) ); AddElem( CreditCardElem, X("Expiration"), X(pClient->GetCreditCard()->GetExpDate()) ); return true; } catch (XMLException& e) { char *pMsg = XMLString::transcode(e.getMessage()); cerr << "Exception when creating the Client XML:\n" << " Exception message:" << pMsg; XMLString::release(&pMsg); } return false; } bool CClientXMLGen::SerializeXMLDocument( char* filename) { try { if ( m_doc != NULL ) { // Write the XML out as a file DOMImplementation* impl = DOMImplementationRegistry ::getDOMImplementation(X("LS")); DOMWriter* serializer = ((DOMImplementationLS*)impl)->createDOMWriter(); serializer->setEncoding(NULL); XMLFormatTarget* target = new LocalFileFormatTarget( filename ); // get the DOM representation serializer->writeNode(target, *m_doc); delete serializer; delete target; return true; } } catch (XMLException& e) { char *pMsg = XMLString::transcode(e.getMessage()); cerr << "Exception when serializing the XML:\n" << " Exception message:" << pMsg; XMLString::release(&pMsg); } return false; } void CClientXMLGen::AddElem(DOMElement* pRootElem, const XMLCh * elemName, const XMLCh * elemVal) { DOMElement* elem = m_doc->createElement( elemName ); DOMText* text = m_doc->createTextNode( elemVal ); elem->appendChild( text ); pRootElem->appendChild( elem ); }
Creating a DOM in Java starts with the DocumentBuilder
class. First, obtain a reference to this class by calling DOMBuilderFactory.newDocumentBuilder
. Next, create a Document
object by calling DocumentBuilder.newDocument
. The process of creating child nodes and assembling the object hierarchy in Java is similar to the process followed in the C++ code.
Serialization of the XML begins with a java.io.FileOutputStream
object, which specifies the filename. This object is passed into the constructor of a new org.apache.xml.serialize.XMLSerializer
object. A call to XMLSerializer.serialize
is all it takes to write the XML DOM to a file on disk. Listing Two shows the complete process of creating a DOM in Java.
DWORD WINAPI ResponseThreadProc(LPVOID lpParam) { HANDLE hTermEvt = (HANDLE)lpParam; WIN32_FIND_DATA findFileData; HANDLE hFind; try { XMLPlatformUtils::Initialize(); } catch(const XMLException& e) { return -1; } char szFileURL[128]; char szFilename[128]; char szBasePath[32]; strcpy(szBasePath, "checkin\\response"); while ( true ) { // Check for a new file every second switch ( WaitForSingleObject( hTermEvt, 1000) ) { case WAIT_OBJECT_0: // The thread was told to terminate XMLPlatformUtils::Terminate(); return 0; default: // Check for check-in response files sprintf(szFilename, "c:\\%s\\*.xml", szBasePath); hFind = FindFirstFile(szFilename, &findFileData); bool fMore = true; while ( fMore && hFind!=INVALID_HANDLE_VALUE ) { sprintf( szFileURL, "file:///checkin/response/%s", findFileData.cFileName); // A file exists, attempt to parse it static const XMLCh gLS[] = {chLatin_L,chLatin_S,chNull}; DOMImplementation* impl = DOMImplementationRegistry ::getDOMImplementation(gLS); DOMBuilder* parser = ((DOMImplementationLS*)impl)->createDOMBuilder( DOMImplementationLS::MODE_SYNCHRONOUS, 0); try { DOMCountErrorHandler errorHandler; parser->setErrorHandler(&errorHandler); // Parse the XML file and traverse the DOM DOMDocument* doc = parser->parseURI(X(szFileURL)); DOMNode* elem = (DOMNode*)doc->getDocumentElement(); if ( elem ) { char szLastName[32]; char szFirstName[32]; char szStatus[32]; GetNodeValue(elem, "Last", szLastName); GetNodeValue(elem, "First", szFirstName); GetNodeValue(elem, "Status", szStatus); char message[128]; sprintf(message, "Payment for %s %s %s", szFirstName, szLastName, szStatus ); ::MessageBox( NULL, message, "Payment Status", MB_OK); } } catch(DOMException& de) { cout << "Exception parsing document: " << de.msg; } catch (...) { } delete parser; char szFileDel[128]; sprintf(szFileDel, "c:\\%s\\%s", szBasePath, findFileData.cFileName ); DeleteFile( szFileDel ); fMore = FindNextFile(hFind, &findFileData); } FindClose(hFind); break; } } XMLPlatformUtils::Terminate(); return 0; }
Parsing an existing XML file in both Java and C++ is similar and straightforward. Example 4 shows the process for parsing a document in C++. First, obtain a DOMBuilder
object by calling createDOMBuilder
on the DOMImplementation
class. Next, call the DOMBuilder.parseURI
method to parse the document, passing in the filename and path in URI format. For instance, to parse the file MyDocument.xml, in the folder \myFiles, the URI is file:///myFiles/MyDocument.xml.
// Get a DOMImplementation object static const XMLCh gLS[] = {chLatin_L,chLatin_S,chNull}; DOMImplementation* impl = DOMImplementationRegistry ::getDOMImplementation(gLS); // Create the DOMBuilder object DOMBuilder* parser = ((DOMImplementationLS*)impl)->createDOMBuilder (DOMImplementationLS::MODE_SYNCHRONOUS, 0); // Parse the file specified in URI format DOMDocument* doc = parser->parseURI("file:///myDocs/myDocument.xml");
Example 5 is the Java code for parsing an XML file. After creating a DOMParser
object, the specified XML file is processed by calling the parse method. Calling DOMParser.getDocument
returns the Document
object.
// Create a DOMParser DOMParser parser = new DOMParser(); // Parse the specified file parser.parse("/myDocuments/myDocument.xml"); Document doc = parser.getDocument();
Traversing the DOM in both C++ and Java means iterating through the document elements recursively. Starting with the root element, each element's type is checked, and child elements are retrieved. The process continues up the tree until the elements needed are found and their values are retrieved. Example 6 shows C++ code to iterate through the DOM, searching for the XML element LastName
.
// Start with the root element DOMNode* elem = (DOMNode*)doc->getDocumentElement(); // Iterate through the child elements DOMNode* child = elem->getFirstChild(); while ( child != NULL ) { // Look for the "LastName" element char* szElem = child->getNodeName(); if ( _stricmp( szElem, "LastName" ) == 0 ) { // Found the element, now go through its // children and find the text value DOMNodeList* childNodes = child->getChildNodes(); for ( UINT y = 0; y < childNodes->getLength(); y++ ) { DOMNode* data = childNodes->item( y ); if ( data->getNodeType() == DOMNode::TEXT_NODE ) return (char*)data->getNodeValue(); } } child = child->getNextSibling(); }
The name of each child node of the root element is compared to the text "LastName". This is done by first calling DOMNode::GetFirstSibling
, then GetNextSibling
, in a loop. When the matching element has been found, its child nodes are retrieved and iterated through. In this case, the code is searching for the textual contents of the LastName
node; therefore, it is searching for an element of type TEXT_NODE
. Once found, the content is retrieved via a call to getNodeValue
on the text node. The Java code to accomplish this is nearly identical; see Example 7.
// Start with the root element Element elem = doc.getDocumentElement(); // Iterate through the child elements Node child = elem.getFirstChild() while ( child != null ) { // Look for the "LastName" element if ( child.getNodeName().equalsIgnoreCase( "LastName" ) ) { // Found the element, now go through its // children and find the text value NodeList childNodes = child.getChildNodes(); for (int y = 0; y < childNodes.getLength(); y++ ) { Node data = childNodes.item(y); if ( data.getNodeType() == Node.TEXT_NODE ) return data.getNodeValue(); } } child = child.getNextSibling(); } // ...
The complete source code for implementing this sample is available at http://www.cuj.com/code/. The full solution consists of a Visual C++ application that generates customer check-in requests in XML, and a Java application that processes those requests and generates XML responses.
The C++ application creates XML files and places them in the directory, c:\checkin. The Java application looks for files in this directory and places the response XML files in the directory c:\checkin\response. The C++ application parses the XML files as they appear in this directory and displays the payment status in a dialog box. This processing is done in a child thread (see the thread procedure code in Listing Two).
The C++ client application must be launched from within the c:\checkin directory; the Java application has no such restriction. Batch files have been included with the download to build and run the Java application.
XML serves as an excellent tool to abstract programming language and platform specifics in application development. Although there are ample tools and environments to help build XML processing code in Java, there are times when C++ is the ideal language. With the Apache XML parser for both Java and C++, you have the flexibility needed to choose the right language for the job.
Eric Bruno is a consultant in New York, and has worked extensively in Java and C++ developing real-time trading and financial applications. He can be contacted at eric ericbruno.com.
Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.