diff options
author | Qt by Nokia <qt-info@nokia.com> | 2011-04-27 12:05:43 +0200 |
---|---|---|
committer | axis <qt-info@nokia.com> | 2011-04-27 12:05:43 +0200 |
commit | e1b2c9deb5943faae2b29be6a5c006f75bb73f06 (patch) | |
tree | fc79e45367c0a8fc71185e9afc33f7503a58653c /src/xmlpatterns/acceltree | |
download | qtxmlpatterns-e1b2c9deb5943faae2b29be6a5c006f75bb73f06.tar.gz |
Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you
want to look at revision history older than this, please refer to the
Qt Git wiki for how to use Git history grafting. At the time of
writing, this wiki is located here:
http://qt.gitorious.org/qt/pages/GitIntroductionWithQt
If you have already performed the grafting and you don't see any
history beyond this commit, try running "git log" with the "--follow"
argument.
Branched from the monolithic repo, Qt master branch, at commit
896db169ea224deb96c59ce8af800d019de63f12
Diffstat (limited to 'src/xmlpatterns/acceltree')
-rw-r--r-- | src/xmlpatterns/acceltree/acceltree.pri | 10 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceliterators.cpp | 181 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceliterators_p.h | 413 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltree.cpp | 749 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltree_p.h | 404 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltreebuilder.cpp | 440 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltreebuilder_p.h | 204 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltreeresourceloader.cpp | 441 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltreeresourceloader_p.h | 210 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qcompressedwhitespace.cpp | 197 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qcompressedwhitespace_p.h | 186 |
11 files changed, 3435 insertions, 0 deletions
diff --git a/src/xmlpatterns/acceltree/acceltree.pri b/src/xmlpatterns/acceltree/acceltree.pri new file mode 100644 index 0000000..284371a --- /dev/null +++ b/src/xmlpatterns/acceltree/acceltree.pri @@ -0,0 +1,10 @@ +SOURCES += $$PWD/qacceltree.cpp \ + $$PWD/qacceltreeresourceloader.cpp \ + $$PWD/qacceliterators.cpp \ + $$PWD/qcompressedwhitespace.cpp + +HEADERS += $$PWD/qacceltreebuilder_p.h \ + $$PWD/qacceltree_p.h \ + $$PWD/qacceltreeresourceloader_p.h \ + $$PWD/qacceliterators_p.h \ + $$PWD/qcompressedwhitespace_p.h diff --git a/src/xmlpatterns/acceltree/qacceliterators.cpp b/src/xmlpatterns/acceltree/qacceliterators.cpp new file mode 100644 index 0000000..20cd805 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceliterators.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtXmlPatterns module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtDebug> + +#include "qacceliterators_p.h" + +QT_BEGIN_NAMESPACE + +using namespace QPatternist; + +xsInteger AccelIterator::position() const +{ + return m_position; +} + +QXmlNodeModelIndex AccelIterator::current() const +{ + return m_current; +} + +QXmlNodeModelIndex FollowingIterator::next() +{ + /* "the following axis contains all nodes that are descendants + * of the root of the tree in which the context node is found, + * are not descendants of the context node, and occur after + * the context node in document order." */ + + if(m_position == 0) + { + /* Skip the descendants. */ + m_currentPre += m_document->size(m_preNumber) + 1; + } + + if(m_currentPre > m_document->maximumPreNumber()) + return closedExit(); + + while(m_document->kind(m_currentPre) == QXmlNodeModelIndex::Attribute) + { + ++m_currentPre; + if(m_currentPre > m_document->maximumPreNumber()) + return closedExit(); + } + + m_current = m_document->createIndex(m_currentPre); + ++m_position; + ++m_currentPre; + return m_current; +} + +QXmlNodeModelIndex PrecedingIterator::next() +{ + if(m_currentPre == -1) + return closedExit(); + + /* We skip ancestors and attributes and take into account that they can be intermixed. If one + * skips them in two separate loops, one can end up with skipping all the attributes to then + * be positioned at an ancestor(which will be accepted because the ancestor loop was before the + * attributes loop). */ + while(m_document->kind(m_currentPre) == QXmlNodeModelIndex::Attribute || + m_document->postNumber(m_currentPre) > m_postNumber) + { + --m_currentPre; + if(m_currentPre == -1) + return closedExit(); + } + + if(m_currentPre == -1) + { + m_currentPre = -1; + return closedExit(); + } + + /* Phew, m_currentPre is now 1) not an ancestor; and + * 2) not an attribute; and 3) preceds the context node. */ + + m_current = m_document->createIndex(m_currentPre); + ++m_position; + --m_currentPre; + + return m_current; +} + +QXmlNodeModelIndex::Iterator::Ptr PrecedingIterator::copy() const +{ + return QXmlNodeModelIndex::Iterator::Ptr(new PrecedingIterator(m_document, m_preNumber)); +} + +QXmlNodeModelIndex::Iterator::Ptr FollowingIterator::copy() const +{ + return QXmlNodeModelIndex::Iterator::Ptr(new FollowingIterator(m_document, m_preNumber)); +} + +QXmlNodeModelIndex ChildIterator::next() +{ + if(m_currentPre == -1) + return closedExit(); + + ++m_position; + m_current = m_document->createIndex(m_currentPre); + + /* We get the count of the descendants, and increment m_currentPre. After + * this, m_currentPre is the node after the descendants. */ + m_currentPre += m_document->size(m_currentPre); + ++m_currentPre; + + if(m_currentPre > m_document->maximumPreNumber() || m_document->depth(m_currentPre) != m_depth) + m_currentPre = -1; + + return m_current; +} + +QXmlNodeModelIndex::Iterator::Ptr ChildIterator::copy() const +{ + return QXmlNodeModelIndex::Iterator::Ptr(new ChildIterator(m_document, m_preNumber)); +} + +QXmlNodeModelIndex AttributeIterator::next() +{ + if(m_currentPre == -1) + return closedExit(); + else + { + m_current = m_document->createIndex(m_currentPre); + ++m_position; + + ++m_currentPre; + + if(m_currentPre > m_document->maximumPreNumber() || + m_document->kind(m_currentPre) != QXmlNodeModelIndex::Attribute) + m_currentPre = -1; + + return m_current; + } +} + +QXmlNodeModelIndex::Iterator::Ptr AttributeIterator::copy() const +{ + return QXmlNodeModelIndex::Iterator::Ptr(new AttributeIterator(m_document, m_preNumber)); +} + +QT_END_NAMESPACE + diff --git a/src/xmlpatterns/acceltree/qacceliterators_p.h b/src/xmlpatterns/acceltree/qacceliterators_p.h new file mode 100644 index 0000000..e472fb2 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceliterators_p.h @@ -0,0 +1,413 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtXmlPatterns module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef Patternist_AccelIterators_H +#define Patternist_AccelIterators_H + +#include "qacceltree_p.h" +#include "qitem_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +namespace QPatternist +{ + /** + * @short Abstract base class for Iterators for the AccelTree, that + * contains common functions and members. + * + * @author Frans Englich<frans.englich@nokia.com> + */ + class AccelIterator : public QXmlNodeModelIndex::Iterator + { + public: + virtual xsInteger position() const; + virtual QXmlNodeModelIndex current() const; + + protected: + inline AccelIterator(const AccelTree *const doc, + const AccelTree::PreNumber pre, + const AccelTree::PreNumber currentPre) : m_document(doc) + , m_preNumber(pre) + , m_currentPre(currentPre) + , m_position(0) + + { + Q_ASSERT(m_document); + Q_ASSERT(m_preNumber >= 0); + } + + inline QXmlNodeModelIndex closedExit() + { + m_position = -1; + m_current.reset(); + return QXmlNodeModelIndex(); + } + + /** + * We do not own it. + */ + const AccelTree *const m_document; + + /** + * The pre number of the node that should be navigated from. + */ + const AccelTree::PreNumber m_preNumber; + AccelTree::PreNumber m_currentPre; + xsInteger m_position; + QXmlNodeModelIndex m_current; + }; + + /** + * @short Iterates along the @c ancestor or @c ancestor-or-self axis in an AccelTree. + * + * @author Frans Englich<frans.englich@nokia.com> + */ + template<const bool IncludeSelf> + class AncestorIterator : public AccelIterator + { + public: + /** + * @p pre is the node from which iteration starts + * from. In the @c ancestor axis it is excluded, + * while in @c ancestor-or-self it is included. @p pre + * must have at least one ancestor. + */ + inline AncestorIterator(const AccelTree *const doc, + const AccelTree::PreNumber pre) : AccelIterator(doc, pre, IncludeSelf ? pre : doc->basicData.at(pre).parent()) + { + Q_ASSERT(IncludeSelf || m_document->hasParent(pre)); + } + + virtual QXmlNodeModelIndex next() + { + if(m_currentPre == -1) + return closedExit(); + else + { + ++m_position; + m_current = m_document->createIndex(m_currentPre); + m_currentPre = m_document->basicData.at(m_currentPre).parent(); + + return m_current; + } + } + + virtual QXmlNodeModelIndex::Iterator::Ptr copy() const + { + return QXmlNodeModelIndex::Iterator::Ptr(new AncestorIterator<IncludeSelf>(m_document, m_preNumber)); + } + }; + + /** + * @short Iterates along the @c child axis in an AccelTree. + * + * @author Frans Englich<frans.englich@nokia.com> + */ + class ChildIterator : public AccelIterator + { + public: + /** + * @p pre must have at least one child. + */ + inline ChildIterator(const AccelTree *const doc, + const AccelTree::PreNumber pre) : AccelIterator(doc, pre, pre + 1), + m_depth(m_document->depth(m_currentPre)) + { + Q_ASSERT(m_document->hasChildren(pre)); + + /* Skip the attributes, that are children in the pre/post plane, of + * the node we're applying the child axis to. */ + while(m_document->kind(m_currentPre) == QXmlNodeModelIndex::Attribute) + { + ++m_currentPre; + /* We check the depth here because we would otherwise include + * following siblings. */ + if(m_currentPre > m_document->maximumPreNumber() || m_document->depth(m_currentPre) != m_depth) + { + m_currentPre = -1; + break; + } + } + } + + virtual QXmlNodeModelIndex next(); + virtual QXmlNodeModelIndex::Iterator::Ptr copy() const; + + private: + const AccelTree::Depth m_depth; + }; + + /** + * @short Iterates along the sibling axes in an AccelTree. + * + * @author Frans Englich<frans.englich@nokia.com> + */ + template<const bool IsFollowing> + class SiblingIterator : public AccelIterator + { + public: + inline SiblingIterator(const AccelTree *const doc, + const AccelTree::PreNumber pre) : AccelIterator(doc, pre, pre + (IsFollowing ? 0 : -1)), + m_depth(doc->depth(pre)) + { + Q_ASSERT_X(IsFollowing || pre != 0, "", + "When being preceding-sibling, the context node cannot be the first node in the document."); + Q_ASSERT_X(!IsFollowing || pre != m_document->maximumPreNumber(), "", + "When being following-sibling, the context node cannot be the last node in the document."); + } + + virtual QXmlNodeModelIndex next() + { + if(m_currentPre == -1) + return QXmlNodeModelIndex(); + + if(IsFollowing) + { + /* Skip the descendants, and jump to the next node. */ + m_currentPre += m_document->size(m_currentPre) + 1; + + if(m_currentPre > m_document->maximumPreNumber() || m_document->depth(m_currentPre) != m_depth) + return closedExit(); + else + { + ++m_position; + m_current = m_document->createIndex(m_currentPre); + return m_current; + } + } + else + { + while(m_document->depth(m_currentPre) > m_depth) + --m_currentPre; + + while(m_document->kind(m_currentPre) == QXmlNodeModelIndex::Attribute) + --m_currentPre; + + if(m_document->depth(m_currentPre) == m_depth && + m_document->kind(m_currentPre) != QXmlNodeModelIndex::Attribute) + { + m_current = m_document->createIndex(m_currentPre); + ++m_position; + --m_currentPre; + return m_current; + } + else + { + m_currentPre = -1; + return closedExit(); + } + } + } + + virtual QXmlNodeModelIndex::Iterator::Ptr copy() const + { + return QXmlNodeModelIndex::Iterator::Ptr(new SiblingIterator<IsFollowing>(m_document, m_preNumber)); + } + + private: + const AccelTree::Depth m_depth; + }; + + /** + * @short Implements axis @c descendant and @c descendant-or-self for the + * AccelTree. + * + * @author Frans Englich <frans.englich@nokia.com> + */ + template<const bool IncludeSelf> + class DescendantIterator : public AccelIterator + { + public: + /** + * @p pre must have at least one child. + */ + inline DescendantIterator(const AccelTree *const doc, + const AccelTree::PreNumber pre) : AccelIterator(doc, pre, pre + (IncludeSelf ? 0 : 1)), + m_postNumber(doc->postNumber(pre)) + { + Q_ASSERT(IncludeSelf || m_document->hasChildren(pre)); + + /* Make sure that m_currentPre is the first node part of this axis. + * Since we're not including ourself, advance to the node after our + * attributes, if any. */ + if(!IncludeSelf) + { + while(m_document->kind(m_currentPre) == QXmlNodeModelIndex::Attribute) + { + ++m_currentPre; + /* We check the depth here because we would otherwise include + * following siblings. */ + if(m_currentPre > m_document->maximumPreNumber() || m_document->postNumber(m_currentPre) > m_postNumber) + { + m_currentPre = -1; + break; + } + } + } + } + + virtual QXmlNodeModelIndex next() + { + if(m_currentPre == -1) + return closedExit(); + + ++m_position; + m_current = m_document->createIndex(m_currentPre); + + ++m_currentPre; + + if(m_currentPre > m_document->maximumPreNumber()) + { + m_currentPre = -1; + return m_current; + } + + if(m_document->postNumber(m_currentPre) < m_postNumber) + { + while(m_document->kind(m_currentPre) == QXmlNodeModelIndex::Attribute) + { + ++m_currentPre; + if(m_currentPre > m_document->maximumPreNumber()) + { + m_currentPre = -1; + break; + } + } + } + else + m_currentPre = -1; + + return m_current; + } + + virtual QXmlNodeModelIndex::Iterator::Ptr copy() const + { + return QXmlNodeModelIndex::Iterator::Ptr(new DescendantIterator<IncludeSelf>(m_document, m_preNumber)); + } + + private: + const AccelTree::PreNumber m_postNumber; + }; + + /** + * @short Implements axis @c following for the AccelTree. + * + * @author Frans Englich <frans.englich@nokia.com> + */ + class FollowingIterator : public AccelIterator + { + public: + /** + * @ pre must have at least one child. + */ + inline FollowingIterator(const AccelTree *const doc, + const AccelTree::PreNumber pre) : AccelIterator(doc, pre, pre) + { + } + + virtual QXmlNodeModelIndex next(); + virtual QXmlNodeModelIndex::Iterator::Ptr copy() const; + }; + + /** + * @short Implements axis @c preceding for the AccelTree. + * + * @author Frans Englich <frans.englich@nokia.com> + */ + class PrecedingIterator : public AccelIterator + { + public: + /** + * @ pre must have at least one child. + */ + inline PrecedingIterator(const AccelTree *const doc, + const AccelTree::PreNumber pre) : AccelIterator(doc, pre, + pre - 1 /* currentPre */) + , m_postNumber(m_document->postNumber(m_preNumber)) + { + } + + virtual QXmlNodeModelIndex next(); + virtual QXmlNodeModelIndex::Iterator::Ptr copy() const; + + private: + const AccelTree::PreNumber m_postNumber; + }; + + /** + * @short Implements axis @c attribute for the AccelTree. + * + * @author Frans Englich <frans.englich@nokia.com> + */ + class AttributeIterator : public AccelIterator + { + public: + /** + * @p pre must have at least one child. + */ + inline AttributeIterator(const AccelTree *const doc, const AccelTree::PreNumber pre) : AccelIterator(doc, pre, pre + 1) + { + Q_ASSERT(m_document->hasChildren(pre)); + Q_ASSERT(m_document->kind(m_currentPre) == QXmlNodeModelIndex::Attribute); + } + + virtual QXmlNodeModelIndex next(); + virtual QXmlNodeModelIndex::Iterator::Ptr copy() const; + }; +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/xmlpatterns/acceltree/qacceltree.cpp b/src/xmlpatterns/acceltree/qacceltree.cpp new file mode 100644 index 0000000..f2f383a --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltree.cpp @@ -0,0 +1,749 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtXmlPatterns module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QStack> + +#include "qabstractxmlreceiver.h" +#include "qabstractxmlnodemodel_p.h" +#include "qacceliterators_p.h" +#include "qacceltree_p.h" +#include "qatomicstring_p.h" +#include "qcommonvalues_p.h" +#include "qcompressedwhitespace_p.h" +#include "qdebug_p.h" +#include "quntypedatomic_p.h" +#include "qxpathhelper_p.h" + +QT_BEGIN_NAMESPACE + +using namespace QPatternist; + +namespace QPatternist { + + class AccelTreePrivate : public QAbstractXmlNodeModelPrivate + { + public: + AccelTreePrivate(AccelTree *accelTree) + : m_accelTree(accelTree) + { + } + + virtual QSourceLocation sourceLocation(const QXmlNodeModelIndex &index) const + { + return m_accelTree->sourceLocation(index); + } + + private: + AccelTree *m_accelTree; + }; +} + +AccelTree::AccelTree(const QUrl &docURI, const QUrl &bURI) + : QAbstractXmlNodeModel(new AccelTreePrivate(this)) + , m_documentURI(docURI) + , m_baseURI(bURI) +{ + /* Pre-allocate at least a little bit. */ + // TODO. Do it according to what an average 4 KB doc contains. + basicData.reserve(100); + data.reserve(30); +} + +void AccelTree::printStats(const NamePool::Ptr &np) const +{ + Q_ASSERT(np); +#ifdef QT_NO_DEBUG + Q_UNUSED(np); /* Needed when compiling in release mode. */ +#else + const int len = basicData.count(); + + pDebug() << "AccelTree stats for" << (m_documentURI.isEmpty() ? QString::fromLatin1("<empty URI>") : m_documentURI.toString()); + pDebug() << "Maximum pre number:" << maximumPreNumber(); + pDebug() << "+---------------+-------+-------+---------------+-------+--------------+-------+"; + pDebug() << "| Pre number | Depth | Size | Post Number | Kind | Name | Value |"; + pDebug() << "+---------------+-------+-------+---------------+-------+--------------+-------+"; + for(int i = 0; i < len; ++i) + { + const BasicNodeData &v = basicData.at(i); + pDebug() << '|' << i + << "\t\t|" << v.depth() + << "\t|" << v.size() + << "\t|" << postNumber(i) + << "\t|" << v.kind() + << "\t\t|" << (v.name().isNull() ? QString::fromLatin1("(none)") : np->displayName(v.name())) + << "\t\t|" << ((v.kind() == QXmlNodeModelIndex::Text && isCompressed(i)) ? CompressedWhitespace::decompress(data.value(i)) + : data.value(i)) + << "\t|"; + /* + pDebug() << '|' << QString().arg(i, 14) + << '|' << QString().arg(v.depth(), 6) + << '|' << QString().arg(v.size(), 6) + << '|' << QString().arg(postNumber(i), 14) + << '|' << QString().arg(v.kind(), 6) + << '|'; + */ + } + pDebug() << "+---------------+-------+-------+---------------+-------+--------------+"; + pDebug() << "Namespaces(" << namespaces.count() << "):"; + + QHashIterator<PreNumber, QVector<QXmlName> > it(namespaces); + while(it.hasNext()) + { + it.next(); + + pDebug() << "PreNumber: " << QString::number(it.key()); + for(int i = 0; i < it.value().count(); ++i) + pDebug() << "\t\t" << np->stringForPrefix(it.value().at(i).prefix()) << " = " << np->stringForNamespace(it.value().at(i).namespaceURI()); + } + +#endif +} + +QUrl AccelTree::baseUri(const QXmlNodeModelIndex &ni) const +{ + switch(kind(toPreNumber(ni))) + { + case QXmlNodeModelIndex::Document: + return baseUri(); + case QXmlNodeModelIndex::Element: + { + const QXmlNodeModelIndex::Iterator::Ptr it(iterate(ni, QXmlNodeModelIndex::AxisAttribute)); + QXmlNodeModelIndex next(it->next()); + + while(!next.isNull()) + { + if(next.name() == QXmlName(StandardNamespaces::xml, StandardLocalNames::base)) + { + const QUrl candidate(next.stringValue()); + // TODO. The xml:base spec says to do URI escaping here. + + if(!candidate.isValid()) + return QUrl(); + else if(candidate.isRelative()) + { + const QXmlNodeModelIndex par(parent(ni)); + + if(par.isNull()) + return baseUri().resolved(candidate); + else + return par.baseUri().resolved(candidate); + } + else + return candidate; + } + + next = it->next(); + } + + /* We have no xml:base-attribute. Can any parent supply us a base URI? */ + const QXmlNodeModelIndex par(parent(ni)); + + if(par.isNull()) + return baseUri(); + else + return par.baseUri(); + } + case QXmlNodeModelIndex::ProcessingInstruction: + /* Fallthrough. */ + case QXmlNodeModelIndex::Comment: + /* Fallthrough. */ + case QXmlNodeModelIndex::Attribute: + /* Fallthrough. */ + case QXmlNodeModelIndex::Text: + { + const QXmlNodeModelIndex par(ni.iterate(QXmlNodeModelIndex::AxisParent)->next()); + if(par.isNull()) + return QUrl(); + else + return par.baseUri(); + } + case QXmlNodeModelIndex::Namespace: + return QUrl(); + } + + Q_ASSERT_X(false, Q_FUNC_INFO, "This line is never supposed to be reached."); + return QUrl(); +} + +QUrl AccelTree::documentUri(const QXmlNodeModelIndex &ni) const +{ + if(kind(toPreNumber(ni)) == QXmlNodeModelIndex::Document) + return documentUri(); + else + return QUrl(); +} + +QXmlNodeModelIndex::NodeKind AccelTree::kind(const QXmlNodeModelIndex &ni) const +{ + return kind(toPreNumber(ni)); +} + +QXmlNodeModelIndex::DocumentOrder AccelTree::compareOrder(const QXmlNodeModelIndex &ni1, + const QXmlNodeModelIndex &ni2) const +{ + Q_ASSERT_X(ni1.model() == ni2.model(), Q_FUNC_INFO, + "The API docs guarantees the two nodes are from the same model"); + + const PreNumber p1 = ni1.data(); + const PreNumber p2 = ni2.data(); + + if(p1 == p2) + return QXmlNodeModelIndex::Is; + else if(p1 < p2) + return QXmlNodeModelIndex::Precedes; + else + return QXmlNodeModelIndex::Follows; +} + +QXmlNodeModelIndex AccelTree::root(const QXmlNodeModelIndex &) const +{ + return createIndex(qint64(0)); +} + +QXmlNodeModelIndex AccelTree::parent(const QXmlNodeModelIndex &ni) const +{ + const AccelTree::PreNumber p = basicData.at(toPreNumber(ni)).parent(); + + if(p == -1) + return QXmlNodeModelIndex(); + else + return createIndex(p); +} + +QXmlNodeModelIndex::Iterator::Ptr AccelTree::iterate(const QXmlNodeModelIndex &ni, + QXmlNodeModelIndex::Axis axis) const +{ + const PreNumber preNumber = toPreNumber(ni); + + switch(axis) + { + case QXmlNodeModelIndex::AxisChildOrTop: + { + if(!hasParent(preNumber)) + { + switch(kind(preNumber)) + { + case QXmlNodeModelIndex::Comment: + /* Fallthrough. */ + case QXmlNodeModelIndex::ProcessingInstruction: + /* Fallthrough. */ + case QXmlNodeModelIndex::Element: + /* Fallthrough. */ + case QXmlNodeModelIndex::Text: + return makeSingletonIterator(ni); + case QXmlNodeModelIndex::Attribute: + /* Fallthrough. */ + case QXmlNodeModelIndex::Document: + /* Fallthrough. */ + case QXmlNodeModelIndex::Namespace: + /* Do nothing. */; + } + } + /* Else, fallthrough to AxisChild. */ + } + case QXmlNodeModelIndex::AxisChild: + { + if(hasChildren(preNumber)) + return QXmlNodeModelIndex::Iterator::Ptr(new ChildIterator(this, preNumber)); + else + return makeEmptyIterator<QXmlNodeModelIndex>(); + } + case QXmlNodeModelIndex::AxisAncestor: + { + if(hasParent(preNumber)) + return QXmlNodeModelIndex::Iterator::Ptr(new AncestorIterator<false>(this, preNumber)); + else + return makeEmptyIterator<QXmlNodeModelIndex>(); + } + case QXmlNodeModelIndex::AxisAncestorOrSelf: + return QXmlNodeModelIndex::Iterator::Ptr(new AncestorIterator<true>(this, preNumber)); + case QXmlNodeModelIndex::AxisParent: + { + if(hasParent(preNumber)) + return makeSingletonIterator(createIndex(parent(preNumber))); + else + return makeEmptyIterator<QXmlNodeModelIndex>(); + } + case QXmlNodeModelIndex::AxisDescendant: + { + if(hasChildren(preNumber)) + return QXmlNodeModelIndex::Iterator::Ptr(new DescendantIterator<false>(this, preNumber)); + else + return makeEmptyIterator<QXmlNodeModelIndex>(); + } + case QXmlNodeModelIndex::AxisDescendantOrSelf: + return QXmlNodeModelIndex::Iterator::Ptr(new DescendantIterator<true>(this, preNumber)); + case QXmlNodeModelIndex::AxisFollowing: + { + if(preNumber == maximumPreNumber()) + return makeEmptyIterator<QXmlNodeModelIndex>(); + else + return QXmlNodeModelIndex::Iterator::Ptr(new FollowingIterator(this, preNumber)); + } + case QXmlNodeModelIndex::AxisAttributeOrTop: + { + if(!hasParent(preNumber) && kind(preNumber) == QXmlNodeModelIndex::Attribute) + return makeSingletonIterator(ni); + /* Else, falthrough to AxisAttribute. */ + } + case QXmlNodeModelIndex::AxisAttribute: + { + if(hasChildren(preNumber) && kind(preNumber + 1) == QXmlNodeModelIndex::Attribute) + return QXmlNodeModelIndex::Iterator::Ptr(new AttributeIterator(this, preNumber)); + else + return makeEmptyIterator<QXmlNodeModelIndex>(); + } + case QXmlNodeModelIndex::AxisPreceding: + { + if(preNumber == 0) + return makeEmptyIterator<QXmlNodeModelIndex>(); + else + return QXmlNodeModelIndex::Iterator::Ptr(new PrecedingIterator(this, preNumber)); + } + case QXmlNodeModelIndex::AxisSelf: + return makeSingletonIterator(createIndex(toPreNumber(ni))); + case QXmlNodeModelIndex::AxisFollowingSibling: + { + if(preNumber == maximumPreNumber()) + return makeEmptyIterator<QXmlNodeModelIndex>(); + else + return QXmlNodeModelIndex::Iterator::Ptr(new SiblingIterator<true>(this, preNumber)); + } + case QXmlNodeModelIndex::AxisPrecedingSibling: + { + if(preNumber == 0) + return makeEmptyIterator<QXmlNodeModelIndex>(); + else + return QXmlNodeModelIndex::Iterator::Ptr(new SiblingIterator<false>(this, preNumber)); + } + case QXmlNodeModelIndex::AxisNamespace: + return makeEmptyIterator<QXmlNodeModelIndex>(); + } + + Q_ASSERT(false); + return QXmlNodeModelIndex::Iterator::Ptr(); +} + +QXmlNodeModelIndex AccelTree::nextFromSimpleAxis(QAbstractXmlNodeModel::SimpleAxis, + const QXmlNodeModelIndex&) const +{ + Q_ASSERT_X(false, Q_FUNC_INFO, "This function is not supposed to be called."); + return QXmlNodeModelIndex(); +} + +QVector<QXmlNodeModelIndex> AccelTree::attributes(const QXmlNodeModelIndex &element) const +{ + Q_ASSERT_X(false, Q_FUNC_INFO, "This function is not supposed to be called."); + Q_UNUSED(element); + return QVector<QXmlNodeModelIndex>(); +} + +QXmlName AccelTree::name(const QXmlNodeModelIndex &ni) const +{ + /* If this node type does not have a name(for instance, it's a comment) + * we will return the default constructed value, which is conformant with + * this function's contract. */ + return name(toPreNumber(ni)); +} + +QVector<QXmlName> AccelTree::namespaceBindings(const QXmlNodeModelIndex &ni) const +{ + /* We get a hold of the ancestor, and loop them in reverse document + * order(first the parent, then the parent's parent, etc). As soon + * we find a binding that hasn't already been added, we add it to the + * result list. In that way, declarations appearing further down override + * those further up. */ + + const PreNumber preNumber = toPreNumber(ni); + + const QXmlNodeModelIndex::Iterator::Ptr it(new AncestorIterator<true>(this, preNumber)); + QVector<QXmlName> result; + QXmlNodeModelIndex n(it->next()); + + /* Whether xmlns="" has been encountered. */ + bool hasUndeclaration = false; + + while(!n.isNull()) + { + const QVector<QXmlName> &forNode = namespaces.value(toPreNumber(n)); + const int len = forNode.size(); + bool stopInheritance = false; + + for(int i = 0; i < len; ++i) + { + const QXmlName &nsb = forNode.at(i); + + if(nsb.namespaceURI() == StandardNamespaces::StopNamespaceInheritance) + { + stopInheritance = true; + continue; + } + + if(nsb.prefix() == StandardPrefixes::empty && + nsb.namespaceURI() == StandardNamespaces::empty) + { + hasUndeclaration = true; + continue; + } + + if(!hasPrefix(result, nsb.prefix())) + { + /* We've already encountered an undeclaration, so we're supposed to skip + * them. */ + if(hasUndeclaration && nsb.prefix() == StandardPrefixes::empty) + continue; + else + result.append(nsb); + } + } + + if(stopInheritance) + break; + else + n = it->next(); + } + + result.append(QXmlName(StandardNamespaces::xml, StandardLocalNames::empty, StandardPrefixes::xml)); + + return result; +} + +void AccelTree::sendNamespaces(const QXmlNodeModelIndex &n, + QAbstractXmlReceiver *const receiver) const +{ + Q_ASSERT(n.kind() == QXmlNodeModelIndex::Element); + + const QXmlNodeModelIndex::Iterator::Ptr it(iterate(n, QXmlNodeModelIndex::AxisAncestorOrSelf)); + QXmlNodeModelIndex next(it->next()); + QVector<QXmlName::PrefixCode> alreadySent; + + while(!next.isNull()) + { + const PreNumber preNumber = toPreNumber(next); + + const QVector<QXmlName> &nss = namespaces.value(preNumber); + + /* This is by far the most common case. */ + if(nss.isEmpty()) + { + next = it->next(); + continue; + } + + const int len = nss.count(); + bool stopInheritance = false; + + for(int i = 0; i < len; ++i) + { + const QXmlName &name = nss.at(i); + + if(name.namespaceURI() == StandardNamespaces::StopNamespaceInheritance) + { + stopInheritance = true; + continue; + } + + if(!alreadySent.contains(name.prefix())) + { + alreadySent.append(name.prefix()); + receiver->namespaceBinding(name); + } + } + + if(stopInheritance) + break; + else + next = it->next(); + } +} + +QString AccelTree::stringValue(const QXmlNodeModelIndex &ni) const +{ + const PreNumber preNumber = toPreNumber(ni); + + switch(kind(preNumber)) + { + case QXmlNodeModelIndex::Element: + { + /* Concatenate all text nodes that are descendants of this node. */ + if(!hasChildren(preNumber)) + return QString(); + + const AccelTree::PreNumber stop = preNumber + size(preNumber); + AccelTree::PreNumber pn = preNumber + 1; /* Jump over ourselves. */ + QString result; + + for(; pn <= stop; ++pn) + { + if(kind(pn) == QXmlNodeModelIndex::Text) + { + if(isCompressed(pn)) + result += CompressedWhitespace::decompress(data.value(pn)); + else + result += data.value(pn); + } + } + + return result; + } + case QXmlNodeModelIndex::Text: + { + if(isCompressed(preNumber)) + return CompressedWhitespace::decompress(data.value(preNumber)); + /* Else, fallthrough. It's not compressed so use it as it is. */ + } + case QXmlNodeModelIndex::Attribute: + /* Fallthrough */ + case QXmlNodeModelIndex::ProcessingInstruction: + /* Fallthrough */ + case QXmlNodeModelIndex::Comment: + return data.value(preNumber); + case QXmlNodeModelIndex::Document: + { + /* Concatenate all text nodes in the whole document. */ + + QString result; + // Perhaps we can QString::reserve() the result based on the size? + const AccelTree::PreNumber max = maximumPreNumber(); + + for(AccelTree::PreNumber i = 0; i <= max; ++i) + { + if(kind(i) == QXmlNodeModelIndex::Text) + { + if(isCompressed(i)) + result += CompressedWhitespace::decompress(data.value(i)); + else + result += data.value(i); + } + } + + return result; + } + default: + { + Q_ASSERT_X(false, Q_FUNC_INFO, + "A node type that doesn't exist in the XPath Data Model was encountered."); + return QString(); /* Dummy, silence compiler warning. */ + } + } +} + +QVariant AccelTree::typedValue(const QXmlNodeModelIndex &n) const +{ + return stringValue(n); +} + +bool AccelTree::hasPrefix(const QVector<QXmlName> &nbs, const QXmlName::PrefixCode prefix) +{ + const int size = nbs.size(); + + for(int i = 0; i < size; ++i) + { + if(nbs.at(i).prefix() == prefix) + return true; + } + + return false; +} + +ItemType::Ptr AccelTree::type(const QXmlNodeModelIndex &ni) const +{ + /* kind() is manually inlined here to avoid a virtual call. */ + return XPathHelper::typeFromKind(basicData.at(toPreNumber(ni)).kind()); +} + +Item::Iterator::Ptr AccelTree::sequencedTypedValue(const QXmlNodeModelIndex &n) const +{ + const PreNumber preNumber = toPreNumber(n); + + switch(kind(preNumber)) + { + case QXmlNodeModelIndex::Element: + /* Fallthrough. */ + case QXmlNodeModelIndex::Document: + /* Fallthrough. */ + case QXmlNodeModelIndex::Attribute: + return makeSingletonIterator(Item(UntypedAtomic::fromValue(stringValue(n)))); + + case QXmlNodeModelIndex::Text: + /* Fallthrough. */ + case QXmlNodeModelIndex::ProcessingInstruction: + /* Fallthrough. */ + case QXmlNodeModelIndex::Comment: + return makeSingletonIterator(Item(AtomicString::fromValue(stringValue(n)))); + default: + { + Q_ASSERT_X(false, Q_FUNC_INFO, + qPrintable(QString::fromLatin1("A node type that doesn't exist " + "in the XPath Data Model was encountered.").arg(kind(preNumber)))); + return Item::Iterator::Ptr(); /* Dummy, silence compiler warning. */ + } + } +} + +void AccelTree::copyNodeTo(const QXmlNodeModelIndex &node, + QAbstractXmlReceiver *const receiver, + const NodeCopySettings &settings) const +{ + /* This code piece can be seen as a customized version of + * QAbstractXmlReceiver::item/sendAsNode(). */ + Q_ASSERT(receiver); + Q_ASSERT(!node.isNull()); + + typedef QHash<QXmlName::PrefixCode, QXmlName::NamespaceCode> Binding; + QStack<Binding> outputted; + + switch(node.kind()) + { + case QXmlNodeModelIndex::Element: + { + outputted.push(Binding()); + + /* Add the namespace for our element name. */ + const QXmlName elementName(node.name()); + + receiver->startElement(elementName); + + if(!settings.testFlag(InheritNamespaces)) + receiver->namespaceBinding(QXmlName(StandardNamespaces::StopNamespaceInheritance, 0, + StandardPrefixes::StopNamespaceInheritance)); + + if(settings.testFlag(PreserveNamespaces)) + node.sendNamespaces(receiver); + else + { + /* Find the namespaces that we actually use and add them to outputted. These are drawn + * from the element name, and the node's attributes. */ + outputted.top().insert(elementName.prefix(), elementName.namespaceURI()); + + const QXmlNodeModelIndex::Iterator::Ptr attributes(iterate(node, QXmlNodeModelIndex::AxisAttribute)); + QXmlNodeModelIndex attr(attributes->next()); + + while(!attr.isNull()) + { + const QXmlName &attrName = attr.name(); + outputted.top().insert(attrName.prefix(), attrName.namespaceURI()); + attr = attributes->next(); + } + + Binding::const_iterator it(outputted.top().constBegin()); + const Binding::const_iterator end(outputted.top().constEnd()); + + for(; it != end; ++it) + receiver->namespaceBinding(QXmlName(it.value(), 0, it.key())); + } + + /* Send the attributes of the element. */ + { + QXmlNodeModelIndex::Iterator::Ptr attributes(node.iterate(QXmlNodeModelIndex::AxisAttribute)); + QXmlNodeModelIndex attribute(attributes->next()); + + while(!attribute.isNull()) + { + const QString &v = attribute.stringValue(); + receiver->attribute(attribute.name(), QStringRef(&v)); + attribute = attributes->next(); + } + } + + /* Send the children of the element. */ + copyChildren(node, receiver, settings); + + receiver->endElement(); + outputted.pop(); + break; + } + case QXmlNodeModelIndex::Document: + { + /* We need to intercept and grab the elements of the document node, such + * that we preserve/inherit preference applies to them. */ + receiver->startDocument(); + copyChildren(node, receiver, settings); + receiver->endDocument(); + break; + } + default: + receiver->item(node); + } + +} + +QSourceLocation AccelTree::sourceLocation(const QXmlNodeModelIndex &index) const +{ + const PreNumber key = toPreNumber(index); + if (sourcePositions.contains(key)) { + const QPair<qint64, qint64> position = sourcePositions.value(key); + return QSourceLocation(m_documentURI, position.first, position.second); + } else { + return QSourceLocation(); + } +} + +void AccelTree::copyChildren(const QXmlNodeModelIndex &node, + QAbstractXmlReceiver *const receiver, + const NodeCopySettings &settings) const +{ + QXmlNodeModelIndex::Iterator::Ptr children(node.iterate(QXmlNodeModelIndex::AxisChild)); + QXmlNodeModelIndex child(children->next()); + + while(!child.isNull()) + { + copyNodeTo(child, receiver, settings); + child = children->next(); + } +} + +QXmlNodeModelIndex AccelTree::elementById(const QXmlName &id) const +{ + const PreNumber pre = m_IDs.value(id.localName(), -1); + if(pre == -1) + return QXmlNodeModelIndex(); + else + return createIndex(pre); +} + +QVector<QXmlNodeModelIndex> AccelTree::nodesByIdref(const QXmlName &) const +{ + return QVector<QXmlNodeModelIndex>(); +} + +QT_END_NAMESPACE + diff --git a/src/xmlpatterns/acceltree/qacceltree_p.h b/src/xmlpatterns/acceltree/qacceltree_p.h new file mode 100644 index 0000000..0a9bf6c --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltree_p.h @@ -0,0 +1,404 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtXmlPatterns module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef Patternist_AccelTree_H +#define Patternist_AccelTree_H + +#include <QHash> +#include <QUrl> +#include <QVector> +#include <QXmlName> + +#include "qitem_p.h" +#include "qnamepool_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +namespace QPatternist +{ + template<bool> class AccelTreeBuilder; + + /** + * @short Stores an XML document using the XPath Accelerator scheme, also + * known as pre/post numbering. + * + * Working on this code will be destructive without a proper understanding of + * the Accelerator scheme, so do check out the links. We don't implement any form + * of staircase join, although that is only due to time constraints. + * + * @author Frans Englich <frans.englich@nokia.com> + * @see <a href="http://www.pathfinder-xquery.org/?q=research/xpath-accel">XPath + * Accelerator</a> + * @see <a href="http://www.pathfinder-xquery.org/files/xpath-accel.pdf">Accelerating + * XPath Location Steps, Torsten Grust</a> + * @see <a href="http://citeseer.ist.psu.edu/cache/papers/cs/29367/http:zSzzSzwww.informatik.uni-konstanz.dezSz~grustzSzfileszSzstaircase-join.pdf/grust03staircase.pdf">Staircase Join: + * Teach a Relational DBMS to Watch its (Axis) Steps</a> + * @see <a href="http://ftp.cwi.nl/CWIreports/INS/INS-E0510.pdf">Loop-lifted + * staircase join: from XPath to XQuery, Torsten Grust</a> + * @see <a href="http://englich.wordpress.com/2007/01/09/xmlstat/">xmlstat, Frans Englich</a> + * @see <a href"http://www.inf.uni-konstanz.de/dbis/publications/download/accelerating-locsteps.pdf">Accelerating + * XPath Evaluation in Any RDBMS, Torsten Grust</a> + */ + class Q_AUTOTEST_EXPORT AccelTree : public QAbstractXmlNodeModel + { + friend class AccelTreePrivate; + public: + using QAbstractXmlNodeModel::createIndex; + + typedef QExplicitlySharedDataPointer<AccelTree> Ptr; + typedef qint32 PreNumber; + typedef PreNumber PostNumber; + typedef qint8 Depth; + + AccelTree(const QUrl &docURI, const QUrl &bURI); + + /** + * @short Houses data for a node, and that all node kinds have. + * + * BasicNodeData is internal to the Accel tree implementation, and is + * only used by those classes. + * + * @author Frans Englich <frans.englich@nokia.com> + * @todo Can't m_kind be coded somewhere else? If m_name is invalid, + * its bits can be used to distinguish the node types that doesn't have + * names, and for elements, attributes and processing instructions, we need + * two bits, somewhere. Attributes and processing instructions can't have a + * size, is that of help? There's also certain rules for the names. For instance, + * a processing instruction will never have a prefix nor namespace. Neither + * will an attribute node have a default, non-empty namespace, right? + * @todo Compress text nodes, add general support for it in Patternist. + */ + class BasicNodeData + { + public: + /* No need to initialize the members. See AccelTreeBuilder. */ + inline BasicNodeData() + { + } + + inline BasicNodeData(const PreNumber aDepth, + const PreNumber aParent, + const QXmlNodeModelIndex::NodeKind k, + const PreNumber s, + const QXmlName n = QXmlName()) : m_parent(aParent) + , m_size(s) + , m_name(n) + , m_depth(aDepth) + , m_kind(k) + { + } + + inline Depth depth() const + { + return m_depth; + } + + inline PreNumber parent() const + { + return m_parent; + } + + /** + * @see AccelTree::size() + */ + inline PreNumber size() const + { + /* Remember that we use the m_size to signal compression if + * we're a text node. */ + if(m_kind == QXmlNodeModelIndex::Text) + return 0; + else + return m_size; + } + + inline void setSize(const PreNumber aSize) + { + m_size = aSize; + } + + inline QXmlNodeModelIndex::NodeKind kind() const + { + return m_kind; + } + + inline QXmlName name() const + { + return m_name; + } + + inline bool isCompressed() const + { + Q_ASSERT_X(m_kind == QXmlNodeModelIndex::Text, Q_FUNC_INFO, + "Currently, only text nodes are compressed."); + /* Note, we don't call size() here, since it has logic for text + * nodes. */ + return m_size == IsCompressed; + } + + private: + /** + * This is the pre number of the parent. + */ + PreNumber m_parent; + + /** + * This is the count of children this node has. + * + * In the case of a text node, which cannot have children, + * it is set to IsCompressed, if the content has been the result + * of CompressedWhitespace::compress(). If it's not compressed, + * it is zero. + */ + PreNumber m_size; + + /** + * For text nodes, and less importantly, comments, + * this variable is not used. + */ + QXmlName m_name; + + Depth m_depth; + + /** + * Technically it is sufficient with 7 bits. However, at least MSVC + * 2005 miscompiles it such that QXmlNodeModelIndex::Text becomes + * -64 instead of 64 with hilarious crashes as result. + * + * Fortunately this extra bit would be padded anyway. + */ + QXmlNodeModelIndex::NodeKind m_kind : 8; + }; + + virtual QUrl baseUri(const QXmlNodeModelIndex &ni) const; + virtual QUrl documentUri(const QXmlNodeModelIndex &ni) const; + virtual QXmlNodeModelIndex::NodeKind kind(const QXmlNodeModelIndex &ni) const; + virtual QXmlNodeModelIndex::DocumentOrder compareOrder(const QXmlNodeModelIndex &ni1, + const QXmlNodeModelIndex &ni2) const; + + /** + * @short Returns the root node. + * + * This function does not use @p n, so a default constructed + * QXmlNodeModelIndex may be passed. + */ + virtual QXmlNodeModelIndex root(const QXmlNodeModelIndex &n) const; + + virtual QXmlNodeModelIndex parent(const QXmlNodeModelIndex &ni) const; + virtual QXmlNodeModelIndex::Iterator::Ptr iterate(const QXmlNodeModelIndex &ni, + QXmlNodeModelIndex::Axis axis) const; + virtual QXmlName name(const QXmlNodeModelIndex &ni) const; + virtual QVector<QXmlName> namespaceBindings(const QXmlNodeModelIndex &n) const; + virtual void sendNamespaces(const QXmlNodeModelIndex &n, + QAbstractXmlReceiver *const receiver) const; + virtual QString stringValue(const QXmlNodeModelIndex &n) const; + virtual QVariant typedValue(const QXmlNodeModelIndex &n) const; + virtual Item::Iterator::Ptr sequencedTypedValue(const QXmlNodeModelIndex &n) const; + virtual ItemType::Ptr type(const QXmlNodeModelIndex &ni) const; + virtual QXmlNodeModelIndex elementById(const QXmlName &id) const; + virtual QVector<QXmlNodeModelIndex> nodesByIdref(const QXmlName &idref) const; + virtual void copyNodeTo(const QXmlNodeModelIndex &node, + QAbstractXmlReceiver *const receiver, + const NodeCopySettings &settings) const; + + friend class AccelTreeBuilder<false>; + friend class AccelTreeBuilder<true>; + + enum Constants + { + IsCompressed = 1 + }; + + /** + * The key is the pre number of an element, and the value is a vector + * containing the namespace declarations being declared on that + * element. Therefore, it does not reflect the namespaces being in + * scope for that element. For that, a walk along axis ancestor is + * necessary. + */ + QHash<PreNumber, QVector<QXmlName> > namespaces; + + /** + * Stores data for nodes. The QHash's value is the data of the processing instruction, and the + * content of a text node or comment. + */ + QHash<PreNumber, QString> data; + + QVector<BasicNodeData> basicData; + QHash<PreNumber, QPair<qint64, qint64> > sourcePositions; + + inline QUrl documentUri() const + { + return m_documentURI; + } + + inline QUrl baseUri() const + { + return m_baseURI; + } + + /** + * @short Returns @c true if the node identified by @p pre has child + * nodes(in the sense of the XDM), but also if it has namespace nodes, + * or attribute nodes. + */ + inline bool hasChildren(const PreNumber pre) const + { + return basicData.at(pre).size() > 0; + } + + /** + * @short Returns the parent node of @p pre. + * + * If @p pre parent doesn't have a parent node, the return value is + * undefined. + * + * @see hasParent() + */ + inline PreNumber parent(const PreNumber pre) const + { + return basicData.at(pre).parent(); + } + + inline bool hasParent(const PreNumber pre) const + { + return basicData.at(pre).depth() > 0; + } + + inline bool hasFollowingSibling(const PreNumber pre) const + { + return pre < maximumPreNumber(); + } + + inline PostNumber postNumber(const PreNumber pre) const + { + const BasicNodeData &b = basicData.at(pre); + return pre + b.size() - b.depth(); + } + + inline QXmlNodeModelIndex::NodeKind kind(const PreNumber pre) const + { + return basicData.at(pre).kind(); + } + + inline PreNumber maximumPreNumber() const + { + return basicData.count() - 1; + } + + inline PreNumber toPreNumber(const QXmlNodeModelIndex n) const + { + return n.data(); + } + + inline PreNumber size(const PreNumber pre) const + { + Q_ASSERT_X(basicData.at(pre).size() != -1, Q_FUNC_INFO, + "The size cannot be -1. That means an uninitialized value is attempted to be used."); + return basicData.at(pre).size(); + } + + inline Depth depth(const PreNumber pre) const + { + return basicData.at(pre).depth(); + } + + void printStats(const NamePool::Ptr &np) const; + + inline QXmlName name(const PreNumber pre) const + { + return basicData.at(pre).name(); + } + + inline bool isCompressed(const PreNumber pre) const + { + return basicData.at(pre).isCompressed(); + } + + static inline bool hasPrefix(const QVector<QXmlName> &nbs, const QXmlName::PrefixCode prefix); + + QUrl m_documentURI; + QUrl m_baseURI; + + protected: + virtual QXmlNodeModelIndex nextFromSimpleAxis(QAbstractXmlNodeModel::SimpleAxis, + const QXmlNodeModelIndex&) const; + virtual QVector<QXmlNodeModelIndex> attributes(const QXmlNodeModelIndex &element) const; + + private: + /** + * Returns the source location for the object with the given @p index. + */ + QSourceLocation sourceLocation(const QXmlNodeModelIndex &index) const; + + /** + * Copies the children of @p node to @p receiver. + */ + inline void copyChildren(const QXmlNodeModelIndex &node, + QAbstractXmlReceiver *const receiver, + const NodeCopySettings &settings) const; + + /** + * The key is the xml:id value, and the value is the element + * with that value. + */ + QHash<QXmlName::LocalNameCode, PreNumber> m_IDs; + }; +} + +Q_DECLARE_TYPEINFO(QPatternist::AccelTree::BasicNodeData, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/xmlpatterns/acceltree/qacceltreebuilder.cpp b/src/xmlpatterns/acceltree/qacceltreebuilder.cpp new file mode 100644 index 0000000..e752632 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltreebuilder.cpp @@ -0,0 +1,440 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtXmlPatterns module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/** + * @file + * @short This file is included by qacceltreebuilder_p.h. + * If you need includes in this file, put them in qacceltreebuilder_p.h, outside of the namespace. + */ + +template <bool FromDocument> +AccelTreeBuilder<FromDocument>::AccelTreeBuilder(const QUrl &docURI, + const QUrl &baseURI, + const NamePool::Ptr &np, + ReportContext *const context, + Features features) : m_preNumber(-1) + , m_isPreviousAtomic(false) + , m_hasCharacters(false) + , m_isCharactersCompressed(false) + , m_namePool(np) + , m_document(new AccelTree(docURI, baseURI)) + , m_skippedDocumentNodes(0) + , m_documentURI(docURI) + , m_context(context) + , m_features(features) +{ + Q_ASSERT(m_namePool); + + /* TODO Perhaps we can merge m_ancestors and m_size + * into one, and store a struct for the two instead? */ + m_ancestors.reserve(DefaultNodeStackSize); + m_ancestors.push(-1); + + m_size.reserve(DefaultNodeStackSize); + m_size.push(0); +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::startStructure() +{ + if(m_hasCharacters) + { + /* We create a node even if m_characters is empty. + * Remember that `text {""}' creates one text node + * with string value "". */ + + ++m_preNumber; + m_document->basicData.append(AccelTree::BasicNodeData(currentDepth(), + currentParent(), + QXmlNodeModelIndex::Text, + m_isCharactersCompressed ? AccelTree::IsCompressed : 0)); + m_document->data.insert(m_preNumber, m_characters); + ++m_size.top(); + + m_characters.clear(); /* We don't want it added twice. */ + m_hasCharacters = false; + + if(m_isCharactersCompressed) + m_isCharactersCompressed = false; + } +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::item(const Item &it) +{ + Q_ASSERT(it); + + if(it.isAtomicValue()) + { + if(m_isPreviousAtomic) + { + m_characters += QLatin1Char(' '); + m_characters += it.stringValue(); + } + else + { + m_isPreviousAtomic = true; + const QString sv(it.stringValue()); + + if(!sv.isEmpty()) + { + m_characters += sv; + m_hasCharacters = true; + } + } + } + else + sendAsNode(it); +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::startElement(const QXmlName &name) +{ + startElement(name, 1, 1); +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::startElement(const QXmlName &name, qint64 line, qint64 column) +{ + startStructure(); + + AccelTree::BasicNodeData data(currentDepth(), currentParent(), QXmlNodeModelIndex::Element, -1, name); + m_document->basicData.append(data); + if (m_features & SourceLocationsFeature) + m_document->sourcePositions.insert(m_document->maximumPreNumber(), qMakePair(line, column)); + + ++m_preNumber; + m_ancestors.push(m_preNumber); + + ++m_size.top(); + m_size.push(0); + + /* With node constructors, we can receive names for which we have no namespace + * constructors, such as in the query '<xs:space/>'. Since the 'xs' prefix has no + * NamespaceConstructor in this case, we synthesize the namespace. + * + * In case we're constructing from an XML document we avoid the call because + * although it's redundant, it's on extra virtual call for each element. */ + if(!FromDocument) + namespaceBinding(QXmlName(name.namespaceURI(), 0, name.prefix())); + + m_isPreviousAtomic = false; +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::endElement() +{ + startStructure(); + const AccelTree::PreNumber index = m_ancestors.pop(); + AccelTree::BasicNodeData &data = m_document->basicData[index]; + + /* Sub trees needs to be included in upper trees, so we add the count of this element + * to our parent. */ + m_size[m_size.count() - 2] += m_size.top(); + + data.setSize(m_size.pop()); + m_isPreviousAtomic = false; +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::attribute(const QXmlName &name, const QStringRef &value) +{ + /* Attributes adds a namespace binding, so lets synthesize one. + * + * We optimize by checking whether we have a namespace for which a binding would + * be generated. Happens relatively rarely. */ + if(name.hasPrefix()) + namespaceBinding(QXmlName(name.namespaceURI(), 0, name.prefix())); + + m_document->basicData.append(AccelTree::BasicNodeData(currentDepth(), currentParent(), QXmlNodeModelIndex::Attribute, 0, name)); + ++m_preNumber; + ++m_size.top(); + + m_isPreviousAtomic = false; + + if(name.namespaceURI() == StandardNamespaces::xml && name.localName() == StandardLocalNames::id) + { + const QString normalized(value.toString().simplified()); + + if(QXmlUtils::isNCName(normalized)) + { + const QXmlName::LocalNameCode id = m_namePool->allocateLocalName(normalized); + + const int oldSize = m_document->m_IDs.count(); + m_document->m_IDs.insert(id, currentParent()); + /* We don't run the value through m_attributeCompress here, because + * the likelyhood of it deing identical to another attribute is + * very small. */ + m_document->data.insert(m_preNumber, normalized); + + /** + * In the case that we're called for doc-available(), m_context is + * null, and we need to flag somehow that we failed to load this + * document. + */ + if(oldSize == m_document->m_IDs.count() && m_context) // TODO + { + Q_ASSERT(m_context); + m_context->error(QtXmlPatterns::tr("An %1-attribute with value %2 has already been declared.") + .arg(formatKeyword("xml:id"), + formatData(normalized)), + FromDocument ? ReportContext::FODC0002 : ReportContext::XQDY0091, + this); + } + } + else if(m_context) // TODO + { + Q_ASSERT(m_context); + + /* If we're building from an XML Document(e.g, we're fed from QXmlStreamReader, we raise FODC0002, + * otherwise XQDY0091. */ + m_context->error(QtXmlPatterns::tr("An %1-attribute must have a " + "valid %2 as value, which %3 isn't.").arg(formatKeyword("xml:id"), + formatType(m_namePool, BuiltinTypes::xsNCName), + formatData(value.toString())), + FromDocument ? ReportContext::FODC0002 : ReportContext::XQDY0091, + this); + } + } + else + m_document->data.insert(m_preNumber, *m_attributeCompress.insert(value.toString())); +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::characters(const QStringRef &ch) +{ + + /* If a text node constructor appears by itself, a node needs to + * be created. Therefore, we set m_hasCharacters + * if we're the only node. + * However, if the text node appears as a child of a document or element + * node it is discarded if it's empty. + */ + if(m_hasCharacters && m_isCharactersCompressed) + { + m_characters = CompressedWhitespace::decompress(m_characters); + m_isCharactersCompressed = false; + } + + m_characters += ch; + + m_isPreviousAtomic = false; + m_hasCharacters = !m_characters.isEmpty() || m_preNumber == -1; /* -1 is our start value. */ +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::whitespaceOnly(const QStringRef &ch) +{ + Q_ASSERT(!ch.isEmpty()); + Q_ASSERT(ch.toString().trimmed().isEmpty()); + + /* This gets problematic due to how QXmlStreamReader works(which + * is the only one we get whitespaceOnly() events from). Namely, text intermingled + * with CDATA gets reported as individual Characters events, and + * QXmlStreamReader::isWhitespace() can return differently for each of those. However, + * it will occur very rarely, so this workaround of 1) mistakenly compressing 2) decompressing 3) + * appending, will happen infrequently. + */ + if(m_hasCharacters) + { + if(m_isCharactersCompressed) + { + m_characters = CompressedWhitespace::decompress(m_characters); + m_isCharactersCompressed = false; + } + + m_characters.append(ch.toString()); + } + else + { + /* We haven't received a text node previously. */ + m_characters = CompressedWhitespace::compress(ch); + m_isCharactersCompressed = true; + m_isPreviousAtomic = false; + m_hasCharacters = true; + } +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::processingInstruction(const QXmlName &target, + const QString &data) +{ + startStructure(); + ++m_preNumber; + m_document->data.insert(m_preNumber, data); + + m_document->basicData.append(AccelTree::BasicNodeData(currentDepth(), + currentParent(), + QXmlNodeModelIndex::ProcessingInstruction, + 0, + target)); + ++m_size.top(); + m_isPreviousAtomic = false; +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::comment(const QString &content) +{ + startStructure(); + m_document->basicData.append(AccelTree::BasicNodeData(currentDepth(), currentParent(), QXmlNodeModelIndex::Comment, 0)); + ++m_preNumber; + m_document->data.insert(m_preNumber, content); + ++m_size.top(); +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::namespaceBinding(const QXmlName &nb) +{ + /* Note, because attribute() sometimes generate namespaceBinding() calls, this function + * can be called after attributes, in contrast to what the class documentation says. This is ok, + * as long as we're not dealing with public API. */ + + /* If we've received attributes, it means the element's size have changed and m_preNumber have advanced, + * so "reverse back" to the actual element. */ + const AccelTree::PreNumber pn = m_preNumber - m_size.top(); + + QVector<QXmlName> &nss = m_document->namespaces[pn]; + + /* "xml" hasn't been declared for each node, AccelTree::namespaceBindings() adds it, so avoid it + * such that we don't get duplicates. */ + if(nb.prefix() == StandardPrefixes::xml) + return; + + /* If we already have the binding, skip it. */ + const int len = nss.count(); + for(int i = 0; i < len; ++i) + { + if(nss.at(i).prefix() == nb.prefix()) + return; + } + + nss.append(nb); +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::startDocument() +{ + /* If we have already received nodes, we can't add a document node. */ + if(m_preNumber == -1) /* -1 is our start value. */ + { + m_size.push(0); + m_document->basicData.append(AccelTree::BasicNodeData(0, -1, QXmlNodeModelIndex::Document, -1)); + ++m_preNumber; + m_ancestors.push(m_preNumber); + } + else + ++m_skippedDocumentNodes; + + m_isPreviousAtomic = false; +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::endDocument() +{ + if(m_skippedDocumentNodes == 0) + { + /* Create text nodes, if we've received any. We do this only if we're the + * top node because if we're getting this event as being a child of an element, + * text nodes or atomic values can appear after us, and which must get + * merged with the previous text. + * + * We call startStructure() before we pop the ancestor, such that the text node becomes + * a child of this document node. */ + startStructure(); + + m_document->basicData.first().setSize(m_size.pop()); + m_ancestors.pop(); + } + else + --m_skippedDocumentNodes; + + m_isPreviousAtomic = false; +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::atomicValue(const QVariant &value) +{ + Q_UNUSED(value); + // TODO +} + +template <bool FromDocument> +QAbstractXmlNodeModel::Ptr AccelTreeBuilder<FromDocument>::builtDocument() +{ + /* Create a text node, if we have received text in some way. */ + startStructure(); + m_document->printStats(m_namePool); + + return m_document; +} + +template <bool FromDocument> +NodeBuilder::Ptr AccelTreeBuilder<FromDocument>::create(const QUrl &baseURI) const +{ + Q_UNUSED(baseURI); + return NodeBuilder::Ptr(new AccelTreeBuilder(QUrl(), baseURI, m_namePool, m_context)); +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::startOfSequence() +{ +} + +template <bool FromDocument> +void AccelTreeBuilder<FromDocument>::endOfSequence() +{ +} + +template <bool FromDocument> +const SourceLocationReflection *AccelTreeBuilder<FromDocument>::actualReflection() const +{ + return this; +} + +template <bool FromDocument> +QSourceLocation AccelTreeBuilder<FromDocument>::sourceLocation() const +{ + if(m_documentURI.isEmpty()) + return QSourceLocation(QUrl(QLatin1String("AnonymousNodeTree"))); + else + return QSourceLocation(m_documentURI); +} + diff --git a/src/xmlpatterns/acceltree/qacceltreebuilder_p.h b/src/xmlpatterns/acceltree/qacceltreebuilder_p.h new file mode 100644 index 0000000..747099f --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltreebuilder_p.h @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtXmlPatterns module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef Patternist_AccelTreeBuilder_H +#define Patternist_AccelTreeBuilder_H + +#include <QSet> +#include <QStack> + +#include "private/qxmlutils_p.h" +#include "qacceltree_p.h" +#include "qbuiltintypes_p.h" +#include "qcompressedwhitespace_p.h" +#include "qnamepool_p.h" +#include "qnodebuilder_p.h" +#include "qreportcontext_p.h" +#include "qsourcelocationreflection_p.h" +#include "qpatternistlocale_p.h" +#include <QtDebug> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +namespace QPatternist +{ + /** + * @short Builds an AccelTree from a stream of XML/Item events + * received through the NodeBuilder interface. + * + * If FromDocument is @c true, it is assumed that AccelTreeBuilder is fed + * events from an XML document, otherwise it is assumed the events + * are from node constructor expressions. + * + * @author Frans Englich <frans.englich@nokia.com> + */ + template<bool FromDocument> + class AccelTreeBuilder : public NodeBuilder + , public SourceLocationReflection + { + public: + typedef QExplicitlySharedDataPointer<AccelTreeBuilder> Ptr; + + /** + * Describes the memory relevant features the builder shall support. + */ + enum Feature + { + NoneFeature, ///< No special features are enabled. + SourceLocationsFeature = 1 ///< The accel tree builder will store source locations for each start element. + }; + Q_DECLARE_FLAGS(Features, Feature) + + /** + * @param context may be @c null. + */ + AccelTreeBuilder(const QUrl &docURI, + const QUrl &baseURI, + const NamePool::Ptr &np, + ReportContext *const context, + Features features = NoneFeature); + virtual void startDocument(); + virtual void endDocument(); + virtual void startElement(const QXmlName &name); + void startElement(const QXmlName &name, qint64 line, qint64 column); + virtual void endElement(); + virtual void attribute(const QXmlName &name, const QStringRef &value); + virtual void characters(const QStringRef &ch); + virtual void whitespaceOnly(const QStringRef &ch); + virtual void processingInstruction(const QXmlName &target, + const QString &data); + virtual void namespaceBinding(const QXmlName &nb); + virtual void comment(const QString &content); + virtual void item(const Item &it); + + virtual QAbstractXmlNodeModel::Ptr builtDocument(); + virtual NodeBuilder::Ptr create(const QUrl &baseURI) const; + virtual void startOfSequence(); + virtual void endOfSequence(); + + inline AccelTree::Ptr builtDocument() const + { + return m_document; + } + + virtual void atomicValue(const QVariant &value); + + virtual const SourceLocationReflection *actualReflection() const; + virtual QSourceLocation sourceLocation() const; + + private: + inline void startStructure(); + + inline AccelTree::PreNumber currentDepth() const + { + return m_ancestors.count() -1; + } + + inline AccelTree::PreNumber currentParent() const + { + return m_ancestors.isEmpty() ? -1 : m_ancestors.top(); + } + + enum Constants + { + DefaultNodeStackSize = 10, + SizeIsEmpty = 0 + }; + + AccelTree::PreNumber m_preNumber; + bool m_isPreviousAtomic; + bool m_hasCharacters; + /** + * Whether m_characters has been run through + * CompressedWhitespace::compress(). + */ + bool m_isCharactersCompressed; + QString m_characters; + NamePool::Ptr m_namePool; + AccelTree::Ptr m_document; + QStack<AccelTree::PreNumber> m_ancestors; + QStack<AccelTree::PreNumber> m_size; + + /** If we have already commenced a document, we don't want to + * add more document nodes. We keep track of them with this + * counter, which ensures that startDocument() and endDocument() + * are skipped consistently. */ + AccelTree::PreNumber m_skippedDocumentNodes; + + /** + * All attribute values goes through this set such that we store only + * one QString for identical attribute values. + */ + QSet<QString> m_attributeCompress; + const QUrl m_documentURI; + /** + * We don't store a reference pointer here because then we get a + * circular reference with GenericDynamicContext, when it stores us as + * a member. + */ + ReportContext *const m_context; + + Features m_features; + }; + + Q_DECLARE_OPERATORS_FOR_FLAGS(AccelTreeBuilder<true>::Features) + Q_DECLARE_OPERATORS_FOR_FLAGS(AccelTreeBuilder<false>::Features) + +#include "qacceltreebuilder.cpp" +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/xmlpatterns/acceltree/qacceltreeresourceloader.cpp b/src/xmlpatterns/acceltree/qacceltreeresourceloader.cpp new file mode 100644 index 0000000..4fbfb65 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltreeresourceloader.cpp @@ -0,0 +1,441 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtXmlPatterns module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QFile> +#include <QtCore/QTextCodec> +#include <QtCore/QTimer> +#include <QtCore/QXmlStreamReader> + +#include <QtNetwork/QNetworkRequest> + +#include "qatomicstring_p.h" +#include "qautoptr_p.h" +#include "qcommonsequencetypes_p.h" + +#include "qacceltreeresourceloader_p.h" + +QT_BEGIN_NAMESPACE + +using namespace QPatternist; + +AccelTreeResourceLoader::AccelTreeResourceLoader(const NamePool::Ptr &np, + const NetworkAccessDelegator::Ptr &manager, + AccelTreeBuilder<true>::Features features) + : m_namePool(np) + , m_networkAccessDelegator(manager) + , m_features(features) +{ + Q_ASSERT(m_namePool); + Q_ASSERT(m_networkAccessDelegator); +} + +bool AccelTreeResourceLoader::retrieveDocument(const QUrl &uri, + const ReportContext::Ptr &context) +{ + Q_ASSERT(uri.isValid()); + AccelTreeBuilder<true> builder(uri, uri, m_namePool, context.data(), m_features); + + const AutoPtr<QNetworkReply> reply(load(uri, m_networkAccessDelegator, context)); + + if(!reply) + return false; + + bool success = false; + success = streamToReceiver(reply.data(), &builder, m_namePool, context, uri); + + m_loadedDocuments.insert(uri, builder.builtDocument()); + return success; +} + +bool AccelTreeResourceLoader::retrieveDocument(QIODevice *source, const QUrl &documentUri, const ReportContext::Ptr &context) +{ + Q_ASSERT(source); + Q_ASSERT(source->isReadable()); + Q_ASSERT(documentUri.isValid()); + + AccelTreeBuilder<true> builder(documentUri, documentUri, m_namePool, context.data(), m_features); + + bool success = false; + success = streamToReceiver(source, &builder, m_namePool, context, documentUri); + + m_loadedDocuments.insert(documentUri, builder.builtDocument()); + + return success; +} + +QNetworkReply *AccelTreeResourceLoader::load(const QUrl &uri, + const NetworkAccessDelegator::Ptr &networkDelegator, + const ReportContext::Ptr &context, ErrorHandling errorHandling) +{ + return load(uri, + networkDelegator->managerFor(uri), + context, errorHandling); +} + +QNetworkReply *AccelTreeResourceLoader::load(const QUrl &uri, + QNetworkAccessManager *const networkManager, + const ReportContext::Ptr &context, ErrorHandling errorHandling) + +{ + Q_ASSERT(networkManager); + Q_ASSERT(uri.isValid()); + + NetworkLoop networkLoop; + + QNetworkRequest request(uri); + QNetworkReply *const reply = networkManager->get(request); + networkLoop.connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(error(QNetworkReply::NetworkError))); + networkLoop.connect(reply, SIGNAL(finished()), SLOT(finished())); + + if(networkLoop.exec(QEventLoop::ExcludeUserInputEvents)) + { + const QString errorMessage(escape(reply->errorString())); + + /* Note, we delete reply before we exit this function with error(). */ + delete reply; + + const QSourceLocation location(uri); + + if(context && (errorHandling == FailOnError)) + context->error(errorMessage, ReportContext::FODC0002, location); + + return 0; + } + else + return reply; +} + +bool AccelTreeResourceLoader::streamToReceiver(QIODevice *const dev, + AccelTreeBuilder<true> *const receiver, + const NamePool::Ptr &np, + const ReportContext::Ptr &context, + const QUrl &uri) +{ + Q_ASSERT(dev); + Q_ASSERT(receiver); + Q_ASSERT(np); + + QXmlStreamReader reader(dev); + + /* Optimize: change NamePool to take QStringRef such that we don't have to call toString() below. That + * will save us a gazillion of temporary QStrings. */ + + while(!reader.atEnd()) + { + reader.readNext(); + + switch(reader.tokenType()) + { + case QXmlStreamReader::StartElement: + { + /* Send the name. */ + receiver->startElement(np->allocateQName(reader.namespaceUri().toString(), reader.name().toString(), + reader.prefix().toString()), reader.lineNumber(), reader.columnNumber()); + + /* Send namespace declarations. */ + const QXmlStreamNamespaceDeclarations &nss = reader.namespaceDeclarations(); + + /* The far most common case, is for it to be empty. */ + if(!nss.isEmpty()) + { + const int len = nss.size(); + + for(int i = 0; i < len; ++i) + { + const QXmlStreamNamespaceDeclaration &ns = nss.at(i); + receiver->namespaceBinding(np->allocateBinding(ns.prefix().toString(), ns.namespaceUri().toString())); + } + } + + /* Send attributes. */ + const QXmlStreamAttributes &attrs = reader.attributes(); + const int len = attrs.size(); + + for(int i = 0; i < len; ++i) + { + const QXmlStreamAttribute &attr = attrs.at(i); + + receiver->attribute(np->allocateQName(attr.namespaceUri().toString(), attr.name().toString(), + attr.prefix().toString()), + attr.value()); + } + + continue; + } + case QXmlStreamReader::EndElement: + { + receiver->endElement(); + continue; + } + case QXmlStreamReader::Characters: + { + if(reader.isWhitespace()) + receiver->whitespaceOnly(reader.text()); + else + receiver->characters(reader.text()); + + continue; + } + case QXmlStreamReader::Comment: + { + receiver->comment(reader.text().toString()); + continue; + } + case QXmlStreamReader::ProcessingInstruction: + { + receiver->processingInstruction(np->allocateQName(QString(), reader.processingInstructionTarget().toString()), + reader.processingInstructionData().toString()); + continue; + } + case QXmlStreamReader::StartDocument: + { + receiver->startDocument(); + continue; + } + case QXmlStreamReader::EndDocument: + { + receiver->endDocument(); + continue; + } + case QXmlStreamReader::EntityReference: + /* Fallthrough. */ + case QXmlStreamReader::DTD: + { + /* We just ignore any DTD and entity references. */ + continue; + } + case QXmlStreamReader::Invalid: + { + if(context) + context->error(escape(reader.errorString()), ReportContext::FODC0002, QSourceLocation(uri, reader.lineNumber(), reader.columnNumber())); + + return false; + } + case QXmlStreamReader::NoToken: + { + Q_ASSERT_X(false, Q_FUNC_INFO, + "This token is never expected to be received."); + return false; + } + } + } + + return true; +} + +Item AccelTreeResourceLoader::openDocument(const QUrl &uri, + const ReportContext::Ptr &context) +{ + const AccelTree::Ptr doc(m_loadedDocuments.value(uri)); + + if(doc) + return doc->root(QXmlNodeModelIndex()); /* Pass in dummy object. We know AccelTree doesn't use it. */ + else + { + if(retrieveDocument(uri, context)) + return m_loadedDocuments.value(uri)->root(QXmlNodeModelIndex()); /* Pass in dummy object. We know AccelTree doesn't use it. */ + else + return Item(); + } +} + +Item AccelTreeResourceLoader::openDocument(QIODevice *source, const QUrl &documentUri, + const ReportContext::Ptr &context) +{ + const AccelTree::Ptr doc(m_loadedDocuments.value(documentUri)); + + if(doc) + return doc->root(QXmlNodeModelIndex()); /* Pass in dummy object. We know AccelTree doesn't use it. */ + else + { + if(retrieveDocument(source, documentUri, context)) + return m_loadedDocuments.value(documentUri)->root(QXmlNodeModelIndex()); /* Pass in dummy object. We know AccelTree doesn't use it. */ + else + return Item(); + } +} + +SequenceType::Ptr AccelTreeResourceLoader::announceDocument(const QUrl &uri, const Usage) +{ + // TODO deal with the usage thingy + Q_ASSERT(uri.isValid()); + Q_ASSERT(!uri.isRelative()); + Q_UNUSED(uri); /* Needed when compiling in release mode. */ + + return CommonSequenceTypes::ZeroOrOneDocumentNode; +} + +bool AccelTreeResourceLoader::isDocumentAvailable(const QUrl &uri) +{ + return retrieveDocument(uri, ReportContext::Ptr()); +} + +static inline uint qHash(const QPair<QUrl, QString> &desc) +{ + /* Probably a lousy hash. */ + return qHash(desc.first) + qHash(desc.second); +} + +bool AccelTreeResourceLoader::retrieveUnparsedText(const QUrl &uri, + const QString &encoding, + const ReportContext::Ptr &context, + const SourceLocationReflection *const where) +{ + const AutoPtr<QNetworkReply> reply(load(uri, m_networkAccessDelegator, context)); + + if(!reply) + return false; + + const QTextCodec * codec; + if(encoding.isEmpty()) + { + /* XSL Transformations (XSLT) Version 2.0 16.2 Reading Text Files: + * + * "if the media type of the resource is text/xml or application/xml + * (see [RFC2376]), or if it matches the conventions text/\*+xml or + * application/\*+xml (see [RFC3023] and/or its successors), then the + * encoding is recognized as specified in [XML 1.0]" + */ + codec = QTextCodec::codecForMib(106); + } + else + { + codec = QTextCodec::codecForName(encoding.toLatin1()); + if(codec && context) + { + context->error(QtXmlPatterns::tr("%1 is an unsupported encoding.").arg(formatURI(encoding)), + ReportContext::XTDE1190, + where); + } + else + return false; + } + + QTextCodec::ConverterState converterState; + const QByteArray inData(reply->readAll()); + const QString result(codec->toUnicode(inData.constData(), inData.length(), &converterState)); + + if(converterState.invalidChars) + { + if(context) + { + context->error(QtXmlPatterns::tr("%1 contains octets which are disallowed in " + "the requested encoding %2.").arg(formatURI(uri), + formatURI(encoding)), + ReportContext::XTDE1190, + where); + } + else + return false; + } + + const int len = result.length(); + /* This code is a candidate for threading. Divide and conqueror. */ + for(int i = 0; i < len; ++i) + { + if(!QXmlUtils::isChar(result.at(i))) + { + if(context) + { + context->error(QtXmlPatterns::tr("The codepoint %1, occurring in %2 using encoding %3, " + "is an invalid XML character.").arg(formatData(result.at(i)), + formatURI(uri), + formatURI(encoding)), + ReportContext::XTDE1190, + where); + } + else + return false; + } + } + + m_unparsedTexts.insert(qMakePair(uri, encoding), result); + return true; +} + +bool AccelTreeResourceLoader::isUnparsedTextAvailable(const QUrl &uri, + const QString &encoding) +{ + return retrieveUnparsedText(uri, encoding, ReportContext::Ptr(), 0); +} + +Item AccelTreeResourceLoader::openUnparsedText(const QUrl &uri, + const QString &encoding, + const ReportContext::Ptr &context, + const SourceLocationReflection *const where) +{ + const QString &text = m_unparsedTexts.value(qMakePair(uri, encoding)); + + if(text.isNull()) + { + if(retrieveUnparsedText(uri, encoding, context, where)) + return openUnparsedText(uri, encoding, context, where); + else + return Item(); + } + else + return AtomicString::fromValue(text); +} + +QSet<QUrl> AccelTreeResourceLoader::deviceURIs() const +{ + QHash<QUrl, AccelTree::Ptr>::const_iterator it(m_loadedDocuments.constBegin()); + const QHash<QUrl, AccelTree::Ptr>::const_iterator end(m_loadedDocuments.constEnd()); + QSet<QUrl> retval; + + while (it != end) + { + if(it.key().toString().startsWith(QLatin1String("tag:trolltech.com,2007:QtXmlPatterns:QIODeviceVariable:"))) + retval.insert(it.key()); + + ++it; + } + + return retval; +} + +void AccelTreeResourceLoader::clear(const QUrl &uri) +{ + m_loadedDocuments.remove(uri); +} + +QT_END_NAMESPACE + diff --git a/src/xmlpatterns/acceltree/qacceltreeresourceloader_p.h b/src/xmlpatterns/acceltree/qacceltreeresourceloader_p.h new file mode 100644 index 0000000..3768ebe --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltreeresourceloader_p.h @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtXmlPatterns module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef Patternist_AccelTreeResourceLoader_H +#define Patternist_AccelTreeResourceLoader_H + +#include <QtCore/QEventLoop> +#include <QtNetwork/QNetworkReply> + +#include "qabstractxmlreceiver.h" +#include "qacceltree_p.h" +#include "qacceltreebuilder_p.h" +#include "qdeviceresourceloader_p.h" +#include "qnamepool_p.h" +#include "qnetworkaccessdelegator_p.h" +#include "qreportcontext_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QIODevice; + +namespace QPatternist +{ + /** + * @short An helper class which enables QNetworkAccessManager + * to be used in a blocking manner. + * + * @see AccelTreeResourceLoader::load() + * @author Frans Englich <frans.englich@nokia.com> + */ + class NetworkLoop : public QEventLoop + { + Q_OBJECT + public: + NetworkLoop() : m_hasReceivedError(false) + { + } + + public Q_SLOTS: + void error(QNetworkReply::NetworkError code) + { + Q_UNUSED(code); + m_hasReceivedError = true; + exit(1); + } + + void finished() + { + if(m_hasReceivedError) + exit(1); + else + exit(0); + } + private: + bool m_hasReceivedError; + }; + + /** + * @short Handles requests for documents, and instantiates + * them as AccelTree instances. + * + * @author Frans Englich <frans.englich@nokia.com> + */ + class Q_AUTOTEST_EXPORT AccelTreeResourceLoader : public DeviceResourceLoader + { + public: + /** + * Describes the behaviour of the resource loader in case of an + * error. + */ + enum ErrorHandling + { + FailOnError, ///< The resource loader will report the error via the report context. + ContinueOnError ///< The resource loader will report no error and return an empty QNetworkReply. + }; + + /** + * AccelTreeResourceLoader does not own @p context. + */ + AccelTreeResourceLoader(const NamePool::Ptr &np, + const NetworkAccessDelegator::Ptr &networkDelegator, AccelTreeBuilder<true>::Features = AccelTreeBuilder<true>::NoneFeature); + + virtual Item openDocument(const QUrl &uri, + const ReportContext::Ptr &context); + virtual Item openDocument(QIODevice *source, const QUrl &documentUri, + const ReportContext::Ptr &context); + virtual SequenceType::Ptr announceDocument(const QUrl &uri, const Usage usageHint); + virtual bool isDocumentAvailable(const QUrl &uri); + + virtual bool isUnparsedTextAvailable(const QUrl &uri, + const QString &encoding); + + virtual Item openUnparsedText(const QUrl &uri, + const QString &encoding, + const ReportContext::Ptr &context, + const SourceLocationReflection *const where); + + /** + * @short Helper function that do NetworkAccessDelegator::get(), but + * does it blocked. + * + * The returned QNetworkReply has emitted QNetworkReply::finished(). + * + * The caller owns the return QIODevice instance. + * + * @p context may be @c null or valid. If @c null, no error reporting + * is done and @c null is returned. + * + * @see NetworkAccessDelegator + */ + static QNetworkReply *load(const QUrl &uri, + QNetworkAccessManager *const networkManager, + const ReportContext::Ptr &context, ErrorHandling handling = FailOnError); + + /** + * @overload + */ + static QNetworkReply *load(const QUrl &uri, + const NetworkAccessDelegator::Ptr &networkDelegator, + const ReportContext::Ptr &context, ErrorHandling handling = FailOnError); + + /** + * @short Returns the URIs this AccelTreeResourceLoader has loaded + * which are for devices through variable bindings. + */ + virtual QSet<QUrl> deviceURIs() const; + + virtual void clear(const QUrl &uri); + + private: + static bool streamToReceiver(QIODevice *const dev, + AccelTreeBuilder<true> *const receiver, + const NamePool::Ptr &np, + const ReportContext::Ptr &context, + const QUrl &uri); + bool retrieveDocument(const QUrl &uri, + const ReportContext::Ptr &context); + bool retrieveDocument(QIODevice *source, const QUrl &documentUri, + const ReportContext::Ptr &context); + /** + * If @p context is @c null, no error reporting should be done. + */ + bool retrieveUnparsedText(const QUrl &uri, + const QString &encoding, + const ReportContext::Ptr &context, + const SourceLocationReflection *const where); + + QHash<QUrl, AccelTree::Ptr> m_loadedDocuments; + const NamePool::Ptr m_namePool; + const NetworkAccessDelegator::Ptr m_networkAccessDelegator; + QHash<QPair<QUrl, QString>, QString> m_unparsedTexts; + AccelTreeBuilder<true>::Features m_features; + }; +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/xmlpatterns/acceltree/qcompressedwhitespace.cpp b/src/xmlpatterns/acceltree/qcompressedwhitespace.cpp new file mode 100644 index 0000000..16c116c --- /dev/null +++ b/src/xmlpatterns/acceltree/qcompressedwhitespace.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtXmlPatterns module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QString> + +#include "qcompressedwhitespace_p.h" + +QT_BEGIN_NAMESPACE + +using namespace QPatternist; + +CompressedWhitespace::CharIdentifier CompressedWhitespace::toIdentifier(const QChar ch) +{ + switch(ch.unicode()) + { + case ' ': + return Space; + case '\n': + return LF; + case '\r': + return CR; + case '\t': + return Tab; + default: + { + Q_ASSERT_X(false, Q_FUNC_INFO, + "The caller must guarantee only whitespace is passed."); + return Tab; + } + } +} + +bool CompressedWhitespace::isEven(const int number) +{ + Q_ASSERT(number >= 0); + return number % 2 == 0; +} + +quint8 CompressedWhitespace::toCompressedChar(const QChar ch, const int len) +{ + Q_ASSERT(len > 0); + Q_ASSERT(len <= MaxCharCount); + + return len + toIdentifier(ch); +} + +QChar CompressedWhitespace::toChar(const CharIdentifier id) +{ + switch(id) + { + case Space: return QLatin1Char(' '); + case CR: return QLatin1Char('\r'); + case LF: return QLatin1Char('\n'); + case Tab: return QLatin1Char('\t'); + default: + { + Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected input"); + return QChar(); + } + } +} + +QString CompressedWhitespace::compress(const QStringRef &input) +{ + Q_ASSERT(!isEven(1) && isEven(0) && isEven(2)); + Q_ASSERT(!input.isEmpty()); + + QString result; + const int len = input.length(); + + /* The amount of compressed characters. For instance, if input is + * four spaces followed by one tab, compressedChars will be 2, and the resulting + * QString will have a length of 1, two compressedChars stored in one QChar. */ + int compressedChars = 0; + + for(int i = 0; i < len; ++i) + { + const QChar c(input.at(i)); + + int start = i; + + while(true) + { + if(i + 1 == input.length() || input.at(i + 1) != c) + break; + else + ++i; + } + + /* The length of subsequent whitespace characters in the input. */ + int wsLen = (i - start) + 1; + + /* We might get a sequence of whitespace that is so long, that we can't + * store it in one unit/byte. In that case we chop it into as many subsequent + * ones that is needed. */ + while(true) + { + const int unitLength = qMin(wsLen, int(MaxCharCount)); + wsLen -= unitLength; + + ushort resultCP = toCompressedChar(c, unitLength); + + if(isEven(compressedChars)) + result += QChar(resultCP); + else + { + resultCP = resultCP << 8; + resultCP |= result.at(result.size() - 1).unicode(); + result[result.size() - 1] = resultCP; + } + + ++compressedChars; + + if(wsLen == 0) + break; + } + } + + return result; +} + +QString CompressedWhitespace::decompress(const QString &input) +{ + Q_ASSERT(!input.isEmpty()); + const int len = input.length() * 2; + QString retval; + + for(int i = 0; i < len; ++i) + { + ushort cp = input.at(i / 2).unicode(); + + if(isEven(i)) + cp &= Lower8Bits; + else + { + cp = cp >> 8; + + if(cp == 0) + return retval; + } + + const quint8 wsLen = cp & Lower6Bits; + const quint8 id = cp & UpperTwoBits; + + /* Resize retval, and fill in on the top. */ + const int oldSize = retval.size(); + const int newSize = retval.size() + wsLen; + retval.resize(newSize); + const QChar ch(toChar(CharIdentifier(id))); + + for(int f = oldSize; f < newSize; ++f) + retval[f] = ch; + } + + return retval; +} + +QT_END_NAMESPACE + diff --git a/src/xmlpatterns/acceltree/qcompressedwhitespace_p.h b/src/xmlpatterns/acceltree/qcompressedwhitespace_p.h new file mode 100644 index 0000000..859811d --- /dev/null +++ b/src/xmlpatterns/acceltree/qcompressedwhitespace_p.h @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtXmlPatterns module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef Patternist_CompressedWhitespace_H +#define Patternist_CompressedWhitespace_H + +#include <QtGlobal> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QChar; +class QString; +class QStringRef; + +namespace QPatternist +{ + /** + * @short A compression facility for whitespace nodes. + * + * CompressedWhitespace compresses and decompresses strings that consists of + * whitespace only, and do so with a scheme that is designed to do this + * specialized task in an efficient way. The approach is simple: each + * sequence of equal whitespace in the input gets coded into one byte, + * where the first two bits signals the type, CharIdentifier, and the + * remininding six bits is the count. + * + * For instance, this scheme manages to compress a sequence of spaces + * followed by a new line into 16 bits(one QChar), and QString stores + * strings of one QChar quite efficiently, by avoiding a heap allocation. + * + * There is no way to tell whether a QString is compressed or not. + * + * The compression scheme originates from Saxon, by Michael Kay. + * + * @author Frans Englich <frans.englich@nokia.com> + */ + class Q_AUTOTEST_EXPORT CompressedWhitespace + { + public: + /** + * @short Compresses @p input into a compressed format, returned + * as a QString. + * + * The caller guarantees that input is not empty + * and consists only of whitespace. + * + * The returned format is opaque. There is no way to find out + * whether a QString contains compressed data or not. + * + * @see decompress() + */ + static QString compress(const QStringRef &input); + + /** + * @short Decompresses @p input into a usual QString. + * + * @p input must be a QString as per returned from compress(). + * + * @see compress() + */ + static QString decompress(const QString &input); + + private: + /** + * We use the two upper bits for communicating what space it is. + */ + enum CharIdentifier + { + Space = 0x0, + + /** + * 0xA, \\r + * + * Binary: 10000000 + */ + CR = 0x80, + + /** + * 0xD, \\n + * + * Binary: 01000000 + */ + LF = 0x40, + + /** + * Binary: 11000000 + */ + Tab = 0xC0 + }; + + enum Constants + { + /* We can at maximum store this many consecutive characters + * of one type. We use 6 bits for the count. */ + MaxCharCount = (1 << 6) - 1, + + /** + * Binary: 11111111 + */ + Lower8Bits = (1 << 8) - 1, + + /** + * Binary: 111111 + */ + Lower6Bits = (1 << 6) - 1, + + /* + * Binary: 11000000 + */ + UpperTwoBits = 3 << 6 + }; + + static inline CharIdentifier toIdentifier(const QChar ch); + + static inline quint8 toCompressedChar(const QChar ch, const int len); + static inline QChar toChar(const CharIdentifier id); + + /** + * @short Returns @c true if @p number is an even number, otherwise + * @c false. + */ + static inline bool isEven(const int number); + + /** + * @short This class can only be used via its static members. + */ + inline CompressedWhitespace(); + Q_DISABLE_COPY(CompressedWhitespace) + }; +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif |