可移植的 C++ XML 序列化
一个开放的 C++ 库,
引言
XML 序列化对象对于许多应用程序都非常重要。
C# 使用 System.Xml.Serialization
命名空间提供了一种简单的方法来完成此操作。
然而,标准 C++ 在这方面功能薄弱。
Xmlser 是一个开源库,允许您轻松地将对象序列化/反序列化为 XML。
背景
您应该对 XPATH 表达式有所了解。只需了解基础知识即可。
给定一个 XML 文档
<Person first_name = "F">
<last_name> L </last_name>
</Person>
要选择 Person
(XML 文档的根元素),请使用 XPATH 表达式:/Person
。
要选择 Person
的属性 first_name
:/Person/@first_name。
正如您注意到的,@
用于指定属性。
/Person/last_name
是一个用于选择 last_name
元素的 XPATH 表达式。
星号 (*) 用于表示任何内容
/Person/*
在此示例中等同于/Person/last_name
/Person/@*
在此示例中等同于/Person/@first_name
Using the Code
公共类
只需使用 3 个类,您可以在 xmlser.h 中找到所有这些类
XmlSerializable
XmlReader
XmlWriter
要使您的对象可 XML 序列化,您必须继承自 XMLSerializable
。
如您在源代码 test.cpp 中看到的,Point2D
类是 XMLSerializable
的。
class Point2D : public XmlSerializable
一旦继承了 XmlSerializable
,类就应该实现两个 abstract
成员:writeXml
和 readXml
。对于 Point2D
,我们将 x 和 y 坐标存储为 double,我们应该指定(作为 XPATH 表达式)将它们放在哪里。
void writeXml(XmlWriter& ws)
{
ws.setDouble(L"/Point2D/x",x);
ws.setDouble(L"/Point2D/y",y);
}
void readXml(XmlReader& rs)
{
rs.getDouble(L"/Point2D/x",x);
rs.getDouble(L"/Point2D/y",y);
}
用于读/写的 XML 结果如下所示:
<Point2D>
<x> value of x as double </x>
<y> value of y as double </y>
</Point2D>
请注意,在进行序列化时使用的字符串是 Unicode 字符串(必须使用 L 前缀,例如:L"bla bla")。
备注
我用于序列化对象的表达式语法是 XPATH 语法的子集,并非所有 XPATH 表达式都可以使用,而是具有以下约束的 XPATH 表达式:
- 始终从根 "/" 开始(绝对表达式而非相对表达式,XPATH 允许相对表达式)。
- 属性只能出现在叶节点。
- 不使用任何 XPATH 函数。
- 仅在表达式末尾使用星号,不允许使用
@*
。
要将 XML 保存到磁盘,您可以轻松地使用 saveTo
方法,该方法接收一个 XmlWriter
对象。
Point2D pt(11.3, 15.5);
XmlWriter writer;
pt.writeXml(writer);
writer.saveTo("c:\\myPt.xml");
实际上,XmlWriter
对象是一个:内存中的 XML 文档累加器。
您可以使用 saveTo
方法随时将其转储。
要将 XML 从磁盘读取为 Point2D
对象,您应该使用 XmlReader
和 loadFrom
方法。
XmlReader reader;
Point2D pt;
reader.loadFrom("c:\\myPt.xml");
pt.readXml(reader);
使用特性
您可能更喜欢将 x
保存为属性,将 y
保存为元素,在这种情况下,请将 Point2D
类中的 readXml
和 writeXml
实现更改为以下内容:
void writeXml(XmlWriter& ws)
{
ws.setDouble(L"/Point2D/@x",x);
ws.setDouble(L"/Point2D/y",y);
}
void readXml(XmlReader& rs)
{
rs.getDouble(L"/Point2D/@x",x);
rs.getDouble(L"/Point2D/y",y);
}
用于读/写的 XML 结果如下所示:
<Point2D x="value of x as double">
<y> value of y as double </y>
</Point2D>
复合对象的 XML 序列化
通常,一个 Object
可能由其他对象组成,这可以通过将组成对象的 XML 表示形式放入被组成对象的 XML 表示形式中来翻译为 XML 格式。例如,一个矩形由 2 个点组成。矩形对象可以根据以下模板表示为 XML:
<Rect2D>
<TopLeft><Point2D><x></x><y></y><Point2D></TopLeft>
<BottomDown><Point2D><x></x><y></y><Point2D></BottomDown>
</Rect2D>
突出显示的部分恰好对应于一个 Point2d
。
Rect2D
类应该具有 2 个 point2d
作为显式成员或作为数组。
作为显式成员
class Rect2D : public XmlSerializable
{
private:
Point2D topLeft, bottomRight;
};
作为数组
class Rect2D : public XmlSerializable
{
private:
Point2D points[2];
};
需要实现 abstract
存根 readXml
和 writeXml
。
在显式成员的情况下
void readXml(XmlReader& rs)
{
rs.getXml(L"/Rect2D/TopLeft",&topLeft);
rs.getXml(L"/Rect2D/BottomRight",&bottomRight);
}
void writeXml(XmlWriter& ws)
{
ws.setXml(L"/Rect2D/TopLeft",&topLeft);
ws.setXml(L"/Rect2D/BottomRight",&bottomRight);
}
getXml
和 setXml
方法在这里提供了重用 XmlSerializable Point2D
对象的序列化。事实上,topLeft
和 bottomRight
成员是 Point2D
,并且已经有了它们自己的读/写 XML 的方式。如果成员定义为数组
void readXml(XmlReader& rs)
{
rs.getXml(L"/Rect2D/TopLeft",&points[0]);
rs.getXml(L"/Rect2D/BottomRight",&points[1]);
}
void writeXml(XmlWriter& ws)
{
ws.setXml(L"/Rect2D/TopLeft",&points[0]);
ws.setXml(L"/Rect2D/BottomRight",&points[1]);
}
现在,如果我们考虑一个像下面这样的 XML 的简写模板怎么办?
<Rect2D>
<Point2D><x></x><y></y><Point2D>
<Point2D><x></x><y></y><Point2D>
</Rect2D>
其中第一个点是左上角,第二个点是右下角。
在这种情况下,用于读写 XML 的代码是:
void readXml(XmlReader& rs)
{
rs.getXml(L"/Rect2D/*",&points[0],0);
rs.getXml(L"/Rect2D/*",&points[1],1);
}
void writeXml(XmlWriter& ws)
{
ws.setXml(L"/Rect2D/*",&points[0]);
ws.setXml(L"/Rect2D/*",&points[1]);
}
您应该仅在想要复制/粘贴其他 XML 表示形式的此类情况下,在 XPATH
表达式中使用 *
。getXml
方法中使用的索引(最后一个参数)用于指定 points[0] 是第一个,point[1] 是第二个,因为 "getter 方法" 的索引例程从 0 开始。"setter 方法" 不使用任何索引。对于同一个表达式调用时,setter 永远不会覆盖之前的元素(如果是属性,则会)。它会追加下一个同级元素。我在这里展示了 writeXml
例程的跟踪:
第一次调用
ws.setXml(L"/Rect2D/*",&points[0]);
<Rect2D>
<Point2D><x></x><y></y><Point2D> (points[0] goes here)
</Rect2D>
第二次调用
ws.setXml(L"/Rect2D/*",&points[1]);
<Rect2D>
<Point2D><x></x><y></y><Point2D> (points[0] still here)
<Point2D><x></x><y></y><Point2D> (points[1] goes here)
</Rect2D>
您可以在 test.cpp 中找到一个由 Points 数组组成的 Polygon 的示例,这很简单,无需解释。
演示示例
位于 test.cpp 中的演示示例让您了解如何基本使用 API 来序列化对象。
在演示的 "start in" 文件夹中会生成 3 个 XML 文件:f.xml、rect2d.xml 和 poly2d.xml。
第三方(我所做的一切都是一个薄包装)
我使用了 VTD-XML 开源库(XimpleWare),C 版本,进行了少量更新以进行自定义并解决了一些内存泄漏问题。
VTD-XML 是一个非常高效的 XML 解析器。
关注点
我使用了一种受限制的 XPATH 格式来完成通用的 XML 序列化,遵循一种简单易用的方法。我编写的代码是跨平台的,尽管这里展示的项目是在 Windows 环境中编译的。Xmlser API 可以处理由不同应用程序生成的任何 XML 文档,并将其转换为对象。XmlSer API 可以转换为 Java,因为有一个用 Java 编写的 VTD-XML 版本。
历史
- 初始版本:2008 年 4 月 11 日