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 "qqmldomitem_p.h" |
4 | #include "qqmldomerrormessage_p.h" |
5 | #include <QtCore/QDebug> |
6 | #include <QtCore/QTextStream> |
7 | #include <QtCore/QChar> |
8 | |
9 | #include <cstdint> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | namespace QQmlJS { |
13 | namespace Dom { |
14 | class ErrorMessage; |
15 | |
16 | namespace PathEls { |
17 | |
18 | /*! |
19 | \internal |
20 | \class QQmljs::Dom::QmlPath::Path |
21 | |
22 | \brief Represents an immutable JsonPath like path in the Qml code model (from a DomItem to another |
23 | DomItem) |
24 | |
25 | It can be created either from a string, with the path static functions |
26 | or by modifying an existing path |
27 | \code |
28 | Path qmlFilePath = |
29 | Path::fromString(u"$env.qmlFilesByPath[\"/path/to/file\"]"); |
30 | Path imports = qmlFilePath.subField(u"imports") |
31 | Path currentComponentImports = Path::current(u"component").subField(u"imports"); |
32 | \endcode |
33 | |
34 | This is a way to refer to elements in the Dom models that is not dependent from the source location, |
35 | and thus can be used also be used in visual tools. |
36 | A Path is quite stable toward reallocations or changes in the Dom model, and accessing it is safe |
37 | even when "dangling", thus it is a good long term reference to an element in the Dom model. |
38 | |
39 | Path objects are a value type that have a shared pointer to extra data if needed, thus one should |
40 | use them as value objects. |
41 | The implementation has still optimization potential, but the behavior for the user should be already |
42 | the final one. |
43 | |
44 | Path is both a range, and a single element (a bit like strings and characters in python). |
45 | |
46 | The root contexts are: |
47 | \list |
48 | \li \l{$modules} All the known modules (even not imported), this is a global, rename independent |
49 | reference |
50 | \li \l{$cpp} The Cpp names (namespaces, and Cpp types) visible in the current component |
51 | \li \l{$libs} The plugins/libraries and their contents |
52 | \li \l{$top} A top level entry in the DOM model, either $env or $universe (stepping in the universe |
53 | one looses the reference to its environment) |
54 | \li \l{$env} The environment containing the currently available modules, i.e. the top level entry in |
55 | the DOM model |
56 | \li \l{$universe} The dom unverse used by ths environment, and potentially shared with others that |
57 | contains all the known parse entries, and also the latest, potentially invalid entries |
58 | \li \l{$} ? undecided, one the previous ones? |
59 | \endlist |
60 | |
61 | The current contexts are: |
62 | \list |
63 | \li \l{@obj} The current object (if in a map or list goes up until it is in the current object) . |
64 | \li \l{@component} The root object of the current component. |
65 | \li \l{@module} The current module instantiation. |
66 | \li \l{@ids} The ids in the current component. |
67 | \li \l{@types} All the types in the current component (reachable through imports, respecting renames) |
68 | \li \l{@lookupStrict} The strict lookup inside the current object: localJS, ids, properties, proto |
69 | properties, component, its properties, global context, oterwise error |
70 | \li \l{@lookupDynamic} The default lookup inside the current object: localJS, ids, properties, proto |
71 | properties, component, its properties, global context, .. |
72 | \li \l{@lookup} Either lookupStrict or lookupDynamic depending on the current component and context. |
73 | \li \l{@} ? undecided, one the previous ones |
74 | \endlist |
75 | */ |
76 | |
77 | void Base::dump(const Sink &sink, const QString &name, bool hasSquareBrackets) const { |
78 | if (hasSquareBrackets) |
79 | sink(u"["); |
80 | sink(name); |
81 | if (hasSquareBrackets) |
82 | sink(u"]"); |
83 | } |
84 | |
85 | Filter::Filter(const function<bool(const DomItem &)> &f, QStringView filterDescription) |
86 | : filterFunction(f), filterDescription(filterDescription) {} |
87 | |
88 | QString Filter::name() const { |
89 | return QLatin1String("?(%1)").arg(args: filterDescription); } |
90 | |
91 | bool Filter::checkName(QStringView s) const |
92 | { |
93 | return s.startsWith(s: u"?(") |
94 | && s.mid(pos: 2, n: s.size()-3) == filterDescription |
95 | && s.endsWith(s: u")"); |
96 | } |
97 | |
98 | enum class ParserState{ |
99 | Start, |
100 | IndexOrKey, |
101 | End |
102 | }; |
103 | |
104 | int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2) |
105 | { |
106 | int k1 = static_cast<int>(p1.kind()); |
107 | int k2 = static_cast<int>(p2.kind()); |
108 | if (k1 < k2) |
109 | return -1; |
110 | if (k1 > k2) |
111 | return 1; |
112 | switch (p1.kind()) { |
113 | case Kind::Empty: |
114 | return 0; |
115 | case Kind::Field: |
116 | return std::get<Field>(v: p1.m_data).fieldName.compare(other: std::get<Field>(v: p2.m_data).fieldName); |
117 | case Kind::Index: |
118 | if (std::get<Index>(v: p1.m_data).indexValue < std::get<Index>(v: p2.m_data).indexValue) |
119 | return -1; |
120 | if (std::get<Index>(v: p1.m_data).indexValue > std::get<Index>(v: p2.m_data).indexValue) |
121 | return 1; |
122 | return 0; |
123 | case Kind::Key: |
124 | return std::get<Key>(v: p1.m_data).keyValue.compare(s: std::get<Key>(v: p2.m_data).keyValue); |
125 | case Kind::Root: |
126 | { |
127 | PathRoot k1 = std::get<Root>(v: p1.m_data).contextKind; |
128 | PathRoot k2 = std::get<Root>(v: p2.m_data).contextKind; |
129 | if (k1 == PathRoot::Env || k1 == PathRoot::Universe) |
130 | k1 = PathRoot::Top; |
131 | if (k2 == PathRoot::Env || k2 == PathRoot::Universe) |
132 | k2 = PathRoot::Top; |
133 | int c = int(k1) - int(k2); |
134 | if (c != 0) |
135 | return c; |
136 | return std::get<Root>(v: p1.m_data).contextName.compare(other: std::get<Root>(v: p2.m_data).contextName); |
137 | } |
138 | case Kind::Current: |
139 | { |
140 | int c = int(std::get<Current>(v: p1.m_data).contextKind) |
141 | - int(std::get<Current>(v: p2.m_data).contextKind); |
142 | if (c != 0) |
143 | return c; |
144 | return std::get<Current>(v: p1.m_data).contextName |
145 | .compare(other: std::get<Current>(v: p2.m_data).contextName); |
146 | } |
147 | case Kind::Any: |
148 | return 0; |
149 | case Kind::Filter: |
150 | { |
151 | int c = std::get<Filter>(v: p1.m_data).filterDescription |
152 | .compare(other: std::get<Filter>(v: p2.m_data).filterDescription); |
153 | if (c != 0) |
154 | return c; |
155 | if (std::get<Filter>(v: p1.m_data).filterDescription.startsWith(s: u"<")) { |
156 | // assuming non comparable native code (target comparison is not portable) |
157 | auto pp1 = &p1; |
158 | auto pp2 = &p2; |
159 | if (pp1 < pp2) |
160 | return -1; |
161 | if (pp1 > pp2) |
162 | return 1; |
163 | } |
164 | return 0; |
165 | } |
166 | } |
167 | Q_ASSERT(false && "unexpected PathComponent in PathComponent::cmp"); |
168 | return 0; |
169 | } |
170 | |
171 | } // namespace PathEls |
172 | |
173 | const PathEls::PathComponent &Path::component(int i) const |
174 | { |
175 | static Component emptyComponent; |
176 | if (i < 0) |
177 | i += m_length; |
178 | if (i >= m_length || i < 0) { |
179 | Q_ASSERT(false && "index out of bounds"); |
180 | return emptyComponent; |
181 | } |
182 | i = i - m_length - m_endOffset; |
183 | auto data = m_data.get(); |
184 | while (data) { |
185 | i += data->components.size(); |
186 | if (i >= 0) |
187 | return std::as_const(t&: data)->components[i]; |
188 | data = data->parent.get(); |
189 | } |
190 | Q_ASSERT(false && "Invalid data reached while resolving a seemengly valid index in Path (inconsisten Path object)"); |
191 | return emptyComponent; |
192 | } |
193 | |
194 | Path Path::operator[](int i) const |
195 | { |
196 | return mid(offset: i,length: 1); |
197 | } |
198 | |
199 | QQmlJS::Dom::Path::operator bool() const |
200 | { |
201 | return length() != 0; |
202 | } |
203 | |
204 | PathIterator Path::begin() const |
205 | { |
206 | return PathIterator{.currentEl: *this}; |
207 | } |
208 | |
209 | PathIterator Path::end() const |
210 | { |
211 | return PathIterator(); |
212 | } |
213 | |
214 | PathRoot Path::headRoot() const |
215 | { |
216 | auto &comp = component(i: 0); |
217 | if (PathEls::Root const * r = comp.asRoot()) |
218 | return r->contextKind; |
219 | return PathRoot::Other; |
220 | } |
221 | |
222 | PathCurrent Path::headCurrent() const |
223 | { |
224 | auto comp = component(i: 0); |
225 | if (PathEls::Current const * c = comp.asCurrent()) |
226 | return c->contextKind; |
227 | return PathCurrent::Other; |
228 | } |
229 | |
230 | Path::Kind Path::headKind() const |
231 | { |
232 | if (m_length == 0) |
233 | return Path::Kind::Empty; |
234 | return component(i: 0).kind(); |
235 | } |
236 | |
237 | QString Path::headName() const |
238 | { |
239 | return component(i: 0).name(); |
240 | } |
241 | |
242 | bool Path::checkHeadName(QStringView name) const |
243 | { |
244 | return component(i: 0).checkName(s: name); |
245 | } |
246 | |
247 | index_type Path::headIndex(index_type defaultValue) const |
248 | { |
249 | return component(i: 0).index(defaultValue); |
250 | } |
251 | |
252 | function<bool(const DomItem &)> Path::headFilter() const |
253 | { |
254 | auto &comp = component(i: 0); |
255 | if (PathEls::Filter const * f = comp.asFilter()) { |
256 | return f->filterFunction; |
257 | } |
258 | return {}; |
259 | } |
260 | |
261 | Path Path::head() const |
262 | { |
263 | return mid(offset: 0,length: 1); |
264 | } |
265 | |
266 | Path Path::last() const |
267 | { |
268 | return mid(offset: m_length-1, length: 1); |
269 | } |
270 | |
271 | Source Path::split() const |
272 | { |
273 | int i = length(); |
274 | while (i>0) { |
275 | const PathEls::PathComponent &c=component(i: --i); |
276 | if (c.kind() == Kind::Field || c.kind() == Kind::Root || c.kind() == Kind::Current) { |
277 | return Source{.pathToSource: mid(offset: 0, length: i), .pathFromSource: mid(offset: i)}; |
278 | } |
279 | } |
280 | return Source{.pathToSource: Path(), .pathFromSource: *this}; |
281 | } |
282 | |
283 | bool inQString(QStringView el, const QString &base) |
284 | { |
285 | if (quintptr(base.constData()) > quintptr(el.begin()) |
286 | || quintptr(base.constData() + base.size()) < quintptr(el.begin())) |
287 | return false; |
288 | ptrdiff_t diff = base.constData() - el.begin(); |
289 | return diff >= 0 && diff < base.size(); |
290 | } |
291 | |
292 | bool inQString(const QString &el, const QString &base) |
293 | { |
294 | if (quintptr(base.constData()) > quintptr(el.constData()) |
295 | || quintptr(base.constData() + base.size()) < quintptr(el.constData())) |
296 | return false; |
297 | ptrdiff_t diff = base.constData() - el.constData(); |
298 | return diff >= 0 && diff < base.size() && diff + el.size() < base.size(); |
299 | } |
300 | |
301 | Path Path::fromString(QStringView s, const ErrorHandler &errorHandler) |
302 | { |
303 | if (s.isEmpty()) |
304 | return Path(); |
305 | int len=1; |
306 | const QChar dot = QChar::fromLatin1(c: '.'); |
307 | const QChar lsBrace = QChar::fromLatin1(c: '['); |
308 | const QChar rsBrace = QChar::fromLatin1(c: ']'); |
309 | const QChar dollar = QChar::fromLatin1(c: '$'); |
310 | const QChar at = QChar::fromLatin1(c: '@'); |
311 | const QChar quote = QChar::fromLatin1(c: '"'); |
312 | const QChar backslash = QChar::fromLatin1(c: '\\'); |
313 | const QChar underscore = QChar::fromLatin1(c: '_'); |
314 | const QChar tilda = QChar::fromLatin1(c: '~'); |
315 | for (int i=0; i < s.size(); ++i) |
316 | if (s.at(n: i) == lsBrace || s.at(n: i) == dot) |
317 | ++len; |
318 | QVector<Component> components; |
319 | components.reserve(asize: len); |
320 | int i = 0; |
321 | int i0 = 0; |
322 | PathEls::ParserState state = PathEls::ParserState::Start; |
323 | QStringList strVals; |
324 | while (i < s.size()) { |
325 | // skip space |
326 | while (i < s.size() && s.at(n: i).isSpace()) |
327 | ++i; |
328 | if (i >= s.size()) |
329 | break; |
330 | QChar c = s.at(n: i++); |
331 | switch (state) { |
332 | case PathEls::ParserState::Start: |
333 | if (c == dollar) { |
334 | i0 = i; |
335 | while (i < s.size() && s.at(n: i).isLetterOrNumber()){ |
336 | ++i; |
337 | } |
338 | components.append(t: Component(PathEls::Root(s.mid(pos: i0,n: i-i0)))); |
339 | state = PathEls::ParserState::End; |
340 | } else if (c == at) { |
341 | i0 = i; |
342 | while (i < s.size() && s.at(n: i).isLetterOrNumber()){ |
343 | ++i; |
344 | } |
345 | components.append(t: Component(PathEls::Current(s.mid(pos: i0,n: i-i0)))); |
346 | state = PathEls::ParserState::End; |
347 | } else if (c.isLetter()) { |
348 | myErrors().warning(message: tr(sourceText: "Field expressions should start with a dot, even when at the start of the path %1.") |
349 | .arg(a: s)).handle(errorHandler); |
350 | return Path(); |
351 | } else { |
352 | --i; |
353 | state = PathEls::ParserState::End; |
354 | } |
355 | break; |
356 | case PathEls::ParserState::IndexOrKey: |
357 | if (c.isDigit()) { |
358 | i0 = i-1; |
359 | while (i < s.size() && s.at(n: i).isDigit()) |
360 | ++i; |
361 | bool ok; |
362 | components.append(t: Component(static_cast<index_type>(s.mid(pos: i0,n: i-i0).toString() |
363 | .toLongLong(ok: &ok)))); |
364 | if (!ok) { |
365 | myErrors().warning(message: tr(sourceText: "Error extracting integer from '%1' at char %2.") |
366 | .arg(a: s.mid(pos: i0,n: i-i0)) |
367 | .arg(a: QString::number(i0))).handle(errorHandler); |
368 | } |
369 | } else if (c.isLetter() || c == tilda || c == underscore) { |
370 | i0 = i-1; |
371 | while (i < s.size() && (s.at(n: i).isLetterOrNumber() || s.at(n: i) == underscore || s.at(n: i) == tilda)) |
372 | ++i; |
373 | components.append(t: Component(PathEls::Key(s.mid(pos: i0, n: i - i0).toString()))); |
374 | } else if (c == quote) { |
375 | i0 = i; |
376 | QString strVal; |
377 | bool properEnd = false; |
378 | while (i < s.size()) { |
379 | c = s.at(n: i); |
380 | if (c == quote) { |
381 | properEnd = true; |
382 | break; |
383 | } else if (c == backslash) { |
384 | strVal.append(s: s.mid(pos: i0, n: i - i0).toString()); |
385 | c = s.at(n: ++i); |
386 | i0 = i + 1; |
387 | if (c == QChar::fromLatin1(c: 'n')) |
388 | strVal.append(c: QChar::fromLatin1(c: '\n')); |
389 | else if (c == QChar::fromLatin1(c: 'r')) |
390 | strVal.append(c: QChar::fromLatin1(c: '\r')); |
391 | else if (c == QChar::fromLatin1(c: 't')) |
392 | strVal.append(c: QChar::fromLatin1(c: '\t')); |
393 | else |
394 | strVal.append(c); |
395 | } |
396 | ++i; |
397 | } |
398 | if (properEnd) { |
399 | strVal.append(s: s.mid(pos: i0, n: i - i0).toString()); |
400 | ++i; |
401 | } else { |
402 | myErrors().error(message: tr(sourceText: "Unclosed quoted string at char %1.") |
403 | .arg(a: QString::number(i - 1))).handle(errorHandler); |
404 | return Path(); |
405 | } |
406 | components.append(t: PathEls::Key(strVal)); |
407 | } else if (c == QChar::fromLatin1(c: '*')) { |
408 | components.append(t: Component(PathEls::Any())); |
409 | } else if (c == QChar::fromLatin1(c: '?')) { |
410 | while (i < s.size() && s.at(n: i).isSpace()) |
411 | ++i; |
412 | if (i >= s.size() || s.at(n: i) != QChar::fromLatin1(c: '(')) { |
413 | myErrors().error(message: tr(sourceText: "Expected a brace in filter after the question mark (at char %1).") |
414 | .arg(a: QString::number(i))).handle(errorHandler); |
415 | return Path(); |
416 | } |
417 | i0 = ++i; |
418 | while (i < s.size() && s.at(n: i) != QChar::fromLatin1(c: ')')) ++i; // check matching braces when skipping?? |
419 | if (i >= s.size() || s.at(n: i) != QChar::fromLatin1(c: ')')) { |
420 | myErrors().error(message: tr(sourceText: "Expected a closing brace in filter after the question mark (at char %1).") |
421 | .arg(a: QString::number(i))).handle(errorHandler); |
422 | return Path(); |
423 | } |
424 | //auto filterDesc = s.mid(i0,i-i0); |
425 | ++i; |
426 | myErrors().error(message: tr(sourceText: "Filter from string not yet implemented.")).handle(errorHandler); |
427 | return Path(); |
428 | } else { |
429 | myErrors().error(message: tr(sourceText: "Unexpected character '%1' after square bracket at %2.") |
430 | .arg(a: c).arg(a: i-1)).handle(errorHandler); |
431 | return Path(); |
432 | } |
433 | while (i < s.size() && s.at(n: i).isSpace()) ++i; |
434 | if (i >= s.size() || s.at(n: i) != rsBrace) { |
435 | myErrors().error(message: tr(sourceText: "square braces misses closing brace at char %1.") |
436 | .arg(a: QString::number(i))).handle(errorHandler); |
437 | return Path(); |
438 | } else { |
439 | ++i; |
440 | } |
441 | state = PathEls::ParserState::End; |
442 | break; |
443 | case PathEls::ParserState::End: |
444 | if (c == dot) { |
445 | while (i < s.size() && s.at(n: i).isSpace()) ++i; |
446 | if (i == s.size()) { |
447 | components.append(t: Component()); |
448 | state = PathEls::ParserState::End; |
449 | } else if (s.at(n: i).isLetter() || s.at(n: i) == underscore || s.at(n: i) == tilda) { |
450 | i0 = i; |
451 | while (i < s.size() && (s.at(n: i).isLetterOrNumber() || s.at(n: i) == underscore || s.at(n: i) == tilda)) { |
452 | ++i; |
453 | } |
454 | components.append(t: Component(PathEls::Field(s.mid(pos: i0,n: i-i0)))); |
455 | state = PathEls::ParserState::End; |
456 | } else if (s.at(n: i).isDigit()) { |
457 | i0 = i; |
458 | while (i < s.size() && s.at(n: i).isDigit()){ |
459 | ++i; |
460 | } |
461 | bool ok; |
462 | components.append(t: Component(static_cast<index_type>(s.mid(pos: i0,n: i-i0).toString().toLongLong(ok: &ok)))); |
463 | if (!ok) { |
464 | myErrors().warning(message: tr(sourceText: "Error extracting integer from '%1' at char %2.") |
465 | .arg(a: s.mid(pos: i0,n: i-i0)) |
466 | .arg(a: QString::number(i0))).handle(errorHandler); |
467 | return Path(); |
468 | } else { |
469 | myErrors().info(message: tr(sourceText: "Index should use square brackets and not a dot (at char %1).") |
470 | .arg(a: QString::number(i0))).handle(errorHandler); |
471 | } |
472 | state = PathEls::ParserState::End; |
473 | } else if (s.at(n: i) == dot || s.at(n: i) == lsBrace) { |
474 | components.append(t: Component()); |
475 | state = PathEls::ParserState::End; |
476 | } else if (s.at(n: i) == at) { |
477 | i0 = ++i; |
478 | while (i < s.size() && s.at(n: i).isLetterOrNumber()){ |
479 | ++i; |
480 | } |
481 | components.append(t: Component(PathEls::Current(s.mid(pos: i0,n: i-i0)))); |
482 | state = PathEls::ParserState::End; |
483 | } else if (s.at(n: i) == dollar) { |
484 | i0 = ++i; |
485 | while (i < s.size() && s.at(n: i).isLetterOrNumber()){ |
486 | ++i; |
487 | } |
488 | components.append(t: Component(PathEls::Root(s.mid(pos: i0,n: i-i0)))); |
489 | state = PathEls::ParserState::End; |
490 | } else { |
491 | c=s.at(n: i); |
492 | myErrors().error(message: tr(sourceText: "Unexpected character '%1' after dot (at char %2).") |
493 | .arg(a: QStringView(&c,1)) |
494 | .arg(a: QString::number(i-1))).handle(errorHandler); |
495 | return Path(); |
496 | } |
497 | } else if (c == lsBrace) { |
498 | state = PathEls::ParserState::IndexOrKey; |
499 | } else { |
500 | myErrors().error(message: tr(sourceText: "Unexpected character '%1' after end of component (char %2).") |
501 | .arg(a: QStringView(&c,1)) |
502 | .arg(a: QString::number(i-1))).handle(errorHandler); |
503 | return Path(); |
504 | } |
505 | break; |
506 | } |
507 | } |
508 | switch (state) { |
509 | case PathEls::ParserState::Start: |
510 | return Path(); |
511 | case PathEls::ParserState::IndexOrKey: |
512 | errorHandler(myErrors().error(message: tr(sourceText: "unclosed square brace at end."))); |
513 | |
514 | return Path(); |
515 | case PathEls::ParserState::End: |
516 | return Path(0, components.size(), std::make_shared<PathEls::PathData>( |
517 | args&: strVals, args&: components)); |
518 | } |
519 | Q_ASSERT(false && "Unexpected state in Path::fromString"); |
520 | return Path(); |
521 | } |
522 | |
523 | Path Path::Root(PathRoot s) |
524 | { |
525 | return Path(0,1,std::make_shared<PathEls::PathData>( |
526 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Root(s))))); |
527 | } |
528 | |
529 | Path Path::Root(const QString &s) |
530 | { |
531 | return Path(0,1,std::make_shared<PathEls::PathData>( |
532 | args: QStringList(s), args: QVector<Component>(1,Component(PathEls::Root(s))))); |
533 | } |
534 | |
535 | Path Path::Index(index_type i) |
536 | { |
537 | return Path(0,1,std::make_shared<PathEls::PathData>( |
538 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Index(i))))); |
539 | } |
540 | |
541 | Path Path::Root(QStringView s) |
542 | { |
543 | return Path(0,1,std::make_shared<PathEls::PathData>( |
544 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Root(s))))); |
545 | } |
546 | |
547 | |
548 | Path Path::Field(QStringView s) |
549 | { |
550 | return Path(0,1,std::make_shared<PathEls::PathData>( |
551 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Field(s))))); |
552 | } |
553 | |
554 | Path Path::Field(const QString &s) |
555 | { |
556 | return Path(0,1,std::make_shared<PathEls::PathData>( |
557 | args: QStringList(s), args: QVector<Component>(1,Component(PathEls::Field(s))))); |
558 | } |
559 | |
560 | Path Path::Key(QStringView s) |
561 | { |
562 | return Path( |
563 | 0, 1, |
564 | std::make_shared<PathEls::PathData>( |
565 | args: QStringList(), args: QVector<Component>(1, Component(PathEls::Key(s.toString()))))); |
566 | } |
567 | |
568 | Path Path::Key(const QString &s) |
569 | { |
570 | return Path(0, 1, |
571 | std::make_shared<PathEls::PathData>( |
572 | args: QStringList(), args: QVector<Component>(1, Component(PathEls::Key(s))))); |
573 | } |
574 | |
575 | Path Path::Current(PathCurrent s) |
576 | { |
577 | return Path(0,1,std::make_shared<PathEls::PathData>( |
578 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Current(s))))); |
579 | } |
580 | |
581 | Path Path::Current(const QString &s) |
582 | { |
583 | return Path(0,1,std::make_shared<PathEls::PathData>( |
584 | args: QStringList(s), args: QVector<Component>(1,Component(PathEls::Current(s))))); |
585 | } |
586 | |
587 | Path Path::Current(QStringView s) |
588 | { |
589 | return Path(0,1,std::make_shared<PathEls::PathData>( |
590 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Current(s))))); |
591 | } |
592 | |
593 | Path Path::Empty() |
594 | { |
595 | return Path(); |
596 | } |
597 | |
598 | Path Path::empty() const |
599 | { |
600 | if (m_endOffset != 0) |
601 | return noEndOffset().empty(); |
602 | return Path(0,m_length+1,std::make_shared<PathEls::PathData>( |
603 | args: QStringList(), args: QVector<Component>(1,Component()), args: m_data)); |
604 | } |
605 | |
606 | Path Path::field(const QString &name) const |
607 | { |
608 | auto res = field(name: QStringView(name)); |
609 | res.m_data->strData.append(t: name); |
610 | return res; |
611 | } |
612 | |
613 | Path Path::field(QStringView name) const |
614 | { |
615 | if (m_endOffset != 0) |
616 | return noEndOffset().field(name); |
617 | return Path(0,m_length+1,std::make_shared<PathEls::PathData>( |
618 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Field(name))), args: m_data)); |
619 | } |
620 | |
621 | Path Path::key(const QString &name) const |
622 | { |
623 | if (m_endOffset != 0) |
624 | return noEndOffset().key(name); |
625 | return Path(0,m_length+1,std::make_shared<PathEls::PathData>( |
626 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Key(name))), args: m_data)); |
627 | } |
628 | |
629 | Path Path::key(QStringView name) const |
630 | { |
631 | return key(name: name.toString()); |
632 | } |
633 | |
634 | Path Path::index(index_type i) const |
635 | { |
636 | if (m_endOffset != 0) |
637 | return noEndOffset().index(i); |
638 | return Path(0,m_length+1,std::make_shared<PathEls::PathData>( |
639 | args: QStringList(), args: QVector<Component>(1,Component(i)), args: m_data)); |
640 | } |
641 | |
642 | Path Path::any() const |
643 | { |
644 | if (m_endOffset != 0) |
645 | return noEndOffset().any(); |
646 | return Path(0,m_length+1,std::make_shared<PathEls::PathData>( |
647 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Any())), args: m_data)); |
648 | } |
649 | |
650 | Path Path::filter(const function<bool(const DomItem &)> &filterF, const QString &desc) const |
651 | { |
652 | auto res = filter(filterF, desc: QStringView(desc)); |
653 | res.m_data->strData.append(t: desc); |
654 | return res; |
655 | } |
656 | |
657 | Path Path::filter(const function<bool(const DomItem &)> &filter, QStringView desc) const |
658 | { |
659 | if (m_endOffset != 0) |
660 | return noEndOffset().filter(filter, desc); |
661 | return Path(0,m_length+1,std::make_shared<PathEls::PathData>( |
662 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Filter(filter, desc))), args: m_data)); |
663 | } |
664 | |
665 | Path Path::current(PathCurrent s) const |
666 | { |
667 | return Path(0,m_length+1,std::make_shared<PathEls::PathData>( |
668 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Current(s))), args: m_data)); |
669 | } |
670 | |
671 | Path Path::current(const QString &s) const |
672 | { |
673 | auto res = current(s: QStringView(s)); |
674 | res.m_data->strData.append(t: s); |
675 | return res; |
676 | } |
677 | |
678 | Path Path::current(QStringView s) const |
679 | { |
680 | if (m_endOffset != 0) |
681 | return noEndOffset().current(s); |
682 | return Path(0,m_length+1,std::make_shared<PathEls::PathData>( |
683 | args: QStringList(), args: QVector<Component>(1,Component(PathEls::Current(s))), args: m_data)); |
684 | } |
685 | |
686 | Path Path::path(const Path &toAdd, bool avoidToAddAsBase) const |
687 | { |
688 | if (toAdd.length() == 0) |
689 | return *this; |
690 | int resLength = length() + toAdd.length(); |
691 | if (m_endOffset != 0) { |
692 | Path thisExtended = this->expandBack(); |
693 | if (thisExtended.length() > resLength) |
694 | thisExtended = thisExtended.mid(offset: 0, length: resLength); |
695 | Path added = thisExtended.mid(offset: length(), length: thisExtended.length() - length()); |
696 | if (added == toAdd.mid(offset: 0, length: toAdd.length())) { |
697 | if (resLength == thisExtended.length()) |
698 | return thisExtended; |
699 | else |
700 | return thisExtended.path(toAdd: toAdd.mid(offset: added.length(), length: resLength - thisExtended.length())); |
701 | } |
702 | } |
703 | if (!avoidToAddAsBase) { |
704 | Path toAddExtended = toAdd.expandFront(); |
705 | if (toAddExtended.length() >= resLength) { |
706 | toAddExtended = toAddExtended.mid(offset: toAddExtended.length() - resLength, length: resLength); |
707 | if (toAddExtended.mid(offset: 0,length: length()) == *this) |
708 | return toAddExtended; |
709 | } |
710 | } |
711 | QVector<Component> components; |
712 | components.reserve(asize: toAdd.length()); |
713 | QStringList addedStrs; |
714 | bool addHasStr = false; |
715 | auto data = toAdd.m_data.get(); |
716 | while (data) { |
717 | if (!data->strData.isEmpty()) { |
718 | addHasStr = true; |
719 | break; |
720 | } |
721 | data = data->parent.get(); |
722 | } |
723 | if (addHasStr) { |
724 | QStringList myStrs; |
725 | data = m_data.get(); |
726 | while (data) { |
727 | myStrs.append(l: data->strData); |
728 | data = data->parent.get(); |
729 | } |
730 | data = toAdd.m_data.get(); |
731 | while (data) { |
732 | for (int ij = 0; ij < data->strData.size(); ++ij) { |
733 | bool hasAlready = false; |
734 | for (int ii = 0; ii < myStrs.size() && !hasAlready; ++ii) |
735 | hasAlready = inQString(el: data->strData[ij], base: myStrs[ii]); |
736 | if (!hasAlready) |
737 | addedStrs.append(t: data->strData[ij]); |
738 | } |
739 | data = data->parent.get(); |
740 | } |
741 | } |
742 | QStringList toAddStrs; |
743 | for (int i = 0; i < toAdd.length(); ++i) { |
744 | components.append(t: toAdd.component(i)); |
745 | QStringView compStrView = toAdd.component(i).stringView(); |
746 | if (!compStrView.isEmpty()) { |
747 | for (int j = 0; j < addedStrs.size(); ++j) { |
748 | if (inQString(el: compStrView, base: addedStrs[j])) { |
749 | toAddStrs.append(t: addedStrs[j]); |
750 | addedStrs.removeAt(i: j); |
751 | break; |
752 | } |
753 | } |
754 | } |
755 | } |
756 | return Path(0, m_length + toAdd.length(), std::make_shared<PathEls::PathData>( |
757 | args&: toAddStrs, args&: components, args: ((m_endOffset == 0) ? m_data : noEndOffset().m_data))); |
758 | } |
759 | |
760 | Path Path::expandFront() const |
761 | { |
762 | int newLen = 0; |
763 | auto data = m_data.get(); |
764 | while (data) { |
765 | newLen += data->components.size(); |
766 | data = data->parent.get(); |
767 | } |
768 | newLen -= m_endOffset; |
769 | return Path(m_endOffset, newLen, m_data); |
770 | } |
771 | |
772 | Path Path::expandBack() const |
773 | { |
774 | if (m_endOffset > 0) |
775 | return Path(0, m_length + m_endOffset, m_data); |
776 | return *this; |
777 | } |
778 | |
779 | Path &Path::operator++() |
780 | { |
781 | if (m_length > 0) |
782 | m_length -= 1; |
783 | return *this; |
784 | } |
785 | |
786 | Path Path::operator++(int) |
787 | { |
788 | Path res = *this; |
789 | if (m_length > 0) |
790 | m_length -= 1; |
791 | return res; |
792 | } |
793 | |
794 | int Path::cmp(const Path &p1, const Path &p2) |
795 | { |
796 | // lexicographic ordering |
797 | const int lMin = qMin(a: p1.m_length, b: p2.m_length); |
798 | if (p1.m_data.get() == p2.m_data.get() && p1.m_endOffset == p2.m_endOffset && p1.m_length == p2.m_length) |
799 | return 0; |
800 | for (int i = 0; i < lMin; ++i) { |
801 | int c = Component::cmp(p1: p1.component(i), p2: p2.component(i)); |
802 | if (c != 0) |
803 | return c; |
804 | } |
805 | if (lMin < p2.m_length) |
806 | return -1; |
807 | if (p1.m_length > lMin) |
808 | return 1; |
809 | return 0; |
810 | } |
811 | |
812 | Path::Path(quint16 endOffset, quint16 length, const std::shared_ptr<PathEls::PathData> &data) |
813 | :m_endOffset(endOffset), m_length(length), m_data(data) |
814 | { |
815 | } |
816 | |
817 | Path Path::noEndOffset() const |
818 | { |
819 | if (m_length == 0) |
820 | return Path(); |
821 | if (m_endOffset == 0) |
822 | return *this; |
823 | // peel back |
824 | qint16 endOffset = m_endOffset; |
825 | std::shared_ptr<PathEls::PathData> lastData = m_data; |
826 | while (lastData && endOffset >= lastData->components.size()) { |
827 | endOffset -= lastData->components.size(); |
828 | lastData = lastData->parent; |
829 | } |
830 | if (endOffset > 0) { |
831 | Q_ASSERT(lastData && "Internal problem, reference to non existing PathData"); |
832 | return Path(0, m_length, std::make_shared<PathEls::PathData>( |
833 | args&: lastData->strData, args: lastData->components.mid(pos: 0, len: lastData->components.size() - endOffset), args&: lastData->parent)); |
834 | } |
835 | return Path(0, m_length, lastData); |
836 | } |
837 | |
838 | Path Path::appendComponent(const PathEls::PathComponent &c) |
839 | { |
840 | if (m_endOffset != 0) { |
841 | Path newP = noEndOffset(); |
842 | return newP.appendComponent(c); |
843 | } |
844 | if (m_data && m_data.use_count() != 1) { |
845 | // create a new path (otherwise paths linking to this will change) |
846 | Path newP(c); |
847 | newP.m_data->parent = m_data; |
848 | newP.m_length = static_cast<quint16>(m_length + 1); |
849 | return newP; |
850 | } |
851 | auto my_data = |
852 | (m_data ? m_data |
853 | : std::make_shared<PathEls::PathData>(args: QStringList(), |
854 | args: QVector<PathEls::PathComponent>())); |
855 | switch (c.kind()) { |
856 | case PathEls::Kind::Any: |
857 | case PathEls::Kind::Empty: |
858 | case PathEls::Kind::Index: |
859 | // no string |
860 | case PathEls::Kind::Field: |
861 | // string assumed to stay valid (Fields::...) |
862 | my_data->components.append(t: c); |
863 | break; |
864 | case PathEls::Kind::Current: |
865 | if (c.asCurrent()->contextKind == PathCurrent::Other) { |
866 | my_data->strData.append(t: c.asCurrent()->contextName.toString()); |
867 | my_data->components.append(t: PathEls::Current(my_data->strData.last())); |
868 | } else { |
869 | my_data->components.append(t: c); |
870 | } |
871 | break; |
872 | case PathEls::Kind::Filter: |
873 | if (!c.asFilter()->filterDescription.isEmpty()) { |
874 | my_data->strData.append(t: c.asFilter()->filterDescription.toString()); |
875 | my_data->components.append( |
876 | t: PathEls::Filter(c.asFilter()->filterFunction, my_data->strData.last())); |
877 | } else { |
878 | my_data->components.append(t: c); |
879 | } |
880 | break; |
881 | case PathEls::Kind::Key: |
882 | my_data->components.append(t: c); |
883 | break; |
884 | case PathEls::Kind::Root: |
885 | if (c.asRoot()->contextKind == PathRoot::Other) { |
886 | my_data->strData.append(t: c.asRoot()->contextName.toString()); |
887 | my_data->components.append(t: PathEls::Root(my_data->strData.last())); |
888 | } else { |
889 | my_data->components.append(t: c); |
890 | } |
891 | break; |
892 | } |
893 | if (m_data) |
894 | m_endOffset = 1; |
895 | return Path { 0, static_cast<quint16>(m_length + 1), my_data }; |
896 | } |
897 | |
898 | ErrorGroups Path::myErrors() |
899 | { |
900 | static ErrorGroups res = {.groups: {NewErrorGroup("PathParsing")}}; |
901 | return res; |
902 | } |
903 | |
904 | void Path::dump(const Sink &sink) const |
905 | { |
906 | bool first = true; |
907 | for (int i = 0; i < m_length; ++i) { |
908 | auto &c = component(i); |
909 | if (!c.hasSquareBrackets()) { |
910 | if (!first || (c.kind() != Kind::Root && c.kind() != Kind::Current)) |
911 | sink(u"."); |
912 | } |
913 | c.dump(sink); |
914 | first = false; |
915 | } |
916 | } |
917 | |
918 | QString Path::toString() const |
919 | { |
920 | QString res; |
921 | QTextStream stream(&res); |
922 | dump(sink: [&stream](QStringView str){ stream << str; }); |
923 | stream.flush(); |
924 | return res; |
925 | } |
926 | |
927 | Path Path::dropFront(int n) const |
928 | { |
929 | if (m_length > n && n >= 0) |
930 | return Path(m_endOffset, m_length - n, m_data); |
931 | return Path(); |
932 | } |
933 | |
934 | Path Path::dropTail(int n) const |
935 | { |
936 | if (m_length > n && n >= 0) |
937 | return Path(m_endOffset + n, m_length - n, m_data); |
938 | return Path(); |
939 | } |
940 | |
941 | Path Path::mid(int offset, int length) const |
942 | { |
943 | length = qMin(a: m_length - offset, b: length); |
944 | if (offset < 0 || offset >= m_length || length <= 0 || length > m_length) |
945 | return Path(); |
946 | int newEndOffset = m_endOffset + m_length - offset - length; |
947 | return Path(newEndOffset, length, m_data); |
948 | } |
949 | |
950 | Path Path::mid(int offset) const |
951 | { |
952 | return mid(offset, length: m_length - offset); |
953 | } |
954 | |
955 | Path Path::fromString(const QString &s, const ErrorHandler &errorHandler) |
956 | { |
957 | Path res = fromString(s: QStringView(s), errorHandler); |
958 | if (res.m_data) |
959 | res.m_data->strData.append(t: s); |
960 | return res; |
961 | } |
962 | |
963 | } // end namespace Dom |
964 | } // end namespace QQmlJS |
965 | QT_END_NAMESPACE |
966 | |
967 | #include "moc_qqmldompath_p.cpp" |
968 |
Definitions
- dump
- Filter
- name
- checkName
- ParserState
- cmp
- component
- operator[]
- operator bool
- begin
- end
- headRoot
- headCurrent
- headKind
- headName
- checkHeadName
- headIndex
- headFilter
- head
- last
- split
- inQString
- inQString
- fromString
- Root
- Root
- Index
- Root
- Field
- Field
- Key
- Key
- Current
- Current
- Current
- Empty
- empty
- field
- field
- key
- key
- index
- any
- filter
- filter
- current
- current
- current
- path
- expandFront
- expandBack
- operator++
- operator++
- cmp
- Path
- noEndOffset
- appendComponent
- myErrors
- dump
- toString
- dropFront
- dropTail
- mid
- mid
Start learning QML with our Intro Training
Find out more