这是用户在 2024-3-5 13:55 为 https://pugixml.org/docs/manual.html 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

 1.概述

 1.1.导言


pugixml 是一个轻量级的 C++ XML 处理库。它包括一个具有丰富遍历/修改功能的类 DOM 接口、一个从 XML 文件/缓冲区构建 DOM 树的超快 XML 解析器,以及一个用于复杂数据驱动树查询的 XPath 1.0 实现。此外,该程序库还支持全部 Unicode 编码,包括两种 Unicode 接口变体以及不同 Unicode 编码之间的转换(在解析/保存过程中自动进行)。pugixml 自 2006 年开始开发和维护,拥有众多用户。所有代码均以 MIT 许可发布,因此可完全免费用于开源和专有应用程序。


pugixml 可以非常快速、方便和节省内存地处理 XML 文档。不过,由于 pugixml 使用的是 DOM 解析器,因此无法处理内存不足的 XML 文档;此外,该解析器是非验证型的,因此如果您需要 DTD 或 XML Schema 验证,那么该库并不适合您。


本手册是 pugixml 的完整手册,详细介绍了该库的所有功能。如果您想尽快开始编写代码,建议您先阅读快速入门指南。

 注意

没有完美的文档,本文档也是如此。如果您发现错误或遗漏,请立即提交问题或打开拉取请求进行修复。

 1.2.反馈意见


如果您认为发现了 pugixml 中的错误(错误包括编译问题(错误/警告)、崩溃、性能下降和不正确的行为),请通过问题提交表单提交问题。请务必包含相关信息,以便重现错误:pugixml 版本、编译器版本和目标架构、使用 pugixml 并显示错误的代码等。


功能请求的报告方式与 Bug 相同,因此如果您觉得 pugixml 缺少某些功能,或者 API 某些地方不够完善,而您可以提出改进建议,请提交问题。但是请注意,在考虑更改 API 时有很多因素(与以前版本的兼容性、API 冗余等),因此一般来说,不接受无需修改 pugixml 即可通过小函数实现的功能。不过,所有规则都有例外。


如果您对 pugixml 有任何贡献,例如为某些构建系统/IDE 提供构建脚本,或提供一套精心设计的辅助函数,或绑定到 C++ 以外的语言,请提交问题或开启拉取请求。您的贡献必须在与 pugixml 许可证兼容的许可证条款下发布;即不接受 GPL/LGPL 许可证代码。


如果由于隐私或其他原因无法提交问题,您可以直接通过电子邮件与 pugixml 作者联系:arseny.kapoulkine@gmail.com。

 1.3.致谢


pugixml 的开发离不开许多人的帮助;本部分列出了其中的一些人。如果您参与了 pugixml 的开发,但却没有出现在这个名单上,我真的很抱歉;请给我发邮件,以便我能够解决这个问题。


感谢 Kristen Wegner 提供 pugxml 解析器,它是 pugixml 的基础。


感谢 Neville Franks 对 pugxml 解析器的贡献。


感谢 Artyom Palvelev 提出的懒惰间隙收缩法。


感谢 Vyacheslav Egorov 的文档校对和模糊测试。

 1.4.许可证


pugixml 库采用 MIT 许可发布:

Copyright (c) 2006-2023 Arseny Kapoulkine

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.


这意味着您可以在您的应用程序中自由使用 pugixml,包括开源和专有应用程序。如果您在产品中使用 pugixml,只需在产品发布时添加这样的确认即可:

