Object-oriented XML Parser






4.36/5 (18 votes)
Aug 25, 2001
3 min read

226323

3091
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:
- Using of parser-a based on sample from Windows Platform SDK
- Each user object that understand that it is his time to be read, throws itself just like "throw this"
- 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
- Each user object have only one function "determine(ProxyObject& proxy)" it should ovewrite
- 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
- There is a template class with template constructor, that gets members of user objects, proxy object, "name" of the object to read
- This template class understands itself what to do with that members and call corresponding proxy's functions.
- There are some proxy classes that can read/write
string
,int
andfloat
.
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 variable | Source in determine | Comments |
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_nodes |
determineMember<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.