perfectxml.com
 Basic Search  Advanced Search   
Topics Resources Free Library Software XML News About Us
  You are here: home Info Bank Articles » MSXML Tips :: August 2002 Sunday, 9 March 2008
 

Back to Articles Page      

        

MSXML Tips :: August 2002

By: Darshan Singh (Managing Editor, perfectxml.com)
August 12, 2002

Abstract
MSXML (Microsoft XML Core Services) is a COM-based XML processing component from Microsoft. Every month on perfectxml.com we'll discuss few cool tips and techniques related to using MSXML.

This month, we'll first look at using MSXML COM DLL without registering it. This is useful if you wish to do XML processing on a clean machine using MSXML and don't want to modify the registry (by registering the MSXML DLL). Next, we'll see how to use XMLHTTP to call a Web service, and finally we'll show how to preserve the XML formatting while saving the XML document using MSXML DOMDocument save method.

MSXML on a Clean Machine
Recently one of my colleagues asked an interesting question: he said "I am familiar with MSXML and would like to use it for some simple XML processing. But only thing that's stopping me is the need to register the DLL as it is a COM interface. I am writing a C++ application that should run on a clean machine, without making registry changes; Just XCopy should make the application work!".

In short, the question is how to use MSXML for XML processing without registering the DLL. I confirmed that he is not dealing with threads. His MSXML code will run in a single thread and will not cross thread/process boundaries; and here is what I suggested.

We can do what the COM library does: Load the DLL using LoadLibrary, get the address of DllGetClassObject, Get the IClassFactory interface and finally create the instance of MSXML objects. Once this is done, we can use the objects just as any other C++ objects.

Let's look at the code. Create a console application and write the following code in the main CPP file:

#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#include <wtypes.h>
#include <objbase.h>
#include <atlbase.h>

#include "C:\\Program Files\\MSXML 4.0\\inc\\msxml2.h"
typedef int (__stdcall *PDLLGETCLSOBJ)(REFCLSID rclsid, REFIID riid, LPVOID* ppv);

LPCTSTR g_szDLLFullPath		= _T("c:\\winnt\\system32\\msxml4.dll");
LPCTSTR g_szXMLFileToLoad	= _T("c:\\1.xml");
LPCTSTR g_szXSLFileToLoad	= _T("c:\\1.xsl");

//	Remember to add MSXML2.LIB under Project | Settings | Link tab

int main(int argc, char* argv[])
{
	HRESULT hr;
	REFCLSID rclsid = CLSID_DOMDocument40;
	REFIID riid = IID_IClassFactory;
	VARIANT_BOOL bLoadResult = VARIANT_FALSE;
	CComVariant vXMLFile(g_szXMLFileToLoad);
	CComVariant vXSLFile(g_szXSLFileToLoad);
	CComVariant vTransformedText("");

	//	See if the DLL is already loaded in the address space
	HMODULE hDll = GetModuleHandle(g_szDLLFullPath);

	if (!hDll)
	{
		//	Load the DLL
		hDll = LoadLibrary(g_szDLLFullPath);
	}

	if (hDll)
	{
		CComPtr<IClassFactory> spFactory;
		CComPtr<IXMLDOMDocument> spXMLDoc;
		CComPtr<IXMLDOMDocument> spXSLDoc;

		//	Get address of DllGetClassObject exported method
		PDLLGETCLSOBJ pDllGetCLSObj = (PDLLGETCLSOBJ)GetProcAddress(hDll,"DllGetClassObject");

		//	Call DllGetClassObject and get the proper IClassFactory interface
		hr = pDllGetCLSObj(rclsid, riid, reinterpret_cast<void**>(&spFactory));

		//	Create an instance of XMLDOMDocument
		hr = spFactory->CreateInstance(NULL, IID_IXMLDOMDocument, (void**)&spXMLDoc);

		//	Load the XML file
		hr = spXMLDoc->load(vXMLFile, &bLoadResult);

		//	Create another DOMDocument to load the XSL file
		hr = spFactory->CreateInstance(NULL, IID_IXMLDOMDocument, (void**)&spXSLDoc);

		//	Load the XSL file
		hr = spXSLDoc->load(vXSLFile, &bLoadResult);

		//	Apply the transformation
		spXMLDoc->transformNode(spXSLDoc, &vTransformedText.bstrVal);

		//	Print the transformed text
		wprintf(vTransformedText.bstrVal);

	}
	else
	{
		printf(_T("Unable to load the DLL!"));
	}

	//	Make sure that CComPtr objects go out of scope (and get released) 
	//	before unloading the DLL.
	//	Unload the DLL
	FreeLibrary(hDll); hDll=NULL;
	
	return 0;
}

The above code manually loads the MSXML DLL (by calling LoadLibrary), then we get the address of DllGetClassObject method, get the IClassFactory interface, and call CreateInstance on it to create the MSXML DOMDocument object. Once we have the DOMDocument object, we can access its properties and call methods like while using any other C++ object.

The above code loads the XML document, and the XSL stylesheet file, applies the transformation, and prints the transformed text to the console.

Click here to download the above sample application code (MSXMLOnCleanMachine.zip, 106KB). For your convenience we have included the sample XML files, MSXML header and library files.

