pugixml
是轻量的C++
的XML
处理库.它由有丰富遍历/修改
功能的类似DOM
的接口,从XML文件/缓冲
构造DOM
树的极快XML
解析器,及复杂数据
驱动树查询
的XPath1.0
实现组成.还提供完整的统一码
支持,有两个统一码
接口变体和不同统一码
编码之间的转换(在解析/保存
时自动).
所有代码均在MIT
许可下分发.下载安装:
pugixml-1.13.zip (窗口行)
pugixml-1.13.tar.gz (联操行)
完整的pugixml
源码由三个文件组成:一个pugixml.cpp
源文件和两个pugixml.hpp
和pugiconfig.hpp
头文件.
pugixml.hpp
是使用pugixml
的类/函数
需要包含的主要标头.假设pugixml.hpp
在当前
目录或项目的包含目录中,以便#include"pugixml.hpp"
可找到标头;
构建pugixml
的最简单方法是把pugixml.cpp
源文件与现有的库/可执行
文件一起编译
.
pugixml
以类似DOM
的方式存储XML
数据:整个XML
文档(文档结构和元素数据
)在内存
中按树
存储.树可从符流
(文件
,串及C++I/O流
)加载,然后通过特殊的API
或XPath
式来遍历
它.
整个树
是可变的:节点
结构和节点/属性
数据都可随时更改.最后,文档转换的结果可保存到符流
(文件,C++I/O
流或自定义
传输).
树
的根是对应C++
的xml_document
类型的文档
自身.文档
有多个对应C++
的xml_node
类型的子节点
.节点
根据类型
有不同的类型,节点可为子节点
的集合,可为对应C++
的xml_attribute
类型的属性
集合及一些附加
数据(即名字).
最常见
的节点类型包括:
文档节点(node_document)
,这是由多个
子节点组成的树的根
.此节点对应xml_document
类;注意,xml_document
是xml_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_node
或using namespace pugi;
)访问相关
符号.
xml_document
是整个文档
结构的物主
;析构
文档会析构整棵树.xml_document
接口由加载
,保存
及允许检查和/或修改文档
的整个xml_node
接口组成
.
注意,虽然xml_document
是xml_node
的子类,但xml_node
不是多态
类型;继承
只是为了简化
使用.
xml_node
是文档
节点句柄;可指向文档中包括文档自身
的任意
节点.所有类型
节点都有个公共接口
.注意,xml_node
只是实际节点
的句柄
,而不是节点
自身.
可有多个
指向同一
基础对象的xml_node
句柄.析构xml_node
句柄不会析构节点
,也不会从树
中删除它.
xml_node
类型有个叫空节点
的特殊值
.它不对应文档
中的节点
,因此类似空针
.但是,在空节点
上定义了所有操作
;一般,是无操作
,并返回空节点/属性
或空串
.
这对链接
调用很有用;即,可如下得到节点
的祖父级:
node.parent().parent();
如果节点
是空
节点或没有父节点
,则第一个parent()
调用返回空节点
;然后,第二个parent()
调用也会返回空节点
,因此不必两次
检查错误.可隐式
布尔转换
来测试句柄
是否为为空
:
if(node){...}
if(!node){...}
xml_attribute
是XML
属性的句柄
;它有与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::istream
或std::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);
}
};