This software is based on pugixml library (https://pugixml.org).
pugixml is Copyright (C) 2006-2023 Arseny Kapoulkine.

 2.安装

 2.1.获取 pugixml


pugixml 以源代码形式发布。你可以下载源代码或克隆 Git 仓库。


2.1.1.资料来源


您可以下载最新的源代码压缩包:


pugixml-1.14.zip (Windows 行结束语)/ pugixml-1.14.tar.gz (Unix 行结束语)


该发行版包含库源代码、文档(您正在阅读的手册和快速入门指南)以及一些代码示例。下载后,从压缩包中解压所有文件,安装 pugixml。


如果您需要旧版本,可以从版本存档中下载。

 2.1.2.Git 仓库


Git 仓库位于 https://github.com/zeux/pugixml/。每个版本都有一个 Git 标签 "v{version}";还有一个 "latest "标签,它总是指向最新的稳定版本。


例如,要检查当前版本,可以使用此命令:

git clone https://github.com/zeux/pugixml
cd pugixml
git checkout v1.14


资源库包含库源、文档、代码示例和完整的单元测试套件。


如果想自动获取新版本,请使用 latest 标签。如果只想明确切换到新版本,请使用其他标记。另外请注意,主分支包含代码的进行中版本;虽然这意味着你可以从主分支获得新功能和错误修复,而无需等待新版本,但这也意味着偶尔代码可能会在某些配置中被破坏。


2.1.3.Subversion 版本库


您可以使用 https://github.com/zeux/pugixml URL 通过 Subversion 访问 Git 仓库。例如,要检出当前版本,可以使用此命令:

svn checkout https://github.com/zeux/pugixml/tags/v1.14 pugixml

 2.1.4.包装


pugixml 作为软件包可通过各种软件包管理器获取。请注意,大多数软件包都与主软件源分开维护,因此不一定包含最新版本。


以下是不同系统中 pugixml 软件包的不完整列表:


  • Linux (Ubuntu、Debian、Fedora、Arch Linux 及其他发行版)

  • FreeBSD

  •  OSX,通过 Homebrew

  •  Windows,通过 NuGet

 2.2.构建 pugixml


pugixml 以源代码形式发布,没有任何预编译的二进制文件;您必须自行编译。


完整的 pugixml 源代码由三个文件组成:一个源文件 pugixml.cpp 和两个头文件 pugixml.hpppugiconfig.hpppugixml.hpp 是使用 pugixml 类/函数时需要包含的主要头文件; pugiconfig.hpp 是补充配置文件(参见附加配置选项)。本指南的其余部分假定 pugixml.hpp 位于当前目录或项目的 include 目录中,这样 #include "pugixml.hpp" 就能找到头文件;不过,你也可以使用相对路径(即 #include "../libs/pugixml/src/pugixml.hpp" )或 include 目录-相对路径(即 #include <xml/thirdparty/pugixml/src/pugixml.hpp> )。


2.2.1.将 pugixml 作为另一个静态库/可执行文件的一部分构建


编译 pugixml 最简单的方法是将源文件 pugixml.cpp 与现有的库/可执行文件一起编译。这一过程取决于构建应用程序的方法;例如,如果您使用 Microsoft Visual Studio [1] 、Apple Xcode、Code::Blocks 或其他 IDE,只需将 pugixml.cpp 添加到其中一个项目即可。


如果使用的是 Microsoft Visual Studio,且项目已打开预编译头文件,则会看到以下错误信息:

pugixml.cpp(3477) : fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "stdafx.h"' to your source?


正确的解决方法是禁用 pugixml.cpp 的预编译头文件;必须将 "创建/使用预编译头文件 "选项(属性对话框 → C/C++ → 预编译头文件 → 创建/使用预编译头文件)设置为 "不使用预编译头文件"。您必须在所有项目配置/平台上都这样做(在编辑该选项之前,您可以选择配置 "All Configurations(所有配置)"和平台 "All Platforms(所有平台)"):

vs2005 pch1
vs2005 pch2
vs2005 pch3
vs2005 pch4


2.2.2.将 pugixml 作为独立静态库构建


可以将 pugixml 编译成独立的静态库。这一过程取决于构建应用程序的方法;pugixml 发行版附带了适用于几种流行集成开发环境/构建系统的项目文件。其中包括适用于 Apple XCode、Code::Blocks、Codelite、Microsoft Visual Studio 2005、2008、2010+ 的项目文件,以及适用于 CMake 和 premake4 的配置脚本。欢迎提交其他软件的项目文件/构建脚本;请参阅 "反馈"。


Microsoft Visual Studio 的每个版本都有两个项目:一个用于动态连接的 CRT,其名称如 pugixml_vs2008.vcproj ,另一个用于静态连接的 CRT,其名称如 pugixml_vs2008_static.vcproj 。您应选择与应用程序中使用的 CRT 相匹配的版本;Microsoft Visual Studio 创建的新项目的默认选项是动态链接 CRT,因此除非您更改了默认值,否则应使用动态 CRT 的版本(例如,Microsoft Visual Studio 2008 的版本为 pugixml_vs2008.vcproj )。


除了将 pugixml 项目添加到工作区外,还必须确保应用程序与 pugixml 库链接。如果使用的是 Microsoft Visual Studio 2005/2008,可以将应用程序项目添加到 pugixml 项目中。如果使用的是 Microsoft Visual Studio 2010+,则必须在应用程序项目中添加引用。对于其他集成开发环境/系统,请查阅相关文档。

Microsoft Visual Studio 2005/2008 Microsoft Visual Studio 2010+
vs2005 link1
vs2005 link2
vs2010 link1
vs2010 link2


2.2.3.将 pugixml 作为独立共享库构建


可以将 pugixml 编译成独立的共享库。编译过程通常与静态库类似;不过,pugixml 发行版中没有预配置项目/脚本,所以你必须自己动手。一般来说,如果使用的是基于 GCC 的工具链,其过程与以 DLL 形式构建其他库并无不同(在编译标志中添加 -shared 即可);如果使用的是基于 MSVC 的工具链,则必须使用 declspec 属性明确标记导出的符号。您可以通过定义 PUGIXML_API 宏,即通过 pugiconfig.hpp

#ifdef _DLL
    #define PUGIXML_API __declspec(dllexport)
#else
    #define PUGIXML_API __declspec(dllimport)
#endif
 注意

如果使用 STL 相关函数,则应使用共享运行时库,以确保在应用程序和 pugixml 中使用单个堆进行 STL 分配;在 MSVC 中,这意味着在 "运行时库 "属性中选择 "多线程 DLL "或 "多线程调试 DLL"( /MD/MDd 链接器开关)。您还应确保在不同项目中选择的运行时库是一致的。


2.2.4.在纯标头模式下使用 pugixml


可以在纯头文件模式下使用 pugixml。这意味着 pugixml 的所有源代码都将包含在每一个包含 pugixml.hpp 的翻译单元中。大多数 Boost 和 STL 库都是这样工作的。


请注意,这种方法有利有弊。如果编译器工具链不支持链接时优化,或者关闭了链接时优化(有了链接时优化,性能应该与非头文件模式类似),头文件模式可能会提高树遍历/修改性能(因为许多简单函数将被内联)。不过,由于编译器现在必须为每个包含 pugixml 的翻译单元编译一次 pugixml 源代码,编译时间可能会明显增加。如果想在头文件模式下使用 pugixml,但又不需要 XPath 支持,可以考虑使用 PUGIXML_NO_XPATH 定义来禁用它,以缩短编译时间。


要启用纯头文件模式,必须定义 PUGIXML_HEADER_ONLY .您可以在 pugiconfig.hpp 中定义,也可以通过编译器命令行提供。


请注意,如果定义了 PUGIXML_HEADER_ONLY ,编译 pugixml.cpp 是安全的 - 因此,如果您想仅在 Release 配置中使用仅头文件模式,可以在项目中包含 pugixml.cpp(请参阅将 pugixml 作为另一个静态库/可执行文件的一部分构建),并在 pugiconfig.hpp 中像这样有条件地启用仅头文件模式:

#ifndef _DEBUG
    #define PUGIXML_HEADER_ONLY
#endif


2.2.5.附加配置选项


pugixml 使用几个定义来控制编译过程。有两种方法可以定义它们:要么将所需定义置 pugiconfig.hpp (它有一些注释出来的示例),要么通过编译器命令行提供它们。一致性很重要:在整个应用程序中,所有包含 pugixml.hpp 的源文件(包括 pugixml 源文件)中的定义都应一致。在 pugiconfig.hpp 中添加定义可以保证这一点,除非你的宏定义被包裹在预处理器 #if / #ifdef 指令中,而且该指令不一致。除了注释之外, pugiconfig.hpp 永远不会包含其他内容,这意味着在升级到新版本时,可以安全地保留修改后的版本。


PUGIXML_WCHAR_MODE 定义可在 UTF-8 风格界面(内存文本编码假定为 UTF-8,大多数函数使用 char 作为字符类型)和 UTF-16/32 风格界面(内存文本编码假定为 UTF-16/32,取决于 wchar_t 的大小,大多数函数使用 wchar_t 作为字符类型)之间切换。详见 Unicode 接口。


PUGIXML_COMPACT 定义会激活一种不同的文档存储内部表示法,对于有大量标记(即节点和属性)的文档来说,这种表示法更节省内存,但解析和访问速度稍慢。详情请参阅 "紧凑模式"。


PUGIXML_NO_XPATH 定义禁用 XPath。XPath 接口和 XPath 实现都不在编译之列。如果您不需要 XPath 功能,又想节省代码空间,可以使用该选项。


PUGIXML_NO_STL 定义禁止在 pugixml 中使用 STL。如果定义了该宏,则不再存在对 STL 类型进行操作的函数(如通过 iostream 加载/保存)。如果您的目标平台没有符合标准的 STL 实现,则可以使用该选项。


PUGIXML_NO_EXCEPTIONS 定义禁止在 pugixml 中使用异常。如果您的目标平台不具备异常处理功能,可以使用该选项。


通过 PUGIXML_APIPUGIXML_CLASSPUGIXML_FUNCTION 定义,您可以为 pugixml 类和非成员函数指定自定义属性(即 declspec 或调用约定)。如果没有 PUGIXML_CLASSPUGIXML_FUNCTION 定义,则使用 PUGIXML_API 定义。例如,要指定固定的调用约定,可以将 PUGIXML_FUNCTION 定义为即 __fastcall 。另一个例子是 MSVC 中的 DLL 导入/导出属性(请参阅将 pugixml 作为独立共享库构建)。

 注意

在这个例子中, PUGIXML_API 在几个源文件之间不一致;这是一致性规则的一个例外。


PUGIXML_MEMORY_PAGE_SIZEPUGIXML_MEMORY_OUTPUT_STACKPUGIXML_MEMORY_XPATH_PAGE_SIZE 可用于自定义某些重要大小,以优化特定应用模式的内存使用。详情请参阅内存消耗调整。


PUGIXML_HAS_LONG_LONG 定义使 pugixml 支持 long long 种类型。如果您的平台已知支持 long long 种类型(即支持 C++11 或使用已知编译器的合理现代版本),则会自动启用该定义;如果 pugixml 无法识别您的平台是否支持 long long 种类型,但实际上支持,则可以手动启用该定义。

 2.3.可移植性


pugixml 是用符合标准的 C++ 编写的,在适当的地方使用了一些特定于编译器的变通方法。pugixml 兼容 C++11 标准,但不需要 C++11 支持。每个版本都经过单元测试套件的测试,代码覆盖率超过 99%。


pugixml 可在各种桌面平台(包括 Microsoft Windows、Linux、FreeBSD、Apple MacOSX 和 Sun Solaris)、游戏机(包括 Microsoft Xbox 360、Microsoft Xbox One、Nintendo Wii、Sony Playstation Portable 和 Sony Playstation 3)和移动平台(包括 Android、iOS、BlackBerry、Samsung bada 和 Microsoft Windows CE)上运行。


pugixml 支持多种体系结构,如 x86/x86-64、PowerPC、ARM、MIPS 和 SPARC。一般来说,它可以在任何体系结构上运行,因为它不使用特定于体系结构的代码,也不依赖于未对齐内存访问等功能。


pugixml 可使用任何 C++ 编译器编译;已在 Microsoft Visual C++ 6.0 至 2015 的所有版本、GCC 3.4 至 5.2、Clang 3.2 至 3.7 以及其他各种编译器(例如 Borland C++、Digital Mars C++、Intel C++、Metrowerks CodeWarrior 和 PathScale)上进行了测试。即使在相当高的警告级别下,编写的代码也能避免编译警告。


请注意,某些平台对 C++ 的支持可能非常有限;在某些情况下,您必须使用 PUGIXML_NO_STL 和/或 PUGIXML_NO_EXCEPTIONS 才能顺利编译。这主要适用于老式游戏机和嵌入式系统。


3.文件对象模型


pugixml 以类似 DOM 的方式存储 XML 数据:整个 XML 文档(包括文档结构和元素数据)以树的形式存储在内存中。该树可以从字符流(文件、字符串、C++ I/O 流)中加载,然后使用特殊的 API 或 XPath 表达式遍历。整个树是可变的:节点结构和节点/属性数据都可以随时更改。最后,文档转换的结果可以保存到字符流(文件、C++ I/O 流或自定义传输)中。

 3.1.树形结构


XML 文档用树形数据结构表示。树的根节点是文档本身,对应于 C++ 类型 xml_document。文档有一个或多个子节点,对应于 C++ 类型 xml_node。节点有不同的类型;根据不同的类型,节点可以有一个子节点集合、一个属性集合(对应于 C++ 类型 xml_attribute)和一些附加数据(如名称)。


树节点可以是以下类型之一(它们共同构成枚举 xml_node_type ):


  • 文档节点( node_document )--它是树的根节点,由多个子节点组成。该节点对应于 xml_document 类;请注意,xml_document 是 xml_node 的子类,因此整个节点接口也是可用的。不过,文档节点有几个特殊之处,下面将一一介绍。树中只能有一个文档节点;文档节点没有任何 XML 表示。文档一般只有一个子元素节点(见 document_element() ),但从 XML 片段(见 parse_fragment )解析而来的文档可以有多个子元素节点。


  • 元素/标记节点( node_element )--这是最常见的节点类型,代表 XML 元素。元素节点有一个名称、一个属性集合和一个子节点集合(两者都可能为空)。属性是一个简单的名称/值对。元素节点的 XML 表示示例如下:

    <node attr="value"><child/></node>


    这里有两个元素节点:一个有名称 "node" 、单一属性 "attr" 和单一子节点 "child" ,另一个有名称 "child" ,没有任何属性或子节点。


  • 纯字符数据节点( node_pcdata )表示 XML 中的纯文本。PCDATA 节点有一个值,但没有名称或子节点/属性。请注意,纯文本数据不是元素节点的一部分,而是有自己的节点;一个元素节点可以有多个子 PCDATA 节点。文本节点的 XML 表示示例如下:

    <node> text1 <child/> text2 </node>


    在这里, "node" 元素有三个子节点,其中两个是 PCDATA 节点,值分别为 " text1 "" text2 "


  • 字符数据节点 ( node_cdata ) 表示 XML 中以特殊方式加引号的文本。CDATA 节点与 PCDATA 节点没有区别,只是在 XML 表示法上有所不同--上面的文本示例在使用 CDATA 时看起来是这样的:

    <node> <![CDATA[text1]]> <child/> <![CDATA[text2]]> </node>


    CDATA 节点可以方便地在纯文本中包含非缩写 <&> 字符。CDATA 值不能包含字符序列 ]]> ,因为它用于确定节点内容的结束。


  • 注释节点 ( node_comment ) 表示 XML 中的注释。注释节点有一个值,但没有名称或子节点/属性。注释节点的 XML 表示示例如下:

    <!-- comment text -->


    此处注释节点的值为 "comment text" 。默认情况下,注释节点被视为 XML 标记的非必要部分,不会在 XML 解析过程中加载。您可以使用 parse_comments 标记覆盖这一行为。


  • 处理指令节点( node_pi )表示 XML 中的处理指令(PI)。PI 节点有一个名称和一个可选值,但没有子节点/属性。PI 节点的 XML 表示示例如下:

    <?name value?>


    此处的名称(也称为 PI 目标)为 "name" ,值为 "value" 。默认情况下,PI 节点被视为 XML 标记的非必要部分,不会在 XML 解析过程中加载。您可以使用 parse_pi 标志覆盖这一行为。


  • 声明节点 ( node_declaration ) 表示 XML 中的文档声明。声明节点有一个名称( "xml" )和一个可选的属性集合,但没有值或子节点。一个文档中只能有一个声明节点,而且它应该是最上面的节点(它的父节点应该是文档)。声明节点的 XML 表示示例如下:

    <?xml version="1.0"?>


    此处节点的名称为 "xml" ,单个属性的名称为 "version" ,值为 "1.0" 。默认情况下,声明节点被视为 XML 标记的非必要部分,不会在 XML 解析过程中加载。您可以使用 parse_declaration 标记覆盖这一行为。此外,默认情况下,除非 XML 文档中已经有声明,否则在保存 XML 文档时会输出一个虚声明;您可以使用 format_noo_declaration 标记禁用这一功能。


  • 文档类型声明节点 ( node_doctype ) 表示 XML 中的文档类型声明。文档类型声明节点有一个值,该值与整个文档类型的内容相对应;不会为内部元素(如 <!ENTITY> )创建额外的节点。一个文档中只能有一个文档类型声明节点,而且它应该是最顶层的节点(它的父节点应该是文档)。文档类型声明节点的 XML 表示示例如下:

    <!DOCTYPE greeting [ <!ELEMENT greeting (#PCDATA)> ]>


    此处节点的值为 "greeting [ <!ELEMENT greeting (#PCDATA)> ]" 。默认情况下,文档类型声明节点被视为 XML 标记的非必要部分,在 XML 解析过程中不会加载。您可以使用 parse_doctype 标记覆盖这一行为。


最后,这里有一个完整的 XML 文档示例和相应的树形表示法(samples/tree.xml):

<?xml version="1.0"?>
<mesh name="mesh_root">
    <!-- here is a mesh node -->
    some text
    <![CDATA[someothertext]]>
    some more text
    <node attr1="value1" attr2="value2" />
    <node attr1="value2">
        <innernode/>
    </node>
</mesh>
<?include somedata?>
dom tree

 3.2.C++ 界面

 注意

所有 pugixml 类和函数都位于 pugi 命名空间;您必须使用显式名称限定(即 pugi::xml_node ),或通过 using 指令(即 using pugi::xml_node;using namespace pugi; )访问相关符号。此后,本文档中的所有声明都将省略命名空间;所有代码示例都将使用完全限定的名称。


尽管有多种节点类型,但表示树的 C++ 类只有三个( xml_documentxml_nodexml_attribute );对 xml_node 的某些操作只对特定节点类型有效。这些类的描述如下。


xml_document 是整个文档结构的所有者;它是一个不可复制的类。 xml_document 的接口包括加载函数(见加载文档)、保存函数(见保存文档)和 xml_node 的整个接口,后者允许检查和/或修改文档。请注意,虽然 xml_documentxml_node 的子类,但 xml_node 并非多态类型;继承的出现只是为了简化使用。您也可以使用 document_element 函数来获取文档的直接子元素节点。


默认构造函数 xml_document 将文档初始化为只有一个根节点(文档节点)的树。然后,可以使用树修改函数或加载函数填充数据;所有加载函数都会销毁之前的树,并占用所有内存,从而使该文档的现有节点/属性句柄处于无效状态。如果要销毁上一棵树,可以使用 xml_document::reset 函数;它会销毁树,并用空树或指定文档的副本取而代之。 xml_document 的销毁函数也会销毁树,因此文档对象的生命周期应超过指向树的节点/属性句柄的生命周期。

 注意

虽然从技术上讲,节点/属性句柄可以在其引用的树被销毁时仍然存在,但调用这些句柄的任何成员函数都会导致未定义的行为。因此,建议确保只有在销毁了对文档节点/属性的所有引用后才销毁文档。


xml_node 是文档节点的句柄;它可以指向文档中的任何节点,包括文档节点本身。所有类型的节点都有一个通用接口;可以通过 xml_node::type() 方法查询实际节点类型。请注意, xml_node 只是指向实际节点的句柄,而不是节点本身--你可以有多个句柄指向同一个底层对象。销毁 xml_node 个句柄不会破坏节点,也不会将其从树中删除。 xml_node 句柄的大小等于指针的大小,因此它只不过是指针的一个轻量级封装而已;你可以安全地按值传递或返回6 个对象,而不会产生额外的开销。


有一种类型为 xml_node 的特殊值,称为空节点或空节点(此类节点的类型为 node_null )。它与任何文档中的任何节点都不对应,因此类似于空指针。然而,所有操作都定义在空节点上;一般情况下,这些操作不做任何事情,并返回空节点/属性或空字符串作为其结果(更多详细信息请参阅特定函数的文档)。这对链式调用非常有用;例如,可以像下面这样获取节点的祖节点: node.parent().parent() ; 如果节点为空节点或没有父节点,则第一次调用会返回空节点;第二次调用也会返回空节点从而使错误处理更容易。


xml_attribute 是指向 XML 属性的句柄;它的语义与 xml_node 相同,即可以有多个句柄指向同一个底层对象,并且有一个特殊的空属性值,它会传播到函数结果中。


xml_nodexml_attribute 的默认构造函数都会将它们初始化为空对象。


xml_nodexml_attribute 的行为类似于指针,也就是说,它们可以与同类型的其他对象进行比较,因此可以用作关联容器中的键。指向同一底层对象的所有句柄都是等价的,而指向不同底层对象的任何两个句柄都是不等价的。空句柄只能与空句柄进行比较。关系比较的结果不能通过文件中节点的顺序或任何其他方式可靠地确定。除搜索优化(即关联容器键)外,请勿使用关系比较操作符。


如果要在基于散列的关联容器中使用 xml_node1 个对象作为键,可以使用 hash_value 个成员函数。它们返回的哈希值保证所有指向同一底层对象的句柄都相同。请注意,哈希值并不取决于节点的内容,而只取决于底层结构在内存中的位置,这意味着两次加载同一文档可能会产生不同的哈希值,而复制节点也不会保留哈希值。


最后,句柄可以隐式地转换为布尔对象,这样就可以用以下代码测试节点/属性是否为空: if (node) { …​ }if (!node) { …​ } else { …​ } 。另外,也可以通过调用以下方法来检查给定的 xml_node / xml_attribute 句柄是否为空:

bool xml_attribute::empty() const;
bool xml_node::empty() const;


如果没有文档树,节点和属性是不存在的,因此如果不将它们添加到某个文档中,就无法创建它们。一旦底层节点/属性对象被销毁,这些对象的句柄就会失效。这意味着销毁整个树会使所有节点/属性句柄失效,同时也意味着销毁子树(通过调用 xml_node::remove_child )或删除属性会使相应的句柄失效。我们无法检查句柄的有效性,因此必须通过外部机制来确保其正确性。

 3.3.统一码界面


在配置 pugixml 时,有两种接口和内部表示法可供选择:可以选择 UTF-8(也称 char)接口,也可以选择 UTF-16/32(也称 wchar_t)接口。选择由 PUGIXML_WCHAR_MODE 定义控制;您可以通过 pugiconfig.hpp 或预处理选项设置该定义,详见附加配置选项。如果设置了该定义,则使用 wchar_t 接口;否则(默认情况下)使用 char 接口。确切的宽字符编码假定为 UTF-16 或 UTF-32,并根据 wchar_t 类型的大小确定。

 注意

如果 wchar_t 的大小为 2,pugixml 将采用 UTF-16 编码,而不是 UCS-2,这意味着某些字符将以两个码位表示。


所有处理字符串的树函数都处理 C 风格空尾字符串或所选字符类型的 STL 字符串。例如,节点名称访问器在 char 模式下如下所示:

const char* xml_node::name() const;
bool xml_node::set_name(const char* value);


而在 wchar_t 模式下则是这样:

const wchar_t* xml_node::name() const;
bool xml_node::set_name(const wchar_t* value);


有一种特殊的类型 pugi::char_t ,它被定义为字符类型,取决于库的配置。还有一种类型 pugi::string_t ,它被定义为字符类型的 STL 字符串;在 char 模式下对应于 std::string ,在 wchar_t 模式下对应于 std::wstring


除了接口之外,内部实现也会改变,将 XML 数据存储为 pugi::char_t ;这意味着这两种模式具有不同的内存使用特性--一般来说,UTF-8 模式更节省内存和性能,尤其是当 sizeof(wchar_t) 为 4 时。在加载文档时自动转换为 pugi::char_t ,在保存文档时自动转换为 pugi::char_t ,这也会带来轻微的性能损失。不过,一般建议是根据使用情况来选择字符模式,例如,如果 UTF-8 处理起来不方便,而且大部分 XML 数据都是非 ASCII 格式,那么 wchar_t 模式可能是更好的选择。


在某些情况下,您需要在 UTF-8 和 wchar_t 编码之间转换字符串数据:

std::string as_utf8(const wchar_t* str);
std::wstring as_wide(const char* str);


这两个函数都接受一个空尾字符串作为参数 str ,并返回转换后的字符串。 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);
 注意


本文档中的大多数示例都假定使用 char 接口,因此不会使用 PUGIXML_WCHAR_MODE 进行编译。这样做是为了简化文档;通常情况下,您需要做的唯一改动就是传递0 个字符串字面量,即用 "PUGIXML_WCHAR_MODE "代替 "PUGIXML_WCHAR_MODE"。

xml_node node = doc.child("bookstore").find_child_by_attribute("book", "id", "12345");


你必须使用

xml_node node = doc.child(L"bookstore").find_child_by_attribute(L"book", L"id", L"12345");


3.4.线程安全保证


pugixml 中几乎所有函数都有以下线程安全保证:


  • 从多个线程调用自由(非成员)函数是安全的


  • 对同一棵树进行并发只读访问是安全的(所有常量成员函数都不会修改该树)


  • 如果每次对单棵树只有一次读取或写入访问,那么执行并发读取/写入访问是安全的


同时修改和遍历一棵树需要同步,例如通过读写器锁。修改包括更改文档结构和更改单个节点/属性数据,即更改名称/值。


唯一的例外是 set_memory_management_functions;它修改全局变量,因此不是线程安全的。它的使用策略有更多限制,请参阅自定义内存分配/去分配函数。


3.5.例外保证


除了 XPath 之外,pugixml 本身不会抛出任何异常。此外,大多数 pugixml 函数都有不抛出异常的保证。


这不适用于对 STL 字符串或 IOstreams 进行操作的函数;此类函数要么有强保证(对字符串进行操作的函数),要么有基本保证(对流进行操作的函数)。此外,调用用户自定义回调(即 xml_node::traverse 或 xml_node::find_node)的函数除了回调提供的异常保证外,不提供任何异常保证。


如果未使用 PUGIXML_NO_EXCEPTIONS 定义禁用异常处理,XPath 函数可能会在解析错误时抛出 xpath_exception;在内存不足的情况下,XPath 函数也可能抛出 std::bad_alloc 。尽管如此,XPath 函数还是提供了强有力的异常保证。

 3.6.内存管理


pugixml 以大块方式请求文档存储所需的内存,并在这些大块中分配文档数据。本节将讨论用于分块分配和内部内存管理的替换函数。


3.6.1.自定义内存分配/去分配功能


树结构、树数据和 XPath 对象的所有内存都是通过全局指定函数(默认为 malloc/free)分配的。你可以使用 set_memory_management 函数设置自己的分配函数。函数接口与 malloc/free 相同:

typedef void* (*allocation_function)(size_t size);
typedef void (*deallocation_function)(void* ptr);


您可以使用以下访问函数来更改或获取当前的内存管理功能:

void set_memory_management_functions(allocation_function allocate, deallocation_function deallocate);
allocation_function get_memory_allocation_function();
deallocation_function get_memory_deallocation_function();


调用分配函数时,参数是内存大小(以字节为单位),函数应返回一个内存块指针,该内存块的对齐方式应适合存储原始类型(通常最大对齐方式为 void*double 类型即可),内存大小应大于或等于请求的内存大小。如果分配失败,函数必须返回空指针或抛出异常。


取消分配函数是用分配函数调用返回的指针调用的,绝不会用空指针调用。如果内存管理函数不是线程安全的,那么库的线程安全就得不到保证。


这是一个自定义内存管理的简单示例(samples/custom_memory_management.cpp):

void* custom_allocate(size_t size)
{
    return new (std::nothrow) char[size];
}

void custom_deallocate(void* ptr)
{
    delete[] static_cast<char*>(ptr);
}
pugi::set_memory_management_functions(custom_allocate, custom_deallocate);


在设置新的内存管理函数时,必须注意确保没有实时的 pugixml 对象。否则,当对象被销毁时,新的取消分配函数将调用旧的分配函数获得的内存,从而导致未定义的行为。


3.6.2.内存消耗调整


pugixml 中有几个重要的缓冲优化依赖于预定义的常量。这些常量的默认值是根据常见的使用模式调整的;对于某些应用程序,更改这些常量可能会改善内存消耗或提高性能。除非这些常量的默认值会导致明显的问题,否则不建议更改这些常量。


这些常量可以通过配置定义进行调整,如附加配置选项中所述;建议将它们设置为 pugiconfig.hpp


  • PUGIXML_MEMORY_PAGE_SIZE 控制文档内存分配的页面大小。节点/属性对象的内存按指定大小的页面分配。默认大小为 32 Kb;对于某些应用程序(如堆空间很小的嵌入式系统或在内存中保存大量 XML 文档的应用程序)来说,这个大小太大了。建议最小大小为 1 Kb。


  • PUGIXML_MEMORY_OUTPUT_STACK 控制输出节点所需的累积堆栈空间。出于性能考虑,任何输出操作(例如将子树保存到文件)都会使用内部缓冲方案。默认值为 10 Kb;如果使用堆栈空间很小的线程输出节点,减少该值可以防止堆栈溢出。建议最小使用 1 Kb。


  • PUGIXML_MEMORY_XPATH_PAGE_SIZE 控制 XPath 内存分配的页面大小。XPath 查询对象的内存以及 XPath 评估的内部内存以指定大小的页面分配。默认大小为 4 Kb;如果有大量常驻 XPath 查询对象,可能需要减小页面大小以改善内存消耗。建议最小大小为 256 字节。


3.6.3.记录内存管理内部结构


使用默认构造函数构造文档对象不会导致任何分配;文档节点存储在 xml_document 对象内。


从文件/缓冲区加载文档时,除非使用了就地加载函数(请参阅从内存加载文档),否则会创建一个完整的字符流副本;节点和属性的所有名称/值都会分配到该缓冲区中。该缓冲区通过一次大分配进行分配,只有在文档内存被回收时(即 xml_document 对象被销毁或在同一对象中加载另一个文档时)才会被释放。此外,从文件或数据流加载时,如果需要进行编码转换,可能会执行额外的大分配;分配一个临时缓冲区,并在加载函数返回前释放。


所有附加内存,如文档结构(节点/属性对象)内存和节点/属性名称/值内存,都是以 32 Kb 左右的页面分配的;实际对象是在页面内分配的,采用的内存管理方案经过优化,可以快速分配/重新分配许多小对象。由于该方案的特殊性,只有当页面中的所有对象都被销毁时,页面才会被销毁;而且,一般情况下,销毁一个对象并不意味着后续对象的创建将重复使用相同的内存。这意味着有可能设计出一种使用方案,导致内存使用率高于预期;其中一个例子是添加大量节点,然后删除所有偶数节点;在此过程中没有回收任何一页内存。然而,这是一个专门为产生不令人满意的行为而设计的例子;在所有实际使用场景中,内存消耗都低于通用分配器,因为分配元数据的大小非常小。

 3.6.4.紧凑模式


默认情况下,节点和属性都经过优化,以提高访问效率。这可能导致它们占用大量内存--对于节点较多、内容(短属性值/节点文本)不多的文档,根据指针大小的不同,文档结构占用的内存可能明显多于文档本身(例如,在 64 位平台上,在 UTF-8 模式下,文件大小为 2.1 Mb 的重标记文档的文档缓冲区占用 2.1 Mb,文档结构占用 8.3 Mb)。


如果您正在处理大型文档,或者您的平台内存有限,并且您愿意牺牲一点性能来换取内存,那么您可以在编译 pugixml 时使用 PUGIXML_COMPACT 定义,这将激活紧凑模式。紧凑模式使用不同的文档结构表示法,假定节点和属性之间的引用具有局部性,以优化内存使用。因此,节点/属性对象的体积会明显缩小;通常情况下,大多数文档中的大多数对象都不需要额外的存储空间,但在最坏的情况下,如果关于引用位置的假设不成立,就需要分配额外的内存来存储所需的额外数据。


紧凑型存储支持所有现有操作(包括树形修改),其摊销复杂度相同(也就是说,所有基本文档操作的平均复杂度仍为 O(1))。这些操作的速度会稍慢一些;除非您的处理过程受内存限制,否则处理时间通常会缩短 10%-50%。


在 32 位架构上,紧凑模式下的文档结构通常会减少约 2.5 倍;在 64 位架构上,这一比例约为 5 倍。因此,对于标记繁重的大型文档来说,紧凑模式可以在完全使用内存处理多GB文档与需要交换到磁盘之间做出选择。即使文档可以放入内存,紧凑型存储也可以通过占用更少空间和减少缓存/TLB 错失来更有效地使用 CPU 缓存。

 4.装入文件


pugixml 提供了多个函数,用于从文件、C++ iostreams、内存缓冲区等不同位置加载 XML 数据。所有函数都使用极快的非验证解析器。该解析器并不完全符合 W3C 标准--它可以加载任何有效的 XML 文档,但不执行某些良好格式检查。虽然我们已经做出了相当大的努力来拒绝无效的 XML 文档,但出于性能方面的考虑,某些验证并没有执行。此外,某些 XML 转换(如 EOL 处理或属性值规范化)会影响解析速度,因此可以禁用。不过,对于绝大多数 XML 文档来说,不同的解析选项在性能上没有差别。解析选项还可以控制是否解析某些 XML 节点;更多信息请参见解析选项。


pugixml 支持所有流行的 Unicode 编码(UTF-8、UTF-16(大内码和小内码)、UTF-32(大内码和小内码);由于 UCS-2 是 UTF-16 的严格子集,因此自然也支持 UCS-2)以及一些非 Unicode 编码(Latin-1),并自动处理所有编码转换。除非指定了明确的编码,否则加载函数会根据源 XML 数据执行自动编码检测,因此在大多数情况下,您无需指定文档编码。编码转换将在编码中详细介绍。


4.1.从文件加载文件


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);


