1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
7 ** This file is part of the QtXmlPatterns module 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 "qdynamiccontext_p.h"
43 #include "qpatternistlocale_p.h"
45 #include "qxmlquery_p.h"
46 #include "qxmlserializer_p.h"
47 #include "qxmlserializer.h"
51 using namespace QPatternist;
53 QXmlSerializerPrivate::QXmlSerializerPrivate(const QXmlQuery &query,
54 QIODevice *outputDevice)
55 : isPreviousAtomic(false),
56 state(QXmlSerializer::BeforeDocumentElement),
57 np(query.namePool().d),
59 codec(QTextCodec::codecForMib(106)), /* UTF-8 */
62 hasClosedElement.reserve(EstimatedTreeDepth);
63 namespaces.reserve(EstimatedTreeDepth);
64 nameCache.reserve(EstimatedNameCount);
66 hasClosedElement.push(qMakePair(QXmlName(), true));
69 We push the empty namespace such that first of all
70 namespaceBinding() won't assert on an empty QStack,
71 and such that the empty namespace is in-scope and
72 that the code doesn't attempt to declare it.
74 We push the XML namespace. Although we won't receive
75 declarations for it, we may output attributes by that
78 QVector<QXmlName> defNss;
80 defNss[0] = QXmlName(StandardNamespaces::empty,
81 StandardLocalNames::empty,
82 StandardPrefixes::empty);
83 defNss[1] = QXmlName(StandardNamespaces::xml,
84 StandardLocalNames::empty,
85 StandardPrefixes::xml);
87 namespaces.push(defNss);
89 /* If we don't set this flag, QTextCodec will generate a BOM. */
90 converterState.flags = QTextCodec::IgnoreHeader;
95 \brief The QXmlSerializer class is an implementation of QAbstractXmlReceiver for transforming XQuery output into unformatted XML.
101 QXmlSerializer translates an \l {XQuery Sequence} {XQuery sequence}, usually
102 the output of an QXmlQuery, into XML. Consider the example:
104 \snippet doc/src/snippets/code/src_xmlpatterns_api_qxmlserializer.cpp 0
106 First it constructs a \l {QXmlQuery} {query} that gets the
107 first paragraph from document \c index.html. Then it constructs
108 an instance of this class with the \l {QXmlQuery} {query} and
109 \l {QIODevice} {myOutputDevice}. Finally, it
110 \l {QXmlQuery::evaluateTo()} {evaluates} the
111 \l {QXmlQuery} {query}, producing an ordered sequence of calls
112 to the serializer's callback functions. The sequence of callbacks
113 transforms the query output to XML and writes it to
114 \l {QIODevice} {myOutputDevice}.
119 \o Declare namespaces when needed,
121 \o Use appropriate escaping, when characters can't be
122 represented in the XML,
124 \o Handle line endings appropriately,
126 \o Report errors, when it can't serialize the content, e.g.,
127 when asked to serialize an attribute that is a top-level node,
128 or when more than one top-level element is encountered.
132 If an error occurs during serialization, result is undefined
133 unless the serializer is driven through a call to
134 QXmlQuery::evaluateTo().
136 If the generated XML should be indented and formatted for reading,
139 \sa {http://www.w3.org/TR/xslt-xquery-serialization/}{XSLT 2.0 and XQuery 1.0 Serialization}
145 Constructs a serializer that uses the name pool and message
146 handler in \a query, and writes the output to \a outputDevice.
148 \a outputDevice must be a valid, non-null device that is open in
149 write mode, otherwise behavior is undefined.
151 \a outputDevice must not be opened with QIODevice::Text because it
152 will cause the output to be incorrect. This class will ensure line
153 endings are serialized as according with the XML specification.
154 QXmlSerializer does not take ownership of \a outputDevice.
156 QXmlSerializer::QXmlSerializer(const QXmlQuery &query,
157 QIODevice *outputDevice) : QAbstractXmlReceiver(new QXmlSerializerPrivate(query, outputDevice))
161 qWarning("outputDevice cannot be null.");
165 if(!outputDevice->isWritable())
167 qWarning("outputDevice must be opened in write mode.");
175 QXmlSerializer::QXmlSerializer(QAbstractXmlReceiverPrivate *d) : QAbstractXmlReceiver(d)
182 bool QXmlSerializer::atDocumentRoot() const
184 Q_D(const QXmlSerializer);
185 return d->state == BeforeDocumentElement ||
186 (d->state == InsideDocumentElement && d->hasClosedElement.size() == 1);
192 void QXmlSerializer::startContent()
195 if (!d->hasClosedElement.top().second) {
197 d->hasClosedElement.top().second = true;
204 void QXmlSerializer::writeEscaped(const QString &toEscape)
206 if(toEscape.isEmpty()) /* Early exit. */
210 result.reserve(int(toEscape.length() * 1.1));
211 const int length = toEscape.length();
213 for(int i = 0; i < length; ++i)
215 const QChar c(toEscape.at(i));
217 if(c == QLatin1Char('<'))
218 result += QLatin1String("<");
219 else if(c == QLatin1Char('>'))
220 result += QLatin1String(">");
221 else if(c == QLatin1Char('&'))
222 result += QLatin1String("&");
224 result += toEscape.at(i);
233 void QXmlSerializer::writeEscapedAttribute(const QString &toEscape)
235 if(toEscape.isEmpty()) /* Early exit. */
239 result.reserve(int(toEscape.length() * 1.1));
240 const int length = toEscape.length();
242 for(int i = 0; i < length; ++i)
244 const QChar c(toEscape.at(i));
246 if(c == QLatin1Char('<'))
247 result += QLatin1String("<");
248 else if(c == QLatin1Char('>'))
249 result += QLatin1String(">");
250 else if(c == QLatin1Char('&'))
251 result += QLatin1String("&");
252 else if(c == QLatin1Char('"'))
253 result += QLatin1String(""");
255 result += toEscape.at(i);
264 void QXmlSerializer::write(const QString &content)
267 d->device->write(d->codec->fromUnicode(content.constData(), content.length(), &d->converterState));
273 void QXmlSerializer::write(const QXmlName &name)
276 const QByteArray &cell = d->nameCache[name.code()];
280 QByteArray &mutableCell = d->nameCache[name.code()];
282 const QString content(d->np->toLexical(name));
283 mutableCell = d->codec->fromUnicode(content.constData(),
286 d->device->write(mutableCell);
289 d->device->write(cell);
295 void QXmlSerializer::write(const char *const chars)
298 d->device->write(chars);
304 void QXmlSerializer::startElement(const QXmlName &name)
308 Q_ASSERT(d->device->isWritable());
310 Q_ASSERT(!name.isNull());
312 d->namespaces.push(QVector<QXmlName>());
316 if(d->state == BeforeDocumentElement)
317 d->state = InsideDocumentElement;
318 else if(d->state != InsideDocumentElement)
320 d->query.d->staticContext()->error(QtXmlPatterns::tr(
321 "Element %1 can't be serialized because it appears outside "
322 "the document element.").arg(formatKeyword(d->np, name)),
323 ReportContext::SENR0001,
324 d->query.d->expression().data());
332 /* Ensure that the namespace URI used in the name gets outputted. */
333 namespaceBinding(name);
335 d->hasClosedElement.push(qMakePair(name, false));
336 d->isPreviousAtomic = false;
342 void QXmlSerializer::endElement()
345 const QPair<QXmlName, bool> e(d->hasClosedElement.pop());
357 d->isPreviousAtomic = false;
363 void QXmlSerializer::attribute(const QXmlName &name,
364 const QStringRef &value)
367 Q_ASSERT(!name.isNull());
369 /* Ensure that the namespace URI used in the name gets outputted. */
371 /* Since attributes doesn't pick up the default namespace, a
372 * namespace declaration would cause trouble if we output it. */
373 if(name.prefix() != StandardPrefixes::empty)
374 namespaceBinding(name);
380 d->query.d->staticContext()->error(QtXmlPatterns::tr(
381 "Attribute %1 can't be serialized because it appears at "
382 "the top level.").arg(formatKeyword(d->np, name)),
383 ReportContext::SENR0001,
384 d->query.d->expression().data());
391 writeEscapedAttribute(value.toString());
399 bool QXmlSerializer::isBindingInScope(const QXmlName nb) const
401 Q_D(const QXmlSerializer);
402 const int levelLen = d->namespaces.size();
404 if(nb.prefix() == StandardPrefixes::empty)
406 for(int lvl = levelLen - 1; lvl >= 0; --lvl)
408 const QVector<QXmlName> &scope = d->namespaces.at(lvl);
409 const int vectorLen = scope.size();
411 for(int s = vectorLen - 1; s >= 0; --s)
413 const QXmlName &nsb = scope.at(s);
415 if(nsb.prefix() == StandardPrefixes::empty)
416 return nsb.namespaceURI() == nb.namespaceURI();
422 for(int lvl = 0; lvl < levelLen; ++lvl)
424 const QVector<QXmlName> &scope = d->namespaces.at(lvl);
425 const int vectorLen = scope.size();
427 for(int s = 0; s < vectorLen; ++s)
429 const QXmlName &n = scope.at(s);
430 if (n.prefix() == nb.prefix() &&
431 n.namespaceURI() == nb.namespaceURI())
443 void QXmlSerializer::namespaceBinding(const QXmlName &nb)
448 * Namespace bindings aren't looked up in a cache, because
449 * we typically receive very few.
453 Q_ASSERT_X(!nb.isNull(), Q_FUNC_INFO,
454 "It makes no sense to pass a null QXmlName.");
456 Q_ASSERT_X((nb.namespaceURI() != StandardNamespaces::empty) ||
457 (nb.prefix() == StandardPrefixes::empty),
459 "Undeclarations of prefixes aren't allowed in XML 1.0 "
460 "and aren't supposed to be received.");
462 if(nb.namespaceURI() == QPatternist::StandardNamespaces::StopNamespaceInheritance)
465 if(isBindingInScope(nb))
468 d->namespaces.top().append(nb);
470 if(nb.prefix() == StandardPrefixes::empty)
475 write(d->np->stringForPrefix(nb.prefix()));
479 writeEscapedAttribute(d->np->stringForNamespace(nb.namespaceURI()));
486 void QXmlSerializer::comment(const QString &value)
489 Q_ASSERT_X(!value.contains(QLatin1String("--")),
491 "Invalid input; it's the caller's responsibility to ensure "
492 "the input is correct.");
498 d->isPreviousAtomic = false;
504 void QXmlSerializer::characters(const QStringRef &value)
507 d->isPreviousAtomic = false;
509 writeEscaped(value.toString());
515 void QXmlSerializer::processingInstruction(const QXmlName &name,
516 const QString &value)
519 Q_ASSERT_X(!value.contains(QLatin1String("?>")),
521 "Invalid input; it's the caller's responsibility to ensure "
522 "the input is correct.");
531 d->isPreviousAtomic = false;
537 void QXmlSerializer::item(const QPatternist::Item &outputItem)
541 if(outputItem.isAtomicValue())
543 if(d->isPreviousAtomic)
547 writeEscaped(outputItem.stringValue());
551 d->isPreviousAtomic = true;
552 const QString value(outputItem.stringValue());
564 Q_ASSERT(outputItem.isNode());
565 sendAsNode(outputItem);
572 void QXmlSerializer::atomicValue(const QVariant &value)
580 void QXmlSerializer::startDocument()
583 d->isPreviousAtomic = false;
589 void QXmlSerializer::endDocument()
592 d->isPreviousAtomic = false;
597 Returns a pointer to the output device. There is no corresponding
598 function to \e set the output device, because the output device must
599 be passed to the constructor. The serializer does not take ownership
602 QIODevice *QXmlSerializer::outputDevice() const
604 Q_D(const QXmlSerializer);
609 Sets the codec the serializer will use for encoding its XML output.
610 The output codec is set to \a outputCodec. By default, the output
611 codec is set to the one for \c UTF-8. The serializer does not take
612 ownership of the codec.
617 void QXmlSerializer::setCodec(const QTextCodec *outputCodec)
620 d->codec = outputCodec;
624 Returns the codec being used by the serializer for encoding its
629 const QTextCodec *QXmlSerializer::codec() const
631 Q_D(const QXmlSerializer);
638 void QXmlSerializer::startOfSequence()
645 void QXmlSerializer::endOfSequence()
647 /* If this function is changed to flush or close or something like that,
648 * take into consideration QXmlFormatter, especially
649 * QXmlFormatter::endOfSequence().