Make QDomNodeList::It bidirectional only

The new (6.9) iterator for QDomNodeList was implemented as random access
iterator. The underlying structure of QDomNodeList is similar to a
linked list and therefore it is better suited for bidirectional
iterator.

This patch changes the iterator to a bidirectional iterator. Further the
tests are expanded because the new implementation uses more custom code.

Fixes: QTBUG-132527
Pick-to: 6.9
Change-Id: I796f445e0b883abc2aea613f1fed6f17c4d93dcd
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Matthias Rauter 2025-01-03 18:31:48 +01:00 committed by Marc Mutz
parent f1f610bc67
commit 8609982791
4 changed files with 180 additions and 82 deletions

View File

@ -656,8 +656,105 @@ void QDomNodeListPrivate::createList() const
forEachNode([&](QDomNodePrivate *p){ list.append(p); }); forEachNode([&](QDomNodePrivate *p){ list.append(p); });
} }
/*! \internal
Checks if a node is valid and fulfills the requirements set during the
generation of this list, i.e. matching tag and matching URI.
*/
bool QDomNodeListPrivate::checkNode(QDomNodePrivate *p) const {
if (nsURI.isNull())
return p->isElement() && p->nodeName() == tagname;
else
return p->isElement() && p->name==tagname && p->namespaceURI==nsURI;
};
/*! \internal
Returns the next node item in the list. If the tagname or the URI are set,
the function iterates through the dom tree and returns node that match them.
If neither tag nor URI are set, the function iterates through a single level
in the tree and returns all nodes.
\sa forEachNode(), findPrevInOrder()
*/
QDomNodePrivate *QDomNodeListPrivate::findNextInOrder(QDomNodePrivate *p) const
{
if (!p)
return p;
if (tagname.isNull()) {
if (p == node_impl)
return p->first;
else if (p && p->next)
return p->next;
}
if (p == node_impl) {
p = p->first;
if (checkNode(p))
return p;
}
while (p && p != node_impl) {
if (p->first) { // go down in the tree
p = p->first;
} else if (p->next) { // traverse the tree
p = p->next;
} else { // go up in the tree
p = p->parent();
while (p && p != node_impl && !p->next)
p = p->parent();
if (p && p != node_impl)
p = p->next;
}
if (checkNode(p))
return p;
}
return node_impl;
}
/*! \internal
Similar as findNextInOrder() but iterarating in the opposite order.
\sa forEachNode(), findNextInOrder()
*/
QDomNodePrivate *QDomNodeListPrivate::findPrevInOrder(QDomNodePrivate *p) const
{
if (!p)
return p;
if (tagname.isNull() && p == node_impl)
return p->last;
if (tagname.isNull())
return p->prev;
// We end all the way down in the tree
// so that is where we have to start
if (p == node_impl) {
while (p->last)
p = p->last;
if (checkNode(p))
return p;
}
while (p) {
if (p->prev) {// traverse the tree backwards
p = p->prev;
// go mmediately down if an item has children
while (p->last)
p = p->last;
} else { // go up in the tree
p = p->parent();
}
if (checkNode(p))
return p;
}
return node_impl;
}
void QDomNodeListPrivate::forEachNode(qxp::function_ref<void(QDomNodePrivate*)> yield) const void QDomNodeListPrivate::forEachNode(qxp::function_ref<void(QDomNodePrivate*)> yield) const
{ {
//TODO: simplify with findNextInList
if (!node_impl) if (!node_impl)
return; return;
@ -910,8 +1007,8 @@ int QDomNodeList::noexceptLength() const noexcept
\typedef QDomNodeList::const_reverse_iterator \typedef QDomNodeList::const_reverse_iterator
\since 6.9 \since 6.9
Typedefs for an opaque class that implements a (reverse) random-access Typedefs for an opaque class that implements a bidirectional iterator over
iterator over a QDomNodeList. a QDomNodeList.
\note QDomNodeList does not support modifying nodes in-place, so \note QDomNodeList does not support modifying nodes in-place, so
there is no mutable iterator. there is no mutable iterator.
@ -920,7 +1017,6 @@ int QDomNodeList::noexceptLength() const noexcept
/*! /*!
\typedef QDomNodeList::value_type \typedef QDomNodeList::value_type
\typedef QDomNodeList::difference_type \typedef QDomNodeList::difference_type
\typedef QDomNodeList::size_type
\typedef QDomNodeList::reference \typedef QDomNodeList::reference
\typedef QDomNodeList::const_reference \typedef QDomNodeList::const_reference
\typedef QDomNodeList::pointer \typedef QDomNodeList::pointer
@ -954,6 +1050,27 @@ int QDomNodeList::noexceptLength() const noexcept
there is no mutable iterator. there is no mutable iterator.
*/ */
QDomNodeList::It::It(const QDomNodeListPrivate *lp, bool start) noexcept
: parent(lp)
{
if (!lp || !lp->node_impl)
current = nullptr;
else if (start)
current = lp->findNextInOrder(lp->node_impl);
else
current = lp->node_impl;
}
QDomNodePrivate *QDomNodeList::It::findNextInOrder(const QDomNodeListPrivate *parent, QDomNodePrivate *current)
{
return parent->findNextInOrder(current);
}
QDomNodePrivate *QDomNodeList::It::findPrevInOrder(const QDomNodeListPrivate *parent, QDomNodePrivate *current)
{
return parent->findPrevInOrder(current);
}
/************************************************************** /**************************************************************
* *
* QDomNodePrivate * QDomNodePrivate

View File

@ -244,74 +244,39 @@ private:
class It class It
{ {
const QDomNodeList *list; const QDomNodeListPrivate *parent;
int i; QDomNodePrivate *current;
friend class QDomNodeList; friend class QDomNodeList;
explicit constexpr It(const QDomNodeList *lp, int ii) noexcept : list(lp), i(ii) {} friend class QDomNodeListPrivate;
Q_XML_EXPORT It(const QDomNodeListPrivate *lp, bool start) noexcept;
friend constexpr bool comparesEqual(const It &lhs, const It &rhs) friend constexpr bool comparesEqual(const It &lhs, const It &rhs)
{ Q_ASSERT(lhs.list == rhs.list); return lhs.i == rhs.i; } { Q_ASSERT(lhs.parent == rhs.parent); return lhs.current == rhs.current; }
friend constexpr Qt::strong_ordering compareThreeWay(const It &lhs, const It &rhs) Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(It);
{ Q_ASSERT(lhs.list == rhs.list); return Qt::compareThreeWay(lhs.i, rhs.i); }
// macro variant does not exist: Q_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE_NON_NOEXCEPT(It) Q_XML_EXPORT static QDomNodePrivate *findNextInOrder(const QDomNodeListPrivate *parent, QDomNodePrivate *current);
friend constexpr bool operator==(It lhs, It rhs) { Q_XML_EXPORT static QDomNodePrivate *findPrevInOrder(const QDomNodeListPrivate *parent, QDomNodePrivate *current);
return comparesEqual(lhs, rhs);
}
#ifdef __cpp_lib_three_way_comparison
friend constexpr std::strong_ordering operator<=>(It lhs, It rhs) {
return compareThreeWay(lhs, rhs);
}
#else
friend constexpr bool operator!=(It lhs, It rhs) {
return !comparesEqual(lhs, rhs);
}
friend constexpr bool operator<(It lhs, It rhs) {
return is_lt(compareThreeWay(lhs, rhs));
}
friend constexpr bool operator<=(It lhs, It rhs) {
return is_lteq(compareThreeWay(lhs, rhs));
}
friend constexpr bool operator>(It lhs, It rhs) {
return is_gt(compareThreeWay(lhs, rhs));
}
friend constexpr bool operator>=(It lhs, It rhs) {
return is_gteq(compareThreeWay(lhs, rhs));
}
#endif
public: public:
// Rule Of Zero applies // Rule Of Zero applies
It() = default; It() = default;
using iterator_category = std::random_access_iterator_tag; using iterator_category = std::bidirectional_iterator_tag;
using value_type = QDomNode; using value_type = QDomNode;
using element_type = const QDomNode; using element_type = const QDomNode;
using difference_type = qptrdiff; // difference to [container.reqmts] using difference_type = qptrdiff; // difference to [container.reqmts]
using size_type = int; // difference to [container.reqmts]
using reference = value_type; // difference to [container.reqmts] using reference = value_type; // difference to [container.reqmts]
using pointer = QtPrivate::ArrowProxy<reference>; using pointer = QtPrivate::ArrowProxy<reference>;
reference operator*() const { return list->item(i); } reference operator*() const { return QDomNode(current); }
pointer operator->() const { return { **this }; } pointer operator->() const { return { **this }; }
It &operator++() { ++i; return *this; } It &operator++() { current = findNextInOrder(parent, current); return *this; }
It operator++(int) { auto copy = *this; ++*this; return copy; } It operator++(int) { auto copy = *this; ++*this; return copy; }
It &operator--() { --i; return *this; } It &operator--() { current = findPrevInOrder(parent, current); return *this; }
It operator--(int) { auto copy = *this; --*this; return copy; } It operator--(int) { auto copy = *this; --*this; return copy; }
It &operator+=(difference_type n) { i += n; return *this; }
friend It operator+(It it, difference_type n) { it += n; return it; }
friend It operator+(difference_type n, It it) { return it + n; }
It &operator-=(difference_type n) { i -= n; return *this; }
friend It operator-(It it, difference_type n) { it -= n; return it; }
friend difference_type operator-(It lhs, It rhs)
{ Q_ASSERT(lhs.list == rhs.list); return lhs.i - rhs.i; }
reference operator[](difference_type n) const { return *(*this + n); }
}; };
public: public:
@ -320,14 +285,13 @@ public:
using value_type = It::value_type; using value_type = It::value_type;
using difference_type = It::difference_type; using difference_type = It::difference_type;
using size_type = It::size_type;
using reference = It::reference; using reference = It::reference;
using const_reference = reference; using const_reference = reference;
using pointer = It::pointer; using pointer = It::pointer;
using const_pointer = pointer; using const_pointer = pointer;
[[nodiscard]] const_iterator begin() const noexcept { return It{this, 0}; } [[nodiscard]] const_iterator begin() const noexcept { return It{impl, true}; }
[[nodiscard]] const_iterator end() const noexcept { return It{this, noexceptLength()}; } [[nodiscard]] const_iterator end() const noexcept { return It{impl, false}; }
[[nodiscard]] const_iterator cbegin() const noexcept { return begin(); } [[nodiscard]] const_iterator cbegin() const noexcept { return begin(); }
[[nodiscard]] const_iterator cend() const noexcept { return end(); } [[nodiscard]] const_iterator cend() const noexcept { return end(); }
@ -346,6 +310,7 @@ private:
friend class QDomNode; friend class QDomNode;
friend class QDomElement; friend class QDomElement;
friend class QDomDocument; friend class QDomDocument;
friend class ::tst_QDom;
}; };
class Q_XML_EXPORT QDomDocumentType : public QDomNode class Q_XML_EXPORT QDomDocumentType : public QDomNode
@ -372,6 +337,7 @@ private:
friend class QDomImplementation; friend class QDomImplementation;
friend class QDomDocument; friend class QDomDocument;
friend class QDomNode; friend class QDomNode;
friend class ::tst_QDom;
}; };
class Q_XML_EXPORT QDomDocument : public QDomNode class Q_XML_EXPORT QDomDocument : public QDomNode

View File

@ -148,6 +148,9 @@ public:
bool operator==(const QDomNodeListPrivate &) const noexcept; bool operator==(const QDomNodeListPrivate &) const noexcept;
void createList() const; void createList() const;
bool checkNode(QDomNodePrivate* p) const;
QDomNodePrivate *findNextInOrder(QDomNodePrivate* p) const;
QDomNodePrivate *findPrevInOrder(QDomNodePrivate* p) const;
void forEachNode(qxp::function_ref<void(QDomNodePrivate*)> yield) const; void forEachNode(qxp::function_ref<void(QDomNodePrivate*)> yield) const;
bool maybeCreateList() const; bool maybeCreateList() const;
QDomNodePrivate *item(int index); QDomNodePrivate *item(int index);

View File

@ -1338,28 +1338,24 @@ void tst_QDom::domNodeListIterator()
QVERIFY(doc.setContent(xml)); QVERIFY(doc.setContent(xml));
QDomNodeList list = doc.childNodes().at(0).childNodes(); QDomNodeList list = doc.childNodes().at(0).childNodes();
{
const QList<QDomNode> items(list.begin(), list.end());
QVERIFY(list.impl->list.isEmpty()); // we evaded maybeCreateList()
QCOMPARE(items.size(), list.size());
int i = 0;
for (const auto &item : items)
QCOMPARE(item, list.item(i++));
}
auto listSize = list.size(); auto listSize = list.size();
QCOMPARE(listSize, 3); QCOMPARE(listSize, 3);
QCOMPARE_EQ(list.begin(), list.begin()); QCOMPARE_EQ(list.begin(), list.begin());
QCOMPARE_EQ(list.end(), list.end()); QCOMPARE_EQ(list.end(), list.end());
QCOMPARE_NE(list.begin(), list.end()); QCOMPARE_NE(list.begin(), list.end());
QCOMPARE_LT(list.begin(), list.end());
QCOMPARE_GE(list.end(), list.begin());
auto it = list.begin(); auto it = list.begin();
it += listSize;
QVERIFY(it == list.end());
it -= listSize;
QVERIFY(it == list.begin());
it = it + listSize;
QVERIFY(it == list.end());
it = it - listSize;
QVERIFY(it == list.begin());
it = listSize + it;
QVERIFY(it == list.end());
it = list.begin();
for (int i = 0; i < listSize; i++, it++) for (int i = 0; i < listSize; i++, it++)
QVERIFY(*it == list.item(i)); QVERIFY(*it == list.item(i));
QVERIFY(it == list.end()); QVERIFY(it == list.end());
@ -1407,28 +1403,24 @@ void tst_QDom::domNodeListReverseIterator()
QVERIFY(doc.setContent(xml)); QVERIFY(doc.setContent(xml));
QDomNodeList list = doc.childNodes().at(0).childNodes(); QDomNodeList list = doc.childNodes().at(0).childNodes();
{
const QList<QDomNode> items(list.begin(), list.end());
QVERIFY(list.impl->list.isEmpty()); // we evaded maybeCreateList()
QCOMPARE(items.size(), list.size());
int i = 0;
for (const auto &item : items)
QCOMPARE(item, list.item(i++));
}
auto listSize = list.size(); auto listSize = list.size();
QCOMPARE(listSize, 3); QCOMPARE(listSize, 3);
QCOMPARE_EQ(list.rbegin(), list.rbegin()); QCOMPARE_EQ(list.rbegin(), list.rbegin());
QCOMPARE_EQ(list.rend(), list.rend()); QCOMPARE_EQ(list.rend(), list.rend());
QCOMPARE_NE(list.rbegin(), list.rend()); QCOMPARE_NE(list.rbegin(), list.rend());
QCOMPARE_LT(list.rbegin(), list.rend());
QCOMPARE_GE(list.rend(), list.rbegin());
auto it = list.rbegin(); auto it = list.rbegin();
it += listSize;
QVERIFY(it == list.rend());
it -= listSize;
QVERIFY(it == list.rbegin());
it = it + listSize;
QVERIFY(it == list.rend());
it = it - listSize;
QVERIFY(it == list.rbegin());
it = listSize + it;
QVERIFY(it == list.rend());
it = list.rbegin();
for (int i = 0; i < listSize; i++, it++) for (int i = 0; i < listSize; i++, it++)
QVERIFY(*it == list.item(listSize - 1 - i)); QVERIFY(*it == list.item(listSize - 1 - i));
QVERIFY(it == list.rend()); QVERIFY(it == list.rend());
@ -1471,6 +1463,16 @@ void tst_QDom::domNodeListIteratorListFilteredByTag()
QVERIFY(doc.setContent(xml)); QVERIFY(doc.setContent(xml));
QDomNodeList list = doc.elementsByTagName("bar"); QDomNodeList list = doc.elementsByTagName("bar");
{
const QList<QDomNode> items(list.begin(), list.end());
QVERIFY(list.impl->list.isEmpty()); // we evaded maybeCreateList()
QCOMPARE(items.size(), list.size());
int i = 0;
for (const auto &item : items)
QCOMPARE(item, list.item(i++));
}
auto listSize = list.size(); auto listSize = list.size();
QCOMPARE(listSize, 8); QCOMPARE(listSize, 8);
@ -1534,6 +1536,16 @@ void tst_QDom::domNodeListReverseIteratorListFilteredByTag()
QVERIFY(doc.setContent(xml)); QVERIFY(doc.setContent(xml));
QDomNodeList list = doc.elementsByTagName("bar"); QDomNodeList list = doc.elementsByTagName("bar");
{
const QList<QDomNode> items(list.begin(), list.end());
QVERIFY(list.impl->list.isEmpty()); // we evaded maybeCreateList()
QCOMPARE(items.size(), list.size());
int i = 0;
for (const auto &item : items)
QCOMPARE(item, list.item(i++));
}
auto listSize = list.size(); auto listSize = list.size();
QCOMPARE(listSize, 8); QCOMPARE(listSize, 8);