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 | |
6 | QT_BEGIN_NAMESPACE |
7 | |
8 | namespace QQmlJS { |
9 | namespace Dom { |
10 | |
11 | bool 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 | |
157 | QStringList |
158 | domCompareStrList(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 | |
232 | QT_END_NAMESPACE |
233 | |