src/xmlpatterns/api/qxmlserializer.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the QtXmlPatterns module of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    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 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    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 
       
    49 QT_BEGIN_NAMESPACE
       
    50 
       
    51 using namespace QPatternist;
       
    52 
       
    53 QXmlSerializerPrivate::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  */
       
   156 QXmlSerializer::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  */
       
   175 QXmlSerializer::QXmlSerializer(QAbstractXmlReceiverPrivate *d) : QAbstractXmlReceiver(d)
       
   176 {
       
   177 }
       
   178 
       
   179 /*!
       
   180   \internal
       
   181  */
       
   182 bool 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  */
       
   192 void 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  */
       
   204 void 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  */
       
   233 void 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  */
       
   264 void 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  */
       
   273 void 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  */
       
   295 void QXmlSerializer::write(const char *const chars)
       
   296 {
       
   297     Q_D(QXmlSerializer);
       
   298     d->device->write(chars);
       
   299 }
       
   300 
       
   301 /*!
       
   302   \reimp
       
   303  */
       
   304 void 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  */
       
   342 void 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  */
       
   363 void 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  */
       
   399 bool 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  */
       
   443 void 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  */
       
   486 void 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  */
       
   504 void 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  */
       
   515 void 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  */
       
   537 void 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  */
       
   572 void QXmlSerializer::atomicValue(const QVariant &value)
       
   573 {
       
   574     Q_UNUSED(value);
       
   575 }
       
   576 
       
   577 /*!
       
   578  \reimp
       
   579  */
       
   580 void QXmlSerializer::startDocument()
       
   581 {
       
   582     Q_D(QXmlSerializer);
       
   583     d->isPreviousAtomic = false;
       
   584 }
       
   585 
       
   586 /*!
       
   587  \reimp
       
   588  */
       
   589 void 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  */
       
   602 QIODevice *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  */
       
   617 void 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  */
       
   629 const QTextCodec *QXmlSerializer::codec() const
       
   630 {
       
   631     Q_D(const QXmlSerializer);
       
   632     return d->codec;
       
   633 }
       
   634 
       
   635 /*!
       
   636  \reimp
       
   637  */
       
   638 void QXmlSerializer::startOfSequence()
       
   639 {
       
   640 }
       
   641 
       
   642 /*!
       
   643  \reimp
       
   644  */
       
   645 void 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 
       
   653 QT_END_NAMESPACE