pugixml 1.10快速入门指南

pugixml 1.10快速入门指南

网站https://pugixml.org存储库https://github.com/zeux/pugixml

目录

pugixml 1.10快速入门指南

介绍

安装

文件物件模型

载入文件

访问文档数据

修改文件资料

保存文件

反馈

执照


介绍

pugixml是一个轻量级的C ++ XML处理库。它由具有丰富遍历/修改功能的类DOM接口,非常快的XML解析器(从XML文件/缓冲区构造DOM树)以及用于复杂数据驱动的树查询的XPath 1.0实现组成。还提供完全的Unicode支持,具有两种Unicode接口变体以及不同Unicode编码之间的转换(在解析/保存期间自动发生)。该库具有极高的可移植性,易于集成和使用。pugixml从2006年开始开发和维护,拥有许多用户。所有代码均根据MIT许可进行分发,从而使其完全免费在开源和专有应用程序中使用。

pugixml支持非常快速,便捷和内存有效的XML文档处理。但是,由于pugixml具有DOM解析器,因此它无法处理内存中不适合的XML文档。解析器也是非验证解析器,因此如果您需要DTD / Schema验证,则该库不适合您。

这是pugixml的快速入门指南,其目的是使您能够快速开始使用该库。许多重要的库功能或者根本没有描述,或者只是简短地提及。有关更完整的信息,请阅读完整的手册

安装

您可以下载最新的源分发作为存档:

pugixml-1.10.zip(Windows行尾)/ pugixml-1.10.tar.gz(Unix行尾)

该发行版包含库源代码,文档(您正在阅读的指南和手册)以及一些代码示例。下载发行版后,通过从压缩档案中提取所有文件来安装pugixml。

完整的pugixml源代码包含三个文件-一个源文件pugixml.cpp和两个头文件pugixml.hpppugiconfig.hpppugixml.hpp是要使用pugixml类/函数需要包括的主要标头。本指南的其余部分假定该文件pugixml.hpp位于项目的当前目录中或项目的包含目录之一中,以便#include "pugixml.hpp"可以找到标头。但是,您也可以使用相对路径(即#include "../libs/pugixml/src/pugixml.hpp")或包含目录相对路径(即#include <xml/thirdparty/pugixml/src/pugixml.hpp>)。

生成pugixml的最简单方法是编译源文件pugixml.cpp,以及现有的库/可执行文件。此过程取决于构建应用程序的方法。例如,如果您使用的是Microsoft Visual Studio [ 1 ],Apple Xcode,Code :: Blocks或任何其他IDE,则只需将其添加pugixml.cpp到您的项目中。还有其他可用的构建方法,包括将pugixml构建为独立的静态/共享库。阅读手册以获取更多信息。

文件物件模型

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

树的根是文档本身,它对应于C ++ type xml_document。文档具有一个或多个子节点,它们对应于C ++ type xml_node。节点具有不同的类型;根据类型,节点可以具有子节点的集合,与C ++类型相对应的属性的集合xml_attribute以及一些其他数据(即名称)。

最常见的节点类型是:

  • 文档节点(node_document)-这是树的根,由几个子节点组成。该节点对应于xml_documentclass;注意,xml_document是一个子类的xml_node,所以整个节点接口也同样有效。

  • 元素/标签节点(node_element)-这是最常见的节点类型,代表XML元素。元素节点具有名称,属性的集合和子节点的集合(两者均可以为空)。该属性是一个简单的名称/值对。

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

尽管有几种类型的节点,有表示树只有三个C ++类型(xml_documentxml_nodexml_attribute); 某些操作xml_node仅对某些节点类型有效。他们在下面描述。

注意

所有的pugixml类和函数都位于pugi名称空间中。您必须使用显式名称限定(即pugi::xml_node),或通过using指令(即using pugi::xml_node;using namespace pugi;)获得对相关符号的访问权限。

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(); 如果节点是空节点或没有父节点,则第一次parent()调用返回空节点;parent()然后,第二个调用也返回空节点,因此您不必两次检查错误。您可以通过隐式boolean cast: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接口的更多信息。

