blob: 23503fef453efd9df99d4a55278b7015f0ea6813 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtXmlPatterns module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QStack>
#include "qabstractxmlreceiver.h"
#include "qabstractxmlnodemodel_p.h"
#include "qacceliterators_p.h"
#include "qacceltree_p.h"
#include "qatomicstring_p.h"
#include "qcommonvalues_p.h"
#include "qcompressedwhitespace_p.h"
#include "qxmldebug_p.h"
#include "quntypedatomic_p.h"
#include "qxpathhelper_p.h"
QT_BEGIN_NAMESPACE
using namespace QPatternist;
namespace QPatternist {
class AccelTreePrivate : public QAbstractXmlNodeModelPrivate
{
public:
AccelTreePrivate(AccelTree *accelTree)
: m_accelTree(accelTree)
{
}
virtual QSourceLocation sourceLocation(const QXmlNodeModelIndex &index) const
{
return m_accelTree->sourceLocation(index);
}
private:
AccelTree *m_accelTree;
};
}
AccelTree::AccelTree(const QUrl &docURI, const QUrl &bURI)
: QAbstractXmlNodeModel(new AccelTreePrivate(this))
, m_documentURI(docURI)
, m_baseURI(bURI)
{
/* Pre-allocate at least a little bit. */
// TODO. Do it according to what an average 4 KB doc contains.
basicData.reserve(100);
data.reserve(30);
}
void AccelTree::printStats(const NamePool::Ptr &np) const
{
Q_ASSERT(np);
#ifdef QT_NO_DEBUG
Q_UNUSED(np); /* Needed when compiling in release mode. */
#else
const int len = basicData.count();
pDebug() << "AccelTree stats for" << (m_documentURI.isEmpty() ? QString::fromLatin1("<empty URI>") : m_documentURI.toString());
pDebug() << "Maximum pre number:" << maximumPreNumber();
pDebug() << "+---------------+-------+-------+---------------+-------+--------------+-------+";
pDebug() << "| Pre number | Depth | Size | Post Number | Kind | Name | Value |";
pDebug() << "+---------------+-------+-------+---------------+-------+--------------+-------+";
for(int i = 0; i < len; ++i)
{
const BasicNodeData &v = basicData.at(i);
pDebug() << '|' << i
<< "\t\t|" << v.depth()
<< "\t|" << v.size()
<< "\t|" << postNumber(i)
<< "\t|" << v.kind()
<< "\t\t|" << (v.name().isNull() ? QString::fromLatin1("(none)") : np->displayName(v.name()))
<< "\t\t|" << ((v.kind() == QXmlNodeModelIndex::Text && isCompressed(i)) ? CompressedWhitespace::decompress(data.value(i))
: data.value(i))
<< "\t|";
/*
pDebug() << '|' << QString().arg(i, 14)
<< '|' << QString().arg(v.depth(), 6)
<< '|' << QString().arg(v.size(), 6)
<< '|' << QString().arg(postNumber(i), 14)
<< '|' << QString().arg(v.kind(), 6)
<< '|';
*/
}
pDebug() << "+---------------+-------+-------+---------------+-------+--------------+";
pDebug() << "Namespaces(" << namespaces.count() << "):";
for (auto it = namespaces.cbegin(), end = namespaces.cend(); it != end; ++it) {
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:
case QXmlNodeModelIndex::Comment:
case QXmlNodeModelIndex::Attribute:
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:
case QXmlNodeModelIndex::ProcessingInstruction:
case QXmlNodeModelIndex::Element:
case QXmlNodeModelIndex::Text:
return makeSingletonIterator(ni);
case QXmlNodeModelIndex::Attribute:
case QXmlNodeModelIndex::Document:
case QXmlNodeModelIndex::Namespace:
/* Do nothing. */;
}
}
Q_FALLTHROUGH();
}
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);
Q_FALLTHROUGH();
}
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));
/* It's not compressed so use it as it is. */
Q_FALLTHROUGH();
}
case QXmlNodeModelIndex::Attribute:
case QXmlNodeModelIndex::ProcessingInstruction:
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:
case QXmlNodeModelIndex::Document:
case QXmlNodeModelIndex::Attribute:
return makeSingletonIterator(Item(UntypedAtomic::fromValue(stringValue(n))));
case QXmlNodeModelIndex::Text:
case QXmlNodeModelIndex::ProcessingInstruction:
case QXmlNodeModelIndex::Comment:
return makeSingletonIterator(Item(AtomicString::fromValue(stringValue(n))));
default:
{
Q_ASSERT_X(false, Q_FUNC_INFO,
qPrintable(QString::fromLatin1("A node type that doesn't exist "
"in the XPath Data Model was encountered.").arg(kind(preNumber))));
return Item::Iterator::Ptr(); /* Dummy, silence compiler warning. */
}
}
}
void AccelTree::copyNodeTo(const QXmlNodeModelIndex &node,
QAbstractXmlReceiver *const receiver,
const NodeCopySettings &settings) const
{
/* This code piece can be seen as a customized version of
* QAbstractXmlReceiver::item/sendAsNode(). */
Q_ASSERT(receiver);
Q_ASSERT(!node.isNull());
typedef QHash<QXmlName::PrefixCode, QXmlName::NamespaceCode> Binding;
QStack<Binding> outputted;
switch(node.kind())
{
case QXmlNodeModelIndex::Element:
{
outputted.push(Binding());
/* Add the namespace for our element name. */
const QXmlName elementName(node.name());
receiver->startElement(elementName);
if(!settings.testFlag(InheritNamespaces))
receiver->namespaceBinding(QXmlName(StandardNamespaces::StopNamespaceInheritance, 0,
StandardPrefixes::StopNamespaceInheritance));
if(settings.testFlag(PreserveNamespaces))
node.sendNamespaces(receiver);
else
{
/* Find the namespaces that we actually use and add them to outputted. These are drawn
* from the element name, and the node's attributes. */
outputted.top().insert(elementName.prefix(), elementName.namespaceURI());
const QXmlNodeModelIndex::Iterator::Ptr attributes(iterate(node, QXmlNodeModelIndex::AxisAttribute));
QXmlNodeModelIndex attr(attributes->next());
while(!attr.isNull())
{
const QXmlName &attrName = attr.name();
outputted.top().insert(attrName.prefix(), attrName.namespaceURI());
attr = attributes->next();
}
Binding::const_iterator it(outputted.top().constBegin());
const Binding::const_iterator end(outputted.top().constEnd());
for(; it != end; ++it)
receiver->namespaceBinding(QXmlName(it.value(), 0, it.key()));
}
/* Send the attributes of the element. */
{
QXmlNodeModelIndex::Iterator::Ptr attributes(node.iterate(QXmlNodeModelIndex::AxisAttribute));
QXmlNodeModelIndex attribute(attributes->next());
while(!attribute.isNull())
{
const QString &v = attribute.stringValue();
receiver->attribute(attribute.name(), QStringRef(&v));
attribute = attributes->next();
}
}
/* Send the children of the element. */
copyChildren(node, receiver, settings);
receiver->endElement();
outputted.pop();
break;
}
case QXmlNodeModelIndex::Document:
{
/* We need to intercept and grab the elements of the document node, such
* that we preserve/inherit preference applies to them. */
receiver->startDocument();
copyChildren(node, receiver, settings);
receiver->endDocument();
break;
}
default:
receiver->item(node);
}
}
QSourceLocation AccelTree::sourceLocation(const QXmlNodeModelIndex &index) const
{
const PreNumber key = toPreNumber(index);
if (sourcePositions.contains(key)) {
const QPair<qint64, qint64> position = sourcePositions.value(key);
return QSourceLocation(m_documentURI, position.first, position.second);
} else {
return QSourceLocation();
}
}
void AccelTree::copyChildren(const QXmlNodeModelIndex &node,
QAbstractXmlReceiver *const receiver,
const NodeCopySettings &settings) const
{
QXmlNodeModelIndex::Iterator::Ptr children(node.iterate(QXmlNodeModelIndex::AxisChild));
QXmlNodeModelIndex child(children->next());
while(!child.isNull())
{
copyNodeTo(child, receiver, settings);
child = children->next();
}
}
QXmlNodeModelIndex AccelTree::elementById(const QXmlName &id) const
{
const PreNumber pre = m_IDs.value(id.localName(), -1);
if(pre == -1)
return QXmlNodeModelIndex();
else
return createIndex(pre);
}
QVector<QXmlNodeModelIndex> AccelTree::nodesByIdref(const QXmlName &) const
{
return QVector<QXmlNodeModelIndex>();
}
QT_END_NAMESPACE