Using XMLHTTP in C++ to call a Web service
XMLHTTP, a very useful utility class inside MSXML allows sending HTTP requests and receiving and processing the response. You can send the GET/POST request for .xml, .asp, .html or any other page and receive XML or non-XML data back.

In the following sample code, we'll use XMLHTTP to POST a SOAP request package to the FedEx Tracker Web service provided by XMethods and print the output on the console.

Let's look at the code. Create a console application and write the following code in the main CPP file:

#include "stdafx.h"
#import "msxml4.dll"
#include <msxml2.h>

using namespace MSXML2;

inline void EVAL_HR( HRESULT _hr ) 
   { if FAILED(_hr) throw(_hr); }

LPCTSTR g_szSOAPRequest	= 
	"<?xml version='1.0' encoding='utf-8'?> "
	"<soap:Envelope "
	"  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "
	"  xmlns:xsd='http://www.w3.org/2001/XMLSchema' "
	"  xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'> "
	"	<soap:Body> "
	"		<getStatus "
	"          xmlns='urn:xmethodsFedEx'> "
	"			<trackingNumber>836252970794</trackingNumber> "
	"		</getStatus> "
	"	</soap:Body> "
	"</soap:Envelope> ";


int main(int argc, char* argv[])
{
	try
	{
		EVAL_HR(CoInitialize(NULL));

		IXMLHTTPRequestPtr pXH = NULL;
		EVAL_HR(pXH.CreateInstance("Msxml2.XMLHTTP.4.0"));
		EVAL_HR(pXH->open("POST", "http://services.xmethods.net:80/perl/soaplite.cgi", VARIANT_FALSE));
		EVAL_HR(pXH->setRequestHeader("Content-Type", "text/xml"));
		EVAL_HR(pXH->setRequestHeader("SOAPAction", "urn:xmethodsFedEx#getStatus"));
		EVAL_HR(pXH->send(g_szSOAPRequest));
		
		if (pXH->status == 200)
		{
			wprintf(pXH->responseText);
		}
		else
		{
			wprintf(pXH->statusText);
		}
	}
	catch(...)
	{
		//	Exception Handling
	}
	

	CoUninitialize();
	return 0;
}

The above code creates an instance of XMLHTTP object, initializes it by calling its open method, sets request headers by calling setRequestHeader and finally sends a POST request by calling the send method. The status property when 200 indicates that the request succeeded, we then print the responseText on the console.

Pretty-Printing When Saving XML Document
One of the commonly asked questions is "I lose the XML formatting (indentation, etc.) when I modify the XML document and call the save method; how do I save the indented document?". The same is true when creating the document from scratch and saving it.

It is a fact that the MSXML DOMDocument save method does not preserve the document formatting, and I am pretty sure the parser will never do that in future also. If you still wish to save the formatted document, there are many workarounds. One of them is to manually insert the newline and tab character nodes as illustrated below.

Create a Visual Basic 6.0 standard EXE application, add reference to MSXML 4.0 type library and write the following code in the Form_Load method:

Dim objXMLDoc As New MSXML2.DOMDocument40
Dim vRoot As IXMLDOMNode
Dim vNode As IXMLDOMNode

objXMLDoc.loadXML _
    "<?xml version='1.0' encoding='UTF-16' ?>" & _
    vbNewLine & "<Feedback />"

Set vRoot = objXMLDoc.documentElement

vRoot.appendChild objXMLDoc.createTextNode(vbNewLine)
vRoot.appendChild objXMLDoc.createTextNode(vbTab)

Set vNode = objXMLDoc.createElement("FeedText")

vNode.appendChild objXMLDoc.createTextNode(vbNewLine)
vNode.appendChild objXMLDoc.createTextNode(vbTab)
vNode.appendChild objXMLDoc.createTextNode(vbTab)

vNode.appendChild objXMLDoc.createTextNode("PerfectXML is now by far " & _
    "the best source on the Web for up-to-date news " & _
    "on Web services and XML. Congrats! ")
vNode.appendChild objXMLDoc.createTextNode(vbNewLine)
vNode.appendChild objXMLDoc.createTextNode(vbTab)

vRoot.appendChild vNode

vRoot.appendChild objXMLDoc.createTextNode(vbNewLine)

MsgBox objXMLDoc.xml

objXMLDoc.save "c:\1.xml"

The above code uses MSXML DOMDocument to create a XML document. Note how manually insert the vbNewLine and vbTab nodes to save the formatted text. Run the above code and open c:\1.xml using Notepad and you'll see that the XML document is properly indented and formatted, as shown below:

<?xml version="1.0" encoding="UTF-16"?>
<Feedback>
	<FeedText>
		PerfectXML is now by far the best source on the Web for 
		up-to-date news on Web services and XML. Congrats! 
	</FeedText>
</Feedback>

Wrap-up
MSXML, like any other COM component, needs the DLL to be registered before we can use the objects. However, sometimes the requirement is to not update the registry (by registering the MSXML DLL objects), but still use it for XML processing. This month's first MSXML tip showed us how to use MSXML COM library without registering the DLL.

The second tip illustrated using XMLHTTP in C++ to call a Web service method; and finally we saw how to preserve the indentation and save the formatted XML documents.

I hope you enjoyed this month's MSXML Tips and will check back again for more such tips next month!
  Contact Us | E-mail Us | Site Guide | About PerfectXML | Advertise ©2004 perfectxml.com. All rights reserved. | Privacy