充分利用 Xerces-C++,第 1 部分
一篇针对 C++ 程序员的有关解析的 how-to 文章

级别: 初级

Rick Parrish (rfmobile@swbell.net), 顾问

2003 年 9 月 01 日

这篇分为两个部分的文章对 Xerces-C++ XML 库作了介绍。第1部分解释如何将这个库链接到在 Linux 和 Windows 中编写的应用程序。大量的代码展示了用 SAX API 进行解析的情况,还有一个示例应用程序显示了如何以 ASCII 艺术的形式创建一个条形图。在第 2 部分,我将展示如何装载、操作或者合成一个 DOM 文档,您还会看到如何用可伸缩矢量图(Scalable Vector Graphics,SVG)创建同样的条形图。C++ 程序员阅读这些文章之后应该可以容易地在他们的应用程序中添加 XML 解析和处理能力。
Xerces-C++ 是一个非常健壮的 XML 解析器,它提供了验证,以及 SAX 和 DOM API。XML 验证在文档类型定义(Document Type Definition,DTD)方面有很好的支持,并且在 2001年12月增加了支持 W3C XML Schema 的基本完整的开放标准。

Xerces-C++: 简史

Xerces-C++ 的前身是 IBM 的 XML4C 项目。XML4C 和 XML4J 是两个并列的项目,而 XML4J 是 Xerces-J——Java 实现——的前身。IBM 将这两个项目的源代码让与 Apache 软件基金会(Apache Software Foundation),他们将其分别改名为 Xerces-C++ 和 Xerces-J。这两个项目是 Apache XML 组的核心项目(如果看到的是“Xerces-C”而不是“Xerces-C++”,也是同一个东西,因为这个项目一开始就是用 C(译者注:原文为C++)语言编写的)。

IBM 仍然在 Xerces-C++ 的基础上继续 XML4C 项目。从我所研究的版本来看,XML4C 与 Xerces-C++ 相比突出的好处是,它的默认安装对大量国际字符编码提供了更好的支持。

验证

指定 XML 文档资料结构的两种基本方法是 DTD 和 W3C XML Schema,其中 DTD 的历史要长得多。XML Schema 基本上就是表示为 XML 的 DTD。Xerces-C++ 提供了很好的默认安装的验证能力以保证一个 XML 文档符合一个 DTD。

许可证

Xerces-C++ 的使用需要遵守 Apache Software License ,它正巧是最具有可读性的开放源代码许可证之一。它可以与 BSD 许可证作一个很好的对比。实质上,不付特许使用费就可以在您(或您公司)的软件中使用 Xerces-C++,只要向客户和用户说明软件中包括 Apache 代码,并加上适当的版权说明即可。关于许可证的具体内容请参见 Web 页面。

SAX: 事件 API 模型

正如您可能知道的,SAX 是一个用于解析 XML 文档的面向事件的编程 API。一个解析引擎消耗 XML 序列数据,并在发现进来的 XML 数据的结构时回调应用程序。这些回调称为事件句柄。SAX 实际上是两个 API:SAX 1.0 是最初的,而 SAX 2.0 是当前修订过的规范。它们两个很类似,但是也有区别,因此大多数基于 SAX 1.0 的应用程序在移植到新规范后会失败。

,SAX API 规范被作为一个单独的项目移植到了SourceForge。本文后面给出的 SAX 例子使用的是 SAX 2.0。

DOM: 文档对象模型

与 SAX 不同,DOM API 允许对 XML 文档进行编辑并保存为一个文件或者流。它还允许以编程方式从头开始构建一个新的 XML 文档。 其原因是 DOM 为文档提供了一个内存中的模型。您可以遍历文档树、删除节点或者嫁接新节点。

tech wrecks

DOM 是 W3C 技术推荐中的一员,被亲切地称之为 tech wrecks。DOM 有 3 级,第 1 级和第 2 级为完全技术推荐状态,而第 3 级为工作草案状态。

第 1 级 DOM 的核心定义了基本 XML 功能所需要的大部分内容:构建 XML 文档表示的能力。 DOMString 类型被显式指定包括宽 UTF-16 字符。第 1 级还定义了与 DOM 树不同部分以编程方式互动的接口。在第 1 级中有意去掉了 XML 的序列化。在第 1 级核心之外是第 1 级 DOM 的 HTML 定义。这部分内容试图用早期的 Dynamic HTML 对象模型解析第 1 级 DOM 的核心(不确切地称为第 0 级)。