这些函数的第一个参数是文件路径,另外还有两个可选参数,分别指定解析选项(请参阅解析选项)和输入数据编码(请参阅编码)。路径具有目标操作系统格式,因此可以是相对路径,也可以是绝对路径;路径应具有目标系统的分隔符;如果目标文件系统区分大小写,路径应具有准确的大小写等。


在第一个函数(接受 const char* path )中,文件路径被原封不动地传递给系统文件打开函数;第二个函数要么使用运行时库提供的特殊文件打开函数,要么将路径转换为 UTF-8 并使用系统文件打开函数。


load_file 会销毁现有的文档树,然后尝试从指定文件中加载新的文档树。操作结果将以 xml_parse_result 对象形式返回;该对象包含操作状态和相关信息(即如果解析失败,输入文件中最后一个成功解析的位置)。有关错误处理的详细信息,请参阅处理解析错误。


这是一个从文件(samples/load_file.cpp)加载 XML 文档的示例:

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;


4.2.从存储器加载文件


有时,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 数据指针 contents 和数据大小(字节)表示的缓冲区。此外,还有两个可选参数,分别指定解析选项(参见解析选项)和输入数据编码(参见编码)。缓冲区不必以 0 结尾。


load_buffer 函数使用不可变缓冲区工作--它不会修改缓冲区。由于这一限制,它必须创建一个私有缓冲区,并在解析前将 XML 数据复制到缓冲区(必要时进行编码转换)。这种复制操作会对性能造成影响,因此需要使用就地函数 load_buffer_inplaceload_buffer_inplace_own 将文档数据存储到缓冲区中,并在此过程中对其进行修改。如果使用置换函数,为了使文档保持有效,必须确保缓冲区的生命周期超过树的生命周期。此外, load_buffer_inplace 不会承担缓冲区的所有权,所以你必须自己销毁它; load_buffer_inplace_own 会承担缓冲区的所有权,并在不需要时将其销毁。这意味着如果使用 load_buffer_inplace_own ,就必须使用 pugixml 分配函数分配内存(可通过 get_memory_allocation_function 获取)。


从性能/内存的角度来看,最好的方法是使用 load_buffer_inplace_own 加载文档;该函数可以最大限度地控制 XML 数据的缓冲区,因此可以避免多余的拷贝,并在解析时减少内存的峰值使用。如果必须从内存中加载文档,且性能至关重要,建议使用此函数。


还有一个简单的辅助函数,可用于从空端字符串加载 XML 文档:

xml_parse_result xml_document::load_string(const char_t* contents, unsigned int options = parse_default);


它相当于调用 load_buffer ,而 size 则是 strlen(contents)wcslen(contents) * sizeof(wchar_t) ,具体取决于字符类型。该函数假定输入数据为本地编码,因此不会进行任何编码转换。一般来说,该函数适用于从字符串文字加载小型文档,但与缓冲区加载函数相比,开销更大,功能更少。


这是一个使用不同函数从内存加载 XML 文档的示例(samples/load_memory.cpp):

const char source[] = "<mesh name='sphere'><bounds>0 0 1 1</bounds></mesh>";
size_t size = sizeof(source);
// You can use load_buffer to load document from immutable memory block:
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_string("<mesh name='sphere'><bounds>0 0 1 1</bounds></mesh>");


4.3.从 C++ IOstreams 中加载文件


为了增强互操作性,pugixml 提供了从任何实现 C++ std::istream 接口的对象加载文档的函数。这允许你从任何标准 C++ 流(如文件流)或任何第三方兼容实现(如 Boost Iostreams)中加载文档。有两个函数,一个适用于窄字符流,另一个适用于宽字符流:

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 带有1 个参数,从当前读取位置到末尾加载数据流中的文档,将数据流内容视为指定编码的字节流(必要时进行编码自动检测)。因此,在已打开的 std::ifstream 对象上调用 xml_document::load 相当于调用 xml_document::load_file


loadstd::wstream 参数将流内容视为宽字符流(编码始终为 encoding_wchar)。因此,在宽字符流中使用 load 时,需要进行仔细的(通常是特定平台的)流设置(即使用 imbue 函数)。一般情况下,我们不鼓励使用宽字符流,但宽字符流提供了加载非 Unicode 编码文档的能力,例如,如果设置了正确的本地语言,就可以加载 Shift-JIS 编码的数据。


这是一个使用流(samples/load_stream.cpp)从文件加载 XML 文档的简单示例;如需了解涉及宽流和本地的更复杂示例,请阅读示例代码:

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


4.4 处理解析错误


