1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtXmlPatterns module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** 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.
100 \inmodule QtXmlPatterns
102 QXmlSerializer translates an \l {XQuery Sequence} {XQuery sequence}, usually
103 the output of an QXmlQuery, into XML. Consider the example:
105 \snippet doc/src/snippets/code/src_xmlpatterns_api_qxmlserializer.cpp 0
107 First it constructs a \l {QXmlQuery} {query} that gets the
108 first paragraph from document \c index.html. Then it constructs
109 an instance of this class with the \l {QXmlQuery} {query} and
110 \l {QIODevice} {myOutputDevice}. Finally, it
111 \l {QXmlQuery::evaluateTo()} {evaluates} the
112 \l {QXmlQuery} {query}, producing an ordered sequence of calls
113 to the serializer's callback functions. The sequence of callbacks
114 transforms the query output to XML and writes it to
115 \l {QIODevice} {myOutputDevice}.
120 \li Declare namespaces when needed,
122 \li Use appropriate escaping, when characters can't be
123 represented in the XML,
125 \li Handle line endings appropriately,
127 \li Report errors, when it can't serialize the content, e.g.,
128 when asked to serialize an attribute that is a top-level node,
129 or when more than one top-level element is encountered.
133 If an error occurs during serialization, result is undefined
134 unless the serializer is driven through a call to
135 QXmlQuery::evaluateTo().
137 If the generated XML should be indented and formatted for reading,
140 \sa {http://www.w3.org/TR/xslt-xquery-serialization/}{XSLT 2.0 and XQuery 1.0 Serialization}
146 Constructs a serializer that uses the name pool and message
147 handler in \a query, and writes the output to \a outputDevice.
149 \a outputDevice must be a valid, non-null device that is open in
150 write mode, otherwise behavior is undefined.
152 \a outputDevice must not be opened with QIODevice::Text because it
153 will cause the output to be incorrect. This class will ensure line
154 endings are serialized as according with the XML specification.
155 QXmlSerializer does not take ownership of \a outputDevice.
157 QXmlSerializer::QXmlSerializer(const QXmlQuery &query,
158 QIODevice *outputDevice) : QAbstractXmlReceiver(new QXmlSerializerPrivate(query, outputDevice))
162 qWarning("outputDevice cannot be null.");
166 if(!outputDevice->isWritable())
168 qWarning("outputDevice must be opened in write mode.");
176 QXmlSerializer::QXmlSerializer(QAbstractXmlReceiverPrivate *d) : QAbstractXmlReceiver(d)
183 bool QXmlSerializer::atDocumentRoot() const
185 Q_D(const QXmlSerializer);
186 return d->state == BeforeDocumentElement ||
187 (d->state == InsideDocumentElement && d->hasClosedElement.size() == 1);
193 void QXmlSerializer::startContent()
196 if (!d->hasClosedElement.top().second) {
198 d->hasClosedElement.top().second = true;
205 void QXmlSerializer::writeEscaped(const QString &toEscape)
207 if(toEscape.isEmpty()) /* Early exit. */
211 result.reserve(int(toEscape.length() * 1.1));
212 const int length = toEscape.length();
214 for(int i = 0; i < length; ++i)
216 const QChar c(toEscape.at(i));
218 if(c == QLatin1Char('<'))
219 result += QLatin1String("<");
220 else if(c == QLatin1Char('>'))
221 result += QLatin1String(">");
222 else if(c == QLatin1Char('&'))
223 result += QLatin1String("&");
225 result += toEscape.at(i);
234 void QXmlSerializer::writeEscapedAttribute(const QString &toEscape)
236 if(toEscape.isEmpty()) /* Early exit. */
240 result.reserve(int(toEscape.length() * 1.1));
241 const int length = toEscape.length();
243 for(int i = 0; i < length; ++i)
245 const QChar c(toEscape.at(i));
247 if(c == QLatin1Char('<'))
248 result += QLatin1String("<");
249 else if(c == QLatin1Char('>'))
250 result += QLatin1String(">");
251 else if(c == QLatin1Char('&'))
252 result += QLatin1String("&");
253 else if(c == QLatin1Char('"'))
254 result += QLatin1String(""");
256 result += toEscape.at(i);
265 void QXmlSerializer::write(const QString &content)
268 d->device->write(d->codec->fromUnicode(content.constData(), content.length(), &d->converterState));
274 void QXmlSerializer::write(const QXmlName &name)
277 const QByteArray &cell = d->nameCache[name.code()];
281 QByteArray &mutableCell = d->nameCache[name.code()];
283 const QString content(d->np->toLexical(name));
284 mutableCell = d->codec->fromUnicode(content.constData(),
287 d->device->write(mutableCell);
290 d->device->write(cell);
296 void QXmlSerializer::write(const char *const chars)
299 d->device->write(chars);
305 void QXmlSerializer::startElement(const QXmlName &name)
309 Q_ASSERT(d->device->isWritable());
311 Q_ASSERT(!name.isNull());
313 d->namespaces.push(QVector<QXmlName>());
317 if(d->state == BeforeDocumentElement)
318 d->state = InsideDocumentElement;
319 else if(d->state != InsideDocumentElement)
321 d->query.d->staticContext()->error(QtXmlPatterns::tr(
322 "Element %1 can't be serialized because it appears outside "
323 "the document element.").arg(formatKeyword(d->np, name)),
324 ReportContext::SENR0001,
325 d->query.d->expression().data());
333 /* Ensure that the namespace URI used in the name gets outputted. */
334 namespaceBinding(name);
336 d->hasClosedElement.push(qMakePair(name, false));
337 d->isPreviousAtomic = false;
343 void QXmlSerializer::endElement()
346 const QPair<QXmlName, bool> e(d->hasClosedElement.pop());
358 d->isPreviousAtomic = false;
364 void QXmlSerializer::attribute(const QXmlName &name,
365 const QStringRef &value)
368 Q_ASSERT(!name.isNull());
370 /* Ensure that the namespace URI used in the name gets outputted. */
372 /* Since attributes doesn't pick up the default namespace, a
373 * namespace declaration would cause trouble if we output it. */
374 if(name.prefix() != StandardPrefixes::empty)
375 namespaceBinding(name);
381 d->query.d->staticContext()->error(QtXmlPatterns::tr(
382 "Attribute %1 can't be serialized because it appears at "
383 "the top level.").arg(formatKeyword(d->np, name)),
384 ReportContext::SENR0001,
385 d->query.d->expression().data());
392 writeEscapedAttribute(value.toString());
400 bool QXmlSerializer::isBindingInScope(const QXmlName nb) const
402 Q_D(const QXmlSerializer);
403 const int levelLen = d->namespaces.size();
405 if(nb.prefix() == StandardPrefixes::empty)
407 for(int lvl = levelLen - 1; lvl >= 0; --lvl)
409 const QVector<QXmlName> &scope = d->namespaces.at(lvl);
410 const int vectorLen = scope.size();
412 for(int s = vectorLen - 1; s >= 0; --s)
414 const QXmlName &nsb = scope.at(s);
416 if(nsb.prefix() == StandardPrefixes::empty)
417 return nsb.namespaceURI() == nb.namespaceURI();
423 for(int lvl = 0; lvl < levelLen; ++lvl)
425 const QVector<QXmlName> &scope = d->namespaces.at(lvl);
426 const int vectorLen = scope.size();
428 for(int s = 0; s < vectorLen; ++s)
430 const QXmlName &n = scope.at(s);
431 if (n.prefix() == nb.prefix() &&
432 n.namespaceURI() == nb.namespaceURI())
444 void QXmlSerializer::namespaceBinding(const QXmlName &nb)
449 * Namespace bindings aren't looked up in a cache, because
450 * we typically receive very few.
454 Q_ASSERT_X(!nb.isNull(), Q_FUNC_INFO,
455 "It makes no sense to pass a null QXmlName.");
457 Q_ASSERT_X((nb.namespaceURI() != StandardNamespaces::empty) ||
458 (nb.prefix() == StandardPrefixes::empty),
460 "Undeclarations of prefixes aren't allowed in XML 1.0 "
461 "and aren't supposed to be received.");
463 if(nb.namespaceURI() == QPatternist::StandardNamespaces::StopNamespaceInheritance)
466 if(isBindingInScope(nb))
469 d->namespaces.top().append(nb);
471 if(nb.prefix() == StandardPrefixes::empty)
476 write(d->np->stringForPrefix(nb.prefix()));
480 writeEscapedAttribute(d->np->stringForNamespace(nb.namespaceURI()));
487 void QXmlSerializer::comment(const QString &value)
490 Q_ASSERT_X(!value.contains(QLatin1String("--")),
492 "Invalid input; it's the caller's responsibility to ensure "
493 "the input is correct.");
499 d->isPreviousAtomic = false;
505 void QXmlSerializer::characters(const QStringRef &value)
508 d->isPreviousAtomic = false;
510 writeEscaped(value.toString());
516 void QXmlSerializer::processingInstruction(const QXmlName &name,
517 const QString &value)
520 Q_ASSERT_X(!value.contains(QLatin1String("?>")),
522 "Invalid input; it's the caller's responsibility to ensure "
523 "the input is correct.");
532 d->isPreviousAtomic = false;
538 void QXmlSerializer::item(const QPatternist::Item &outputItem)
542 if(outputItem.isAtomicValue())
544 if(d->isPreviousAtomic)
548 writeEscaped(outputItem.stringValue());
552 d->isPreviousAtomic = true;
553 const QString value(outputItem.stringValue());
565 Q_ASSERT(outputItem.isNode());
566 sendAsNode(outputItem);
573 void QXmlSerializer::atomicValue(const QVariant &value)
581 void QXmlSerializer::startDocument()
584 d->isPreviousAtomic = false;
590 void QXmlSerializer::endDocument()
593 d->isPreviousAtomic = false;
598 Returns a pointer to the output device. There is no corresponding
599 function to \e set the output device, because the output device must
600 be passed to the constructor. The serializer does not take ownership
603 QIODevice *QXmlSerializer::outputDevice() const
605 Q_D(const QXmlSerializer);
610 Sets the codec the serializer will use for encoding its XML output.
611 The output codec is set to \a outputCodec. By default, the output
612 codec is set to the one for \c UTF-8. The serializer does not take
613 ownership of the codec.
618 void QXmlSerializer::setCodec(const QTextCodec *outputCodec)
621 d->codec = outputCodec;
625 Returns the codec being used by the serializer for encoding its
630 const QTextCodec *QXmlSerializer::codec() const
632 Q_D(const QXmlSerializer);
639 void QXmlSerializer::startOfSequence()
646 void QXmlSerializer::endOfSequence()
648 /* If this function is changed to flush or close or something like that,
649 * take into consideration QXmlFormatter, especially
650 * QXmlFormatter::endOfSequence().