第 2 级 DOM 增加了命名空间、事件和迭代器,以及视图和样式表的支持。一些应用程序需要第 2 级 DOM:例如,为一个命名空间指定一个 XML Schema 对于像 RDF 这样的应用程序是很重要的,在这样的应用程序中,XML 标记来自不同的架构,很有可能出现命名冲突。第 2 级对 DOMImplementation 接口增加了两个 createDocument 方法。有一个例子显示为什么它是重要的。当您认为不会再在 SAX 中发现回调和事件句柄时,它们又出现在 Event 接口中。与用于解析的 SAX 事件不同,DOM 事件可以反映出用户与文档的互动以及对使用文档的改变。反映文档结构改变的 DOM 事件称为 mutation events。 TreeWalkers 和 NodeIterators 增强了 DOM 树的遍历。程序可以通过 StyleSheet 接口检查样式信息。最后,视图支持使 XML 应用程序可以检查原始的和经过样式表处理过的这两种形式的文档。在此之前和之后的视图分别称为 document视图和 abstract视图。

第 3 级 DOM 核心对 DOMImplementation 接口增加了 getInterface 方法。在第 3 级文档中,可以指定文档的字符编码或者设置一些基本的 XML 声明,如 version 和 standalone 。第 2 级不允许将 DOM 节点从一个文档移动到另一个文档。第 3 级取消了这种限制。第 3 级增加了 user data——可以选择性地附加到任何节点上的额外的应用程序数据。第 3 级还有一些其他高级特性,但是 W3C 委员会仍然在完善第 3 级草案。

下载和安装

可以下载压缩后的 tar 形式的 Xerces-C++,也可以下载预编译的二进制文件。通过 Perl、Python、VBScript 或者 JavaScript 访问库的脚本用户可以下载适用于他们平台的二进制文件以进行安装。C++ 程序员很可能愿意从源压缩文件编译自己的二进制文件。在 Apache XML 组 Web 站点上有很好的编译指导,在本文稍后的地方我会讨论我所发现的几个微妙的问题—— pthreads 链接问题和修复 Windows 平台上潜在的 内存泄漏 问题。第 2 部分将包括在 SVG 例子中指定 DOCTYPE 的提示。如果希望在阅读时编译库,那么要先看看 Apache 站点上的 Xerces 编译文档(见 参考资料),然后回到这里了解如何将 Xerces 链接到自己的应用程序。

可以下载 tar 形式的文件并脱机操作(比如用笔记本电脑)。tar 文件中包括了全部 HTML 文档,所以不需要回到 Web 站点去看指示。

Win32 版本上的编译

在 Visual Studio dot-NET 或者 Win64 上安装软件的步骤与在 Win32 上的编译步骤一样。

