Revision control
Copy as Markdown
// 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