C/C++编程:pugixml

1059 篇文章 275 订阅

pugixml是一个轻量级的C ++ XML操作库。

Pugixml包含三个文件pugixml.cpp、 pugixml.hpp 、 pugiconfig.hpp。

  • pugixml.hpp是主头文件,要使用pugixml类及其方法必须将它包含到工程中。
  • pugiconfig.hpp是pugixml的配置文件,例如配置是否使用宽字符模式(#define PUGIXML_WCHAR_MODE)
  • pugixml.cpp是源文件,该文件需设置为不使用预编译头。

Pugixml节点类型

在这里插入图片描述
XML数据加载和输出
在这里插入图片描述
字符集识别和转换
在这里插入图片描述

文档对象模型

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

树的根是文档本身,它对应于C类型xml_document. 文档有一个或多个子节点,这些子节点对应于C类型xml节点. 节点有不同的类型;根据类型的不同,节点可以有一组子节点,一组属性,这些属性对应于C类型xml_attribute,以及一些附加数据(即名称)。

最常见的节点类型有:

  • 文档节点(node_document)——这是树的根,它由几个子节点组成。这个节点对应于xml_document类;注意,xml_document是xml_node的一个子类,因此整个节点接口也是可用的。
  • 元素/标记节点(node_element)——这是最常见的节点类型,它表示XML元素。元素节点有一个名称、一个属性集合和一个子节点集合(两者都可以是空的)。该属性是一个简单的名称/值对。
  • 纯字符数据节点(node_pcdata)表示XML中的纯文本。PCDATA节点有一个值,但没有名称或子/属性。注意,普通字符数据不是元素节点的一部分,而是有自己的节点;例如,一个元素节点可以有几个子PCDATA节点。

尽管存在几种节点类型,但表示树的c++类型只有三种(xml_document、xml_node、xml_attribute);xml_node上的某些操作仅对某些节点类型有效。它们描述如下。

  • Xml_document是整个文档结构的所有者;其功能包含加载/保存文档等,销毁它将销毁整个文档。xml_document的接口由加载函数、保存函数和xml_node的整个接口组成,该接口允许检查和/或修改文档。注意,虽然xml_document是xml_node的子类,但xml_node不是多态类型;继承只是为了简化使用。
  • Xml_node是文档节点的句柄;它可以指向文档中的任何节点,包括文档本身。所有类型的节点都有一个公共接口。注意,xml_node只是实际节点的句柄,而不是节点本身——可以有几个xml_node句柄指向相同的底层对象。销毁xml_node句柄不会销毁该节点,也不会将其从树中删除。
    xml_node类型有一个特殊值,称为空节点或空节点。它不对应于任何文档中的任何节点,因此类似于空指针。但是,所有操作都是在空节点上定义的;通常,这些操作不做任何事情,返回空节点/属性或空字符串作为其结果。这对于链接调用很有用;也就是说,你可以像这样获得一个节点的祖父结点:node.parent().parent();如果一个节点是一个空节点或它没有父节点,第一个父()调用返回null节点;第二个parent()调用也返回null节点,因此您不必检查错误两次。你可以通过隐式布尔类型转换来测试句柄是否为空:if (node){…}或if (!node){…}。
  • xml_attribute是XML属性的句柄;它具有与xml_node相同的语义,即可以有多个xml_attribute句柄指向相同的底层对象,并且有一个特殊的null属性值,它将传播到函数结果。

在配置pugixml时,有两种接口和内部表示方式可供选择:您可以选择UTF-8(也称为char)接口或UTF-16/32(也称为wchar_t)接口。选择是通过PUGIXML_WCHAR_MODE定义控制的;你可以通过pugiconfig.hpp或预处理器选项来设置它。所有处理字符串的树函数都处理c风格的null结束字符串或选定字符类型的STL字符串。阅读手册了解Unicode接口的更多信息。

使用

<Root>
    <students>
        <student>
            <name>张三</name>
            <sex></sex>
        </student>
        <student>
            <name>李四</name>
            <sex></sex>
        </student>
        <student>
            <name>王五</name>
            <sex></sex>
        </student>
    </students>
</Root>
void Read()

{

    pugi::xml_document doc;

    if (doc.load_file("students.xml",pugi::parse_default,pugi::encoding_utf8))
    {
        pugi::xml_node root_node = doc.child(_T("Root"));
        pugi::xml_node students_node = root_node.child(_T("students "));
        // 分别读取每个学生信息
        for (pugi::xml_node student_node = students_node.child(_T("student"));
            student _node;
            student _node = student _node.next_sibling(_T("student ")))
        {
            pugi::xml_node name_node = students_node.child(_T("name"));
            printf("name : %s\n",name_node.first_child().value());
            pugi::xml_node sex_node = student_node.child(_T("sex"));
            printf("sex: %s\n",sex_node.first_child().value());
        }
    }
}
 

正在加载文档

pugixml提供了几个从不同位置加载XML数据的函数-文件、iostream、内存缓冲区。所有函数都使用一个非常快速的非验证解析器。这个解析器不完全符合W3C—它可以加载任何有效的XML文档,但不执行一些格式良好的检查。虽然在拒绝无效的XML文档方面做了大量的工作,但是由于性能原因,有些验证没有执行。XML数据总是在解析之前转换为内部字符格式。pugixml支持所有流行的Unicode编码(UTF-8、UTF-16(big和little-endian)、UTF-32(big和little-endian);UCS-2自然受到支持,因为它是UTF-16的严格子集),并自动处理所有编码转换。

最常见的XML数据源是文件;pugixml为从文件加载XML文档提供了一个单独的函数。这个函数接受文件路径作为它的第一个参数,以及两个可选参数,它们指定解析选项和输入数据编码,这在手册中有描述。

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

pugi::xml_document doc;

pugi::xml_parse_result result = doc.load_file("tree.xml");

std::cout << "Load result: " << result.description() << ", mesh name: " << doc.child("mesh").attribute("name").value() << std::endl;

Load_file和其他加载函数都会破坏现有的文档树,然后尝试从指定的文件加载新树。操作的结果以xml_parse_result对象的形式返回;该对象包含操作状态和相关信息(例如,如果解析失败,则最后一次成功解析的位置在输入文件中)。

解析结果对象可以隐式转换为bool类型;如果你不想彻底处理解析错误,你可以检查加载函数的返回值,就像它是一个bool值一样:否则,您可以使用status成员来获取解析状态,或者使用description()成员函数以字符串形式获取状态。

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

有时XML数据应该从其他源而不是文件加载,例如HTTP URL;你也可以使用非标准函数从文件中加载XML数据,例如,使用虚拟文件系统工具或从gzip压缩文件加载XML。这些场景都需要从内存加载文档,在这种情况下,你应该准备一个连续的内存块与所有XML数据并将其传递给一个缓冲区加载功能,或从c++ IOstream加载文档,在这种情况下,您应该提供一个对象实现std:: istream或std:: wistream接口。

从内存中加载文档有不同的功能;它们将传递的缓冲区视为不可变缓冲区(load_buffer)、可变缓冲区(由调用者拥有)或可变缓冲区(所有者属于pugixml)。还有一个简单的辅助函数xml_document::load,用于从以空结束的字符串加载XML文档。

这是一个使用以下函数之一从内存中加载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_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;

这是一个使用流从文件中加载XML文档的简单示例(samples/load_stream.cpp);阅读示例代码,获得更复杂的涉及宽流和区域设置的示例:

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

访问文档数据

Pugixml提供了一个扩展接口,用于从文档中获取各种类型的数据,并用于遍历文档。可以使用各种访问器获取节点/属性数据,可以通过访问器或迭代器遍历子节点/属性列表,可以使用xml_tree_walker对象进行深度优先遍历,还可以使用XPath进行复杂的数据驱动查询。

您可以通过name()或者value()来获取名字和值。请注意,这两个函数从不返回空指针-它们要么返回包含相关内容的字符串,要么返回空字符串(如果名称/值不存在或句柄为null)。此外,还有两件值得注意的事情可以用来解读价值观:

  • 您可以通过name()访问器获取节点或属性名称,通过value()访问器获取值。注意,这两个函数都不会返回空指针——它们要么返回一个带有相关内容的字符串,要么返回一个空字符串(如果name/value不存在,或者如果句柄为空)。对于阅读值,还有两个值得注意的事情:
  • 通常将数据存储为某个节点的文本内容,如< node>< description>This is a node< /description>< /node>。在本例中,< description>节点没有值,而是有一个类型为node_pcdata的子节点,值为“This is a node”。Pugixml提供了child_value()和text()助手函数来解析这些数据。

在许多情况下,属性值的类型不是字符串,也就是说,属性可能始终包含应被视为整数的值,尽管它们在XML中表示为字符串。pugixml提供了几个将属性值转换为其他类型的访问器。

下面是使用以下函数的示例(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";
}

由于许多文档遍历都是查找具有正确名称的节点/属性,因此有一些特殊的函数用于此目的。例如,child(“Tool”)返回具有名称的第一个节点“工具”,或空句柄(如果没有此类节点).下面是使用这些函数的示例(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";
}

子节点列表和属性列表是简单的双链接列表;虽然可以使用previous_sibling/next_sibling和其他类似的函数进行迭代,但pugixml还提供了节点和属性迭代器,因此可以将节点视为其他节点或属性的容器。所有迭代器都是双向的,并支持所有常见的迭代器操作。如果迭代器所指向的节点/属性对象被从树中移除,则迭代器将失效;添加节点/属性不会使任何迭代器失效。

下面是一个使用迭代器遍历文档的示例(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;
}

如果你的c++编译器支持基于范围的for循环(这是c++ 11的一个特性,在编写时它已经被Microsoft Visual Studio 11 Beta, GCC 4.6和Clang 3.0所支持),你可以使用它来枚举节点/属性。提供额外的助手来支持这一点;请注意,它们也兼容Boost Foreach,可能还有其他c++ 11之前的Foreach工具。

下面是一个使用c++ 11基于范围的for循环遍历文档的示例(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;
}

上面描述的方法允许遍历某个节点的直接子节点;如果你想做一个深树遍历,你必须通过递归函数或其他等价方法来做。但是,pugixml为子树的深度优先遍历提供了一个助手。要使用它,必须实现xml_tree_walker接口并调用traverse函数。

下面是使用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);

最后,对于复杂的查询,通常需要更高级别的DSL。pugixml为此类查询提供了XPath 1.0语言的实现。手册中提供了有关XPath用法的完整说明,但以下是一些示例:

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";
  • 10
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值