解压缩 Xerces 源 tar 文件到一个工作目录。Xerces-C++ 有自己的目录结构,所以应保证在这一步中保持相对路径名。
用 Windows 资源管理器或者习惯使用的文件管理器进入到 //xerces-c-src_2_3_0//Projects//Win32//VC6//xerces-all// 文件夹并单击 xerces-all.dsw workspace 文件以启动 Microsoft Developer Studio。
注:这些指导假定您是在 Visual Studio 6 中编译 Win32 应用程序。对于 Visual Studio dot-NET 或者 Win64 应用程序,在 Win64 或者 VC7 各自的目录中重复步骤 1和2。
在 Developer Studio 中,让 XercesLib 成为当前活跃的项目,并按 F7 以编译 DLL。对于去年的硬件,这需要一到两分钟。
在您的项目中增加到 Xerces 头文件的路径(要链接到 Xerces-C++ 的应用程序需要在它们的工作空间中包括 XercesLib DSP 项目文件,或者在它们的项目文件中增加 LIB 文件以允许链接)。选择 Project>Settings 以调出项目设置对话框。从 Settings组合框中选择 All Configurations,单击 C++ 标签,选择 Preprocessor类别,并在 Additional include directories 文本框中添加 Xerces 包含路径(类似于 //xerces-c-sr2_2_0//src )。
如果在工作空间中添加了 XercesLib DSP,记得要将自己的项目标记为依赖于 XercesLib 项目,否则,就会得到链接错误。
编译一个 stub C++ 源文件,该文件不做任何事情,只包含一行内容用于读取 #include <xercesc/sax/HandlerBase.hpp> 。如果能够编译这个只有一行的 C++ 文件,那么您的包含路径就可能是正确的。之后保存工作空间。为了运行和调试这个应用程序,在工作目录中放入 Xerces DLL 的一个副本。
Linux 下的 编译

按照 doc/html 文件夹中的详细指导编译 Xerces-C++ 共享库。下面的命令展示了如何用压缩的源文件编译 Xerces-C++ 库。这里假定在像 /home/user 这样的目录中有 xerces-c-src_2_3_0.tar.gz 文件。不管选的是什么目录,它都应该与 XERCESCROOT 变量匹配,因为 configure 脚本有这个要求。

# cd /home/user
# gunzip xerces-c-src_2_3_0.tar.gz
# tar -xvf xerces-c-src_2_3_0.tar
# export XERCESCROOT=/home/user/xerces-c-src_2_3_0
# cd $(XERCESCROOT)/src/xercesc
# ./configure
# make all
 
对于本例后面的部分,我假设源树是在 /home/user/xerces-c-src_2_3_0 目录中。如果一切顺利,共享库应该出现在 lib 文件夹中。如果有问题,那么请参考 /doc/html 文件夹中的编译指导。这时,您可以将这个库(和 symlinks)拷贝到 /usr/lib ,或者定义相应的环境变量以使装载器可以找到新编译的库。

测试新库的方便方法是编译并运行一个例子:

# export XERCESCROOT=/home/user/xerces-c-src_2_3_0
# cd $(XERCESCROOT)/samples
# ./configure
# make all

我在一个全新安装的 Slackware Linux 9.0 上编译其中一个例子中遇到了一个小问题。链接器抱怨缺少与 pthread 相关的输出。我编辑了 Makefile.in 文件以包括对 -lpthread 的引用并再次运行 configure 。第二次时键入 make all 就可以了。

证明库可以工作后,就可以开始自己的 Xerces-C++ 项目了。使用 -I 编译器选项以帮助编译器找到 Xerces 头文件。用链接器选项 -L 和 -l 以帮助链接器找到 Xerces-C++ 库。清单 1 给出了一个可以使用的最简单的 makefile 以供开始。

清单 1. 最简单的 makefile
APP = example
XERCES = /home/user/xerces-c-src_2_3_0
INCS = ${XERCES}/src
${APP} :: ${APP}.cpp
${CC} -lxerces-c-src_2_3_0 -I${INCS} ${APP}.cpp -o ${APP}

使用清单 1 的命令是 make 或者 gmake 。可以将 APP 变量改变为任何您所使用的源文件。本文中的例子使用类似的 makefiles。

Xerces C++ 从版本 2.2.0 开始增加了对 C++ 命名空间的支持(不要与 XML 命名空间搞混)。如果有工作于 2.1.0 的代码,并且希望利用新版本的好处,那么在代码中包括 Xerces C++ 头文件的后面添加下面三行。

清单 2. Xerces C++ 命名空间支持
#ifdef XERCES_CPP_NAMESPACE_USE
XERCES_CPP_NAMESPACE_USE
#endif
 
当然,可以仅仅对所有 Xerces-C++ 对象加上 XERCES_CPP_NAMESPACE:: namespace 前缀。

为了在解释有关使用 Xerces-C++ 的基本内容时能够有趣一些,我将用XML作为数据格式创建一个简单条形图。为了避开跨平台项目中的平台 GUI 特定障碍,我用 ASCII 艺术制作这个条形图。不管怎么说,这是一篇有关 XML 而不是 GTK、OpenGL 或者 Direct-X 的文章。如果对于使用图形数据的 XML 表示有兴趣,可参见 SVG 和 SMIL 。我在第 2 部分描述的 DOM 例子输出 SVG。我将以简单的文本应用程序开始。

清单 3 是数据的 DTD。下面我将构建一个程序以装载这些数据,确定使用什么比例、然后实际在屏幕上描绘这个数据。

清单 3. 示例应用程序数据的 DTD
APP = example
<?xml version="1.0" ?>
<!ELEMENT figures (PCDATA) >
<!ATTLIST figures type (sales | inventory | labor) >
<!ATTLIST figures value CDATA >
<!ELEMENT department (figures*) >
<!ATTLIST department name CDATA>
<!ELEMENT corporate (department*) >
<!ATTLIST corporate name CDATA >

清单 4. 示例输入 XML 数据
APP = example
<?xml version="1.0" ?>
<corporate name="Big Biz">
<department name="North">
<figures type="sales" value="125000.00"/>
<figures type="inventory" value="90000.00"/>
<figures type="labor" value="110000.00">estimated</figures>
</department>
<department name="South">
<figures type="sales" value="980000.00"/>
<figures type="inventory" value="110000.00"/>
<figures type="labor" value="115000.00">estimated</figures>
</department>
<department name="East">
<figures type="sales" value="210000.00"/>
<figures type="inventory" value="80000.00"/>
<figures type="labor" value="95000.00">estimated</figures>
</department>
<department name="West">
<figures type="sales" value="160000.00"/>
<figures type="inventory" value="75000.00"/>
<figures type="labor" value="130000.00">estimated</figures>
</department>
<department name="Central">
<figures type="sales" value="723000.00"/>
<figures type="inventory" value="11000.00"/>
<figures type="labor" value="221000.00">estimated</figures>
</department>
</corporate>

SAX2 实现

清单 5是基准 SAX 实现。这不是一个完整的程序,因为它缺少句柄实现,但是它的确显示了使框架就序所需要的东西。对 XMLPlatformUtils:Initialize() 和 XMLPlatformUtils::Terminate() 的调用非常重要。在应用程序不能正确初始化库时,库会抛出一个异常来提供保护。

为了使清单 5 中的程序成为完整的应用程序,需要添加 清单 6中的事件句柄类。SAX2 带有名为 DefaultHandler 的默认事件句柄类,它在同名的 C++ 文件中定义。默认句柄什么也不做——它只是一个 stub 实现——但是它是完整的,所以我在这里用它作为图形事件句柄类的基类。

清单 7中的这个文件是 清单 6中的事件句柄类的实际实现。虽然程序其他部分只是让 SAX2 解析器运行的样板代码,但是清单 7 中的部分定义了应用程序的个性。

Xerces-C++ 使用 XMLCh 作为 typedef'd 字符表示。在一些平台上, XMLCh 与 C类型 wchar_t 兼容,后者通常是两个——但是有时是四个——字节宽。因为这种宽度不固定的可能性,所以文档不鼓励 wchar_t 与 XMLCh 互换的做法。在一些平台上这没问题,但是在另一些平台上就会出错。Xerces-C++ 使用更大的字符表示 UTF-16 而不是 UTF-8 或者 ISO-8859 交换文本。为了调试这个程序,我使用了 XMLString::transcode 函数来转换将在控制台上显示的宽字符字符串。 
 
我发现在 Microsoft Windows 中使用 Xerces 内部字符串类有一个问题。在 XMLString.hpp 中的注释要求调用 replicate 和其他类似的函数以释放返回的内存。问题出现在将应用程序与作为 DLL的 Xerces-C++ 库进行链接上。字符串在 DLL 的本地堆上分配。如果应用程序和 XercesLib DLL 都使用同一个 C 运行时(CRT)库 DLL,那么一切正常。但是,如果应用程序使用单线程的 CRT,而 XercesLib 使用多线程的 CRT,那么 DLL 问题就会出现。当应用程序试图释放字符串内存时,C 运行时注意到内存不是来自于应用程序的本地堆。在调试编译时它会抛出一个异常,但是在发布编译时它可能无警告地泄漏内存。在以前版本的 Xerces(如 1_5_1)的示例程序是通过不释放内存来避免这个问题。

我解决这个问题的方法是在 XMLString 类中增加两个静态放弃函数。因为字符串内存是由在 DLL 内部执行的代码释放的,所以使用的是正确的本地堆,没有调试断言结果。我高兴地看到 Xerces 开发者 Tinny Ng 将它添加到了 XMLString 类中,并更进一步设字符串指针为 null。这样做的另一个好处是程序员不需要担心 XMLString 是如何分配内存的。不需要猜测它们应该使用 delete[] 还是 free ,它们只要调用 XMLString::release 就行了。当然,也可以只是确保您的应用程序要使用的 CRT 与 XercesLib DLL 使用的 CRT 一样。

后面是什么?

在第 1 部分,您了解了如何将 Xerces-C++ XML 库链接到在 Linux 和 Windows 中编写的应用程序,我通过创建 ASCII 艺术的条形图展示了用 SAX API 进行解析。在第 2 部分,我将向您展示如何装载、操作或者合成一个 DOM 文档,并用可伸缩矢量图(Scalable Vector Graphics,SVG)创建同样的条形图。

参考资料

您可以参阅本文在 developerWorks 全球站点上的 英文原文,下载代码.

在本文作者的“ 充分利用 Xerces-C++,第 2 部分”中,学习装载、操作或者合成文档对象模型(Document Object Model,DOM)文档,以及如何用可伸缩矢量图(SVG)重新创建第 1 部分的条形图。

Logo

更多推荐