级别: 中级
Neil Graham , XML 解析器开发经理, IBM
Khaled Noaman , 软件开发人员, IBM
2005 年 8 月 25 日
可以使用 Xerces-C++ 进行更有效地文档验证。XML 在 C 和 C++ 应用程序中发挥着越来越重要的作用。为了确保成功地解释文档内容,很多应用程序需要使用 W3C XML Schemas 来验证所处理的文档。本文将通过例子说明在验证过程之前如何对模式进行预处理和缓冲,从而避免重复处理给定 XML Schema 文档的高昂代价。还将介绍如何将处理后的模式保存到磁盘上,这样,只在原来的 XML Schema 文档改变后才需要重新处理它。
Xerces-C++ 是目前用 C++ 编写的功能最完善、可移植性最高的 XML 解析器之一。此外,它还是开源的(通过 Apache Xerces Project 分发),完全遵循最主要的 XML 标准:XML 1.0 第 3 版、XML 1.1 和 XML Schema 1.0 第 2 版“Structures and Datatypes”。由于在 Web 服务中的重要作用,对当前的 XML 应用程序而言,XML Schemas 变得尤其重要。本文中,我们将介绍如何根据 XML Schema 使用 Xerces-C++ 来验证文档。然后将探讨如何通过利用语法缓冲和语法序列化功能,从 Xerces-C++ 获得最佳的模式验证性能(请参阅 参考资料)。
使用 SAX2 的简单 XML Schema 验证
W3C XML Schema 规范定义了 XML 作者可用来描述 XML 文档结构的一套组件。它还提供了规定元素和属性文本内容的一种丰富的数据类型语言,从而让应用程序环境能够在不损失信息的前提下将数据转化成另一种类型。这正是 XML Schemas 在 Web 服务中起到关键作用,并且在其他 XML 处理方面越来越重要的原因。本文的第一项任务是说明如何按照 XML Schema 使用 Xerces-C++ 来验证文档。
本文使用 Xerces-C++ 的 SAX 2.0 API 版本来说明它的模式验证能力。Xerces-C++ 还支持 W3C DOM Level 2 Core 规范的绑定。将这里用于 SAX2 的代码修改成在 Xerces-C++ DOM 实现上下文中工作非常简单。虽然本文不准备详细描述 Xerces-C++ 或者它的 SAX2 API 的用法,但是将提到和 XML Schema 验证有关的 SAX2 的一些重要方面(请参阅 参考资料)。
根据对应的 XML Schema 文档验证 XML 文档,首先需要创建一个 Xerces-C++ SAX 2 解析器实例,并设置适当的特性和处理程序。然后告诉解析器实例开始解析 XML 文档。比如:
清单 1. 对 Xerces-C++ SAX 2 解析器启用模式验证
// Necessary includes. We refer to these as "common includes"
// in the following examples.
#include <xercesc/sax2/XMLReaderFactory.hpp>
#include <xercesc/sax2/SAX2XMLReader.hpp>
#include <xercesc/sax2/DefaultHandler.hpp>
// Handy definitions of constants.
#include <xercesc/util/XMLUni.hpp>
// Create a SAX2 parser object.
SAX2XMLReader* parser = XMLReaderFactory::createXMLReader();
// Set the appropriate features on the parser.
// Enable namespaces, schema validation, and the checking
// of all Schema constraints.
// We refer to these as "common features" in following examples.
parser->setFeature(XMLUni::fgSAX2CoreNameSpaces, true);
parser->setFeature(XMLUni::fgSAX2CoreValidation, true);
parser->setFeature(XMLUni::fgXercesDynamic, false);
parser->setFeature(XMLUni::fgXercesSchema, true);
parser->setFeature(XMLUni::fgXercesSchemaFullChecking, true);
// Set appropriate ContentHandler, ErrorHandler, and EntityResolver.
// These will be referred to as "common handlers" in subsequent examples.
// You will use a default handler provided by Xerces-C++ (no op action).
// Users should write their own handlers and install them.
DefaultHandler handler;
parser->setContentHandler(&handler);
// The object parser calls when it detects violations of the schema.
parser->setErrorHandler(&handler);
// The object parser calls to find the schema and
// resolve schema imports/includes.
parser->setEntityResolver(&handler);
// Parse the XML document.
// Document content sent to registered ContentHandler instance.
parser->parse(xmlFile);
// Delete the parser instance.
delete parser;
|
 |
