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