所有文档加载函数都通过 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 只由1 个函数返回,表示文件无法打开。


  • status_io_error 表示读取文件/数据流时发生了 I/O 错误。


  • status_out_of_memory 表示在分配过程中内存不足;解析过程中的任何分配失败都会导致此错误。


  • status_internal_error 表示出现了严重错误;目前不会出现这种错误


  • status_unrecognized_tag 表示由于标签名称为空或名称以错误字符(如 # )开头而导致解析停止。


  • status_bad_pi 表示由于文件声明/处理指令不正确而停止解析


  • status_bad_comment , status_bad_cdata , status_bad_doctypestatus_bad_pcdata 表示由于相应类型的构造无效而停止解析


  • status_bad_start_element 表示解析停止,因为起始标记要么没有结尾 > 符号,要么包含某些不正确的符号


  • status_bad_attribute 表示解析停止,因为存在不正确的属性,例如属性没有值或属性值没有加引号(注意 <node attr=1> 在 XML 中是不正确的)。


  • status_bad_end_element 表示由于结束标记的语法不正确(即标记名和 > 之间有额外的非空格符号)而停止解析


  • status_end_element_mismatch 表示解析停止,因为关闭的标记与打开的标记不匹配(即 <node></nedo> ),或者因为某些标记根本没有关闭


  • status_no_document_element 表示在解析过程中未发现元素节点;这通常表示文档为空或无效


description() 成员函数可用于将解析状态转换为字符串;返回的信息始终为英文,因此如果需要本地化字符串,则必须编写自己的函数。但请注意, description() 函数返回的确切信息可能会因版本不同而有所变化,因此任何复杂的状态处理都应基于 status 的值。请注意,即使在 PUGIXML_WCHAR_MODE 中, description() 也会返回4 个字符串;您必须调用 as_wide 才能获得6 个字符串。


如果由于源数据不是有效的 XML 而导致解析失败,生成的树并不会被破坏--尽管加载函数返回错误信息,但仍可使用已成功解析的树的一部分。显然,最后一个元素的名称/值可能会出乎意料;例如,如果属性值没有以必要的引号结束(如 <node attr="value>some data</node> 示例),属性 attr 的值将包含字符串 value>some data</node> .


除了状态代码外,解析结果还有一个 offset 成员,其中包含最后一个成功解析字符的偏移量(如果解析因源数据中的错误而失败);否则 offset 为 0。 出于提高解析效率的考虑,pugixml 在解析过程中不跟踪当前行;该偏移量以 pugi::char_t 为单位(字符模式为字节,宽字符模式为宽字符)。许多文本编辑器都支持 "转到位置"(Go To Position)功能--你可以用它来定位准确的错误位置。另外,如果从内存中加载文档,也可以显示错误块和错误描述(见下面的示例代码)。

 注意

偏移量是在本地编码的 XML 缓冲区中计算的;如果在解析过程中进行了编码转换,偏移量就不能用于可靠地跟踪错误位置。


解析结果还有一个 encoding 成员,可用于检查源数据编码是否猜对。它等于解析过程中使用的精确编码(即精确的字节序);更多信息请参阅编码。


解析结果对象可以隐式转换为 bool ;如果不想彻底处理解析错误,可以直接检查加载函数的返回值,将其视为 bool : if (doc.load_file("file.xml")) { …​ } else { …​ }


这是一个处理加载错误的示例 ( samples/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";
}

 4.5.解析选项


所有文档加载函数都接受可选参数 options 。这是一个位掩码,用于自定义解析过程:您可以选择解析的节点类型以及对 XML 文本执行的各种转换。禁用某些转换可以提高某些文档的解析性能;但是,所有转换的代码都经过了很好的优化,因此大多数文档都不会获得任何性能上的好处。根据经验,只有当您想获取文档中默认排除的某些节点(如声明或注释节点)时,才修改解析标志。

 注意

您应该使用通常的位运算来操作位掩码:启用一个标志,使用 mask | flag ;禁用一个标志,使用 mask & ~flag


这些标志控制着生成的树内容:


  • parse_declaration 表示是否将 XML 文档声明(具有 node_declaration 类型的节点)放入 DOM 树。如果该标记为关闭,则不会将其放入树中,但仍会对其进行解析并检查其正确性。该标记默认为关闭。


  • parse_doctype 决定是否将 XML 文档类型声明(具有 node_doctype 类型的节点)放入 DOM 树。如果该标记为关闭,则不会将其放入树中,但仍会对其进行解析并检查其正确性。该标记默认为关闭。


  • parse_pi 表示是否将处理指令(node_pi 类型的节点)放入 DOM 树。如果该标记为关闭,则不会将其放入树中,但仍会对其进行解析并检查其正确性。请注意, <?xml …​?> (文档声明)不被视为 PI。该标记默认为关闭。


  • parse_comments 表示是否将注释(node_comment 类型的节点)放入 DOM 树。如果该标记为关闭,则不会将其放入树中,但仍会对其进行解析并检查其正确性。该标记默认为关闭。


  • parse_cdata 表示是否将 CDATA 部分(节点类型为 node_cdata)放入 DOM 树。如果该标记为关闭,则不会将其放入树中,但仍会对其进行解析并检查其正确性。该标记默认为打开。


  • parse_trim_pcdata 决定是否从 PCDATA 节点中删除前导和尾部空白字符。虽然对于某些应用程序来说,前导/尾部空白字符很重要,但通常应用程序只关心非空白内容,因此在解析过程中更容易从文本中删除空白字符。该标记默认为关闭。


  • parse_ws_pcdata 决定是否将仅由空白字符组成的 PCDATA 节点(node_pcdata 类型的节点)放入 DOM 树中。通常,只包含空白字符的数据对应用程序来说并不重要,而分配和存储此类节点的成本(内存和速度方面)可能会很高。例如,在解析 XML 字符串 <node> <a/> </node> 后,当设置 parse_ws_pcdata 时, <node> 元素将有三个子元素(子元素类型为 node_pcdata、值为 " " ,子元素类型为 node_element、名称为 "a" ,另一个子元素类型为 node_pcdata、值为 " " ),而当不设置 parse_ws_pcdata 时,只有一个子元素。该标记默认为关闭。


  • parse_ws_pcdata_single 决定是否将没有同级节点的仅留白 PCDATA 节点放入 DOM 树中。在某些情况下,应用程序需要解析节点的纯空白内容(即 <node> </node> ),但对其他地方的空白标记不感兴趣。在这种情况下,可以使用 parse_ws_pcdata 标志,但这会导致过多的分配,并使文档处理复杂化;使用该标志可以避免这种情况。例如,在设置了 parse_ws_pcdata_single 标志的情况下解析 XML 字符串 <node> <a> </a> </node> 后, <node> 元素将有一个子元素 <a><a> 元素将有一个子元素,其类型为 node_pcdata,值为 " " 。如果已启用 parse_ws_pcdata,则此标记无效。该标记默认为关闭。


  • parse_embed_pcdata 决定 PCDATA 内容是否保存为元素值。通常情况下,元素节点只有名称而没有值;如果 PCDATA 是元素节点的第一个子节点,则此标记会强制解析器将内容保存为值(否则,PCDATA 节点将照常创建)。这可以大大减少有许多 PCDATA 节点的文档所需的内存。要检索数据,可以在元素节点上使用 xml_node::value() 或任何更高级的函数,如 child_valuetext 。该标记默认为关闭。由于该标记会极大地改变 DOM 结构,因此只建议在内存受限的环境中解析有许多 PCDATA 节点的文档时使用。该标记默认为关闭。


  • parse_merge_pcdata 决定当 PCDATA 节点之间没有中间节点时,是否将 PCDATA 内容与前一个 PCDATA 节点合并。如果 PCDATA 节点之间包含 CDATA 部分、PI 节点或注释,且未设置 parse_cdata , parse_pi , parse_comments 标志,则 PCDATA 节点的内容将与前一个节点合并。该标记默认为关闭。


  • parse_fragment 决定文档是否应被视为有效 XML 的片段。将文档作为片段解析后,顶层 PCDATA 内容(即不在节点内的文本)会被添加到树中,此外,不含元素节点的文档也会被视为有效文档,并允许使用多个顶层元素节点(目前,该标志关闭时也允许使用多个顶层元素节点,但不应依赖该行为)。该标记默认为关闭。

 注意

使用带 parse_fragment 标志的就地解析(load_buffer_inplace)可能会导致丢失缓冲区的最后一个字符(如果它是 PCDATA 的一部分)。由于 PCDATA 值是空端字符串,解决这个问题的唯一方法是提供一个空端缓冲区作为 load_buffer_inplace 的输入,即 doc.load_buffer_inplace("test\0", 5, pugi::parse_default | pugi::parse_fragment) .


这些标志控制着树元素内容的转换:


  • parse_escapes 决定在解析过程中是否扩展字符和实体引用。字符引用的形式为 &#…​;&#x…​;…​ 是十进制( &#…​; )或十六进制( &#x…​; )形式的 Unicode 字符数字表示),实体引用的形式为 &lt;&gt;&amp;&apos;&quot; (注意,由于 pugixml 不处理 DTD,因此只允许使用预定义的实体)。如果字符/实体引用无法扩展,则保持原样,以便稍后进行额外处理。对属性值和 PCDATA 内容执行引用扩展。该标记默认为打开。


  • parse_eol 表示是否对输入数据(即注释内容、PCDATA/CDATA 内容和属性值)执行 EOL 处理(即用单个 \n 字符替换序列 \r\n ,用 \n 字符替换所有独立的 \r 字符)。该标记默认为打开。


  • parse_wconv_attribute 表示是否对所有属性执行属性值规范化。这意味着空白字符(换行符、制表符和空格)会被空格( ' ' )替换。如果设置了 parse_eol,新行字符将始终被视为新行字符,即 \r\n 会被转换为单个空格。该标记默认为打开。


  • parse_wnorm_attribute 表示是否要对所有属性执行扩展属性值规范化。这意味着在属性值规范化后,就像设置了 parse_wconv_attribute 一样,前导和尾部的空格字符会被删除,所有空格字符序列都会被单个空格字符替换。如果该标记为开启,则 parse_wconv_attribute 无效。该标记默认为关闭。

 注意

parse_wconv_attribute 选项对声明为 CDATA 的属性执行 W3C 规范要求的转换;parse_wnorm_attribute 执行 NMTOKENS 属性要求的转换。在没有文档类型声明的情况下,所有属性的行为都应与声明为 CDATA 的属性相同,因此 parse_wconv_attribute 是默认选项。


此外,还有三个预定义的选项屏蔽:


  • parse_minimal 则关闭所有选项。该选项掩码意味着 pugixml 不会在生成的树中添加声明节点、文档类型声明节点、PI 节点、CDATA 部分和注释,也不会对输入数据进行任何转换,因此理论上它是最快的模式。不过,如上所述,实际上 parse_default 通常也同样快。


  • parse_default 是默认的标志集,即所有选项都设置为默认值。它包括解析 CDATA 部分(不解析注释/PI)、执行字符和实体引用扩展、在属性值中用空格替换空白字符以及执行 EOL 处理。需要注意的是,由于性能原因,只包含空白字符的 PCDATA 部分不进行解析(默认情况下)。


  • parse_full 是一组标志,用于将所有类型的节点添加到生成的树中,并对输入数据执行默认转换。它包括解析 CDATA 部分、注释、PI 节点、文档声明节点和文档类型声明节点,执行字符和实体引用扩展,在属性值中用空格替换空白字符,以及执行 EOL 处理。需要注意的是,该模式下不解析仅由空白字符组成的 PCDATA 部分。


这是一个使用不同解析选项的示例 ( samples/load_options.cpp):

const char* source = "<!--comment--><node>&lt;</node>";

// Parsing with default options; note that comment node is not added to the tree, and entity reference &lt; is expanded
doc.load_string(source);
std::cout << "First node value: [" << doc.first_child().value() << "], node child value: [" << doc.child_value("node") << "]\n";

// Parsing with additional parse_comments option; comment node is now added to the tree
doc.load_string(source, pugi::parse_default | pugi::parse_comments);
std::cout << "First node value: [" << doc.first_child().value() << "], node child value: [" << doc.child_value("node") << "]\n";

// Parsing with additional parse_comments option and without the (default) parse_escapes option; &lt; is not expanded
doc.load_string(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";

// Parsing with minimal option mask; comment node is not added to the tree, and &lt; is not expanded
doc.load_string(source, pugi::parse_minimal);
std::cout << "First node value: [" << doc.first_child().value() << "], node child value: [" << doc.child_value("node") << "]\n";

 4.6.编码


pugixml 支持所有流行的 Unicode 编码(UTF-8、UTF-16(大内码和小内码)、UTF-32(大内码和小内码);由于 UCS-2 是 UTF-16 的严格子集,因此自然也支持 UCS-2)以及一些非 Unicode 编码(Latin-1),并处理所有编码转换。大多数加载函数接受可选参数 encoding 。这是一个枚举类型 xml_encoding 的值,可以有以下值:


  • encoding_auto 表示 pugixml 将尝试根据源 XML 数据猜测编码。该算法是 XML 建议附录 F 中提出的算法的修改版。它首先会尝试查找其中一种支持编码的字节序标记;如果找不到,它就会检查输入数据的前几个字节是否看起来像 <<? 在 UTF-16 或 UTF-32 变体中的表示形式;如果也失败,则假定编码为 UTF-8 或非 Unicode 编码之一--为了做出最终决定,算法会尝试解析 XML 文档声明的 encoding 属性,如果文档声明不存在或未指定支持的编码,则最终返回 UTF-8。


  • encoding_utf8 对应 Unicode 标准中定义的 UTF-8 编码;长度等于 5 或 6 的 UTF-8 序列不符合标准,将被拒绝。


  • encoding_utf16_le 对应 Unicode 标准中定义的 little-endian UTF-16 编码;支持代理对。


  • encoding_utf16_be 对应 Unicode 标准中定义的 big-endian UTF-16 编码;支持代理对。


  • encoding_utf16 对应 Unicode 标准中定义的 UTF-16 编码;假定字节序为目标平台的字节序。


  • encoding_utf32_le 对应 Unicode 标准中定义的小双位 UTF-32 编码。


  • encoding_utf32_be 对应 Unicode 标准中定义的 big-endian UTF-32 编码。


  • encoding_utf32 对应 Unicode 标准中定义的 UTF-32 编码;假定字节序为目标平台的字节序。


  • encoding_wchar 对应 wchar_t 类型的编码;它与 encoding_utf16encoding_utf32 意义相同,取决于 wchar_t 的大小。


  • encoding_latin1 对应 ISO-8859-1 编码(也称为 Latin-1)。


对于所有格式良好的 XML 文档(因为它们以文档声明开头)和所有其他以 < 开头的 XML 文档,用于 encoding_auto 的算法可正确检测任何受支持的 Unicode 编码;如果您的 XML 文档不是以 < 开头,且编码不同于 UTF-8,请使用特定编码。

 注意

当前的 Unicode 转换行为是在转换过程中跳过所有无效的 UTF 序列。此外,如果不进行编码转换,无效序列也不会被移除,因此节点/属性内容中的无效序列将保持原样。


4.7.符合 W3C 规范


pugixml 并不完全符合 W3C 标准--它可以加载任何有效的 XML 文档,但不执行某些良好格式检查。虽然 pugixml 已做出相当大的努力来拒绝无效的 XML 文档,但出于性能方面的考虑,某些验证并没有执行。


在处理有效的 XML 文档时,只有一种不符合规范的行为:pugixml 不会使用文档类型声明中提供的信息进行解析。这意味着 DOCTYPE 中声明的实体不会被展开,所有属性/PCDATA 值始终以统一的方式处理,仅取决于解析选项。


至于拒绝无效的 XML 文档,有许多与 W3C 规范不兼容的地方,包括


  • 同一节点的多个属性可以有相同的名称。


  • 标签和属性名称未完全验证是否由允许的字符组成,因此不会拒绝某些无效标签


  • 不拒绝包含 < 的属性值。


  • 无效的实体/字符引用不会被拒绝,而是保持原样。


  • 注释值可包含 -- .


  • XML 数据不需要从文档声明开始;此外,文档声明可以出现在注释和其他节点之后。


  • 在某些情况下,无效的文档类型声明会被静默忽略。


  • 不执行 Unicode 验证,因此不会拒绝无效的 UTF 序列。


  • 文档可包含多个顶级元素节点。


5.获取文件数据


pugixml 具有一个广泛的接口,用于从文档中获取各种类型的数据和遍历文档。除 XPath 相关函数外,本节提供了所有不修改树的函数的文档;有关 XPath 的参考,请参见 XPath。正如 C++ 接口中所讨论的,有两种类型的树数据句柄--xml_node 和 xml_attribute。这些句柄有特殊的空(空)值,可在各种函数中传播,因此有助于编写更简洁的代码;详情请参见本说明。本节的文档将明确说明在输入为空的情况下所有函数的结果。


基本遍历功能


文档的内部表示形式是一棵树,每个节点都有一个子节点列表(子节点的顺序与 XML 表示形式中的顺序一致),此外,元素节点还有一个属性列表,也是有序的。为了让你从树中的一个节点到另一个节点,我们提供了几个函数。这些函数与内部表示法大致对应,因此通常是其他遍历方法的基础模块(即 XPath 遍历基于这些函数)。

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 返回节点的父节点;除文档外,所有非空节点的父节点都是非空的。 first_childlast_child 分别返回节点的第一个和最后一个子节点;请注意,只有文档节点和元素节点可以有非空的子节点列表。如果节点没有子节点,两个函数都返回空节点。 next_siblingprevious_sibling 分别返回子节点列表中紧靠该节点右侧/左侧的节点--例如,在 <a/><b/><c/> 中,调用 next_sibling 获取指向 <b/> 的句柄会得到指向 <c/> 的句柄,而调用 previous_sibling 则会得到指向 <a/> 的句柄。如果节点没有下一个/上一个兄弟节点(如果它分别是列表中的最后一个/第一个节点),函数将返回空节点。 first_attribute , last_attribute , next_attributeprevious_attribute 函数的行为与相应的子节点函数类似,允许以相同的方式遍历属性列表。

 注意

由于内存消耗的原因,属性没有与其父节点的链接。因此没有 xml_attribute::parent() 功能。


在 null 句柄上调用上述任何函数都会产生一个 null 句柄,即 node.first_child().next_sibling() 返回 node 的第二个子节点,如果 node 为空、没有子节点或只有一个子节点,则返回 null 句柄。


使用这些函数,可以遍历所有子节点并显示所有属性,就像这样(samples/traverse_base.cpp):

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;
}


5.1.获取节点数据


除了结构信息(父节点、子节点、属性)外,节点还可以有名称和值,两者都是字符串。node_document 节点没有名称或值,node_element 和 node_declaration 节点始终有名称但没有值,node_pcdata、node_cdata、node_comment 和 node_doctype 节点始终有值但没有名称(可能为空),node_pi 节点始终有名称和值(同样,值可能为空)。要获取节点的名称或值,可以使用以下函数:

const char_t* xml_node::name() const;
const char_t* xml_node::value() const;


如果节点没有名称或值,或者节点句柄为空,这两个函数都会返回空字符串,而不会返回空指针。


将数据存储为某个节点(即 <node><description>This is a node</description></node> )的文本内容很常见。在这种情况下, <description> 节点没有值,但有一个值为 "This is a node" 的 node_pcdata 类型的子节点。 pugixml 提供了几个辅助函数来解析此类数据:

const char_t* xml_node::child_value() const;
const char_t* xml_node::child_value(const char_t* name) const;
xml_text xml_node::text() const;


child_value() 返回 node_pcdata 或 node_cdata 类型的第一个子节点的值; child_value(name)child(name).child_value() 的简单封装。在上例中,调用 node.child_value("description")description.child_value() 都会产生字符串 "This is a node" 。如果没有相关类型的子节点,或者句柄为空, child_value 功能将返回空字符串。


text() 返回一个特殊对象,该对象可用于在更复杂的情况下处理 PCDATA 内容,而不仅仅是检索值;有关该对象的描述,请参阅处理文本内容章节。


下一节末将举例说明如何使用其中的一些功能。


5.2.获取属性数据


所有属性都有 name 和 value,两者都是字符串(value 可以为空)。有两个相应的访问器,如 xml_node

const char_t* xml_attribute::name() const;
const char_t* xml_attribute::value() const;


如果属性句柄为空,这两个函数都会返回空字符串,而不会返回空指针。


如果在属性句柄为空的情况下需要一个非空字符串(例如,需要从 XML 属性中获取选项值,但如果没有指定,则需要默认为 "sorted" 而不是 "" ),可以使用 as_string 访问器:

const char_t* xml_attribute::as_string(const char_t* def = "") const;


如果属性句柄为空,则返回 def 参数。如果不指定参数,则函数等价于 value()


在许多情况下,属性值的类型并不是字符串,例如,一个属性可能总是包含应被视为整数的值,尽管在 XML 中它们被表示为字符串:

int xml_attribute::as_int(int def = 0) const;
unsigned int xml_attribute::as_uint(unsigned int def = 0) const;
double xml_attribute::as_double(double def = 0) const;
float xml_attribute::as_float(float def = 0) const;
bool xml_attribute::as_bool(bool def = false) const;
long long xml_attribute::as_llong(long long def = 0) const;
unsigned long long xml_attribute::as_ullong(unsigned long long def = 0) const;


as_int , as_uint , as_llong , as_ullong , as_doubleas_float 将属性值转换为数字。如果属性句柄为空,将返回6 个参数(默认为 0)。否则,所有前导空白字符将被截断,剩余字符串将被解析为十进制或十六进制形式的整数(适用于 as_int , as_uint , as_llongas_ullong ;如果数字前缀为 0x0X ,则使用十六进制格式),或十进制或科学数形式的浮点数( as_doubleas_float )。


如果输入字符串包含非数字字符序列或超出目标数字范围的数字,结果将是未定义的。

 注意

数字转换函数取决于用 setlocale 设置的当前 C 语言区域设置,因此如果区域设置不同于 "C" ,可能会返回意外结果。


as_bool 将属性值转换为布尔值的方法如下:如果属性句柄为空,则返回1 个参数(默认为 false )。如果属性值为空,则返回 false 。否则,如果第一个字符是 '1', 't', 'T', 'y', 'Y' 中的一个,则返回 true 。这意味着 "true""yes" 这样的字符串会被识别为 true ,而 "false""no" 这样的字符串会被识别为 false 。要实现更复杂的匹配,您需要编写自己的函数。

 注意

只有当您的平台可靠地支持 long long 类型(包括字符串转换)时, as_llongas_ullong 才可用。


这是一个使用这些函数以及节点数据检索函数的示例(samples/traverse_base.cpp):

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";
}


5.3.基于内容的遍历函数


由于大量的文档遍历包括查找具有正确名称的节点/属性,因此有专门的函数用于此目的:

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;


childattribute 返回具有指定名称的第一个子代/属性; next_siblingprevious_sibling 返回相应方向上具有指定名称的第一个同胞。所有字符串比较均区分大小写。如果节点句柄为空或没有指定名称的节点/属性,则返回空句柄。


childnext_sibling 函数一起使用,可以像这样循环遍历具有所需名称的所有子节点:

for (pugi::xml_node tool = tools.child("Tool"); tool; tool = tool.next_sibling("Tool"))


attribute 函数需要根据名称查找目标属性。如果一个节点有很多属性,按名称查找每个属性会很耗时。如果你知道节点中的属性是如何排序的,就可以使用更快的函数:

xml_attribute xml_node::attribute(const char_t* name, xml_attribute& hint) const;


额外的 hint 参数用于猜测属性可能所在的位置,并更新为下一个属性的位置,这样如果以正确的顺序搜索多个属性,就能最大限度地提高性能。请注意, hint 必须为空,或者必须属于节点,否则其行为将是未定义的。


该功能的使用方法如下:

xml_attribute hint;
xml_attribute id = node.attribute("id", hint);
xml_attribute name = node.attribute("name", hint);
xml_attribute version = node.attribute("version", hint);


无论属性的顺序如何,这段代码都是正确的,但如果 "id""name""version" 按顺序出现,速度会更快。


有时,所需的节点不是由唯一名称指定的,而是由某些属性的值指定的;例如,节点集合很常见,每个节点都有一个唯一的 id: <group><item id="1"/> <item id="2"/></group> 。有两个函数可以根据属性值查找子节点:

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;


三参数函数返回具有指定名称的第一个子节点,该节点具有指定名称/值的属性;二参数函数跳过节点名称测试,这对于在异构集合中搜索非常有用。如果节点句柄为空或未找到节点,则返回空句柄。所有字符串比较都区分大小写。


在上述所有函数中,所有参数都必须是有效字符串;传递空指针会导致未定义的行为。


这是一个使用这些函数的示例 ( samples/traverse_base.cpp):

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";
}


5.4.基于范围的 for 循环支持


如果您的 C++ 编译器支持基于范围的 for-loop(这是 C++11 的特性,在撰写本文时,Microsoft Visual Studio 2012+、GCC 4.6+ 和 Clang 3.0+ 均支持该特性),您就可以使用它来枚举节点/属性。为支持这一功能,我们还提供了额外的辅助工具;请注意,这些辅助工具也兼容 Boost Foreach 以及其他可能是 C++11 之前的 foreach 设施。

implementation-defined-type xml_node::children() const;
implementation-defined-type xml_node::children(const char_t* name) const;
implementation-defined-type xml_node::attributes() const;


使用 children 函数可以枚举所有子节点;使用带有 name 个参数的 children 函数可以枚举具有特定名称的所有子节点;使用 attributes 函数可以枚举节点的所有属性。请注意,您也可以在基于范围的 for 结构中使用节点对象本身,这等同于使用 children() .


这是一个使用这些函数的示例 ( samples/traverse_rangefor.cpp):

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;
}


使用"0"可以清楚地表达代码的意图,但请注意,每个节点都可以被视为子节点的容器,因为它提供了下一节描述的 begin() / end() 成员函数。因此,只需使用节点本身,就可以遍历节点的子节点:

for (pugi::xml_node child: tool)


5.5.通过迭代器遍历节点/属性列表


子节点列表和属性列表是简单的双链列表;虽然可以使用 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;


beginattributes_begin 将分别返回指向第一个节点/属性的迭代器; endattributes_end 将分别返回节点/属性列表的过终点迭代器--该迭代器不能被取消引用,但递减它将导致迭代器指向列表中的最后一个元素(空列表除外,递减过终点迭代器将导致未定义的行为)。过终点迭代器通常用作迭代循环的终止值(见下面的示例)。如果想获得指向现有句柄的迭代器,可以将句柄作为单个构造函数参数来构造迭代器,如下所示: xml_node_iterator(node) .对于 xml_attribute_iterator ,你必须同时提供一个属性和它的父节点。


如果在空节点上调用, beginend 返回相等的迭代器;这种迭代器不能被取消引用。 attributes_beginattributes_end 的行为方式相同。为了正确使用迭代器,这意味着空节点的子节点/属性集合看起来是空的。


这两种类型的迭代器都具有双向迭代器语义(即可以递增和递减,但不支持有效的随机访问),并支持所有常用的迭代器操作--比较、取消引用等。如果迭代器指向的节点/属性对象被从树中删除,迭代器就会失效;添加节点/属性不会使任何迭代器失效。


下面是一个使用迭代器进行文档遍历的示例(samples/traverse_iter.cpp):

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;
}
 注意

节点和属性迭代器介于常量和非常量迭代器之间。虽然取消引用操作会产生对象的非常数引用,因此可以将其用于树修改操作,但使用赋值修改该引用(即将迭代器传递给 std::sort 等函数)不会得到预期的结果,因为赋值会修改存储在迭代器中的本地句柄。


5.6.使用 xml_tree_walker 进行递归遍历


上述方法允许遍历某个节点的直接子节点;如果要进行深度树遍历,则必须通过递归函数或其他类似方法。不过,pugixml 提供了一个子树深度优先遍历的辅助工具。要使用它,必须实现0 个接口并调用1 个函数:

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);


通过调用遍历根上的 traverse 函数启动遍历,过程如下


  • 首先,以遍历根为参数调用 begin 函数。


  • 然后,对遍历子树中的所有节点(不包括遍历根节点)按深度第一顺序调用 for_each 函数。节点作为参数传递。


  • 最后,以遍历根为参数调用 end 函数。


如果 beginendfor_each 中的任何一个调用返回 false ,则遍历结束, false 作为遍历结果返回;否则,遍历结果为 true 。请注意,您不必覆盖 beginend 函数;它们的默认实现返回 true


您可以通过调用 depth 函数来获取节点相对于遍历根的深度。如果从 begin / end 处调用,则返回 -1 ;如果从 for_each 处调用,则返回基于 0 的深度--遍历根的所有子节点的深度为 0,所有孙节点的深度为 1,以此类推。


这是使用 xml_tree_walker ( samples/traverse_walker.cpp) 遍历树层次结构的示例:

struct simple_walker: pugi::xml_tree_walker
{
    virtual bool for_each(pugi::xml_node& node)
    {
        for (int i = 0; i < depth(); ++i) std::cout << "  "; // indentation

        std::cout << node_types[node.type()] << ": name='" << node.name() << "', value='" << node.value() << "'\n";

        return true; // continue traversal
    }
};
simple_walker walker;
doc.traverse(walker);


5.7.使用谓词搜索节点/属性


虽然现有函数可以获取已知内容的节点/属性,但它们往往不足以满足简单查询的需要。作为手动遍历节点/属性直到找到所需的节点/属性的替代方法,您可以创建一个谓词并调用 find_ 个函数中的一个:

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;


谓词应该是一个普通函数或函数对象,它接受一个类型为 xml_attribute (对于 find_attribute )或 xml_node (对于 find_childfind_node )的参数,并返回 bool 。在调用谓词时,不能将空句柄作为参数。


find_attribute 函数遍历指定节点的所有属性,并返回谓词返回 true 的第一个属性。如果所有属性的谓词返回值都是 false ,或者没有任何属性(包括节点为空的情况),则返回空属性。


find_child 函数遍历指定节点的所有子节点,并返回谓词返回 true 的第一个节点。如果所有节点的谓词返回值都是 false ,或者没有子节点(包括节点为空的情况),则返回空节点。


find_node 函数对指定节点的子树(不包括节点本身)执行深度优先遍历,并返回谓词返回 true 的第一个节点。如果所有节点的谓词返回值都是 false ,或者子树为空,则返回空节点。


这是一个使用基于谓词的函数的示例 ( 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;


5.8.处理文本内容


将数据存储为某个节点(即 <node><description>This is a node</description></node> )的文本内容是很常见的。pugixml 提供了一个特殊类 xml_text 来处理此类数据。在修改文档数据的文档中介绍了如何使用文本对象来修改数据;本节将介绍 xml_text 的访问接口。


您可以使用 text() 方法从节点获取文本对象:

xml_text xml_node::text() const;


如果节点的类型为 node_pcdatanode_cdata ,则使用节点本身来返回数据;否则,使用类型为 node_pcdatanode_cdata 的第一个子节点。


您可以使用布尔值(即 if (text) { …​ }if (!text) { …​ } )来检查文本对象是否绑定到有效的 PCDATA/CDATA 节点。或者,您也可以使用 empty() 方法进行检查:

bool xml_text::empty() const;


如果给定了一个文本对象,您可以使用以下函数获取其内容(即 PCDATA/CDATA 节点的值):

const char_t* xml_text::get() const;


如果文本对象为空,函数将返回空字符串,而不会返回空指针。


如果文本对象为空,或者文本内容实际上是以字符串形式存储的数字或布尔值,则需要非空字符串,这时可以使用以下访问器:

const char_t* xml_text::as_string(const char_t* def = "") const;
int xml_text::as_int(int def = 0) const;
unsigned int xml_text::as_uint(unsigned int def = 0) const;
double xml_text::as_double(double def = 0) const;
float xml_text::as_float(float def = 0) const;
bool xml_text::as_bool(bool def = false) const;
long long xml_text::as_llong(long long def = 0) const;
unsigned long long xml_text::as_ullong(unsigned long long def = 0) const;


上述所有函数的语义都与类似的 xml_attribute 成员相同:如果文本对象为空,它们会返回默认参数,并使用相同的规则和限制将文本内容转换为目标类型。有关详细信息,请参阅属性函数文档。


xml_text 本质上是一个对 xml_node 值进行操作的辅助类。它与 node_pcdata 或 node_cdata 类型的节点绑定。您可以使用下面的函数检索该节点:

xml_node xml_text::data() const;


从本质上讲,假设 text 是一个 xml_text 对象,那么调用 text.get() 就等同于调用 text.data().value()


这是一个使用 xml_text 对象的示例(samples/text.cpp):

std::cout << "Project name: " << project.child("name").text().get() << std::endl;
std::cout << "Project version: " << project.child("version").text().as_double() << std::endl;
std::cout << "Project visibility: " << (project.child("public").text().as_bool(/* def= */ true) ? "public" : "private") << std::endl;
std::cout << "Project description: " << project.child("description").text().get() << std::endl;


5.9.杂项功能


如果需要获取某个节点的文档根目录,可以使用下面的函数:

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 返回从文档根指向节点的路径, first_element_by_path 寻找给定路径所代表的节点;路径可以是绝对路径(绝对路径以分隔符开始),在这种情况下,路径的其余部分被视为文档根相对路径和给定节点的相对路径。例如,在下面的文档中 <a><b><c/></b></a> , 节点 <c/> 的路径为 "a/b/c" ;调用路径为 "a/b" 的文档 first_element_by_path ,结果为节点 <b/> ;调用路径为 "../a/./b/../." 的节点 <a/> 的路径 first_element_by_path ,结果为节点 <a/> ;调用路径为 "/a" 的节点 first_element_by_path ,结果为任意节点的节点 <a/>


如果路径组件模棱两可(如果有两个节点具有给定的名称),则选择第一个;路径不能保证唯一标识文档中的节点。如果找不到路径的任何组件, first_element_by_path 的结果是空节点;对于空节点, first_element_by_path 也返回空节点,在这种情况下,路径并不重要。对于空节点, path 返回空字符串。

 注意

path 函数以 STL 字符串形式返回结果,因此如果定义了 PUGIXML_NO_STL,则该函数不可用。


出于提高效率的考虑,pugixml 在解析时不会记录节点的行/列信息。但是,如果节点在解析后没有发生重大变化(名称/值没有变化,节点本身是原始节点,即没有从树中删除,之后又重新添加),则可以从 XML 缓冲区的起始位置获取偏移量:

ptrdiff_t xml_node::offset_debug() const;


如果偏移量不可用(如果节点为空、最初不是从流中解析的,或者发生了重大变化),函数将返回-1。否则,函数将以 pugi::char_t 为单位,返回节点数据从 XML 缓冲区开始的偏移量。有关解析偏移量的更多信息,请参阅解析错误处理文档。


6.修改文件数据


pugixml 中的文档是完全可变的:您可以完全改变文档结构并修改节点/属性的数据。本节提供了相关函数的文档。所有函数都负责内存管理和结构完整性,因此它们总是能生成结构有效的树--不过,也有可能生成无效的 XML 树(例如,添加两个同名属性或将属性/节点名称设置为空/无效字符串)。树的修改已针对性能和内存消耗进行了优化,因此如果内存充足,就可以使用 pugixml 从头开始创建文档,然后将其保存到文件/流中,而不必依赖容易出错的手动文本编写,也不会造成太大的开销。


所有改变节点/属性数据或结构的成员函数都是非常量函数,因此不能在常量句柄上调用。不过,通过简单的赋值,可以很容易地将常量句柄转换为非常量句柄: void foo(const pugi::xml_node& n) { pugi::xml_node nc = n; } ,所以这里的常量正确性主要是提供额外的文档。

 设置节点数据


如前所述,节点可以有名称和值,两者都是字符串。node_document 节点没有名称或值,node_element 和 node_declaration 节点总是有名称但没有值,node_pcdata、node_cdata、node_comment 和 node_doctype 节点没有名称但总是有值(可能为空),node_pi 节点总是有名称和值(同样,值可能为空)。要设置节点的名称或值,可以使用以下函数:

bool xml_node::set_name(const char_t* rhs);
bool xml_node::set_name(const char_t* rhs, size_t sz)
bool xml_node::set_value(const char_t* rhs);
bool xml_node::set_value(const char_t* rhs, size_t size);


这两个函数都会尝试将名称/值设置为指定的字符串,并返回操作结果。如果节点没有名称或值(例如,在 node_pcdata 节点上调用 set_name 时)、节点句柄为空或内存不足以处理请求,则操作失败。提供的字符串会被复制到文档管理的内存中,并可在函数返回后销毁(例如,可以安全地将堆栈分配的缓冲区传递给这些函数)。名称/值内容未经验证,因此请注意只使用有效的 XML 名称,否则文档可能会畸形。


这是一个设置节点名称和值的示例 ( samples/modify_base.cpp):

pugi::xml_node node = doc.child("node");

// change node name
std::cout << node.set_name("notnode");
std::cout << ", new node name: " << node.name() << std::endl;

// change comment text
std::cout << doc.last_child().set_value("useless comment");
std::cout << ", new comment text: " << doc.last_child().value() << std::endl;

// we can't change value of the element or name of the comment
std::cout << node.set_value("1") << ", " << doc.last_child().set_name("2") << std::endl;


6.1.设置属性数据


所有属性都有 name 和 value,两者都是字符串(value 可以为空)。您可以使用以下函数设置它们:

bool xml_attribute::set_name(const char_t* rhs);
bool xml_attribute::set_name(const char_t* rhs, size_t sz)
bool xml_attribute::set_value(const char_t* rhs);
bool xml_attribute::set_value(const char_t* rhs, size_t size);


这两个函数都会尝试将名称/值设置为指定的字符串,并返回操作结果。如果属性句柄为空,或内存不足以处理请求,则操作失败。提供的字符串会被复制到文档管理的内存中,并可在函数返回后销毁(例如,可以安全地将堆栈分配的缓冲区传递给这些函数)。名称/值内容未经验证,因此请注意只使用有效的 XML 名称,否则文档可能会畸形。


除字符串函数外,还提供了几个函数用于处理以数字和布尔值为值的属性:

bool xml_attribute::set_value(int rhs);
bool xml_attribute::set_value(unsigned int rhs);
bool xml_attribute::set_value(long rhs);
bool xml_attribute::set_value(unsigned long rhs);
bool xml_attribute::set_value(double rhs);
bool xml_attribute::set_value(double rhs, int precision);
bool xml_attribute::set_value(float rhs);
bool xml_attribute::set_value(float rhs, int precision);
bool xml_attribute::set_value(bool rhs);
bool xml_attribute::set_value(long long rhs);
bool xml_attribute::set_value(unsigned long long rhs);


上述函数将参数转换为字符串,然后调用基 set_value 函数。整数被转换为十进制形式,浮点数被转换为十进制或科学数形式(取决于数字大小),布尔值被转换为 "true""false"

 注意

数字转换函数依赖于当前 C 语言的本地化设置 setlocale ,因此如果本地化设置不同于 "C" ,可能会产生意想不到的结果。
 注意

只有当您的平台对该类型(包括字符串转换)有可靠的支持时,才可使用1 种类型的 set_value 重载。


为方便起见,所有 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=(long rhs);
xml_attribute& xml_attribute::operator=(unsigned long rhs);
xml_attribute& xml_attribute::operator=(double rhs);
xml_attribute& xml_attribute::operator=(float rhs);
xml_attribute& xml_attribute::operator=(bool rhs);
xml_attribute& xml_attribute::operator=(long long rhs);
xml_attribute& xml_attribute::operator=(unsigned long long rhs);


这些运算符只需调用右边的 set_value 函数,并返回所调用的属性;返回值为 set_value 时将被忽略,因此错误也将被忽略。


这是一个设置属性名和值的示例 ( samples/modify_base.cpp):

pugi::xml_attribute attr = node.attribute("id");

// change attribute name/value
std::cout << attr.set_name("key") << ", " << attr.set_value("345");
std::cout << ", new attribute: " << attr.name() << "=" << attr.value() << std::endl;

// we can use numbers or booleans
attr.set_value(1.234);
std::cout << "new attribute value: " << attr.value() << std::endl;

// we can also use assignment operators for more concise code
attr = true;
std::cout << "final attribute value: " << attr.value() << std::endl;


6.2.添加节点/属性


如果没有文档树,节点和属性是不存在的,因此如果不将它们添加到某个文档中,就无法创建它们。节点或属性可以在节点/属性列表的末尾创建,也可以在其他节点之前或之后创建:

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_attributeappend_child 在方法被调用的节点对应列表的末尾创建一个新节点/属性; prepend_attributeprepend_child 在列表的开头创建一个新节点/属性; insert_attribute_afterinsert_attribute_beforeinsert_child_afterinsert_attribute_before 在指定节点/属性之前或之后添加节点/属性。


属性函数用指定的名称创建属性;如果需要,可以指定空名,然后再更改名称。带有 type 参数的节点函数会创建指定类型的节点;由于节点类型不可更改,因此必须事先知道所需的类型。另外请注意,并非所有类型的节点都可以添加为子节点;请参阅下面的说明。使用 name 参数的节点函数会创建具有指定名称的元素节点 ( node_element)。


所有函数都会在成功时返回创建对象的句柄,在失败时返回空句柄。失败的原因有以下几种:


  • 如果目标节点为空,则添加失败;


  • 只有 node_element 节点可以包含属性,因此如果节点不是元素,则属性添加失败;


  • 只有 node_document 和 node_element 节点可以包含子节点,因此如果目标节点不是元素或文档,则子节点添加失败;


  • node_document 和 node_null 节点不能作为子节点插入,因此传递 node_document 或 node_null 值为 type 会导致操作失败;


  • node_declaration 节点只能作为文档节点的子节点添加;将声明节点作为元素节点的子节点插入的尝试失败;


  • 添加节点/属性会导致内存分配失败;


  • 如果指定的节点或属性为空或不在目标节点的子节点/属性列表中,则插入函数会失败。


即使操作失败,文档也会保持一致状态,但不会添加所请求的节点/属性。

 注意

attribute()child() 函数不会在树上添加属性或节点,因此如果 node 没有名称为 "id" 的属性, node.attribute("id") = 123; 这样的代码将不起任何作用。请确保您在操作时使用了现有的属性/节点,必要时添加它们。


这是一个向文档添加新属性/节点的示例(samples/modify_add.cpp):

// add node with some name
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";


6.3.删除节点/属性


如果不想让文档包含某些节点或属性,可以使用以下功能之一将其删除:

bool xml_node::remove_attribute(const xml_attribute& a);
bool xml_node::remove_attributes();
bool xml_node::remove_child(const xml_node& n);
bool xml_node::remove_children();


remove_attribute 从节点的属性列表中删除属性,并返回操作结果。 remove_child 从文档中删除子节点和整个子树(包括所有子节点和属性),并返回操作结果。 remove_attributes 删除节点的所有属性,并返回操作结果。 remove_children 删除节点的所有子节点,并返回操作结果。如果以下情况之一为真,则删除失败:


  • 函数被调用的节点为空;


  • 要删除的属性/节点为空;


  • 要删除的属性/节点不在节点的属性/子节点列表中。


删除属性或节点会使指向同一底层对象的所有句柄失效,也会使指向同一对象的所有迭代器失效。删除节点也会使指向其属性或子节点列表的所有结束迭代器失效。请注意,在删除属性/节点后,所有这些句柄和迭代器要么不存在,要么不会被使用。


如果想通过名称移除属性或子节点,还可以使用另外两个辅助函数:

bool xml_node::remove_attribute(const char_t* name);
bool xml_node::remove_child(const char_t* name);


这些函数查找第一个具有指定名称的属性或子节点,然后将其删除,并返回结果。如果没有名称相同的属性或子节点,函数返回 false ;如果有两个节点的名称相同,则只删除第一个节点。如果要删除所有具有指定名称的节点,可以使用如下代码: while (node.remove_child("tool")) ; .


这是一个从文档中删除属性/节点的示例(samples/modify_remove.cpp):

// 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);


6.4.处理文本内容


pugixml 提供了一个特殊的类 xml_text ,用于处理作为某个节点值存储的文本内容,即 <node><description>This is a node</description></node> 。在访问文档数据的文档中介绍了如何使用文本对象检索数据;本节将介绍 xml_text 的修改界面。


有了 xml_text 对象后,就可以使用以下函数设置文本内容:

bool xml_text::set(const char_t* rhs);
bool xml_text::set(const char_t* rhs, size_t size);


该函数尝试将内容设置为指定的字符串,并返回操作结果。如果文本对象是从一个不能有值且不是元素节点(即节点声明节点)的节点中获取的,或者文本对象为空,或者内存不足以处理请求,则操作失败。提供的字符串会被复制到文档管理的内存中,并可在函数返回后销毁(例如,可以安全地将堆栈分配的缓冲区传递给此函数)。需要注意的是,如果文本对象是从元素节点获取的,则该函数会在必要时创建 PCDATA 子节点(即如果元素节点还没有 PCDATA/CDATA 子节点)。


除字符串函数外,还提供了多个函数用于处理以数字和布尔值为内容的文本:

bool xml_text::set(int rhs);
bool xml_text::set(unsigned int rhs);
bool xml_text::set(long rhs);
bool xml_text::set(unsigned long rhs);
bool xml_text::set(double rhs);
bool xml_text::set(double rhs, int precision);
bool xml_text::set(float rhs);
bool xml_text::set(float rhs, int precision);
bool xml_text::set(bool rhs);
bool xml_text::set(long long rhs);
bool xml_text::set(unsigned long long rhs);


上述函数将参数转换为字符串,然后调用基 set 函数。这些函数的语义与类似的 xml_attribute 函数相同。详细信息请参阅属性函数文档。


为方便起见,所有 set 函数都有相应的赋值操作符:

xml_text& xml_text::operator=(const char_t* rhs);
xml_text& xml_text::operator=(int rhs);
xml_text& xml_text::operator=(unsigned int rhs);
xml_text& xml_text::operator=(long rhs);
xml_text& xml_text::operator=(unsigned long rhs);
xml_text& xml_text::operator=(double rhs);
xml_text& xml_text::operator=(float rhs);
xml_text& xml_text::operator=(bool rhs);
xml_text& xml_text::operator=(long long rhs);
xml_text& xml_text::operator=(unsigned long long rhs);


这些运算符只需调用右边的 set 函数,并返回所调用的属性;返回值为 set 时将被忽略,因此错误也将被忽略。


这是一个使用 xml_text 对象修改文本内容的示例(samples/text.cpp):

// change project version
project.child("version").text() = 1.2;

// add description element and set the contents
// note that we do not have to explicitly add the node_pcdata child
project.append_child("description").text().set("a test project");


6.5.克隆节点/属性


在前面所述函数的帮助下,可以创建具有任何内容和结构的树,包括克隆现有数据。不过,由于这是一项经常需要的操作,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 及相关函数的结构如出一辙--它们获取要克隆的原型对象的句柄,在适当的位置插入新的属性/节点,然后将属性数据或整个节点子树复制到新对象中。这些函数会返回复制对象的句柄,如果失败则返回空句柄。


属性会连同名称和值一起复制;节点会连同其类型、名称和值一起复制;此外,属性列表和所有子节点都会被递归克隆,从而形成深子树克隆。原型对象可以是同一文档的一部分,也可以是任何其他文档的一部分。


故障条件与 append_childinsert_child_before 和相关函数类似,更多信息请查阅它们的文档。还有一些克隆函数特有的注意事项:


  • 克隆空句柄会导致操作失败;


  • 节点克隆从插入与原型类型相同的节点开始;因此,克隆函数不能直接用于克隆整个文档,因为 node_document 不是有效的插入类型。下面的示例提供了一种变通方法。


  • 可以将子树复制为子树内部某个节点的子节点,即 node.append_copy(node.parent().parent()); 。这是一个有效的操作,其结果是克隆子树开始克隆前的状态,即不会发生无限递归。


这是一个 XML 中 include 标记的可能实现示例(samples/include.cpp)。它说明了节点克隆和其他文档修改功能的使用:

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;
}

 6.6.移动节点


