当前位置: 首页 > article >正文

【XML协议】轻松掌握使用C++ XML解析库——pugixml

文章介绍了xml协议的组成以及C++ xml解析库pugixml的常用操作。源于开发中每次遇到xml操作时,都要回过头查看pugixml库常用操作时什么样的,能不能有个更深刻和清晰的认识呢?其实搞清楚xml结构和pugixml组织结构的对照关系,以及pugixml中节点、属性的增删改查逻辑,可以帮助我们快速回忆起这些东西。遂,本文留作查询使用。

XML协议

XML(Extensible Markup Language)是一种可扩展标记语言,在后端开发中,常作为一种数据格式进行消息的传输、存储。我们先来了解一下XML协议通常包含哪些内容。

  1. XML声明

<?xml version="1.0" encoding="UTF-8"?> xml声明常位于文件头,指明xml版本、编码、standalone。

  • version: 指明xml版本
  • encoding: 指明文本编码
  • standalone: “yes” 表示不需要依赖外部文件,"no"表示依赖外部文件,一般用于schema校验等(后端目前没碰到这种场景,不再深究)
  1. PI(processing instruction)

<?xml-stylesheet type="text/xsl" href="style.xsl"?> pi信息通常是指明样式表给到浏览器使用。

  1. 文档类型(doc type)

