14.4 XML文档操作
问题
你想使用一个C++对象来代表一个XML文档,这样你就可以操作它的元素、属性、文本、DTD、处理指令和注释等。
解决方案
使用W3C DOM Xerces实现。首先,使用类xercesc::DOMImplementationRegistry来获取一个xercesc::DOMImplementation的实例,然后使用这个实例来创建一个xercesc::DOMBuilder解析器的实例。接下来,注册一个xercesc::DOMErrorHandler实例来接收解析错误的通知,并且用你的XML文档的URI或者文件路径作为参数来调用你的解析器的parseURI()方法。如果这个解析是成功的,parseURI()方法将返回一个指向代表你的XML文档的DOMDocument指针。然后你就可以使用由W3C DOM规范定义的函数来检查和操作你的文档。
当你操作你的XML文档时,你可以通过获取一个来自于 DOMImplementation实例的DOMWriter并且使用一个指向DOMDocument的指针作为参数来调用writeNode()方法,从而保存你的XML文档到一个文件中。
示例14-10描述了如何使用DOM来解析示例14-1中的animals.xml文档,它定位并删除这个对应于Herby大象的节点,然后保存修改后的文档。
示例14-10 使用DOM来加载、修改和保存XML文档
#include <exception>
#include <iostream>// cout
#include <xercesc/dom/DOM.hpp>
#include <xercesc/framework/LocalFileFormatTarget.hpp>
#include <xercesc/sax/SAXException.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include "animal.hpp"
#include "xerces_strings.hpp"
using namespace std;
using namespace xercesc;
/*
* Define XercesInitializer as in Example 14-8
*/
// RAII utility that releases a resource when it goes out of scope.
template<typename T>
class DOMPtr {
public:
DOMPtr(T* t) : t_(t) { }
~DOMPtr() { t_->release(); }
T* operator->() const { return t_; }
private:
// prohibit copying and assigning
DOMPtr(const DOMPtr&);
DOMPtr& operator=(const DOMPtr&);
T* t_;
};
// Reports errors encountered while parsing using a DOMBuilder.
class CircusErrorHandler : public DOMErrorHandler {
public:
bool handleError(const DOMError& e)
{
std::cout << toNative(e.getMessage()) << "\n";
return false;
}
};
// Returns the value of the "name" child of an "animal" element.
const XMLCh* getAnimalName(const DOMElement* animal)
{
static XercesString name = fromNative("name");
// Iterate though animal's children
DOMNodeList* children = animal->getChildNodes();
for ( size_t i = 0,
len = children->getLength();
i < len;
++i )
{
DOMNode* child = children->item(i);
if ( child->getNodeType() == DOMNode::ELEMENT_NODE &&
static_cast<DOMElement*>(child)->getTagName() == name )
{
// We've found the "name" element.
return child->getTextContent();
}
}
return 0;
}
int main()
{
try {
// Initialize Xerces and retrieve a DOMImplementation;
// specify that you want to use the Load and Save (LS)
// feature
XercesInitializer init;
DOMImplementation* impl =
DOMImplementationRegistry::getDOMImplementation(
fromNative("LS").c_str()
);
if (impl == 0) {
cout << "couldn't create DOM implementation\n";
return EXIT_FAILURE;
}
// Construct a DOMBuilder to parse animals.xml.
DOMPtr<DOMBuilder> parser =
static_cast<DOMImplementationLS*>(impl)->
createDOMBuilder(DOMImplementationLS::MODE_SYNCHRONOUS, 0);
// Enable namespaces (not needed in this example)
parser->setFeature(XMLUni::fgDOMNamespaces, true);
// Register an error handler
CircusErrorHandler err;
parser->setErrorHandler(&err);
// Parse animals.xml; you can use a URL here
// instead of a file name
DOMDocument* doc =
parser->parseURI("animals.xml");
// Search for Herby the elephant: first, obtain a pointer
// to the "animalList" element.
DOMElement* animalList = doc->getDocumentElement();
if (animalList->getTagName() != fromNative("animalList")) {
cout << "bad document root: "
<< toNative(animalList->getTagName())
<< "\n";
return EXIT_FAILURE;
}
// Next, iterate through the "animal" elements, searching
// for Herby the elephant.
DOMNodeList* animals =
animalList->getElementsByTagName(fromNative("animal").c_str());
for ( size_t i = 0,
len = animals->getLength();
i < len;
++i )
{
DOMElement* animal =
static_cast<DOMElement*>(animals->item(i));
const XMLCh* name = getAnimalName(animal);
if (name != 0 && name == fromNative("Herby")) {
// Found Herby -- remove him from document.
animalList->removeChild(animal);
animal->release(); // optional.
break;
}
}
// Construct a DOMWriter to save animals.xml.
DOMPtr<DOMWriter> writer =
static_cast<DOMImplementationLS*>(impl)->createDOMWriter();
writer->setErrorHandler(&err);
// Save animals.xml.
LocalFileFormatTarget file("animals.xml");
writer->writeNode(&file, *animalList);
} catch (const SAXException& e) {
cout << "xml error: " << toNative(e.getMessage()) << "\n";
return EXIT_FAILURE;
} catch (const DOMException& e) {
cout << "xml error: " << toNative(e.getMessage()) << "\n";
return EXIT_FAILURE;
} catch (const exception& e) {
cout << e.what() << "\n";
return EXIT_FAILURE;
}
}
讨论
如同TinyXml解析器那样,Xerces DOM解析器也产生一个代表XML文档的树结构的C++对象,这个树的节点代表XML文档的组件。Xerces是一个非常复杂的解析器,然而,例如:不像TinyXml,它明白XML的命名空间并且能够处理复杂的DTD。它也能构造代表XML文档的更为详细的结构,包括它的处理指令以及与元素和属性相关的命名空间。更为重要的是,它提供了由W3C DOM规范描述的接口来访问这些信息。
W3C规范还处在发展中,它被分成多个级别;目前有三个级别。类DOMImplementation、DOMDocument、DOMElement和DOMNodeList在示例14-10中使用的都是在DOM第一级中规定的。类DOMBuilder和DOMWrite是在DOM第三级规定的,作为加载和保存的推荐标准的一部分。
注意: Xerces类名并不总是和W3C DOM实现的接口名字相同; 因为Xerces在一个单一的命名空间中实现了多个规范,为了避免名字的冲突在一些类名前面加了一些前缀。
示例14-10应该是很容易明白的。就像在示例14-8中一样,我开始时初始化这个Xerces。然后从DOMImplementationRegistry获取DOMImplementation实例,通过传给静态方法DOMImplementationRegistry::getDOMImplementation()一个“LS”字符串来获取加载和存储功能特性。接下来我从DOMIMplementation中获取DOMBuilder。这里我不得不把DOMIMplementation转换成DOMIMplementationLS类型,因为从W3C DOM的第一级中的接口DOMIMplementation是不能访问加载和保存功能特性的。这个createDOMBuilder()的第一个参数表明返回的解析器是以同步方式工作的。Xerces目前还不支持另外一个可能的方式:异步方式。
获取到一个DOMBuilder后,我就打开XML命名空间功能特性,注册一个ErrorHandler并解析这个文档。这个解析器返回一个DOMDocument对象代表这个XML文档;然后我使用DOMDocument的getElementsByTagName()方法,获取一个DOMElement对象,这个DOMElement对象对应于文档中的animalList元素,并且我使用DOMNodeList类型的对象来枚举它的每一个子节点。当我发现一个元素,它的name类型的子元素中包含文本“Herby”时,我就调用根节点元素的removeChild()方法来删除它。
注意: 就像SAX2XMLReader有一个带InputSource实例的方法parse()一样,DOMBuilder也有一个带xercesc::DOMInputSource实例的方法parse(),一个封装了DOMInputSource字符数据源的抽象类有一个具体子类Wrapper4DOMInputSource,这个具体子类可以用来把任意的InputSource转换成xercesc::DOMInputSource。请参考14.3节。
最后我以获取DOMBuilder相同的方式从DOMImplementation中获取一个DOMWriter对象,然后再把这个文档的根节点元素作为参数调用writeNode()方法,把这个修改后的XML文档存储到硬盘中。
注意: 你必须调用release()方法来把由DOMImplementation::createXXX()形式的方法返回的指针释放掉。使用示例14-10中的DOMP tr方法来保证在有异常抛出的情况下也删除这个指针。由DOMDocument::createXXX()形式返回的指针不用显式地删除,尽管当它们不再需要时你也可以删除它们。更详细的信息请参考Xerces类库文档。






