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
11QT_BEGIN_NAMESPACE
12namespace QQmlJS {
13namespace Dom {
14class ErrorMessage;
15
16namespace 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
25It can be created either from a string, with the path static functions
26or by modifying an existing path
27\code
28Path qmlFilePath =
29 Path::fromString(u"$env.qmlFilesByPath[\"/path/to/file\"]");
30Path imports = qmlFilePath.subField(u"imports")
31Path currentComponentImports = Path::current(u"component").subField(u"imports");
32\endcode
33
34This is a way to refer to elements in the Dom models that is not dependent from the source location,
35and thus can be used also be used in visual tools.
36A Path is quite stable toward reallocations or changes in the Dom model, and accessing it is safe
37even when "dangling", thus it is a good long term reference to an element in the Dom model.
38
39Path objects are a value type that have a shared pointer to extra data if needed, thus one should
40use them as value objects.
41The implementation has still optimization potential, but the behavior for the user should be already
42the final one.
43
44Path is both a range, and a single element (a bit like strings and characters in python).
45
46The 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
61The 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
77void Base::dump(Sink sink) const {
78 if (hasSquareBrackets())
79 sink(u"[");
80 sink(name());
81 if (hasSquareBrackets())
82 sink(u"]");
83}
84
85Filter::Filter(function<bool(DomItem)> f, QStringView filterDescription): filterFunction(f), filterDescription(filterDescription) {}
86
87QString Filter::name() const {
88 return QLatin1String("?(%1)").arg(args: filterDescription); }
89
90bool 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
97enum class ParserState{
98 Start,
99 IndexOrKey,
100 End
101};
102
103PathComponent::~PathComponent(){
104}
105
106int 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
172const 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
193Path Path::operator[](int i) const
194{
195 return mid(offset: i,length: 1);
196}
197
198QQmlJS::Dom::Path::operator bool() const
199{
200 return length() != 0;
201}
202
203PathIterator Path::begin() const
204{
205 return PathIterator{.currentEl: *this};
206}
207
208PathIterator Path::end() const
209{
210 return PathIterator();
211}
212
213PathRoot 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
221PathCurrent 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
229Path::Kind Path::headKind() const
230{
231 if (m_length == 0)
232 return Path::Kind::Empty;
233 return component(i: 0).kind();
234}
235
236QString Path::headName() const
237{
238 return component(i: 0).name();
239}
240
241bool Path::checkHeadName(QStringView name) const
242{
243 return component(i: 0).checkName(s: name);
244}
245
246index_type Path::headIndex(index_type defaultValue) const
247{
248 return component(i: 0).index(defaultValue);
249}
250
251function<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
260Path Path::head() const
261{
262 return mid(offset: 0,length: 1);
263}
264
265Path Path::last() const
266{
267 return mid(offset: m_length-1, length: 1);
268}
269
270Source 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
282bool 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
291bool 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
300Path 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
522Path 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
528Path 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
534Path 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
540Path 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
547Path 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
553Path 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
559Path 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
567Path 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
574Path 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
580Path 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
586Path 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
592Path Path::Empty()
593{
594 return Path();
595}
596
597Path 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
605Path 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
612Path 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
620Path 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
628Path Path::key(QStringView name) const
629{
630 return key(name: name.toString());
631}
632
633Path 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
641Path 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
649Path 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
656Path 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
664Path 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
670Path 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
677Path 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
685Path 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
759Path 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
771Path Path::expandBack() const
772{
773 if (m_endOffset > 0)
774 return Path(0, m_length + m_endOffset, m_data);
775 return *this;
776}
777
778Path &Path::operator++()
779{
780 if (m_length > 0)
781 m_length -= 1;
782 return *this;
783}
784
785Path Path::operator++(int)
786{
787 Path res = *this;
788 if (m_length > 0)
789 m_length -= 1;
790 return res;
791}
792
793int 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
811Path::Path(quint16 endOffset, quint16 length, std::shared_ptr<PathEls::PathData> data)
812 :m_endOffset(endOffset), m_length(length), m_data(data)
813{
814}
815
816Path 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
837Path 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
897ErrorGroups Path::myErrors()
898{
899 static ErrorGroups res = {.groups: {NewErrorGroup("PathParsing")}};
900 return res;
901}
902
903void 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
917QString 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
926Path 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
933Path 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
940Path 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
949Path Path::mid(int offset) const
950{
951 return mid(offset, length: m_length - offset);
952}
953
954Path 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
964QT_END_NAMESPACE
965
966#include "moc_qqmldompath_p.cpp"
967

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