diff options
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 | 706 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltree_p.h | 404 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltreebuilder.cpp | 429 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltreebuilder_p.h | 187 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltreeresourceloader.cpp | 411 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qacceltreeresourceloader_p.h | 195 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qcompressedwhitespace.cpp | 197 | ||||
-rw-r--r-- | src/xmlpatterns/acceltree/qcompressedwhitespace_p.h | 186 |
11 files changed, 3319 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..152c5d8 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceliterators.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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..545ff73 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceliterators_p.h @@ -0,0 +1,413 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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<fenglich@trolltech.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<fenglich@trolltech.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<fenglich@trolltech.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<fenglich@trolltech.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 <fenglich@trolltech.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 <fenglich@trolltech.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 <fenglich@trolltech.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 <fenglich@trolltech.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..60e6e27 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltree.cpp @@ -0,0 +1,706 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QStack> + +#include "qabstractxmlreceiver.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; + +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); + } + +} + +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..10320ba --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltree_p.h @@ -0,0 +1,404 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 <fenglich@trolltech.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 AccelTree : public QAbstractXmlNodeModel + { + public: + using QAbstractXmlNodeModel::createIndex; + + typedef QExplicitlySharedDataPointer<AccelTree> Ptr; + typedef qint32 PreNumber; + typedef PreNumber PostNumber; + typedef qint8 Depth; + + inline AccelTree(const QUrl &docURI, + const QUrl &bURI) : 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); + } + + /** + * @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 <fenglich@trolltech.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: + 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 8 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; + + 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: + /** + * 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..5b16cc3 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltreebuilder.cpp @@ -0,0 +1,429 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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) : 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) +{ + 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) +{ + startStructure(); + + m_document->basicData.append(AccelTree::BasicNodeData(currentDepth(), currentParent(), QXmlNodeModelIndex::Element, -1, name)); + + ++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..653eb85 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltreebuilder_p.h @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 <fenglich@trolltech.com> + */ + template<bool FromDocument> + class AccelTreeBuilder : public NodeBuilder + , public SourceLocationReflection + { + public: + typedef QExplicitlySharedDataPointer<AccelTreeBuilder> Ptr; + + /** + * @param context may be @c null. + */ + AccelTreeBuilder(const QUrl &docURI, + const QUrl &baseURI, + const NamePool::Ptr &np, + ReportContext *const context); + virtual void startDocument(); + virtual void endDocument(); + virtual void startElement(const QXmlName &name); + 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; + }; + +#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..4a5c219 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltreeresourceloader.cpp @@ -0,0 +1,411 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QFile> +#include <QtCore/QTextCodec> +#include <QtCore/QTimer> +#include <QtCore/QXmlStreamReader> + +#include <QtNetwork/QNetworkRequest> + +#include "qacceltreebuilder_p.h" +#include "qatomicstring_p.h" +#include "qautoptr_p.h" +#include "qcommonsequencetypes_p.h" + +#include "qacceltreeresourceloader_p.h" + +QT_BEGIN_NAMESPACE + +using namespace QPatternist; + +static inline uint qHash(const QUrl &uri) +{ + return qHash(uri.toString()); +} + +AccelTreeResourceLoader::AccelTreeResourceLoader(const NamePool::Ptr &np, + const NetworkAccessDelegator::Ptr &manager) : m_namePool(np) + , m_networkAccessDelegator(manager) +{ + 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()); + + 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; +} + +QNetworkReply *AccelTreeResourceLoader::load(const QUrl &uri, + const NetworkAccessDelegator::Ptr &networkDelegator, + const ReportContext::Ptr &context) +{ + return load(uri, + networkDelegator->managerFor(uri), + context); +} + +QNetworkReply *AccelTreeResourceLoader::load(const QUrl &uri, + QNetworkAccessManager *const networkManager, + const ReportContext::Ptr &context) +{ + 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()) + { + 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) + context->error(errorMessage, ReportContext::FODC0002, location); + + return 0; + } + else + return reply; +} + +bool AccelTreeResourceLoader::streamToReceiver(QIODevice *const dev, + QAbstractXmlReceiver *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())); + + /* 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(); + } +} + +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..863fd65 --- /dev/null +++ b/src/xmlpatterns/acceltree/qacceltreeresourceloader_p.h @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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/QHash> +#include <QtCore/QEventLoop> +#include <QtNetwork/QNetworkReply> + +#include "qabstractxmlreceiver.h" +#include "qacceltree_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 <fenglich@trolltech.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 <fenglich@trolltech.com> + */ + class Q_AUTOTEST_EXPORT AccelTreeResourceLoader : public DeviceResourceLoader + { + public: + /** + * AccelTreeResourceLoader does not own @p context. + */ + AccelTreeResourceLoader(const NamePool::Ptr &np, + const NetworkAccessDelegator::Ptr &networkDelegator); + + virtual Item openDocument(const QUrl &uri, + 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); + + /** + * @overload + */ + static QNetworkReply *load(const QUrl &uri, + const NetworkAccessDelegator::Ptr &networkDelegator, + const ReportContext::Ptr &context); + + /** + * @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, + QAbstractXmlReceiver *const receiver, + const NamePool::Ptr &np, + const ReportContext::Ptr &context, + const QUrl &uri); + bool retrieveDocument(const QUrl &uri, + 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; + }; +} + +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..730d1d9 --- /dev/null +++ b/src/xmlpatterns/acceltree/qcompressedwhitespace.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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..92bc0a5 --- /dev/null +++ b/src/xmlpatterns/acceltree/qcompressedwhitespace_p.h @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 <fenglich@trolltech.com> + */ + class 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 |