有时,你需要将现有节点移动到树中的另一个位置,而不是克隆一个节点。这可以通过复制节点并移除原节点来实现,但这样做成本很高,因为会产生大量额外操作。要在同一文档树中移动节点,可以使用以下函数:

xml_node xml_node::append_move(const xml_node& moved);
xml_node xml_node::prepend_move(const xml_node& moved);
xml_node xml_node::insert_move_after(const xml_node& moved, const xml_node& node);
xml_node xml_node::insert_move_before(const xml_node& moved, const xml_node& node);


这些函数与 append_copyprepend_copyinsert_copy_beforeinsert_copy_after 的结构如出一辙--它们获取被移动对象的句柄,并将其移动到带有所有属性和/或子节点的适当位置。这些函数会返回结果对象的句柄(与被移动对象相同),如果失败则返回空句柄。


故障条件与 append_childinsert_child_before 和相关函数类似,更多信息请查阅它们的文档。还有一些移动函数特有的注意事项:


  • 移动空句柄会导致操作失败;


  • 移动只适用于属于同一文档的节点;尝试在文档之间移动节点将失败。


  • 如果移动的节点与 node 参数相同, insert_move_afterinsert_move_before 函数将失效(否则此操作将是无操作)。


  • 不可能将子树移动到该子树内某个节点的子节点上,即 node.append_move(node.parent().parent()); 将失败。


6.7.从文件片段组装文件


pugixml 提供了几种从其他 XML 文档组装 XML 文档的方法。假定有一组以内存缓冲区形式表示的文档片段,其实现方法如下:


  • 使用临时文档解析字符串中的数据,然后将节点克隆到目标节点。例如

    bool append_fragment(pugi::xml_node target, const char* buffer, size_t size)
    {
        pugi::xml_document doc;
        if (!doc.load_buffer(buffer, size)) return false;
    
        for (pugi::xml_node child = doc.first_child(); child; child = child.next_sibling())
            target.append_copy(child);
    }

  • 缓存解析步骤--不保留内存缓冲区,而是保留已包含解析片段的文档对象:

    bool append_fragment(pugi::xml_node target, const pugi::xml_document& cached_fragment)
    {
        for (pugi::xml_node child = cached_fragment.first_child(); child; child = child.next_sibling())
            target.append_copy(child);
    }

  • 直接使用 xml_node::append_buffer

    xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);


第一种方法更方便,但比其他两种方法慢。 append_copyappend_buffer 的相对性能取决于缓冲区的格式--如果缓冲区采用本地编码(UTF-8 或 wchar_t,取决于 PUGIXML_WCHAR_MODE ),通常 append_buffer 会更快。同时,它在内存使用方面的效率可能较低--实现过程中会复制所提供的缓冲区,而该副本的生命周期与文档的生命周期相同--该副本所使用的内存将在文档销毁后回收,但不会更早。即使删除文档中的所有节点,包括添加的节点,也不会回收内存。


0 的行为方式与 xml_document::load_buffer 相同--输入缓冲区是一个字节缓冲区,大小以字节为单位;缓冲区不会被修改,可以在函数返回后释放。


由于 append_buffer 需要将子节点追加到当前节点,因此只有当当前节点是文档节点或元素节点时才有效。在其他类型的节点上调用 append_buffer 会导致错误,状态为 status_append_invalid_root

 7.保存文件


在创建新文档或加载现有文档并对其进行处理后,通常需要将结果保存到文件中。pugixml 提供了多个函数,用于将文档的任何子树输出到文件、流或其他通用传输接口;这些函数允许自定义输出格式(参见输出选项),并执行必要的编码转换(参见编码)。本节记录了相关功能。


在写入目的地之前,节点/属性数据会根据节点类型正确格式化;所有特殊的 XML 符号,如 <& ,都会被正确转义(除非设置了 format_no_escapes 标志)。为了防止节点/属性名称被遗忘,空节点/属性名称会打印为 ":anonymous" 。为获得格式良好的输出,请确保所有节点和属性名称都设置为有意义的值。


值为 "]]>" 的 CDATA 部分会被分割成几个部分,如下所示:值为 "pre]]>post" 的部分被写成 <![CDATA[pre]]]]><![CDATA[>post]]> 。虽然这会改变文档的结构(如果您在保存文档后加载文档,则会出现两个 CDATA 部分,而不是一个),但这是转义 CDATA 内容的唯一方法。


7.1.将文档保存到文件


如果要将整个文档保存到文件中,可以使用以下功能之一:

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;


这些函数的第一个参数是文件路径,另外还有三个可选参数,分别指定缩进和其他输出选项(请参阅输出选项)以及输出数据编码(请参阅编码)。路径具有目标操作系统的格式,因此可以是相对路径或绝对路径,路径应具有目标系统的分隔符,如果目标文件系统区分大小写,路径应具有准确的大小写等。函数成功时返回 true ,如果文件无法打开或写入,则返回 false


在第一个函数(接受 const char* path )中,文件路径被原封不动地传递给系统文件打开函数;第二个函数要么使用运行时库提供的特殊文件打开函数,要么将路径转换为 UTF-8 并使用系统文件打开函数。


save_file 打开要写入的目标文件,输出请求的页眉(默认情况下输出文档声明,除非文档已有声明),然后保存文档内容。调用 save_file 相当于创建一个 xml_writer_file 对象,将 FILE* 句柄作为唯一的构造函数参数,然后调用 save ;有关写入器接口的详细信息,请参阅通过写入器接口保存文档。


这是一个将 XML 文档保存到文件的简单示例(samples/save_file.cpp):

// save document to file
std::cout << "Saving result: " << doc.save_file("save_file_output.xml") << std::endl;


7.2.将文档保存为 C++ IOstreams


为了增强互操作性,pugixml 提供了将文档保存到任何实现 C++ std::ostream 接口的对象的函数。这样,您就可以将文档保存到任何标准 C++ 流(即文件流)或任何第三方兼容实现(即 Boost Iostreams)。最值得注意的是,由于可以使用1 个流作为保存目标,因此可以方便地进行调试输出。有两个函数,一个用于处理窄字符流,另一个用于处理宽字符流:

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;


带有1 个参数的 save 会以与 save_file 相同的方式将文档保存到数据流中(即带有请求的标头和编码转换)。另一方面,带有4 个参数的 save 会使用 encoding_wchar 编码将文档保存到宽字符流中。因此,在宽字符流中使用 save 时,需要仔细(通常是针对特定平台)设置流(即使用 imbue 函数)。一般情况下,我们不鼓励使用宽字符流,但它提供了将文档保存为非 Unicode 编码的能力,例如,如果设置了正确的本地语言,就可以保存 Shift-JIS 编码的数据。


使用流目标调用 save 相当于创建一个以流为唯一构造函数参数的 xml_writer_stream 对象,然后调用 save ;有关写入器接口的详细信息,请参阅通过写入器接口保存文档。


这是一个将 XML 文档保存到标准输出的简单示例(samples/save_stream.cpp):

// save document to standard output
std::cout << "Document:\n";
doc.save(std::cout);


7.3.通过写入界面保存文件


上述所有保存功能都是通过写入界面实现的。这是一个简单的接口,只有一个函数,在以文档数据块为输入的输出过程中会被多次调用:

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 接口的对象,并将其传递给 save 函数。 xml_writer::write 函数在调用时会将缓冲区作为输入,其中 data 指向缓冲区的起点, size 等于缓冲区的大小(以字节为单位)。 write 实现必须将缓冲区写入传输;不能保存传递的缓冲区指针,因为 write 返回后缓冲区内容会发生变化。缓冲区包含所需编码的文档数据块。


write 函数会调用相对较大的数据块(大小通常为几千字节,但最后一个数据块可能较小),因此在执行过程中通常不需要额外的缓冲。


这是一个将文档数据保存为 STL 字符串的自定义写入器的简单示例(samples/save_custom_writer.cpp);如需了解更复杂的示例,请阅读示例代码:

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);
    }
};


7.4.保存单个子树


虽然前面介绍的函数可以将整个文档保存到目的地,但保存单个子树也很容易。我们提供了以下函数:

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 函数相同,可以将子树保存到 C++ IOstream 或任何实现 xml_writer 接口的对象中。


保存子树与保存整个文档不同:即使标志的实际值不同,保存过程也会表现为 format_write_bom 关闭,format_no_declaration 打开。这意味着 BOM 不会被写入目的地,而文档声明只有在它是节点本身或节点的一个子节点时才会被写入。请注意,在保存文档时也是如此;本例(samples/save_subtree.cpp)说明了两者的区别:

// get a test document
pugi::xml_document doc;
doc.load_string("<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;

 7.5.输出选项


所有保存函数都接受可选参数 flags 。这是一个自定义输出格式的位掩码;您可以选择文档节点的打印方式,并选择在文档内容之前输出所需的附加信息。

 注意

您应该使用通常的位运算来操作位掩码:启用一个标志,使用 mask | flag ;禁用一个标志,使用 mask & ~flag


这些标志控制着生成的树内容:


  • format_indent 表示是否使用缩进字符串缩进所有节点(这是所有保存函数的附加参数,默认为 "\t" )。如果启用该标志,则会在每个节点前多次打印缩进字符串,缩进量取决于节点相对于输出子树的深度。如果启用了 format_raw,则此标记无效。默认情况下此标记为打开。


  • format_indent_attributes 表示是否应将所有属性打印在新行上,并根据属性的深度用缩进字符串缩进。该标志意味着 format_indent。如果启用了 format_raw,则该标志不起作用。该标志默认为关闭。


  • format_raw 则在格式化输出和原始输出之间切换。如果启用此标记,则不会以任何方式缩进节点,也不会打印不属于文档文本的换行符。原始模式可用于序列化,其结果不会被人类读取;如果文档是使用 parse_ws_pcdata 标志解析的,原始模式也很有用,可以尽可能保留原始文档格式。该标记默认为关闭。


  • format_no_escapes 则禁用属性值和 PCDATA 内容的输出转义。如果关闭该标记,特殊符号( "&<> )和所有不可打印字符(代码点值小于 32 的字符)将在输出时转换为 XML 转义序列(即 &amp; )。如果打开此标记,则不进行文本处理;因此,如果输出内容包含无效符号,输出的 XML 可能会畸形(即 PCDATA 中的杂散 < 会使输出畸形)。该标记默认为关闭。


  • format_no_empty_element_tags 决定是否输出 start/end 标记,而不是空元素(即无子元素)的空元素标记。该标记默认为关闭。


  • format_skip_control_chars 可以跳过范围 [0; 32) 的字符,而不是 "&#xNN;" 编码。该标志默认为关闭。


  • format_attribute_single_quote 可以使用单引号 ' 代替双引号 " 来括弧属性值。该标志默认为关闭。


这些标志控制附加输出信息:


  • format_no_declaration 禁用默认节点声明输出。默认情况下,如果文档是通过 savesave_file 功能保存的,且没有任何文档声明,则会在文档内容之前输出默认声明。启用此标记将禁用此声明。此标记对 xml_node::print 函数没有影响:它们从不输出默认声明。该标记默认为关闭。


  • format_write_bom 可启用字节序号 (BOM) 输出。默认情况下不输出 BOM,因此在非 UTF-8 编码的情况下,如果某些解析器和文本编辑器没有实现复杂的编码检测功能,生成文档的编码可能无法被它们识别。启用此标记会在输出中添加特定编码的 BOM。此标记对 xml_node::print 函数没有影响:它们从不输出 BOM。该标记默认为关闭。


  • 当使用 save_file 功能时, format_save_file_text 会改变文件模式。默认情况下,文件以二进制模式打开,这意味着输出文件将包含与平台无关的换行 \n (ASCII 10)。如果打开该标志,文件将以文本模式打开,在某些系统上,文本模式会改变换行格式(例如,在 Windows 系统上,可以使用该标志输出带有 \r\n (ASCII 13 10) 换行符的 XML 文档。该标志默认为关闭。


此外,还有一个预定义的选项掩码:


  • format_default 是默认的标志集,即所有选项都设置为默认值。如果需要,它将设置带缩进的格式化输出,不带 BOM 和默认节点声明。


该示例显示了不同输出选项的输出结果(samples/save_options.cpp):

// get a test document
pugi::xml_document doc;
doc.load_string("<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;

 7.6.编码


pugixml 支持所有流行的 Unicode 编码(UTF-8、UTF-16(大内码和小内码)、UTF-32(大内码和小内码);由于 UCS-2 是 UTF-16 的严格子集,因此自然也支持 UCS-2),并在输出过程中处理所有编码转换。输出编码通过保存函数的 encoding 参数设置,参数类型为 xml_encoding 。编码的可能值记录在编码中;唯一有不同含义的标志是 encoding_auto


所有其他标志都会设置准确的编码,而 encoding_auto 则用于自动检测编码。自动检测对输出编码没有意义,因为通常没有什么可以推断出实际编码,所以这里的 encoding_auto 表示 UTF-8 编码,这是 XML 数据存储中最常用的编码。这也是输出编码的默认值;如果不需要 UTF-8 编码输出,请指定其他值。


还要注意的是,宽数据流保存函数没有 encoding 参数,并且总是假定使用 encoding_wchar 编码。

 注意

当前的 Unicode 转换行为是在转换过程中跳过所有无效的 UTF 序列。如果您的节点/属性名称不包含任何有效的 UTF 序列,它们可能会被当作空序列输出,从而导致 XML 文档畸形。


7.7.自定义文件声明


使用 xml_document::save()xml_document::save_file() 保存文档时,如果未指定 format_no_declaration ,且文档中没有声明节点,则会输出默认的 XML 文档声明。但是,默认声明是不可定制的。如果要自定义声明输出,则需要自己创建声明节点。

 注意

默认情况下,声明节点不会在解析过程中添加到文档中。如果只需要保留原始声明节点,则必须在解析标志中添加 parse_declaration 标志;生成的文档将包含原始声明节点,并在保存时输出。


声明节点是一个具有 node_declaration 类型的节点;它的行为类似于元素节点,因为它具有带值的属性(但它没有子节点)。因此,设置自定义版本、编码或独立声明涉及添加属性和设置属性值。


这是一个示例,说明如何创建自定义声明节点(samples/save_declaration.cpp):

// get a test document
pugi::xml_document doc;
doc.load_string("<foo bar='baz'><call>hey</call></foo>");

// add a custom declaration node
pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
decl.append_attribute("encoding") = "UTF-8";
decl.append_attribute("standalone") = "no";

// <?xml version="1.0" encoding="UTF-8" standalone="no"?>
// <foo bar="baz">
//         <call>hey</call>
// </foo>
doc.save(std::cout);
std::cout << std::endl;

8. XPath


如果手头的任务是选择符合某些标准的文档节点子集,那么可以使用现有的遍历功能为任何实用标准编写一个函数。但是,通常情况下,如果标准不是预先定义的,而是来自文件,那么就需要数据驱动的方法,或者使用遍历接口不方便,需要更高级别的 DSL。pugixml 实现了 XPath 1.0 的一个几乎完整的子集。由于文档对象模型的差异和一些性能影响,pugixml 与官方规范有一些小的冲突,这些冲突可以在 Conformance to W3C specification 中找到。本节其余部分将介绍 XPath 功能的接口。请注意,如果您想学习使用 XPath 语言,必须查找其他教程或手册;例如,您可以阅读 W3Schools XPath 教程或 XPath 1.0 规范。

 8.1.XPath 类型


每个 XPath 表达式可以有以下类型:布尔、数字、字符串或节点集。布尔类型对应 bool 类型,数字类型对应 double 类型,字符串类型对应 std::stringstd::wstring 类型(取决于是否启用了宽字符接口),节点集对应 xpath_node_set 类型。还有一个枚举,即 xpath_value_type ,它的值可以是 xpath_type_booleanxpath_type_numberxpath_type_stringxpath_type_node_set


由于 XPath 节点既可以是节点,也可以是属性,因此有一种特殊的类型 xpath_node ,它是这些类型的区分联合。这种类型的值包含两个节点句柄,一个是 xml_node 类型,另一个是 xml_attribute 类型;其中最多有一个是非空的。我们提供了获取这些句柄的访问器:

xml_node xpath_node::node() const;
xml_attribute xpath_node::attribute() const;


XPath 节点可以为空,在这种情况下,两个访问器都返回空句柄。


请注意,根据 XPath 规范,每个 XPath 节点都有一个父节点,可以通过此函数检索父节点:

xml_node xpath_node::parent() const;


如果 XPath 节点对应1 个句柄(相当于 node().parent() ),函数 parent 返回节点的父节点;如果 XPath 节点对应 xml_attribute 个句柄,函数 parent 返回属性所属的节点。对于空节点, parent 会返回空句柄。


与节点和属性句柄一样,XPath 节点句柄也可以隐式地转换为布尔对象,以检查是否为空节点,还可以相互比较是否相等。


您还可以使用以下三种构造函数之一创建 XPath 节点:默认构造函数、包含节点参数的构造函数以及包含属性和节点参数的构造函数(在这种情况下,属性必须属于节点的属性列表)。从 xml_node 开始的构造函数是隐式的,因此通常可以将 xml_node 传递给期望值为 xpath_node 的函数。除此之外,您通常不需要创建自己的 XPath 节点对象,因为它们会通过选择函数返回给您。


XPath 表达式不对单个节点进行操作,而是对节点集进行操作。节点集是节点的集合,可以选择按文档正序或反序排序。文档顺序在 XPath 规范中定义;如果一个 XPath 节点在相应文档的 XML 表示中出现在另一个节点之前,那么它在文档顺序中就在另一个节点之前。


节点集由 xpath_node_set 对象表示,其接口类似于顺序随机访问容器。它有一个迭代器类型和通常的开始/结束迭代器访问器:

typedef const xpath_node* xpath_node_set::const_iterator;
const_iterator xpath_node_set::begin() const;
const_iterator xpath_node_set::end() const;


它也可以通过索引迭代,就像 std::vector

const xpath_node& xpath_node_set::operator[](size_t index) const;
size_t xpath_node_set::size() const;
bool xpath_node_set::empty() const;


上述所有操作的语义都与 std::vector 相同:迭代器是随机存取的,上述所有操作的时间都是恒定的,访问索引处大于或等于集合大小的元素会导致未定义的行为。你可以同时使用基于迭代器的访问和基于索引的访问进行迭代,但基于迭代器的访问可能更快。


迭代的顺序取决于集合内节点的顺序;可以通过以下函数查询顺序:

enum xpath_node_set::type_t {type_unsorted, type_sorted, type_sorted_reverse};
type_t xpath_node_set::type() const;


type 函数返回节点的当前顺序; type_sorted 表示节点按正向文档顺序排列, type_sorted_reverse 表示节点按反向文档顺序排列, type_unsorted 表示两种顺序都不保证(即使 type() 返回 type_unsorted ,节点也可能意外地按排序顺序排列)。如果您需要特定的迭代顺序,可以通过函数 sort 进行更改:

void xpath_node_set::sort(bool reverse = false);


调用"0"将根据参数以正向或反向文档顺序对节点排序;调用 " type() "后将返回 " type_sorted "或 " type_sorted_reverse "。


通常不需要实际迭代,而只需要文档顺序中的第一个元素。为此,我们提供了一个特殊的访问器:

xpath_node xpath_node_set::first() const;


该函数从集合中按正向文档顺序返回第一个节点,如果集合为空,则返回空节点。需要注意的是,虽然节点的结果与集合中节点的顺序无关(即与 type() 的结果无关),但复杂度却有关--如果集合是排序的,则复杂度为常数,否则复杂度与元素个数成线性关系,甚至更差。


在大多数情况下,节点集是由 XPath 函数返回的,但有时也需要手动构建节点集。在这种情况下,我们提供了一个构造函数,它接受一个迭代器范围( const_iteratorconst xpath_node* 的类型定义)和一个可选类型:

xpath_node_set::xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted);


构造函数复制指定的范围并设置指定的类型。我们不会以任何方式检查范围中的对象;您必须确保范围中不包含重复对象,并根据 type 参数对对象进行排序。否则,对该集合进行 XPath 操作可能会产生意想不到的结果。


8.2.通过 XPath 表达式选择节点


如果要选择与某个 XPath 表达式相匹配的节点,可以使用以下函数:

xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables = 0) const;
xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables = 0) const;


select_nodes 函数编译表达式,然后将节点作为上下文节点执行,并返回结果节点集。 select_node 只返回结果中按文档顺序排列的第一个节点,等同于调用 select_nodes(query).first() 。如果 XPath 表达式不匹配任何内容,或者节点句柄为空,则 select_nodes 返回空集, select_node 返回空 XPath 节点。


如果未禁用异常处理,则在查询无法编译或返回的值类型不是节点集时,两个函数都会抛出 xpath_exception;详情请参阅错误处理。


虽然编译表达式的速度很快,但如果在小的子树上多次使用相同的表达式,编译时间可能会带来很大的开销。如果要进行许多类似的查询,可以考虑将它们编译成查询对象(更多信息请参阅使用查询对象)。获得编译后的查询对象后,就可以将其传递给选择函数,而不是表达式字符串:

xpath_node xml_node::select_node(const xpath_query& query) const;
xpath_node_set xml_node::select_nodes(const xpath_query& query) const;


如果未禁用异常处理,则在查询返回的值类型不是节点集类型时,两个函数都会抛出 xpath_exception。


这是一个使用 XPath 表达式选择节点的示例(samples/xpath_select.cpp):

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";


8.3.使用查询对象


以表达式字符串为参数调用 select_nodes 时,会在后台创建一个查询对象。查询对象代表一个已编译的 XPath 表达式。在以下情况下可能需要查询对象:


  • 如果出现问题,可以预编译表达式来查询对象,以节省编译时间;


  • 您可以使用查询对象来评估 XPath 表达式,从而得到布尔、数字或字符串;


  • 您可以通过查询对象获取表达式值的类型。


查询对象对应 xpath_query 类型。查询对象是不可变和不可复制的:创建时与表达式绑定,不能克隆。如果要将查询对象放在容器中,可以通过 new 运算符在堆上分配查询对象,并在容器中存储指向 xpath_query 的指针,或者使用 C11 编译器(在 C11 中查询对象是可移动的)。


您可以使用将 XPath 表达式作为参数的构造函数创建查询对象:

explicit xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables = 0);


