/***************************************************************************** * * Atmel Corporation * * File : XMLParser.cpp * Compiler : Dev-C++ 4.9.8.0 - http://bloodshed.net/dev/ * Revision : $Revision: 3419 $ * Date : $Date: 2008-02-22 09:56:34 +0100 (fr, 22 feb 2008) $ * Updated by : $Author: khole $ * * Support mail : avr@atmel.com * * Target platform : Win32 * * AppNote : AVR911 - AVR Open-source Programmer * * Description : A simple XML DOM-like parser. It builds a complete tree from * the XML file. IT supports
tags, but not tag attributes. * * ****************************************************************************/ #include "XMLParser.hpp" /* Private classes */ enum XMLNodeType { xml_node, xml_subtree }; /* Abstract class. XMLTree and XMLNode is derived from this class */ class XMLAbstractNode { protected: string name; // Name of this node. XMLNodeType type; public: /* Constructor */ XMLAbstractNode( const string & _name, XMLNodeType _type ); /* Destructor */ ~XMLAbstractNode(); /* Methods */ const string & getName(); XMLNodeType getType(); bool isName( const string & _name ); // Compare name to _name. virtual void print() = 0; }; /* Class describing a subtree, derived from XMLAbstractNode */ class XMLTree : public XMLAbstractNode { protected: list nodes; // Nodes contained in this tree. public: /* Constructor */ XMLTree( const string & _name ); /* Destructor */ ~XMLTree(); /* Methods */ void addNode( XMLAbstractNode * newnode ); bool containsNode( const string & _name ); // Searches for name in list. XMLAbstractNode * getNode( const string & _name ); void print(); }; /* Class describing an ordinary string-valued node, derived from XMLAbstractNode */ class XMLNode : public XMLAbstractNode { protected: string value; // String value. public: /* Constructor */ XMLNode( const string & _name, const string & _value ); /* Destructor */ ~XMLNode(); /* Methods */ bool isEmpty(); // Contains an empty string? const string & getValue(); void print(); }; void XMLFile::removeComments( string & txt ) { long pos = 0; // Everything up to this point is clean. long startFoundAt; // Comment start and end found at these positions. long endFoundAt; /* Search and remove all comment tags */ do { /* Search for comment start */ startFoundAt = txt.find( "", startFoundAt ); /* Error if start but no end is found */ if( endFoundAt == string::npos ) throw new ErrorMsg( "Unclosed comment tag encountered! " "Comment start-tag ''." ); /* Remove comment tag */ txt.erase( startFoundAt, endFoundAt - startFoundAt + 3 ); pos = startFoundAt; // Prepare for next search. } while( pos < txt.size() ); } void XMLFile::removeStartXML( string & txt ) { long pos = 0; // Everything up to this point is clean. long startFoundAt; // Comment start and end found at these positions. long endFoundAt; /* Search and remove the ", startFoundAt ); /* Remove tag */ txt.erase( startFoundAt, endFoundAt - startFoundAt ); } void XMLFile::removeAttributes( string & txt ) { long pos; // Everything up to this point is clean. long startFoundAt; // Tag start and end found at these positions. long endFoundAt; long spaceFoundAt; // Space before attribute found at this position. long slashFoundAt; // Ending slash found at this position. /* Convert all whitespace to plain spaces, just to make things easier */ for( pos = 0; pos < txt.size(); pos++ ) { if( txt[pos] == '\n' || txt[pos] == '\r' || txt[pos] == '\t' ) txt.replace( pos, 1, " " ); } pos = 0; /* Search and clean all tags */ do { /* Search for tag start */ startFoundAt = txt.find( "<", pos ); /* Exit loop if no tag is found */ if( startFoundAt == string::npos ) break; /* Search for comment end */ endFoundAt = txt.find( ">", startFoundAt ); /* Error if start but no end is found */ if( endFoundAt == string::npos ) throw new ErrorMsg( "Unclosed tag encountered! " "Tag start token '<' found, but not " "closing '>'." ); /* Remove whitespace before tag name */ while( txt[startFoundAt+1] == ' ' ) { txt.erase( startFoundAt+1, 1 ); // Remove. endFoundAt--; // String has now shrunk. } /* Search for space before attributes */ spaceFoundAt = txt.find( " ", startFoundAt ); if( spaceFoundAt < endFoundAt && spaceFoundAt != string::npos ) // Space found inside tag? { // If empty tag, we dont want to remove the / in /> if ( txt.at(endFoundAt-1) == '/' ) { endFoundAt--; } /* Remove attributes */ txt.erase( spaceFoundAt, endFoundAt - spaceFoundAt ); endFoundAt -= endFoundAt - spaceFoundAt; // String has now shrunk. } pos = endFoundAt + 1; // Prepare for next search. } while( pos < txt.size() ); } void XMLFile::readFile( const string & _filename ) { ifstream f; // XML file stream. string contents; // XML file contents. string templine; /* Attempt to open file */ f.open( _filename.c_str(), ios::in ); if( !f ) throw new ErrorMsg( "Error opening XML file for input!" ); /* Read everything into the contents string */ contents.erase(); templine.erase(); do { contents += templine + " "; f >> templine; // This will cause EOF only when reading from the end. } while( !f.eof() ); f.close(); /* Remove comments and tag attributes */ removeComments( contents ); removeAttributes( contents ); removeStartXML ( contents ); /* Create root node */ root = new XMLTree( "root" ); parseFragment( contents, root ); Util.progress( "\r\n" ); // Finish progress indicator. } void XMLFile::parseFragment( string & fragment, XMLTree * parent ) { long startFoundAt; // Tag start and end found at these positions. long endFoundAt; string closingString; // Search string used for finding closing tag. long closingFoundAt; // Closing tag found at this position. string tagName; // These are for recently created nodes. string tagValue; XMLTree * newTree; XMLNode * newNode; long nestedFoundAt; // Nested tags found at this position. /* Find top level tags */ Util.progress( "#" ); // Advance progress indicator. while( true ) // Wait for break from inside. { /* Find start of tag */ startFoundAt = fragment.find( "<", 0 ); if( startFoundAt == string::npos ) // Exit loop if no tags found. break; /* Check if this is a closing tag for a higher level tag pair */ if( fragment[startFoundAt+1] == '/' ) break; // Exit loop. /* Find end of tag */ endFoundAt = fragment.find( ">", startFoundAt ); if( endFoundAt == string::npos ) // Error if end not found. throw new ErrorMsg( "Unclosed tag encountered! " "Tag start token '<' found, but no " "closing '>'." ); /* Extract name of tag */ tagName = fragment.substr( startFoundAt+1, endFoundAt-startFoundAt-1 ); if( tagName.size() == 0 ) // Error if zero-length tag name. throw new ErrorMsg( "Unnamed tag encountered! " "No text between '<' and '>'." ); /* Remove tag from fragment */ fragment.erase( 0, startFoundAt+tagName.size()+2 ); /* Check if it is an empty tag */ if( tagName[tagName.size()-1] == '/' ) { /* Create a new empty ordinary node */ tagName.erase( tagName.size()-1 ); // Remove the slash. tagValue.erase(); // This tag has no value. newNode = new XMLNode( tagName, tagValue ); parent->addNode( newNode ); } else { /* Find the matching closing tag for this pair */ closingString.erase(); closingString += ""; closingFoundAt = fragment.find( closingString, 0 ); if( closingFoundAt == string::npos ) // Error if not found. throw new ErrorMsg( "Closing tag not found! " "Opening tag '<" + tagName + ">' found, " "but not closing '" + closingString + "'." ); /* Check for tags inside this tag pair, indicating a subtree */ nestedFoundAt = fragment.find( "<", 0 ); if( nestedFoundAt == closingFoundAt ) // No other tags inside? { /* Extract contents within tag pair */ tagValue = fragment.substr( 0, closingFoundAt ); /* Create new ordinary node */ newNode = new XMLNode( tagName, tagValue ); parent->addNode( newNode ); } else { /* Create new subtree and parse it's fragment */ newTree = new XMLTree( tagName ); parent->addNode( newTree ); parseFragment( fragment, newTree ); /* Check that we can still find the closing tag */ closingFoundAt = fragment.find( closingString, 0 ); if( closingFoundAt == string::npos ) throw new ErrorMsg( "Closing tag not found! " "Opening tag '<" + tagName + ">' found, " "but not closing '" + closingString + "'." ); } /* Remove value and closing tag from fragment */ fragment.erase( 0, closingFoundAt + closingString.size() ); } }; } /* Constructor */ XMLFile::XMLFile( const string & _filename ) { readFile( _filename ); } /* Destructor */ XMLFile::~XMLFile() { if( root != NULL ) delete root; } bool XMLFile::exists( const string & path ) { XMLAbstractNode * currentNode = root; XMLTree * currentTree; long namePos; // Position for current tag name in path. long separatorPos; // Position for #-separator following tag name. string tagName; // Current tag name. namePos = 0; while( true ) // This will break out from the inside. { /* Find separator or set pos to end of text */ separatorPos = path.find( "\\", namePos ); if( separatorPos == string::npos ) separatorPos = path.size(); /* Extract tag name and check if it exists */ tagName = path.substr( namePos, separatorPos-namePos ); currentTree = (XMLTree *) currentNode; // It is indeed a tree. if( !currentTree->containsNode( tagName ) ) return false; // Not found. currentNode = currentTree->getNode( tagName ); /* Are there more tags in the path? */ if( separatorPos < path.size() ) { /* Now, the current node better be a tree */ if( currentNode->getType() != xml_subtree ) { return false; // Not found. } else { namePos = separatorPos + 1; // Advance position in path. } } else { break; // Found, exit loop. } } return true; // Found! } const string & XMLFile::getValue( const string & path ) { XMLAbstractNode * currentNode = root; XMLTree * currentTree; long namePos; // Position for current tag name in path. long separatorPos; // Position for #-separator following tag name. string tagName; // Current tag name. namePos = 0; while( true ) // This will break out from the inside. { /* Find separator or set pos to end of text */ separatorPos = path.find( "\\", namePos ); if( separatorPos == string::npos ) separatorPos = path.size(); /* Extract tag name and check if it exists */ tagName = path.substr( namePos, separatorPos-namePos ); currentTree = (XMLTree *) currentNode; // It is indeed a tree. if( !currentTree->containsNode( tagName ) ) throw new ErrorMsg( "Node '" + tagName + "' not found!" ); currentNode = currentTree->getNode( tagName ); /* Are there more tags in the path? */ if( separatorPos < path.size() ) { /* Now, the current node better be a tree */ if( currentNode->getType() != xml_subtree ) { throw new ErrorMsg( "Illegal path: (" + path + ")!" ); } else { namePos = separatorPos + 1; // Advance position in path. } } else { break; // Found, exit loop. } } /* Check that the current node is an ordinary node */ if( currentNode->getType() != xml_node ) throw new ErrorMsg( "Node '" + tagName + "' is not an element!" ); return ((XMLNode *) currentNode)->getValue(); } void XMLFile::print() { root->print(); } /* Constructor */ XMLAbstractNode::XMLAbstractNode( const string & _name, XMLNodeType _type ) : name( _name ), type( _type ) { // Node code here. } /* Destructor */ XMLAbstractNode::~XMLAbstractNode() { // No code here. } const string & XMLAbstractNode::getName() { return name; } XMLNodeType XMLAbstractNode::getType() { return type; } bool XMLAbstractNode::isName( const string & _name ) { return (name == _name); } /* Constructor */ XMLTree::XMLTree( const string & _name ) : XMLAbstractNode::XMLAbstractNode( _name, xml_subtree ) { // No code here. } /* Destructor */ XMLTree::~XMLTree() { /* Create an iterator for the list */ list::iterator i; /* Destruct all contained nodes */ for( i = nodes.begin(); i != nodes.end(); i++ ) { delete (*i); } } void XMLTree::addNode( XMLAbstractNode * newnode ) { nodes.push_back( newnode ); } bool XMLTree::containsNode( const string & _name ) { /* Create an iterator for the list */ list::iterator i; /* Search for the node with name _name */ i = nodes.begin(); while( i != nodes.end() ) { if( (*i)->isName( _name ) ) return true; i++; } return false; } XMLAbstractNode * XMLTree::getNode( const string & _name ) { /* Create an iterator for the list */ list::iterator i; /* Search for the node with name _name */ i = nodes.begin(); while( i != nodes.end() ) { if( (*i)->isName( _name ) ) return *i; i++; } return NULL; } void XMLTree::print() { /* Create an iterator for the list */ list::iterator i; cout << "TREE[ Name: \"" << name << "\" ]:" << endl; /* Search for the node with name _name */ i = nodes.begin(); while( i != nodes.end() ) { (*i)->print(); i++; } cout << ":END[\"" << name << "\"]" << endl; } /* Constructor */ XMLNode::XMLNode( const string & _name, const string & _value ) : XMLAbstractNode::XMLAbstractNode( _name, xml_node ), value( _value ) { // No code here. } /* Destructor */ XMLNode::~XMLNode() { // Node code here. } bool XMLNode::isEmpty() { return value.empty(); } const string & XMLNode::getValue() { return value; } void XMLNode::print() { cout << "NODE[ Name: \"" << name << "\" Value: \"" << value << "\" ]" << endl; } /* end of file */