使用 MSXML 读取 XML 文档
如何使用 MSXML,以现代 C++/模板 方式读取 XML 文档
引言
现在每个人都需要解析 XML。我发现很难找到好的 C++ 示例源代码——大多数代码似乎都是用一种没有模板的过时风格编写的,或者是针对 C# 或 Visual Basic 的。因此,本文提供了一个例子。
解析是使用 MSXML 完成的,我使用 ATL“智能指针”来避免手动释放所有内容。请注意,MSXML 始终是 Unicode。尝试将其与多字节/ASCII 一起使用会浪费大量精力。
随附的源代码包含用于嵌入式 Visual C++ (.vcw .vcp)、Visual C++ .NET (.sln .vcproj) 和 Borland C++Builder5 (.bpr .bpf) 的项目文件。但没有用于 Visual C++6 的项目文件,因为它没有附带足够新的 MSXML 标头。
PocketPC 注意事项:我使用 XML 来存储我的配置文件。它们每个都已增长到大约 80k,在 PocketPC 上,解析它们需要 2 秒。因此,我实际上将其解析为更有效的内存块结构,并将此内存块写入磁盘。这样,只有在发生任何更改时才需要重新解析。
准备工作
设置取决于您使用的开发环境
- Visual Studio .NET -- 一切都很好
- Borland C++ Builder -- 在 Project > Options > Directories 下,添加($BCB)\include\atl
- eMbedded Visual C++ (EVC) -- 下载 Giuseppe Govi 制作的免费 STL 端口,并将其放入项目的子目录 "stl_eVC" 中
#include <windows.h>
#include <msxml.h>
#include <objsafe.h>
#include <objbase.h>
#include <atlbase.h>
#pragma warning( push )
#pragma warning( disable: 4018 4786)
#include <string>
#pragma warning( pop )
using namespace std;
(警告禁用程序仅适用于 EVC,否则会生成虚假警告。)
此外,事先执行 CoInitializeEx(NULL,COINIT_MULTITHREADED);
(通常在 WinMain
的开头),并在之后执行 CoUninitialize();
(通常在 WinMain
的结尾)。
实际上,为桌面 win32 编译时,CoInitialize(NULL)
更容易,因为它可以在 Win'95 上运行,因此不需要您定义 _WIN32_WINNT
。但它在 PocketPC 上不可用。
XML 解析
这是加载 XML 文档的方法。它使用 ATL 安全指针的魔力,避免了之后需要 Release()
所有内容。(为简单起见,省略了错误检查。)
CComPtr<IXMLDOMDocument> iXMLDoc;
iXMLDoc.CoCreateInstance(__uuidof(DOMDocument));
#ifdef UNDER_CE
// Following is a bugfix for PocketPC.
iXMLDoc->put_async(VARIANT_FALSE);
CComQIPtr<IObjectSafety,&IID_IObjectSafety> isafe(iXMLDoc);
if (iSafety)
{ DWORD dwSupported, dwEnabled;
isafe->GetInterfaceSafetyOptions(IID_IXMLDOMDocument,
&dwSupported,&dwEnabled);
isafe->SetInterfaceSafetyOptions(IID_IXMLDOMDocument,
dwSupported,0);
}
#endif
// Load the file.
VARIANT_BOOL bSuccess=false;
// Can load it from a url/filename...
iXMLDoc->load(CComVariant(url),&bSuccess);
// or from a BSTR...
//iXMLDoc->loadXML(CComBSTR(s),&bSuccess);
// Get a pointer to the root
CComPtr<IXMLDOMElement> iRootElm;
iXMLDoc->get_documentElement(&iRootElm);
// Thanks to the magic of CComPtr, we never need call
// Release() -- that gets done automatically.
至于访问元素和遍历它们,我编写了一个小助手类 TElem
。这是我将用它演示的示例 XML 文档
<?xml version="1.0" encoding="utf-16"?>
<root desc="Simple Prog">
<text>Hello World</text>
<layouts>
<lay pos="15" bold="true"/>
<layoff pos="12"/>
<layin pos="17"/>
</layouts>
</root>
这是使用 TElem
的方法
TElem eroot(iRootElm);
wstring desc = eroot.attr(L"desc");
// returns "Simple Prog"
TElem etext = eroot.subnode(L"text");
wstring s = etext.val();
// returns "Hello World"
s = eroot.subval(L"text");
// This is a shorter way to achieve the same thing
TElem elays = eroot.subnode(L"layouts");
for (TElem e=elays.begin(); e!=elays.end(); e++)
{ int pos = e.attrInt(L"pos",-1);
bool bold = e.attrBool(L"bold",false);
// we suggest defaults, in case the attribute is missing
wstring id = e.name();
// returns "lay" or "layoff" or "layin"
}
同样,无需释放 TElem
- 这是自动完成的。TElem
中的完整方法列表
// TElem -- a simple class to wrap up IXMLDomElement
// and to iterate its children.
wstring TElem::name() const;
// in <item>stuff</item> it returns "item"
wstring TElem::val() const;
// in <item>stuff</item> it returns "stuff"
wstring TElem::attr(const wstring name) const;
// in <item name="hello">stuff</item> it returns "hello"
// int x=e.attrInt(L"a",2)
// bool b=e.attrBool(L"a",true),
// We supply defaults in case the attribute was absent.
TElem TElem::subnode(const wstring name) const;
// in <item><a>hello</a><name>there</name></item>
// it returns the TElem <name>there</name>
wstring TElem::subval(const wstring name) const;
// in <item><a>hello</a><name>there</name></item>
// it returns "there"
for (TElem c=e.begin(); c!=e.end(); c++) {...}
// iterates over the subnodes
TElem 的源代码
请注意,在此源代码中使用了 CComPtr
和 CComQIPtr
和 CComBSTR
。这些是 ATL 提供的可爱的“安全指针”,意味着我们不必担心 Release()
。
我有点吝啬,所以在 TElem
中包含了迭代器功能,而不是编写单独的 TElemIterator
类。
struct TElem
{ CComPtr<IXMLDOMElement> elem;
CComPtr<IXMLDOMNodeList> nlist; int pos; long clen;
TElem() :
elem(0), nlist(0), pos(-1), clen(0) {}
TElem(int _clen) :
elem(0),nlist(0),pos(-1),clen(_clen) {}
TElem(CComPtr<IXMLDOMElement> _elem) :
elem(_elem), nlist(0), pos(-1), clen(0) {get();}
TElem(CComPtr<IXMLDOMNodeList> _nlist) :
elem(0), nlist(_nlist), pos(0), clen(0) {get();}
void get()
{ if (pos!=-1)
{ elem=0;
CComPtr<IXMLDOMNode> inode;
nlist->get_item(pos,&inode);
if (inode==0) return;
DOMNodeType type; inode->get_nodeType(&type);
if (type!=NODE_ELEMENT) return;
CComQIPtr<IXMLDOMElement> e(inode);
elem=e;
}
clen=0; if (elem!=0)
{ CComPtr<IXMLDOMNodeList> iNodeList;
elem->get_childNodes(&iNodeList);
iNodeList->get_length(&clen);
}
}
//
wstring name() const
{ if (!elem) return L"";
CComBSTR bn; elem->get_tagName(&bn);
return wstring(bn);
}
wstring attr(const wstring name) const
{ if (!elem) return L"";
CComBSTR bname(name.c_str());
CComVariant val(VT_EMPTY);
elem->getAttribute(bname,&val);
if (val.vt==VT_BSTR) return val.bstrVal;
return L"";
}
bool attrBool(const wstring name,bool def) const
{ wstring a = attr(name);
if (a==L"true" || a==L"TRUE") return true;
else if (a==L"false" || a==L"FALSE") return false;
else return def;
}
int attrInt(const wstring name, int def) const
{ wstring a = attr(name);
int i, res=swscanf(a.c_str(),L"%i",&i);
if (res==1) return i; else return def;
}
wstring val() const
{ if (!elem) return L"";
CComVariant val(VT_EMPTY);
elem->get_nodeTypedValue(&val);
if (val.vt==VT_BSTR) return val.bstrVal;
return L"";
}
TElem subnode(const wstring name) const
{ if (!elem) return TElem();
for (TElem c=begin(); c!=end(); c++)
{ if (c.name()==name) return c;
}
return TElem();
}
wstring subval(const wstring name) const
{ if (!elem) return L"";
TElem c=subnode(name);
return c.val();
}
TElem begin() const
{ if (!elem) return TElem();
CComPtr<IXMLDOMNodeList> iNodeList;
elem->get_childNodes(&iNodeList);
return TElem(iNodeList);
}
TElem end() const
{ return TElem(clen);
}
TElem operator++(int)
{ if (pos!=-1) {pos++; get();}
return *this;
}
bool operator!=(const TElem &e) const
{ return pos!=e.clen;
}
};