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(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
85Filter::Filter(const function<bool(const DomItem &)> &f, QStringView filterDescription)
86 : filterFunction(f), filterDescription(filterDescription) {}
87
88QString Filter::name() const {
89 return QLatin1String("?(%1)").arg(args: filterDescription); }
90
91bool 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
98enum class ParserState{
99 Start,
100 IndexOrKey,
101 End
102};
103
104int 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
173const 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
194Path Path::operator[](int i) const
195{
196 return mid(offset: i,length: 1);
197}
198
199QQmlJS::Dom::Path::operator bool() const
200{
201 return length() != 0;
202}
203
204PathIterator Path::begin() const
205{
206 return PathIterator{.currentEl: *this};
207}
208
209PathIterator Path::end() const
210{
211 return PathIterator();
212}
213
214PathRoot 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
222PathCurrent 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
230Path::Kind Path::headKind() const
231{
232 if (m_length == 0)
233 return Path::Kind::Empty;
234 return component(i: 0).kind();
235}
236
237QString Path::headName() const
238{
239 return component(i: 0).name();
240}
241
242bool Path::checkHeadName(QStringView name) const
243{
244 return component(i: 0).checkName(s: name);
245}
246
247index_type Path::headIndex(index_type defaultValue) const
248{
249 return component(i: 0).index(defaultValue);
250}
251
252function<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
261Path Path::head() const
262{
263 return mid(offset: 0,length: 1);
264}
265
266Path Path::last() const
267{
268 return mid(offset: m_length-1, length: 1);
269}
270
271Source 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
283bool 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
292bool 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
301Path 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
523Path 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
529Path 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
535Path 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
541Path 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
548Path 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
554Path 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
560Path 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
568Path 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
575Path 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
581Path 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
587Path 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
593Path Path::Empty()
594{
595 return Path();
596}
597
598Path 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
606Path 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
613Path 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
621Path 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
629Path Path::key(QStringView name) const
630{
631 return key(name: name.toString());
632}
633
634Path 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
642Path 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
650Path 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
657Path 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
665Path 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
671Path 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
678Path 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
686Path 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
760Path 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
772Path Path::expandBack() const
773{
774 if (m_endOffset > 0)
775 return Path(0, m_length + m_endOffset, m_data);
776 return *this;
777}
778
779Path &Path::operator++()
780{
781 if (m_length > 0)
782 m_length -= 1;
783 return *this;
784}
785
786Path Path::operator++(int)
787{
788 Path res = *this;
789 if (m_length > 0)
790 m_length -= 1;
791 return res;
792}
793
794int 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
812Path::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
817Path 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
838Path 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
898ErrorGroups Path::myErrors()
899{
900 static ErrorGroups res = {.groups: {NewErrorGroup("PathParsing")}};
901 return res;
902}
903
904void 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
918QString 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
927Path 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
934Path 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
941Path 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
950Path Path::mid(int offset) const
951{
952 return mid(offset, length: m_length - offset);
953}
954
955Path 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
965QT_END_NAMESPACE
966
967#include "moc_qqmldompath_p.cpp"
968

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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