1 /*
2  * Copyright (C) 2001-2012 Jacek Sieka, arnetheduck on gmail point com
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #pragma once
20 
21 #include "Exception.h"
22 #include "Streams.h"
23 #include "SimpleXMLReader.h"
24 
25 namespace dcpp {
26 
27 STANDARD_EXCEPTION(SimpleXMLException);
28 
29 /**
30  * A simple XML class that loads an XML-ish structure into an internal tree
31  * and allows easy access to each element through a "current location".
32  */
33 class SimpleXML : private boost::noncopyable
34 {
35 public:
SimpleXML()36     SimpleXML() : root("BOGUSROOT", Util::emptyString, NULL), current(&root), found(false) {
37         resetCurrentChild();
38     }
~SimpleXML()39     ~SimpleXML() { }
40 
41     void addTag(const string& aName, const string& aData = Util::emptyString);
addTag(const string & aName,int aData)42     void addTag(const string& aName, int aData) {
43         addTag(aName, Util::toString(aData));
44     }
addTag(const string & aName,int64_t aData)45     void addTag(const string& aName, int64_t aData) {
46         addTag(aName, Util::toString(aData));
47     }
48 
49     template<typename T>
addAttrib(const string & aName,const T & aData)50     void addAttrib(const string& aName, const T& aData) {
51         addAttrib(aName, Util::toString(aData));
52     }
53 
54     void addAttrib(const string& aName, const string& aData);
addAttrib(const string & aName,bool aData)55     void addAttrib(const string& aName, bool aData) {
56         addAttrib(aName, string(aData ? "1" : "0"));
57     }
58 
59     template <typename T>
addChildAttrib(const string & aName,const T & aData)60     void addChildAttrib(const string& aName, const T& aData) {
61         addChildAttrib(aName, Util::toString(aData));
62     }
63     void addChildAttrib(const string& aName, const string& aData);
addChildAttrib(const string & aName,bool aData)64     void addChildAttrib(const string& aName, bool aData) {
65         addChildAttrib(aName, string(aData ? "1" : "0"));
66     }
67 
getData()68     const string& getData() const {
69         dcassert(current != NULL);
70         return current->data;
71     }
72 
stepIn()73     void stepIn() {
74         checkChildSelected();
75         current = *currentChild;
76         currentChild = current->children.begin();
77         found = false;
78     }
79 
stepOut()80     void stepOut() {
81         if(current == &root)
82             throw SimpleXMLException("Already at lowest level");
83 
84         dcassert(current->parent != NULL);
85 
86         currentChild = find(current->parent->children.begin(), current->parent->children.end(), current);
87 
88         current = current->parent;
89         found = true;
90     }
91 
resetCurrentChild()92     void resetCurrentChild() noexcept {
93         found = false;
94         dcassert(current != NULL);
95         currentChild = current->children.begin();
96     }
97 
98     bool findChild(const string& aName) noexcept;
99 
getChildData()100     const string& getChildData() const {
101         checkChildSelected();
102         return (*currentChild)->data;
103     }
104 
105     const string& getChildAttrib(const string& aName, const string& aDefault = Util::emptyString) {
106         checkChildSelected();
107         return (*currentChild)->getAttrib(aName, aDefault);
108     }
109 
getIntChildAttrib(const string & aName)110     int getIntChildAttrib(const string& aName) {
111         checkChildSelected();
112         return Util::toInt(getChildAttrib(aName));
113     }
getLongLongChildAttrib(const string & aName)114     int64_t getLongLongChildAttrib(const string& aName) {
115         checkChildSelected();
116         return Util::toInt64(getChildAttrib(aName));
117     }
getBoolChildAttrib(const string & aName)118     bool getBoolChildAttrib(const string& aName) {
119         checkChildSelected();
120         const string& tmp = getChildAttrib(aName);
121 
122         return !tmp.empty() && tmp[0] == '1';
123     }
124 
125     void fromXML(const string& aXML);
toXML()126     string toXML() { string tmp; StringOutputStream os(tmp); toXML(&os); return tmp; }
toXML(OutputStream * f)127     void toXML(OutputStream* f) { if(!root.children.empty()) root.children[0]->toXML(0, f); }
128 
129     static const string& escape(const string& str, string& tmp, bool aAttrib, bool aLoading = false, const string &encoding = Text::utf8) {
130         if(needsEscape(str, aAttrib, aLoading, encoding)) {
131             tmp = str;
132             return escape(tmp, aAttrib, aLoading, encoding);
133         }
134         return str;
135     }
136     static string& escape(string& aString, bool aAttrib, bool aLoading = false, const string &encoding = Text::utf8);
137     /**
138      * This is a heuristic for whether escape needs to be called or not. The results are
139      * only guaranteed for false, i e sometimes true might be returned even though escape
140      * was not needed...
141      */
142     static bool needsEscape(const string& aString, bool aAttrib, bool aLoading = false, const string &encoding = Text::utf8) {
143         return Util::stricmp(encoding, Text::utf8) != 0 || (((aLoading) ? aString.find('&') : aString.find_first_of(aAttrib ? "<&>'\"" : "<&>")) != string::npos);
144     }
145     static const string utf8Header;
146 private:
147     class Tag {
148     public:
149         typedef Tag* Ptr;
150         typedef vector<Ptr> List;
151         typedef List::iterator Iter;
152 
153         /**
154          * A simple list of children. To find a tag, one must search the entire list.
155          */
156         List children;
157         /**
158          * Attributes of this tag. According to the XML standard the names
159          * must be unique (case-sensitive). (Assuming that we have few attributes here,
160          * we use a vector instead of a (hash)map to save a few bytes of memory and unnecessary
161          * calls to the memory allocator...)
162          */
163         StringPairList attribs;
164 
165         /** Tag name */
166         string name;
167 
168         /** Tag data, may be empty. */
169         string data;
170 
171         /** Parent tag, for easy traversal */
172         Ptr parent;
173 
Tag(const string & aName,const StringPairList & a,Ptr aParent)174         Tag(const string& aName, const StringPairList& a, Ptr aParent) : attribs(a), name(aName), data(), parent(aParent) {
175         }
176 
Tag(const string & aName,const string & d,Ptr aParent)177         Tag(const string& aName, const string& d, Ptr aParent) : name(aName), data(d), parent(aParent) {
178         }
179 
180         const string& getAttrib(const string& aName, const string& aDefault = Util::emptyString) {
181             StringPairIter i = find_if(attribs.begin(), attribs.end(), CompareFirst<string,string>(aName));
182             return (i == attribs.end()) ? aDefault : i->second;
183         }
184         void toXML(int indent, OutputStream* f);
185 
186         void appendAttribString(string& tmp);
187         /** Delete all children! */
~Tag()188         ~Tag() {
189             for(Iter i = children.begin(); i != children.end(); ++i) {
190                 delete *i;
191             }
192         }
193 
194     private:
195         Tag(const Tag&);
196         Tag& operator=(Tag&);
197     };
198 
199     class TagReader : public SimpleXMLReader::CallBack {
200     public:
TagReader(Tag * root)201         TagReader(Tag* root) : cur(root) { }
getData(string &)202         virtual bool getData(string&) { return false; }
startTag(const string & name,StringPairList & attribs,bool simple)203         virtual void startTag(const string& name, StringPairList& attribs, bool simple) {
204             cur->children.push_back(new Tag(name, attribs, cur));
205             if(!simple)
206                 cur = cur->children.back();
207         }
endTag(const string &,const string & d)208         virtual void endTag(const string&, const string& d) {
209             cur->data = d;
210             if(cur->parent == NULL)
211                 throw SimpleXMLException("Invalid end tag");
212             cur = cur->parent;
213         }
214 
215         Tag* cur;
216     };
217 
218     /** Bogus root tag, should have only one child! */
219     Tag root;
220 
221     /** Current position */
222     Tag::Ptr current;
223 
224     Tag::Iter currentChild;
225 
checkChildSelected()226     void checkChildSelected() const noexcept {
227         dcassert(current != NULL);
228         dcassert(currentChild != current->children.end());
229     }
230 
231     bool found;
232 };
233 
234 } // namespace dcpp
235