<!DOCTYPE library [
   <!ELEMENT library (book+)>
   <!ELEMENT book (title, author, description)>
   <!ELEMENT title (#PCDATA)>
   <!ELEMENT author (firstName, lastName)>
   <!ELEMENT firstName (#PCDATA)>
   <!ELEMENT lastName (#PCDATA)>
   <!ELEMENT description (#PCDATA)>
]> 

文档类型,对xml内容的解释,常用于浏览器校验xml内容是否合法。

  1. 文档、节点、属性、注释

xml正文是由一个个节点组成,每个节点可以包含若干子节点、属性。下图给出了一个xml示例及其组成,最右侧是对应到pugixml库中的节点类型。
xml结构示例

注意这里的data和cdata, 后者是原始文本不会被xml解析,例如有时我们的属性包含一些特殊字符,这些字符可能会破坏xml结构,此时要作用cdatta使用。

pugixml解析库

pugixml项目,是一个C++ xml解析库,主要实现包含在pugixml.h pugixml.cpp pugiconfig.hpp 三个文件中,提供了xml解析、生成、xpath等功能。主要包含以下三个类:
pugixml主要的类

  • xml_document: 对应着一个xml文档
  • xml_node: 对应着xml中的节点
  • xml_attribute: xml节点的属性

解析

pugixml解析提供从字符流、字符串、buffer、文件这几种方式加载,解析就是生成和初始化xml_document的过程。
加载方法
下面是一个示例,

  const std::string src = R"(
<Document user_id="5001">
  <display>
    <mode>1</mode>
    <item color="blue" name="global" />
    <item color="red" name="global" />
  </display>
</Document>
)";
  pugi::xml_document doc;
  // 从字符串加载,不能指定编码类型
  pugi::xml_parse_result result = doc.load_string(src.c_str(), pugi::parse_default);
  // 从buffer中加载,可以指定buffer中的编码类型
  result = doc.load_buffer(src.c_str(), src.size(), pugi::parse_default, pugi::encoding_utf8);
  // 从本地文件中加载
  result = doc.load_file("./test.xml", pugi::parse_default, pugi::encoding_utf8);
  if (result.status == pugi::xml_parse_status::status_ok) {
    doc.print(std::cout);
  } else {
    std::cout << result.description() << std::endl;
  }

load返回的是parse_result类型,该类型记录了加载的状态码、编码、加载偏移、描述信息等。

	struct PUGIXML_CLASS xml_parse_result {
		// Parsing status (see xml_parse_status)
		xml_parse_status status;
		// Last parsed offset (in char_t units from start of input data)
		ptrdiff_t offset;
		// Source document encoding
		xml_encoding encoding;
		// Default constructor, initializes object to failed state
		xml_parse_result();
		// Cast to bool operator
		operator bool() const;
		// Get error description
		const char* description() const;
	};

查询

xml_document 继承自xml_node, 除了父类方法外,自己实现了load,reset这些逻辑,其他接口和父类一致,所以增删改查也就是xml_node的增删改查。增删改是在查到指定节点后进行set操作,所以掌握查询方法更为关键。

<Document user_id="5001" >
  <display>
    <mode>1</mode>
    <item color="blue" name="global" />
    <item color="red" name="global" />
  </display>
</Document>
  • 根据节点名称获取节点
    例如查询mode节点相关的信息:
    pugi::xml_node node = doc.child("Document").child("display").child("mode");
    std::cout << "name=" << node.name() << ",text=" << node.text() << std::endl;
  • 根据xpath获取节点(select_node)
    这里需要明确,select返回的是xpath_node类型,该类型可能包含的是节点,也可能是属性,需要调用方清晰知道到底是哪一种类型以便调用不同的方法获取值。
  pugi::xpath_node xpath_result = doc.select_node("/Document/display/mode");
  std::cout << "name=" << xpath_result.node().name()
            << ",text=" << xpath_result.node().text() << std::endl;
  • 根据属性名获取属性(attribute("name"))
std::cout << doc.child("Document").attribute("user_id").as_string() << std::endl;
  • 根据xpath获取属性(/.../node/@key)
  pugi::xpath_node first_item =
      doc.select_node("/Document/display/item/@color");
  std::cout << "name=" << first_item.attribute().name()
            << ",text=" << first_item.attribute().value() << std::endl;
  • 获取指定属性的节点(/.../node[@attr='val'])
  auto ret = doc.select_node("/Document/display/item[@color='blue']");
  std::cout << "name=" << ret.node().name() << std::endl;
  • 遍历节点
    提供了循环遍历的迭代器,也可以使用for-each。遍历属性与此类似,不再赘述
  // 1. children
  {
    auto children = display.children();
    for (const auto &ele : children) {
      std::cout << ele.name() << std::endl;
    }
  }
  {
    auto children = display.children("item");
    for (const auto &ele : children) {
      std::cout << ele.name() << std::endl;
    }
  }

  // 2. itr
  for (auto itr = display.begin(); itr != display.end(); ++itr) {
    std::cout << itr->name() << std::endl;
  }

修改

对于一个节点来说,有哪些内容呢。节点本身的name, text, 以及包含的属性(属性名称、属性值)。
注意节点的text同样是一个结构体,不存在直接的set_text()方法
插入节点时提供了一些带位置参数的接口,例如在某节点之前和之后插入。

    pugi::xml_node ret =
        doc.select_node("/Document/display/item[@color='blue']").node();
    ret.set_name("item_new");
    ret.text().set("abs");
    // ret.set_text();
    // 注意这里,设置text值,text是一个结构,不存在直接的set_text()方法
    ret.set_value("abs");

    ret.attribute("color").set_value("new blue");     // 修改已有属性值
    ret.append_attribute("modify").set_value("2024"); // 添加新的属性

    auto new_node = ret.parent().append_child("item");  // 在其后追加节点
    new_node.append_attribute("color").set_value("white");
    doc.print(std::cout);

删除

  • xml_document 重置状态时reset接口。
  • remove_child、remove_attribute 对应移除单个子节点和属性;remove_children、remove_attributes对应移除所有子节点和属性。

序列化

将document生成字符串的过程

  • 字符流
    format参数可以自定义,这里取得是不进行格式化。
  std::stringstream out;
  doc.save(out, "\t", pugi::format_raw);
  std::cout << out.str() << std::endl;
  • 字符串
class XmlStringWriter : public pugi::xml_writer {
public:
  void write(const void *data, size_t size) {
    result.assign(reinterpret_cast<const char *>(data), size);
  }

  std::string result;
};

XmlStringWriter writer;
doc.save(writer);
std::cout << writer.result << std::endl;

总结

首先,使用pugixml时要注意,该库支持unicode编码,是不支持gbk编码的,解析时对于gbk类型要自己进行转码。
其次,pugixml提供了一些解析的选项,默认选项有时可能会出问题,例如value包含tab时,默认的反序列化参数可能会直接消除tab,关于这些选项(const unsigned int parser_x这些参数),仓库由比较明细的解释。同样,序列化时也有一些选项(const unsigned int format_xx)。
然后,要快速掌握该库的使用,需要明确xml包含哪些结构,例如版本头、节点、属性、注释等,以及对应到pugixml库是哪些结构。需要留意的是对于属性值、text值都是一个结构体,你要知道你操作的不是一个简单的string类型。
最后,该库提供了xpath功能,select_node和select_nodes 返回值可能是包含的节点也可能是属性,调用者要知道自己在筛选什么。而对于xpath至少要搞清楚如何查节点(/.../node)、查属性(/.../node/@attr)、查指定属性的节点((/.../node[@attr='val'])这几个xpath格式是什么样的。


http://www.kler.cn/a/394483.html

相关文章:

  • 安装CPU版的torch(清华源)
  • Scala项目(图书管理系统)
  • CASA(Carnegie-Ames-Stanford Approach) 模型原理及实践
  • NLP 中文拼写检测开源-01-基于贝叶斯公式的拼写检查器 CSC
  • 【机器学习与数据挖掘实战】案例04:基于K-Means算法的信用卡高风险客户识别
  • Kali操作系统简单介绍
  • 电脑技巧:推荐一款开源免费的多显示器管理工具Dual Monitor Tools
  • 【k8s】k8s集群中拉取需要登录的私有镜像库
  • Z分位数速查表
  • 4.1 Android NDK 简介
  • 开源项目低代码表单设计器FcDesigner扩展自定义的容器组件.例如col
  • 电动工具中的扭矩调节系统:原理与优化
  • [每日一氵] PySpark 的 log GC 部分是什么意思
  • 控制中心应该如何选择控制台
  • LeetCode题解:5.最长回文子串【Python题解超详细,中心拓展、动态规划、暴力解法】
  • WPF Prism中的区域(Region)管理
  • 腾讯云双11最强攻略:如何选购优惠产品,薅最划算的羊毛
  • ssm117网络教学平台的设计与实现+vue(论文+源码)_kaic
  • 安卓aab包的安装教程,附带adb环境的配置
  • 电脑局域网内让其他电脑通过IP访问配置
  • ssh远程连接和nfs共享文件系统
  • 不安全 Rust
  • Linux 抓包工具 --- tcpdump
  • 安全见闻六:通讯协议安全问题剖析
  • Innovus Flexible H-tree and Multi-tap Clock Flow Lab实操系列教程(Day1)
  • jdk8 有哪些新特性?