表达式将被编译,编译后的表达式将存储在新的查询对象中。如果编译失败,在未禁用异常处理的情况下会抛出 xpath_exception(详见错误处理)。创建查询后,可以使用以下函数查询评估结果的类型:

xpath_value_type xpath_query::return_type() const;


您可以使用以下函数之一对查询进行评估:

bool xpath_query::evaluate_boolean(const xpath_node& n) const;
double xpath_query::evaluate_number(const xpath_node& n) const;
string_t xpath_query::evaluate_string(const xpath_node& n) const;
xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const;
xpath_node xpath_query::evaluate_node(const xpath_node& n) const;


所有函数都将上下文节点作为参数,计算表达式并返回转换为请求类型的结果。根据 XPath 规范,任何类型的值都可以转换为布尔值、数字值或字符串值,但节点集以外的任何类型都不能转换为节点集。因此, evaluate_booleanevaluate_numberevaluate_string 总是返回结果,但如果返回类型不是节点集, evaluate_node_setevaluate_node 则会导致错误(请参阅错误处理)。

 注意

调用 node.select_nodes("query") 相当于调用 xpath_query("query").evaluate_node_set(node) 。调用 node.select_node("query") 相当于调用 xpath_query("query").evaluate_node(node)


请注意, evaluate_string 函数返回 STL 字符串;因此,它在 PUGIXML_NO_STL 模式下不可用,而且通常会分配内存。还有另一个字符串评估函数:

size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const;


该函数对字符串进行求值,然后将结果写入 buffer (但最多写入1 个字符);然后返回结果的完整字符数,包括结束符 0。如果 capacity 不是 0,则结果缓冲区总是以 0 结尾。该函数的使用方法如下:


  • 首先以 buffer = 0capacity = 0 调用函数;然后分配返回的字符数,并再次调用函数,传递分配的存储空间和字符数;


  • 首先用较小的缓冲区和缓冲区容量调用函数;然后,如果结果大于容量,说明输出已被修剪,因此分配一个较大的缓冲区并再次调用函数。


这是一个使用查询对象的示例 ( samples/xpath_query.cpp):

// Select nodes via compiled query
pugi::xpath_query query_remote_tools("/Profile/Tools/Tool[@AllowRemote='true']");

pugi::xpath_node_set tools = query_remote_tools.evaluate_node_set(doc);
std::cout << "Remote tool: ";
tools[2].node().print(std::cout);

// Evaluate numbers via compiled query
pugi::xpath_query query_timeouts("sum(//Tool/@Timeout)");
std::cout << query_timeouts.evaluate_number(doc) << std::endl;

// Evaluate strings via compiled query for different context nodes
pugi::xpath_query query_name_valid("string-length(substring-before(@Filename, '_')) > 0 and @OutputFileMasks");
pugi::xpath_query query_name("concat(substring-before(@Filename, '_'), ' produces ', @OutputFileMasks)");

for (pugi::xml_node tool = doc.first_element_by_path("Profile/Tools/Tool"); tool; tool = tool.next_sibling())
{
    std::string s = query_name.evaluate_string(tool);

    if (query_name_valid.evaluate_boolean(tool)) std::cout << s << std::endl;
}

 8.4.使用变量


XPath 查询可能包含对变量的引用;如果您想使用依赖于某些动态参数的查询,而无需手动准备完整的查询字符串,或者您想在类似的查询中重复使用相同的查询对象,那么 XPath 查询就非常有用。


变量引用的形式为 $name ;要使用变量引用,必须提供一个变量集,其中包括查询中所有类型正确的变量。这个变量集会传递给1 个构造函数或 select_nodes /3 个函数:

explicit xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables = 0);
xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables = 0) const;
xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables = 0) const;


如果使用查询对象,可以在 evaluate / select 调用前更改变量值,以改变查询行为。

 注意

变量集指针存储在查询对象中;必须确保变量集的生命周期超过查询对象的生命周期。


变量集与 xpath_variable_set 类型相对应,后者本质上是一个变量容器。


您可以使用以下函数添加新变量:

xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type);


函数会尝试添加一个具有指定名称和类型的新变量;如果集合中不存在具有该名称的变量,函数会添加一个新变量并返回变量句柄;如果已经存在一个具有指定名称的变量,如果变量具有指定类型,函数会返回变量句柄。否则,函数返回空指针;分配失败时也返回空指针。


新变量的默认值取决于其类型:数字为 0 ,布尔型为 false ,字符串为空字符串,节点集为空集。


您可以使用以下函数获取现有变量:

xpath_variable* xpath_variable_set::get(const char_t* name);
const xpath_variable* xpath_variable_set::get(const char_t* name) const;


函数返回变量句柄,如果找不到指定名称的变量,则返回空指针。


此外,还有通过名称设置变量值的辅助函数;如果相应类型的变量不存在,它们会尝试添加该变量,并设置其值。如果同名但不同类型的变量已经存在,则返回 false ;如果分配失败,则返回 false 。请注意,这些函数不执行任何类型转换。

bool xpath_variable_set::set(const char_t* name, bool value);
bool xpath_variable_set::set(const char_t* name, double value);
bool xpath_variable_set::set(const char_t* name, const char_t* value);
bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value);


变量值会被复制到内部变量存储空间,因此可以在函数返回后修改或销毁它们。


如果通过名称设置变量不够高效,或者需要检查变量信息或获取变量值,可以使用变量句柄。变量对应于 xpath_variable 类型,而变量句柄只是指向 xpath_variable 的指针。


要获取变量信息,可以使用以下函数之一:

const char_t* xpath_variable::name() const;
xpath_value_type xpath_variable::type() const;


请注意,每个变量都有一个不同的类型,该类型在创建变量时指定,以后不能更改。


要获取变量值,应根据变量类型使用以下函数之一:

bool xpath_variable::get_boolean() const;
double xpath_variable::get_number() const;
const char_t* xpath_variable::get_string() const;
const xpath_node_set& xpath_variable::get_node_set() const;


这些函数返回变量的值。需要注意的是,这些函数不进行类型转换;如果出现类型不匹配,则返回一个虚拟值(布尔型为 false ,数字型为 NaN ,字符串型为空字符串,节点集型为空集)。


要设置变量值,应根据变量类型使用以下函数之一:

bool xpath_variable::set(bool value);
bool xpath_variable::set(double value);
bool xpath_variable::set(const char_t* value);
bool xpath_variable::set(const xpath_node_set& value);


这些函数修改变量值。请注意,函数不进行类型转换;如果出现类型不匹配,函数返回 false ;如果分配失败,函数也返回 false 。变量值会被复制到内部变量存储空间,因此可以在函数返回后修改或销毁它们。


这是一个在 XPath 查询中使用变量的示例(samples/xpath_variables.cpp):

// Select nodes via compiled query
pugi::xpath_variable_set vars;
vars.add("remote", pugi::xpath_type_boolean);

pugi::xpath_query query_remote_tools("/Profile/Tools/Tool[@AllowRemote = string($remote)]", &vars);

vars.set("remote", true);
pugi::xpath_node_set tools_remote = query_remote_tools.evaluate_node_set(doc);

vars.set("remote", false);
pugi::xpath_node_set tools_local = query_remote_tools.evaluate_node_set(doc);

std::cout << "Remote tool: ";
tools_remote[2].node().print(std::cout);

std::cout << "Local tool: ";
tools_local[0].node().print(std::cout);

// You can pass the context directly to select_nodes/select_node
pugi::xpath_node_set tools_local_imm = doc.select_nodes("/Profile/Tools/Tool[@AllowRemote = string($remote)]", &vars);

std::cout << "Local tool imm: ";
tools_local_imm[0].node().print(std::cout);

 8.5 错误处理


XPath 实现中有两种不同的错误处理机制;使用哪种机制取决于是否禁用了异常支持(这由 PUGIXML_NO_EXCEPTIONS 定义控制)。


默认情况下,XPath 函数出错时会抛出0 个对象;此外,如果内存分配失败,也会抛出1 个异常。如果查询被求值为节点集,但返回类型不是节点集,也会抛出 xpath_exception 个异常。如果查询构造函数成功(即没有抛出异常),则查询对象有效。否则,您可以通过以下函数之一获取错误详情:

virtual const char* xpath_exception::what() const throw();
const xpath_parse_result& xpath_exception::result() const;


如果禁用了异常,那么在解析失败的情况下,查询将被初始化为无效状态;你可以在布尔表达式中使用查询对象来测试它是否有效: if (query) { …​ } 。此外,还可以通过 result() 访问器获取解析结果:

const xpath_parse_result& xpath_query::result() const;


毫无例外,根据类型的不同,评估无效查询的结果是 false 、空字符串、 NaN 或空节点集;如果返回类型不是节点集,则评估作为节点集的查询的结果是空节点集。


解析结果信息通过 xpath_parse_result 对象返回。它包含解析状态和最后一个成功解析的字符与源流开头的偏移量:

struct xpath_parse_result
{
    const char* error;
    ptrdiff_t offset;

    operator bool() const;
    const char* description() const;
};


解析结果用错误信息表示;如果没有错误,则是一个空指针,或者是以 ASCII 零端字符串形式表示的错误信息。


description() 成员函数可以用来获取错误信息;它永远不会返回空指针,因此即使查询解析成功,也可以安全地使用 description() 。请注意,即使在 PUGIXML_WCHAR_MODE 中, description() 也会返回 char 个字符串;您必须调用 as_wide 才能获得5 个字符串。


除了错误信息外,解析结果还有一个 offset 成员,其中包含最后一个成功解析的字符的偏移量。偏移量以 pugi::char_t 为单位(字符模式为字节,宽字符模式为宽字符)。


解析结果对象可以这样隐式转换为 boolif (result) { …​ } else { …​ } .


这是一个 XPath 错误处理示例(samples/xpath_error.cpp):

// Exception is thrown for incorrect query syntax
try
{
    doc.select_nodes("//nodes[#true()]");
}
catch (const pugi::xpath_exception& e)
{
    std::cout << "Select failed: " << e.what() << std::endl;
}

// Exception is thrown for incorrect query semantics
try
{
    doc.select_nodes("(123)/next");
}
catch (const pugi::xpath_exception& e)
{
    std::cout << "Select failed: " << e.what() << std::endl;
}

// Exception is thrown for query with incorrect return type
try
{
    doc.select_nodes("123");
}
catch (const pugi::xpath_exception& e)
{
    std::cout << "Select failed: " << e.what() << std::endl;
}


8.6.符合 W3C 规范


由于文档对象模型、性能考虑因素和实现复杂性方面的差异,pugixml 没有提供完全一致的 XPath 1.0 实现。这是当前的不兼容性列表:


  • 共享同一父节点的连续文本节点不会合并,即 <node>text1 <![CDATA[data]]> text2</node> 节点本应有一个文本节点子节点,但却有三个。


  • 由于文档类型声明不用于解析,因此 id() 函数总是返回空节点集。


  • 不支持命名空间节点(影响 namespace:: 轴)。


  • 名称测试是对 XML 文档中的 QNames 而不是扩展名称进行的;对于 <foo xmlns:ns1='uri' xmlns:ns2='uri'><ns1:child/><ns2:child/></foo> ,查询 foo/ns1:* 将只返回第一个子节点,而不会同时返回这两个节点。如果用户提供了适当的命名空间声明,符合要求的 XPath 实现可以同时返回这两个节点。


  • 字符串函数将字符视为单个 char 值或单个 wchar_t 值,具体取决于库配置;这意味着某些字符串函数不能完全识别 Unicode。这影响到 substring()string-length()translate() 函数。

 9.更新日志

