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("&");
125 }
126 } else if (c == QLatin1Char('<')) {
127 output += QLatin1String("<");
128 } else if (c == QLatin1Char('>')) {
129 output += QLatin1String(">");
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("""));
144 output.replace('\'', QLatin1String("'"));
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