这篇分为两个部分的文章介绍了 Xerces-C++ XML 库。在本第 2 部分中,Rick Parrish 展示了如何装载、操作或者合成一个文档对象模型(DOM)文档,以及如何用可伸缩矢量图形(SVG)重新创建第1部分中的条形图。C++ 程序员阅读这些文章 之后应该可以在他们的应用程序中容易地增加 XML 解析和处理能力
在 第 1 部分,您看到了如何将库链接到在 Linux 和 Windows 中编写的应用程序,以及如何用 SAX API 进行解析。一个示例应用程序展示了如何创建一个 ASCII 艺术条形图。
下面是对 DOM 节点组织的描述,然后是装载和解析以从文件或者流中产生一个 DOM 文档,合成以编程方式产生 DOM 文档,序列化或者将 DOM 文档作为输出写到文件或者流中。
如果您使用过版本 2.0 以前的 Xerces-C++,那么要当心!有些地方改变了。最显著的改变是将大多数 DOM 类对象的名字从 DOM_ 前缀重新命名为 DOM
前缀,以及选择传递指针而不是引用。
DOM 的基础数据类型是 DOMNode
类。所有 DOM 节点对象都是从这个基类扩展来的。表 1 显示了 DOM 节点类型。第一列是由方法 DOMNode::getNodeType()
返回的枚举的类型名。第二列是用于声明这一特定节点类型的实例所使用的 C++ 类。第三列显示用于实际生成这个实例所使用的构造方法。
表 1. DOM 节点类型
DOM 类型 | DOM 类 | 构造方法 |
DOMNode::TEXT_NODE | DOMText | createTextNode |
DOMNode::PROCESSING_INSTRUCTION_NODE | DOMProcessingInstruction | createProcessingInstruction |
DOMNode::DOCUMENT_NODE | DOMDocument | createDocument |
DOMNode::ELEMENT_NODE | DOMElement | createElement |
DOMNode::ATTRIBUTE | DOMAttr | createAttribute |
DOMNode::ENTITY_REFERENCE_NODE | DOMEntityReference | createEntityReference |
DOMNode::CDATA_SECTION_NODE | DOMCDATASection | createCDATASection |
DOMNode::COMMENT_NODE | DOMComment | createComment |
DOMNode::DOCUMENT_TYPE_NODE | DOMDocumentType | createDocumentType |
DOMNode::ENTITY_NODE | DOMEntity | createEntity |
DOMNode::NOTATION_NODE | DOMNotation | createNotation |
注意在 Xerces-C++ 版本 2 中,节点类型 XML_DECL_NODE
已被 get/setEncoding
、 get/setVersion
和 get/setStandalone
的 DOMDocument
方法所取代。
表 1 中的所有构造方法都存在于 DOMDocument
类中。创建 DOMDocument
对象的构造方法通过强迫您经历一个 DOMImplementation
实例以避免“鸡生蛋蛋生鸡”的问题。可以通过调用 DOMImplementation::getImplementation()
方法得到 DOMImplementation
实例,这个方法声明为静态的。要创建一个文档节点,执行以下两个步骤:
DOMImplementation *pImpl = DOMImplementation::getImplementation(); DOMDocumentType* pDoctype = pImpl->createDocumentType(L"svg", NULL, L"svg-20000303-stylable.dtd"); pSVG = pImpl->createDocument(L"svg", L"svg", pDoctype); |
清单 8 中的代码初始化解析器并将一个 XML 文档作为 DOM 树装载。它使用一个 DOMParser
对象完成所有这些工作。解析的工作完成后,对 getDocument()
的调用返回得到的 DOM 树。
解析器可以抛出三种异常——DOM、SAX 或者 XML——所以我在清单 8 中加入了 stubbed 异常句柄。另外一个检查错误的地方是 DOMParser::getErrorCount
函数。
清单 9 中的示例代码通过调用 DOM API 建立、或者 合成一个XML文档。所建立的这个文档是一个小的 XHTML 页面,但是事实上可以是任何 XML。为了证实事实上产生了 XML 文档,代码在控制台中显示了这个 XML 文档、标记和所有内容的内存映象(在后面我会描述在控制台显示文档所需要的额外代码)。
在阅读清单 9 中的这些代码时,注意它是如何使用 DOM 文档对象创建另一个节点的。还要注意每一个节点是如何必须显式附加到其父节点上的。即使是根节点也必须用 appendChild
方法显式附加到文档上。
在一个实现了 DOM API 的库中,您会希望具有将 DOM 文档持久化到一个加入到库的文件或者流中的能力。在基类 XMLFormatTarget
和 XMLWriter
中描述了这种能力。在名为 XMLWriter
、 LocalFileFormatTarget
和 StdOutFormatTarget
的类中它的实现被隐藏起来了。
清单 10 中的代码创建一个用于写入文件或者标准输出流的 XMLFormatter
对象。 XMLFormatter
对象处理不同字符集的代码转换,还负责可能包含保留的 XML 字符的文本的替换。 XMLWriter
对象遍历 DOM 树、将 XML 数据块传递给格式器以便输出。使用特别的 dump_xml
检查 DOM 内容的不利之处是缺少元素之间的换行。尽管这不是必需的——或者在一些情况下甚至是不希望的——但是对于生产数据,额外的空格会使 XML 在调试期更具有可读性。下面的只有两行的清单解决了上面代码的这个问题,它添加的空格提供了可读性,但是又不会使文本节点挤满 DOM 树。
清单 11. 打印效果好的 XML
// turn on serializer "pretty print" option if ( pSerializer->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true) ) pSerializer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true); |
将清单 10 和 11 放在手边以备调试。即使不需要创建一个 XML 文档,也会发现取得工作的 DOM 子树的快照的能力是很有用的。如果花时间下载本文的示例代码(见 参考资料),那么您会发现将 UTF-16 数据写入文件的宽字符版本。在 Xerces-C++ 以前的版本中,我惊讶地发现无法为“UTF-16”编码创建一个 XMLFormatter
对象,但是为“UTF-16(LE)”创建这个对象却可以成功。这个问题在版本 2 中得到了解决。
清单 11 中的 DOMPrint
代码显示了如何访问 DOM 树中的每一个节点。清单 12 显示了另一种方式:使用迭代器及树遍历器达到同样的目的。清单 12 中的节点迭代器代码假定已经存在有效的 DOM 节点变量 root
。
清单 12. 创建一个迭代器以访问所有文本节点
// create an iterator to visit all text nodes. DOMNodeIterator iterator = doc.createNodeIterator(root, DOMNodeFilter::SHOW_TEXT, NULL, true); // use the tree walker to print out the text nodes. for ( current = iterator.nextNode(); current != 0; current = iterator.nextNode() ) // note: this leaks memory! std::cout << current.getNodeValue().transcode(); std::cout << std::endl; |
清单 12 中的例子所做的就是穿过整个文档,提取文本节点并显示它们。注意 wcout
,它是 cout
的宽字符版本。清单 13 是 tree-walker 代码,它同样假定已经存在有效 DOM 节点变量 root
。
清单 13. 创建一个遍历器以访问所有文本节点
// create a walker to visit all text nodes. DOMTreeWalker walker = doc.createTreeWalker(root, DOMNodeFilter::SHOW_TEXT, NULL, true); // use the tree walker to print out the text nodes. for (DOMNode current = walker.nextNode(); current != 0; current = walker.nextNode() ) // note: this leaks memory! std::cout << current.getNodeValue().transcode(); std::cout << std::endl; |
清单 13 中的 tree-walker 例子的作用与这个实例中的节点迭代器一样,因为它不使用树遍历器的任何附加功能。在用 createTreeWalker
第一次创建树遍历器时, getCurrentNode()
方法返回根节点,不管 filter 或者 to-show 设置是什么。只有在第一次调用 nextNode()
之后, getCurrentNode()
才会像预期那样操作。
DOM API 使您可以像树木修理工那样修整、嫁接和剪掉 DOM 树的节点。操作 DOM 树的方法与从头开始合成一个 DOM 树的方法相同。清单 14 总结了这些方法。
清单 14.
DOMNode
方法总结
DOMNode cloneNode(bool deep) const; DOMNode insertBefore(const DOMNode &newChild, const DOMNode &refChild); DOMNode replaceChild(const DOMNode &newChild, const DOMNode &oldChild); DOMNode removeChild(const DOMNode &oldChild); DOMNode appendChild(const DOMNode &newChild); DOMNode insertBefore(const DOMNode &newChild, const DOMNode &refChild); DOMNode replaceChild(const DOMNode &newChild, const DOMNode &oldChild); |
节点特定的创建方法如 createTextNode
和 createElement
只在 DOMDocument
对象中可用。不过,可以在任何 DOMNode
对象中使用 cloneNode
方法。
DOMElement
节点有几个额外的方法用于处理嫁接和剪除属性,如清单 15 所示。
清单 15.
DOMElement
方法总结
void setAttribute(const DOMString &name, const DOMString &value); DOMAttr setAttributeNode(DOMAttr newAttr); void setAttributeNS(const DOMString &namespaceURI, const DOMString &qualifiedName, const DOMString &value); DOMAttr removeAttributeNode(DOMAttr oldAttr); void removeAttribute(const DOMString &name); void removeAttributeNS(const DOMString &namespaceURI, const DOMString &localName); |
从一个绑定到 DTD 的元素中删除一个属性时如果不当心,有时会产生意想不到的结果。如果这个 DTD 定义了属性的默认值,那么这个属性就会出现在 DOM 树中,而不管产生它的原始 XML。如果用 DOM API 从 DOM 树中剪除这个属性,那么它就会换成其默认值。换句话说,一个有默认值的属性节点是无法删除的!
说的差不多了,该用这些知识做一些有用的工作了。虽然基于 SAX 的图形应用程序是可用的,但是它给人的印象不是那么深刻。既然现在您已经可以使用 DOM API 了,那么就可以生成并解析 XML 了。为了加强前面的条形图的视觉效果,用 DOM 生成一个可伸缩矢量图(SVG)版本,它适合于在像 Adobe 插件、W3C 浏览器 Amaya 或者 Mozilla 的 SVG 版本(包括 1.0 及以后版本)这样的 SVG 查看器中显示。
用同样的 XML 数据作为输入,输出看起来类似于清单 16。
清单 16. 示例 XML/SVG 输出
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg SYSTEM "svg-20000303-stylable.dtd"> <svg width="320" height="200"> <g style="font-size:14"> <rect x="100" y="20" style="stroke:node; fill:red" width="20" height="20"/> <text x="20" y="35">North</text> </g> <g style="font-size:14"> <rect x="100" y="40" style="stroke:node; fill:blue" width="100" height="20"/> <text x="20" y="55">South</text> </g> <g style="font-size:14"> <rect x="100" y="60" style="stroke:node; fill:yellow" width="27" height="20"/> <text x="20" y="75">East</text> </g> <g style="font-size:14"> <rect x="100" y="80" style="stroke:node; fill:violet" width="23" height="20"/> <text x="20" y="95">West</text> </g> <g style="font-size:14"> <rect x="100" y="100" style="stroke:node; fill:orange" width="75" height="20"/> <text x="20" y="115">Central</text> </g> <text x="20" y="135" style="font-size:10">Reported figures are preliminary only.</text> </svg> |
通过输出 SVG 兼容的 XML 标记,可以使用 SAX 来实现这个效果。注意在 SVG 输出中包括 "!DOCTYPE"
声明。文档类型给 SVG 查看器很重要的线索,表明期望它们使用哪一个版本的 SVG 技术推荐。使用一种巧妙的技巧,可以让 Xerces-C++ 在文档输出中加入 DOCTYPE。图 2 显示清单 16 中的 SVG 在查看器中的样子。
图 2. SVG 输出的截图
![Screen shot of SVG output](https://i-blog.csdnimg.cn/blog_migrate/c5628c12bd54ca343ee1fd68278445d0.png)
下面, 清单 17显示了加入 XML 源数据以生成 SVG 最终结果的 DOM 代码。
在 SVG 输出文档中加入 "!DOCTYPE"
声明的技巧是使用 DOMDOMImplementation::createDocument()
而不是 DOMDocument::createDocument()
来创建文档。该代码在静态函数 doc2svg 靠近开始的地方。使用 DOMDOMImplementation
使您有机会创建一个 DOMDocumentType
节点,可以在创建方法中加入这个节点。这个创建方法的 DOMDocument
版本不提供指定文档类型的方法。
可以用 DOMDocument::createDocumentType()
创建方法创建一个 DOMDocumentType
节点。这个方法在这里没什么用,因为它不允许设置 DOCTYPE 的 system ID 或者 public ID。这种技巧的另一个巧妙之处是它为您创建顶级根元素。这就是为什么代码可以调用 getDocumentElement()
而不是为文档对象显式创建并附加根元素。
在本文及前面第 1 部分中,您已经看到 Xerces-C++ 库的好处包括开放源代码、可移植性和社团支持。可以通过解析源代码来解析库的操作。可以部署到任何支持 C++ 编译器的平台上。Windows 程序员可以得到在 C++、Visual Basic甚至 VBScript/JScript 中可以使用的 COM 版本。Apache 许可证允许在向用户做一个简单的法律声明和放弃声明的条件下,将 Xerces-C++ 用于商业目的。可以与邮件列表中的其他开发人员共同探讨有关开发的问题。所有这些好处使 Xerces-C++ 成为一个在自己项目中添加 XML 能力的绝好选择。
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
- 下载 源代码 以及本文中的图。
- 在本文作者的“ 充分利用 Xerces-C++,第 1 部分”中,学习将库链接到在 Linux 和 Windows 中编写的应用程序,以用 SAX API 进行解析。一个示例应用程序显示了如何创建一个 ASCII 艺术的条形图。
- 寻找更多有关 IBM 的 XML4C++ 解析器项目的信息,这个项目基于 Xerces-C++,可以在 alphaWorks上找到。
- 从 Apache 站点下载 Xerces-C++ XML 解析器。在那里还可以阅读 Xerces 编译文档。
- 阅读 Apache 软件许可证 条款,该许可证管理您在自己的应用程序中对 Xerces-C++ 的使用。
- 阅读有关内容或者下载 Apache XML 项目的 Xalan-C++,这是一个 XSLT 转换引擎。
- 了解 Apache 发起的其他 XML 项目。
- 阅读 SourceForge 上的 SAX API规范。
- 学习 Benoit Marchal 的 SAX, the Power API 一书中有关 SAX 的章节( developerWorks, 2001年8月),或者使用基础教程“ 理解 SAX”( developerWorks, 2003年7月)。
- 补充 DOM 的内容:
- DOM Level 1
- DOM Level 2 Core
- DOM Level 2 Views
- DOM Level 2 Events
- DOM Level 2 Style
- DOM Level 2 Traversal and Range
- DOM Level 3 Core
- DOM Level 3 Events
- DOM Level 3 Validation
- DOM Level 3 Abstract Schemas with Load and Save
- 有关 DOM 的基本介绍,可参阅 developerWorks教程“ 理解 DOM”( developerWorks,2003年7月)。
- 从 developerWorks教程“ SVG 简介”(2002年2月)和“ 交互式动态可伸缩向量图形”(2003年6月)学习有关可伸缩矢量图的更多内容。
- 阅读 W3C 站点上有关 SVG 规范的内容。
- 阅读 W3C 站点和 developerWorks article by Anne Zieger(2002年9月)中有关 SMIL中时间线上的动画 SVG 图形的内容。
- 在 developerWorks XML 专区中找到更多 XML 的内容。
- IBM 的 DB2数据库不仅提供了关系数据库存储,而且还提供了 XML 相关的工具,如提供了桥接XML与关系系统的 DB2 XML Extender。访问 DB2 Developer Domain以了解有关 DB2 的更多内容。
- 找到如何成为 在XML和相关技术方面IBM认证的开发员的内容。