1 /* This file is part of the KDE project
2    Copyright (C) 2004 David Faure <faure@kde.org>
3    Copyright (C) 2007 Thomas Zander <zander@kde.org>
4    Copyright (C) 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.com>
5 
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public
8    License as published by the Free Software Foundation; either
9    version 2 of the License, or (at your option) any later version.
10 
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15 
16    You should have received a copy of the GNU Library General Public License
17    along with this library; see the file COPYING.LIB.  If not, write to
18    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20 */
21 
22 #ifndef XMLWRITER_H
23 #define XMLWRITER_H
24 
25 #include <QMap>
26 #include <QIODevice>
27 #include "kritastore_export.h"
28 
29 /**
30  * A class for writing out XML (to any QIODevice), with a special attention on performance.
31  * The XML is being written out along the way, which avoids requiring the entire
32  * document in memory (like QDom does).
33  */
34 class KRITASTORE_EXPORT KoXmlWriter
35 {
36 public:
37     /**
38      * Create a KoXmlWriter instance to write out an XML document into
39      * the given QIODevice.
40      */
41     explicit KoXmlWriter(QIODevice* dev, int indentLevel = 0);
42 
43     /// Destructor
44     ~KoXmlWriter();
45 
46     QIODevice *device() const;
47 
48     /**
49      * Start the XML document.
50      * This writes out the \<?xml?\> tag with utf8 encoding, and the DOCTYPE.
51      * @param rootElemName the name of the root element, used in the DOCTYPE tag.
52      * @param publicId the public identifier, e.g. "-//OpenOffice.org//DTD OfficeDocument 1.0//EN"
53      * @param systemId the system identifier, e.g. "office.dtd" or a full URL to it.
54      */
55     void startDocument(const char* rootElemName, const char* publicId = 0, const char* systemId = 0);
56 
57     /// Call this to terminate an XML document.
58     void endDocument();
59 
60     /**
61      * Start a new element, as a child of the current element.
62      * @param tagName the name of the tag. Warning: this string must
63      * remain alive until endElement, no copy is internally made.
64      * Usually tagName is a string constant so this is no problem anyway.
65      * @param indentInside if set to false, there will be no indentation inside
66      * this tag. This is useful for elements where whitespace matters.
67      */
68     void startElement(const char* tagName, bool indentInside = true);
69 
70     /**
71      * Overloaded version of addAttribute( const char*, const char* ),
72      * which is a bit slower because it needs to convert @p value to utf8 first.
73      */
addAttribute(const char * attrName,const QString & value)74     inline void addAttribute(const char* attrName, const QString& value) {
75         addAttribute(attrName, value.toUtf8());
76     }
77     /**
78      * Add an attribute whose value is an integer
79      */
addAttribute(const char * attrName,int value)80     inline void addAttribute(const char* attrName, int value) {
81         addAttribute(attrName, QByteArray::number(value));
82     }
83     /**
84      * Add an attribute whose value is an unsigned integer
85      */
addAttribute(const char * attrName,uint value)86     inline void addAttribute(const char* attrName, uint value) {
87         addAttribute(attrName, QByteArray::number(value));
88     }
89     /**
90      * Add an attribute whose value is an bool
91      * It is written as "true" or "false" based on value
92      */
addAttribute(const char * attrName,bool value)93     inline void addAttribute(const char* attrName, bool value) {
94         addAttribute(attrName, value ? "true" : "false");
95     }
96     /**
97      * Add an attribute whose value is a floating point number
98      * The number is written out with the highest possible precision
99      * (unlike QString::number and setNum, which default to 6 digits)
100      */
101     void addAttribute(const char* attrName, double value);
102     /**
103      * Add an attribute whose value is a floating point number
104      * The number is written out with the highest possible precision
105      * (unlike QString::number and setNum, which default to 6 digits)
106      */
107     void addAttribute(const char* attrName, float value);
108 
109     /// Overloaded version of the one taking a const char* argument, for convenience
110     void addAttribute(const char* attrName, const QByteArray& value);
111 
112     /**
113      * Add an attribute to the current element.
114      */
115     void addAttribute(const char* attrName, const char* value);
116     /**
117      * Terminate the current element. After this you should start a new one (sibling),
118      * add a sibling text node, or close another one (end of siblings).
119      */
120     void endElement();
121     /**
122      * Overloaded version of addTextNode( const char* ),
123      * which is a bit slower because it needs to convert @p str to utf8 first.
124      */
addTextNode(const QString & str)125     inline void addTextNode(const QString& str) {
126         addTextNode(str.toUtf8());
127     }
128     /// Overloaded version of the one taking a const char* argument
129     void addTextNode(const QByteArray& cstr);
130     /**
131      * @brief Adds a text node as a child of the current element.
132      *
133      * This is appends the literal content of @p str to the contents of the element.
134      * E.g. addTextNode( "foo" ) inside a \<p\> element gives \<p\>foo\</p\>,
135      * and startElement( "b" ); endElement( "b" ); addTextNode( "foo" ) gives \<p\>\<b/\>foo\</p\>
136      */
137     void addTextNode(const char* cstr);
138 
139     /**
140      * @brief Adds a processing instruction
141      *
142      * This writes a processing instruction, like <?foo bar blah?>, where foo
143      * is the target, and the rest is the data.
144      *
145      * Processing instructions are used in XML to keep processor-specific
146      * information in the text of the document.
147      */
148     void addProcessingInstruction(const char* cstr);
149 
150     /**
151      * This is quite a special-purpose method, not for everyday use.
152      * It adds a complete element (with its attributes and child elements)
153      * as a child of the current element. The string is supposed to be escaped
154      * for XML already, so it will usually come from another KoXmlWriter.
155      */
156     void addCompleteElement(const char* cstr);
157 
158     /**
159      * This is quite a special-purpose method, not for everyday use.
160      * It adds a complete element (with its attributes and child elements)
161      * as a child of the current element. The iodevice is supposed to be escaped
162      * for XML already, so it will usually come from another KoXmlWriter.
163      * This is usually used with KTempFile.
164      */
165     void addCompleteElement(QIODevice* dev);
166 
167     // #### Maybe we want to subclass KoXmlWriter for manifest files.
168     /**
169      * Special helper for writing "manifest" files
170      * This is equivalent to startElement/2*addAttribute/endElement
171      * This API will probably have to change (or not be used anymore)
172      * when we add support for encrypting/signing.
173      * @note OASIS-specific
174      */
175     void addManifestEntry(const QString& fullPath, const QString& mediaType);
176 
177     /**
178      * Special helper for writing config item into settings.xml
179      * @note OASIS-specific
180      */
181     void addConfigItem(const QString & configName, const QString& value);
182     /// @note OASIS-specific
183     void addConfigItem(const QString & configName, bool value);
184     /// @note OASIS-specific
185     void addConfigItem(const QString & configName, int value);
186     /// @note OASIS-specific
187     void addConfigItem(const QString & configName, double value);
188     /// @note OASIS-specific
189     void addConfigItem(const QString & configName, float value);
190     /// @note OASIS-specific
191     void addConfigItem(const QString & configName, long value);
192     /// @note OASIS-specific
193     void addConfigItem(const QString & configName, short value);
194 
195     // TODO addConfigItem for datetime and base64Binary
196 
197     /**
198      * @brief Adds a text span as nodes of the current element.
199      *
200      * Unlike KoXmlWriter::addTextNode it handles tabulations, linebreaks,
201      * and multiple spaces by using the appropriate OASIS tags.
202      *
203      * @param text the text to write
204      *
205      * @note OASIS-specific
206      */
207     void addTextSpan(const QString& text);
208     /**
209      * Overloaded version of addTextSpan which takes an additional tabCache map.
210      * @param text the text to write
211      * @param tabCache optional map allowing to find a tab for a given character index
212      * @note OASIS-specific
213      */
214     void addTextSpan(const QString& text, const QMap<int, int>& tabCache);
215 
216     /**
217      * @return the current indentation level.
218      * Useful when creating a sub-KoXmlWriter (see addCompleteElement)
219      */
220     int indentLevel() const;
221 
222     /**
223      * Return all the open tags at this time, root element first.
224      */
225     QList<const char*> tagHierarchy() const;
226 
227     /**
228      * Return the so far written XML as string for debugging purposes.
229      */
230     QString toString() const;
231 
232 private:
233     struct Tag {
234         Tag(const char* t = 0, bool ind = true)
tagNameTag235                 : tagName(t), hasChildren(false), lastChildIsText(false),
236                 openingTagClosed(false), indentInside(ind) {}
TagTag237         Tag(const Tag &original)
238         {
239             tagName = original.tagName;
240             hasChildren = original.hasChildren;
241             lastChildIsText = original.lastChildIsText;
242             openingTagClosed = original.openingTagClosed;
243             indentInside = original.indentInside;
244         }
245         const char* tagName;
246         bool hasChildren : 1; ///< element or text children
247         bool lastChildIsText : 1; ///< last child is a text node
248         bool openingTagClosed : 1; ///< true once the '\>' in \<tag a="b"\> is written out
249         bool indentInside : 1; ///< whether to indent the contents of this tag
250     };
251 
252     /// Write out \n followed by the number of spaces required.
253     void writeIndent();
254 
255     // writeCString is much faster than writeString.
256     // Try to use it as much as possible, especially with constants.
257     void writeString(const QString& str);
258 
259     // TODO check return value!!!
writeCString(const char * cstr)260     inline void writeCString(const char* cstr) {
261         device()->write(cstr, qstrlen(cstr));
262     }
writeChar(char c)263     inline void writeChar(char c) {
264         device()->putChar(c);
265     }
closeStartElement(Tag & tag)266     inline void closeStartElement(Tag& tag) {
267         if (!tag.openingTagClosed) {
268             tag.openingTagClosed = true;
269             writeChar('>');
270         }
271     }
272     char* escapeForXML(const char* source, int length) const;
273     bool prepareForChild(bool indentInside = true);
274     void prepareForTextNode();
275     void init();
276 
277     class Private;
278     Private * const d;
279 
280     KoXmlWriter(const KoXmlWriter &);   // forbidden
281     KoXmlWriter& operator=(const KoXmlWriter &);   // forbidden
282 };
283 
284 #endif /* XMLWRITER_H */
285 
286