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