diff options
Diffstat (limited to 'src/qmldom/qqmldomcompare.cpp')
-rw-r--r-- | src/qmldom/qqmldomcompare.cpp | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/src/qmldom/qqmldomcompare.cpp b/src/qmldom/qqmldomcompare.cpp new file mode 100644 index 0000000000..65b357cc75 --- /dev/null +++ b/src/qmldom/qqmldomcompare.cpp @@ -0,0 +1,232 @@ +// 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 +#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.field(k1))) + return false; + ++it1; + ++it2; + } else if (!hasK1 || (hasK2 && k1 > k2)) { + if (!change(basePath.field(k1), DomItem::empty, el2)) + return false; + ++it2; + } else { + if (!change(basePath.field(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.key(k1))) + return false; + ++it1; + ++it2; + } else if (!hasK1 || (hasK2 && k1 > k2)) { + if (!change(basePath.key(k1), DomItem::empty, el2)) + return false; + ++it2; + } else { + if (!change(basePath.key(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.index(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 |