aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmldom/qqmldompath.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmldom/qqmldompath.cpp')
-rw-r--r--src/qmldom/qqmldompath.cpp967
1 files changed, 967 insertions, 0 deletions
diff --git a/src/qmldom/qqmldompath.cpp b/src/qmldom/qqmldompath.cpp
new file mode 100644
index 0000000000..d2b4582bc0
--- /dev/null
+++ b/src/qmldom/qqmldompath.cpp
@@ -0,0 +1,967 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqmldomitem_p.h"
+#include "qqmldomerrormessage_p.h"
+#include <QtCore/QDebug>
+#include <QtCore/QTextStream>
+#include <QtCore/QChar>
+
+#include <cstdint>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+class ErrorMessage;
+
+namespace PathEls {
+
+/*!
+\internal
+\class QQmljs::Dom::QmlPath::Path
+
+\brief Represents an immutable JsonPath like path in the Qml code model (from a DomItem to another
+ DomItem)
+
+It can be created either from a string, with the path static functions
+or by modifying an existing path
+\code
+Path qmlFilePath =
+ Path::fromString(u"$env.qmlFilesByPath[\"/path/to/file\"]");
+Path imports = qmlFilePath.subField(u"imports")
+Path currentComponentImports = Path::current(u"component").subField(u"imports");
+\endcode
+
+This is a way to refer to elements in the Dom models that is not dependent from the source location,
+and thus can be used also be used in visual tools.
+A Path is quite stable toward reallocations or changes in the Dom model, and accessing it is safe
+even when "dangling", thus it is a good long term reference to an element in the Dom model.
+
+Path objects are a value type that have a shared pointer to extra data if needed, thus one should
+use them as value objects.
+The implementation has still optimization potential, but the behavior for the user should be already
+the final one.
+
+Path is both a range, and a single element (a bit like strings and characters in python).
+
+The root contexts are:
+\list
+\li \l{$modules} All the known modules (even not imported), this is a global, rename independent
+ reference
+\li \l{$cpp} The Cpp names (namespaces, and Cpp types) visible in the current component
+\li \l{$libs} The plugins/libraries and their contents
+\li \l{$top} A top level entry in the DOM model, either $env or $universe (stepping in the universe
+ one looses the reference to its environment)
+\li \l{$env} The environment containing the currently available modules, i.e. the top level entry in
+ the DOM model
+\li \l{$universe} The dom unverse used by ths environment, and potentially shared with others that
+ contains all the known parse entries, and also the latest, potentially invalid entries
+\li \l{$} ? undecided, one the previous ones?
+\endlist
+
+The current contexts are:
+\list
+\li \l{@obj} The current object (if in a map or list goes up until it is in the current object) .
+\li \l{@component} The root object of the current component.
+\li \l{@module} The current module instantiation.
+\li \l{@ids} The ids in the current component.
+\li \l{@types} All the types in the current component (reachable through imports, respecting renames)
+\li \l{@lookupStrict} The strict lookup inside the current object: localJS, ids, properties, proto
+ properties, component, its properties, global context, oterwise error
+\li \l{@lookupDynamic} The default lookup inside the current object: localJS, ids, properties, proto
+ properties, component, its properties, global context, ..
+\li \l{@lookup} Either lookupStrict or lookupDynamic depending on the current component and context.
+\li \l{@} ? undecided, one the previous ones
+\endlist
+ */
+
+void Base::dump(const Sink &sink, const QString &name, bool hasSquareBrackets) const {
+ if (hasSquareBrackets)
+ sink(u"[");
+ sink(name);
+ if (hasSquareBrackets)
+ sink(u"]");
+}
+
+Filter::Filter(const function<bool(const DomItem &)> &f, QStringView filterDescription)
+ : filterFunction(f), filterDescription(filterDescription) {}
+
+QString Filter::name() const {
+ return QLatin1String("?(%1)").arg(filterDescription); }
+
+bool Filter::checkName(QStringView s) const
+{
+ return s.startsWith(u"?(")
+ && s.mid(2, s.size()-3) == filterDescription
+ && s.endsWith(u")");
+}
+
+enum class ParserState{
+ Start,
+ IndexOrKey,
+ End
+};
+
+int PathComponent::cmp(const PathComponent &p1, const PathComponent &p2)
+{
+ int k1 = static_cast<int>(p1.kind());
+ int k2 = static_cast<int>(p2.kind());
+ if (k1 < k2)
+ return -1;
+ if (k1 > k2)
+ return 1;
+ switch (p1.kind()) {
+ case Kind::Empty:
+ return 0;
+ case Kind::Field:
+ return std::get<Field>(p1.m_data).fieldName.compare(std::get<Field>(p2.m_data).fieldName);
+ case Kind::Index:
+ if (std::get<Index>(p1.m_data).indexValue < std::get<Index>(p2.m_data).indexValue)
+ return -1;
+ if (std::get<Index>(p1.m_data).indexValue > std::get<Index>(p2.m_data).indexValue)
+ return 1;
+ return 0;
+ case Kind::Key:
+ return std::get<Key>(p1.m_data).keyValue.compare(std::get<Key>(p2.m_data).keyValue);
+ case Kind::Root:
+ {
+ PathRoot k1 = std::get<Root>(p1.m_data).contextKind;
+ PathRoot k2 = std::get<Root>(p2.m_data).contextKind;
+ if (k1 == PathRoot::Env || k1 == PathRoot::Universe)
+ k1 = PathRoot::Top;
+ if (k2 == PathRoot::Env || k2 == PathRoot::Universe)
+ k2 = PathRoot::Top;
+ int c = int(k1) - int(k2);
+ if (c != 0)
+ return c;
+ return std::get<Root>(p1.m_data).contextName.compare(std::get<Root>(p2.m_data).contextName);
+ }
+ case Kind::Current:
+ {
+ int c = int(std::get<Current>(p1.m_data).contextKind)
+ - int(std::get<Current>(p2.m_data).contextKind);
+ if (c != 0)
+ return c;
+ return std::get<Current>(p1.m_data).contextName
+ .compare(std::get<Current>(p2.m_data).contextName);
+ }
+ case Kind::Any:
+ return 0;
+ case Kind::Filter:
+ {
+ int c = std::get<Filter>(p1.m_data).filterDescription
+ .compare(std::get<Filter>(p2.m_data).filterDescription);
+ if (c != 0)
+ return c;
+ if (std::get<Filter>(p1.m_data).filterDescription.startsWith(u"<")) {
+ // assuming non comparable native code (target comparison is not portable)
+ auto pp1 = &p1;
+ auto pp2 = &p2;
+ if (pp1 < pp2)
+ return -1;
+ if (pp1 > pp2)
+ return 1;
+ }
+ return 0;
+ }
+ }
+ Q_ASSERT(false && "unexpected PathComponent in PathComponent::cmp");
+ return 0;
+}
+
+} // namespace PathEls
+
+const PathEls::PathComponent &Path::component(int i) const
+{
+ static Component emptyComponent;
+ if (i < 0)
+ i += m_length;
+ if (i >= m_length || i < 0) {
+ Q_ASSERT(false && "index out of bounds");
+ return emptyComponent;
+ }
+ i = i - m_length - m_endOffset;
+ auto data = m_data.get();
+ while (data) {
+ i += data->components.size();
+ if (i >= 0)
+ return std::as_const(data)->components[i];
+ data = data->parent.get();
+ }
+ Q_ASSERT(false && "Invalid data reached while resolving a seemengly valid index in Path (inconsisten Path object)");
+ return emptyComponent;
+}
+
+Path Path::operator[](int i) const
+{
+ return mid(i,1);
+}
+
+QQmlJS::Dom::Path::operator bool() const
+{
+ return length() != 0;
+}
+
+PathIterator Path::begin() const
+{
+ return PathIterator{*this};
+}
+
+PathIterator Path::end() const
+{
+ return PathIterator();
+}
+
+PathRoot Path::headRoot() const
+{
+ auto &comp = component(0);
+ if (PathEls::Root const * r = comp.asRoot())
+ return r->contextKind;
+ return PathRoot::Other;
+}
+
+PathCurrent Path::headCurrent() const
+{
+ auto comp = component(0);
+ if (PathEls::Current const * c = comp.asCurrent())
+ return c->contextKind;
+ return PathCurrent::Other;
+}
+
+Path::Kind Path::headKind() const
+{
+ if (m_length == 0)
+ return Path::Kind::Empty;
+ return component(0).kind();
+}
+
+QString Path::headName() const
+{
+ return component(0).name();
+}
+
+bool Path::checkHeadName(QStringView name) const
+{
+ return component(0).checkName(name);
+}
+
+index_type Path::headIndex(index_type defaultValue) const
+{
+ return component(0).index(defaultValue);
+}
+
+function<bool(const DomItem &)> Path::headFilter() const
+{
+ auto &comp = component(0);
+ if (PathEls::Filter const * f = comp.asFilter()) {
+ return f->filterFunction;
+ }
+ return {};
+}
+
+Path Path::head() const
+{
+ return mid(0,1);
+}
+
+Path Path::last() const
+{
+ return mid(m_length-1, 1);
+}
+
+Source Path::split() const
+{
+ int i = length();
+ while (i>0) {
+ const PathEls::PathComponent &c=component(--i);
+ if (c.kind() == Kind::Field || c.kind() == Kind::Root || c.kind() == Kind::Current) {
+ return Source{mid(0, i), mid(i)};
+ }
+ }
+ return Source{Path(), *this};
+}
+
+bool inQString(QStringView el, const QString &base)
+{
+ if (quintptr(base.constData()) > quintptr(el.begin())
+ || quintptr(base.constData() + base.size()) < quintptr(el.begin()))
+ return false;
+ ptrdiff_t diff = base.constData() - el.begin();
+ return diff >= 0 && diff < base.size();
+}
+
+bool inQString(const QString &el, const QString &base)
+{
+ if (quintptr(base.constData()) > quintptr(el.constData())
+ || quintptr(base.constData() + base.size()) < quintptr(el.constData()))
+ return false;
+ ptrdiff_t diff = base.constData() - el.constData();
+ return diff >= 0 && diff < base.size() && diff + el.size() < base.size();
+}
+
+Path Path::fromString(QStringView s, const ErrorHandler &errorHandler)
+{
+ if (s.isEmpty())
+ return Path();
+ int len=1;
+ const QChar dot = QChar::fromLatin1('.');
+ const QChar lsBrace = QChar::fromLatin1('[');
+ const QChar rsBrace = QChar::fromLatin1(']');
+ const QChar dollar = QChar::fromLatin1('$');
+ const QChar at = QChar::fromLatin1('@');
+ const QChar quote = QChar::fromLatin1('"');
+ const QChar backslash = QChar::fromLatin1('\\');
+ const QChar underscore = QChar::fromLatin1('_');
+ const QChar tilda = QChar::fromLatin1('~');
+ for (int i=0; i < s.size(); ++i)
+ if (s.at(i) == lsBrace || s.at(i) == dot)
+ ++len;
+ QVector<Component> components;
+ components.reserve(len);
+ int i = 0;
+ int i0 = 0;
+ PathEls::ParserState state = PathEls::ParserState::Start;
+ QStringList strVals;
+ while (i < s.size()) {
+ // skip space
+ while (i < s.size() && s.at(i).isSpace())
+ ++i;
+ if (i >= s.size())
+ break;
+ QChar c = s.at(i++);
+ switch (state) {
+ case PathEls::ParserState::Start:
+ if (c == dollar) {
+ i0 = i;
+ while (i < s.size() && s.at(i).isLetterOrNumber()){
+ ++i;
+ }
+ components.append(Component(PathEls::Root(s.mid(i0,i-i0))));
+ state = PathEls::ParserState::End;
+ } else if (c == at) {
+ i0 = i;
+ while (i < s.size() && s.at(i).isLetterOrNumber()){
+ ++i;
+ }
+ components.append(Component(PathEls::Current(s.mid(i0,i-i0))));
+ state = PathEls::ParserState::End;
+ } else if (c.isLetter()) {
+ myErrors().warning(tr("Field expressions should start with a dot, even when at the start of the path %1.")
+ .arg(s)).handle(errorHandler);
+ return Path();
+ } else {
+ --i;
+ state = PathEls::ParserState::End;
+ }
+ break;
+ case PathEls::ParserState::IndexOrKey:
+ if (c.isDigit()) {
+ i0 = i-1;
+ while (i < s.size() && s.at(i).isDigit())
+ ++i;
+ bool ok;
+ components.append(Component(static_cast<index_type>(s.mid(i0,i-i0).toString()
+ .toLongLong(&ok))));
+ if (!ok) {
+ myErrors().warning(tr("Error extracting integer from '%1' at char %2.")
+ .arg(s.mid(i0,i-i0))
+ .arg(QString::number(i0))).handle(errorHandler);
+ }
+ } else if (c.isLetter() || c == tilda || c == underscore) {
+ i0 = i-1;
+ while (i < s.size() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda))
+ ++i;
+ components.append(Component(PathEls::Key(s.mid(i0, i - i0).toString())));
+ } else if (c == quote) {
+ i0 = i;
+ QString strVal;
+ bool properEnd = false;
+ while (i < s.size()) {
+ c = s.at(i);
+ if (c == quote) {
+ properEnd = true;
+ break;
+ } else if (c == backslash) {
+ strVal.append(s.mid(i0, i - i0).toString());
+ c = s.at(++i);
+ i0 = i + 1;
+ if (c == QChar::fromLatin1('n'))
+ strVal.append(QChar::fromLatin1('\n'));
+ else if (c == QChar::fromLatin1('r'))
+ strVal.append(QChar::fromLatin1('\r'));
+ else if (c == QChar::fromLatin1('t'))
+ strVal.append(QChar::fromLatin1('\t'));
+ else
+ strVal.append(c);
+ }
+ ++i;
+ }
+ if (properEnd) {
+ strVal.append(s.mid(i0, i - i0).toString());
+ ++i;
+ } else {
+ myErrors().error(tr("Unclosed quoted string at char %1.")
+ .arg(QString::number(i - 1))).handle(errorHandler);
+ return Path();
+ }
+ components.append(PathEls::Key(strVal));
+ } else if (c == QChar::fromLatin1('*')) {
+ components.append(Component(PathEls::Any()));
+ } else if (c == QChar::fromLatin1('?')) {
+ while (i < s.size() && s.at(i).isSpace())
+ ++i;
+ if (i >= s.size() || s.at(i) != QChar::fromLatin1('(')) {
+ myErrors().error(tr("Expected a brace in filter after the question mark (at char %1).")
+ .arg(QString::number(i))).handle(errorHandler);
+ return Path();
+ }
+ i0 = ++i;
+ while (i < s.size() && s.at(i) != QChar::fromLatin1(')')) ++i; // check matching braces when skipping??
+ if (i >= s.size() || s.at(i) != QChar::fromLatin1(')')) {
+ myErrors().error(tr("Expected a closing brace in filter after the question mark (at char %1).")
+ .arg(QString::number(i))).handle(errorHandler);
+ return Path();
+ }
+ //auto filterDesc = s.mid(i0,i-i0);
+ ++i;
+ myErrors().error(tr("Filter from string not yet implemented.")).handle(errorHandler);
+ return Path();
+ } else {
+ myErrors().error(tr("Unexpected character '%1' after square bracket at %2.")
+ .arg(c).arg(i-1)).handle(errorHandler);
+ return Path();
+ }
+ while (i < s.size() && s.at(i).isSpace()) ++i;
+ if (i >= s.size() || s.at(i) != rsBrace) {
+ myErrors().error(tr("square braces misses closing brace at char %1.")
+ .arg(QString::number(i))).handle(errorHandler);
+ return Path();
+ } else {
+ ++i;
+ }
+ state = PathEls::ParserState::End;
+ break;
+ case PathEls::ParserState::End:
+ if (c == dot) {
+ while (i < s.size() && s.at(i).isSpace()) ++i;
+ if (i == s.size()) {
+ components.append(Component());
+ state = PathEls::ParserState::End;
+ } else if (s.at(i).isLetter() || s.at(i) == underscore || s.at(i) == tilda) {
+ i0 = i;
+ while (i < s.size() && (s.at(i).isLetterOrNumber() || s.at(i) == underscore || s.at(i) == tilda)) {
+ ++i;
+ }
+ components.append(Component(PathEls::Field(s.mid(i0,i-i0))));
+ state = PathEls::ParserState::End;
+ } else if (s.at(i).isDigit()) {
+ i0 = i;
+ while (i < s.size() && s.at(i).isDigit()){
+ ++i;
+ }
+ bool ok;
+ components.append(Component(static_cast<index_type>(s.mid(i0,i-i0).toString().toLongLong(&ok))));
+ if (!ok) {
+ myErrors().warning(tr("Error extracting integer from '%1' at char %2.")
+ .arg(s.mid(i0,i-i0))
+ .arg(QString::number(i0))).handle(errorHandler);
+ return Path();
+ } else {
+ myErrors().info(tr("Index should use square brackets and not a dot (at char %1).")
+ .arg(QString::number(i0))).handle(errorHandler);
+ }
+ state = PathEls::ParserState::End;
+ } else if (s.at(i) == dot || s.at(i) == lsBrace) {
+ components.append(Component());
+ state = PathEls::ParserState::End;
+ } else if (s.at(i) == at) {
+ i0 = ++i;
+ while (i < s.size() && s.at(i).isLetterOrNumber()){
+ ++i;
+ }
+ components.append(Component(PathEls::Current(s.mid(i0,i-i0))));
+ state = PathEls::ParserState::End;
+ } else if (s.at(i) == dollar) {
+ i0 = ++i;
+ while (i < s.size() && s.at(i).isLetterOrNumber()){
+ ++i;
+ }
+ components.append(Component(PathEls::Root(s.mid(i0,i-i0))));
+ state = PathEls::ParserState::End;
+ } else {
+ c=s.at(i);
+ myErrors().error(tr("Unexpected character '%1' after dot (at char %2).")
+ .arg(QStringView(&c,1))
+ .arg(QString::number(i-1))).handle(errorHandler);
+ return Path();
+ }
+ } else if (c == lsBrace) {
+ state = PathEls::ParserState::IndexOrKey;
+ } else {
+ myErrors().error(tr("Unexpected character '%1' after end of component (char %2).")
+ .arg(QStringView(&c,1))
+ .arg(QString::number(i-1))).handle(errorHandler);
+ return Path();
+ }
+ break;
+ }
+ }
+ switch (state) {
+ case PathEls::ParserState::Start:
+ return Path();
+ case PathEls::ParserState::IndexOrKey:
+ errorHandler(myErrors().error(tr("unclosed square brace at end.")));
+
+ return Path();
+ case PathEls::ParserState::End:
+ return Path(0, components.size(), std::make_shared<PathEls::PathData>(
+ strVals, components));
+ }
+ Q_ASSERT(false && "Unexpected state in Path::fromString");
+ return Path();
+}
+
+Path Path::Root(PathRoot s)
+{
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Root(s)))));
+}
+
+Path Path::Root(const QString &s)
+{
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(s), QVector<Component>(1,Component(PathEls::Root(s)))));
+}
+
+Path Path::Index(index_type i)
+{
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Index(i)))));
+}
+
+Path Path::Root(QStringView s)
+{
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Root(s)))));
+}
+
+
+Path Path::Field(QStringView s)
+{
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Field(s)))));
+}
+
+Path Path::Field(const QString &s)
+{
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(s), QVector<Component>(1,Component(PathEls::Field(s)))));
+}
+
+Path Path::Key(QStringView s)
+{
+ return Path(
+ 0, 1,
+ std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1, Component(PathEls::Key(s.toString())))));
+}
+
+Path Path::Key(const QString &s)
+{
+ return Path(0, 1,
+ std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1, Component(PathEls::Key(s)))));
+}
+
+Path Path::Current(PathCurrent s)
+{
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Current(s)))));
+}
+
+Path Path::Current(const QString &s)
+{
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(s), QVector<Component>(1,Component(PathEls::Current(s)))));
+}
+
+Path Path::Current(QStringView s)
+{
+ return Path(0,1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Current(s)))));
+}
+
+Path Path::Empty()
+{
+ return Path();
+}
+
+Path Path::empty() const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().empty();
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component()), m_data));
+}
+
+Path Path::field(const QString &name) const
+{
+ auto res = field(QStringView(name));
+ res.m_data->strData.append(name);
+ return res;
+}
+
+Path Path::field(QStringView name) const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().field(name);
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Field(name))), m_data));
+}
+
+Path Path::key(const QString &name) const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().key(name);
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Key(name))), m_data));
+}
+
+Path Path::key(QStringView name) const
+{
+ return key(name.toString());
+}
+
+Path Path::index(index_type i) const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().index(i);
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(i)), m_data));
+}
+
+Path Path::any() const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().any();
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Any())), m_data));
+}
+
+Path Path::filter(const function<bool(const DomItem &)> &filterF, const QString &desc) const
+{
+ auto res = filter(filterF, QStringView(desc));
+ res.m_data->strData.append(desc);
+ return res;
+}
+
+Path Path::filter(const function<bool(const DomItem &)> &filter, QStringView desc) const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().filter(filter, desc);
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Filter(filter, desc))), m_data));
+}
+
+Path Path::current(PathCurrent s) const
+{
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data));
+}
+
+Path Path::current(const QString &s) const
+{
+ auto res = current(QStringView(s));
+ res.m_data->strData.append(s);
+ return res;
+}
+
+Path Path::current(QStringView s) const
+{
+ if (m_endOffset != 0)
+ return noEndOffset().current(s);
+ return Path(0,m_length+1,std::make_shared<PathEls::PathData>(
+ QStringList(), QVector<Component>(1,Component(PathEls::Current(s))), m_data));
+}
+
+Path Path::path(const Path &toAdd, bool avoidToAddAsBase) const
+{
+ if (toAdd.length() == 0)
+ return *this;
+ int resLength = length() + toAdd.length();
+ if (m_endOffset != 0) {
+ Path thisExtended = this->expandBack();
+ if (thisExtended.length() > resLength)
+ thisExtended = thisExtended.mid(0, resLength);
+ Path added = thisExtended.mid(length(), thisExtended.length() - length());
+ if (added == toAdd.mid(0, toAdd.length())) {
+ if (resLength == thisExtended.length())
+ return thisExtended;
+ else
+ return thisExtended.path(toAdd.mid(added.length(), resLength - thisExtended.length()));
+ }
+ }
+ if (!avoidToAddAsBase) {
+ Path toAddExtended = toAdd.expandFront();
+ if (toAddExtended.length() >= resLength) {
+ toAddExtended = toAddExtended.mid(toAddExtended.length() - resLength, resLength);
+ if (toAddExtended.mid(0,length()) == *this)
+ return toAddExtended;
+ }
+ }
+ QVector<Component> components;
+ components.reserve(toAdd.length());
+ QStringList addedStrs;
+ bool addHasStr = false;
+ auto data = toAdd.m_data.get();
+ while (data) {
+ if (!data->strData.isEmpty()) {
+ addHasStr = true;
+ break;
+ }
+ data = data->parent.get();
+ }
+ if (addHasStr) {
+ QStringList myStrs;
+ data = m_data.get();
+ while (data) {
+ myStrs.append(data->strData);
+ data = data->parent.get();
+ }
+ data = toAdd.m_data.get();
+ while (data) {
+ for (int ij = 0; ij < data->strData.size(); ++ij) {
+ bool hasAlready = false;
+ for (int ii = 0; ii < myStrs.size() && !hasAlready; ++ii)
+ hasAlready = inQString(data->strData[ij], myStrs[ii]);
+ if (!hasAlready)
+ addedStrs.append(data->strData[ij]);
+ }
+ data = data->parent.get();
+ }
+ }
+ QStringList toAddStrs;
+ for (int i = 0; i < toAdd.length(); ++i) {
+ components.append(toAdd.component(i));
+ QStringView compStrView = toAdd.component(i).stringView();
+ if (!compStrView.isEmpty()) {
+ for (int j = 0; j < addedStrs.size(); ++j) {
+ if (inQString(compStrView, addedStrs[j])) {
+ toAddStrs.append(addedStrs[j]);
+ addedStrs.removeAt(j);
+ break;
+ }
+ }
+ }
+ }
+ return Path(0, m_length + toAdd.length(), std::make_shared<PathEls::PathData>(
+ toAddStrs, components, ((m_endOffset == 0) ? m_data : noEndOffset().m_data)));
+}
+
+Path Path::expandFront() const
+{
+ int newLen = 0;
+ auto data = m_data.get();
+ while (data) {
+ newLen += data->components.size();
+ data = data->parent.get();
+ }
+ newLen -= m_endOffset;
+ return Path(m_endOffset, newLen, m_data);
+}
+
+Path Path::expandBack() const
+{
+ if (m_endOffset > 0)
+ return Path(0, m_length + m_endOffset, m_data);
+ return *this;
+}
+
+Path &Path::operator++()
+{
+ if (m_length > 0)
+ m_length -= 1;
+ return *this;
+}
+
+Path Path::operator++(int)
+{
+ Path res = *this;
+ if (m_length > 0)
+ m_length -= 1;
+ return res;
+}
+
+int Path::cmp(const Path &p1, const Path &p2)
+{
+ // lexicographic ordering
+ const int lMin = qMin(p1.m_length, p2.m_length);
+ if (p1.m_data.get() == p2.m_data.get() && p1.m_endOffset == p2.m_endOffset && p1.m_length == p2.m_length)
+ return 0;
+ for (int i = 0; i < lMin; ++i) {
+ int c = Component::cmp(p1.component(i), p2.component(i));
+ if (c != 0)
+ return c;
+ }
+ if (lMin < p2.m_length)
+ return -1;
+ if (p1.m_length > lMin)
+ return 1;
+ return 0;
+}
+
+Path::Path(quint16 endOffset, quint16 length, const std::shared_ptr<PathEls::PathData> &data)
+ :m_endOffset(endOffset), m_length(length), m_data(data)
+{
+}
+
+Path Path::noEndOffset() const
+{
+ if (m_length == 0)
+ return Path();
+ if (m_endOffset == 0)
+ return *this;
+ // peel back
+ qint16 endOffset = m_endOffset;
+ std::shared_ptr<PathEls::PathData> lastData = m_data;
+ while (lastData && endOffset >= lastData->components.size()) {
+ endOffset -= lastData->components.size();
+ lastData = lastData->parent;
+ }
+ if (endOffset > 0) {
+ Q_ASSERT(lastData && "Internal problem, reference to non existing PathData");
+ return Path(0, m_length, std::make_shared<PathEls::PathData>(
+ lastData->strData, lastData->components.mid(0, lastData->components.size() - endOffset), lastData->parent));
+ }
+ return Path(0, m_length, lastData);
+}
+
+Path Path::appendComponent(const PathEls::PathComponent &c)
+{
+ if (m_endOffset != 0) {
+ Path newP = noEndOffset();
+ return newP.appendComponent(c);
+ }
+ if (m_data && m_data.use_count() != 1) {
+ // create a new path (otherwise paths linking to this will change)
+ Path newP(c);
+ newP.m_data->parent = m_data;
+ newP.m_length = static_cast<quint16>(m_length + 1);
+ return newP;
+ }
+ auto my_data =
+ (m_data ? m_data
+ : std::make_shared<PathEls::PathData>(QStringList(),
+ QVector<PathEls::PathComponent>()));
+ switch (c.kind()) {
+ case PathEls::Kind::Any:
+ case PathEls::Kind::Empty:
+ case PathEls::Kind::Index:
+ // no string
+ case PathEls::Kind::Field:
+ // string assumed to stay valid (Fields::...)
+ my_data->components.append(c);
+ break;
+ case PathEls::Kind::Current:
+ if (c.asCurrent()->contextKind == PathCurrent::Other) {
+ my_data->strData.append(c.asCurrent()->contextName.toString());
+ my_data->components.append(PathEls::Current(my_data->strData.last()));
+ } else {
+ my_data->components.append(c);
+ }
+ break;
+ case PathEls::Kind::Filter:
+ if (!c.asFilter()->filterDescription.isEmpty()) {
+ my_data->strData.append(c.asFilter()->filterDescription.toString());
+ my_data->components.append(
+ PathEls::Filter(c.asFilter()->filterFunction, my_data->strData.last()));
+ } else {
+ my_data->components.append(c);
+ }
+ break;
+ case PathEls::Kind::Key:
+ my_data->components.append(c);
+ break;
+ case PathEls::Kind::Root:
+ if (c.asRoot()->contextKind == PathRoot::Other) {
+ my_data->strData.append(c.asRoot()->contextName.toString());
+ my_data->components.append(PathEls::Root(my_data->strData.last()));
+ } else {
+ my_data->components.append(c);
+ }
+ break;
+ }
+ if (m_data)
+ m_endOffset = 1;
+ return Path { 0, static_cast<quint16>(m_length + 1), my_data };
+}
+
+ErrorGroups Path::myErrors()
+{
+ static ErrorGroups res = {{NewErrorGroup("PathParsing")}};
+ return res;
+}
+
+void Path::dump(const Sink &sink) const
+{
+ bool first = true;
+ for (int i = 0; i < m_length; ++i) {
+ auto &c = component(i);
+ if (!c.hasSquareBrackets()) {
+ if (!first || (c.kind() != Kind::Root && c.kind() != Kind::Current))
+ sink(u".");
+ }
+ c.dump(sink);
+ first = false;
+ }
+}
+
+QString Path::toString() const
+{
+ QString res;
+ QTextStream stream(&res);
+ dump([&stream](QStringView str){ stream << str; });
+ stream.flush();
+ return res;
+}
+
+Path Path::dropFront(int n) const
+{
+ if (m_length > n && n >= 0)
+ return Path(m_endOffset, m_length - n, m_data);
+ return Path();
+}
+
+Path Path::dropTail(int n) const
+{
+ if (m_length > n && n >= 0)
+ return Path(m_endOffset + n, m_length - n, m_data);
+ return Path();
+}
+
+Path Path::mid(int offset, int length) const
+{
+ length = qMin(m_length - offset, length);
+ if (offset < 0 || offset >= m_length || length <= 0 || length > m_length)
+ return Path();
+ int newEndOffset = m_endOffset + m_length - offset - length;
+ return Path(newEndOffset, length, m_data);
+}
+
+Path Path::mid(int offset) const
+{
+ return mid(offset, m_length - offset);
+}
+
+Path Path::fromString(const QString &s, const ErrorHandler &errorHandler)
+{
+ Path res = fromString(QStringView(s), errorHandler);
+ if (res.m_data)
+ res.m_data->strData.append(s);
+ return res;
+}
+
+} // end namespace Dom
+} // end namespace QQmlJS
+QT_END_NAMESPACE
+
+#include "moc_qqmldompath_p.cpp"