1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3#include "qqmldomcompare_p.h"
4#include "QtCore/qglobal.h"
5
6QT_BEGIN_NAMESPACE
7
8namespace QQmlJS {
9namespace Dom {
10
11bool domCompare(DomItem &i1, DomItem &i2, function_ref<bool(Path, DomItem &, DomItem &)> change,
12 function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &)> filter,
13 Path basePath)
14{
15 DomKind k1 = i1.domKind();
16 DomKind k2 = i2.domKind();
17 if (k1 != k2) {
18 if (!change(basePath, i1, i2))
19 return false;
20 } else {
21 switch (k1) {
22 case DomKind::Empty:
23 return true;
24 case DomKind::Object: {
25 QStringList f1 = i1.fields();
26 QStringList f2 = i2.fields();
27 f1.sort();
28 f2.sort();
29 qsizetype it1 = 0;
30 qsizetype it2 = 0;
31 while (it1 != f1.size() || it2 != f2.size()) {
32 QString k1, k2;
33 DomItem el1, el2;
34 bool hasK1 = it1 != f1.size();
35 bool filt1 = hasK1;
36 if (hasK1) {
37 k1 = f1.at(i: it1);
38 el1 = i1.field(name: k1);
39 filt1 = i1.isCanonicalChild(child&: el1) && filter(i1, PathEls::Field(k1), el1);
40 }
41 bool hasK2 = it2 != f2.size();
42 bool filt2 = hasK2;
43 if (hasK2) {
44 k2 = f2.at(i: it2);
45 el2 = i2.field(name: k2);
46 filt2 = i2.isCanonicalChild(child&: el2) && filter(i2, PathEls::Field(k2), el2);
47 }
48 // continue when filtering out
49 if (hasK1 && !filt1) {
50 ++it1;
51 if (hasK2 && !filt2)
52 ++it2;
53 continue;
54 } else if (hasK2 && !filt2) {
55 ++it2;
56 continue;
57 }
58 if (filt1 && filt2 && k1 == k2) {
59 if (!domCompare(i1&: el1, i2&: el2, change, filter, basePath: basePath.field(name: k1)))
60 return false;
61 ++it1;
62 ++it2;
63 } else if (!hasK1 || (hasK2 && k1 > k2)) {
64 if (!change(basePath.field(name: k1), DomItem::empty, el2))
65 return false;
66 ++it2;
67 } else {
68 if (!change(basePath.field(name: k1), el1, DomItem::empty))
69 return false;
70 ++it1;
71 }
72 }
73 } break;
74 case DomKind::Map: {
75 QStringList f1 = i1.sortedKeys();
76 QStringList f2 = i2.sortedKeys();
77 qsizetype it1 = 0;
78 qsizetype it2 = 0;
79 while (it1 != f1.size() || it2 != f2.size()) {
80 QString k1, k2;
81 DomItem el1, el2;
82 bool hasK1 = it1 != f1.size();
83 bool filt1 = hasK1;
84 if (hasK1) {
85 k1 = f1.at(i: it1);
86 el1 = i1.key(name: k1);
87 filt1 = i1.isCanonicalChild(child&: el1) && filter(i1, PathEls::Key(k1), el1);
88 }
89 bool hasK2 = it2 != f2.size();
90 bool filt2 = hasK2;
91 if (hasK2) {
92 k2 = f2.at(i: it2);
93 el2 = i2.key(name: k2);
94 filt2 = i2.isCanonicalChild(child&: el2) && filter(i2, PathEls::Key(k2), el2);
95 }
96 // continue when filtering out
97 if (hasK1 && !filt1) {
98 ++it1;
99 if (hasK2 && !filt2)
100 ++it2;
101 continue;
102 } else if (hasK2 && !filt2) {
103 ++it2;
104 continue;
105 }
106 if (filt1 && filt2 && k1 == k2) {
107 if (!domCompare(i1&: el1, i2&: el2, change, filter, basePath: basePath.key(name: k1)))
108 return false;
109 ++it1;
110 ++it2;
111 } else if (!hasK1 || (hasK2 && k1 > k2)) {
112 if (!change(basePath.key(name: k1), DomItem::empty, el2))
113 return false;
114 ++it2;
115 } else {
116 if (!change(basePath.key(name: k1), el1, DomItem::empty))
117 return false;
118 ++it1;
119 }
120 }
121 } break;
122 case DomKind::List: {
123 // we could support smarter matching keeping filtering in account like map
124 // currently it is just a simple index by index comparison
125 index_type len1 = i1.indexes();
126 index_type len2 = i2.indexes();
127 if (len1 != len2)
128 return change(basePath, i1, i2);
129 for (index_type i = 0; i < len1; ++i) {
130 DomItem v1 = i1.index(i);
131 DomItem v2 = i2.index(i);
132 if (filter(i1, PathEls::Index(i), v1) && filter(i2, PathEls::Index(i), v2)) {
133 DomItem el1 = i1.index(i);
134 DomItem el2 = i2.index(i);
135 if (i1.isCanonicalChild(child&: el1) && i2.isCanonicalChild(child&: el2)
136 && !domCompare(i1&: el1, i2&: el2, change, filter, basePath: basePath.index(i)))
137 return false;
138 }
139 }
140 } break;
141 case DomKind::Value: {
142 QCborValue v1 = i1.value();
143 QCborValue v2 = i2.value();
144 if (v1 != v2)
145 return change(basePath, i1, i2);
146 } break;
147 case DomKind::ScriptElement: {
148 // TODO: implement me
149 return false;
150
151 } break;
152 }
153 }
154 return true;
155}
156
157QStringList
158domCompareStrList(DomItem &i1, DomItem &i2,
159 function_ref<bool(DomItem &, const PathEls::PathComponent &, DomItem &) const> filter,
160 DomCompareStrList stopAtFirstDiff)
161{
162 QStringList res;
163 bool hasDiff = false;
164 domCompare(
165 i1, i2,
166 change: [&res, &hasDiff, stopAtFirstDiff](Path p, DomItem &j1, DomItem &j2) {
167 hasDiff = true;
168 if (!j1) {
169 res.append(QStringLiteral("- %1\n").arg(a: p.toString()));
170 } else if (!j2) {
171 res.append(QStringLiteral("+ %1\n").arg(a: p.toString()));
172 } else {
173 DomKind k1 = j1.domKind();
174 DomKind k2 = j2.domKind();
175 if (k1 != k2) {
176 res.append(
177 QStringLiteral("- %1 %2\n").arg(args: p.toString(), args: domKindToString(k: k1)));
178 res.append(
179 QStringLiteral("+ %1 %2\n").arg(args: p.toString(), args: domKindToString(k: k2)));
180 } else {
181 switch (k1) {
182 case DomKind::Empty:
183 case DomKind::Object:
184 case DomKind::Map:
185 Q_ASSERT(false);
186 break;
187 case DomKind::List: {
188 index_type len1 = j1.indexes();
189 index_type len2 = j2.indexes();
190 res.append(QStringLiteral("- %1 #%2\n").arg(a: p.toString()).arg(a: len1));
191 res.append(QStringLiteral("+ %1 #%2\n").arg(a: p.toString()).arg(a: len2));
192 } break;
193 case DomKind::Value: {
194 QCborValue v1 = j1.value();
195 QCborValue v2 = j2.value();
196 auto t1 = v1.type();
197 auto t2 = v2.type();
198 if (t1 != t2) {
199 res.append(QStringLiteral("- %1 type(%2)\n")
200 .arg(a: p.toString())
201 .arg(a: int(t1)));
202 res.append(QStringLiteral("+ %1 type(%2)\n")
203 .arg(a: p.toString())
204 .arg(a: int(t2)));
205 } else {
206 res.append(QStringLiteral("- %1 value(%2)\n")
207 .arg(a: p.toString())
208 .arg(a: j1.toString()));
209 res.append(QStringLiteral("+ %1 value(%2)\n")
210 .arg(a: p.toString())
211 .arg(a: j2.toString()));
212 }
213 } break;
214 case DomKind::ScriptElement: {
215 // implement me
216 break;
217 }
218 }
219 }
220 }
221 return (stopAtFirstDiff == DomCompareStrList::AllDiffs);
222 },
223 filter);
224 if (hasDiff && res.isEmpty()) // should never happen
225 res.append(QStringLiteral(u"Had changes!"));
226 return res;
227}
228
229} // end namespace Dom
230} // end namespace QQmlJS
231
232QT_END_NAMESPACE
233

source code of qtdeclarative/src/qmldom/qqmldomcompare.cpp