1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the test suite of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include <QCoreApplication>
50 #include "XMLWriter.h"
53 * - Switch to Qt's d-pointer semantics, if in Qt.
54 * - Remove namespace(PatternistSDK), and change name, if in Qt.
55 * - Is it really necessary to pass the tag name to endElement()?
56 * - Could it be of interest to let the user control the encoding? Are those cases common
57 * enough to justify support in Qt? Using anything but UTF-8 or UTF-16
58 * means asking for trouble, from an interoperability perspective.
61 /* Design rationalis, comments:
63 * - The class is called XMLWriter to harvest familarity by being consistent with
64 * Java's XMLWriter class. If XMLWriter is moved to Qt, the name QXmlWriter is perhaps suitable.
65 * - The class does not handle indentation because the "do one thing well"-principle is
66 * in use. XMLWriter should be fast and not assume a certain idea of indentation. Indentation
67 * should be implemented in a standalone QXmlContentHandler that performs the indentation and
68 * "has a" QXmlContentHandler which it in addition calls, and by that proxying/piping another
69 * QXmlContentHandler(which most likely is an XMLWriter). Thus, achieving a modularized,
70 * flexibly approach to indentation. A reason is also that indentation is very subjective.
71 * The indenter class should probably be called XMLIndenter/QXmlIndenter.
72 * - It could be of interest to implement QXmlDTDHandler such that it would be possible to serialize
73 * DTDs. Must be done before BC becomes significant.
74 * - I think the most valuable of this class is its Q_ASSERT tests. Many programmers have severe problems
75 * producing XML, and the tests helps them catching their mistakes. They therefore promote
76 * interoperability. Do not remove them. If any are wrong, fix them instead.
79 using namespace QPatternistSDK;
82 * A namespace binding, prefix/namespace URI.
84 typedef QPair<QString, QString> NSBinding;
85 typedef QList<NSBinding> NSBindingList;
88 # define DEBUG_CODE(code)
90 # define DEBUG_CODE(code) code
93 class XMLWriter::Private
96 inline Private(QIODevice *devP) : insideCDATA(false),
97 addModificationNote(false),
100 hasContentStack.push(true);
104 inline void validateQName(const QString &) const
108 inline void verifyNS(const QString &) const
113 * Simple test of that @p name is an acceptable QName.
115 inline void validateQName(const QString &name)
117 Q_ASSERT_X(!name.isEmpty(), Q_FUNC_INFO,
118 "An XML name cannot be empty.");
119 Q_ASSERT_X(!name.endsWith(QLatin1Char(':')), Q_FUNC_INFO,
120 "An XML name cannot end with a colon(QLatin1Char(':')).");
121 Q_ASSERT_X(!name.contains(QRegExp(QLatin1String("[ \t\n]"))), Q_FUNC_INFO,
122 "An XML name cannot contain whitespace.");
126 * Ensures that the prefix of @p qName is declared.
128 inline void verifyNS(const QString &qName) const
130 const QString prefix(qName.left(qName.indexOf(QLatin1Char(':'))));
132 if(qName.contains(QLatin1Char(':')) && prefix != QLatin1String("xml"))
134 bool foundPrefix = false;
135 const QStack<NSBindingList>::const_iterator end(namespaceTracker.constEnd());
136 QStack<NSBindingList>::const_iterator it(namespaceTracker.constBegin());
138 for(; it != end; ++it)
140 const NSBindingList::const_iterator lend((*it).constEnd());
141 NSBindingList::const_iterator lit((*it).constBegin());
143 for(; lit != lend; ++it)
145 if((*lit).first == prefix)
155 Q_ASSERT_X(foundPrefix, "XMLWriter::startElement()",
156 qPrintable(QString::fromLatin1("The prefix %1 is not declared. All prefixes "
157 "except 'xml' must be declared.").arg(prefix)));
162 inline QString escapeElementContent(const QString &ch)
164 const int l = ch.length();
167 for(int i = 0; i != l; ++i)
169 const QChar c(ch.at(i));
171 if(c == QLatin1Char(QLatin1Char('&')))
172 retval += QLatin1String("&");
173 else if(c == QLatin1Char(QLatin1Char('<')))
174 retval += QLatin1String("<");
182 inline QString escapeAttributeContent(const QString &ch)
184 const int l = ch.length();
187 for(int i = 0; i != l; ++i)
189 const QChar c(ch.at(i));
191 /* We don't have to escape '\'' because we use '\"' as attribute delimiter. */
192 if(c == QLatin1Char('&'))
193 retval += QLatin1String("&");
194 else if(c == QLatin1Char('<'))
195 retval += QLatin1String("<");
196 else if(c == QLatin1Char('"'))
197 retval += QLatin1String(""");
205 inline QString escapeCDATAContent(const QString &ch)
207 const int l = ch.length();
211 for(int i = 0; i != l; ++i)
213 const QChar c(ch.at(i));
215 /* Escape '>' if in "]]>" */
216 if(c == QLatin1Char(']'))
218 if(atEnd == 0 || atEnd == 1)
223 retval += QLatin1Char(']');
225 else if(c == QLatin1Char('>'))
228 retval += QLatin1String(">");
232 retval += QLatin1Char('>');
243 * We wrap dev in this function such that we can deploy the Q_ASSERT_X
244 * macro in each place it's used.
246 inline QIODevice *device() const
248 Q_ASSERT_X(dev, Q_FUNC_INFO,
249 "No device specified for XMLWriter; one must be specified with "
250 "setDevice() or via the constructor before XMLWriter can be used.");
255 * @returns true on success, otherwise false
257 inline bool serialize(const QString &data)
259 const QByteArray utf8(data.toUtf8());
261 return device()->write(utf8) == utf8.size();
265 * @returns true on success, otherwise false
267 inline bool serialize(const char data)
269 return device()->putChar(data);
273 * @returns true on success, otherwise false
275 inline bool serialize(const char *data)
277 return device()->write(data) == qstrlen(data);
280 inline bool hasElementContent() const
282 return hasContentStack.top();
285 inline void handleElement()
287 if(!hasElementContent())
290 /* This element is content for the parent. */
291 hasContentStack.top() = true;
294 NSBindingList namespaces;
296 bool addModificationNote;
299 QStack<bool> hasContentStack;
301 DEBUG_CODE(QStack<QString> tags;)
302 DEBUG_CODE(QStack<NSBindingList> namespaceTracker;)
306 * Reduces complexity. The empty else clause is for avoiding mess when macro
307 * is used in the 'then' branch of an if clause, which is followed by an else clause.
309 #define serialize(string) if(!d->serialize(string)) \
311 d->errorString = d->device()->errorString(); \
314 else do {} while (false)
316 XMLWriter::XMLWriter(QIODevice *outStream) : d(new Private(outStream))
320 XMLWriter::~XMLWriter()
325 bool XMLWriter::startDocument()
327 if(!device()->isOpen() && !device()->open(QIODevice::WriteOnly))
330 if(d->addModificationNote)
334 d->msg = QString::fromLatin1("NOTE: This file was automatically generated "
335 "by %1 at %2. All changes to this file will be lost.")
336 .arg(QCoreApplication::instance()->applicationName(),
337 QDateTime::currentDateTime().toString());
345 serialize(QLatin1String("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"));
350 bool XMLWriter::startElement(const QString &/*namespaceURI*/,
351 const QString &/*localName*/,
352 const QString &qName,
353 const QXmlAttributes &atts)
355 return startElement(qName, atts);
358 bool XMLWriter::startElement(const QString &qName,
359 const QXmlAttributes &atts)
361 Q_ASSERT_X(!d->insideCDATA, Q_FUNC_INFO,
362 "Only characters() can be received when inside CDATA.");
363 Q_ASSERT_X(!qName.startsWith(QLatin1String("xmlns")), Q_FUNC_INFO,
364 "startElement should not be used for declaring prefixes, "
365 "use startPrefixMapping() for that.");
367 d->validateQName(qName);
375 DEBUG_CODE(d->tags.push(qName));
376 DEBUG_CODE(d->namespaceTracker.push(d->namespaces));
378 /* Add namespace declarations. */
379 const NSBindingList::const_iterator end(d->namespaces.constEnd());
380 NSBindingList::const_iterator it(d->namespaces.constBegin());
382 for(; it != end; ++it)
384 if((*it).first.isEmpty())
385 serialize(" xmlns=");
388 serialize(" xmlns:");
389 serialize((*it).first);
394 serialize(d->escapeElementContent((*it).second));
397 d->namespaces.clear();
399 const int c = atts.count();
401 /* Serialize attributes. */
402 for(int i = 0; i != c; ++i)
404 d->validateQName(atts.qName(i));
405 d->verifyNS(atts.qName(i));
408 serialize(atts.qName(i));
410 serialize(d->escapeAttributeContent(atts.value(i)));
414 d->hasContentStack.push(false);
418 bool XMLWriter::endElement(const QString &/*namespaceURI*/,
419 const QString &/*localName*/,
420 const QString &qName)
422 return endElement(qName);
425 bool XMLWriter::endElement(const QString &qName)
427 Q_ASSERT_X(!d->insideCDATA, Q_FUNC_INFO,
428 "Only characters() can be received when inside CDATA.");
429 Q_ASSERT_X(d->tags.pop() == qName, Q_FUNC_INFO,
430 "The element tags are not balanced, the produced XML is invalid.");
432 DEBUG_CODE(d->namespaceTracker.pop());
434 /* "this" element is content for our parent, so ensure hasElementContent is true. */
436 if(d->hasElementContent())
438 serialize(QLatin1String("</"));
443 serialize(QLatin1String("/>"));
445 d->hasContentStack.pop();
450 bool XMLWriter::startPrefixMapping(const QString &prefix, const QString &uri)
452 Q_ASSERT_X(!d->insideCDATA, Q_FUNC_INFO,
453 "Only characters() can be received when inside CDATA.");
454 Q_ASSERT_X(prefix.toLower() != QLatin1String("xml") ||
455 (prefix.toLower() == QLatin1String("xml") &&
456 (uri == QLatin1String("http://www.w3.org/TR/REC-xml-names/") ||
459 "The prefix 'xml' can only be bound to the namespace "
460 "\"http://www.w3.org/TR/REC-xml-names/\".");
461 Q_ASSERT_X(prefix.toLower() != QLatin1String("xml") &&
462 uri != QLatin1String("http://www.w3.org/TR/REC-xml-names/"),
464 "The namespace \"http://www.w3.org/TR/REC-xml-names/\" can only be bound to the "
467 d->namespaces.append(qMakePair(prefix, uri));
471 bool XMLWriter::processingInstruction(const QString &target,
474 Q_ASSERT_X(target.toLower() != QLatin1String("xml"), Q_FUNC_INFO,
475 "A processing instruction cannot have the name xml in any "
476 "capitalization, because it is reserved.");
477 Q_ASSERT_X(!data.contains(QLatin1String("?>")), Q_FUNC_INFO,
478 "The content of a processing instruction cannot contain the string \"?>\".");
479 Q_ASSERT_X(!d->insideCDATA, "XMLWriter::processingInstruction()",
480 "Only characters() can be received when inside CDATA.");
484 serialize(QLatin1String("<?"));
488 serialize(QLatin1String("?>"));
492 bool XMLWriter::characters(const QString &ch)
494 Q_ASSERT_X(d->tags.count() >= 1, Q_FUNC_INFO,
495 "Text nodes can only appear inside elements(no elements sent).");
499 serialize(d->escapeCDATAContent(ch));
501 serialize(d->escapeElementContent(ch));
506 bool XMLWriter::comment(const QString &ch)
508 Q_ASSERT_X(!d->insideCDATA, Q_FUNC_INFO,
509 "Only characters() can be received when inside CDATA.");
510 Q_ASSERT_X(!ch.contains(QLatin1String("--")), Q_FUNC_INFO,
511 "XML comments may not contain double-hyphens(\"--\").");
512 Q_ASSERT_X(!ch.endsWith(QLatin1Char('-')), Q_FUNC_INFO,
513 "XML comments cannot end with a hyphen, \"-\"(add a space, for example).");
514 /* A comment starting with "<!---" is ok. */
518 serialize(QLatin1String("<!--"));
520 serialize(QLatin1String("-->"));
525 bool XMLWriter::startCDATA()
527 Q_ASSERT_X(d->insideCDATA, Q_FUNC_INFO,
528 "startCDATA() has already been called.");
529 Q_ASSERT_X(d->tags.count() >= 1, Q_FUNC_INFO,
530 "CDATA sections can only appear inside elements(no elements sent).");
531 d->insideCDATA = true;
532 serialize(QLatin1String("<![CDATA["));
536 bool XMLWriter::endCDATA()
538 d->insideCDATA = false;
543 bool XMLWriter::startDTD(const QString &name,
544 const QString &publicId,
545 const QString &systemId)
547 Q_ASSERT_X(!d->insideCDATA, Q_FUNC_INFO,
548 "Only characters() can be received when inside CDATA.");
549 Q_ASSERT_X(!name.isEmpty(), Q_FUNC_INFO,
550 "The DOCTYPE name cannot be empty.");
551 Q_ASSERT_X(d->tags.isEmpty() && d->namespaces.isEmpty(), Q_FUNC_INFO,
552 "No content such as namespace declarations or elements can be serialized "
553 "before the DOCTYPE declaration, the XML is invalid.");
554 Q_ASSERT_X(!publicId.contains(QLatin1Char('"')), Q_FUNC_INFO,
555 "The PUBLIC ID cannot contain quotes('\"').");
556 Q_ASSERT_X(!systemId.contains(QLatin1Char('"')), Q_FUNC_INFO,
557 "The SYSTEM ID cannot contain quotes('\"').");
559 serialize(QLatin1String("<!DOCTYPE "));
562 if(!publicId.isEmpty())
564 Q_ASSERT_X(!systemId.isEmpty(), Q_FUNC_INFO,
565 "When a public identifier is specified, a system identifier "
566 "must also be specified in order to produce valid XML.");
567 serialize(" PUBLIC \"");
572 if(!systemId.isEmpty())
574 if(publicId.isEmpty())
575 serialize(" SYSTEM");
585 bool XMLWriter::endDTD()
587 Q_ASSERT_X(d->tags.isEmpty() && d->namespaces.isEmpty(), Q_FUNC_INFO,
588 "Content such as namespace declarations or elements cannot occur inside "
589 "the DOCTYPE declaration, the XML is invalid.");
590 serialize(QLatin1String(">\n"));
594 bool XMLWriter::startEntity(const QString &)
599 bool XMLWriter::endEntity(const QString &)
604 void XMLWriter::setMessage(const QString &msg)
609 QString XMLWriter::modificationMessage() const
614 bool XMLWriter::endDocument()
616 Q_ASSERT_X(d->tags.isEmpty(), Q_FUNC_INFO,
617 "endDocument() called before all elements were closed with endElement().");
618 d->device()->close();
622 QString XMLWriter::errorString() const
624 return d->errorString;
627 bool XMLWriter::ignorableWhitespace(const QString &ch)
629 return characters(ch);
632 bool XMLWriter::endPrefixMapping(const QString &)
634 /* Again, should we do something with this? */
638 bool XMLWriter::skippedEntity(const QString &)
643 void XMLWriter::setDocumentLocator(QXmlLocator *)
647 QIODevice *XMLWriter::device() const
652 void XMLWriter::setDevice(QIODevice *dev)
657 void XMLWriter::setAddMessage(const bool toggle)
659 d->addModificationNote = toggle;
662 bool XMLWriter::addModificationMessage() const
664 return d->addModificationNote;
668 // vim: et:ts=4:sw=4:sts=4