当前位置: 首页 > 工具软件 > pugixml > 使用案例 >

pugixml教程

李谦
2023-12-01

pugixml是轻量的C++XML处理库.它由有丰富遍历/修改功能的类似DOM的接口,从XML文件/缓冲构造DOM树的极快XML解析器,及复杂数据驱动树查询XPath1.0实现组成.还提供完整的统一码支持,有两个统一码接口变体和不同统一码编码之间的转换(在解析/保存时自动).
所有代码均在MIT许可下分发.下载安装:

pugixml-1.13.zip (窗口行)
pugixml-1.13.tar.gz (联操行)

完整的pugixml源码由三个文件组成:一个pugixml.cpp源文件和两个pugixml.hpppugiconfig.hpp头文件.
pugixml.hpp是使用pugixml类/函数需要包含的主要标头.假设pugixml.hpp当前目录或项目的包含目录中,以便#include"pugixml.hpp"可找到标头;

构建pugixml的最简单方法是把pugixml.cpp源文件与现有的库/可执行文件一起编译.

文档对象模型(DOM)

pugixml以类似DOM的方式存储XML数据:整个XML文档(文档结构和元素数据)在内存按树存储.树可从符流(文件,串及C++I/O流)加载,然后通过特殊的APIXPath式来遍历它.

整个树是可变的:节点结构和节点/属性数据都可随时更改.最后,文档转换的结果可保存到符流(文件,C++I/O流或自定义传输).

的根是对应C++xml_document类型的文档自身.文档有多个对应C++xml_node类型的子节点.节点根据类型有不同的类型,节点可为子节点的集合,可为对应C++xml_attribute类型的属性集合及一些附加数据(即名字).

最常见的节点类型包括:
文档节点(node_document),这是由多个子节点组成的树的根.此节点对应xml_document类;注意,xml_documentxml_node的子类,因此整个节点接口也可用.
元素/标签节点(node_element),这是最常见的表示XML元素的节点类型.元素节点有名字,属性集合和子节点集合(两者都可能为).该属性是个简单名/值对.
纯符数据节点(node_pcdata)表示XML中的纯文本.PCDATA节点有值,但无名子节点/属性.注意,纯符数据不是元素节点的一部分,而是有自己的节点;如,一个元素节点可有多个子PCDATA节点.

尽管有几种节点类型,但只有三个C++类型表示树(xml_document,xml_node,xml_attribute);xml_node上的某些操作仅对某些节点类型有效.如下:

注意

所有pugixml类和函数都在pugi名字空间中;你必须使用显式名限定(即pugi::xml_node),或通过using指令(即using pugi::xml_nodeusing namespace pugi;)访问相关符号.

xml_document是整个文档结构的物主;析构文档会析构整棵树.xml_document接口由加载,保存及允许检查和/或修改文档的整个xml_node接口组成.

注意,虽然xml_documentxml_node的子类,但xml_node不是多态类型;继承只是为了简化使用.

xml_node文档节点句柄;可指向文档中包括文档自身任意节点.所有类型节点都有个公共接口.注意,xml_node只是实际节点句柄,而不是节点自身.
可有多个指向同一基础对象的xml_node句柄.析构xml_node句柄不会析构节点,也不会从中删除它.
xml_node类型有个叫空节点特殊值.它不对应文档中的节点,因此类似空针.但是,在空节点上定义了所有操作;一般,是无操作,并返回空节点/属性空串.

这对链接调用很有用;即,可如下得到节点的祖父级:

node.parent().parent();

如果节点节点或没有父节点,则第一个parent()调用返回空节点;然后,第二个parent()调用也会返回空节点,因此不必两次检查错误.可隐式布尔转换来测试句柄是否为为空:

if(node){...}
if(!node){...}

xml_attributeXML属性的句柄;它有与xml_node相同的语义,即可多个同一底层对象的xml_attribute句柄,且有特殊的传播到函数结果的null属性值.
配置pugixml时,接口内部表示有两个选择:可选择UTF-8(也叫char)接口或UTF-16/32(也叫wchar_t)接口.
通过定义PUGIXML_WCHAR_MODE控制选择;可用pugiconfig.hpp或通过预处理器选项设置.

所有处理串的树函数都使用C风格的以null结尾的或所选符类型STL串.

加载文档

pugixml提供了几个函数,来从文件,C++iostream,内存缓冲等不同位置加载XML数据.所有函数都使用极快的不验证解析器.

解析前,总是把XML数据转换内部符格式.pugixml支持所有流行的统一码编码,并自动处理所有编码转换.

XML数据最常见的来源是文件;pugixml提供了一个单独从文件加载XML文档的函数.它接受文件路径作为第一个参数,及两个指定分析选项和输入数据编码可选参数.
从文件加载XML文档的示例(示例/load_file.cpp):

pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file("tree.xml");
std::cout << "Load result: " << result.description() << ", mesh name: " << doc.child("mesh").attribute("name").value() << std::endl;

load_file及其他加载函数会析构现有文档树,然后试从指定文件加载新树.在包含操作状态和相关信息xml_parse_result对象中返回操作结果(即,如果解析失败,则为上次成功解析输入文件中的位置).

可隐式转换解析结果对象为布尔值;如果不想彻底处理解析错误,可像检查布尔值一样,检查加载函数的返回值:

if(doc.load_file("file.xml")){...}
else{...}

否则,可用状态成员来取解析状态,或使用description()成员函数,按串形式取状态.

以下是处理加载错误(示例/load_error_handling.cpp)的示例:

pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(source);
if (result)
{
    std::cout << "XML [" << source << "] parsed without errors, attr value: [" << doc.child("node").attribute("attr").value() << "]\n\n";
}
else
{
    std::cout << "XML [" << source << "] parsed with errors, attr value: [" << doc.child("node").attribute("attr").value() << "]\n";
    std::cout << "Error description: " << result.description() << "\n";
    std::cout << "Error offset: " << result.offset << " (error at [..." << (source + result.offset) << "]\n\n";
}

有时XML数据应该从文件外的其他加载,即HTTP的URL;此外,想用非标准函数,从文件加载XML数据,即使用虚文件系统工具或从gzip压缩文件加载XML.

这些方案或需要从内存加载文档,此时,应该准备包含所有XML数据的连续内存块,并把它传递给加载缓冲函数之一,如从C++IOstream加载文档时,此时,你应该提供实现std::istreamstd::wistream接口的对象.

内存加载文档有不同的函数,按(load_buffer)不变缓冲,调用者拥有的(load_buffer_inplace)可变缓冲或pugixml拥有的(load_buffer_inplace_own)可变缓冲对待传递的缓冲.还有个简单的xml_document::load助手函数,来从以null结尾的串加载XML文档.如下为示例:

const char source[] = "<mesh name='sphere'><bounds>0 0 1 1</bounds></mesh>";
size_t size = sizeof(source);
//可用`load_buffer_inplace`从`可变`内存块加载文档;块的生命期必须`超过`文档生命期
char* buffer = new char[size];
memcpy(buffer, source, size);
//可由方法分配块;解析过程中`修改`块
pugi::xml_parse_result result = doc.load_buffer_inplace(buffer, size);
//不再使用文档后,必须自己析构块
delete[] buffer;

示例:

std::ifstream stream("weekly-utf-8.xml");
pugi::xml_parse_result result = doc.load(stream);

访问文档数据

pugixml有广泛从文档中取各种类型的数据和遍历文档的接口.可用各种访问器来取节点/属性数据,可访问器或迭代器遍历子节点/属性列表,可用xml_tree_walker对象深度优先遍历,并且可驱动XPath查询复杂数据.

可用name()节点或属性名,并通过value()取值.注意,这两个函数从不返回null指针,或返回包含相关内容的串,或返回空串(如果没有名/值或句柄为null).此外,读取值还有两件注意:

一般按某个节点的文本内容存储数据,即

<node><description>这是个node</description></node>

在本例中,<description>节点没有值,而是有值为"这是个节点"的node_pcdata类型的子节点.pugixml提供child_value()text()助手函数来解析此类数据.
属性值可有非串类型.即属性可能总是包含应被视为整数的值,尽管它们在XML中表示为串.pugixml提供了几个转换属性值为其他类型的函数,如下:

for (pugi::xml_node tool = tools.child("Tool"); tool; tool = tool.next_sibling("Tool"))
{
    std::cout << "Tool " << tool.attribute("Filename").value();
    std::cout << ": AllowRemote " << tool.attribute("AllowRemote").as_bool();
    std::cout << ", Timeout " << tool.attribute("Timeout").as_int();
    std::cout << ", Description '" << tool.child_value("Description") << "'\n";
}

因为许多文档遍历包括查找有正确名的节点/属性,为此有特殊函数.如,child("Tool")返回名叫"Tool"的第一个节点,如果没有此类节点,则返回null句柄.示例:

std::cout << "Tool for *.dae generation: " << tools.find_child_by_attribute("Tool", "OutputFileMasks", "*.dae").attribute("Filename").value() << "\n";
for (pugi::xml_node tool = tools.child("Tool"); tool; tool = tool.next_sibling("Tool"))
{
    std::cout << "Tool " << tool.attribute("Filename").value() << "\n";
}

子节点列表属性列表只是双向链表;虽然你可用previous_sibling/next_sibling和其他此类函数迭代,但pugixml还提供了节点和属性迭代器,因此可将节点视为其他节点或属性的容器.
所有迭代器都是双向的,支持所有常用的迭代器操作.如果从树中删除迭代器指向的节点/属性对象,则迭代器将失效;加节点/属性不会使迭代器失效.

