Revision control
++ // Copyright (C) 2020 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qqmldomcompare_p.h"
++ #include "QtCore/qglobal.h"
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QQmlJS {
++ namespace Dom {
++
++ bool domCompare(const DomItem &i1, const DomItem &i2, function_ref<bool(Path, const DomItem &, const DomItem &)> change,
++ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &)> filter,
++ Path basePath)
++ {
++ DomKind k1 = i1.domKind();
++ DomKind k2 = i2.domKind();
++ if (k1 != k2) {
++ if (!change(basePath, i1, i2))
++ return false;
++ } else {
++ switch (k1) {
++ case DomKind::Empty:
++ return true;
++ case DomKind::Object: {
++ QStringList f1 = i1.fields();
++ QStringList f2 = i2.fields();
++ f1.sort();
++ f2.sort();
++ qsizetype it1 = 0;
++ qsizetype it2 = 0;
++ while (it1 != f1.size() || it2 != f2.size()) {
++ QString k1, k2;
++ DomItem el1, el2;
++ bool hasK1 = it1 != f1.size();
++ bool filt1 = hasK1;
++ if (hasK1) {
++ k1 = f1.at(it1);
++ el1 = i1.field(k1);
++ filt1 = i1.isCanonicalChild(el1) && filter(i1, PathEls::Field(k1), el1);
++ }
++ bool hasK2 = it2 != f2.size();
++ bool filt2 = hasK2;
++ if (hasK2) {
++ k2 = f2.at(it2);
++ el2 = i2.field(k2);
++ filt2 = i2.isCanonicalChild(el2) && filter(i2, PathEls::Field(k2), el2);
++ }
++ // continue when filtering out
++ if (hasK1 && !filt1) {
++ ++it1;
++ if (hasK2 && !filt2)
++ ++it2;
++ continue;
++ } else if (hasK2 && !filt2) {
++ ++it2;
++ continue;
++ }
++ if (filt1 && filt2 && k1 == k2) {
++ if (!domCompare(el1, el2, change, filter, basePath.withField(k1)))
++ return false;
++ ++it1;
++ ++it2;
++ } else if (!hasK1 || (hasK2 && k1 > k2)) {
++ if (!change(basePath.withField(k1), DomItem::empty, el2))
++ return false;
++ ++it2;
++ } else {
++ if (!change(basePath.withField(k1), el1, DomItem::empty))
++ return false;
++ ++it1;
++ }
++ }
++ } break;
++ case DomKind::Map: {
++ QStringList f1 = i1.sortedKeys();
++ QStringList f2 = i2.sortedKeys();
++ qsizetype it1 = 0;
++ qsizetype it2 = 0;
++ while (it1 != f1.size() || it2 != f2.size()) {
++ QString k1, k2;
++ DomItem el1, el2;
++ bool hasK1 = it1 != f1.size();
++ bool filt1 = hasK1;
++ if (hasK1) {
++ k1 = f1.at(it1);
++ el1 = i1.key(k1);
++ filt1 = i1.isCanonicalChild(el1) && filter(i1, PathEls::Key(k1), el1);
++ }
++ bool hasK2 = it2 != f2.size();
++ bool filt2 = hasK2;
++ if (hasK2) {
++ k2 = f2.at(it2);
++ el2 = i2.key(k2);
++ filt2 = i2.isCanonicalChild(el2) && filter(i2, PathEls::Key(k2), el2);
++ }
++ // continue when filtering out
++ if (hasK1 && !filt1) {
++ ++it1;
++ if (hasK2 && !filt2)
++ ++it2;
++ continue;
++ } else if (hasK2 && !filt2) {
++ ++it2;
++ continue;
++ }
++ if (filt1 && filt2 && k1 == k2) {
++ if (!domCompare(el1, el2, change, filter, basePath.withKey(k1)))
++ return false;
++ ++it1;
++ ++it2;
++ } else if (!hasK1 || (hasK2 && k1 > k2)) {
++ if (!change(basePath.withKey(k1), DomItem::empty, el2))
++ return false;
++ ++it2;
++ } else {
++ if (!change(basePath.withKey(k1), el1, DomItem::empty))
++ return false;
++ ++it1;
++ }
++ }
++ } break;
++ case DomKind::List: {
++ // we could support smarter matching keeping filtering in account like map
++ // currently it is just a simple index by index comparison
++ index_type len1 = i1.indexes();
++ index_type len2 = i2.indexes();
++ if (len1 != len2)
++ return change(basePath, i1, i2);
++ for (index_type i = 0; i < len1; ++i) {
++ DomItem v1 = i1.index(i);
++ DomItem v2 = i2.index(i);
++ if (filter(i1, PathEls::Index(i), v1) && filter(i2, PathEls::Index(i), v2)) {
++ DomItem el1 = i1.index(i);
++ DomItem el2 = i2.index(i);
++ if (i1.isCanonicalChild(el1) && i2.isCanonicalChild(el2)
++ && !domCompare(el1, el2, change, filter, basePath.withIndex(i)))
++ return false;
++ }
++ }
++ } break;
++ case DomKind::Value: {
++ QCborValue v1 = i1.value();
++ QCborValue v2 = i2.value();
++ if (v1 != v2)
++ return change(basePath, i1, i2);
++ } break;
++ case DomKind::ScriptElement: {
++ // TODO: implement me
++ return false;
++
++ } break;
++ }
++ }
++ return true;
++ }
++
++ QStringList
++ domCompareStrList(const DomItem &i1, const DomItem &i2,
++ function_ref<bool(const DomItem &, const PathEls::PathComponent &, const DomItem &) const> filter,
++ DomCompareStrList stopAtFirstDiff)
++ {
++ QStringList res;
++ bool hasDiff = false;
++ domCompare(
++ i1, i2,
++ [&res, &hasDiff, stopAtFirstDiff](const Path &p, const DomItem &j1, const DomItem &j2) {
++ hasDiff = true;
++ if (!j1) {
++ res.append(QStringLiteral("- %1\n").arg(p.toString()));
++ } else if (!j2) {
++ res.append(QStringLiteral("+ %1\n").arg(p.toString()));
++ } else {
++ DomKind k1 = j1.domKind();
++ DomKind k2 = j2.domKind();
++ if (k1 != k2) {
++ res.append(
++ QStringLiteral("- %1 %2\n").arg(p.toString(), domKindToString(k1)));
++ res.append(
++ QStringLiteral("+ %1 %2\n").arg(p.toString(), domKindToString(k2)));
++ } else {
++ switch (k1) {
++ case DomKind::Empty:
++ case DomKind::Object:
++ case DomKind::Map:
++ Q_ASSERT(false);
++ break;
++ case DomKind::List: {
++ index_type len1 = j1.indexes();
++ index_type len2 = j2.indexes();
++ res.append(QStringLiteral("- %1 #%2\n").arg(p.toString()).arg(len1));
++ res.append(QStringLiteral("+ %1 #%2\n").arg(p.toString()).arg(len2));
++ } break;
++ case DomKind::Value: {
++ QCborValue v1 = j1.value();
++ QCborValue v2 = j2.value();
++ auto t1 = v1.type();
++ auto t2 = v2.type();
++ if (t1 != t2) {
++ res.append(QStringLiteral("- %1 type(%2)\n")
++ .arg(p.toString())
++ .arg(int(t1)));
++ res.append(QStringLiteral("+ %1 type(%2)\n")
++ .arg(p.toString())
++ .arg(int(t2)));
++ } else {
++ res.append(QStringLiteral("- %1 value(%2)\n")
++ .arg(p.toString())
++ .arg(j1.toString()));
++ res.append(QStringLiteral("+ %1 value(%2)\n")
++ .arg(p.toString())
++ .arg(j2.toString()));
++ }
++ } break;
++ case DomKind::ScriptElement: {
++ // implement me
++ break;
++ }
++ }
++ }
++ }
++ return (stopAtFirstDiff == DomCompareStrList::AllDiffs);
++ },
++ filter);
++ if (hasDiff && res.isEmpty()) // should never happen
++ res.append(QStringLiteral(u"Had changes!"));
++ return res;
++ }
++
++ } // end namespace Dom
++ } // end namespace QQmlJS
++
++ QT_END_NAMESPACE