1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the qmake application of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "xmloutput.h"
30 
31 QT_BEGIN_NAMESPACE
32 
XmlOutput(QTextStream & file,ConverstionType type)33 XmlOutput::XmlOutput(QTextStream &file, ConverstionType type)
34     : xmlFile(file), indent("\t"), currentLevel(0), currentState(Bare), format(NewLine),
35       conversion(type)
36 {
37     tagStack.clear();
38 }
39 
~XmlOutput()40 XmlOutput::~XmlOutput()
41 {
42     closeAll();
43 }
44 
45 // Settings ------------------------------------------------------------------
setIndentString(const QString & indentString)46 void XmlOutput::setIndentString(const QString &indentString)
47 {
48     indent = indentString;
49 }
50 
indentString()51 QString XmlOutput::indentString()
52 {
53     return indent;
54 }
55 
setIndentLevel(int level)56 void XmlOutput::setIndentLevel(int level)
57 {
58     currentLevel = level;
59 }
60 
indentLevel()61 int XmlOutput::indentLevel()
62 {
63     return currentLevel;
64 }
65 
setState(XMLState state)66 void XmlOutput::setState(XMLState state)
67 {
68     currentState = state;
69 }
70 
setFormat(XMLFormat newFormat)71 void XmlOutput::setFormat(XMLFormat newFormat)
72 {
73     format = newFormat;
74 }
75 
state()76 XmlOutput::XMLState XmlOutput::state()
77 {
78     return currentState;
79 }
80 
updateIndent()81 void XmlOutput::updateIndent()
82 {
83     currentIndent.clear();
84     currentIndent.reserve(currentLevel);
85     for (int i = 0; i < currentLevel; ++i)
86         currentIndent.append(indent);
87 }
88 
increaseIndent()89 void XmlOutput::increaseIndent()
90 {
91     ++currentLevel;
92     updateIndent();
93 }
94 
decreaseIndent()95 void XmlOutput::decreaseIndent()
96 {
97     if (currentLevel)
98         --currentLevel;
99     updateIndent();
100     if (!currentLevel)
101         currentState = Bare;
102 }
103 
doConversion(const QString & text)104 QString XmlOutput::doConversion(const QString &text)
105 {
106     if (!text.count())
107         return QString();
108     else if (conversion == NoConversion)
109         return text;
110 
111     QString output;
112     if (conversion == XMLConversion) {
113 
114         // this is a way to escape characters that shouldn't be converted
115         for (int i=0; i<text.count(); ++i) {
116             const QChar c = text.at(i);
117             if (c == QLatin1Char('&')) {
118                 if ( (i + 7) < text.count() &&
119                     text.at(i + 1) == QLatin1Char('#') &&
120                     text.at(i + 2) == QLatin1Char('x') &&
121                     text.at(i + 7) == QLatin1Char(';') ) {
122                     output += text.at(i);
123                 } else {
124                     output += QLatin1String("&amp;");
125                 }
126             } else if (c == QLatin1Char('<')) {
127                 output += QLatin1String("&lt;");
128             } else if (c == QLatin1Char('>')) {
129                 output += QLatin1String("&gt;");
130             } else {
131                 if (c.unicode() < 0x20) {
132                     output += QString("&#x%1;").arg(c.unicode(), 2, 16, QLatin1Char('0'));
133                 } else {
134                     output += c;
135                 }
136             }
137         }
138     } else {
139         output = text;
140     }
141 
142     if (conversion == XMLConversion) {
143         output.replace('\"', QLatin1String("&quot;"));
144         output.replace('\'', QLatin1String("&apos;"));
145     } else if (conversion == EscapeConversion) {
146         output.replace('\"', QLatin1String("\\\""));
147         output.replace('\'', QLatin1String("\\\'"));
148     }
149     return output;
150 }
151 
152 // Stream functions ----------------------------------------------------------
operator <<(const QString & o)153 XmlOutput& XmlOutput::operator<<(const QString& o)
154 {
155     return operator<<(data(o));
156 }
157 
operator <<(const xml_output & o)158 XmlOutput& XmlOutput::operator<<(const xml_output& o)
159 {
160     switch(o.xo_type) {
161     case tNothing:
162         break;
163     case tRaw:
164         addRaw(o.xo_text);
165         break;
166     case tDeclaration:
167         addDeclaration(o.xo_text, o.xo_value);
168         break;
169     case tTag:
170         newTagOpen(o.xo_text);
171         break;
172     case tTagValue:
173         addRaw(QString("\n%1<%2>").arg(currentIndent).arg(o.xo_text));
174         addRaw(doConversion(o.xo_value));
175         addRaw(QString("</%1>").arg(o.xo_text));
176         break;
177     case tValueTag:
178         addRaw(doConversion(o.xo_text));
179         setFormat(NoNewLine);
180         closeTag();
181         setFormat(NewLine);
182         break;
183     case tImport:
184         addRaw(QString("\n%1<Import %2=\"%3\" />").arg(currentIndent).arg(o.xo_text).arg(o.xo_value));
185         break;
186     case tCloseTag:
187         if (o.xo_value.count())
188             closeAll();
189         else if (o.xo_text.count())
190             closeTo(o.xo_text);
191         else
192             closeTag();
193         break;
194     case tAttribute:
195         addAttribute(o.xo_text, o.xo_value);
196         break;
197     case tAttributeTag:
198         addAttributeTag(o.xo_text, o.xo_value);
199         break;
200     case tData:
201         {
202             // Special case to be able to close tag in normal
203             // way ("</tag>", not "/>") without using addRaw()..
204             if (!o.xo_text.count()) {
205                 closeOpen();
206                 break;
207             }
208             QString output = doConversion(o.xo_text);
209             output.replace('\n', "\n" + currentIndent);
210             addRaw(QString("\n%1%2").arg(currentIndent).arg(output));
211         }
212         break;
213     case tComment:
214         {
215             QString output("<!--%1-->");
216             addRaw(output.arg(o.xo_text));
217         }
218         break;
219     case tCDATA:
220         {
221             QString output("<![CDATA[\n%1\n]]>");
222             addRaw(output.arg(o.xo_text));
223         }
224         break;
225     }
226     return *this;
227 }
228 
229 
230 // Output functions ----------------------------------------------------------
newTag(const QString & tag)231 void XmlOutput::newTag(const QString &tag)
232 {
233     Q_ASSERT_X(tag.count(), "XmlOutput", "Cannot open an empty tag");
234     newTagOpen(tag);
235     closeOpen();
236 }
237 
newTagOpen(const QString & tag)238 void XmlOutput::newTagOpen(const QString &tag)
239 {
240     Q_ASSERT_X(tag.count(), "XmlOutput", "Cannot open an empty tag");
241     closeOpen();
242 
243     if (format == NewLine)
244         xmlFile << Qt::endl << currentIndent;
245     xmlFile << '<' << doConversion(tag);
246     currentState = Attribute;
247     tagStack.append(tag);
248     increaseIndent(); // ---> indent
249 }
250 
closeOpen()251 void XmlOutput::closeOpen()
252 {
253     switch(currentState) {
254         case Bare:
255         case Tag:
256             return;
257         case Attribute:
258             break;
259     }
260     xmlFile << '>';
261     currentState = Tag;
262 }
263 
closeTag()264 void XmlOutput::closeTag()
265 {
266     switch(currentState) {
267         case Bare:
268             if (tagStack.count())
269                 //warn_msg(WarnLogic, "<Root>: Cannot close tag in Bare state, %d tags on stack", tagStack.count());
270                 qDebug("<Root>: Cannot close tag in Bare state, %d tags on stack", tagStack.count());
271             else
272                 //warn_msg(WarnLogic, "<Root>: Cannot close tag, no tags on stack");
273                 qDebug("<Root>: Cannot close tag, no tags on stack");
274             return;
275         case Tag:
276             decreaseIndent(); // <--- Pre-decrease indent
277             if (format == NewLine)
278                 xmlFile << Qt::endl << currentIndent;
279             xmlFile << "</" << doConversion(tagStack.last()) << '>';
280             tagStack.pop_back();
281             break;
282         case Attribute:
283             xmlFile << " />";
284             tagStack.pop_back();
285             currentState = Tag;
286             decreaseIndent(); // <--- Post-decrease indent
287             break;
288     }
289 }
290 
closeTo(const QString & tag)291 void XmlOutput::closeTo(const QString &tag)
292 {
293     bool cont = true;
294     if (!tagStack.contains(tag) && !tag.isNull()) {
295         //warn_msg(WarnLogic, "<%s>: Cannot close to tag <%s>, not on stack", tagStack.last().latin1(), tag.latin1());
296         qDebug("<%s>: Cannot close to tag <%s>, not on stack", tagStack.last().toLatin1().constData(), tag.toLatin1().constData());
297         return;
298     }
299     int left = tagStack.count();
300     while (left-- && cont) {
301         cont = tagStack.last().compare(tag) != 0;
302         closeTag();
303     }
304 }
305 
closeAll()306 void XmlOutput::closeAll()
307 {
308     if (!tagStack.count())
309         return;
310     closeTo(QString());
311 }
312 
addDeclaration(const QString & version,const QString & encoding)313 void XmlOutput::addDeclaration(const QString &version, const QString &encoding)
314 {
315     switch(currentState) {
316         case Bare:
317             break;
318         case Tag:
319         case Attribute:
320             //warn_msg(WarnLogic, "<%s>: Cannot add declaration when not in bare state", tagStack.last().toLatin1().constData());
321             qDebug("<%s>: Cannot add declaration when not in bare state", tagStack.last().toLatin1().constData());
322             return;
323     }
324     QString outData = QString("<?xml version=\"%1\" encoding=\"%2\"?>")
325                               .arg(doConversion(version))
326                               .arg(doConversion(encoding));
327     addRaw(outData);
328 }
329 
addRaw(const QString & rawText)330 void XmlOutput::addRaw(const QString &rawText)
331 {
332     closeOpen();
333     xmlFile << rawText;
334 }
335 
addAttribute(const QString & attribute,const QString & value)336 void XmlOutput::addAttribute(const QString &attribute, const QString &value)
337 {
338      switch(currentState) {
339         case Bare:
340         case Tag:
341             //warn_msg(WarnLogic, "<%s>: Cannot add attribute since tags not open", tagStack.last().toLatin1().constData());
342             qDebug("<%s>: Cannot add attribute (%s) since tag's not open",
343                    (tagStack.count() ? tagStack.last().toLatin1().constData() : "Root"),
344                    attribute.toLatin1().constData());
345             return;
346         case Attribute:
347             break;
348     }
349     if (format == NewLine)
350         xmlFile << Qt::endl;
351     xmlFile << currentIndent << doConversion(attribute) << "=\"" << doConversion(value) << "\"";
352 }
353 
addAttributeTag(const QString & attribute,const QString & value)354 void XmlOutput::addAttributeTag(const QString &attribute, const QString &value)
355 {
356      switch(currentState) {
357         case Bare:
358         case Tag:
359             //warn_msg(WarnLogic, "<%s>: Cannot add attribute since tags not open", tagStack.last().toLatin1().constData());
360             qDebug("<%s>: Cannot add attribute (%s) since tag's not open",
361                    (tagStack.count() ? tagStack.last().toLatin1().constData() : "Root"),
362                    attribute.toLatin1().constData());
363             return;
364         case Attribute:
365             break;
366     }
367     xmlFile << " " << doConversion(attribute) << "=\"" << doConversion(value) << "\"";
368 }
369 
370 QT_END_NAMESPACE
371