pugixml 是一个轻量级的 C++ XML 处理库,具有极强的可移植性,易于集成和使用。
pugixml 是一个轻量级、高性能的 C++ XML 处理库,采用 DOM 模型(非流式解析),核心设计围绕 节点(node)、属性(attribute) 和 文本(text) 三大对象。
1 环境配置
1️⃣ 下载项目
项目地址:
👉GitHub - zeux/pugixml: Light-weight, simple and fast XML parser for C++ with XPath support · GitHub
git拉取:
git clone https://github.com/zeux/pugixml.git
2️⃣ 文件需求说明
无需编译,只需要src下的纯文本文件,目录结构如下:
src
└─ pugiconfig.hpp
└─ pugixml.cpp
└─ pugixml.hpp
✅ 无需 .lib,无需编译
实际在使用时,加载pugixml.hpp头文件即可。
#include "pugixml.hpp"
2 文档对象模型
pugixml以类似DOM的方式存储XML数据:整个XML文档(包括文档结构和元素数据)以树的形式存储在内存中。该树可以从字符流(文件、字符串、C++ I/O流)加载,然后通过特殊API或XPath表达式进行遍历。整个树是可变的:节点结构和节点/属性数据都可以随时更改。最后,文档转换的结果可以保存到字符流(文件、C++ I/O流或自定义传输)中。
树的根节点是文档本身,对应C++类型xml_document。文档拥有一个或多个子节点,这些子节点对应C++类型xml_node。节点具有不同类型;根据类型不同,一个节点可以包含一组子节点、一组属性(对应C++类型xml_attribute)以及一些额外数据(例如名称)。
❓📚🚧🐛
→
2.1 核心对象关系
graph LR
xml_document[xml_document] -->|包含| xml_node[xml_node]
xml_node -->|包含| xml_attribute[xml_attribute]
xml_node -->|包含| xml_text[xml_text]
xml_node -->|包含| 子节点
xml_document:根对象,代表整个 XML 文档。xml_node:通用节点(元素、注释、文本等),90% 操作围绕它展开。xml_attribute:节点的属性(如<node attr="value">中的attr)。xml_text:节点的文本内容(通过.text()获取)。
2.2 文档加载与保存
1️⃣ 加载 XML
pugi::xml_document doc;
// 从文件加载(自动检测编码)
pugi::xml_parse_result result = doc.load_file("file.xml");
// 检查错误(关键!)
if (!result) {
std::cerr << "Parse error: " << result.description()
<< " at offset " << result.offset << std::endl;
return;
}
注意:
- 必须检查
result!空文件/格式错误会返回false,但不会抛出异常。
2️⃣ 保存 XML
// 保存到文件(格式化/压缩)
doc.save_file("output.xml", " ", 1); // " "=缩进, 1=格式化(0=压缩)
// 保存到字符串
std::string buffer;
doc.save_string(buffer, nullptr, pugi::format_default);关键选项:
pugi::format_indent:缩进格式化(默认)。pugi::format_no_declaration:不输出<?xml ...?>声明。pugi::format_save_file_text:强制 UTF-8 编码(避免 BOM 问题)。
3️⃣基础遍历
// 获取根节点
pugi::xml_node root = doc.document_element();
// 遍历所有子节点(包括元素/文本/注释等)
for (pugi::xml_node child : root.children()) {
if (child.type() == pugi::node_element) { // 过滤非元素节点
std::string name = child.name(); // 元素名(如 "Website")
}
}
// 按标签名遍历子节点(推荐!跳过非元素节点)
for (pugi::xml_node website : root.children("Website")) {
// 直接处理 <Website> 元素
}
最佳实践:
- 优先用
children("tag")而非children()+ 手动过滤,避免误处理文本/注释节点。 node.name()返回 C 风格字符串(const char\*),比较时用strcmp或转std::string。
2.1.as_xxx() 系列方法详解
pugixml 为 xml_attribute 和 xml_text 提供了类型安全的转换方法,避免手动处理字符串。所有方法均自动处理空值(返回默认值),无需显式检查指针。
1. xml_attribute 的转换方法
表格
| 方法 | 作用 | 空属性/非法值时的返回值 | 示例用法 |
|---|---|---|---|
.as_string() | 转换为 std::string | ""(空字符串) | attr.as_string() → "1"(若属性值为 id="1") |
.as_int(int def = 0) | 转换为 int | def(默认 0) | attr.as_int() → 1(若 id="1") |
.as_uint(unsigned int def = 0) | 转换为无符号整型 | def(默认 0) | attr.as_uint() → 4294967295(若 id="-1" 且平台为 32 位) |
.as_double(double def = 0) | 转换为 double | def(默认 0.0) | attr.as_double() → 3.14(若 value="3.14") |
.as_bool(bool def = false) | 转换为 bool | def(默认 false) | attr.as_bool() → true(若 enabled="1" 或 "true") |
关键特性:
- 空属性处理:若属性不存在,直接返回默认值(如
as_int()返回0),无需额外检查。 - 类型转换逻辑:
- 数字类型:按 C 风格解析(如
"123abc"→123,"abc"→0)。 - 布尔类型:仅当值为
"1","true","t","y","yes"时返回true,其余为false。
- 数字类型:按 C 风格解析(如
- 安全边界:
- 整型溢出时行为未定义(如
as_int("2147483648")在 32 位系统可能返回负数)。 - 需自定义校验时,应先获取字符串再手动解析(如用
std::stoi捕获异常)。
- 整型溢出时行为未定义(如
2. xml_text 的转换方法(用于节点文本内容)
当通过 child("year").text() 获取 xml_text 对象时,使用相同方法:
1int year = website.child("year").text().as_int(); // ✅ 正确:text() 返回 xml_text
表格
| 方法 | 作用 | 空文本/非法值时的返回值 |
|---|---|---|
.as_string() | 转换为 std::string | "" |
.as_int(int def = 0) | 转换为 int | def |
.as_bool(bool def = false) | 转换为 bool | def |
与 xml_attribute 的区别:
-
空值来源不同:
xml_attribute空:属性不存在。xml_text空:节点无文本内容(如<year></year>)。
-
节点文本需通过
.text()获取:1// 正确:child("year") 返回 xml_node → text() 返回 xml_text 2website.child("year").text().as_int(); 3 4// 错误:child("year") 是节点,不能直接调用 as_int() 5website.child("year").as_int(); // ❌ 编译失败
3 示例
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<Website id="1">
<title>pgmpocket</title>
<address>https://pgmpocket.com</address>
<author>quanshuang</author>
<year>2025</year>
</Website>
<!-- <Website id="2"> -->
<!-- <title>Design Patterns</title> -->
<!-- <author>Gang of Four</author> -->
<!-- <year>1994</year> -->
<!-- </Website> -->
</Root>
#include <iostream>
#include <string>
#include "pugixml.hpp"
#ifdef _WIN32
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
#include <windows.h>
#else
#include <unistd.h>
#include <limits.h>
#endif
std::string get_executable_path()
{
#ifdef _WIN32
char path[MAX_PATH];
GetModuleFileNameA(nullptr, path, MAX_PATH);
PathRemoveFileSpecA(path);
return std::string(path);
#elif defined(__linux__)
char path[PATH_MAX];
ssize_t count = readlink("/proc/self/exe", path, PATH_MAX);
if (count != -1) {
path[count] = '\0';
char* lastSlash = strrchr(path, '/');
if (lastSlash) *lastSlash = '\0';
return std::string(path);
}
return "";
#else
return "";
#endif
}
int main()
{
pugi::xml_document doc;
// 加载 XML 文件
pugi::xml_parse_result result = doc.load_file((get_executable_path() + "\\demo.xml").c_str());
if (!result)
{
std::cout << "XML load fail: " << result.description() << std::endl;
return 1;
}
pugi::xml_node root = doc.child("Root");
if (!root) {
return false;
}
// 遍历所有 book 节点
for (pugi::xml_node website : root.children("Website"))
{
std::string id = website.attribute("id").as_string();
std::string title = website.child("title").text().as_string();
std::string address = website.child("address").text().as_string();
std::string author = website.child("author").text().as_string();
int year = website.child("year").text().as_int();
std::cout << "Website ID : " << id << "\n"
<< " Title : " << title << "\n"
<< " Address : " << address << "\n"
<< " Author : " << author << "\n"
<< " Year : " << year << "\n"
<< std::endl;
}
return 0;
}