面向对象的 XML 解析器






4.36/5 (18投票s)
2001年8月25日
3分钟阅读

227207

3091
面向对象的解析器,使用MSXML解析器读/写XML文件
引言
我有一个任务,需要编写一个XML解析器来读/写XML文件,并具备一系列的可能性(需求)。
要求
- 开发者(解析器用户)的便利性
- 使用同一个解析器实例进行记录和读取的可能性
- 不使用MFC
- 用户对象自行读写
- 可能以二进制流写入,而无需更改用户对象的源代码
- 可能创建代理类来读/写用户类,但禁止修改
- 用户对象中使用相同的代码进行读写
- 读/写简单类型的可能性
- 读/写对象的可能性(加载时可以确定类型)
- 读/写简单和复杂对象的vector、map的可能性
- 读/写属性的可能性
- 对一种类型嵌套在其他类型中的限制的消除
- 最小化“if”操作
- 父标签中标签顺序的无关性
- 不支持命名空间和模式
解决方案
基于几个想法
- 使用基于Windows Platform SDK样本的解析器
- 每个理解自己是时候被读取的用户对象,都会像“throw this”一样抛出自己
- 然后解析器捕获它并放入栈,所有调用都将发送给该对象,直到有人抛出另一个对象或找到对象结束。
- 每个用户对象只有一个“determine(ProxyObject& proxy)”函数,它应该被覆盖。
- Proxy - 这是一个抽象对象,它有一些虚拟函数来读/写某些类型的数据,而代理实现则由解析器自动创建,以精确地“了解”流的类型和当前操作。
- 有一个模板类,带有模板构造函数,它获取用户对象的成员、代理对象、“要读取的对象名称”。
- 这个模板类自己知道如何处理这些成员,并调用相应的代理函数。
- 有一些代理类可以读/写
string
、int
和float
。
这是那个神奇的模板
template <class T = void*> class determineMember { public: template <class OT> determineMember(OT& t, const char* str,XmlParserProxy& p,T* _t=0) throw(XmlObject*) { determine(t,str,p,_t,type(t)); } };
目前我的解析器满足以上所有要求。这是用户对象的示例
struct Nodes : XmlObject { vectorDel<Node*> m_nodes; virtual void determine(XmlParserProxy& p) throw(XmlObject*) { determineMember<StartNode>(m_nodes,"start-node",p); determineMember<InteractionNode>(m_nodes,"interaction-node",p); } }; struct Pipeline : XmlObject { string m_name; int m_count; float m_sum; SomeObject* m_someObject; vector<string> m_transitions; vector<int> m_point; Nodes m_nodes; map<int,NodeDisplay> m_map; virtual void determine(XmlParserProxy& p) throw(XmlObject*) { determineMember<>(m_name,"name",p); determineMember<>(m_count,"count",p); determineMember<>(m_sum,"sum",p); determineMember<FirstObject>(m_someObject,"FirstObject",p); determineMember<SecondObject>(m_someObject,"SecondObject",p); determineMember<Nodes>(m_nodes,"nodes",p); determineMember<string>(m_transitions,"transition",p); determineMember<int>(m_point,"point",p); determineMember<>(m_map,"Map",p); } };
请注意,您可以拥有对象的指针和vector,并且其中可以包含不同的类。
这是使用解析器的一个例子
CoInitialize(NULL); Document doc; XML::ZXmlParserImpl parser(&doc); parser.parse(_bstr_t("test.xml")); parser.save(_bstr_t("saved_test.xml")); CoUninitialize();
看看这个示例XML文件
<pipeline> <!-- string --> <name>Default</name> <!-- int --> <count>2</count> <!-- float --> <sum>1.234567</sum> <!-- object having type from set of types--> <SecondObject> <second>1</second> </SecondObject> <!-- uncomment FirstObject and comment SecondObject to make FirstObject instead of SecondObject <FirstObject> <first>1</first> </FirstObject> --> <!-- new XMLObject of type Nodes--> <nodes> <!-- insert in vector<Node> new StartNode, string attribute--> <start-node id="Start"> <!-- insert vector<NodeDisplay> new NodeDisplay--> <node-display> <x-center>0</x-center> <y-center>0</y-center> </node-display> <node-display> <x-center>1</x-center> <y-center>1</y-center> </node-display> </start-node> <!-- insert in vector<Node> new InteractionNode, string attribute--> <interaction-node id="It"> <!-- two string attributes--> <template dynamic="false" index="2"> testS </template> </interaction-node> </nodes> <!-- vector of strings --> <transition>Tr1</transition> <transition>Tr2</transition> <!-- vector of ints --> <point>3</point> <point>2</point> <point>1</point> <!-- and now - std::map<int,NodeDisplay> --> <Map> <pair> <name>1</name> <value> <x-center>1</x-center> <y-center>2</y-center> </value> </pair> <pair> <name>2</name> <value> <x-center>4</x-center> <y-center>3</y-center> </value> </pair> </Map> </pipeline>
此表显示了如何实现具有不同成员的determine
函数
成员变量 | determine中的源代码 | 注释 |
string m_name; |
determineMember<>(m_name,"name",p); |
|
string m_name; |
determineMember<AtribValue>(m_name,"name",p); |
使用AtribValue 来表示name是属性而不是节点 |
int m_count; |
determineMember<>(m_name,"count",p); |
|
float m_sum; |
determineMember<>(m_name,"sum",p); |
|
SomeObject* m_someObject; |
determineMember <FirstObject>(m_someObject, "FirstObject",p); |
如果找到FirstObject 标签,则将新的FirstObject 赋值给m_someObject 。 |
SomeObject* m_someObject; |
determineMember <SecondObject>(m_someObject, "SecondObject",p); |
如果找到SecondObject 标签,则将新的SecondObject 赋值给m_someObject 。 |
SomeObject m_nodes; |
determineMember<>(m_nodes,"nodes",p); |
|
std::vector<int> m_point; |
determineMember<int>(m_point,"point",p); |
|
std::vector<string> m_transitions; |
determineMember <string>(m_transitions, "transition",p); |
|
std::map<int,SomeObject> m_map; |
determineMember<>(m_map,"Map",p); |
|
std::vector<Node*> m_nodes |
determineMember<StartNode>(m_nodes,"start-node",p); |
如果找到start-node 标签,则会创建一个新的StartNode 并插入到m_nodes 中。 |
std::vector<Node*> m_nodes; |
determineMember <InteractionNode>(m_nodes, "interaction-node",p); |
如果找到interaction-node 标签,则会创建一个新的InteractionNode 并插入到m_nodes 中。 |
在演示项目中,我展示了如何在解析器的源代码文件之外实现用于读/写std::map
的类。