/**************************************************************************** ** ** 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 #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("") : 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(); } case QXmlNodeModelIndex::AxisAncestor: { if(hasParent(preNumber)) return QXmlNodeModelIndex::Iterator::Ptr(new AncestorIterator(this, preNumber)); else return makeEmptyIterator(); } case QXmlNodeModelIndex::AxisAncestorOrSelf: return QXmlNodeModelIndex::Iterator::Ptr(new AncestorIterator(this, preNumber)); case QXmlNodeModelIndex::AxisParent: { if(hasParent(preNumber)) return makeSingletonIterator(createIndex(parent(preNumber))); else return makeEmptyIterator(); } case QXmlNodeModelIndex::AxisDescendant: { if(hasChildren(preNumber)) return QXmlNodeModelIndex::Iterator::Ptr(new DescendantIterator(this, preNumber)); else return makeEmptyIterator(); } case QXmlNodeModelIndex::AxisDescendantOrSelf: return QXmlNodeModelIndex::Iterator::Ptr(new DescendantIterator(this, preNumber)); case QXmlNodeModelIndex::AxisFollowing: { if(preNumber == maximumPreNumber()) return makeEmptyIterator(); 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(); } case QXmlNodeModelIndex::AxisPreceding: { if(preNumber == 0) return makeEmptyIterator(); 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(); else return QXmlNodeModelIndex::Iterator::Ptr(new SiblingIterator(this, preNumber)); } case QXmlNodeModelIndex::AxisPrecedingSibling: { if(preNumber == 0) return makeEmptyIterator(); else return QXmlNodeModelIndex::Iterator::Ptr(new SiblingIterator(this, preNumber)); } case QXmlNodeModelIndex::AxisNamespace: return makeEmptyIterator(); } 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 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(); } 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 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(this, preNumber)); QVector result; QXmlNodeModelIndex n(it->next()); /* Whether xmlns="" has been encountered. */ bool hasUndeclaration = false; while(!n.isNull()) { const QVector &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 alreadySent; while(!next.isNull()) { const PreNumber preNumber = toPreNumber(next); const QVector &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 &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 Binding; QStack 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 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 AccelTree::nodesByIdref(const QXmlName &) const { return QVector(); } QT_END_NAMESPACE