充分利用 Xerces-C++,第 2 部分

充分利用 Xerces-C++,第 2 部分
一个 DOM 实现

级别: 初级

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

2003 年 9 月 01 日

这篇分为两个部分的文章介绍了 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 节点类型

DOM 的基础数据类型是 DOMNode 类。所有 DOM 节点对象都是从这个基类扩展来的。表 1 显示了 DOM 节点类型。第一列是由方法 DOMNode::getNodeType() 返回的枚举的类型名。第二列是用于声明这一特定节点类型的实例所使用的 C++ 类。第三列显示用于实际生成这个实例所使用的构造方法。

表 1. DOM 节点类型

DOM 类型 DOM 类 构造方法
DOMNode::TEXT_NODEDOMTextcreateTextNode
DOMNode::PROCESSING_INSTRUCTION_NODEDOMProcessingInstructioncreateProcessingInstruction
DOMNode::DOCUMENT_NODEDOMDocumentcreateDocument
DOMNode::ELEMENT_NODEDOMElementcreateElement
DOMNode::ATTRIBUTEDOMAttrcreateAttribute
DOMNode::ENTITY_REFERENCE_NODEDOMEntityReferencecreateEntityReference
DOMNode::CDATA_SECTION_NODEDOMCDATASectioncreateCDATASection
DOMNode::COMMENT_NODEDOMCommentcreateComment
DOMNode::DOCUMENT_TYPE_NODEDOMDocumentTypecreateDocumentType
DOMNode::ENTITY_NODEDOMEntitycreateEntity
DOMNode::NOTATION_NODEDOMNotationcreateNotation
 注意在 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);

DOM 装载和解析

清单 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 中描述了这种能力。在名为 XMLWriterLocalFileFormatTarget 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 中得到了解决。

DOM 遍历

清单 11 中的 DOMPrint 代码显示了如何访问 DOM 树中的每一个节点。清单 12 显示了另一种方式:使用迭代器及树遍历器达到同样的目的。清单 12 中的节点迭代器代码假定已经存在有效的 DOM 节点变量 root 。 

// 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 操作

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

下面, 清单 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 能力的绝好选择。

参考资料

关于作者

Rick Parrish 以编写软件为生,但是还参与了几个开放源代码项目。他目前的兴趣包括 3 维图形和可视化、解码数字无线电信号和为 Mozilla 建立脚本化的数据框架。可以通过 rfmobile@swbell.net 与 Rick 联系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值