SimpleXML.cpp

Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2006-2010 Jacek Sieka, arnetheduck on gmail point com
00003  *
00004  * This program is free software; you can redistribute it and/or modify
00005  * it under the terms of the GNU General Public License as published by
00006  * the Free Software Foundation; either version 2 of the License, or
00007  * (at your option) any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00017  */
00018 
00019 #include "adchpp.h"
00020 
00021 #include "SimpleXML.h"
00022 
00023 namespace adchpp {
00024 
00025 using namespace std;
00026 
00027 SimpleXML::SimpleXML(int numAttribs) : attribs(numAttribs), found(false) {
00028     root = current = new Tag("BOGUSROOT", Util::emptyString, NULL);
00029 }
00030 
00031 SimpleXML::~SimpleXML() {
00032     delete root;
00033 }
00034 
00035 void SimpleXML::escape(string& aString, bool aAttrib, bool aLoading /* = false */) {
00036     string::size_type i = 0;
00037     const char* chars = aAttrib ? "<&>'\"" : "<&>";
00038 
00039     if(aLoading) {
00040         while((i = aString.find('&', i)) != string::npos) {
00041             if(aString.compare(i+1, 3, "lt;") == 0) {
00042                 aString.replace(i, 4, 1, '<');
00043             } else if(aString.compare(i+1, 4, "amp;") == 0) {
00044                 aString.replace(i, 5, 1, '&');
00045             } else if(aString.compare(i+1, 3, "gt;") == 0) {
00046                 aString.replace(i, 4, 1, '>');
00047             } else if(aAttrib) {
00048                 if(aString.compare(i+1, 5, "apos;") == 0) {
00049                     aString.replace(i, 6, 1, '\'');
00050                 } else if(aString.compare(i+1, 5, "quot;") == 0) {
00051                     aString.replace(i, 6, 1, '"');
00052                 }
00053             }
00054             i++;
00055         }
00056         i = 0;
00057         if( (i = aString.find('\n')) != string::npos) {
00058             if(i > 0 && aString[i-1] != '\r') {
00059                 // This is a unix \n thing...convert it...
00060                 i = 0;
00061                 while( (i = aString.find('\n', i) ) != string::npos) {
00062                     if(aString[i-1] != '\r')
00063                         aString.insert(i, 1, '\r');
00064 
00065                     i+=2;
00066                 }
00067             }
00068         }
00069     } else {
00070         while( (i = aString.find_first_of(chars, i)) != string::npos) {
00071             switch(aString[i]) {
00072             case '<': aString.replace(i, 1, "&lt;"); i+=4; break;
00073             case '&': aString.replace(i, 1, "&amp;"); i+=5; break;
00074             case '>': aString.replace(i, 1, "&gt;"); i+=4; break;
00075             case '\'': aString.replace(i, 1, "&apos;"); i+=6; break;
00076             case '"': aString.replace(i, 1, "&quot;"); i+=6; break;
00077             default: dcasserta(0);
00078             }
00079         }
00080     }
00081 }
00082 
00083 void SimpleXML::Tag::appendAttribString(string& tmp) {
00084     for(AttribIter i = attribs.begin(); i!= attribs.end(); ++i) {
00085         tmp.append(i->first);
00086         tmp.append("=\"", 2);
00087         if(needsEscape(i->second, true)) {
00088             string tmp2(i->second);
00089             escape(tmp2, true);
00090             tmp.append(tmp2);
00091         } else {
00092             tmp.append(i->second);
00093         }
00094         tmp.append("\" ", 2);
00095     }
00096     tmp.erase(tmp.size()-1);
00097 }
00098 
00099 string SimpleXML::Tag::toXML(int indent) {
00100     if(children.empty() && data.empty()) {
00101         string tmp;
00102         tmp.reserve(indent + name.length() + 30);
00103         tmp.append(indent, '\t');
00104         tmp.append(1, '<');
00105         tmp.append(name);
00106         tmp.append(1, ' ');
00107         appendAttribString(tmp);
00108         tmp.append("/>\r\n", 4);
00109         return tmp;
00110     } else {
00111         string tmp;
00112         tmp.append(indent, '\t');
00113         tmp.append(1, '<');
00114         tmp.append(name);
00115         tmp.append(1, ' ');
00116         appendAttribString(tmp);
00117         if(children.empty()) {
00118             tmp.append(1, '>');
00119             if(needsEscape(data, false)) {
00120                 string tmp2(data);
00121                 escape(tmp2, false);
00122                 tmp.append(tmp2);
00123             } else {
00124                 tmp.append(data);
00125             }
00126         } else {
00127             tmp.append(">\r\n", 3);
00128             for(Iter i = children.begin(); i!=children.end(); ++i) {
00129                 tmp.append((*i)->toXML(indent + 1));
00130             }
00131             tmp.append(indent, '\t');
00132         }
00133         tmp.append("</", 2);
00134         tmp.append(name);
00135         tmp.append(">\r\n", 3);
00136         return tmp;
00137     }
00138 }
00139 
00140 bool SimpleXML::findChild(const string& aName) const throw() {
00141     dcassert(current != NULL);
00142 
00143     if(found && currentChild != current->children.end())
00144         currentChild++;
00145 
00146     while(currentChild!=current->children.end()) {
00147         if(aName.empty() || (*currentChild)->name == aName) {
00148             found = true;
00149             return true;
00150         } else
00151             currentChild++;
00152     }
00153     return false;
00154 }
00155 
00156 void SimpleXML::stepIn() const throw(SimpleXMLException) {
00157     checkChildSelected();
00158     current = *currentChild;
00159     currentChild = current->children.begin();
00160     found = false;
00161 }
00162 
00163 void SimpleXML::stepOut() const throw(SimpleXMLException) {
00164     if(current == root)
00165         throw SimpleXMLException("Already at lowest level");
00166 
00167     dcassert(current->parent != NULL);
00168 
00169     currentChild = find(current->parent->children.begin(), current->parent->children.end(), current);
00170 
00171     current = current->parent;
00172     found = true;
00173 }
00174 
00175 string::size_type SimpleXML::Tag::loadAttribs(const string& tmp, string::size_type start) throw(SimpleXMLException) {
00176     string::size_type i = start;
00177     string::size_type j;
00178     for(;;) {
00179         j = tmp.find('=', i);
00180         if(j == string::npos) {
00181             throw SimpleXMLException("Missing '=' in " + name);
00182         }
00183         if(tmp[j+1] != '"' && tmp[j+1] != '\'') {
00184             throw SimpleXMLException("Invalid character after '=' in " + name);
00185         }
00186         string::size_type x = j + 2;
00187         string::size_type y = tmp.find(tmp[j+1], x);
00188         if(y == string::npos) {
00189             throw SimpleXMLException("Missing '" + string(1, tmp[j+1]) + "' in " + name);
00190         }
00191         // Ok, we have an attribute...
00192         attribs.push_back(make_pair(tmp.substr(i, j-i), tmp.substr(x, y-x)));
00193         escape(attribs.back().second, true, true);
00194 
00195         i = tmp.find_first_not_of("\r\n\t ", y + 1);
00196         if(tmp[i] == '/' || tmp[i] == '>')
00197             return i;
00198     }
00199 }
00200 
00201 string::size_type SimpleXML::Tag::fromXML(const string& tmp, string::size_type start, int aa, bool isRoot /* = false */) throw(SimpleXMLException) {
00202     string::size_type i = start;
00203     string::size_type j;
00204 
00205     bool hasChildren = false;
00206     dcassert(tmp.size() > 0);
00207 
00208     for(;;) {
00209         j = tmp.find('<', i);
00210         if(j == string::npos) {
00211             if(isRoot) {
00212                 throw SimpleXMLException("Invalid XML file, missing root tag");
00213             } else {
00214                 throw SimpleXMLException("Missing end tag in " + name);
00215             }
00216         }
00217 
00218         // Check that we have at least 3 more characters as the shortest valid xml tag is <a/>...
00219         if((j + 3) > tmp.size()) {
00220             throw SimpleXMLException("Missing end tag in " + name);
00221         }
00222 
00223         Ptr child = NULL;
00224 
00225         i = j + 1;
00226 
00227         if(tmp[i] == '?') {
00228             // <? processing instruction ?>, ignore...
00229             i = tmp.find("?>", i);
00230             if(i == string::npos) {
00231                 throw SimpleXMLException("Missing '?>' in " + name);
00232             }
00233             i+= 2;
00234             continue;
00235         }
00236 
00237         if(tmp[i] == '!' && tmp[i+1] == '-' && tmp[i+2] == '-') {
00238             // <!-- comment -->, ignore...
00239             i = tmp.find("-->", i);
00240             if(i == string::npos) {
00241                 throw SimpleXMLException("Missing '-->' in " + name);
00242             }
00243             continue;
00244         }
00245 
00246         // Check if we reached the end tag
00247         if(tmp[i] == '/') {
00248             i++;
00249             if( (tmp.compare(i, name.length(), name) == 0) &&
00250                 (tmp[i + name.length()] == '>') )
00251             {
00252                 if(!hasChildren) {
00253                     data = tmp.substr(start, i - start - 2);
00254                     escape(data, false, true);
00255                 }
00256                 return i + name.length() + 1;
00257             } else {
00258                 throw SimpleXMLException("Missing end tag in " + name);
00259             }
00260         }
00261 
00262         // Alright, we have a real tag for sure...now get the name of it.
00263         j = tmp.find_first_of("\r\n\t />", i);
00264         if(j == string::npos) {
00265             throw SimpleXMLException("Missing '>' in " + name);
00266         }
00267 
00268         child = new Tag(tmp.substr(i, j-i), Util::emptyString, this, aa);
00269         // Put it here immideately to avoid mem leaks
00270         children.push_back(child);
00271 
00272         if(tmp[j] == ' ')
00273             j = tmp.find_first_not_of("\r\n\t ", j+1);
00274 
00275         if(j == string::npos) {
00276             throw SimpleXMLException("Missing '>' in " + name);
00277         }
00278 
00279         if(tmp[j] != '/' && tmp[j] != '>') {
00280             // We have attribs...
00281             j = child->loadAttribs(tmp, j);
00282         }
00283 
00284         if(tmp[j] == '>') {
00285             // This is a real tag with data etc...
00286             hasChildren = true;
00287             j = child->fromXML(tmp, j+1, aa);
00288         } else {
00289             // A simple tag (<xxx/>
00290             j++;
00291         }
00292         i = j;
00293         if(isRoot) {
00294             if(tmp.find('<', i) != string::npos) {
00295                 throw SimpleXMLException("Invalid XML file, multiple root tags");
00296             }
00297             return tmp.length();
00298         }
00299     }
00300 }
00301 
00302 void SimpleXML::addTag(const string& aName, const string& aData /* = "" */) throw(SimpleXMLException) {
00303     if(aName.empty()) {
00304         throw SimpleXMLException("Empty tag names not allowed");
00305     }
00306 
00307     if(current == root) {
00308         if(current->children.empty()) {
00309             current->children.push_back(new Tag(aName, aData, root, attribs));
00310             currentChild = current->children.begin();
00311         } else {
00312             throw SimpleXMLException("Only one root tag allowed");
00313         }
00314     } else {
00315         current->children.push_back(new Tag(aName, aData, current, attribs));
00316         currentChild = current->children.end() - 1;
00317     }
00318 }
00319 
00320 void SimpleXML::addAttrib(const string& aName, const string& aData) throw(SimpleXMLException) {
00321     if(current==root)
00322         throw SimpleXMLException("No tag is currently selected");
00323 
00324     current->attribs.push_back(make_pair(aName, aData));
00325 }
00326 
00327 void SimpleXML::addChildAttrib(const string& aName, const string& aData) throw(SimpleXMLException) {
00328     checkChildSelected();
00329 
00330     (*currentChild)->attribs.push_back(make_pair(aName, aData));
00331 }
00332 
00333 void SimpleXML::fromXML(const string& aXML) throw(SimpleXMLException) {
00334     if(root) {
00335         delete root;
00336     }
00337     root = new Tag("BOGUSROOT", Util::emptyString, NULL, 0);
00338 
00339     root->fromXML(aXML, 0, attribs, true);
00340 
00341     if(root->children.size() != 1) {
00342         throw SimpleXMLException("Invalid XML file, missing or multiple root tags");
00343     }
00344 
00345     current = root;
00346     resetCurrentChild();
00347 }
00348 
00349 }
Generated on Sat Nov 27 23:37:53 2010 for adchpp by  doxygen 1.6.3