Click here to Skip to main content
15,892,059 members
Articles / Programming Languages / XML
Article

Object-oriented XML Parser

Rate me:
Please Sign up or sign in to vote.
4.36/5 (18 votes)
28 Aug 20013 min read 223.6K   3.1K   58   44
Object-oriented parser to read/write XML files using MSXML parser

Introduction

I had a task to write an XML parser to read/write XML files with list of possibilities (requirements).

Requirements:

  • Comfort for developer (user of parser)
  • Possibility of recording and reading using the same instance of parser
  • No use of MFC
  • User object writes and reads itself
  • Possibility to write in binary stream, without changing source of user objects
  • Possibility of creation of proxy classes for reading/writing of user classes, modification of which is forbidden
  • Same code in user object for reading and writing
  • Possibility of reading/writing simple types
  • Possibility of reading/writing objects (with possibility of determination of type during loading)
  • Possibility of reading/writing vector, map of simple and complex objects
  • Possibility of reading/writing the attributes
  • Absences of restrictions on nesting of one type in others
  • Minimization of operations "if"
  • Unimportance of order of tags in parent tag
  • Absence of support namespace and schema

Solution

Based on several ideas:

  1. Using of parser-a based on sample from Windows Platform SDK
  2. Each user object that understand that it is his time to be read, throws itself just like "throw this"
  3. Than parser catch him and put into stack and all calls will be send to this object, until anyone else throw itself or end of object will be found
  4. Each user object have only one function "determine(ProxyObject& proxy)" it should ovewrite
  5. Proxy - it is an abstract object that have some virtual functions to read/write some types of data and proxy implementations which are made automaticaly by parser to exactly "know" the type of stream and current operation
  6. There is a template class with template constructor, that gets members of user objects, proxy object, "name" of the object to read
  7. This template class understands itself what to do with that members and call corresponding proxy's functions.
  8. There are some proxy classes that can read/write string, int and float.

Here is that wonderful template:

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

Currently my parser satisfies all requirements above. And this is example of user objects:

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

Note that you can have pointers and vectors of objects and have different classes there.

This is an example of using the parser:

CoInitialize(NULL);

Document doc;
XML::ZXmlParserImpl parser(&doc);

parser.parse(_bstr_t("test.xml"));

parser.save(_bstr_t("saved_test.xml"));

CoUninitialize();

Look at this sample XML file:

<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>

This table shows how to implement determine function with different members

Member variableSource in determineComments
string m_name;determineMember<>(m_name,"name",p); 
string m_name;determineMember<AtribValue>(m_name,"name",p);Use AtribValue to indicate that name is attribute but not a node
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);If tag FirstObject found, then new FirstObject will be assigned to m_someObject
SomeObject* m_someObject;determineMember <SecondObject>(m_someObject, "SecondObject",p);If tag SecondObject found, then new SecondObjectwill be assigned to 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_nodesdetermineMember<StartNode>(m_nodes,"start-node",p);If tag start-node found, then new StartNode will be made and inserted in m_nodes
std::vector<Node*> m_nodes;determineMember <InteractionNode>(m_nodes, "interaction-node",p);If tag interaction-node found, then new InteractionNode will be made and inserted in m_nodes

In demo project I have shown how to implement classes for reading/writing std::map not in parser's source files.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Russian Federation Russian Federation
Mike has been programming in C/C++ for 11 years and Visual C++/MFC for 4 years. His background includes pure and applied mathematics, engineering and physics, and he is currently based in Moscow, Russia.

Comments and Discussions

 
GeneralRe: just get string, don't save to file. Pin
Mike Melnikov11-May-03 23:03
Mike Melnikov11-May-03 23:03 
GeneralRe: just get string, don't save to file. Pin
marco.song13-May-03 15:56
marco.song13-May-03 15:56 
GeneralProblem after fixing the memory leaks Pin
grebulon3-Apr-03 5:51
grebulon3-Apr-03 5:51 
GeneralQuestion about progam flow Pin
Pankaj Kumar7-Jan-03 19:51
Pankaj Kumar7-Jan-03 19:51 
Questionwhat about header? Pin
gok19-Dec-02 13:40
professionalgok19-Dec-02 13:40 
GeneralGreat Job :-) Pin
Ram. Sundar13-Nov-02 8:37
Ram. Sundar13-Nov-02 8:37 
GeneralMemory leaks Pin
9-Jul-02 0:28
suss9-Jul-02 0:28 
GeneralRe: Memory leaks Pin
John Kirk13-Sep-02 7:39
John Kirk13-Sep-02 7:39 
There are a number of memory leaks dealing with bstr's (which I didn't take the time to track down and fix). However, many of the other leaks can be fixed by three simple changes:

Memory leak 1 (ZXmlParserImpl.h):

