65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 中的流式 XML 解析器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (16投票s)

2013年5月3日

MIT

4分钟阅读

viewsIcon

101009

downloadIcon

2903

C++ 中简单、非验证的流式 XML 解析器。

引言

XmlInspector 是一个用 C++ 编写的流式 XML 解析器。创建这个项目的起因是我找不到任何符合我要求的解析器

  • 简单 - 我喜欢简单。 我不想阅读如何配置解析库的教程,我只想在我的项目中添加一些文件并忘记它们。 我不喜欢学习如何使用成千上万的库类,我更喜欢专注于我自己的项目。
  • 跨平台 - “非验证 C++ XML 解析器”真的需要比 C++ 标准库更多的东西吗? 我不这么认为。
  • 小巧 - 当我想在我的小型 C++ 项目中读取单个 XML 文件时,我是否需要包含数百个用于解析的文件?
  • 符合 XML 标准 - 有些解析器声称速度极快,但它们不能保证每个格式良好的 XML 文件都会被正确解析。 此外,无效的 XML 文件可能会在没有错误消息的情况下通过,这对我来说是不可接受的。

我认为这个项目达到了这些期望,但首先您应该知道两件事

  • 我尝试尊重 XML 建议XML 命名空间建议,除了 DOCTYPE 元素。 如果 XML 文档包含类似 <!DOCTYPE doc...> 的内容,那么将忽略此类元素的所有信息。此 XML 解析器不会解析任何 DTD。
  • XmlInspector 使用 C++11 标准中的一些特性 (char16_t, char32_t, std::u16string, std::u32string, std::uint_least64_t, std::unique_ptr强类型枚举)。 在 Linux 上的 GCC 4.7.2 和 Clang 3.0-6.2、Windows 上的 Visual C++ 2012 和 MinGW 4.7.2 上进行了测试。

XML 解析器有两种最流行的 API

  • DOM - 基于树的解析器,它将整个文档读入树结构。
  • SAX - 基于事件的解析器,它按顺序处理文档的每个部分。

XmlInspector 的思路与 SAX 类似,但您无需注册回调函数并等待事件,而是可以随时从 XML 文档中读取下一个节点。 我认为这是一种更方便的解析 XML 文档的方式。 欲了解更多信息,请搜索“StAX”。 现在让我们解析一些文件

test.xml
<?xml version="1.0"?>
<shelter>
    <!--Only 3 pets at this moment. It's the new shelter.-->
    <pets>
        <dog name="Rambo">Very aggressive dog, be careful.</dog>
        <dog name="Pinky">2 years old dog with a short tail.</dog>
        <cat name="Boris">Very lazy cat.</cat>
    </pets>

    <!--No pokemons yet.-->
    <pokemons />
</shelter>

首先,您需要在项目中添加 3 个头文件

XmlInspector.hpp
CharactersReader.hpp
CharactersWriter.hpp

现在您可以像这样读取一些元素

#include "XmlInspector.hpp"
#include <iostream>
#include <cstdlib>

int main()
{
    Xml::Inspector<Xml::Encoding::Utf8Writer> inspector("test.xml");

    while (inspector.Inspect())
    {
        switch (inspector.GetInspected())
        {
            case Xml::Inspected::StartTag:
                std::cout << "StartTag name: " << inspector.GetName() << "\n";
                break;
            case Xml::Inspected::EndTag:
                std::cout << "EndTag name: " << inspector.GetName() << "\n";
                break;
            case Xml::Inspected::EmptyElementTag:
                std::cout << "EmptyElementTag name: " << inspector.GetName() << "\n";
                break;
            case Xml::Inspected::Text:
                std::cout << "Text value: " << inspector.GetValue() << "\n";
                break;
            case Xml::Inspected::Comment:
                std::cout << "Comment value: " << inspector.GetValue() << "\n";
                break;
            default:
                // Ignore the rest of elements.
                break;
        }
    }

    if (inspector.GetErrorCode() != Xml::ErrorCode::None)
    {
        std::cout << "Error: " << inspector.GetErrorMessage() <<
            " At row: " << inspector.GetRow() <<
            ", column: " << inspector.GetColumn() << ".\n";
    }

    return EXIT_SUCCESS;
}

结果

