/*

XML File Reader...

Author: Matthew W. Coan
Date: Tue Jul  3 03:44:44 EDT 2012

*/

#ifndef _XML_H
#define _XML_H

#include <fstream>
#include <string>
#include <map>

#include <cctype>

namespace xml_tools {

using namespace std;

typedef map< string, string > string_map_type;

class XML_Exception {
public:
};

class XML_Tag {
   string_map_type param;
   string name;

public:
   XML_Tag(const string & str) {
      string name,value;
      char ch;
      bool get_name = true;
      bool first = true;
      bool get = true;

      for(size_t i = 0; i < str.size(); i++) {
         ch = str[i];

         if(isspace(ch) && get_name) {
            get = false;
            name = "";
            continue;
         }

         if(ch == '<' | (ch == '/' && i == 1)) {
            continue;
         }

         if(ch == '=') {
            if(str[i+1] == '\"') {
               i++;
            }
            get_name = false;
            continue;
         }
         if(ch == '\"') {
            cout << "\"" << name << "\" -> \"" << value << "\"" << endl << flush;
            param[name] = value;
            get_name = true;
            continue;
         }
         if(ch == '>') {
            break; 
         }
         if(get_name) {
            name += ch;
            if(get) {
               this->name += ch;
            }
         }
         else {
            value += ch;
         }
      } 
   }
   const string & operator[](const string & name) {
      return param[name];
   }

   const string & get_name() { 
      return name; 
   }
};

class XML_Data {
   string data;
public:
   XML_Data(const string & str) {
      data = str;
   }
   const string & get_data() { return data; }
};

class XML_Parser {
   string file_name;
   ifstream fin;

public:
   XML_Parser() {

   }

   XML_Parser(const string & filename)  
   :file_name(filename) {
      fin.open(file_name.c_str(), ios::in);
      if(!fin) throw XML_Exception();
   }

   XML_Parser(const XML_Parser & cp) {
      file_name = cp.file_name;
      if(fin) fin.close();
      fin.open(file_name.c_str(), ios::in);
      if(!fin) throw XML_Exception();
   }

   virtual ~XML_Parser() {
      if(fin) {
         fin.close();
      }
   }

   XML_Parser & operator=(const XML_Parser & right) {
      file_name = right.file_name;
      if(fin) fin.close();
      fin.open(file_name.c_str(), ios::in);
      if(!fin) throw XML_Exception();
      return *this;
   }

   virtual void on_start_tag(XML_Tag & tag) = 0;
   virtual void on_end_tag(XML_Tag & tag) = 0;
   virtual void on_data(XML_Data & data) = 0;

   string trim(const string & str) {
      string ret;
      size_t i;
      for(i = 0; i < str.size(); i++) {
         if(!isspace(str[i])) {
            break;
         }
      }
      while(i < str.size()) {
         ret += str[i];
         i++;
      }
      if(ret.size()) {
         while(isspace(ret[ret.size()-1])) {
            ret.erase(ret.end());
         }
      }
      return ret;
   }

   string read_tag() {
      string tag, data;
      char ch;
      if(fin) {
         bool in_tag = false;
         ch = fin.get();
         while(fin) {
            if(ch == '<') {
               if(data.size()) {
                  fin.unget();
                  break;
               }
               in_tag = true;
            }
            else if(ch == '>') { 
               tag += '>';
               in_tag = false;
               break;
            }

            if(in_tag) {
               tag += ch;
            }
            else {
               data += ch;
            }

            ch = fin.get();
         }
         //fin.close();
      }
      string ret;
      if(tag.size())
         ret = tag;
      else 
         ret = data;
      ret = trim(ret);
      return ret;
   }

   int parse() {
      int ret;
      if(!fin) {
         ret = 1;
      }
      else {
         string tag = read_tag();
         while(fin) {
            if(tag.size()) {
               if(tag[0] == '<') {
                  if(tag[1] == '/') {
                     XML_Tag the_tag(tag);
                     on_end_tag(the_tag);
                  }
                  else {
                     XML_Tag the_tag(tag);
                     on_start_tag(the_tag);
                  }
               }
               else {
                  XML_Data the_data(tag);
                  on_data(the_data);
               }
            }
            tag = read_tag();
         }
         fin.close();
         ret = 0;
      }
      return ret;
   }
};
}

#endif /* _XML_H */
