以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 DTD/XML Schema 』  (http://bbs.xml.org.cn/list.asp?boardid=23)
----  技巧:如何利用Xerces C++正确处理XML文档中的WhiteSpace  (http://bbs.xml.org.cn/dispbbs.asp?boardid=23&rootid=&id=12025)


--  作者:anchen0617
--  发布时间:11/13/2004 2:29:00 PM

--  技巧:如何利用Xerces C++正确处理XML文档中的WhiteSpace
1.背景介绍

Apache的Xerces C++ 和IBM的XML4C是广大C/C++编程人员非常喜欢使用的XML解析器,我比较偏好XML4C,最主要的原因是它能正确处理XML文档中的中文字符,具体可参见我以前在IBM developerWorkers China上发表的文章《如何利用Xerces-C++解析包含中文字符的XML文档》。

Xerces C++提供DOMParser和SAXParser解析XML文档,主要用途可有以下三种:

生成DOM_Document,并调用Xerces C++的API操纵内存中的XML Tree;
与XMLFormat等其它类结合,格式化XML文档,生成包含WhiteSpace的缩进式XML,方便使用者阅读;
去除XML文档中多余的whiteSpace,紧缩XML文档,应用于以XML为数据格式实现消息传送的应用,尽可能降低XML传输占用的网络带宽。
第2和第3种应用的实现比较简单,可参照Xerces C++提供的DOMPrint和SAXPrint的例子,并略做改进即可解决。而对于应用非常普遍的第一种情况,常有人会因为XML文档中的whitespace而出现程序处理问题,不能正确操纵DOM_Document表示的XML tree,从而影响了Xerces C++或XML4C上的应用开发。

2.问题描述

下面以Xerces C++的DOMParser为例,描述当解析含whitespace的XML文档时存在的问题,图1是部分程序代码,图2是要解析的XML文档。

图1中的代码在创建DOMParser之后,调用了DOMParser类的setIncludeIgnorableWhitespace()方法,目的是告诉解析器不要在DOM_Document中包含whitespace,因而,根据图2所示,图1代码运行结果应为:

List size = 1
但是,程序实际运行的结果为:

List size = 3
对DOM_NodeList遍历时发现,DOMParser解析test.xml时将文档中的whitespace认为了DOM_Text节点。DOMParser没有理会setIncludeIgnorableWhitespace()方法的调用,这样,DOM_Document中会存在多个代表whitespace的DOM_Text节点,这不仅占用了多余内存,而且,极大影响了程序员对DOM_Document的操作,甚至会使子节点是否等于某值的判断始终为false。因而,Xerces C++的程序员需要在DOM_Document中过滤掉所有的whitespace,如果将test.xml中多余的whitespace去掉(见图3所示)后,重新运行图1的代码,结果与预想的相同。


XMLPlatformUtils::Initialize();
... ...
DOMParser * Parser = new DOMParser;
Parser->setIncludeIgnorableWhitespace(false);
Parser->parse( "test.xml" );

DOMDocument  doc = Parser->getDocument();
DOM_Element root = doc.getDocumentElement();
DOM_NodeList List = root.getChildNodes();
Int len = list.getLength();
printf("\n list size = %d", len);
    


图 1 相关程序代码

<?xml version="1.0" encoding="UTF-8"?>
<personnel>
  <person id="Big.Boss" >
    <name><family>Boss</family> <given>Big</given></name>
    <email>chief@foo.com</email>
    <link subordinates="one.worker two.worker three.worker four.worker five.worker"/>
  </person>
</personnel>

图 2 test.xml

<?xml version="1.0" encoding="UTF-8"?>
<personnel><person id="Big.Boss" >   
<name><family>Boss</family><given>Big</given></name>
<email>chief@foo.com</email>
<link subordinates="one.worker two.worker three.worker four.worker five.worker"/>
</person>
</personnel>

图 3 去除whitespace后的test.xml

显然,利用额外的程序编程解决此问题是不明智的。Xerces C++应提供了相应的机制来解决它。我用google搜索了这方面的信息,发现还是有许多人遇到了此类问题,尽管IBM论坛上有人提出了解决思路,但是,还不够完整,我在研究Xerces C++的相关资料和代码解决此问题后认为,如何利用Xerces C++正确处理xml文档中的whitespace问题需要有较详细的解释和解决方法,权当是抛砖引玉吧,希望能为Xerces C++或XML4C的普及应用有所帮助。

3.原因分析

根据void DOMParser::setIncludeIgnorableWhitespace (const bool include ) 的文档说明,Parser是否包含whitespace的设置仅在Parser对XML文档进行有效性验证处理时有效。因而,图2中的test.xml只能是格式良好的XML文档,由于它没有相应的schema定义,所以,DOMParser无法对此文档进行有效性验证,缺省认为whitespace是DOM_Document的子节点,类型是DOM_Text。

那么,为test.xml提供schema之后,图1的运行结果是否正确呢?答案是不正确的,DOMParser还需要调用下列的API来设置其它选项。

方法名 方法说明
Void SetDoSchema(const bool newState ) 设置Parser是否处理xml文档中的schema,如果为true,则Parser还要处理xml中的schema,否则,parser不处理xml文档的schema。
Void setDoValidation ( const bool newState ) 此方法同setValidationScheme,不推荐使用。
Void setDoNamespaces ( const bool newState ) 设置Parser是否处理xml文档中的名域,如果为true,则Parser增强名域定义的约束和规则。
Void setValidationScheme (const ValSchemes newScheme )  设置Parser利用定义的schema对xml文档进行有效性验证解析。NewScheme的值有Val_Never,Val_Auto,Val_Always,分别表示对xml文档不进行shema有效验证、自动选择是否验证、总是进行有效验证。

因而,通过设置DOMParser的几个选项并提供test.xml的schema就应该能解决whitespace的问题。

4. 解决方法

基于上面的分析,我们首先需要为test.xml提供schema定义,这是解决此问题的第一步,也是必须的,如果不提供schema定义而想完成xml文档的紧缩处理,则需要程序员额外增加实现代码;或者使用SAXParser,在实现ContentHandler::ignorableWhitespace(const XMLCh* const chars, const unsigned int length)的纯虚方法中特殊处理,不为whitespace生成DOM_Text节点。但我这里不推荐此种处理方法,XML文档的有效性验证在许多应用系统中是必须的。

Test.xml的schema文件定义见Xerces C++包中的文件:<xerces C++安装路径>\data\personal.xsd,相应地,test.xml中需要标注shema,详细见:<xerces C++安装路径>\data\personal-schema.xml文件,这里在图5中给出shema的声明部分。

在上述步骤完成后,修改图1的程序代码,设置Parser支持XML文档的有效性验证,具体见图6。


<personnel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation='personal.xsd'>
... ...
</personnel>

图 5 schema的声明

XMLPlatformUtils::Initialize();
... ...
DOMParser * Parser = new DOMParser;
Parser->setIncludeIgnorableWhitespace(false);
parser->setDoNamespaces(true);
parser->setDoSchema(true);   
parser->setValidationScheme(DOMParser::Val_Auto);
 
parser->parse( "test.xml" );

DOMDocument  doc = Parser->getDocument();
DOM_Element root = doc.getDocumentElement();
DOM_NodeList List = root.getChildNodes();
Int len = list.getLength();
printf("\n list size = %d", len);

图 6 最终程序代码

需要说明以下几点:

XML文档和shema定义中都用到了名域,如图5中的xsi,schema定义中的xsd:element等等,所以,parser一定要设置名域的支持(调用setDoNamespaces(true)),否则,parser在解析xml的过程中会抛出异常。
setDoValidation()方法和setValidationScheme()方法在同一程序中不应同时出现。
注意这些方法调用之间的相互关系,例如,如果设置了对xml进行有效验证(调用setValidationScheme),而没有设置Parser对schema的处理支持(没有调用SetDoSchema),这时,DOMParser会在处理过程中抛出异常。
最好选用Val_Auto设置DOMParser的有效性验证,由Parser根据XML中是否标记schema来确定是否需要有效性验证,这种处理会使程序较为灵活。
5. 结束语

目前,许多企业已经或者正在采用Xerces C++开发XML的应用系统,相信在应用的过程中会遇到各种问题,欢迎有兴趣的朋友与我联系,共同交流。


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
6,058.594ms