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

可移植的 C++ XML 序列化

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.68/5 (6投票s)

2008 年 4 月 11 日

GPL3

5分钟阅读

viewsIcon

51224

downloadIcon

778

一个开放的 C++ 库, 用于将对象序列化为 XML

引言

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 中找到所有这些类

  1. XmlSerializable
  2. XmlReader
  3. XmlWriter

要使您的对象可 XML 序列化,您必须继承自 XMLSerializable

如您在源代码 test.cpp 中看到的,Point2D 类是 XMLSerializable 的。

class Point2D : public XmlSerializable 

一旦继承了 XmlSerializable,类就应该实现两个 abstract 成员:writeXmlreadXml。对于 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 对象,您应该使用 XmlReaderloadFrom 方法。

XmlReader reader;
Point2D pt;
reader.loadFrom("c:\\myPt.xml");
pt.readXml(reader);    

使用特性

您可能更喜欢将 x 保存为属性,将 y 保存为元素,在这种情况下,请将 Point2D 类中的 readXmlwriteXml 实现更改为以下内容:

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 存根 readXmlwriteXml
在显式成员的情况下

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);
}

getXmlsetXml 方法在这里提供了重用 XmlSerializable Point2D 对象的序列化。事实上,topLeftbottomRight 成员是 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.xmlrect2d.xmlpoly2d.xml

第三方(我所做的一切都是一个薄包装)

我使用了 VTD-XML 开源库(XimpleWare),C 版本,进行了少量更新以进行自定义并解决了一些内存泄漏问题。

VTD-XML 是一个非常高效的 XML 解析器。

关注点

我使用了一种受限制的 XPATH 格式来完成通用的 XML 序列化,遵循一种简单易用的方法。我编写的代码是跨平台的,尽管这里展示的项目是在 Windows 环境中编译的。Xmlser API 可以处理由不同应用程序生成的任何 XML 文档,并将其转换为对象。XmlSer API 可以转换为 Java,因为有一个用 Java 编写的 VTD-XML 版本。

历史

  • 初始版本:2008 年 4 月 11 日
© . All rights reserved.