1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtXml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <QtXml/qtxmlglobal.h>
41
42#ifndef QT_NO_DOM
43
44#include "qdomhelpers_p.h"
45#include "qdom_p.h"
46#include "qxmlstream.h"
47
48#include <stack>
49
50QT_BEGIN_NAMESPACE
51
52/**************************************************************
53 *
54 * QXmlDocumentLocators
55 *
56 **************************************************************/
57
58int QDomDocumentLocator::column() const
59{
60 Q_ASSERT(reader);
61 return static_cast<int>(reader->columnNumber());
62}
63
64int QDomDocumentLocator::line() const
65{
66 Q_ASSERT(reader);
67 return static_cast<int>(reader->lineNumber());
68}
69
70/**************************************************************
71 *
72 * QDomBuilder
73 *
74 **************************************************************/
75
76QDomBuilder::QDomBuilder(QDomDocumentPrivate *d, QXmlDocumentLocator *l, bool namespaceProcessing)
77 : errorLine(0),
78 errorColumn(0),
79 doc(d),
80 node(d),
81 locator(l),
82 nsProcessing(namespaceProcessing)
83{
84}
85
86QDomBuilder::~QDomBuilder() {}
87
88bool QDomBuilder::endDocument()
89{
90 // ### is this really necessary? (rms)
91 if (node != doc)
92 return false;
93 return true;
94}
95
96bool QDomBuilder::startDTD(const QString &name, const QString &publicId, const QString &systemId)
97{
98 doc->doctype()->name = name;
99 doc->doctype()->publicId = publicId;
100 doc->doctype()->systemId = systemId;
101 return true;
102}
103
104bool QDomBuilder::startElement(const QString &nsURI, const QString &qName,
105 const QXmlStreamAttributes &atts)
106{
107 QDomNodePrivate *n =
108 nsProcessing ? doc->createElementNS(nsURI, qName) : doc->createElement(qName);
109 if (!n)
110 return false;
111
112 n->setLocation(locator->line(), locator->column());
113
114 node->appendChild(n);
115 node = n;
116
117 // attributes
118 for (const auto &attr : atts) {
119 auto domElement = static_cast<QDomElementPrivate *>(node);
120 if (nsProcessing) {
121 domElement->setAttributeNS(attr.namespaceUri().toString(),
122 attr.qualifiedName().toString(),
123 attr.value().toString());
124 } else {
125 domElement->setAttribute(attr.qualifiedName().toString(),
126 attr.value().toString());
127 }
128 }
129
130 return true;
131}
132
133bool QDomBuilder::endElement()
134{
135 if (!node || node == doc)
136 return false;
137 node = node->parent();
138
139 return true;
140}
141
142bool QDomBuilder::characters(const QString &characters, bool cdata)
143{
144 // No text as child of some document
145 if (node == doc)
146 return false;
147
148 QScopedPointer<QDomNodePrivate> n;
149 if (cdata) {
150 n.reset(doc->createCDATASection(characters));
151 } else if (!entityName.isEmpty()) {
152 QScopedPointer<QDomEntityPrivate> e(
153 new QDomEntityPrivate(doc, nullptr, entityName, QString(), QString(), QString()));
154 e->value = characters;
155 e->ref.deref();
156 doc->doctype()->appendChild(e.data());
157 e.take();
158 n.reset(doc->createEntityReference(entityName));
159 } else {
160 n.reset(doc->createTextNode(characters));
161 }
162 n->setLocation(locator->line(), locator->column());
163 node->appendChild(n.data());
164 n.take();
165
166 return true;
167}
168
169bool QDomBuilder::processingInstruction(const QString &target, const QString &data)
170{
171 QDomNodePrivate *n;
172 n = doc->createProcessingInstruction(target, data);
173 if (n) {
174 n->setLocation(locator->line(), locator->column());
175 node->appendChild(n);
176 return true;
177 } else
178 return false;
179}
180
181bool QDomBuilder::skippedEntity(const QString &name)
182{
183 QDomNodePrivate *n = doc->createEntityReference(name);
184 n->setLocation(locator->line(), locator->column());
185 node->appendChild(n);
186 return true;
187}
188
189void QDomBuilder::fatalError(const QString &message)
190{
191 errorMsg = message;
192 errorLine = static_cast<int>(locator->line());
193 errorColumn = static_cast<int>(locator->column());
194}
195
196QDomBuilder::ErrorInfo QDomBuilder::error() const
197{
198 return ErrorInfo(errorMsg, errorLine, errorColumn);
199}
200
201bool QDomBuilder::startEntity(const QString &name)
202{
203 entityName = name;
204 return true;
205}
206
207bool QDomBuilder::endEntity()
208{
209 entityName.clear();
210 return true;
211}
212
213bool QDomBuilder::comment(const QString &characters)
214{
215 QDomNodePrivate *n;
216 n = doc->createComment(characters);
217 n->setLocation(locator->line(), locator->column());
218 node->appendChild(n);
219 return true;
220}
221
222bool QDomBuilder::unparsedEntityDecl(const QString &name, const QString &publicId,
223 const QString &systemId, const QString &notationName)
224{
225 QDomEntityPrivate *e =
226 new QDomEntityPrivate(doc, nullptr, name, publicId, systemId, notationName);
227 // keep the refcount balanced: appendChild() does a ref anyway.
228 e->ref.deref();
229 doc->doctype()->appendChild(e);
230 return true;
231}
232
233bool QDomBuilder::externalEntityDecl(const QString &name, const QString &publicId,
234 const QString &systemId)
235{
236 return unparsedEntityDecl(name, publicId, systemId, QString());
237}
238
239bool QDomBuilder::notationDecl(const QString &name, const QString &publicId,
240 const QString &systemId)
241{
242 QDomNotationPrivate *n = new QDomNotationPrivate(doc, nullptr, name, publicId, systemId);
243 // keep the refcount balanced: appendChild() does a ref anyway.
244 n->ref.deref();
245 doc->doctype()->appendChild(n);
246 return true;
247}
248
249/**************************************************************
250 *
251 * QDomParser
252 *
253 **************************************************************/
254
255QDomParser::QDomParser(QDomDocumentPrivate *d, QXmlStreamReader *r, bool namespaceProcessing)
256 : reader(r), locator(r), domBuilder(d, &locator, namespaceProcessing)
257{
258}
259
260bool QDomParser::parse()
261{
262 return parseProlog() && parseBody();
263}
264
265QDomBuilder::ErrorInfo QDomParser::errorInfo() const
266{
267 return domBuilder.error();
268}
269
270bool QDomParser::parseProlog()
271{
272 Q_ASSERT(reader);
273
274 bool foundDtd = false;
275
276 while (!reader->atEnd()) {
277 reader->readNext();
278
279 if (reader->hasError()) {
280 domBuilder.fatalError(reader->errorString());
281 return false;
282 }
283
284 switch (reader->tokenType()) {
285 case QXmlStreamReader::StartDocument:
286 if (!reader->documentVersion().isEmpty()) {
287 QString value(QLatin1String("version='"));
288 value += reader->documentVersion();
289 value += QLatin1Char('\'');
290 if (!reader->documentEncoding().isEmpty()) {
291 value += QLatin1String(" encoding='");
292 value += reader->documentEncoding();
293 value += QLatin1Char('\'');
294 }
295 if (reader->isStandaloneDocument()) {
296 value += QLatin1String(" standalone='yes'");
297 } else {
298 // TODO: Add standalone='no', if 'standalone' is specified. With the current
299 // QXmlStreamReader there is no way to figure out if it was specified or not.
300 // QXmlStreamReader needs to be modified for handling that case correctly.
301 }
302
303 if (!domBuilder.processingInstruction(QLatin1String("xml"), value)) {
304 domBuilder.fatalError(
305 QDomParser::tr("Error occurred while processing XML declaration"));
306 return false;
307 }
308 }
309 break;
310 case QXmlStreamReader::DTD:
311 if (foundDtd) {
312 domBuilder.fatalError(QDomParser::tr("Multiple DTD sections are not allowed"));
313 return false;
314 }
315 foundDtd = true;
316
317 if (!domBuilder.startDTD(reader->dtdName().toString(),
318 reader->dtdPublicId().toString(),
319 reader->dtdSystemId().toString())) {
320 domBuilder.fatalError(
321 QDomParser::tr("Error occurred while processing document type declaration"));
322 return false;
323 }
324 if (!parseMarkupDecl())
325 return false;
326 break;
327 case QXmlStreamReader::Comment:
328 if (!domBuilder.comment(reader->text().toString())) {
329 domBuilder.fatalError(QDomParser::tr("Error occurred while processing comment"));
330 return false;
331 }
332 break;
333 case QXmlStreamReader::ProcessingInstruction:
334 if (!domBuilder.processingInstruction(reader->processingInstructionTarget().toString(),
335 reader->processingInstructionData().toString())) {
336 domBuilder.fatalError(
337 QDomParser::tr("Error occurred while processing a processing instruction"));
338 return false;
339 }
340 break;
341 default:
342 // If the token is none of the above, prolog processing is done.
343 return true;
344 }
345 }
346
347 return true;
348}
349
350bool QDomParser::parseBody()
351{
352 Q_ASSERT(reader);
353
354 std::stack<QString> tagStack;
355 while (!reader->atEnd() && !reader->hasError()) {
356 switch (reader->tokenType()) {
357 case QXmlStreamReader::StartElement:
358 tagStack.push(reader->qualifiedName().toString());
359 if (!domBuilder.startElement(reader->namespaceUri().toString(),
360 reader->qualifiedName().toString(),
361 reader->attributes())) {
362 domBuilder.fatalError(
363 QDomParser::tr("Error occurred while processing a start element"));
364 return false;
365 }
366 break;
367 case QXmlStreamReader::EndElement:
368 if (tagStack.empty() || reader->qualifiedName() != tagStack.top()) {
369 domBuilder.fatalError(
370 QDomParser::tr("Unexpected end element '%1'").arg(reader->name()));
371 return false;
372 }
373 tagStack.pop();
374 if (!domBuilder.endElement()) {
375 domBuilder.fatalError(
376 QDomParser::tr("Error occurred while processing an end element"));
377 return false;
378 }
379 break;
380 case QXmlStreamReader::Characters:
381 if (!reader->isWhitespace()) { // Skip the content consisting of only whitespaces
382 if (!reader->text().toString().trimmed().isEmpty()) {
383 if (!domBuilder.characters(reader->text().toString(), reader->isCDATA())) {
384 domBuilder.fatalError(QDomParser::tr(
385 "Error occurred while processing the element content"));
386 return false;
387 }
388 }
389 }
390 break;
391 case QXmlStreamReader::Comment:
392 if (!domBuilder.comment(reader->text().toString())) {
393 domBuilder.fatalError(QDomParser::tr("Error occurred while processing comments"));
394 return false;
395 }
396 break;
397 case QXmlStreamReader::ProcessingInstruction:
398 if (!domBuilder.processingInstruction(reader->processingInstructionTarget().toString(),
399 reader->processingInstructionData().toString())) {
400 domBuilder.fatalError(
401 QDomParser::tr("Error occurred while processing a processing instruction"));
402 return false;
403 }
404 break;
405 case QXmlStreamReader::EntityReference:
406 if (!domBuilder.skippedEntity(reader->name().toString())) {
407 domBuilder.fatalError(
408 QDomParser::tr("Error occurred while processing an entity reference"));
409 return false;
410 }
411 break;
412 default:
413 domBuilder.fatalError(QDomParser::tr("Unexpected token"));
414 return false;
415 }
416
417 reader->readNext();
418 }
419
420 if (reader->hasError()) {
421 domBuilder.fatalError(reader->errorString());
422 reader->readNext();
423 return false;
424 }
425
426 if (!tagStack.empty()) {
427 domBuilder.fatalError(QDomParser::tr("Tag mismatch"));
428 return false;
429 }
430
431 return true;
432}
433
434bool QDomParser::parseMarkupDecl()
435{
436 Q_ASSERT(reader);
437
438 const auto entities = reader->entityDeclarations();
439 for (const auto &entityDecl : entities) {
440 // Entity declarations are created only for Extrenal Entities. Internal Entities
441 // are parsed, and QXmlStreamReader handles the parsing itself and returns the
442 // parsed result. So we don't need to do anything for the Internal Entities.
443 if (!entityDecl.publicId().isEmpty() || !entityDecl.systemId().isEmpty()) {
444 // External Entity
445 if (!domBuilder.unparsedEntityDecl(entityDecl.name().toString(),
446 entityDecl.publicId().toString(),
447 entityDecl.systemId().toString(),
448 entityDecl.notationName().toString())) {
449 domBuilder.fatalError(
450 QDomParser::tr("Error occurred while processing entity declaration"));
451 return false;
452 }
453 }
454 }
455
456 const auto notations = reader->notationDeclarations();
457 for (const auto &notationDecl : notations) {
458 if (!domBuilder.notationDecl(notationDecl.name().toString(),
459 notationDecl.publicId().toString(),
460 notationDecl.systemId().toString())) {
461 domBuilder.fatalError(
462 QDomParser::tr("Error occurred while processing notation declaration"));
463 return false;
464 }
465 }
466
467 return true;
468}
469
470QT_END_NAMESPACE
471
472#endif // QT_NO_DOM
473