Main purpose and abilities
EasyXML is a simple yet powerful XML Parser which can read and parse XML from three different sources:
- URL
- File Input
- String Input
It is written completely in native MQL5 and relies on the Windows native “wininet.dll” only for fetching XML documents from an URL.
EasyXML will read XML as well as XHTML with (nearly) infinite node depth, as long as the document you are trying to parse is well-formed. It does not, however, validate the XML against a DTD or XSLT Stylesheet.
MQL5 Integration
EasyXML’s Node Classes are inherited from the MQL5 native CObject and nodes are stored in a CArrayObj.
When walking the DOM tree nodes can be manipulated easily by using the public EasyXML methods as well as the MQL5 native functions to retrieve data from and store data to the DOM.
Â
URL File Caching and Debugging
Since one can not always rely on RSS Feed Uptimes, EasyXML can store a XML cache file of the Feed, once it has loaded it from a URL successfully for the first time. The user then can use the cache file instead of the Live Feed for parsing, should the Feed be down for some reason.
Since XML and XHTML documents tend to be erroneous, EasyXML has a debugging option. While it can not repair broken XML, it surely will help to detect where the error is. If turned on it will print detailed information of the nodes parsed.
Besides that, any errors that occur will always be tracked and printed, despite the fact, if debugging is turned on or off.
Â
Basic Usage
Just include the base class in your scripts and you’re set up and ready to go:
//+------------------------------------------------------------------+ //| Includes                                                        | //+------------------------------------------------------------------+ #include <EasyXML\EasyXml.mqh>
First, in your script, create an an instance of the EasyXML Class. Then set debugging and/or file caching and call one of the available methods to load the XML and start parsing:
//+------------------------------------------------------------------+ //| Script program start function                                    | //+------------------------------------------------------------------+ void OnStart()   {   // Create instance of class CEasyXml   CEasyXml EasyXmlDocument;   // Optional debugging   EasyXmlDocument.setDebugging(true);   // Set Url Cache File   EasyXmlDocument.setUrlCacheFile("forexcalendar.xml");   // Method 1: Load XML from URL   if(EasyXmlDocument.loadXmlFromUrl("http://www.forexfactory.com/ffcal_week_this.xml"))     {       readRecursive(EasyXmlDocument.getDocumentRoot());     }   // Clear the DOM   EasyXmlDocument.Clear();   // Method 2: Load XML from string   if(EasyXmlDocument.loadXmlFromString("<root><child attr="value">content</child><sibling>siblingcontent</sibling></root>"))     {       readRecursive(EasyXmlDocument.getDocumentRoot());     }   // Clear the DOM   EasyXmlDocument.Clear();   // Method 3: Load XML from file   if(EasyXmlDocument.loadXmlFromFile("forexcalendar.xml"))     {       readRecursive(EasyXmlDocument.getDocumentRoot());     }   }
For demonstration purposes all of the three methods are shown. Normally you won’t need all of them at once, although it is possible to clear the DOM tree in between and start parsing over again, even from another source. Just use the Clear() Command to erase the parsed DOM tree. setDebugging() and setUrlCacheFile() are optional and don’t have to be called if they are not needed.
EasyXmlDocument.getDocumentRoot() will always return the root node of the DOM tree. All nodes including the root node are of type CEasyXmlNode, which itself derived from the MQL5 CObject (as mentioned before). From here on all of the methods of EasyXml as well as from CArrayObj and CObject may be used side by side to walk the parsed DOM tree.
The following example shows the implementation of readRecursive(), the global function which is called in the last code example:
//+------------------------------------------------------------------+ //| read xml recursive                                              | //+------------------------------------------------------------------+ int readRecursive(CEasyXmlNode *ActualNode,int iNodeLevel=0)   {   // Output vars   string sSpace;   string sOutput;   // Indent output for better readability   StringInit(sSpace,iNodeLevel*4,StringGetCharacter(" ",0));   // Concatenate output string   sOutput += sSpace + IntegerToString(iNodeLevel) + " - Node Name: '" + ActualNode.getName() + "'";   sOutput += (ActualNode.getValue()) ? " Value: '" + ActualNode.getValue() + "'" : "";   // Iterate through AttributeNodes   for(int i=0; i<ActualNode.Attributes().Total(); i++)     {       CEasyXmlAttribute *Attribute=ActualNode.Attributes().At(i);       sOutput+=" || Attribute "+IntegerToString(i+1)+": '"+Attribute.getName()+"' Value: '"+Attribute.getValue()+"'";     }   Print(sOutput);   // Iterate through child nodes   for(int j=0; j<ActualNode.Children().Total(); j++)     {       CEasyXmlNode *ChildNode=ActualNode.Children().At(j);       readRecursive(ChildNode,iNodeLevel+1);     }   return(0);   }
Recursive reading of XML documents has big advantages over inline reading, although it may not be suitable for all needs. Calling Attributes() on a node will fetch all parsed Attributes while Children() will get the child nodes stored in the actual node. Both methods return a CArrayObj containing the elements. Calling Total() on those objects can be used in a for() loop to iterate over the elements. getName() and getValue() will return actual content stored in the node.
Of course it is possible to iterate over nodes inline as well:
//+------------------------------------------------------------------+ //| Script program start function                                    | //+------------------------------------------------------------------+ void OnStart()   { // Create object of class CEasyXml   CEasyXml EasyXmlDocument; // Set debugging   EasyXmlDocument.setDebugging(false); // Example: Walking through the dom tree inline   if(EasyXmlDocument.loadXmlFromUrl("http://www.forexfactory.com/ffcal_week_this.xml"))     {       CEasyXmlNode *RootNode=EasyXmlDocument.getDocumentRoot();       //iterate through root node       for(int i=0; i<RootNode.Children().Total(); i++)         {         CEasyXmlNode *ChildNode=RootNode.Children().At(i);         Print(IntegerToString(i)+" "+ChildNode.getName());         //iterate through child nodes         for(int j=0; j<ChildNode.Children().Total(); j++)           {             CEasyXmlNode *SubNode=ChildNode.Children().At(j);             Print(IntegerToString(i)+"-"+IntegerToString(j)+"  "+SubNode.getName()+" | "+SubNode.getValue());           }         }     }  }
Iteration works just as in the recursive example, except that a separate for() loop has to be established for each and every node level to be read.
Besides that it is also possible to walk the DOM step by step and manipulate single elements if needed:
//+------------------------------------------------------------------+ //| Script program start function                                    | //+------------------------------------------------------------------+ void OnStart()   { // Create object of class CEasyXml   CEasyXml EasyXmlDocument; // Set debugging   EasyXmlDocument.setDebugging(true); // Example 2: Walking through the DOM tree step by step   if(EasyXmlDocument.loadXmlFromString("<root><child attr="value">content</child><sibling>siblingcontent</sibling></root>"))     {       CEasyXmlNode *Node=EasyXmlDocument.getDocumentRoot();       Print(Node.getName());       CEasyXmlNode *ChildNode=Node.FirstChild();       Print(ChildNode.getName());       // Always check for valid pointers if stepping sidewards manually.       while(CheckPointer(ChildNode.Next())!=POINTER_INVALID)         {         ChildNode=ChildNode.Next();         Print(ChildNode.getName());         }       CEasyXmlNode *ParentNode=ChildNode.Parent();       Print(ParentNode.getName());       // Back to root: ParentNode and Node are two different descriptors of the same object       Print("Comparison of object descriptors: ParentNode == Node ? ",ParentNode==Node);     }   }
Here all of the available EasyXML methods as well as the native MQL5 Iteration/Getter/Setter of CObject and CArrayObj come in play.
Keep in mind, though, that some of those functions don’t care about valid memory access and just return NULL, if they don’t succeed.
In the last example calling ChildNode.Next() on the sibling node – without checking for pointer validity – would entail a serious bad pointer fault (= bad memory access), that will definitely crash the script. So if you ever have the need to step or manipulate the DOM tree manually, take care of pointer validity, as long as it concerns the CObject and CArrayObj class methods.
Most important node getters
Method | Purpose | Return |
---|---|---|
 Chilrden() |  Get all children of node |  CArrayObj – containing CEasyXmlNodes |
 Attributes() |  Get all attributes of node |  CArrayObj – containing CEasyXmlAttributes |
 Parent() |  Get parent node |  CEasyXmlNode (CObject) |
 LastChild() |  Get last node from children |  CEasyXmlNode (CObject) |
 FirstChild() |  Get first node from children |  CEasyXmlNode (CObject) |
 getName() |  Get node name |  string |
 getValue() |  Get node value (text content) |  string |
 getAttribute(string pName) |  Get Attribute by specified name |  string |
 Next() (Inherited from CObject) |  Get next sibling node |  CEasyXmlNode (CObject) || NULL |
 Prev() (Inherited from CObject) |  Get previous sibling node |  CEasyXmlNode (CObject) || NULL |
Â
Most important node setters
Method | Purpose | Return |
---|---|---|
 createChild(CEasyXmlNode *pChildNode) |  create new child node |  CEasyXmlNode (CObject) – the new child node |
 createSibling(CEasyXmlNode *pSiblingNode) |  create new sibling node |  CEasyXmlNode (CObject) – the new sibling node |
 setName(string pName) |  set node name |  void |
 setValue(string pValue) |  set node value (text content) |  void |
 setAttribute(string pName,string pValue) |  set new node attribute |  void |
Attribute getters/setters
Attribute Objects implements the same get/setName(), get/SetValue() methods to store and retrieve data just like the node objects.
Disclaimer
This piece of code is under active development, and, as with all software, does not claim to be free of bugs or other defects. Use EasyXml at your own risk and test thoroughly before implementing this library into any live trading EA. If you encounter any problems or have questions regarding the usage, please feel free to contact me.
Credits
The Integration of the wininet.dll used for fetching URL content uses WININET_TEST by Integer. Although this library is build upon its own, unique parsing system, the XML Parser written by yu-sha was a great learning source for dealing with MQL5 string operations.