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 | |
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 | |
40 | XmlOutput::~XmlOutput() |
41 | { |
42 | closeAll(); |
43 | } |
44 | |
45 | // Settings ------------------------------------------------------------------ |
46 | void XmlOutput::setIndentString(const QString &indentString) |
47 | { |
48 | indent = indentString; |
49 | } |
50 | |
51 | QString XmlOutput::indentString() |
52 | { |
53 | return indent; |
54 | } |
55 | |
56 | void XmlOutput::setIndentLevel(int level) |
57 | { |
58 | currentLevel = level; |
59 | } |
60 | |
61 | int XmlOutput::indentLevel() |
62 | { |
63 | return currentLevel; |
64 | } |
65 | |
66 | void XmlOutput::setState(XMLState state) |
67 | { |
68 | currentState = state; |
69 | } |
70 | |
71 | void XmlOutput::setFormat(XMLFormat newFormat) |
72 | { |
73 | format = newFormat; |
74 | } |
75 | |
76 | XmlOutput::XMLState XmlOutput::state() |
77 | { |
78 | return currentState; |
79 | } |
80 | |
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 | |
89 | void XmlOutput::increaseIndent() |
90 | { |
91 | ++currentLevel; |
92 | updateIndent(); |
93 | } |
94 | |
95 | void XmlOutput::decreaseIndent() |
96 | { |
97 | if (currentLevel) |
98 | --currentLevel; |
99 | updateIndent(); |
100 | if (!currentLevel) |
101 | currentState = Bare; |
102 | } |
103 | |
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 ---------------------------------------------------------- |
153 | XmlOutput& XmlOutput::operator<<(const QString& o) |
154 | { |
155 | return operator<<(data(o)); |
156 | } |
157 | |
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 ---------------------------------------------------------- |
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 | |
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 | |
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 | |
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" , int(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 | |
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 | |
306 | void XmlOutput::closeAll() |
307 | { |
308 | if (!tagStack.count()) |
309 | return; |
310 | closeTo(QString()); |
311 | } |
312 | |
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 | |
330 | void XmlOutput::addRaw(const QString &rawText) |
331 | { |
332 | closeOpen(); |
333 | xmlFile << rawText; |
334 | } |
335 | |
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 | |
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 | |