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

source code of qtbase/qmake/generators/xmloutput.cpp