for (pugi::xml_node_iterator it = tools.begin(); it != tools.end(); ++it)
{
    std::cout << "Tool:";
    for (pugi::xml_attribute_iterator ait = it->attributes_begin(); ait != it->attributes_end(); ++ait)
    {
        std::cout << " " << ait->name() << "=" << ait->value();
    }
    std::cout << std::endl;
}

区间示例:

for (pugi::xml_node tool: tools.children("Tool"))
{
    std::cout << "Tool:";
    for (pugi::xml_attribute attr: tool.attributes())
    {
        std::cout << " " << attr.name() << "=" << attr.value();
    }
    for (pugi::xml_node child: tool.children())
    {
        std::cout << ", child " << child.name();
    }
    std::cout << std::endl;
}

上述方法允许遍历某个节点的直接子节点;如果想深度遍历树,则必须通过递归函数或等效方法来完成.但是,pugixml为子树的深度优先遍历提供了一个助手.为了使用它,你必须实现xml_tree_walker接口并调用遍历函数.示例:

struct simple_walker: pugi::xml_tree_walker
{
    virtual bool for_each(pugi::xml_node& node)
    {
        for (int i = 0; i < depth(); ++i) std::cout << "  "; // 缩进
        std::cout << node_types[node.type()] << ": name='" << node.name() << "', value='" << node.value() << "'\n";
        return true; // 继续遍历
    }
};
simple_walker walker;
doc.traverse(walker);

最后,查询复杂的,一般需要更高级的DSL.pugixml为此类查询提供了XPath1.0语言的实现.这里有一些示例:

pugi::xpath_node_set tools = doc.select_nodes("/Profile/Tools/Tool[@AllowRemote='true' and @DeriveCaptionFrom='lastparam']");
std::cout << "Tools:\n";
for (pugi::xpath_node_set::const_iterator it = tools.begin(); it != tools.end(); ++it)
{
    pugi::xpath_node node = *it;
    std::cout << node.node().attribute("Filename").value() << "\n";
}
pugi::xpath_node build_tool = doc.select_node("//Tool[contains(Description, 'build system')]");
if (build_tool)
    std::cout << "Build tool: " << build_tool.node().attribute("Filename").value() << "\n";

修改文档,

pugi::xml_node node = doc.child("node");
// 更改节点名
std::cout << node.set_name("notnode");
std::cout << ", new node name: " << node.name() << std::endl;
// 更改注释文本
std::cout << doc.last_child().set_value("useless comment");
std::cout << ", new comment text: " << doc.last_child().value() << std::endl;
//无法更改元素的值或注释名
std::cout << node.set_value("1") << ", " << doc.last_child().set_name("2") << std::endl;
pugi::xml_attribute attr = node.attribute("id");
// 更改属性名/值
std::cout << attr.set_name("key") << ", " << attr.set_value("345");
std::cout << ", new attribute: " << attr.name() << "=" << attr.value() << std::endl;
// 可用数字或布尔值
attr.set_value(1.234);
std::cout << "new attribute value: " << attr.value() << std::endl;
//为了更简洁,还可用赋值操作符来
attr = true;
std::cout << "final attribute value: " << attr.value() << std::endl;

新属性/节点的示例:

// 加有某个名的节点
pugi::xml_node node = doc.append_child("node");
//用文本子项加描述节点
pugi::xml_node descr = node.append_child("description");
descr.append_child(pugi::node_pcdata).set_value("Simple node");
// 描述前加参数节点
pugi::xml_node param = node.insert_child_before("param", descr);
// 加属性到参数节点
param.append_attribute("name") = "version";
param.append_attribute("value") = 1.1;
param.insert_attribute_after("type", param.attribute("name")) = "float";

删除属性/节点示例:

//删除包含整个子树的描述节点
pugi::xml_node node = doc.child("node");
node.remove_child("description");
// 删除`ID`属性
pugi::xml_node param = node.child("param");
param.remove_attribute("value");
// 还可用句柄删除`节点/属性`
pugi::xml_attribute id = param.attribute("name");
param.remove_attribute(id);

保存文档

示例:

// 保存文档到文件
std::cout << "Saving result: " << doc.save_file("save_file_output.xml") << std::endl;

保存XML文档到标准输出:

// 保存文档到标准输出
std::cout << "Document:\n";
doc.save(std::cout);

自定义保存文档数据到STL串的编写器:

struct xml_string_writer: pugi::xml_writer
{
    std::string result;
    virtual void write(const void* data, size_t size)
    {
        result.append(static_cast<const char*>(data), size);
    }
};
 类似资料: