// 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 #include #include #include 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) \section1 Implementation Details The Path class is implemented as a linked list tree where components of the path can be shared between multiple paths. We will represent a list of components with \badcode [a] // one element "a" in the list [a,b,c] // three elements in the list \endcode and the linked list tree structure with arrows: \badcode [a,b,c] <- [y,z] // one linked list made up of two nodes consisting of a, b, c, y and z ^ |______ [q] <- [w] // one linked list made up of three nodes consisting of a, b, c, q and w, // sharing the a,b and c part with the above list \endcode Appending components to the path will create a new linked list node, for example when appending \c d to \c{[a,b,c]}: \badcode [a,b,c] <- [d] \endcode This allows to share the path prefix when constructing paths recursively, for example. \section2 PathData as elements of the linked list trees Each node of the linked list tree is stored as \c PathData. It contains a vector of \c PathComponents, a pointer to the previous element in the path (called parent), and a QStringList to extend the lifetime of QStringViews used inside of PathComponent. \section3 PathComponents The path can also be "compressed", as the linked list structure contains vectors of components: \c {a.b.c.d} could be represented by \badcode [a] <- [b] <- [c] <- [d] \endcode (which is the default) or also \badcode [a, b] <- [c, d] \endcode when \c{[c,d]} got appended to \c{[a, b]} via \c withPath, for example. \section3 String Data The PathData is responsible to own the string data used by the PathComponents it contains. PathComponents do not contain any owning QStrings, they only hold QStringViews. To construct a PathData, store the string data in the QStringList such that it can be referenced by the QStringViews of PathData's PathComponents. PathData are assumed to be immutable after construction to avoid any dangling references due to a QStringList reallocation, for example. \section2 Appending Appending one component to the Path allows to share the "parent" path in the memory and should be the preferred way of constructing paths for DomItems. \section2 Iteration To iterate through the path, you need two iterators. One for the current data (that contains the vector of components and the pointer to the "parent") and one for the current idx in the vector of components. To access \c d in that path: \badcode [a, b] <- [c, d] \endcode you need to do \badcode path.m_data->components[1]. \endcode to access \c b instead, you need to do: \badcode path.m_data->parent->components[1] \endcode Note that all kind of forward iterations (getting the components a, b, c, and d in this order from a.b.c.d) is a bit more involved, and backward iteration is much faster/easier. \section2 Slicing The path also supports slicing via the \c m_length and \c m_endOffset members. \c m_endOffset ignores the n last components, for example \c{a.b.c.d} can be stored as \badcode [root] <- [a] <- [b] <- [c] <- [d] <- [e] <- [f] <- [g] \endcode with m_endOffset = 3 and m_length = 4. \section2 Lifetime in the DOM Paths are usually created during the Dom construction, and destroyed when the DomItem or Dom element holding it is destroyed. The underlying PathData, on the other hand, is managed by a std::shared_ptr. It is destroyed when all Paths referencing it are destroyed. PathData can be referenced either directly through Path's \c m_data member, or indirectly through another PathData's \c m_parent member. \section1 Usage 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.withField(u"imports") Path currentComponentImports = Path::fromCurrent(u"component").withField(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 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 &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(p1.kind()); int k2 = static_cast(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(p1.m_data).fieldName.compare(std::get(p2.m_data).fieldName); case Kind::Index: if (std::get(p1.m_data).indexValue < std::get(p2.m_data).indexValue) return -1; if (std::get(p1.m_data).indexValue > std::get(p2.m_data).indexValue) return 1; return 0; case Kind::Key: return std::get(p1.m_data).keyValue.compare(std::get(p2.m_data).keyValue); case Kind::Root: { PathRoot k1 = std::get(p1.m_data).contextKind; PathRoot k2 = std::get(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(p1.m_data).contextName.compare(std::get(p2.m_data).contextName); } case Kind::Current: { int c = int(std::get(p1.m_data).contextKind) - int(std::get(p2.m_data).contextKind); if (c != 0) return c; return std::get(p1.m_data).contextName .compare(std::get(p2.m_data).contextName); } case Kind::Any: return 0; case Kind::Filter: { int c = std::get(p1.m_data).filterDescription .compare(std::get(p2.m_data).filterDescription); if (c != 0) return c; if (std::get(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 /*! \internal \brief Returns the n.th component in linear(!) time. Especially component(0)-calls have to iterate through the entire path. Example: returns \c d for 3 on \badcode a.b <- c.d <- e <- f \endcode */ 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 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); } /*! \internal \brief Splits the path at the last field, root or current Component. */ 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}; } 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 components; components.reserve(len); int i = 0; int i0 = 0; PathEls::ParserState state = PathEls::ParserState::Start; 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(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(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( QStringList(), components)); } Q_ASSERT(false && "Unexpected state in Path::fromString"); return Path(); } Path Path::fromRoot(PathRoot s) { return Path(0,1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Root(s))))); } Path Path::fromRoot(const QString &s) { return Path(0,1,std::make_shared( QStringList(s), QVector(1,Component(PathEls::Root(s))))); } Path Path::fromIndex(index_type i) { return Path(0,1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Index(i))))); } Path Path::fromRoot(QStringView s) { return Path(0,1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Root(s))))); } Path Path::fromField(QStringView s) { return Path(0,1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Field(s))))); } Path Path::fromField(const QString &s) { return Path(0,1,std::make_shared( QStringList(s), QVector(1,Component(PathEls::Field(s))))); } Path Path::fromKey(QStringView s) { return Path( 0, 1, std::make_shared( QStringList(), QVector(1, Component(PathEls::Key(s.toString()))))); } Path Path::fromKey(const QString &s) { return Path(0, 1, std::make_shared( QStringList(), QVector(1, Component(PathEls::Key(s))))); } Path Path::fromCurrent(PathCurrent s) { return Path(0,1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Current(s))))); } Path Path::fromCurrent(const QString &s) { return Path(0,1,std::make_shared( QStringList(s), QVector(1,Component(PathEls::Current(s))))); } Path Path::fromCurrent(QStringView s) { return Path(0,1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Current(s))))); } Path Path::withEmpty() const { if (m_endOffset != 0) return noEndOffset().withEmpty(); return Path(0,m_length+1,std::make_shared( QStringList(), QVector(1,Component()), m_data)); } Path Path::withField(const QString &name) const { auto res = withField(QStringView(name)); res.m_data->strData.append(name); return res; } Path Path::withField(QStringView name) const { if (m_endOffset != 0) return noEndOffset().withField(name); return Path(0,m_length+1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Field(name))), m_data)); } Path Path::withKey(const QString &name) const { if (m_endOffset != 0) return noEndOffset().withKey(name); return Path(0,m_length+1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Key(name))), m_data)); } Path Path::withKey(QStringView name) const { return withKey(name.toString()); } Path Path::withIndex(index_type i) const { if (m_endOffset != 0) return noEndOffset().withIndex(i); return Path(0,m_length+1,std::make_shared( QStringList(), QVector(1,Component(i)), m_data)); } Path Path::withAny() const { if (m_endOffset != 0) return noEndOffset().withAny(); return Path(0,m_length+1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Any())), m_data)); } Path Path::withFilter(const function &filterF, const QString &desc) const { auto res = withFilter(filterF, QStringView(desc)); res.m_data->strData.append(desc); return res; } Path Path::withFilter(const function &filter, QStringView desc) const { if (m_endOffset != 0) return noEndOffset().withFilter(filter, desc); return Path(0,m_length+1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Filter(filter, desc))), m_data)); } Path Path::withCurrent(PathCurrent s) const { return Path(0,m_length+1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Current(s))), m_data)); } Path Path::withCurrent(const QString &s) const { auto res = withCurrent(QStringView(s)); res.m_data->strData.append(s); return res; } Path Path::withCurrent(QStringView s) const { if (m_endOffset != 0) return noEndOffset().withCurrent(s); return Path(0,m_length+1,std::make_shared( QStringList(), QVector(1,Component(PathEls::Current(s))), m_data)); } static QVector reverseAndConcat(const std::vector *> &list, qsizetype size) { using Component = Path::Component; QVector components; components.reserve(size); for (auto it = list.crbegin(), end = list.crend(); it != end; ++it) { for (auto it2 = (**it).cbegin(), end2 = (**it).cend(); it2 != end2; ++it2) { components.append(*it2); if (components.size() >= size) return components; } } return components; } static std::pair *>, QStringList> collectBackwards(PathEls::PathData *pathStart, qsizetype size) { using Component = Path::Component; std::vector *> componentList; qsizetype foundComponents = 0; QStringList addedStrs; for (auto data = pathStart; data; data = data->parent.get()) { addedStrs.append(data->strData); componentList.push_back(&data->components); foundComponents += data->components.size(); if (foundComponents >= size) return std::make_pair(componentList, addedStrs); } return std::make_pair(componentList, addedStrs); } /*! \internal \brief Returns a copy of this with \c toAdd appended to it Compresses \c toAdd into one vector of components before adding it to \c this. */ Path Path::withPath(const Path &toAdd, bool avoidToAddAsBase) const { if (toAdd.length() == 0) return *this; const 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.withPath(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; } } const Path toAddNoEndOffset = toAdd.noEndOffset(); auto [componentListList, addedStrs] = collectBackwards(toAddNoEndOffset.m_data.get(), toAdd.length()); QVector componentList = reverseAndConcat(componentListList, toAdd.length()); Path result( 0, resLength, std::make_shared(addedStrs, componentList, noEndOffset().m_data)); return result; } /*! \internal \brief Expand a path prefix hidden by slicing This sets m_length to the maximum length this path can have. For example, for a path \badcode b -> c -> d when encoded as a -> b -> c -> d -> e -> f, m_endOffset = 2, m_length = 3 \endcode expandFront() will return \badcode a -> b -> c -> d when encoded as a -> b -> c -> d -> e -> f, m_endOffset = 2, m_length = 4 \endcode */ 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); } /*! \internal \brief Expand a path suffix hidden by slicing. This sets m_endOffset to 0. For example, for a path \badcode c -> d when encoded as a -> b -> c -> d -> e -> f -> g, m_endOffset = 3, m_length = 2 \endcode expandBack() will return \badcode c -> d -> e -> f -> g when encoded as a -> b -> c -> d -> e -> f -> g, m_endOffset = 0, m_length = 5 \endcode */ 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 &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 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( lastData->strData, lastData->components.mid(0, lastData->components.size() - endOffset), lastData->parent)); } return Path(0, m_length, lastData); } Path Path::withComponent(const PathEls::PathComponent &c) { if (m_endOffset != 0) { Path newP = noEndOffset(); return newP.withComponent(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(m_length + 1); return newP; } auto my_data = (m_data ? m_data : std::make_shared(QStringList(), QVector())); 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(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"