source: trunk/src/xmlpatterns/api/qxmlserializer.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 16.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtXmlPatterns module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qdynamiccontext_p.h"
43#include "qpatternistlocale_p.h"
44#include "qitem_p.h"
45#include "qxmlquery_p.h"
46#include "qxmlserializer_p.h"
47#include "qxmlserializer.h"
48
49QT_BEGIN_NAMESPACE
50
51using namespace QPatternist;
52
53QXmlSerializerPrivate::QXmlSerializerPrivate(const QXmlQuery &query,
54 QIODevice *outputDevice)
55 : isPreviousAtomic(false),
56 state(QXmlSerializer::BeforeDocumentElement),
57 np(query.namePool().d),
58 device(outputDevice),
59 codec(QTextCodec::codecForMib(106)), /* UTF-8 */
60 query(query)
61{
62 hasClosedElement.reserve(EstimatedTreeDepth);
63 namespaces.reserve(EstimatedTreeDepth);
64 nameCache.reserve(EstimatedNameCount);
65
66 hasClosedElement.push(qMakePair(QXmlName(), true));
67
68 /*
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.
73
74 We push the XML namespace. Although we won't receive
75 declarations for it, we may output attributes by that
76 name.
77 */
78 QVector<QXmlName> defNss;
79 defNss.resize(2);
80 defNss[0] = QXmlName(StandardNamespaces::empty,
81 StandardLocalNames::empty,
82 StandardPrefixes::empty);
83 defNss[1] = QXmlName(StandardNamespaces::xml,
84 StandardLocalNames::empty,
85 StandardPrefixes::xml);
86
87 namespaces.push(defNss);
88
89 /* If we don't set this flag, QTextCodec will generate a BOM. */
90 converterState.flags = QTextCodec::IgnoreHeader;
91}
92
93/*!
94 \class QXmlSerializer
95 \brief The QXmlSerializer class is an implementation of QAbstractXmlReceiver for transforming XQuery output into unformatted XML.
96
97 \reentrant
98 \since 4.4
99 \ingroup xml-tools
100
101 QXmlSerializer translates an \l {XQuery Sequence} {XQuery sequence}, usually
102 the output of an QXmlQuery, into XML. Consider the example:
103
104 \snippet doc/src/snippets/code/src_xmlpatterns_api_qxmlserializer.cpp 0
105
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}.
115
116 QXmlSerializer will:
117
118 \list
119 \o Declare namespaces when needed,
120
121 \o Use appropriate escaping, when characters can't be
122 represented in the XML,
123
124 \o Handle line endings appropriately,
125
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.
129
130 \endlist
131
132 If an error occurs during serialization, result is undefined
133 unless the serializer is driven through a call to
134 QXmlQuery::evaluateTo().
135
136 If the generated XML should be indented and formatted for reading,
137 use QXmlFormatter.
138
139 \sa {http://www.w3.org/TR/xslt-xquery-serialization/}{XSLT 2.0 and XQuery 1.0 Serialization}
140
141 \sa QXmlFormatter
142 */
143
144/*!
145 Constructs a serializer that uses the name pool and message
146 handler in \a query, and writes the output to \a outputDevice.
147
148 \a outputDevice must be a valid, non-null device that is open in
149 write mode, otherwise behavior is undefined.
150
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.
155 */
156QXmlSerializer::QXmlSerializer(const QXmlQuery &query,
157 QIODevice *outputDevice) : QAbstractXmlReceiver(new QXmlSerializerPrivate(query, outputDevice))
158{
159 if(!outputDevice)
160 {
161 qWarning("outputDevice cannot be null.");
162 return;
163 }
164
165 if(!outputDevice->isWritable())
166 {
167 qWarning("outputDevice must be opened in write mode.");
168 return;
169 }
170}
171
172/*!
173 \internal
174 */
175QXmlSerializer::QXmlSerializer(QAbstractXmlReceiverPrivate *d) : QAbstractXmlReceiver(d)
176{
177}
178
179/*!
180 \internal
181 */
182bool QXmlSerializer::atDocumentRoot() const
183{
184 Q_D(const QXmlSerializer);
185 return d->state == BeforeDocumentElement ||
186 (d->state == InsideDocumentElement && d->hasClosedElement.size() == 1);
187}
188
189/*!
190 \internal
191 */
192void QXmlSerializer::startContent()
193{
194 Q_D(QXmlSerializer);
195 if (!d->hasClosedElement.top().second) {
196 d->write('>');
197 d->hasClosedElement.top().second = true;
198 }
199}
200
201/*!
202 \internal
203 */
204void QXmlSerializer::writeEscaped(const QString &toEscape)
205{
206 if(toEscape.isEmpty()) /* Early exit. */
207 return;
208
209 QString result;
210 result.reserve(int(toEscape.length() * 1.1));
211 const int length = toEscape.length();
212
213 for(int i = 0; i < length; ++i)
214 {
215 const QChar c(toEscape.at(i));
216
217 if(c == QLatin1Char('<'))
218 result += QLatin1String("&lt;");
219 else if(c == QLatin1Char('>'))
220 result += QLatin1String("&gt;");
221 else if(c == QLatin1Char('&'))
222 result += QLatin1String("&amp;");
223 else
224 result += toEscape.at(i);
225 }
226
227 write(result);
228}
229
230/*!
231 \internal
232 */
233void QXmlSerializer::writeEscapedAttribute(const QString &toEscape)
234{
235 if(toEscape.isEmpty()) /* Early exit. */
236 return;
237
238 QString result;
239 result.reserve(int(toEscape.length() * 1.1));
240 const int length = toEscape.length();
241
242 for(int i = 0; i < length; ++i)
243 {
244 const QChar c(toEscape.at(i));
245
246 if(c == QLatin1Char('<'))
247 result += QLatin1String("&lt;");
248 else if(c == QLatin1Char('>'))
249 result += QLatin1String("&gt;");
250 else if(c == QLatin1Char('&'))
251 result += QLatin1String("&amp;");
252 else if(c == QLatin1Char('"'))
253 result += QLatin1String("&quot;");
254 else
255 result += toEscape.at(i);
256 }
257
258 write(result);
259}
260
261/*!
262 \internal
263 */
264void QXmlSerializer::write(const QString &content)
265{
266 Q_D(QXmlSerializer);
267 d->device->write(d->codec->fromUnicode(content.constData(), content.length(), &d->converterState));
268}
269
270/*!
271 \internal
272 */
273void QXmlSerializer::write(const QXmlName &name)
274{
275 Q_D(QXmlSerializer);
276 const QByteArray &cell = d->nameCache[name.code()];
277
278 if(cell.isNull())
279 {
280 QByteArray &mutableCell = d->nameCache[name.code()];
281
282 const QString content(d->np->toLexical(name));
283 mutableCell = d->codec->fromUnicode(content.constData(),
284 content.length(),
285 &d->converterState);
286 d->device->write(mutableCell);
287 }
288 else
289 d->device->write(cell);
290}
291
292/*!
293 \internal
294 */
295void QXmlSerializer::write(const char *const chars)
296{
297 Q_D(QXmlSerializer);
298 d->device->write(chars);
299}
300
301/*!
302 \reimp
303 */
304void QXmlSerializer::startElement(const QXmlName &name)
305{
306 Q_D(QXmlSerializer);
307 Q_ASSERT(d->device);
308 Q_ASSERT(d->device->isWritable());
309 Q_ASSERT(d->codec);
310 Q_ASSERT(!name.isNull());
311
312 d->namespaces.push(QVector<QXmlName>());
313
314 if(atDocumentRoot())
315 {
316 if(d->state == BeforeDocumentElement)
317 d->state = InsideDocumentElement;
318 else if(d->state != InsideDocumentElement)
319 {
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());
325 }
326 }
327
328 startContent();
329 d->write('<');
330 write(name);
331
332 /* Ensure that the namespace URI used in the name gets outputted. */
333 namespaceBinding(name);
334
335 d->hasClosedElement.push(qMakePair(name, false));
336 d->isPreviousAtomic = false;
337}
338
339/*!
340 \reimp
341 */
342void QXmlSerializer::endElement()
343{
344 Q_D(QXmlSerializer);
345 const QPair<QXmlName, bool> e(d->hasClosedElement.pop());
346 d->namespaces.pop();
347
348 if(e.second)
349 {
350 write("</");
351 write(e.first);
352 d->write('>');
353 }
354 else
355 write("/>");
356
357 d->isPreviousAtomic = false;
358}
359
360/*!
361 \reimp
362 */
363void QXmlSerializer::attribute(const QXmlName &name,
364 const QStringRef &value)
365{
366 Q_D(QXmlSerializer);
367 Q_ASSERT(!name.isNull());
368
369 /* Ensure that the namespace URI used in the name gets outputted. */
370 {
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);
375 }
376
377 if(atDocumentRoot())
378 {
379 Q_UNUSED(d);
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());
385 }
386 else
387 {
388 d->write(' ');
389 write(name);
390 write("=\"");
391 writeEscapedAttribute(value.toString());
392 d->write('"');
393 }
394}
395
396/*!
397 \internal
398 */
399bool QXmlSerializer::isBindingInScope(const QXmlName nb) const
400{
401 Q_D(const QXmlSerializer);
402 const int levelLen = d->namespaces.size();
403
404 if(nb.prefix() == StandardPrefixes::empty)
405 {
406 for(int lvl = levelLen - 1; lvl >= 0; --lvl)
407 {
408 const QVector<QXmlName> &scope = d->namespaces.at(lvl);
409 const int vectorLen = scope.size();
410
411 for(int s = vectorLen - 1; s >= 0; --s)
412 {
413 const QXmlName &nsb = scope.at(s);
414
415 if(nsb.prefix() == StandardPrefixes::empty)
416 return nsb.namespaceURI() == nb.namespaceURI();
417 }
418 }
419 }
420 else
421 {
422 for(int lvl = 0; lvl < levelLen; ++lvl)
423 {
424 const QVector<QXmlName> &scope = d->namespaces.at(lvl);
425 const int vectorLen = scope.size();
426
427 for(int s = 0; s < vectorLen; ++s)
428 {
429 const QXmlName &n = scope.at(s);
430 if (n.prefix() == nb.prefix() &&
431 n.namespaceURI() == nb.namespaceURI())
432 return true;
433 }
434 }
435 }
436
437 return false;
438}
439
440/*!
441 \reimp
442 */
443void QXmlSerializer::namespaceBinding(const QXmlName &nb)
444{
445 /*
446 * Writes out \a nb.
447 *
448 * Namespace bindings aren't looked up in a cache, because
449 * we typically receive very few.
450 */
451
452 Q_D(QXmlSerializer);
453 Q_ASSERT_X(!nb.isNull(), Q_FUNC_INFO,
454 "It makes no sense to pass a null QXmlName.");
455
456 Q_ASSERT_X((nb.namespaceURI() != StandardNamespaces::empty) ||
457 (nb.prefix() == StandardPrefixes::empty),
458 Q_FUNC_INFO,
459 "Undeclarations of prefixes aren't allowed in XML 1.0 "
460 "and aren't supposed to be received.");
461
462 if(nb.namespaceURI() == QPatternist::StandardNamespaces::StopNamespaceInheritance)
463 return;
464
465 if(isBindingInScope(nb))
466 return;
467
468 d->namespaces.top().append(nb);
469
470 if(nb.prefix() == StandardPrefixes::empty)
471 write(" xmlns");
472 else
473 {
474 write(" xmlns:");
475 write(d->np->stringForPrefix(nb.prefix()));
476 }
477
478 write("=\"");
479 writeEscapedAttribute(d->np->stringForNamespace(nb.namespaceURI()));
480 d->write('"');
481}
482
483/*!
484 \reimp
485 */
486void QXmlSerializer::comment(const QString &value)
487{
488 Q_D(QXmlSerializer);
489 Q_ASSERT_X(!value.contains(QLatin1String("--")),
490 Q_FUNC_INFO,
491 "Invalid input; it's the caller's responsibility to ensure "
492 "the input is correct.");
493
494 startContent();
495 write("<!--");
496 write(value);
497 write("-->");
498 d->isPreviousAtomic = false;
499}
500
501/*!
502 \reimp
503 */
504void QXmlSerializer::characters(const QStringRef &value)
505{
506 Q_D(QXmlSerializer);
507 d->isPreviousAtomic = false;
508 startContent();
509 writeEscaped(value.toString());
510}
511
512/*!
513 \reimp
514 */
515void QXmlSerializer::processingInstruction(const QXmlName &name,
516 const QString &value)
517{
518 Q_D(QXmlSerializer);
519 Q_ASSERT_X(!value.contains(QLatin1String("?>")),
520 Q_FUNC_INFO,
521 "Invalid input; it's the caller's responsibility to ensure "
522 "the input is correct.");
523
524 startContent();
525 write("<?");
526 write(name);
527 d->write(' ');
528 write(value);
529 write("?>");
530
531 d->isPreviousAtomic = false;
532}
533
534/*!
535 \internal
536 */
537void QXmlSerializer::item(const QPatternist::Item &outputItem)
538{
539 Q_D(QXmlSerializer);
540
541 if(outputItem.isAtomicValue())
542 {
543 if(d->isPreviousAtomic)
544 {
545 startContent();
546 d->write(' ');
547 writeEscaped(outputItem.stringValue());
548 }
549 else
550 {
551 d->isPreviousAtomic = true;
552 const QString value(outputItem.stringValue());
553
554 if(!value.isEmpty())
555 {
556 startContent();
557 writeEscaped(value);
558 }
559 }
560 }
561 else
562 {
563 startContent();
564 Q_ASSERT(outputItem.isNode());
565 sendAsNode(outputItem);
566 }
567}
568
569/*!
570 \reimp
571 */
572void QXmlSerializer::atomicValue(const QVariant &value)
573{
574 Q_UNUSED(value);
575}
576
577/*!
578 \reimp
579 */
580void QXmlSerializer::startDocument()
581{
582 Q_D(QXmlSerializer);
583 d->isPreviousAtomic = false;
584}
585
586/*!
587 \reimp
588 */
589void QXmlSerializer::endDocument()
590{
591 Q_D(QXmlSerializer);
592 d->isPreviousAtomic = false;
593}
594
595/*!
596
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
600 of its IO device.
601 */
602QIODevice *QXmlSerializer::outputDevice() const
603{
604 Q_D(const QXmlSerializer);
605 return d->device;
606}
607
608/*!
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.
613
614 \sa codec()
615
616 */
617void QXmlSerializer::setCodec(const QTextCodec *outputCodec)
618{
619 Q_D(QXmlSerializer);
620 d->codec = outputCodec;
621}
622
623/*!
624 Returns the codec being used by the serializer for encoding its
625 XML output.
626
627 \sa setCodec()
628 */
629const QTextCodec *QXmlSerializer::codec() const
630{
631 Q_D(const QXmlSerializer);
632 return d->codec;
633}
634
635/*!
636 \reimp
637 */
638void QXmlSerializer::startOfSequence()
639{
640}
641
642/*!
643 \reimp
644 */
645void QXmlSerializer::endOfSequence()
646{
647 /* If this function is changed to flush or close or something like that,
648 * take into consideration QXmlFormatter, especially
649 * QXmlFormatter::endOfSequence().
650 */
651}
652
653QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.