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); });
}
/*! \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
{
//TODO: simplify with findNextInList
if (!node_impl)
return;
@ -910,8 +1007,8 @@ int QDomNodeList::noexceptLength() const noexcept
\typedef QDomNodeList::const_reverse_iterator
\since 6.9
Typedefs for an opaque class that implements a (reverse) random-access
iterator over a QDomNodeList.
Typedefs for an opaque class that implements a bidirectional iterator over
a QDomNodeList.
\note QDomNodeList does not support modifying nodes in-place, so
there is no mutable iterator.
@ -920,7 +1017,6 @@ int QDomNodeList::noexceptLength() const noexcept
/*!
\typedef QDomNodeList::value_type
\typedef QDomNodeList::difference_type
\typedef QDomNodeList::size_type
\typedef QDomNodeList::reference
\typedef QDomNodeList::const_reference
\typedef QDomNodeList::pointer
@ -954,6 +1050,27 @@ int QDomNodeList::noexceptLength() const noexcept
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

View File

@ -244,74 +244,39 @@ private:
class It
{
const QDomNodeList *list;
int i;
const QDomNodeListPrivate *parent;
QDomNodePrivate *current;
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)
{ Q_ASSERT(lhs.list == rhs.list); return lhs.i == rhs.i; }
friend constexpr Qt::strong_ordering compareThreeWay(const It &lhs, const It &rhs)
{ 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)
friend constexpr bool operator==(It lhs, It rhs) {
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
{ Q_ASSERT(lhs.parent == rhs.parent); return lhs.current == rhs.current; }
Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(It);
Q_XML_EXPORT static QDomNodePrivate *findNextInOrder(const QDomNodeListPrivate *parent, QDomNodePrivate *current);
Q_XML_EXPORT static QDomNodePrivate *findPrevInOrder(const QDomNodeListPrivate *parent, QDomNodePrivate *current);
public:
// Rule Of Zero applies
It() = default;
using iterator_category = std::random_access_iterator_tag;
using iterator_category = std::bidirectional_iterator_tag;
using value_type = QDomNode;
using element_type = const QDomNode;
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 pointer = QtPrivate::ArrowProxy<reference>;
reference operator*() const { return list->item(i); }
reference operator*() const { return QDomNode(current); }
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--() { --i; return *this; }
It &operator--() { current = findPrevInOrder(parent, current); return *this; }
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:
@ -320,14 +285,13 @@ public:
using value_type = It::value_type;
using difference_type = It::difference_type;
using size_type = It::size_type;
using reference = It::reference;
using const_reference = reference;
using pointer = It::pointer;
using const_pointer = pointer;
[[nodiscard]] const_iterator begin() const noexcept { return It{this, 0}; }
[[nodiscard]] const_iterator end() const noexcept { return It{this, noexceptLength()}; }
[[nodiscard]] const_iterator begin() const noexcept { return It{impl, true}; }
[[nodiscard]] const_iterator end() const noexcept { return It{impl, false}; }
[[nodiscard]] const_iterator cbegin() const noexcept { return begin(); }
[[nodiscard]] const_iterator cend() const noexcept { return end(); }
@ -346,6 +310,7 @@ private:
friend class QDomNode;
friend class QDomElement;
friend class QDomDocument;
friend class ::tst_QDom;
};
class Q_XML_EXPORT QDomDocumentType : public QDomNode
@ -372,6 +337,7 @@ private:
friend class QDomImplementation;
friend class QDomDocument;
friend class QDomNode;
friend class ::tst_QDom;
};
class Q_XML_EXPORT QDomDocument : public QDomNode

View File

@ -148,6 +148,9 @@ public:
bool operator==(const QDomNodeListPrivate &) const noexcept;
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;
bool maybeCreateList() const;
QDomNodePrivate *item(int index);

View File

@ -1338,28 +1338,24 @@ void tst_QDom::domNodeListIterator()
QVERIFY(doc.setContent(xml));
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();
QCOMPARE(listSize, 3);
QCOMPARE_EQ(list.begin(), list.begin());
QCOMPARE_EQ(list.end(), list.end());
QCOMPARE_NE(list.begin(), list.end());
QCOMPARE_LT(list.begin(), list.end());
QCOMPARE_GE(list.end(), 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++)
QVERIFY(*it == list.item(i));
QVERIFY(it == list.end());
@ -1407,28 +1403,24 @@ void tst_QDom::domNodeListReverseIterator()
QVERIFY(doc.setContent(xml));
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();
QCOMPARE(listSize, 3);
QCOMPARE_EQ(list.rbegin(), list.rbegin());
QCOMPARE_EQ(list.rend(), list.rend());
QCOMPARE_NE(list.rbegin(), list.rend());
QCOMPARE_LT(list.rbegin(), list.rend());
QCOMPARE_GE(list.rend(), 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++)
QVERIFY(*it == list.item(listSize - 1 - i));
QVERIFY(it == list.rend());
@ -1471,6 +1463,16 @@ void tst_QDom::domNodeListIteratorListFilteredByTag()
QVERIFY(doc.setContent(xml));
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();
QCOMPARE(listSize, 8);
@ -1534,6 +1536,16 @@ void tst_QDom::domNodeListReverseIteratorListFilteredByTag()
QVERIFY(doc.setContent(xml));
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();
QCOMPARE(listSize, 8);