使用受语法缓冲支持的 SAX2 进行简单的 XML Schema 验证
XML Schema 验证是一个比较复杂的过程。解析器要对文档的各个方面做大量检查,但是只有对组成 XML Schema 的文档进行广泛处理后,解析器才能进行这些检查。这样做是为将这些文档转化成一种称为 语法 的内部形式,可将其用于执行验证。
此外,XML Schema 规范要求解析器必须确保组成 XML Schema 的文档是有效的 XML Schema 文档。如果应用程序将 fgXercesSchemaFullChecking 特性设为 false(默认值),则可以避免后一种检查。但如果这样做,解析器就不会对模式执行非常复杂的检查,比如保证一旦在文档中遇到对该模式有效的元素,就会使用惟一的类型定义来验证此元素。虽然这听起来似乎有点深奥,但是依赖于 XML Schema 的 Web 服务以及其他标准和技术的大部分逻辑都假定:有效的模式都具有这样或那样的属性,这些属性验证了模式完整性检查(schema-full-checking)特性是可禁用的。
启用该特性一般来说比较好。所幸的是,Xerces-C++ 提供了一种简便方法,可以避免反复重建与通用 XML Schema 对应的语法,从而在后续的解析中不需要再次进行构造和验证。这就是所谓的语法缓冲,因为只需要构造语法一次,然后将其放入缓冲区中,在需要的时候,解析器可以找到并检索这些语法,不需要进行额外的处理。
清单 2 和 清单 1 惟一的区别是设置了 CacheGrammarFromParse 特性。如果设置了该特性,解析器在文档中遇到 schemaLocation 属性时,就会查询内部语法缓冲区(XMLGrammarPool),看看是否存在与 schemaLocation 目标名称空间对应的语法。如果存在,则使用该语法,否则解析 schemaLocation 关联的模式文档,并将得到的语法添加到 XMLGrammarPool 中。如果只引用有限的目标名称空间并且有准确的 schemaLocation 线索,这是一种很好的模型。
清单 2. 简单的语法缓冲
// Include "common includes".
// Create a SAX2 parser object.
SAX2XMLReader* parser = XMLReaderFactory::createXMLReader();
// Set "common features".
// Enable grammar caching feature.
parser->setFeature(XMLUni::fgXercesCacheGrammarFromParse);
// Set "common handlers".
// Parse the XML document.
// As Xerces-C++ processes the XML document, it will also process its
// associated XML schema and cache it for later reference.
parser->parse(xmlFile);
// Xerces-C++ won't re-process the XML schema, instead it'll use
// the processed and cached schema.
parser->parse(xmlFile);
// Delete the parser instance.
delete parser;
|
但是如果希望更主动地参与 XML 解析,该如何办呢?假设需要规定解析器从哪里查找具有特定目标名称空间的模式文档。当然,一种办法是在解析器上注册一个 SAX EntityResolver (请参阅 参考资料)。另一种是使用 Xerces-C++ 的 loadGrammar 方法明确创建与特定目标名称空间对应的语法(清单 3)。
清单 3. 使用模式缓冲并指定模式位置的 XML Schema 验证
// Include "common includes".
// Home of Xerces-C++ grammar constants.
#include <xercesc/validators/common/Grammar.hpp>
// Create a SAX2 parser object.
SAX2XMLReader* parser = XMLReaderFactory::createXMLReader();
// Set "common features".
// Note that this time you don't want to cache schemas from parse
// Set "common handlers".
// Preprocess the XML Schema and cache it.
// xsdFile could be a file path or
// of an object type xercesc/sax/InputSource
parser->loadGrammar(xsdFile, Grammar::SchemaGrammarType, true);
// Instruct the parser to use the cached schema
// when processing XML documents.
parser->setFeature(XMLUni::fgXercesUseCachedGrammarInParse);
// Parse the XML document.
// Xerces-C++ will use the preprocessed schema when it validates
// the document's contents, if the target namespaces match.
parser->parse(xmlFile);
// Delete the parser instance.
delete parser;
|
要注意的是,fgXercesUseCachedGrammarInParse 导致 Xerces-C++ 在请求注册的 EntityResolver 或者尝试对 schemaLocation 线索解除引用之前,首先要向它的 XMLGrammarPool 实例请求语法。这一点与 fgXercesCacheGrammarFromParse 不同,它把在解析文档中遇到的新语法增加到 XMLGrammarPool 中。
XML Schema 验证:将语法序列化到磁盘
如果应用程序不能长期重用解析器实例及其相关的 XMLGrammarPool 怎么办呢?如果不经常解析 XML 文档,或者应用程序中的线程数量变化很大(Xerces-C++ 解析器不支持再次进入),就会出现这种情况。这种情况下,即便语法重用多次,第一次从一组模式文档构造语法花费的时间可能非常重要。
不过在这方面 Xerces-C++ 也能提供帮助:它提供了将 XMLGrammarPool 中的全部内容以原始形式序列化到磁盘的方法。这大大加快了验证语法对象的创建。它还可以让应用程序将要用到的所有 XML Schema 集中到一个地方,因此,了解哪些模式很重要并且可信任的应用程序逻辑可以完全从关于实例文档处理的应用程序逻辑中分离出来。
清单 4 示范了如何构造一个 XMLGrammarPool,然后将其内容序列化为二进制文件。本文中的最后一个例子将说明如何使用该文件的内容验证文档。
清单 4. 将模式语法序列化到磁盘
// Include "common includes".
// Various interfaces you'll need:
#include <xercesc/validators/common/Grammar.hpp>
#include <xercesc/framework/MemoryManager.hpp>
#include <xercesc/framework/XMLGrammarPool.hpp>
#include <xercesc/framework/BinOutputStream.hpp>
// Xerces-C++'s default MemoryManager/XMLGrammarPool implementations.
#include <xercesc/internal/MemoryManagerImpl.hpp>
#include <xercesc/internal/XMLGrammarPoolImpl.hpp>
// Binary output stream for files.
#include <xercesc/internal/BinFileOutputStream.hpp>
// Create a memory manager instance for memory handling requests.
MemoryManager *memMgr = new MemoryManagerImpl();
// Create a grammar pool that stores the cached grammars.
XMLGrammarPool* pool = new XMLGrammarPoolImpl(memMgr);
// Create a SAX2 parser object.
SAX2XMLReader* parser = XMLReaderFactory::createXMLReader(memMgr, pool);
// Set "common features".
// Enable grammar caching feature.
parser->setFeature(XMLUni::fgXercesCacheGrammarFromParse);
// Set errorHandler and entityResolver (no need for ContentHandler).
// You will use a default handler provided by Xerces-C++ (no op action).
// Users should write their own handlers and install them.
DefaultHandler handler;
parser->setErrorHandler(&handler);
parser->setEntityResolver(&handler);
// xsdFile1 could be a file path or
// of an object type xercesc/sax/InputSource.
parser->loadGrammar(xsdFile1, Grammar::SchemaGrammarType, true);
// Include however many XSD files you might require.
// Create an output stream instance to serialize processed grammar
// to use a BinFileOutputStream instance to serialize data to disk.
BinOutputStream outStream = new BinFileOutputStream(outFile);
// Serialize the grammar pool.
pool->serializeGrammars(outStream);
// Clean up.
delete parser;
delete pool;
delete outStream;
delete memMgr;
|
清单 4 使用了多种内部 Xerces-C++ 类作为接口的实现。一般而言,可以通过提供自己的实现来定制其行为。
现在已经有了一个二进制文件,其中包括 XMLGrammarPool 实例的 Xerces-C++ 表示,缓冲池中包含应用程序用到的所有模式文档。假设需要按照其中一个 XML Schemas 来验证实例文档,那么将该文件重新加载到内存中就可以使用它了。
清单 5. 使用一个从磁盘反序列化的 XMLGrammarPool 的 XML Schema 验证
// Include "common includes".
// Various interfaces you'll need.
#include <xercesc/framework/MemoryManager.hpp>
#include <xercesc/framework/XMLGrammarPool.hpp>
#include <xercesc/util/BinInputStream.hpp>
// Xerces-C++'s default MemoryManager/XMLGrammarPool implementations.
#include <xercesc/internal/MemoryManagerImpl.hpp>
#include <xercesc/internal/XMLGrammarPoolImpl.hpp>
// Binary input stream for files.
#include <xercesc/util/BinFileInputStream.hpp>
// Create a memory manager instance for memory handling requests.
MemoryManager *memMgr = new MemoryManagerImpl();
// Create a grammar pool to receive the serialized grammars.
XMLGrammarPool* pool = new XMLGrammarPoolImpl(memMgr);
// Create an input stream instance
// to deserialize processed grammar from.
// Use a BinFileInputStream instance to deserialize data from disk.
BinInputStream inStream = new BinFileInputStream(inFile);
// Deserialize grammars from disk.
pool->deserializeGrammars(inStream);
// Create a SAX2 parser object.
SAX2XMLReader* parser = XMLReaderFactory::createXMLReader(memMgr, pool);
// Set "common features".
// Enable use of cached grammar feature.
parser->setFeature(XMLUni::fgXercesUseCachedGrammarInParse);
// Set "common handlers".
// Parse the instance document that will use the cached schemas
// for validation.
parser->parse(xmlFile);
// Delete instances.
delete parser;
delete pool;
delete inStream;
delete memMgr;
|
 |
结束语
本文说明了如何根据 XML Schema,使用 Xerces-C++ 验证实例文档。我们还示范了如何让解析器缓冲 XML Schema 的内部表示来改进验证过程的性能。然后使用 Xerces-C++ 将这些内部表示序列化到磁盘,在需要的时候,可以将它们从磁盘反序列化到内存,从而避免从原始模式文档初始化语法缓冲区的花费。
对于使用 XML Schema 的应用程序而言,性能一直是需要关注的问题。本文可以减轻使用 Xerces-C++ 解析器的 C 和 C++ 应用程序的这些担忧。 |