pugixml比tinyxml快不止一个数量级,仅比最快的RapidXml慢一点。pugixml比rapidxml的一个优点是pugixml支持xpath, rapidxml不支持xpath。
1. pugixml项目主页 http://code.google.com/p/pugixml/
2. pugixml文档 http://pugixml.googlecode.com/svn/tags/latest/docs/quickstart.html
3. pugxml性能测试 http://pugixml.org/benchmark/
4. pugxml文档翻译 http://blog.csdn.net/jdzfjfhnui/article/details/6672532
5. pugxml使用例子 http://hi.baidu.com/blueapple_c/item/753334c6cff25e7388ad9e58
6. pugxml使用心得 http://blog.csdn.net/clever101/article/details/7524985
7. pugxml简介 http://blog.csdn.net/clever101/article/details/7521603
8. pugxml简单介绍 http://blog.sina.com.cn/s/blog_5a237c2b0100vvrn.html
9. xmlcpp库比较 http://lars.ruoff.free.fr/xmlcpp/
10. 常见的xml解析库http://fantasy1215.blog.hexun.com/55545682_d.html
11. rapidxml简介 http://rapidxml.sourceforge.net/manual.html
学习文档:http://pugixml.googlecode.com/svn/tags/latest/docs/quickstart.html , 总结一下使用步骤和简单的使用方法:(1)使用pugixml库需要三个文件:pugiconfig.h/pugixml.h/pugixml.cpp,可直接从gugixml官网下载,将其加入工程,使用处包含头文件pugiconfig.h/pugixml.h即可。
(2)加载xml文件,使用xml_document类的load_file接口:
std::strFile = "../test.xml";
pugi::xml_document doc;
if (!doc.load_file(strFile.c_str()))
{ //return -1;}
(3)加载xml格式的字符串,使用xml_document类的load接口:
std::strText = "****";
pugi::xml_document doc;
if (!doc.load(strText.c_str()))
{ //return -1;}
(4)xml节点读取,如xml文件params.xml:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<!-- 输入参数配置 -->
<form ip="10.2.134.243" port="80" action="sisserver.php">
<input name="data_type" value="POI" />
<input name="query_type" value="TQUERY" />
<input name="category" value="" />
<!-- 查询词的返回结果xpath配置 -->
<xpath poiroot="//list/poi" idfield="pguid" namefield="name"/>
<!-- 评分权重配置 r1~r4-期望结果的权重,n1~n10-实际查询结果的排名权重-->
<weight>
<!-- 查询词正常得分阀值 -->
<threshold>3</threshold>
<!-- 计算分数分布情况的步长值 -->
<step>0.5</step>
</weight>
</root>
读取代码:
std::string strFile = "/bak/workspace/test/src/params.xml";
pugi::xml_document doc;
if (!doc.load_file(strFile.c_str()))
{return 0;}
pugi::xml_node form = doc.child("root").child("form");
std::string ip = form.attribute("ip").value();
std::string port = form.attribute("port").value();
char cBuf[2083];
sprintf(cBuf, "http://%s:%s/%s?", ip.c_str(), port.c_s());
std::string strTemp(cBuf);
std::string m_strURLBase = strTemp;
for (pugi::xml_node input = form.first_child(); input;
input = input.next_sibling())
{
std::string strValue = input.attribute("value").value();
if (!strValue.empty())
{
std::string strName = input.attribute("name").value();
sprintf(cBuf, "%s=%s&", strName.c_str(), strValue.c_str());
std::string strTemp(cBuf);
m_strURLBase += strTemp;
}
}
//读取xpath
pugi::xml_node xpath = doc.child("root").child("xpath");
std::string m_strPOIRoot = xpath.attribute("poiroot").value();
std::string m_strPOIID = xpath.attribute("idfield").value();
//读取评分权重
pugi::xml_node weight = doc.child("root").child("weight");
float m_fThred = atof(weight.child_value("threshold"));
float m_fStep = atof(weight.child_value("step"));
(5)xpath解析,如xml格式的字符串strWebContent:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<list count="3" time"10">
<poi>
<pguid>123</pguid>
<name>xx1</name>
</poi>
<poi>
<pguid>456</pguid>
<name>xx2</name>
</poi>
<poi>
<pguid>789</pguid>
<name>xx3</name>
</poi>
</list>
</root>
其中,xpath根路径:m_strPOIRoot="//list/poi",
需要取值的项:strPOIID=“pguid”,strPOINam=“name”。
读取代码:
//从strWebContent内容中解析出pguid和name
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load(strWebContent.c_str());
if (!result)
{return -1;}
pugi::xpath_node_set tools = doc.select_nodes(m_strPOIRoot.c_str());
for (pugi::xpath_node_set::const_iterator it = tools.begin();
it != tools.end(); ++it)
{
pugi::xpath_node node = *it;
string strPOI = node.node().child_value(m_strPOIID.c_str());
string strName = node.node().child_value(m_strPOIName.c_str());
}
只是简单的翻译了pugixml文档,暂时未翻译XPath部分.
/*
1.对象模型
pugixaml存储XML数据为DOM风格,整个xml文档(文档结构和元素数据)被存储在内存中作为一棵树.这棵树可以从字符流(文件,字符串,C++ I/O流)中加载,可以使用
特定的API和XPath表达式来遍历它们.
整个树是可变的:节点结构和节点属性属性都可以在任何时候更改.
最后,可以将文档存储到字符流(文件, C++ I/O流,或自定的传输口).
XML文档由树结构呈现,树的根就是文档本身,由xml_document表示,
文档可以有一个或多个子节点, 由xml_node表示, 节点有不同的类型,视节点类型而定,一个节点可以有一组子节点,一组属性(由xml_attribute表示),
和额外的属性,例如name.
节点类型定义在枚举xml_node_type
1.文档节点(node_document)
这是树的根,它由一些子节点组成.这个节点相当于xml_document类,xml_document是xml_node的子类,因此节点所有的接口都可以用,
然而文档节点是有专门用途的,稍后解释.一棵树中只能有一个文档接口, document node does not have any XML representation
2.元素/标记 节点(node_element)
这是最普遍的节点类型,它代表XML元素.元素节点可以有一个名字,一组属性, 一组子节点(属性和子节点都可以为空).属性就是一组简单name/value pair.
就像如下:
<node attr="value"><child/></node>
这里有两个元素节点,一个名字为node, 有一个属性attr, 和一个子节点, 另一个元素的名字为chid,它没有任何属性和子节点.
3.文本字符数据节点(node_pcdata)
PCDATA 有一个值,但没有名字或子节点/或属性. 这种节点只能作为PCDATA插入到其他节点.例如一个元素可以有几个子PCDATA节点,下面是一个例子:
<node> text1 <child/> text2 </node>
这里 node元素有三个子节点, 两个为PCDATA节点,分别为text1和text2.还有一个child节点.
4.字符数据节点(node_cdata)
在XML用文本表示,他可以用特殊方式来引用.CDATA节点与PCDATA节点的不同之处是,CDATA在xml中表示.
the above text example looks like this with CDATA:
<node> <![CDATA[[text1]]> <child/> <![CDATA[[text2]]> </node>
CDATA节点可以让你容易的包含<, & and > characters in plain text.
CDATA的值可以包含字符序列]]>, since it is used to determine the end of node contents.
5.注释节点(node_comment)
在XML中表示注释.注释节点可以有值,但不能有名字或子节点或属性.在XML中表示如下:
<!-- comment text -->
这个注释节点的值为comment text
默认情况下注释节点不被视作为xml标记的一部分,因此在载入后分析器将丢弃它.你可以改变这个行为,通过加上parse_comments标记.
6.处理指令节点(node_pi)
在xml中表示processing instructions (PI).PI节点有一个名字和可选的值,但不能有子节点/属性.
在XML中PI表现如下:
<?name value?>
name(可以称作PI target)为name, and the value is "value".
默认情况下PI节点不是被视作为xml标记的一部分,因此在载入后分析器将丢弃它.你可以改变这个行为,通过加上parse_pi标记.
7.声明节点(node_declaration)
在XML中表示文档声明.声明节点的名字为xml,和一组可选的属性, 它不能有值和子节点.
在一个文档中只能有一个声明节点,而且,它只能在最文档的最前面.
There can be only one declaration node in a document; moreover, it should be the topmost node (its parent should be the document).
声明节点在XML表现如下:
<?xml version="1.0"?>
默认情况下声明节点不是视作为xml标记的一部分,因此在载入后分析器将丢弃它.你可以改变这个行为,通过加上parse_declaration标记.
在保存xml文档的时候,如果文档有声明则使用该声明否则,使用库自己设置的声明节点.你可以禁止此功能,通过打上format_no_declaration标记.
8.文档类型声明节点(node_doctype)
在XML中表示文档类型声明.文档类型声明节点可以有一个值,它对于到整个文档类型内容;
Document type declaration nodes have a value, which corresponds to the entire document type contents;
no additional nodes are created for inner elements like <!ENTITY>. There can be only one document type declaration node in a document;
moreover, it should be the topmost node (its parent should be the document). The example XML representation of a document type declaration node is as follows:
<!DOCTYPE greeting [ <!ELEMENT greeting (#PCDATA)> ]>
Here the node has value "greeting [ <!ELEMENT greeting (#PCDATA)> ]". By default document type declaration nodes are treated as
non-essential part of XML markup and are not loaded during XML parsing. You can override this behavior with parse_doctype flag.
所有的类和函数都在 pugi 名称空间.
xml_document是不可拷贝的类,他包含加载和保存XML文档的接口和继承了xml_node的接口,用于对文档进行修改.
注意,当xl_document为xml_node的子类型时,xml_node是一个多态类型,the inheritance is present only to simplify usage.
另外你可以使用document_element函数去获取元素节点, that's the immediate child of the document.
3.对象模型
xml_document的默认构造函数初始化文档树为一个根节点(文档节点),你可以通过修改函数或加载函数来populate该树.所有加载函数将销毁先前树用的内存空间.
并且使指向先前树的节点或者属性的句柄将变的无效.你可以使用xml_document::reset函数销毁树,它销毁树,你可以用一个空的或指定文档替换它.
xml_document的析构函数将自动销毁文档树,因此文档对象的生命周期要超过任何节点/属性句柄的句柄生命周期.
严格来说,节点/属性句柄可以继续生存即使他们引用的树已经销毁,对句柄调用任何成员函数将导致未知的行为.因此推荐只有在所有引用文档的节点/属性的句柄销毁后在销毁文档树.
xml_node是文档节点的句柄;它可以指向文档中的任何节点,包括文档节点本身.它是所有节点类型的一个通用的接口;节点的实际类型可以通过xml_node::type来查询.
注意:xml_node只有一个句柄指向实际的节点,而不是节点自身-你可以有多个xml_node句柄指向同一个对象.销毁xml_node句柄不会销毁该节点,也不会从树中移除该节点.
xml_node的大小就等于指针大小,因此它仅仅是一个指针的轻量级包装类;你可以安全的传递或返回xml_node对象以值类型方式,而不会有额外开销.
xml_node类型有一个特殊的值,叫做null节点或空节点(这样的节点的值为node_null).这样的节点没有对应到文档中的任何节点,就好比喻空指针.
然而,在空节点上所有的操作都有定义(有特定返回值).一般来说,操作空节点不会做任何事情,而返回值将是空节点或空属性或空字符串.(see documentation for specific functions for more detailed information).
这中设计很有用,例如当chaining调用时;你可用如下方式以获取某个节点的外祖父:
node.parent().parent();
如果node为null节点或它没有parent,第一次调用parent()将返回null节点,第二次调用parent()调用也同样返回null节点,这样更容易处理错误.
xml_attribute是指向XML属性的句柄;它与xml_node是一样的语义,例如,可以有多个xml_attribute句柄指向同一个对象,也可以是特殊的null属性指针.
xml_node和xml_attribute的默认构造函数初始化他们为null对象.
xml_node和xml_attribute的行为都像指针,就是说,他们可以与同一类型的其他对象进行比较,以确保他们在同一个容器内是一致的.
所有句柄如果指向同一个对象,则他们是相等的,否则不相等.null句柄只与自身比较时才相等.
The result of relational comparison can not be reliably determined from the order of nodes in file or in any other way. Do not use relational comparison operators except for
search optimization (i.e. associative container keys).
如果你想使用xml_node或xml_attribute对象作为哈希表的键,你可以使用has_value成员函数来获取该对象的哈希值.null指针的哈希值为0.指向同一个对象的句柄的哈希值是一样的.
句柄可以隐士的转换为布尔类型,你可以用下面的方法来测试是否节点/属性是空的:
if (node) { ... } or if (!node) { ... } else { ... }.
你也可以显式的检查xml_node/xml_attribute句柄是否为null:
bool xml_attribute::empty() const;
bool xml_node::empty() const;
没有文档树,则节点和属性不能单独存在,在创建他们之前,你不能添加他们到某个文档.
一旦节点/属性对象被销毁之后,指向他们的句柄将变得无效.这意味这entire tree的析构函数将使用所以的节点/属性句柄变得无限,它同样意味这销毁子树(通过调用xml_node::remove_chilid)或
移除属性,使用对应的句柄无效.没有办法检查句柄的有效性,你需要通过额外机制来确认.
Nodes and attributes do not exist without a document tree, so you can't create them without adding them to some document.
Once underlying node/attribute objects are destroyed, the handles to those objects become invalid. While this means that destruction of the entire tree invalidates
all node/attribute handles, it also means that destroying a subtree (by calling xml_node::remove_child) or removing an attribute invalidates the corresponding handles.
There is no way to check handle validity; you have to ensure correctness through external mechanisms.
Unicdoe接口:
你可以使用UTF-8(also called char)接口,或UTF-16/32(also called wchar_t)接口之一使用库.
这个选择可以通过PUGIXML_WCHAR_MODE定义,你可以在pugiconfig.hpp或预处理选项来设置.如果这个宏被定义,将使用wchar_t接口,否则(默认)使用char接口.
库将使用wchar_t类型的大小来确定宽字符编码是否为UTF-16或UTF-32.
注意:如果wchar_t的大小为2,pugixml使用UTF-16编码来代替UCS-2, which means that some characters are represented as two code points.
树的所有函数处理字符串可以是c风格null结尾的字符串或使用STL的string,例如:
const char* xml_node::name() const;
bool xml_node::set_name(const char* value);
and like this in wchar_t mode:
const wchar_t* xml_node::name() const;
bool xml_node::set_name(const wchar_t* value);
There is a special type, pugi::char_t, that is defined as the character type and depends on the library configuration; it will be also used in the documentation hereafter.
There is also a type pugi::string_t, which is defined as the STL string of the character type; it corresponds to std::string in char mode and to std::wstring in wchar_t mode.
除了这些接口之外,内部的实现是,将XML数据存储为pugi:char_t
In addition to the interface, the internal implementation changes to store XML data as pugi::char_t; this means that these two modes have different memory usage characteristics.
The conversion to pugi::char_t upon document loading and from pugi::char_t upon document saving happen automatically, which also carries minor performance penalty.
最后建议使用合适的字符模式,对于ASCII方式的XML数据可以使用UTF-8,否则可以使用wchar_t模式更合适.
下面帮助函数有助于你在UTF-8和wchar_t编码之间进行转换.
std::string as_utf8(const wchar_t* str);
std::wstring as_wide(const char* str);
这个两个函数接收一个null结尾的字符串作为参数,然后返回转换后的字符串为STL的字符串.
as_utf8将指向UTF-16/32转换为UTF-8,as_wide将指向UTF-8到UTF-16/32的转换.
在上面的转换中,无效的UTF序列将被无情的丢弃.str参数必须是一个有效的字符串,否则返回结果未知.这里还有两个重载:
std::string as_utf8(const std::wstring& str);
std::wstring as_wide(const std::string& str);
线程安全保证:
pugi库保证几乎所有的pugi函数在线程中有如下保证:
1.可以安全的在多线程中调用free(非成员)函数.
2.可以同时以只读的方式访问同一个树(所有const成员函数都不会修改树).
3.可以同时的进行读/写访问,如果此时对树只有一个读或或一个写访问.
同时修改和遍历树需要同步,例如,通过读写锁.修改是指修改文档结构和修改单独节点/属性的数据,例如改变names/values.
set_memory_function是个例外,它修改全局变量,因此不是线程安全的. 该函数可以定制内存分配和释放指定代理.
异常保证
除XPath之外,pugixml它自身没有抛出任何异常.另外, 多数的pugixml函数也具有不抛异常的保证.
Also functions that call user-defined callbacks (i.e. xml_node::traverse or xml_node::find_node) do not provide any exception guarantees beyond the ones provided by the callback.
If exception handling is not disabled with PUGIXML_NO_EXCEPTIONS define, XPath functions may throw xpath_exception on parsing errors; also, XPath functions may throw std::bad_alloc
in low memory conditions. Still, XPath functions provide strong exception guarantee.
使用默认构造函数来构造文档不会进行任何内存分配,文档节点在内存存储xml_document对象.
当从文件或缓冲区加载文档时候,除非是使用原地加载函数(see Loading document from memory),否则将产生字符流的一个副本,所有节点和属性的name/value都从此缓冲区分配.这个缓冲区
是分配的一个大的内存块,当文档销毁时候将释放该缓冲区.在分配该缓冲区之前,可能还需要分配一个缓冲区来做字符串编码转换缓冲区.
所有额外的内存,例如文档结构(节点/属性对象)的内存都是分配在页(该库作者自己写的页池分配内存池)上,这些页大小为32kb,然后的一些小对象都在这页里分配.这写页只有在该页分配
的所有对象都销毁后才能被释放,因而,销毁一个对象也不意味这随后创建的对象将重用同一个内存地址.
This means that it is possible to devise a usage scheme which will lead to higher memory usage than expected; one example is adding a lot of nodes,
and them removing all even numbered ones; not a single page is reclaimed in the process. However this is an example specifically crafted to produce unsatisfying behavior;
in all practical usage scenarios the memory consumption is less than that of a general-purpose allocator because allocation meta-data is very small in size
2.加载
pugixml提供几个函数用于从不同地方(文件,c++ io流, 内存缓冲区)加载xml数据.这些函数使用超快非验证的解析器,这个解析器不是完全遵照W3C标准-它可以
加载任何有效的XML文档,但没有执行某些well-formedness检查.虽然已经很努力来排除无效的XML文档,但为了性能的原因有些验证是没有完全执行的.
一些XML转换(例如: EOL处理或属性值标准化)将导致解析速度,因此这些验证将没有处理.对于绝大多数XML文档的不同解析选项之间不存在性能上的差异.
解析选项可以控制是否某些XML节点可以被继续,see parsing options for more information.
在解析之前XML数据总是转换为内部字符格式(see uinicode接口).pugixml支持当前流行的Unicode编码(UTF-8, UTF-16(大小端), UTF-32(大小端),UCS-2也自然支持,因为
它是UTF-16的子集.)并自动处理所有编码转换.加载函数通过检查XML数据的头部分自动进行编码检测,当然你也可以显式的指定了编码.多数情况下你不需要自己
指定文档编码.编码转换在编码一节有详细描述.
从文件加载文档
多数的XML数据源可能要算是文件了;pugixml提供专门的函数从文件加载XML文档,如下:
xml_parse_result xml_document::load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
xml_parse_result xml_document::load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
这些函数接收文件路径作为第一个参数及两个可选参数,一个用于指定解析选项,另一个指定输入数据的编码类型.路径与目标操作系统格式相关,可以指定
为绝对路径或相对路径.
load_file首先销毁先前的文档树然后尝试从指定文件中加载新的文档树.操作的返回类型为xml_parse_result对象;这个对象包含操作状态和相关的信息(例如,如果
解析失败,这里可以检查在文件中最后成功解析的位置).
//一个例子
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;
从内存加载
有些时候XML数据需要从其他源加载,例如HTTP URL;也许你希望通过非标准函数来提供XML数据,例如你虚拟文件系统或从gzip压缩的文件加载XML.这些方案都需要
从内存中加载文档.首先你需要为XML数据提供一个连续的内存块;然后你可以调用缓冲区加载函数.如果需要,这些函数将处理编码转换,然后解析数据为XML文档树.
这些缓冲区加载函数,都有不同的行为,因此有不同的性能和内存使用开销.
xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
这些函数接收缓冲区指针,该指针指向的内存块为XML数据,size用于指定缓冲区大小.同样,还有两个可选参数,用于指定解析选项和编码.缓冲区不必须要是zero-terminated.
load_buffer函数从缓冲区加载,并且不改变的缓冲区,因此在解析之前需要创建另一个缓冲区,并把XML数据拷贝该缓冲区.执行拷贝操作可能导致性能损失.
因此提供了load_buffer_inplace和load_buffer_inplace_own函数,它们将文档数据存储到该缓冲区,在处理时候修改它.为保持文档一直有效,你应该确保缓冲区在
树销毁前一直有效.除此之外,load_buffer_inplace不会认为缓冲区的所有权归自身,所有你必须自己销毁该缓冲区.load_buffer_inplace_own会把缓冲区的所有权归
为自己,在文档树不在需要的时候将销毁该缓冲区,这意味着如果你使用load_buffer_inplace_own,你得使用pugixml分配函数(通过get_memory_alloc_fuction)来分配内存.
性能和内存双优的方式是采用load_buffer_inplace_own函数,这个函数优化了沉余拷贝,减少在解析时的内存峰值使用率.
如果从内存中加载文档时,内存和性能都很关键,这是推荐使用的函数.
putixml提供一个函数用于从null结束的字符串中加载XML文档.
xml_parse_result xml_document::load(const char_t* contents, unsigned int options = parse_default);
这个函数相当于使用load_buffer函数,把缓冲区的长度参数设置为strlen(contents) or wcslen(contents) * sizeof(wchar_t),取决于字符类型.
这个函数对输入数据采用本地编码,因此不会有任何编码转换.通常,从小段常量字符串加载文档使用该函数.
//一个例子
const char source[] = "<mesh name='sphere'><bounds>0 0 1 1</bounds></mesh>";
size_t size = sizeof(source);
// 从内存块加载xml
pugi::xml_parse_result result = doc.load_buffer(source, size);
// You can use load_buffer_inplace to load document from mutable memory block; the block's lifetime must exceed that of document
char* buffer = new char[size];
memcpy(buffer, source, size);
// The block can be allocated by any method; the block is modified during parsing
pugi::xml_parse_result result = doc.load_buffer_inplace(buffer, size);
// You have to destroy the block yourself after the document is no longer used
delete[] buffer;
// You can use load_buffer_inplace_own to load document from mutable memory block and to pass the ownership of this block
// The block has to be allocated via pugixml allocation function - using i.e. operator new here is incorrect
char* buffer = static_cast<char*>(pugi::get_memory_allocation_function()(size));
memcpy(buffer, source, size);
// The block will be deleted by the document
pugi::xml_parse_result result = doc.load_buffer_inplace_own(buffer, size);
// You can use load to load document from null-terminated strings, for example literals:
pugi::xml_parse_result result = doc.load("<mesh name='sphere'><bounds>0 0 1 1</bounds></mesh>");
从C++ IO流加载
为了加强交互性,pugixml提供了从任何实现c++ std::istream接口的对象中加载文档.这允许你从任何c++流(例如文件流)或任何第三方实现(例如boost Iostream)
中加载文档.
pubixml提供了两个不同函数,一个用于窄字符,一个宽字符.
xml_parse_result xml_document::load(std::istream& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
xml_parse_result xml_document::load(std::wistream& stream, unsigned int options = parse_default);
load with std::istream argument loads the document from stream from the current read position to the end, treating the stream contents as a
byte stream of the specified encoding (with encoding autodetection as necessary). Thus calling xml_document::load on an opened std::ifstream
object is equivalent to calling xml_document::load_file.
load with std::wstream argument treats the stream contents as a wide character stream (encoding is always encoding_wchar). Because of this,
using load with wide character streams requires careful (usually platform-specific) stream setup (i.e. using the imbue function). Generally use of
wide streams is discouraged, however it provides you the ability to load documents from non-Unicode encodings, i.e. you can load Shift-JIS encoded data
if you set the correct locale.
std::ifstream stream("weekly-utf-8.xml");
pugi::xml_parse_result result = doc.load(stream);
Stream loading requires working seek/tell functions and therefore may fail when used with some stream implementations like gzstream.
处理分析错误
所有文档加载函数都返回xml_parse_result对象作为解析结果.它包含解析状态,最后成功解析的流位置,源的编码.
struct xml_parse_result
{
xml_parse_status status;
ptrdiff_t offset;
xml_encoding encoding;
operator bool() const;
const char* description() const;
};
解析状态由枚举xml_parse_status表示.
status_ok
在解析期间没有遇到错误,源是一个有效的xml文档,已被完全解析并转为文档树.
status_file_not_found
只有load_file函数返回,表示不能打开指定的文件.
status_io_error
load_fle和通过std::istream/std::wstream作为参数的函数,意味在读文件/流时出现某些I/O错误.
status_out_of_memory
在解析时没有足够可用的内存,表示分配内存失败.
status_internal_error
遭遇到严重错误,当前这个错误不会出现.
status_unrecognized_tag
由于标记为空或名字已错误的字符大头,例如#,解析已停止.
status_bad_pi
不正确的文档声明或pi导致解析停止.
status_bad_comment, status_bad_cdata, status_bad_doctype and status_bad_pcdata
无效的类型导致解析停止.
status_bad_start_element
由于标记没有>符号或包含错误的符号导致解析停止.
status_bad_attribute
由于不正确的属性,例如属性没有值或值没有用引号引起来(例如:<node attr=1>在XML中是错误的).
status_bad_end_element
由于结束标记中包含错误的语法.(例如,多余的非空格字符在标记名和>符号直接).
status_end_element_mismatch
由于结束标记没有匹配开头标记(例如,<node></nedo>)或某些标记没有结束.
description()函数可以将解析状态转换为字符串,返回的消息总是英文,如果你需要本地化字符串,你要自己写.
如果由于无效的XML导致解析失败,成功解析的一部分树没有被销毁-尽管加载函数返回错误,你还是可以使用成功解析的那部分树.
当然如果你坚持使用半成功解析的树,将可能有如下错误.
例如<node attr="value>some data</node>, 属性attr将包含value>some data</node> 这些值.因此不要使用这种树.
除了解析状态,解析结果还有一个偏移域,如果解析失败表示最后成功解析字符的位置,如果解析成功则为0.为了解析效率的原因,pugixml在解析时不会记录
当前行号,这个偏移的单位是pugi::char_t(对于char mode是字节,对于wide char mode是word).许多文本编辑器都支持 转到位置 功能-你可以使用它去定位
出错的位置.如果从内存加载文档, you can display the error chunk along with the error description (see the example code below).
注意:
Offset是根据XML缓冲区中的本地编码计算的,如果在解析期间发生了编码转换,则offset将不能真实的返回错误位置.
解析结果还有一个编码成员,表示解析时候用的编码.
解析结果可以隐式的转换为bool类型,你可以是如下方式来检查加载结果.
if (doc.load_file("file.xml"))
{ ... }
else
{ ... }.
//一个例子
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load(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文本转换.
为文档禁用某些转换可以改良解析性能;不过,所有的转换代码也经过优化,因此多数文档不需要担心性能损失.
作为大拇指规则,如果你想解析被默认排除的节点类型(例如,声明,或注释节点),你可以修改解析标记.
技巧,使用为掩码运算符来操作位掩码,要启用标记,则使用 mask|标记, 要禁用标记, 使用 mask & ~标记.
下面这些标记控制解析树的内容.
parse_declaration
如果XML文档有声明,则放node_declaration到DOM树中.如果关闭这个标记,将不会放到树中,但是依然解析并且检查准确性.默认是关闭此标记.
parse_doctype
如果XML文档中有文档类型声明, 则放node_doctype到DOM树中.如果关闭这个标记,将不会放到树中,但是依然解析并且检查准确性.默认是关闭此标记.
parse_pi
如果XML文档中有PI,则放node_pi到DOM树中.如果关闭这个标记,将不会放到树中,但是依然解析并且检查准确性.默认是关闭此标记.
注意: <?xml ...?> (文档声明) 不认为它是一个PI.
parse_comments
如果XML文档中包含注释, 则放node_comment到DOM树中.如果关闭这个标记,将不会放到树中,但是依然解析并且检查准确性.默认是关闭此标记.
parse_cdata
如果XML文档中有CDATA段, 则放node_cdata到DOM树中.如果关闭这个标记,将不会放到树中,但是依然解析并且检查准确性.默认是关闭此标记.
parse_ws_pcdata
如果XML文档中有PCDATA节点(类型为node_pcdata),它只包含空白字符,以这种形式放到DOM树.
通常这种数据对于应用程序来说不是重要的,为了存储这样的节点需要为它们分配内存,这将影响效率.例如,在解析XML字符串 <node> <a/> </node>,
如果此标记启用了,那么元素<node>有三个字元素,
1.child(node_pcdata类型和值" ").
2.child(node_element,名字为a).
3.child(node_pcdata类型和值" ").
就是说a的前面和后面的空白就是这种类型的节点.
如果没有启用此标记,那么node元素只有一个子元素.这个标记默认是关闭的.
下面这些标记控制树元素内容的转换.
parse_escapes
决定是否字符和原语引用在解析过程中被展开.字符引用的格式为&#...或&#x..;(其中...是unicode字符的十进制(&#...;)或十六进制(&#x...)的数字表示格式).
原语引用为<, >&, ' and " (pugixml不能处理DTD, 只允许预定义的原语).
如果字符串/原语引用不能被展开,将保持原样,因此你需要稍后额外在处理.只有属性值或PCDATA内容的引用才会执行展开.
默认此标记是启用的.
parse_eol
确定是否在输入数据中(注释内容, PCDATA/CDATA 内容和属性值)启用EOL处理(就是说,替换0x0d,0x0a序列为单个0x0a,用0x0a替换所有单独的0x0d).
默认此标记是启用的.
parse_wconv_attribute
确定是否属性值正规化应用到所有属性.也就是说,空白字符(换行, tab, 空格)替换为空格(' ').
如果parse_eol启用换行字符串总是被处理.例如. \r\n 转换为单个空格.
默认此标记是启用的.
parse_wnorm_attribute
确定是否属性值扩展应用到所有属性.就是说,在属性值正规化后,如果parse_wconv_attribute也设置了,前导和尾随空格字符都将被移除,并且所有顺序的空格
将被替换为单个空格.如果设置了此标记,则parse_wconv_attribute无效果.
这个标记默认是禁用的.
注意:
parse_wconv_attribute 选项执行转换需要满足W3C规范定义属性为CDATA, parse_wnorm_attribute performs transformations required for NMTOKENS attributes. In the absence of document type declaration all attributes
should behave as if they are declared as CDATA, thus parse_wconv_attribute is the default option.
下面三个是预定义的掩码选项.
parse_minimal
关闭所有选项.这个选项掩码表示pugixml不会添加声明节点, 文档类型声明节点, PI节点, CDATA段和注释节点不会添加到文档树,以不会对输入数据进行任何
转换,因此理论上将这是最快的模式.
作为前面提到的,在实际上,parse_default也相当的快.
parse_default
这是默认的标记.例如,解析CDATA段(注释/PI节点不解析),执行字符和原语展开,替换属性值中的空白字符为空格,处理EOL.
注意:PCDATA段只由空白字符组成,为了性能原因,因此没有解析(默认情况).
parse_full
添加所有类型的节点到树中,对输入数据执行默认转换.解析CDATA段,注释, PI节点, 文档声明节点,文档类型声明节点, 字符和原语引用展开,
替换属性值中的空白字符为空格,处理EOL.
注意:PCDATA段只由空白字符组成,也不解析.
const char* source = "<!--comment--><node><</node>";
// 默认选项解析,注释节点没有添加到树, 原语引用<将会展开.
doc.load(source);
std::cout << "First node value: [" << doc.first_child().value() << "], node child value: [" << doc.child_value("node") << "]\n";
// 解析包括注释节点, 现在注释节点现在将添加到树.
doc.load(source, pugi::parse_default | pugi::parse_comments);
std::cout << "First node value: [" << doc.first_child().value() << "], node child value: [" << doc.child_value("node") << "]\n";
// 解析注释节点,但不解析原语展开,因此 <将保持原样
doc.load(source, (pugi::parse_default | pugi::parse_comments) & ~pugi::parse_escapes);
std::cout << "First node value: [" << doc.first_child().value() << "], node child value: [" << doc.child_value("node") << "]\n";
// 最快速度解析,注释节点和原语引用不被展开.
doc.load(source, pugi::parse_minimal);
std::cout << "First node value: [" << doc.first_child().value() << "], node child value: [" << doc.child_value("node") << "]\n";
编码
pugixml支持当前流行的Unicode编码(UTF-8, UTF-16(大小端), UTF-32(大小端),UCS-2也自然支持,因为它是UTF-16的子集.)并自动处理所有编码转换.
大多数加载函数都接收一个可选编码参数.这个值是xml_encoding类型的枚举成员,可以有下面这些值:
encoding_auto
表示pugixml将基于XML数据来猜测编码类型.这个算法基于下面的顺序来测试XML文档的编码.
如果前4字节为UTF-32 BOM(byte order mark),则假设编码为UTF-32小端.
如果前2字节为UTF-16 BOM, 则假设编码为UTF-16小端.
如果前3个字节为UTF-8 BOM, 则假设编码为UTF-8.
If first four bytes match UTF-32 representation of <, encoding is assumed to be UTF-32 with the corresponding endianness;
If first four bytes match UTF-16 representation of <?, encoding is assumed to be UTF-16 with the corresponding endianness;
If first two bytes match UTF-16 representation of <, encoding is assumed to be UTF-16 with the corresponding endianness(这个假设可能不正确,但比UTF-8更好)
如果前面条件都不符合,则假设为UTF-8编码.
encoding_utf8
corresponds to UTF-8 encoding as defined in the Unicode standard; UTF-8 sequences with length equal to 5 or 6 are not standard and are rejected.
encoding_utf16_le
corresponds to little-endian UTF-16 encoding as defined in the Unicode standard; surrogate pairs are supported.
encoding_utf16_be
corresponds to big-endian UTF-16 encoding as defined in the Unicode standard; surrogate pairs are supported.
encoding_utf16
corresponds to UTF-16 encoding as defined in the Unicode standard; the endianness is assumed to be that of the target platform.
encoding_utf32_le
corresponds to little-endian UTF-32 encoding as defined in the Unicode standard.
encoding_utf32_be
corresponds to big-endian UTF-32 encoding as defined in the Unicode standard.
encoding_utf32
corresponds to UTF-32 encoding as defined in the Unicode standard; the endianness is assumed to be that of the target platform.
encoding_wchar
对于与wchar_t类型的编码,取决于wchar_t的大小,要么是encoding_utf16或encoding_utf32.
encoding_auto的算法为well-formed XML文档正确的检测任何支持的unicode 编码(从文档声明开始),为其他XML文档从<开始;
如果你的xml文档不是以<开头并且编码不是utf-8,则使用指定的编码.
注意:
在转换期间无效的UTF序列将被跳过.不要依赖这个行为;此外,在没有执行编码转换的情况下,无效的序列没有移除,因此你可以从节点/属性内容中获取他们.
3.访问文档数据
pugixml提供一些方法用于从文档中获取不同类型的数据和遍历文档.下面提供的方法他们都没有修改文档,除了XPath相关的函数;
有两种类型的树数据句柄,xml_node和xml_attribute.
基本遍历函数
在内部文档的表现形式是一棵树,每个节点可以有一组子节点(子节点的顺序与XML文件中一致),并且元素节点可以有一组属性.
为了让你从树中的一个节点获取另一个节点,pugixml提供了几个这样的函数.
These functions roughly correspond to the internal representation, and thus are usually building blocks for other methods of traversing(i.e. XPath traversals are based on these functions).
xml_node xml_node::parent() const; 父节点
xml_node xml_node::first_child() const; 第一个子节点
xml_node xml_node::last_child() const; 最后一个子节点
xml_node xml_node::next_sibling() const; 下一个兄弟节点
xml_node xml_node::previous_sibling() const; 前一个兄弟节点
xml_attribute xml_node::first_attribute() const; 第一个属性
xml_attribute xml_node::last_attribute() const; 最后一个属性
xml_attribute xml_attribute::next_attribute() const; 下一个属性
xml_attribute xml_attribute::previous_attribute() const; 前一个属性
parent()函数返回节点的父节点;所有非null的节点(除了文档节点)都有一个非null的父节点.
first_child()和last_child()分别返回节点的第一个或最后一个字节点;
注意:只有文档节点和元素节点可以有非空的子节点列表.如果节点没有子节点,这两个函数都返回null节点.
next_sibling()和previous_sibling()分别返回该节点在子节点列表中节点的左/右立即节点-例如:
<a/><b/><c/>; 对指向<b/>句柄调用next_sibling()将返回指向<c/>的句柄,而调用previous_sibling()将返回执行<a/>的句柄.
如果节点没有next/previous 兄弟(只有当该节点在子节点列表中是last或first时),这两个函数都返回null节点.
first_attribute, last_attribute, next_attribute 和 previous_attribute 函数的行为类似于对应的子节点函数,允许你在同样的方式下遍历属性.
注意:
因为内存消耗的原因,属性没有链接到它的父节点.因此没有xml_attribute::parent()函数.
在一个null句柄上调用上面这些函数,将返回一个null节点.
例如:node.first_child().net_sibling(),如果node为null节点,那么这返回的还是null节点.
通过执行函数,你可以遍历所有的子节点和显示所有属性就像下面这么简单.
for (pugi::xml_node tool = tools.first_child(); tool; tool = tool.next_sibling())
{
std::cout << "Tool:";
for (pugi::xml_attribute attr = tool.first_attribute(); attr; attr = attr.next_attribute()) {
std::cout << " " << attr.name() << "=" << attr.value();
}
std::cout << std::endl;
}
获取节点数据
除去结构信息之外(parent, child nodes, attributs),节点可以有name和value,都是以字符串表示.取决于节点类型,name或value可以不存在.
node_document节点没有name和value,
node_element和node_declaration节点总是有name但不可能有value,
node_pcdata, node_cdata, node_comment和node_doctype节点不可能有name但总是有value(可以是空),
node_pi总是有name和value(value也可以为空).
为了获取节点的name或value,你可以使用下面函数.
const char_t* xml_node::name() const;
const char_t* xml_node::value() const;
如果节点句柄是null或节点没有name或value时, 这些函数返回空字符串,绝不返回null指针.
某些节点通常存储文本数据作为它的内容.
例如:<node><description>This is a node</description></node>.
在这种情况下, <description>节点没有value,但他有一个node_pcdata类型的的子节点(它的值为"This is a node"), pubixml提供两个函数用于分析这样的数据:
const char_t* xml_node::child_value() const;
const char_t* xml_node::child_value(const char_t* name) const;
child_value() 返回节点的第一个子节点(可以是node_pcdata或node_cdaa类型的节点)的值.
child_value(name) 只是简单的包装child(name).child_value().
对于上面的例子, 调用node.child_value("description")和description.child_value()都返回字符串"This is a node".
如果node没有那些相关类型(可以是node_pcdata或node_cdaa类型的节点)的子节点,或node句柄为null,child_value就返回空字符串.
获取属性数据
所有属性都有一个name和value,都用字符串表示(value可以为空).下面这个两个函数用于获取属性的name和value.
const char_t* xml_attribute::name() const;
const char_t* xml_attribute::value() const;
如果属性句柄为null, 这两个函数都返回空字符串-绝不返回null指针.
在许多情况下属性的value类型可能不是字符串,例如:某个属性的的值应该视作为整型,尽管事实上它们在XML文件中用字符串表示.pugixml提供几个函数
用于转换属性value为其他类型.
int xml_attribute::as_int() const;
unsigned int xml_attribute::as_uint() const;
double xml_attribute::as_double() const;
float xml_attribute::as_float() const;
bool xml_attribute::as_bool() const;
as_int, as_uint, as_double and as_float 转换属性value为数字类型.如果属性句柄是null句柄或属性的value是空,则返回0.
否则,所有前导截断,剩下的字符串将解析为一个十进制数(as_int或as_uint)或解析为浮点数(as_double或as_float).任何其他字符将无情的被舍弃.
例如:对于字符串"1abc"见返回1.
如果输入的字符串数据超出目标数字的范围,返回结果是未知的.
注意:
数字转换函数依赖与当前的C locale,可以用setlocale设置, 如果locale与 经典"C"不同,则返回可能不是预期.
如果属性句柄为null或属性value是空,则as_bool()返回false,如果属性vluae的第一个字符是'1', 't', 'T', 'y', 'Y'之一返回true.
因此字符串如果是像"false"或"no"都认为是false.你可以写自己的判断函数.
注意:
此库没有提供可移植的64位类型,因此没有对应的转换函数.如果你的平台是64为整型,你可以容易写一个你自己的转换函数.
//下面是一个例子
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";
}
基于内容的遍历函数.
要找到指定的名字的节点或属性, pugixml提供了几个这样的函数.
xml_node xml_node::child(const char_t* name) const;
xml_attribute xml_node::attribute(const char_t* name) const;
xml_node xml_node::next_sibling(const char_t* name) const;
xml_node xml_node::previous_sibling(const char_t* name) const;
child()和attribute()返回第一个名字与指定名字相同子节点或属性.
next_sibling()和previous_sibling()返回第一个名字与指定名字相同的兄弟.
这些字符串的比较都是区分大小写的.
在null句柄的节点或该节点没有子node或属性的时候, 将返回null句柄.
child()和nest_sibling()函数可以一起循环遍历给定名字的节点,如下一样简单
for (pugi::xml_node tool = tools.child("Tool"); tool; tool = tool.next_sibling("Tool"))
有时候需要找出节点有某些属性和属性值的节点,例如:
<group><item id="1"/> <item id="2"/></group>.
下面两个方法基于属性values来找子节点.
xml_node xml_node::find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const;
xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const;
三个参数的函数返回第一个子节点(该节点的name和属性和属性的value与参数指定的一致)
两个参数的函数跳过节点名字对比,这对于搜索混杂的集合非常有用.
如果节点句柄为null或node节点找到,返回null句柄,字符串比较都是大小写敏感的.
在上面的所有函数,所有参数必须为有效的字符串,传递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提供节点和属性迭代器,因此可以把节点看作为其他节点或属性的容器.
class xml_node_iterator;
class xml_attribute_iterator;
typedef xml_node_iterator xml_node::iterator;
iterator xml_node::begin() const;
iterator xml_node::end() const;
typedef xml_attribute_iterator xml_node::attribute_iterator;
attribute_iterator xml_node::attributes_begin() const;
attribute_iterator xml_node::attributes_end() const;
beging()和attributes_begin()分别返回第一个节点或属性的迭代器.
end()和attributes_end()分别返回指向节点或属性end下一个的迭代器-这个迭代器不能解引用,但可以递减,但是注意递减past-the-end迭代器将导致未知行为.
Past-the-end 迭代器通常作为迭代器循环的终止值.如果你想获取指向已存句柄的迭代器,你可以使用单个参数来构造迭代器,例如:xml_node_iterator(node).
对于xml_attribute_iterator你需要提供属性和parnet节点句柄.
在null节点上调用begin()和end()返回的迭代器是相等的;这样的迭代器不能解引用. attributes_begin and attributes_end 行为一样.
For correct iterator usage this means that child node/attribute collections of null nodes appear to be empty.
这些迭代器都是双向的(例如可以递增,递减,但是随机访问不支持),并且支持所有常用的迭代器操作-比较,解引用,等待.
如果节点或属性对象从树中被移除,则指向该句柄的迭代器将变得无效.
//下面是个例子
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;
}
注意:
Node and attribute iterators are somewhere in the middle between const and non-const iterators. While dereference operation yields a
non-constant reference to the object, so that you can use it for tree modification operations, modifying this reference by assignment -
i.e. passing iterators to a function like std::sort - will not give expected results, as assignment modifies local handle that's stored in the iterator.
使用xml_tree_walker递归遍历
上面提及的函数运行在直接子节点之间进行遍历;如果你想做更深层次的树遍历,你可以通过递归函数,pugixml提供一个helper用于子树的depth-first遍历.
为了使用它,你得实现xml_tree_walker解开并调用traverse函数.
class xml_tree_walker
{
public:
virtual bool begin(xml_node& node);
virtual bool for_each(xml_node& node) = 0;
virtual bool end(xml_node& node);
int depth() const;
};
//开始遍历该节点,就是说该节点作为一个子树遍历
bool xml_node::traverse(xml_tree_walker& walker);
开始遍历操作如下:
首先,begin()函数被调用,参数为树根.
然后,为所有节点调用for_each函数,但不包括树根节点.每个节点作为该调用参数.
最后,end函数被调用,参数为树根.
如果begin, end或for_each返回false, 遍历将结束,并且traversal函数返回false,你不需要重写begin或end函数,它们的默认实现都返回true.
你可以通过depth()函数获取当前遍历的深度.如果没有调用begin/end,则返回-1.这个深度是基于0的.
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; // 返回true继续遍历
}
};
simple_walker walker;
doc.traverse(walker);
使用谓词(就是判断表达式)来搜索节点或属性
//不解释,好东西
While there are existing functions for getting a node/attribute with known contents, they are often not sufficient for simple queries.
As an alternative for manual iteration through nodes/attributes until the needed one is found, you can make a predicate and call one of find_ functions:
template <typename Predicate> xml_attribute xml_node::find_attribute(Predicate pred) const;
template <typename Predicate> xml_node xml_node::find_child(Predicate pred) const;
template <typename Predicate> xml_node xml_node::find_node(Predicate pred) const;
The predicate should be either a plain function or a function object which accepts one argument of type xml_attribute (for find_attribute) or
xml_node (for find_child and find_node), and returns bool. The predicate is never called with null handle as an argument.
find_attribute function iterates through all attributes of the specified node, and returns the first attribute for which the predicate returned true.
If the predicate returned false for all attributes or if there were no attributes (including the case where the node is null), null attribute is returned.
find_child function iterates through all child nodes of the specified node, and returns the first node for which the predicate returned true.
If the predicate returned false for all nodes or if there were no child nodes (including the case where the node is null), null node is returned.
find_node function performs a depth-first traversal through the subtree of the specified node (excluding the node itself), and returns the
first node for which the predicate returned true. If the predicate returned false for all nodes or if subtree was empty, null node is returned.
This is an example of using predicate-based functions (samples/traverse_predicate.cpp):
bool small_timeout(pugi::xml_node node)
{
return node.attribute("Timeout").as_int() < 20;
}
struct allow_remote_predicate
{
bool operator()(pugi::xml_attribute attr) const
{
return strcmp(attr.name(), "AllowRemote") == 0;
}
bool operator()(pugi::xml_node node) const
{
return node.attribute("AllowRemote").as_bool();
}
};
// Find child via predicate (looks for direct children only)
std::cout << tools.find_child(allow_remote_predicate()).attribute("Filename").value() << std::endl;
// Find node via predicate (looks for all descendants in depth-first order)
std::cout << doc.find_node(allow_remote_predicate()).attribute("Filename").value() << std::endl;
// Find attribute via predicate
std::cout << tools.last_child().find_attribute(allow_remote_predicate()).value() << std::endl;
// We can use simple functions instead of function objects
std::cout << tools.find_child(small_timeout).attribute("Filename").value() << std::endl;
辅助函数
如果你需要获取文档的根,使用下面这个函数.
xml_node xml_node::root() const;
这个函数返回node_document类型的节点,它是文档的根节点.
pugixml支持完整的XPath表达式,有时候一个简单的路径处理设置是必要的,这里有两个这样的函数,用于获取节点路径和转换路节点的路径.
string_t xml_node::path(char_t delimiter = '/') const;
xml_node xml_node::first_element_by_path(const char_t* path, char_t delimiter = '/') const;
节点路径由节点名字和分割符组成(默认为/),路径以可以包含self(.),parent(..),因此"../../foo/./bar"是一个有效的路径.
path returns the path to the node from the document root,
first_element_by_path looks for a node represented by a given path; a path can be an absolute one (absolute paths start with the delimiter),
in which case the rest of the path is treated as document root relative, and relative to the given node. For example,
in the following document: <a><b><c/></b></a>, node <c/> has path "a/b/c"; calling first_element_by_path for document with path "a/b" results in node <b/>;
calling first_element_by_path for node <a/> with path "../a/./b/../." results in node <a/>;
calling first_element_by_path with path "/a" results in node <a/> for any node.
In case path component is ambiguous (if there are two nodes with given name), the first one is selected; paths are not guaranteed to uniquely
identify nodes in a document. If any component of a path is not found, the result of first_element_by_path is null node; also first_element_by_path
returns null node for null nodes, in which case the path does not matter. path returns an empty string for null nodes.
Note
path function returns the result as STL string, and thus is not available if PUGIXML_NO_STL is defined.
pugixml does not record row/column information for nodes upon parsing for efficiency reasons. However, if the node has not changed in a
significant way since parsing (the name/value are not changed, and the node itself is the original one,
i.e. it was not deleted from the tree and re-added later), it is possible to get the offset from the beginning of XML buffer:
ptrdiff_t xml_node::offset_debug() const;
If the offset is not available (this happens if the node is null, was not originally parsed from a stream, or has changed in a significant way),
the function returns -1. Otherwise it returns the offset to node's data from the beginning of XML buffer in pugi::char_t units.
For more information on parsing offsets, see parsing error handling documentation.
4.修改
通过pugixml提供的函数你可以修改文档结构或修改节点.属性的数据,这些函数负责管理内存和结构的完整性,因此操作之后总是保持树的结构是有效的.
然而, 也有可能创建一个无效的XML树(例如,添加两个相同名字的属性或设置属性/节点的名字为或或无效的字符串).
对于属性的修改,内存消耗和性能都是经过优化的,如果你有足够的内存从头开始创建文档,稍后在保存它们到文件比起手写xml文本没有太大的开销.
所有改变节点/属性数据或结构的成员函数都是非const的,因此不能在const的句柄上调用它们. 然后你可以轻易的转换const句柄到非const,仅仅只需要一个
赋值操作,例如void foo(const pugi::xml_node& n) { pugi::xml_node nc = n; }, 因此常量的准确性在这里主要提供作为参考文档而已.
设置节点数据
前面说过,节点可以有name和value,都可以分别设置.取决与节点类型,name或value可以不存在.
node_document节点不可以有name或value,
node_element和node_declaration节点总是有一个name但不能有value,
node_pcdata, node_cdata, node_comment和node_doctype节点不可以有名字,但是总是有value(可能是空字).
node_pid节点总是有一个name和value(value可以为空).
通过下面的函数设置节点的name或vaue.
bool xml_node::set_name(const char_t* rhs);
bool xml_node::set_value(const char_t* rhs);
这两个函数设置节点的name或value为指定字符串,然后返回操作结果.如果节点不可以有name或value(例如, 对node_pcdaa节点调用set_name),或当节点句柄为null,
或没有足够的内存去处理该请求,将返回false.
指定的字符串将被拷贝到文档管理的内存中,因此字符串可以在函数返回后释放(例如,你可以安全的传递栈分配缓冲区到这些函数).
name和value内容是不经验证的,因此你注意使用有效的XMLanem否则文档可能变得malformed.
没有等量的child_value()函数来修改子节点的文本.
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;
// 我们不能改变element的value,也不能改变注释的name.
std::cout << node.set_value("1") << ", " << doc.last_child().set_name("2") << std::endl;
设置属性数据
所有属性都可以有name和value,都是有字符串表示(value可以为空).你可以使用下面的函数设置.
bool xml_attribute::set_name(const char_t* rhs);
bool xml_attribute::set_value(const char_t* rhs);
这两个函数尝试设置name或value为指定字符串,然后返回操作结果.如果属性句柄为null,或如果没有足够的内存处理该请求,操作将失败.
指定的字符串将被拷贝到文档管理的内存中,因此字符串可以在函数返回后释放(例如,你可以安全的传递栈分配缓冲区到这些函数).
name和value内容是不经验证的,因此你注意使用有效的XMLanem否则文档可能变得malformed.
除了字符串函数外,还有几个函数用于处理属性作为数字和布尔类型的值:
bool xml_attribute::set_value(int rhs);
bool xml_attribute::set_value(unsigned int rhs);
bool xml_attribute::set_value(double rhs);
bool xml_attribute::set_value(bool rhs);
上面这些函数将参数转为字符串,然后调用基类的set_value函数.整型将转为十进制格式,浮点型数字将转为十进制或科学计数法的形式(取决与数字的大小),
布尔值将转为"true"或"false";
注意:
数字转换函数依赖与当前的C locale,可以用setlocale设置, 如果locale与 经典"C"不同,则返回可能不是预期.
Note
There are no portable 64-bit types in C++, so there is no corresponding set_value function. If your platform has a 64-bit integer,
you can easily write such a function yourself.
为了更方便, 所有set_value函数有对于的复制运算符.
xml_attribute& xml_attribute::operator=(const char_t* rhs);
xml_attribute& xml_attribute::operator=(int rhs);
xml_attribute& xml_attribute::operator=(unsigned int rhs);
xml_attribute& xml_attribute::operator=(double rhs);
xml_attribute& xml_attribute::operator=(bool rhs);
这些运算符简单的调用正确的set_value函数,然后返回它调用的属性,而set_value的返回将被忽略,因此错误也忽略了.
//下面是一个例子
pugi::xml_attribute attr = node.attribute("id");
// 改变属性的name和value
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;
添加节点和属性
Nodes and attributes do not exist without a document tree, so you can't create them without adding them to some document.
节点或属性可以在某个节点或属性的后面或之后,之前创建.
xml_attribute xml_node::append_attribute(const char_t* name);
xml_attribute xml_node::prepend_attribute(const char_t* name);
xml_attribute xml_node::insert_attribute_after(const char_t* name, const xml_attribute& attr);
xml_attribute xml_node::insert_attribute_before(const char_t* name, const xml_attribute& attr);
xml_node xml_node::append_child(xml_node_type type = node_element);
xml_node xml_node::prepend_child(xml_node_type type = node_element);
xml_node xml_node::insert_child_after(xml_node_type type, const xml_node& node);
xml_node xml_node::insert_child_before(xml_node_type type, const xml_node& node);
xml_node xml_node::append_child(const char_t* name);
xml_node xml_node::prepend_child(const char_t* name);
xml_node xml_node::insert_child_after(const char_t* name, const xml_node& node);
xml_node xml_node::insert_child_before(const char_t* name, const xml_node& node);
append_attribute()和append_child()在调用节点的子列表后面创建一个新的节点或属性;
prepend_attribute()和prepend_child()在子列表前面创建一个新的节点或属性;
insert_attribute_after, insert_attribute_berfor,insert_child_after and insert_attribute_before添加节点或属性在指定节点或属性的之前或之后.
属性函数创建指定名字的属性;你可以指定空name或稍后改变属性的name.
节点函数创建指定类型的节点,因此不能改变节点类型,你需要事先知道自己需要什么类型.
也请注意,不是所有的类型都可以被作为子添加.
节点函数使用name参数创建一个指定名字的元素节点(node_element).
如果创建成功,所有函数返回指向创建的对象的句柄,如果失败则返回null句柄.可能导致失败的原因有:
如果目标节点为null(调用这些方法的节点);
只有node_element节点可以包含属性,因此往非element节点添加属性将失败.
只有node_document和node_element节点可以包含子节点,因此往非element或document添加子节点将失败.
node_document和node_null节点不能作为子插入到其他节点.
node_declaration节点只能作为document节点的子;尝试插入声明节点作为element节点的子将失败.
添加节点和属性会引起内存分配,这也可能失败.
插入函数将失败,如果指定节点或属性为null或不在目标节点的子或属性列表中.
即使操作失败,文档将保持一致状态,但是请求的节点或属性没有被真正添加.
注意:
attribute()和child()函数不会添加属性或节点到树中,因此写这样的代码:node.attribute("id") = 123;
如果节点没有名为"id"的属性,将不会做任何事情.
// 添加指定名字的节点
pugi::xml_node node = doc.append_child("node");
// add description node with text child
pugi::xml_node descr = node.append_child("description");
descr.append_child(pugi::node_pcdata).set_value("Simple node");
// add param node before the description
pugi::xml_node param = node.insert_child_before("param", descr);
// add attributes to param node
param.append_attribute("name") = "version";
param.append_attribute("value") = 1.1;
param.insert_attribute_after("type", param.attribute("name")) = "float";
移除节点或属性
如果你不想文档包含某些节点或属性,你可以使用下列函数移除它们.
bool xml_node::remove_attribute(const xml_attribute& a);
bool xml_node::remove_child(const xml_node& n);
remove_attribute()从节点的属性列表移除属性,然后返回操作结果.
remove_child()从文档的整个子树(包括所有子孙节点和属性, 这些都是xml_node的派生类)中移除子节点,然后返回操作结果,移除失败如果满足下面条件:
调用节点为null.
要移除的属性或节点为null.
属性和节点不在节点的属性或节点列表中.
移除节点或属性将导致所有指向同一个对象的句柄,和指向同一个对象的迭代器变得无效.
移除节点也会使所有past-the-end迭代器无效.
所有迭代器和句柄在属性或节点移除之后,都不应该在使用.
如果你想通过name来移除指定节点或属性, 可以使用如下函数:
bool xml_node::remove_attribute(const char_t* name);
bool xml_node::remove_child(const char_t* name);
这些函数在属性或节点列表中搜索与给定名字相同的节点或属性,如果找到则删除它,然后返回结果.
如果没有找到指定名字的子节点或属性,将返回false.如果找到两个与指定名字一样,只有第一个节点被删除.
如果你希望删除所有节点与指定名字一样的节点,你可以使用这样的代码:while (node.remove_child("tool")) ;.
// remove description node with the whole subtree
pugi::xml_node node = doc.child("node");
node.remove_child("description");
// remove id attribute
pugi::xml_node param = node.child("param");
param.remove_attribute("value");
// we can also remove nodes/attributes by handles
pugi::xml_attribute id = param.attribute("name");
param.remove_attribute(id);
克隆属性或节点
通过先前描述的功能,可以创建任何内容和结构的树, 包括克隆已存的数据.pugixml提供内建的节点属性克隆设施.
因为节点和属性不能在文档树之外存在,因此你不能创建一份单独的拷贝-你得立刻插入它们到树的某处.鉴于此,你可以使用下面这些函数.
xml_attribute xml_node::append_copy(const xml_attribute& proto);
xml_attribute xml_node::prepend_copy(const xml_attribute& proto);
xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr);
xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr);
xml_node xml_node::append_copy(const xml_node& proto);
xml_node xml_node::prepend_copy(const xml_node& proto);
xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node);
xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node);
这些函数从append_child, prepend_child, insert_child_before操作的结果中镜像对象的结构,他通过指向原型对象的句柄,然后克隆它们,在
插入一个新的属性和节点到合适的位置,然后拷贝属性数据或整个节点的子树到新对象.这个函数返回指向复制后的对象的句柄,或null句柄,如果失败.
属性拷贝的时候连同name和value,节点拷贝时候连同它的类型和name,value,其他的属性列表和所有子都被递归的克隆.
对克隆函数的几点说明:
克隆null句柄导致操作失败.
克隆节点时候,将从指定类型的插入节点开始,因此不能之间克隆整个文档, 因为node_document不是一个有效的插入类型.
下面例子提供一种方式.
可以克隆同一个节点所在子树的节点而作为该节点的子树. i.e. node.append_copy(node.parent().parent());.
下面是一个例子,演示节点克隆和其他修改文档函数.
bool load_preprocess(pugi::xml_document& doc, const char* path);
bool preprocess(pugi::xml_node node)
{
for (pugi::xml_node child = node.first_child(); child; )
{
if (child.type() == pugi::node_pi && strcmp(child.name(), "include") == 0)
{
pugi::xml_node include = child;
// load new preprocessed document (note: ideally this should handle relative paths)
const char* path = include.value();
pugi::xml_document doc;
if (!load_preprocess(doc, path)) return false;
// insert the comment marker above include directive
node.insert_child_before(pugi::node_comment, include).set_value(path);
// copy the document above the include directive (this retains the original order!)
for (pugi::xml_node ic = doc.first_child(); ic; ic = ic.next_sibling())
{
node.insert_copy_before(ic, include);
}
// remove the include node and move to the next child
child = child.next_sibling();
node.remove_child(include);
}
else
{
if (!preprocess(child)) return false;
child = child.next_sibling();
}
}
return true;
}
bool load_preprocess(pugi::xml_document& doc, const char* path)
{
pugi::xml_parse_result result = doc.load_file(path, pugi::parse_default | pugi::parse_pi); // for <?include?>
return result ? preprocess(doc) : false;
}
保存文档
通常在创建一个新文档或加载已存文档,在处理完之后,可能需要保存结果.pubixml提供几个函数用于保存文档到文件,流,或其他定制接口,这些函数允许
定制输出格式(看输出选项),执行必要的编码转换.
在写到目标之前节点/属性的数据将被依照节点的类型被合适的格式化,所有XML专用的符号,例如< , &将被转义. 空的节点/属性名字将被打印为:anonyous,
为了well-formed输出,你要确保所有节点和属性的名字都设置为有意义的值.
CDATA节的值如果包含"]]>"将被分离为如下几个节:
例如节的值为"pre]]>post",将转为:<![CDATA[pre]]]]><![CDATA[>post]]>.
保存文档至文件
bool xml_document::save_file(const char* path, const char_t* indent = "\t", unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
bool xml_document::save_file(const wchar_t* path, const char_t* indent = "\t", unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
这些函数接收文件路径作为第一个参数,它还有三个可选参数,用于指定缩进和输出数据编码类型.路径受目标操作系统约束,可以为相对或绝对等.
save_file函数首先以写方式打开文件,然后输出文档头(默认文档声明是输出的,如果文档以及定义了文档声明则用指定的),然后保存文档内容.如果不能打开文件,
将返回false,调用save_file等于用打开的文件句柄FILE*创建一个xml_writer_file对象,然后调用它的save.你可以定制输出接口.
// save document to file
std::cout << "Saving result: " << doc.save_file("save_file_output.xml") << std::endl;
保存文档至to C++ IOstreams
为了增强交互性,pubixml提供了用于保存文档至任何实现了c++ std::ostream接口的对象.
这允许你保存文档至任何标准的c++流,(例如文件流)或其他第三方实现的(例如boost io流).
这样使你可以容易的调试输出,因为你可以使用std::cout流作为保存目标.
这两个函数分别用于窄字符流和宽字符流:
void xml_document::save(std::ostream& stream, const char_t* indent = "\t", unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
void xml_document::save(std::wostream& stream, const char_t* indent = "\t", unsigned int flags = format_default) const;
使用std::ostream参数来保存文档到流的方式与save_file函数类似(例如,写文档声明头和编码转换).
而是要std::wstream将使用encoding_wchar编码保存文档到宽字节流,因此,使用宽字符流需要注意(通常是平台相关的)流设置(例如使用imbue函数).
通常使用宽字符流是discouraged,但是如果你需要保存文档至非unicode编码时候却要这么做,例如你可以保存文档为Shift-JIS编码,但你必须设置合适的locale.
使用流作为保存目标相当于创建了一个xml_wiriter_stream对象然后调用它的save方法.
// save document to standard output
std::cout << "Document:\n";
doc.save(std::cout);
保存文档至自由定义写接口
上面所有提到的保存函数都实现了一个叫做写接口.这个接口只有一个方法.如下:
class xml_writer
{
public:
virtual void write(const void* data, size_t size) = 0;
};
void xml_document::save(xml_writer& writer, const char_t* indent = "\t", unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
为了实现输出文档到某些定制的目标,例如套接字,你应该创建一个实现xml_writer_file接口的对象,然后把它作为save的参数.
//保存文档为stl字符串.
struct xml_string_writer: pugi::xml_writer
{
std::string result;
virtual void write(const void* data, size_t size)
{
result += std::string(static_cast<const char*>(data), size);
}
};
保存单个子树
先前描述的函数都是保存整个文档,pugixml额外提供了几个函数用于保存单个子树.
void xml_node::print(std::ostream& os, const char_t* indent = "\t", unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const;
void xml_node::print(std::wostream& os, const char_t* indent = "\t", unsigned int flags = format_default, unsigned int depth = 0) const;
void xml_node::print(xml_writer& writer, const char_t* indent = "\t", unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const;
这些函数与xml_document::save函数有相同的参数.
保存子树与整个文档不同:处理时将关闭format_wirter_bom, 开启format_no_decleartion,即使实际的值不是这样.这意味BOM不会写到目标,如果文档声明
是节点的子节点才会写到目标.
// get a test document
pugi::xml_document doc;
doc.load("<foo bar='baz'><call>hey</call></foo>");
// print document to standard output (prints <?xml version="1.0"?><foo bar="baz"><call>hey</call></foo>)
doc.save(std::cout, "", pugi::format_raw);
std::cout << std::endl;
// print document to standard output as a regular node (prints <foo bar="baz"><call>hey</call></foo>)
doc.print(std::cout, "", pugi::format_raw);
std::cout << std::endl;
// print a subtree to standard output (prints <call>hey</call>)
doc.child("foo").child("call").print(std::cout, "", pugi::format_raw);
std::cout << std::endl;
输出选项
所有保存函数接收可选标记参数.这是一个位掩码,用于指定输出格式.
这些标记控制文档输出输出的内容格式:
format_indent
确定是否所有节点使用缩进,并用指定字符串(默认是"\t").如果format_raw指定,则此标记无效.默认此标记是启用的.
format_raw
如果此标记启用,节点将不会有任何缩进,也没有换行.raw模式通常用于序列化文档.如果文档使用parese_ws_pcdata标记,此标记用于尽可能的保持文档的原始格式.
默认此标记是关闭的.
这些标记控制输出信息:
format_no_declaration
禁用默认的文档声明输出.通常通过save函数保存文档时,如果文档没有任何文档声明,一个默认的文档声明将输出.启用此标记来禁用文档声明自动输出.
这个参数对xml_node::print函数没有效果:它不会输出任何默认文档声明.默认这个标记是关闭的.
format_write_bom
允许BOM输出. By default, no BOM is output, so in case of non UTF-8 encodings the resulting document's encoding
may not be recognized by some parsers and text editors, if they do not implement sophisticated encoding detection. Enabling this flag adds an
encoding-specific BOM to the output. This flag has no effect in xml_node::print functions: they never output the BOM. This flag is off by default.
Additionally, there is one predefined option mask:
format_default
is the default set of flags, i.e. it has all options set to their default values. It sets formatted output with indentation,
without BOM and with default node declaration, if necessary.
// get a test document
pugi::xml_document doc;
doc.load("<foo bar='baz'><call>hey</call></foo>");
// default options; prints
// <?xml version="1.0"?>
// <foo bar="baz">
// <call>hey</call>
// </foo>
doc.save(std::cout);
std::cout << std::endl;
// default options with custom indentation string; prints
// <?xml version="1.0"?>
// <foo bar="baz">
// --<call>hey</call>
// </foo>
doc.save(std::cout, "--");
std::cout << std::endl;
// default options without indentation; prints
// <?xml version="1.0"?>
// <foo bar="baz">
// <call>hey</call>
// </foo>
doc.save(std::cout, "\t", pugi::format_default & ~pugi::format_indent); // can also pass "" instead of indentation string for the same effect
std::cout << std::endl;
// raw output; prints
// <?xml version="1.0"?><foo bar="baz"><call>hey</call></foo>
doc.save(std::cout, "\t", pugi::format_raw);
std::cout << std::endl << std::endl;
// raw output without declaration; prints
// <foo bar="baz"><call>hey</call></foo>
doc.save(std::cout, "\t", pugi::format_raw | pugi::format_no_declaration);
std::cout << std::endl;
*/
using namespace std;
#include "./src/pugixml.hpp"
using namespace pugi;
int _tmain(int argc, _TCHAR* argv[])
{
//load exits document
xml_document xml_doc;
xml_parse_result pr = xml_doc.load_file(_T(".\\test.xml"), parse_default, encoding_utf8);
if (!pr){
cout<<pr.description()<<endl;
}
//load from text constant
xml_doc.load(_T("<root name=\"a\" />"));
xml_doc.remove_attribute(_T("name"));
//输出到cout
xml_doc.print(std::cout);
/*
<root>
<element />
</root>
xml_document xml_doc
xml_doc.load(_T("<root><element /></root>"));
xml_doc.document_element() 返回xml_document类型的root,
xml_doc.document_element().first_child() 返回xml_node类型的element
xml_doc.first_child(),则返回root
*/
for (xml_node node = xml_doc.document_element().first_child(); node; node = node.next_sibling(_T("body")))
{
wcout<<node.name()<<endl;
node.append_attribute(_T("attr")).set_value(_T("great xml library, i like it"));
}
xml_node node = xml_doc.append_child();
node.set_name(_T("abc"));
node.append_child(node_pcdata).set_value(_T("aa"));
xml_doc.save_file(_T(".\\modifiered.xml"));
xml_doc.print(std::cout);
return 0;
}