C++ 中的流式 XML 解析器






4.67/5 (16投票s)
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”。 现在让我们解析一些文件
<?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.