StartTag name: shelter
Comment value: Only 3 pets at this moment. It's the new shelter.
StartTag name: pets
StartTag name: dog
Text value: Very aggressive dog, be careful.
EndTag name: dog
StartTag name: dog
Text value: 2 years old dog with a short tail.
EndTag name: dog
StartTag name: cat
Text value: Very lazy cat.
EndTag name: cat
EndTag name: pets
Comment value: No pokemons yet.
EmptyElementTag name: pokemons
EndTag name: shelter

Xml::Inspector 是主要的解析器类。 Xml::Encoding::Utf8Writer 是用于以 UTF-8 编码写入字符的类。 这意味着您将收到对 std::string 的引用。 如果您更喜欢例如 UTF-16 编码和 std::u16string,您可以编写

Xml::Inspector<Xml::Encoding::Utf16Writer> inspector("test.xml");
// ...
const std::u16string& referenceToElementName = inspector.GetName();

Xml::Inspector 类有更多构造函数,例如,您可能希望解析存储在内存中的 XML 文档

std::string doc = "abc";
Xml::Inspector<Xml::Encoding::Utf16Writer> inspector(doc.begin(), doc.end());

您还可以使用单个 Inspector 对象解析更多 XML 文档 - 存在 Xml::Inspector::Reset 方法,其参数与构造函数中的参数相同

Xml::Inspector<Xml::Encoding::Utf16Writer> inspector;
inspector.Reset("test.xml");
// ...
std::string doc = "<root>abc</root>";
inspector.Reset(doc.begin(), doc.end());

每次调用 Xml::Inspector::Inspect 方法都会尝试从文档中读取一个元素。 当检查的元素例如是 StartTag 时,Xml::Inspector::GetName 方法将提供此元素的名称。 如果检查的元素例如是 ProcessingInstruction,那么您将从 Xml::Inspector::GetName 方法中收到此处理指令的目标名称。

现在让我们只打印狗的名字

#include "XmlInspector.hpp"
#include <iostream>
#include <cstdlib>

int main()
{
    Xml::Inspector<Xml::Encoding::Utf8Writer> inspector("test.xml");

    while (inspector.Inspect())
    {
        if ((inspector.GetInspected() == Xml::Inspected::StartTag ||
            inspector.GetInspected() == Xml::Inspected::EmptyElementTag) &&
            inspector.GetName() == "dog")
        {
            Xml::Inspector<Xml::Encoding::Utf8Writer>::SizeType i;
            for (i = 0; i < inspector.GetAttributesCount(); ++i)
            {
                if (inspector.GetAttributeAt(i).Name == "name")
                {
                    std::cout << inspector.GetAttributeAt(i).Value << "\n";
                    break;
                }
            }
        }
    }

    if (inspector.GetErrorCode() != Xml::ErrorCode::None)
    {
        std::cout << "Error: " << inspector.GetErrorMessage() <<
            " At row: " << inspector.GetRow() <<
            ", column: " << inspector.GetColumn() << ".\n";
    }

    return EXIT_SUCCESS;
}

结果

Rambo
Pinky

正如您所看到的,访问属性名称与 Xml::Inspector 方法略有不同。 属性是一个简单的结构,包含一些字符串,没有任何方法。

工作原理

XmlInspector 不会将整个 XML 文档加载到内存中。 它会分配它需要的内存来遍历元素,因此文件的大小并不重要。 您甚至可以在您的计算机上拥有比 XML 文档大小更少的内存。

如果某个元素需要更多字符串分配,则下一个元素不会删除它,即使现在不需要此字符串。 考虑这个文件

<root name1="value1" name2="value2" name3="value3">
    <aaa attr="abc" />
    <bbb name1="value1" name2="value2" />
</root>

根元素需要为三个属性分配空间。 元素 aaa 只有一个属性,所以现在不需要另外两个属性。 如果元素 aaa 将删除这些字符串,则下一个元素 bbb 将需要再次为属性分配空间。 XmlInspector 尝试减少下一个元素和文档的分配次数。 如果您不再需要 Xml::Inspector 对象,您可以调用 Xml::Inspector::Clear 方法,或等待析构函数释放一些空间。

支持的编码

XmlInspector 支持的编码集

  • UTF-8, UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE;
  • ISO-8859-1, ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6, ISO-8859-7, ISO-8859-8, ISO-8859-9, ISO-8859-10, ISO-8859-13, ISO-8859-14, ISO-8859-15, ISO-8859-16, TIS-620;
  • windows-874, windows-1250, windows-1251, windows-1252, windows-1253, windows-1254, windows-1255, windows-1256, windows-1257, windows-1258.

参考文献

© . All rights reserved.