载入文件

pugixml提供了几种从不同位置加载XML数据的功能-文件,C ++ iostream,内存缓冲区。所有函数都使用非常快速的非验证解析器。此解析器不完全符合W3C规范-它可以加载任何有效的XML文档,但不执行某些格式正确的检查。尽管已做出很大的努力来拒绝无效的XML文档,但是由于性能原因,未执行某些验证。XML数据始终在解析之前转换为内部字符格式。pugixml支持所有流行的Unicode编码(UTF-8,UTF-16(大和小尾数),UTF-32(大和小尾数);自然支持UCS-2,因为它是UTF-16的严格子集)并处理所有自动编码转化。

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

这是一个从文件(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;

load_file以及其他加载功能,将销毁现有文档树,然后尝试从指定文件加载新树。操作的结果在一个xml_parse_result对象中返回;该对象包含操作状态和相关信息(即,如果解析失败,则在输入文件中最后成功解析的位置)。

解析结果对象可以隐式转换到bool; 如果你不想彻底处理解析错误,你可以检查负载函数的返回值,如果它是一个boolif (doc.load_file("file.xml")) { …​ } else { …​ }。否则,您可以使用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::istreamstd::wistream接口。

从内存加载文档有不同的功能。他们将传递的缓冲区视为不变的(load_buffer),调用方拥有的可变缓冲区(load_buffer_inplace)或所有权属于pugixml(load_buffer_inplace_own)的可变缓冲区。还有一个简单的帮助程序功能,xml_document::load用于需要从以空字符结尾的字符串加载XML文档的情况。

这是一个使用以下函数之一从内存中加载XML文档的示例(sample / 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;

这是一个使用流(samples / load_stream.cpp)从文件中加载XML文档的简单示例;阅读示例代码以获取涉及广泛流和语言环境的更复杂示例:

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

访问文档数据

pugixml具有广泛的接口,可从文档中获取各种类型的数据并遍历文档。您可以使用各种访问器来获取节点/属性数据,可以通过访问器或迭代器遍历子节点/属性列表,可以对xml_tree_walker对象进行深度优先遍历,还可以将XPath用于复杂的数据驱动查询。

您可以通过name()访问器获取节点或属性名称,并通过value()访问器获取值。请注意,两个函数都绝不会返回空指针-它们要么返回包含相关内容的字符串,要么返回空字符串(如果名称/值不存在或句柄为空)。另外,有两个值得注意的东西可以读取值:

  • 通常将数据存储为某个节点的文本内容,即<node><description>This is a node</description></node>。在这种情况下,<description>节点没有值,而是具有类型node_pcdata为value的子级"This is a node"。pugixml提供child_value()text()辅助功能解析这样的数据。

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

这是使用这些功能的示例(sample / 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")返回名称为的第一个节点,"Tool"如果没有这样的节点,则返回null句柄。这是使用此类函数的示例(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_siblingnext_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";

警告

XPath函数将xpath_exception对象抛出错误;上面的示例没有捕获这些异常。

修改文件资料

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

更改节点/属性数据或结构的所有成员函数都是非恒定的,因此不能在恒定的句柄上调用。但是,您可以通过简单的赋值轻松地将常量句柄转换为非常量句柄:void foo(const pugi::xml_node& n) { pugi::xml_node nc = n; },因此这里的const-correctness主要提供其他文档。

如前所述,节点可以具有名称和值,它们都是字符串。根据节点类型,名称或值可能不存在。您可以使用set_nameset_value成员函数进行设置。类似的功能可用于属性。但是,set_value对于字符串以外的其他一些类型,该函数将重载,例如浮点数。另外,可以使用赋值运算符设置属性值。这是设置节点/属性名称和值(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;
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;

没有文档树就没有节点和属性,因此如果不将它们添加到某些文档中就无法创建它们。可以在节点/属性列表的末尾或其他某个节点之前/之后创建一个节点或属性。所有插入函数在成功时都将句柄返回到新创建的对象,而在失败时将其返回空句柄。即使操作失败(例如,如果您尝试将子节点添加到PCDATA节点),文档仍保持一致状态,但不会添加请求的节点/属性。

警告

attribute()child()函数不会在树上添加属性或节点,因此,如果代码node.attribute("id") = 123;没有nodename属性,像like这样的代码将不会执行任何操作"id"。通过添加必要的属性/节点来确保您正在使用它们。

这是向文档中添加新属性/节点的示例(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";

如果您不希望文档包含某些节点或属性,则可以使用remove_attributeremove_child函数将其删除。删除属性或节点会使指向同一基础对象的所有句柄无效,并使指向同一对象的所有迭代器也无效。删除节点还会使所有其属性或子节点列表的后端迭代器无效。小心确保删除属性/节点后不存在或不使用所有此类句柄和迭代器。

这是从文档(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);

保存文件

通常在创建新文档或加载现有文档并对其进行处理之后,有必要将结果保存回文件中。将整个文档或子树输出到某个流有时也很有用;用例包括调试打印,通过网络或其他面向文本的介质进行序列化等。pugixml提供了多种功能,可将文档的任何子树输出到文件,流或其他通用传输接口;这些功能允许自定义输出格式,并执行必要的编码转换。

在写入目标之前,根据节点类型正确格式化节点/属性数据;所有特殊的XML符号(例如<和&)均已正确转义。为了防止遗忘的节点/属性名称,将空节点/属性名称打印为":anonymous"。对于格式正确的输出,请确保所有节点和属性名称均设置为有意义的值。

如果要将整个文档保存到文件中,则可以使用该save_file函数,该函数true成功返回。这是将XML文档保存到文件(samples / save_file.cpp)的简单示例:

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

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

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

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

以上所有保存功能均通过编写器接口实现。这是一个具有单个功能的简单界面,在输出过程中以文档数据块作为输入多次被调用。为了通过某些自定义传输(例如套接字)输出文档,您应该创建一个实现xml_writer_file接口的对象并将其传递给xml_document::save函数。

这是用于将文档数据保存到STL字符串(sample / 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);
    }
};

前面描述的功能将整个文档保存到目标位置时,很容易保存单个子树。除了调用之外xml_document::save,只需xml_node::print在目标节点上调用函数即可。您可以通过这种方式将节点内容保存到C ++ IOstream对象或自定义编写器。保存子树与保存整个文档略有不同。阅读手册以获取更多信息。

反馈

如果您认为自己在pugixml中发现了错误,请通过问题提交表单提交问题。确保包括相关信息,以便可以重现该错误:pugixml的版本,编译器版本和目标体系结构,使用pugixml并显示该bug的代码等。功能请求和贡献也可以作为问题提交。

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

执照

pugixml库是根据MIT许可证分发的:

版权所有(c)2006-2019 Arseny Kapoulkine 

特此免费授予任何
获得本软件和相关文档
文件(以下简称“软件”)副本的任何人,以
不受限制地(包括但不限于)使用,
复制,修改,合并,发布,分发,再许可和/或出售
软件副本的权利,并允许
提供软件的人员这样做,但须满足以下
条件:

上述版权声明和该许可通知应
包含在本软件的所有副本或大部分内容中。

该软件按“原样”提供,没有任何形式的保证,
明确或隐含的,包括但
不限于对
适销性,特定目的的适用性和非侵权性的保证。在任何情况下,作者或版权
持有者都不对任何索赔,损坏或其他责任,
无论是在合同的ACTION,侵权或其他原因,
FROM,OUT的或在软件或使用或连接
其他交易软件。

这意味着您可以在开源和专有应用程序中自由使用pugixml。如果您在产品中使用pugixml,则向产品分发中添加这样的确认就足够了:

该软件基于pugixml库(https://pugixml.org)。
pugixml是Arseny Kapoulkine版权所有(C)2006-2019。
posted @ 2022-04-01 21:56  Wang_h_f  阅读(2475)  评论(0编辑  收藏  举报