namespace XMLPrivate
{
. . .
class XmlParserProxyRead : public XmlParserProxy
{
virtual void do_attrib(const char* key, string& str)
{m_p.do_read_member(*(new Value2<string>(str)),key);}
// This method always allocates a new 'Value2<string>', but the new object
// is not always thrown. In fact, usually it is simply forgotten.
. . .
};
}

// Change to...

namespace XMLPrivate
{
. . .
class XmlParserProxyRead : public XmlParserProxy
{
virtual void do_attrib(const char* key, string& str)
{
XmlObject* pObj = new Value2<string>(str);
m_p.do_read_member(*pObj,key);
// If we made it to here, pObj was not thrown. Clean up.
delete pObj;
}
. . .
};
}

Memory leak 2 (ZXmlParserImpl.h):

inline XmlObject& xmlproxy(string &s) {return *(new XMLPrivate::Value2<string>(s));}
inline XmlObject& xmlproxy(int &i) {return *(new XMLPrivate::Value2<int>(i));}
inline XmlObject& xmlproxy(float &i) {return *(new XMLPrivate::Value2<float>(i));}
// ^ These functions always allocate a new object.
. . .

template <class T>
inline void determine(T& t,const char* str,XmlParserProxy& p,void*,common_t)
{
p.do_member(xmlproxy(t),str);
// ^ Always allocates a new XMLPrivate::Value2<> of some sort. If the
// new object is not thrown by p.do_member(), the object leaks.
}

class XmlParserProxyRead : public XmlParserProxy
{
. . .
virtual void do_member(XmlObject& t,const char* str) throw(XmlObject*)
{m_p.do_read_member(t,str);}
. . .
}

void ZXmlParserImpl::do_read_member(XmlObject& t,const char* str) throw(XmlObject*)
{
if(load()==str) // If this test is false, t leaks.
throw &t;
}

// Change to...

template <class T>
inline void determine(T& t,const char* str,XmlParserProxy& p,void*,common_t)
{
XmlObject* pObj = new XMLPrivate::Value2<T>(t);
p.do_member(*pObj,str);

// If we make it to here, pObj was not thrown. Clean up.
delete pObj;
}

Memory leak 3 (ZXmlParser.cpp):

void ZXmlParser::parse(const _bstr_t& strFileName, bool isFile)
{
XMLNode* hItemRoot = new XMLNode;
. . .
print(hItemRoot, "");
// ^ hItemRoot is allocated, but never freed.
}

// Change to...
void ZXmlParser::parse(const _bstr_t& strFileName, bool isFile)
{
XMLNode* hItemRoot = new XMLNode;
. . .
print(hItemRoot, "");

// Clean up.
delete hItemRoot;
}

I hope this helps Smile | :)


John Kirk

"Man - A figment of God's imagination" Mark Twain

GeneralRe: Memory leaks Pin
Anonymous25-May-03 14:06
Anonymous25-May-03 14:06 
Generala couple of questions: Pin
Eugene Polonsky8-Jul-02 13:09
Eugene Polonsky8-Jul-02 13:09 
GeneralRe: a couple of questions: Pin
Mike Melnikov28-Aug-02 21:50
Mike Melnikov28-Aug-02 21:50 
GeneralExtension for std::list's Pin
Thomas Freudenberg10-Jun-02 2:28
Thomas Freudenberg10-Jun-02 2:28 
GeneralNice package, but... Pin
Andreas Saurwein27-Feb-02 11:34
Andreas Saurwein27-Feb-02 11:34 
GeneralRe: Nice package, but... Pin
Mike Melnikov2-Mar-02 23:58
Mike Melnikov2-Mar-02 23:58 
GeneralRe: Nice package, but... Pin
Anonymous25-May-03 14:05
Anonymous25-May-03 14:05 
GeneralI am using expat to read the xml files, I need to get the value data to display through the web Pin
22-Feb-02 14:54
suss22-Feb-02 14:54 
QuestionHow to save new struct Pin
DanielVisual5-Sep-01 3:56
DanielVisual5-Sep-01 3:56 
AnswerRe: How to save new struct Pin
Mike Melnikov5-Sep-01 4:30
Mike Melnikov5-Sep-01 4:30 
GeneralThats what I need Pin
4-Sep-01 5:01
suss4-Sep-01 5:01 
GeneralPlease post a comment Pin
Mike Melnikov30-Aug-01 19:25
Mike Melnikov30-Aug-01 19:25 
GeneralRe: Please post a comment Pin
Christian Graus30-Aug-01 19:52
protectorChristian Graus30-Aug-01 19:52 
GeneralRe: Please post a comment Pin
Mike Melnikov5-Sep-01 4:24
Mike Melnikov5-Sep-01 4:24 
GeneralRe: Please post a comment Pin
14-Sep-01 5:05
suss14-Sep-01 5:05 
GeneralRe: Please post a comment Pin
14-Sep-01 5:06
suss14-Sep-01 5:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.