This tutorial shows you how to create an XML DOM Document using Xerces for C++ using Visual Studio 9, and also how to output the results to a console, or save to a file. This tutorial produces the following output:
<?xml version="1.0" encoding="UTF-8" standalone="no" ?> <Hello_World> <!-- Dates are formatted mm/dd/yy. Don't forget XML is case-sensitive. --> <data date="01/22/09" float="3.14159265" integer="65536"> <row index="1">Comments and data can also go in text nodes.</row> <row description="The value of PI">3.1415926535897932384626433832795</row> <row website="http://www.ted.com.au/">TED - ideas worth sharing.</row> </data> </Hello_World>
This tutorial assumes you have already downloaded Xerces binaries and have correctly configured your Visual Studio environment. If you need help with this, read my previous tutorial on the subject.
For this tutorial you will need the following includes at a minimum:
// Mandatory for using any feature of Xerces. #include <xercesc/util/PlatformUtils.hpp> // Use the Document Object Model (DOM) API #include <xercesc/dom/DOM.hpp> // Required for outputing a Xerces DOMDocument to a standard output stream (Also see: XMLFormatTarget) #include <xercesc/framework/StdOutFormatTarget.hpp> // Required for outputing a Xerces DOMDocument to the file system (Also see: XMLFormatTarget) #include <xercesc/framework/LocalFileFormatTarget.hpp>
This tutorial also uses the following includes for creating sample data:
// (optional) The following includes are only used to create sample data in this tutorial. If you don't need the sample data, then you don't need these headers. #include <time.h> #include <string> #include <sstream>
The following line (without semi-colon) will expand to use the correct Xerces namespace. The alternative is to prefix all Xerces code & functions with the XERCES_CPP_NAMESPACE:: namespace.
// Define namespace symbols XERCES_CPP_NAMESPACE_USE
Initialize Xerces and get the DOM implementation used for creating DOM documents.
// Initilize Xerces. XMLPlatformUtils::Initialize();
Before we can work with the Document Object Model to create and manipulate documents, we need to get an instance of the DOM implementation code.
// Pointer to our DOMImplementation.
DOMImplementation * pDOMImplementation = NULL;
// Get the DOM Implementation (used for creating DOMDocuments).
// Also see: http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html
pDOMImplementation = DOMImplementationRegistry::getDOMImplementation(XMLString::transcode("core"));
Both Xerces and Xalan follow the official specifications for the XML and XSLT languages. These can be found at the w3c.
Question: What is transcode?
Answer:
Xerces (and Xalan) has been built with an international audience in mind; multiple languages, multiple platforms and multiple character encodings.
The short answer is, transcoding helps take care of all these issues. Since this can be a big subject, I will leave this up to the reader to research (since this is supposed to be a tutorial for beginners).
The following code block creates a DOM Document and fills in some sample data. The sample data starts life as integers, floats, dates or strings, but ultimately everything must be converted to a wide-charater string as required by Xerces.
Don't be intimidated by the size of the code, most of it is comments. Hopefully they are self-explanatory.
// Pointer to a DOMDocument. DOMDocument * pDOMDocument = NULL; /* Create an empty DOMDocument. When an empty document is created specify the namespace of the document (optional) and also the root node. Both of these values can be any valid text (ie. no spaces or weird characters). The "root node" in this example is "Hello_World". (XML is case-sensitive) This root node *owns everything* beneath it, just as if it were a separate object... in fact, it is. It's defined by the 'DOMNode' class. NOTE: The namespace in this example happens to be a website within the 'example.com' domain [RFC2606] However, it does NOT have to be a domain name. It could be your company name, or the name of your dog. It's just that domain names tend to be unique. Since any name will do, there does not even have to be a website at the address specified. */ pDOMDocument = pDOMImplementation->createDocument(L"schemas.example.com/2008/", L"ex:Hello_World", 0); // If you're not interested in namespaces (and most of the time I'm not), just use the following line instead of the one above... pDOMDocument = pDOMImplementation->createDocument(0, L"Hello_World", 0); /* ================================================================================================ Anything below this point is optional, although it would be a pretty boring and empty document!. ================================================================================================ Since we are going to add nodes to the document's root, we need a pointer to this root node/element (which was previously created when the document was created). Note: Due to their close relationship, the words "node" and "element" are often used interchangably. Nodes are the base class, and Elements are the specilizations. */ DOMElement * pRootElement = NULL; pRootElement = pDOMDocument->getDocumentElement(); // Create a Comment node, and then append this to the root element. DOMComment * pDOMComment = NULL; pDOMComment = pDOMDocument->createComment(L"Dates are formatted mm/dd/yy. Don't forget XML is case-sensitive."); pRootElement->appendChild(pDOMComment); // Create an Element node, then fill in some attributes, and then append this to the root element. DOMElement * pDataElement = NULL; pDataElement = pDOMDocument->createElement(L"data"); // Copy the current system date to a buffer, then set/create the attribute. wchar_t wcharBuffer[128]; _wstrdate_s(wcharBuffer, 9); pDataElement->setAttribute(L"date", wcharBuffer); // Convert an integer to a string, then set/create the attribute. _itow_s(65536, wcharBuffer, 128, 10); pDataElement->setAttribute(L"integer", wcharBuffer); // Convert a floating-point number to a wstring, then set/create the attribute. std::wstringstream myStream; myStream.precision(8); myStream.setf(std::ios_base::fixed, std::ios_base::floatfield); myStream << 3.1415926535897932384626433832795; const std::wstring ws(myStream.str()); pDataElement->setAttribute(L"float", ws.c_str()); // Append 'pDataElement' to 'pRootElement'. pRootElement->appendChild(pDataElement); // Create an Element node, then fill in some attributes, add some text, then append this to the 'pDataElement' element. DOMElement * pRow = NULL; pRow = pDOMDocument->createElement(L"row"); // Create some sample data. _itow_s(1, wcharBuffer, 128, 10); pRow->setAttribute(L"index", wcharBuffer); /* Create a text node and append this as well. Some people prefer to place their data in the text node which is perfectly valid, others prefer to use the attributes. A combination of the two is quite common. */ DOMText* pTextNode = NULL; pTextNode = pDOMDocument->createTextNode(L"Comments and data can also go in text nodes."); pRow->appendChild(pTextNode); pDataElement->appendChild(pRow); // Create another row (this time putting data and descriptions into different places). pRow = pDOMDocument->createElement(L"row"); pRow->setAttribute(L"description", L"The value of PI"); pTextNode = pDOMDocument->createTextNode(L"3.1415926535897932384626433832795"); pRow->appendChild(pTextNode); pDataElement->appendChild(pRow); // Create another row. pRow = pDOMDocument->createElement(L"row"); pRow->setAttribute(L"website", L"http://www.ted.com.au/"); pTextNode = pDOMDocument->createTextNode(L"TED - ideas worth sharing."); pRow->appendChild(pTextNode); pDataElement->appendChild(pRow);
If you were to output the DOM Document at this point, it will look like this:
<?xml version="1.0" encoding="UTF-8" standalone="no" ?> <Hello_World> <!-- Dates are formatted mm/dd/yy. Don't forget XML is case-sensitive. --> <data date="01/22/09" float="3.14159265" integer="65536"> <row index="1">Comments and data can also go in text nodes.</row> <row description="The value of PI">3.1415926535897932384626433832795</row> <row website="http://www.ted.com.au/">TED - ideas worth sharing.</row> </data> </Hello_World>
Wrap the following code up into a method call and use it to output a DOM Document to the standard output stream [console window].
DOMImplementation *pImplement = NULL; DOMWriter *pSerializer = NULL; XMLFormatTarget *pTarget = NULL; /* Return the first registered implementation that has the desired features. In this case, we are after a DOM implementation that has the LS feature... or Load/Save. */ pImplement = DOMImplementationRegistry::getDOMImplementation(L"LS"); /* From the DOMImplementation, create a DOMWriter. DOMWriters are used to serialize a DOM tree [back] into an XML document. */ pSerializer = ((DOMImplementationLS*)pImplement)->createDOMWriter(); /* This line is optional. It just sets a feature of the Serializer to make the output more human-readable by inserting line-feeds and tabs, without actually inserting any new nodes into the DOM tree. (There are many different features to set.) Comment it out and see the difference. */ pSerializer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true); /* Choose a location for the serialized output. The 3 options are: 1) StdOutFormatTarget (std output stream - good for debugging) 2) MemBufFormatTarget (to Memory) 3) LocalFileFormatTarget (save to file) (Note: You'll need a different header file for each one) */ pTarget = new StdOutFormatTarget(); // Write the serialized output to the target. pSerializer->writeNode(pTarget, *pmyDOMDocument);
Similar to the above section, use the following code to save a DOM Document to the file system.
void DoOutput2File(DOMDocument* pmyDOMDocument, const wchar_t * FullFilePath )
{
DOMImplementation *pImplement = NULL;
DOMWriter *pSerializer = NULL;
XMLFormatTarget *pTarget = NULL;
/*
Return the first registered implementation that has the desired features. In this case, we are after
a DOM implementation that has the LS feature... or Load/Save.
*/
pImplement = DOMImplementationRegistry::getDOMImplementation(L"LS");
/*
From the DOMImplementation, create a DOMWriter.
DOMWriters are used to serialize a DOM tree [back] into an XML document.
*/
pSerializer = ((DOMImplementationLS*)pImplement)->createDOMWriter();
/*
This line is optional. It just sets a feature of the Serializer to make the output
more human-readable by inserting line-feeds, without actually inserting any new elements/nodes
into the DOM tree. (There are many different features to set.) Comment it out and see the difference.
*/
pSerializer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true);
/*
Choose a location for the serialized output. The 3 options are:
1) StdOutFormatTarget (std output stream - good for debugging)
2) MemBufFormatTarget (to Memory)
3) LocalFileFormatTarget (save to file)
(Note: You'll need a different header file for each one)
Don't forget to escape file-paths with a backslash character, or
just specify a file to be saved in the exe directory.
eg. c:\\example\\subfolder\\pfile.xml
*/
pTarget = new LocalFileFormatTarget(FullFilePath);
// Write the serialized output to the target.
pSerializer->writeNode(pTarget, *pmyDOMDocument);
}
If you get the following error:
error LNK2001: unresolved external symbol "__declspec(dllimport) public: static wchar_t * __cdecl xercesc_2_7::XMLString::transcode(char const * const)" ( __imp_?transcode@XMLString@xercesc_2_7@@SAPA_WQBD@Z)
Modify the following C++ language compiler option:
/Zc:wchar_t (wchar_t Is Native Type)