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

面向对象的 XML 解析器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.36/5 (18投票s)

2001年8月25日

3分钟阅读

viewsIcon

227207

downloadIcon

3091

面向对象的解析器,使用MSXML解析器读/写XML文件

引言

我有一个任务,需要编写一个XML解析器来读/写XML文件,并具备一系列的可能性(需求)。

要求

  • 开发者(解析器用户)的便利性
  • 使用同一个解析器实例进行记录和读取的可能性
  • 不使用MFC
  • 用户对象自行读写
  • 可能以二进制流写入,而无需更改用户对象的源代码
  • 可能创建代理类来读/写用户类,但禁止修改
  • 用户对象中使用相同的代码进行读写
  • 读/写简单类型的可能性
  • 读/写对象的可能性(加载时可以确定类型)
  • 读/写简单和复杂对象的vector、map的可能性
  • 读/写属性的可能性
  • 对一种类型嵌套在其他类型中的限制的消除
  • 最小化“if”操作
  • 父标签中标签顺序的无关性
  • 不支持命名空间和模式

解决方案

基于几个想法

  1. 使用基于Windows Platform SDK样本的解析器
  2. 每个理解自己是时候被读取的用户对象,都会像“throw this”一样抛出自己
  3. 然后解析器捕获它并放入栈,所有调用都将发送给该对象,直到有人抛出另一个对象或找到对象结束。
  4. 每个用户对象只有一个“determine(ProxyObject& proxy)”函数,它应该被覆盖。
  5. Proxy - 这是一个抽象对象,它有一些虚拟函数来读/写某些类型的数据,而代理实现则由解析器自动创建,以精确地“了解”流的类型和当前操作。
  6. 有一个模板类,带有模板构造函数,它获取用户对象的成员、代理对象、“要读取的对象名称”。
  7. 这个模板类自己知道如何处理这些成员,并调用相应的代理函数。
  8. 有一些代理类可以读/写stringintfloat

这是那个神奇的模板

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的类。

© . All rights reserved.