1 #ifndef _XMLDoc_h_ 2 #define _XMLDoc_h_ 3 4 //! @copyright 5 //! Copyright (C) 2006 T. Zachary Laine 6 //! 7 //! This library is free software; you can redistribute it and/or 8 //! modify it under the terms of the GNU Lesser General Public License 9 //! as published by the Free Software Foundation; either version 2.1 10 //! of the License, or (at your option) any later version. 11 //! 12 //! This library is distributed in the hope that it will be useful, 13 //! but WITHOUT ANY WARRANTY; without even the implied warranty of 14 //! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 //! Lesser General Public License for more details. 16 //! 17 //! You should have received a copy of the GNU Lesser General Public 18 //! License along with this library; if not, write to the Free 19 //! Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 20 //! 02111-1307 USA 21 //! 22 //! If you do not wish to comply with the terms of the LGPL please 23 //! contact the author as other terms are available for a fee. 24 //! 25 //! Zach Laine 26 //! whatwasthataddress@hotmail.com 27 28 //! @file 29 //! Declares XMLElement and XMLDoc class to modify, read and write simple 30 //! XML files. 31 32 #include <map> 33 #include <stdexcept> 34 #include <string> 35 #include <vector> 36 37 #include "Export.h" 38 39 //! Represents a simplified XML markup element. 40 //! 41 //! An XMLElement represents a XML element from the opening tag \<tag-name\>, 42 //! including its attributes to the corresponding closing tag \</tag-name\>. 43 //! This may or may not include a text data section @b OR child XML elements. 44 //! Using the "burns" example: 45 //! 46 //! ```{.xml} 47 //! <burns>Say <quote>Goodnight</quote> Gracie.</burns> 48 //! ``` 49 //! 50 //! may not work as expected. The resulting XMLElement \<burns\> node will 51 //! contain both the "Say " and the " Gracie." text fragment. Or represented 52 //! differently: 53 //! 54 //! ```{.js} 55 //! burns.Text() == "Say Gracie." 56 //! ``` 57 //! 58 //! However, if used to represent data structures like the example struct foo: 59 //! 60 //! ```{.cpp} 61 //! struct foo 62 //! { 63 //! int ref_ct; 64 //! double data; 65 //! }; 66 //! ``` 67 //! 68 //! the current implementation creates a XML representation similar to: 69 //! 70 //! ```{.xml} 71 //! <bar> 72 //! <foo> 73 //! <ref_ct>13<ref_ct/> 74 //! <data>0.364951<data/> 75 //! </foo> 76 //! </bar> 77 //! ``` 78 //! 79 //! Further, while the "burns" example is standard XML, an XMLElement optionally 80 //! accepts its single text string in quotes, and strips off trailing white 81 //! space, in direct contrary to the XML standard. So "burns" from above is 82 //! equivalent to: 83 //! 84 //! ```{.xml} 85 //! <burns>"Say Gracie."<quote>Goodnight</quote></burns> 86 //! ``` 87 //! 88 //! or: 89 //! 90 //! ```{.xml} 91 //! <burns>Say Gracie.<quote>Goodnight</quote></burns> 92 //! ``` 93 //! or: 94 //! 95 //! ```{.xml} 96 //! <burns>"Say Gracie." 97 //! <quote>Goodnight</quote> 98 //! </burns> 99 //! ``` 100 //! 101 //! or: 102 //! 103 //! ```{.xml} 104 //! <burns>Say Gracie. 105 //! <quote>Goodnight</quote> 106 //! </burns> 107 //! ``` 108 //! 109 //! Each of these examples yields to 110 //! 111 //! ```{.js} 112 //! burns.Text() == "Say Gracie." 113 //! ``` 114 //! 115 //! When an XMLElement is saved, its text is saved within a CDATA section. Any 116 //! string can be put inside one of these quoted text fields, even text that 117 //! includes an arbitrary number of quotes. So any std::string or c-string can 118 //! be assigned to an element. However, when hand-editing an XML file 119 //! containing such text strings, one needs to be careful. The closing quote 120 //! must be the last thing other than white space. Adding more than one quoted 121 //! text string to the XML element, with each string separated by other 122 //! elements, will result in a single concatenated string, as illustrated above. 123 //! 124 //! This is not the most time- or space-efficient way to organize object data, 125 //! but it may just be one of the simplest and most easily read. 126 class FO_COMMON_API XMLElement 127 { 128 public: 129 //! Creates a new XMLElement with an empty tag-name assigned. 130 //! 131 //! Create a new XMLElement with no tag-name, text, attribute or child nodes 132 //! set. Also the new instance isn't marked as root node. XMLElement()133 XMLElement() 134 {} 135 136 //! Creates a new XMLElement with the given @p tag tag-name and @p text 137 //! content. 138 //! 139 //! Create a new XMLElement with the given @p tag tag-name and @p text 140 //! content, but no attribute or child nodes set. Also the new instance 141 //! isn't marked as root node. 142 //! 143 //! @param[in] tag 144 //! The tag name of this XML element. 145 //! @param[in] text 146 //! The text assigned to this XML element. 147 explicit XMLElement(const std::string& tag, const std::string& text = "") : m_tag(tag)148 m_tag(tag), 149 m_text(text) 150 {} 151 152 //! Returns the the tag-name of this XMLElement. 153 //! 154 //! @return 155 //! The tag-name of this XMLElement. Can be an empty string. 156 const std::string& Tag() const; 157 158 //! Returns the text body of this XMLElement. 159 //! 160 //! @return 161 //! The text content of this XMLElement. Can be an empty string. 162 const std::string& Text() const; 163 164 //! Returns if this XMLElement contains a child with @p tag as tag-name. 165 //! 166 //! @param[in] tag 167 //! The tag-name of the searched child XMLElement. 168 //! @return 169 //! True if there is at least one child with a @p tag tag-name, false if 170 //! not. 171 bool ContainsChild(const std::string& tag) const; 172 173 //! Returns the first XMLElement child that has @p tag as tag-name. 174 //! 175 //! @param[in] tag 176 //! The tag-name of the child XMLElement requested. 177 //! @return 178 //! A reference to the first XMLElement child which has the tag-name 179 //! @p tag. 180 //! @throw std::out_of_range 181 //! When no child with a tag-name @p tag exists. 182 const XMLElement& Child(const std::string& tag) const; 183 184 //! Write this XMLElement XML formatted into the given output stream @p os 185 //! with indentation level @p indent when @p whitespace is set. 186 //! 187 //! @param[in] os 188 //! The output stream this document should be written to. 189 //! @param[in] indent 190 //! The indentation level this element should be indented. 191 //! @param[in] whitespace 192 //! If set to true the child XMLElement%s are indented and newline 193 //! separated. 194 //! @return 195 //! The given @p os output stream. 196 std::ostream& WriteElement(std::ostream& os, int indent = 0, bool whitespace = true) const; 197 198 //! Return this XMLElement XML formatted as string with indentation level 199 //! @p indent when @p whitespace is set. 200 //! 201 //! @param[in] indent 202 //! The indentation level this element should be indented. 203 //! @param[in] whitespace 204 //! If set to true the child XMLElement%s are indented and newline 205 //! separated. 206 //! @return 207 //! A string containing the XML formatted representation of this 208 //! XMLElement. 209 std::string WriteElement(int indent = 0, bool whitespace = true) const; 210 211 //! @see XMLElement::Child(const std::string&) const 212 XMLElement& Child(const std::string& tag); 213 214 //! Sets the tag-name of this XMLElement to @p tag. 215 //! 216 //! @param[in] tag 217 //! The new tag-name this XMLElement should have. 218 void SetTag(const std::string& tag); 219 220 //! Sets the text content of this XMLEement to @p text. 221 //! 222 //! @param[in] text 223 //! The new text content this XMLElement should have. 224 void SetText(const std::string& text); 225 226 //! The attributes associated to this XMLElement by key name mapping. 227 std::map<std::string, std::string> attributes; 228 229 //! Stores a list of the child XMLElement%s associated to this XMLElement. 230 //! 231 //! This list can be empty when this XMLElement has no associated child 232 //! elements. 233 std::vector<XMLElement> children; 234 235 private: 236 //! Creates a new XMLElement with the given @p tag tag-name and marked as 237 //! root node, if @p root is set. 238 //! 239 //! @param[in] tag 240 //! The tag name of this XML element. 241 //! @param[in] root 242 //! When true this XMLElement should be interpreted as root node in 243 //! a XMLElement tree. 244 //! 245 //! @note 246 //! Called by friend XMLDoc. XMLElement(const std::string & tag,bool root)247 XMLElement(const std::string& tag, bool root) : 248 m_tag(tag), 249 m_root(root) 250 {} 251 252 //! Stores the tag-name associated to this XMLElement. 253 //! 254 //! @bug 255 //! Currently this can contain an empty string but I doubt it will be 256 //! useful as it will cause invalid XML documents serializations. 257 std::string m_tag; 258 259 //! Stores the text content associated to this XMLElement. 260 std::string m_text; 261 262 //! Set to true if this XMLElement is the root element of an XMLDoc 263 //! document. 264 bool m_root = false; 265 266 friend class XMLDoc; 267 }; 268 269 //! Represents a document formatted with XML markup. 270 //! 271 //! Each XMLDoc instance is assumed to represent a complete document. It 272 //! contains a tree of nested XMLElement%s, starting from the always existing 273 //! root XMLElement node. 274 class FO_COMMON_API XMLDoc 275 { 276 public: 277 //! Create an empty document with the given tag-name @p root_tag. 278 //! 279 //! @param[in] root_tag 280 //! The tag-name of the created root XMLElement. 281 XMLDoc(const std::string& root_tag = "XMLDoc"); 282 283 //! Construct a document from the given input stream @p is. 284 //! 285 //! @param[in] is 286 //! An input stream that provides an XML markup document once read. 287 //! 288 //! @bug 289 //! @p is isn't actually read but ignored and an empty (and maybe 290 //! invalid) document is created. Use XMLDoc::ReadDoc(std::istream&) 291 //! instead. 292 XMLDoc(const std::istream& is); 293 294 //! Write the contents of the XMLDoc into the given output stream @p os 295 //! with optional @p indent. 296 //! 297 //! @param[in] os 298 //! The output stream this document should be written to. 299 //! @param[in] indent 300 //! If set to true the XML elements are indented and newline separated. 301 //! 302 //! @return 303 //! The given @p os output stream. 304 std::ostream& WriteDoc(std::ostream& os, bool indent = true) const; 305 306 //! Clears the current content of this XMLDoc instance and read a new 307 //! document from the given input stream @p is. 308 //! 309 //! @param[in] is 310 //! An input stream that provides an XML markup document once read. 311 //! 312 //! @return 313 //! The given @p is input stream. 314 std::istream& ReadDoc(std::istream& is); 315 316 //! Clears the current content of this XMLDoc instance and read a new 317 //! document from the given string @p s. 318 //! 319 //! @param[in] s 320 //! A string containing an XML markup document. 321 void ReadDoc(const std::string& s); 322 323 //! The root element that contains the parsed document, which is represented 324 //! by XMLElement%s. 325 XMLElement root_node; 326 327 private: 328 //! Creates the XML parsing rules at static initialization time. 329 struct RuleDefiner { RuleDefiner(); }; 330 static RuleDefiner s_rule_definer; 331 332 //! Holds the XMLDoc to which the XML parser should add parsed elements. 333 //! 334 //! @todo 335 //! No need to hold a static instance here, pass the document into the 336 //! parser as additional attribute. This also avoids potential problems 337 //! in multithreaded setups. 338 static XMLDoc* s_curr_parsing_doc; 339 340 //! Holds the current environment for reading XMLElement%s (the current 341 //! enclosing XMLElement%s). 342 static std::vector<XMLElement*> s_element_stack; 343 344 //! Convert string tokens into XMLElement attributes. 345 //! @{ 346 static XMLElement s_temp_elem; 347 static std::string s_temp_attr_name; 348 349 static void SetElemName(const char* first, const char* last); 350 static void SetAttributeName(const char* first, const char* last); 351 static void AddAttribute(const char* first, const char* last); 352 static void PushElem1(const char*, const char*); 353 static void PushElem2(const char*, const char*); 354 static void PopElem(const char*, const char*); 355 static void AppendToText(const char* first, const char* last); 356 //! @} 357 }; 358 359 #endif // _XMLDoc_h_ 360