v1.14 2023-10-01


维护版本。更改:

  •  改进:


    1. xml_attribute::set_namexml_node::set_name 现在有了接受非空字符串指针和大小的重载


    2. 实施 parse_merge_pcdata 解析模式,当原始文档中的注释在解析过程中被跳过时,PCDATA 内容会被合并到一个节点中


    3. xml_document::load_file 现在在给出文件夹路径时返回的错误状态更加一致

  •  错误修复:


    1. 修复使用非英语本地语言时 XPath 数字→字符串转换中的断言


    2. 修正 PUGIXML_STATIC_CRT CMake 选项,以便在使用 MSVC 和最新 CMake 时正确选择静态 CRT


  • 兼容性改进


    1. 修复 GCC 2.95/3.3 版本


    2. 修正 CMake 3.27 过时警告


    3. 在 C++03 模式下编译时修复 XCode 14 sprintf 过时警告


    4. 修正 clang/gcc 警告 -Wweak-vtables , -Wreserved-macro-identifier

v1.13 2022-11-01


维护版本。更改:

  •  改进:


    1. xml_attribute::set_valuexml_node::set_valuexml_text::set 现在有了接受非空字符串指针和大小的重载


    2. 改善使用紧凑模式时的树遍历性能 ( PUGIXML_COMPACT )

  •  错误修复:


    1. 修正 xml_document::save_file 中可能导致函数成功但磁盘空间耗尽的错误处理方法


    2. 修复在 xml_document::load 时处理某些内存不足情况的错误过程中的内存泄漏问题


  • 兼容性改进


    1. 使用 CMake 时,修复 CMake DLL 编译中的导出符号


    2. 修复使用 -fvisibility=hidden 时 CMake 共享对象构建中的导出符号问题

 v1.12 2022-02-09


维护版本。更改:

  •  错误修复:


    1. 当移动源为空时,修复 xml_document 移动构造中的一个错误


    2. 修正迭代器对象的常量正确性问题,以支持 C++20 范围

  •  XPath 改进:


    1. 改进了对过于复杂的查询的检测,这些查询可能会在解析过程中导致堆栈溢出


  • 兼容性改进


    1. 修复 DLL 生成的 Cygwin 支持


    2. 修复 Windows CE 支持


    3. 为 VS2022 添加 NuGet 构建和项目文件

  •  构建系统更改


    1. 现在所有 CMake 选项的前缀都是 PUGIXML_ 。这可能需要更改依赖的构建配置。


    2. 现在,许多编译设置通过 CMake 设置公开,最明显的是 PUGIXML_COMPACTPUGIXML_WCHAR_MODE 可以在不更改 pugiconfig.hpp 的情况下设置

 v1.11 2020-11-26


维护版本。更改:

  •  新功能


    1. 添加 xml_node::remove_attributes 和 xml_node::remove_children


    2. 通过 xml_attribute::set 和 xml_text::set 重载,添加自定义浮点精度的方法

  •  XPath 改进:


    1. XPath 解析器现在可限制递归深度,从而防止恶意查询的堆栈溢出


  • 兼容性改进


    1. 修复使用 clang-cl 编译器编译时的 Visual Studio 警告


    2. 修复 gcc 中的 Wconversion 警告


    3. 修复 pugixml.hpp 中的 Wzero-as-null-pointer-constant 警告


    4. 解决若干静态分析误报问题

  •  构建系统更改


    1. pugixml 的 CMake 软件包现在提供 pugixml::pugixml 目标而非 pugixml 目标。如果未请求至少 1.11 版本,则提供兼容性 pugixml 目标。

 v1.10 2019-09-15


维护版本。更改:

  •  行为改变:


    1. 属性值中的 Tab 字符(ASCII 9)现在编码为"'",以避免往返处理


    2. 属性值中不再转义为 > 字符

  •  新功能


    1. 添加 Visual Studio .natvis 文件,改善调试体验


    2. CMake 的改进(用于构建多个版本的 USE_POSTFIX 和 BUILD_SHARED_AND_STATIC_LIBS 选项以及 pkg-config 调整)


    3. 添加 format_skip_control_chars 格式标志,以跳过无法在格式良好的 XML 文件中使用的不可打印的 ASCII 字符


    4. 添加 format_attribute_single_quote 格式化标志,以便对属性值使用单引号,而不是默认的双引号。

  •  XPath 改进:


    1. XPath 联合现在可以产生不依赖内存分配的稳定顺序;重要的是,如果依赖文档有序遍历,这可能需要对 XPath 查询操作的输出进行排序


    2. 提高 XPath 联合操作的性能,使其速度提高 ~2 倍


  • 兼容性改进


    1. 修复在 DLL 配置中构建时的 Visual Studio 警告


    2. 修复 Coverity 和 clang 中的静态分析误报问题


    3. 修正 gcc 中的 Wdouble-promotion 警告


    4. 添加 Visual Studio 2019 对 NuGet 软件包的支持

 v1.9 2018-04-04


维护版本。更改:

  •  规格变更:


    1. xml_document::load(const char*) (在 1.5 中被弃用)现在有1 个属性;请使用 xml_document::load_string 代替


    2. xml_node::select_single_node (在 1.5 中被弃用)现在有1 个属性;请使用 xml_node::select_node 代替

  •  新功能


    1. 为 xml_document 添加移动语义支持,并改进对其他对象的移动语义支持


    2. CMake 编译现在可导出包含目录


    3. 使用 BUILD_SHARED_LIBS=ON 的 CMake 构建现在会使用 MSVC 的 dllexport 属性

  •  XPath 改进:


    1. 重做解析器/评估器,不再依赖异常控制流;禁用异常时不再使用 longjmp


    2. 改进某些无效表达式(如 .[1](1 )的错误信息


    3. 性能小幅提升


  • 兼容性改进


    1. 修复德州仪器编译器警告


    2. 修复某些版本 gcc 中 limits.h 的编译问题


    3. 修复 Clang/C2 的编译问题


    4. 修正 gcc 7 中的隐式突破警告


    5. 修正 gcc 8 中的未知属性指令警告


    6. 修复 cray++ 编译器错误


    7. 使用 -fsanitize=integer 时修正无符号整数溢出错误


    8. 修复紧凑模式下未定义的行为消除器问题

 v1.8 2016-11-24


维护版本。更改:

  •  规格变更:


    1. 打印空元素时,在 format_raw 模式下,"/"之前不再添加空格

  •  新功能


    1. 添加了 parse_embed_pcdata 解析模式,在该模式下,PCDATA 值会尽可能存储在元素节点中(大大减少了某些文档的内存消耗)。


    2. 在解析过程中添加了对 Latin-1 (ISO-8859-1) 编码的自动检测支持


    3. 添加了 format_no_empty_element_tags 格式化标记,可输出开始/结束标记,而不是空元素的空元素标记


  • 性能改进:


    1. 内存分配略有改进(在某些情况下可节省多达 1%的内存)


  • 兼容性改进


    1. 修正了 Borland C++ 5.4 的编译问题


    2. 修正了 MinGW 3.8 某些发行版的编译问题


    3. 修正了各种 Clang/GCC 警告


    4. 为 MSVC 2010 及以上版本的 XPath 对象启用移动语义支持

 v1.7 2015-10-19


重大版本,改进了性能和内存,并增加了一些新功能。更改:

  •  紧凑模式


    1. 引入了新的树存储模式,大大减少了内存占用(将 DOM 缩减了 2-5 倍),但在一定程度上降低了性能。


    2. 使用 PUGIXML_COMPACT 定义可以启用该模式。


  • 新的整数解析/格式化实现:


    1. 整数之间的转换函数(如 as_int / set_value )不再依赖 CRT。


    2. 新实现的速度是原来的 3-5 倍,而且无论溢出还是下溢都是正确的。这是一个行为变化--以前 as_uint() 在值为"-1 "时会返回 UINT_MAX,现在则返回 0。

  •  新功能


    1. 如果您的编译器支持 C++11,XPath 对象 ( xpath_query , xpath_node_set , xpath_variable_set ) 现在是可移动的。 此外, xpath_variable_set 也是可复制的。


    2. 添加了 format_indent_attributes ,使生成的 XML 对行差异/合并工具更友好。


    3. 添加了 xml_node::attribute 函数的变体,该变体带有提示,可提高查找性能。


    4. 现在允许(但不要求)自定义分配函数抛出而不是返回空指针。

  •  错误修复:


    1. 修复 Clang 3.7 在内存不足的情况下崩溃的问题(C++ DR 1748)


    2. 修复 XPath 在 SPARC64(以及其他双倍必须对齐到 8 字节的 32 位体系结构)上崩溃的问题


    3. 修正 xpath_node_set 赋值,以提供强有力的异常保证


    4. 修复可从 write() 引发的自定义 xml_writer 实现的保存问题

 v1.6 2015-04-10


维护版本。更改:

  •  规格变更:


    1. 属性/文本值现在在打印浮点数时使用更多位数,以保证往返。


    2. 在漂亮打印具有混合内容的节点时,文本节点不再获得额外的空白区域

  •  错误修复:


    1. 修正了翻译和规范化空间 XPath 函数,使其不再返回内部 NUL 字符


    2. 修正了 DOCTYPE 部分内畸形注释的缓冲区超限问题


    3. DOCTYPE 解析不再会因畸形输入而耗尽堆栈空间(XML 解析现在使用有界堆栈空间)。


    4. 调整了处理指令输出,以避免在 PI 值包含 ?> 时出现畸形文档

 v1.5 2014-11-27


重大版本,改进了大量性能并增加了一些新功能。

  •  规格变更:


    1. xml_document::load(const char_t*) 更名为 load_string ;旧方法仍然可用,并将在未来的版本中弃用


    2. xml_node::select_single_node 更名为 select_node ;旧方法仍然可用,并将在未来的版本中被弃用。

  •  新功能


    1. 添加了在文档中移动节点的 xml_node::append_move 和其他功能


    2. 已添加 xpath_query::evaluate_node ,用于评估结果为单个节点的查询


  • 性能改进:


    1. 优化 XML 解析(使用 clang/gcc 时速度提高 10-40%,使用 MSVC 时提高 10)


    2. 优化了复制同一文档中的节点时的内存消耗(现在共享字符串内容)


    3. 优化了节点复制(跨文档复制的速度提高了 10%,跨文档复制的速度提高了 3 倍;此外,它现在消耗的堆栈空间数量不变)


    4. 优化了节点输出(速度提高了 60%;而且现在消耗的堆栈空间保持不变)


    5. 优化了 XPath 分配(查询评估现在可减少临时分配次数)


    6. 优化 XPath 排序(在某些情况下,节点集排序速度提高了 2-3 倍)


    7. 优化的 XPath 评估(XPathMark 套件的速度提高了 100 倍;某些常用查询的速度提高了 3-4 倍)


  • 兼容性改进


    1. 角情况固定为 xml_node::offset_debug


    2. 修正了某些情况下调用 memcpy 时的未定义行为


    3. 修正了 MSVC 2015 编译警告


    4. 为 Boost 1.56.0 修正了 contrib/foreach.hpp

  •  错误修复


    1. 调整了注释输出,以避免在注释值为 -- 时出现畸形文档


    2. 修复使用 append_buffer 构建的文档的 XPath 排序问题


    3. 在启用 C++11 模式的 MinGW 中,修复 load_file 字符宽路径中的非 ASCII 字符问题

 v1.4 2014-02-27


重大版本,包含各种新功能、错误修复和兼容性改进。

  •  规格变更:


    1. 除非使用 parse_fragment 选项,否则不含元素节点的文档现在会以 status_no_document_element 错误被拒绝

  •  新功能


    1. 已添加 XML 片段解析功能( parse_fragment 标志)


    2. 已添加 PCDATA 空白修剪( parse_trim_pcdata 标志)


    3. 添加了对 xml_attributexml_textas_llongas_ullongset_value / set 重载)的超长支持


    4. 已为 as_int / as_uint / as_llong / as_ullong 添加十六进制整数解析支持


    5. 添加了 xml_node::append_buffer ,以提高从片段组装文档的性能


    6. xml_named_node_iterator 现在是双向的


    7. 减少编译和评估过程中 XPath 栈的消耗(适用于嵌入式系统)


  • 兼容性改进


    1. 改进对不支持 wchar_t 的平台的支持


    2. 修正了 clang 静态分析中的几处误报


    3. 修正了多个 GCC 版本的编译警告

  •  错误修复:


    1. 修正了 XPath 实现中未定义的指针运算


    2. 修正了对某些流类型的不可搜索 iostream 支持,即使用管道输入的 Boost file_source


    3. 修正了某些表达式的 xpath_query::return_type


    4. 修正了 xml_named_node_iterator 的 dllexport 问题


    5. 修正了名称/值为空的属性的 find_child_by_attribute 断言

 v1.2 2012-05-01


主要版本,具有纯头文件模式、各种界面增强功能(如 PCDATA 操作和 C++11 迭代)、许多其他功能和兼容性改进。

  •  新功能


    1. 已添加0 个辅助类,用于处理元素节点的 PCDATA/CDATA 内容


    2. 添加了可选的纯标题模式(由 PUGIXML_HEADER_ONLY 定义控制)


    3. 已为 C++11 添加 xml_node::children()xml_node::attributes() ,用于有变化的 for 循环或 BOOST_FOREACH


    4. 在加载和保存过程中增加了对 Latin-1 (ISO-8859-1) 编码转换的支持


    5. xml_attribute::as_* 添加了自定义默认值(如果属性不存在,则返回默认值)


    6. 添加了 parse_ws_pcdata_single 标志,用于在 PCDATA 是唯一子代的情况下保留仅留白的 PCDATA


    7. 添加了 " format_save_file_text "换 " xml_document::save_file ",以文本而非二进制形式打开文件(在 Windows 上更改换行符)


    8. 已添加 format_no_escapes 标志以禁用特殊符号转义(补充1)


    9. 已添加对从不支持寻道的流中加载文档的支持


    10. 添加了0 个常数,用于调整分配行为(适用于嵌入式系统)


    11. 添加了0 个预处理器定义


  • 兼容性改进


    1. 解析器不需要 setjmp 支持(提高了与某些嵌入式平台的兼容性,可实现 /clr:pure 编译)


    2. 不再使用 STL 前向声明(修复了 SunCC/RWSTL 编译,修复了 C++11 模式下的 clang 编译)


    3. 修复了 AirPlay SDK、Android、Windows Mobile (WinCE) 和 C++/CLI 的编译问题


    4. 修正了多个 GCC 版本、Intel C++ 编译器和 Clang 的编译警告

  •  错误修复:


    1. 修正了不安全的 bool 转换,以避免在 C++/CLI 中出现问题


    2. 迭代器取消引用操作符现在是 const(修复 Boost filter_iterator 支持)。


    3. xml_document::save_file 现在会在保存过程中检查文件 I/O 错误

 v1.0 2010-11-01


主要版本,具有许多 XPath 增强功能、宽字符文件名支持、各种性能改进、错误修复等。

  •  XPath:


    1. XPath 实现移至 pugixml.cpp (现在是唯一的源文件);如果要禁用 XPath 以减少代码量,请使用 PUGIXML_NO_XPATH


    2. XPath 现在支持无异常 ( PUGIXML_NO_EXCEPTIONS );错误处理机制取决于是否存在异常支持


    3. 现在支持 XPath,无需 STL ( PUGIXML_NO_STL )


    4. 引入变量支持


    5. 引入新的 xpath_query::evaluate_string ,无需 STL 即可运行


    6. 引入新的 xpath_node_set 构造函数(来自迭代器范围)


    7. 评估功能现在可接受属性上下文节点


    8. 所有内部分配都使用自定义分配功能


    9. 改进了错误报告;现在,最后解析的偏移量会与解析错误一起返回

  •  错误修复:


    1. 修正了在开启流异常的情况下从流加载时的内存泄漏问题


    2. 修正了在一种情况下使用空指针调用自定义解除分配函数的问题


    3. 修复了迭代器类别函数的缺失属性;现在所有函数/类都可通过 DLL 导出


    4. 解决了 Digital Mars 编译器的错误,该错误导致在几个函数中出现轻微的读取超量调用现象


    5. load_file 现在可在 MSVC/MinGW 中处理 2GB 以上的文件


    6. XPath:修复了错误查询的内存泄漏问题


    7. XPath:修正了 xpath_node() 属性构造函数中的空属性参数


    8. XPath:修正了非 ASCII 参数的 lang() 功能

  •  规格变更:


    1. 包含 ]]> 的 CDATA 节点被打印为多个节点;虽然这会改变内部结构,但这是转义 CDATA 内容的唯一方法


    2. 解析过程中的内存分配错误现在会保留最后解析的偏移量(以便了解解析进度)


    3. 如果元素节点只有一个子节点,且该子节点为 CDATA 类型,则省略额外的缩进(以前只有 PCDATA 子节点才有这种行为)。


  • 附加功能


    1. 已添加 xml_parse_result 默认构造函数


    2. 添加了带有宽字符路径的 xml_document::load_filexml_document::save_file


    3. std::wstring / std::string 个参数添加了 as_utf8as_wide 重载


    4. 已添加 DOCTYPE 节点类型 ( node_doctype ) 和特殊解析标志 parse_doctype ,以便在解析过程中将此类节点添加到文档中


    5. 已添加 parse_full 解析标志屏蔽,它扩展了 parse_default ,包含了除 parse_ws_pcdata 以外的所有节点类型解析标志


    6. 添加了 xml_node::hash_value()xml_attribute::hash_value() 函数,以便在基于散列的容器中使用


    7. xml_nodexml_attribute 添加了 internal_object()