2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2013-05-21 11:35:15 +02:00
|
|
|
|
2019-02-07 17:22:39 +01:00
|
|
|
#include "qrcparser.h"
|
|
|
|
|
|
2022-05-25 06:30:04 +02:00
|
|
|
#include "qtcassert.h"
|
2019-02-07 17:22:39 +01:00
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
#include <utils/filepath.h>
|
|
|
|
|
|
2019-02-07 17:22:39 +01:00
|
|
|
#include <QCoreApplication>
|
2013-05-21 11:35:15 +02:00
|
|
|
#include <QDir>
|
|
|
|
|
#include <QDomDocument>
|
2019-02-07 17:22:39 +01:00
|
|
|
#include <QFile>
|
|
|
|
|
#include <QFileInfo>
|
2013-05-21 11:35:15 +02:00
|
|
|
#include <QLocale>
|
2019-02-07 17:22:39 +01:00
|
|
|
#include <QLoggingCategory>
|
2013-05-21 11:35:15 +02:00
|
|
|
#include <QMutex>
|
|
|
|
|
#include <QMutexLocker>
|
2019-02-07 17:22:39 +01:00
|
|
|
|
2020-01-15 14:39:23 +01:00
|
|
|
static Q_LOGGING_CATEGORY(qrcParserLog, "qtc.qrcParser", QtWarningMsg)
|
2013-05-21 11:35:15 +02:00
|
|
|
|
2019-02-07 17:22:39 +01:00
|
|
|
namespace Utils {
|
2013-05-21 11:35:15 +02:00
|
|
|
|
|
|
|
|
namespace Internal {
|
2020-02-06 16:56:16 +01:00
|
|
|
|
2013-05-21 11:35:15 +02:00
|
|
|
class QrcParserPrivate
|
|
|
|
|
{
|
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(QmlJS::QrcParser)
|
|
|
|
|
public:
|
|
|
|
|
typedef QMap<QString,QStringList> SMap;
|
|
|
|
|
QrcParserPrivate(QrcParser *q);
|
2016-10-24 19:30:24 +02:00
|
|
|
bool parseFile(const QString &path, const QString &contents);
|
2013-05-21 11:35:15 +02:00
|
|
|
QString firstFileAtPath(const QString &path, const QLocale &locale) const;
|
2019-07-31 17:21:41 +02:00
|
|
|
void collectFilesAtPath(const QString &path, QStringList *res, const QLocale *locale = nullptr) const;
|
|
|
|
|
bool hasDirAtPath(const QString &path, const QLocale *locale = nullptr) const;
|
2013-05-21 11:35:15 +02:00
|
|
|
void collectFilesInPath(const QString &path, QMap<QString,QStringList> *res, bool addDirs = false,
|
2019-07-31 17:21:41 +02:00
|
|
|
const QLocale *locale = nullptr) const;
|
2016-03-18 17:43:33 +01:00
|
|
|
void collectResourceFilesForSourceFile(const QString &sourceFile, QStringList *res,
|
2019-07-31 17:21:41 +02:00
|
|
|
const QLocale *locale = nullptr) const;
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
QrcParser::MatchResult longestReverseMatches(const QString &) const;
|
2016-03-18 17:43:33 +01:00
|
|
|
|
2013-05-21 11:35:15 +02:00
|
|
|
QStringList errorMessages() const;
|
|
|
|
|
QStringList languages() const;
|
|
|
|
|
private:
|
|
|
|
|
static QString fixPrefix(const QString &prefix);
|
2019-03-19 10:23:42 +01:00
|
|
|
const QStringList allUiLanguages(const QLocale *locale) const;
|
2013-05-21 11:35:15 +02:00
|
|
|
|
|
|
|
|
SMap m_resources;
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
SMap m_reverseResources;
|
2016-03-18 17:43:33 +01:00
|
|
|
SMap m_files;
|
2013-05-21 11:35:15 +02:00
|
|
|
QStringList m_languages;
|
|
|
|
|
QStringList m_errorMessages;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class QrcCachePrivate
|
|
|
|
|
{
|
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(QmlJS::QrcCachePrivate)
|
|
|
|
|
public:
|
|
|
|
|
QrcCachePrivate(QrcCache *q);
|
2016-10-24 19:30:24 +02:00
|
|
|
QrcParser::Ptr addPath(const QString &path, const QString &contents);
|
2013-05-21 11:35:15 +02:00
|
|
|
void removePath(const QString &path);
|
2016-10-24 19:30:24 +02:00
|
|
|
QrcParser::Ptr updatePath(const QString &path, const QString &contents);
|
2013-05-21 11:35:15 +02:00
|
|
|
QrcParser::Ptr parsedPath(const QString &path);
|
|
|
|
|
void clear();
|
|
|
|
|
private:
|
|
|
|
|
QHash<QString, QPair<QrcParser::Ptr,int> > m_cache;
|
|
|
|
|
QMutex m_mutex;
|
|
|
|
|
};
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
\class Utils::QrcParser
|
|
|
|
|
\inmodule QtCreator
|
|
|
|
|
\brief The QrcParser class parses one or more QRC files and keeps their
|
|
|
|
|
content cached.
|
|
|
|
|
|
|
|
|
|
A \l{The Qt Resource System}{Qt resource collection (QRC)} contains files
|
|
|
|
|
read from the file system but organized in a possibly different way.
|
|
|
|
|
To easily describe that with a simple structure, we use a map from QRC paths
|
|
|
|
|
to the paths in the filesystem.
|
|
|
|
|
By using a map, we can easily find all QRC paths that start with a given
|
|
|
|
|
prefix, and thus loop on a QRC directory.
|
|
|
|
|
|
|
|
|
|
QRC files also support languages, which are mapped to a prefix of the QRC
|
|
|
|
|
path. For example, the French /image/bla.png (lang=fr) will have the path
|
|
|
|
|
\c {fr/image/bla.png}. The empty language represents the default resource.
|
|
|
|
|
Languages are looked up using the locale uiLanguages() property
|
|
|
|
|
|
|
|
|
|
For a single QRC, a given path maps to a single file, but when one has
|
|
|
|
|
multiple (platform-specific and mutually exclusive) QRC files, multiple
|
|
|
|
|
files match, so QStringList are used.
|
|
|
|
|
|
|
|
|
|
Especially, the \c collect* functions are thought of as low level interface.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
\typedef QrcParser::Ptr
|
|
|
|
|
Represents pointers.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
\typedef QrcParser::ConstPtr
|
|
|
|
|
Represents constant pointers.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
Normalizes the \a path to a file in a QRC resource by dropping the \c qrc:/
|
|
|
|
|
or \c : and any extra slashes in the beginning.
|
2013-05-21 11:35:15 +02:00
|
|
|
*/
|
|
|
|
|
QString QrcParser::normalizedQrcFilePath(const QString &path) {
|
|
|
|
|
QString normPath = path;
|
|
|
|
|
int endPrefix = 0;
|
2013-11-11 22:20:47 +02:00
|
|
|
if (path.startsWith(QLatin1String("qrc:/")))
|
2013-05-21 11:35:15 +02:00
|
|
|
endPrefix = 4;
|
2013-11-11 22:20:47 +02:00
|
|
|
else if (path.startsWith(QLatin1String(":/")))
|
2013-05-21 11:35:15 +02:00
|
|
|
endPrefix = 1;
|
|
|
|
|
if (endPrefix < path.size() && path.at(endPrefix) == QLatin1Char('/'))
|
|
|
|
|
while (endPrefix + 1 < path.size() && path.at(endPrefix+1) == QLatin1Char('/'))
|
|
|
|
|
++endPrefix;
|
|
|
|
|
normPath = path.right(path.size()-endPrefix);
|
2014-08-29 14:00:18 +02:00
|
|
|
if (!normPath.startsWith(QLatin1Char('/')))
|
2013-05-21 11:35:15 +02:00
|
|
|
normPath.insert(0, QLatin1Char('/'));
|
|
|
|
|
return normPath;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Returns the path to a directory normalized to \a path in a QRC resource by
|
|
|
|
|
dropping the \c qrc:/ or \c : and any extra slashes at the beginning, and
|
|
|
|
|
by ensuring that the path ends with a slash
|
2013-05-21 11:35:15 +02:00
|
|
|
*/
|
|
|
|
|
QString QrcParser::normalizedQrcDirectoryPath(const QString &path) {
|
|
|
|
|
QString normPath = normalizedQrcFilePath(path);
|
|
|
|
|
if (!normPath.endsWith(QLatin1Char('/')))
|
|
|
|
|
normPath.append(QLatin1Char('/'));
|
|
|
|
|
return normPath;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Returns the QRC directory path for \a file.
|
|
|
|
|
*/
|
2016-03-18 17:43:33 +01:00
|
|
|
QString QrcParser::qrcDirectoryPathForQrcFilePath(const QString &file)
|
|
|
|
|
{
|
|
|
|
|
return file.left(file.lastIndexOf(QLatin1Char('/')));
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-21 11:35:15 +02:00
|
|
|
QrcParser::QrcParser()
|
|
|
|
|
{
|
|
|
|
|
d = new Internal::QrcParserPrivate(this);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
2020-02-07 15:25:09 +01:00
|
|
|
\internal
|
2020-02-06 16:56:16 +01:00
|
|
|
*/
|
2013-05-21 11:35:15 +02:00
|
|
|
QrcParser::~QrcParser()
|
|
|
|
|
{
|
|
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
2020-02-07 15:25:09 +01:00
|
|
|
Parses the QRC file at \a path. If \a contents is not empty, it is used as
|
|
|
|
|
the file contents instead of reading it from the file system.
|
|
|
|
|
|
|
|
|
|
Returns whether the parsing succeeded.
|
|
|
|
|
|
|
|
|
|
\sa errorMessages(), parseQrcFile()
|
2020-02-06 16:56:16 +01:00
|
|
|
*/
|
2016-10-24 19:30:24 +02:00
|
|
|
bool QrcParser::parseFile(const QString &path, const QString &contents)
|
2013-05-21 11:35:15 +02:00
|
|
|
{
|
2016-10-24 19:30:24 +02:00
|
|
|
return d->parseFile(path, contents);
|
2013-05-21 11:35:15 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Returns the file system path of the first (active) file at the given QRC
|
|
|
|
|
\a path and \a locale.
|
2013-05-21 11:35:15 +02:00
|
|
|
*/
|
|
|
|
|
QString QrcParser::firstFileAtPath(const QString &path, const QLocale &locale) const
|
|
|
|
|
{
|
|
|
|
|
return d->firstFileAtPath(path, locale);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Adds the file system paths for the given QRC \a path to \a res.
|
|
|
|
|
|
|
|
|
|
If \a locale is null, all possible files are added. Otherwise, just
|
|
|
|
|
the first one that matches the locale is added.
|
2013-05-21 11:35:15 +02:00
|
|
|
*/
|
|
|
|
|
void QrcParser::collectFilesAtPath(const QString &path, QStringList *res, const QLocale *locale) const
|
|
|
|
|
{
|
|
|
|
|
d->collectFilesAtPath(path, res, locale);
|
|
|
|
|
}
|
|
|
|
|
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
QrcParser::MatchResult QrcParser::longestReverseMatches(const QString &p) const
|
|
|
|
|
{
|
|
|
|
|
return d->longestReverseMatches(p);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Returns \c true if \a path is a non-empty directory and matches \a locale.
|
|
|
|
|
|
2013-05-21 11:35:15 +02:00
|
|
|
*/
|
|
|
|
|
bool QrcParser::hasDirAtPath(const QString &path, const QLocale *locale) const
|
|
|
|
|
{
|
|
|
|
|
return d->hasDirAtPath(path, locale);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Adds the directory contents of the given QRC \a path to \a res if \a addDirs
|
|
|
|
|
is set to \c true.
|
|
|
|
|
|
|
|
|
|
Adds the QRC filename to file system path associations contained in the
|
|
|
|
|
given \a path to \a res. If addDirs() is \c true, directories are also
|
|
|
|
|
added.
|
|
|
|
|
|
|
|
|
|
If \a locale is null, all possible files are added. Otherwise, just the
|
|
|
|
|
first file with a matching the locale is added.
|
2013-05-21 11:35:15 +02:00
|
|
|
*/
|
|
|
|
|
void QrcParser::collectFilesInPath(const QString &path, QMap<QString,QStringList> *res, bool addDirs,
|
|
|
|
|
const QLocale *locale) const
|
|
|
|
|
{
|
|
|
|
|
d->collectFilesInPath(path, res, addDirs, locale);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Adds the resource files from the QRC file \a sourceFile to \a res.
|
|
|
|
|
|
|
|
|
|
If \a locale is null, all possible files are added. Otherwise, just
|
|
|
|
|
the first file with a matching the locale is added.
|
|
|
|
|
*/
|
2016-03-18 17:43:33 +01:00
|
|
|
void QrcParser::collectResourceFilesForSourceFile(const QString &sourceFile, QStringList *res,
|
|
|
|
|
const QLocale *locale) const
|
|
|
|
|
{
|
|
|
|
|
d->collectResourceFilesForSourceFile(sourceFile, res, locale);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Returns the errors found while parsing.
|
2013-05-21 11:35:15 +02:00
|
|
|
*/
|
|
|
|
|
QStringList QrcParser::errorMessages() const
|
|
|
|
|
{
|
|
|
|
|
return d->errorMessages();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Returns all languages used in this QRC.
|
2013-05-21 11:35:15 +02:00
|
|
|
*/
|
|
|
|
|
QStringList QrcParser::languages() const
|
|
|
|
|
{
|
|
|
|
|
return d->languages();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Indicates whether the QRC contents are valid.
|
|
|
|
|
|
|
|
|
|
Returns an error if the QRC is empty.
|
2013-05-21 11:35:15 +02:00
|
|
|
*/
|
|
|
|
|
bool QrcParser::isValid() const
|
|
|
|
|
{
|
|
|
|
|
return errorMessages().isEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Returns the \a contents of the QRC file at \a path.
|
|
|
|
|
*/
|
2016-10-24 19:30:24 +02:00
|
|
|
QrcParser::Ptr QrcParser::parseQrcFile(const QString &path, const QString &contents)
|
2013-05-21 11:35:15 +02:00
|
|
|
{
|
|
|
|
|
Ptr res(new QrcParser);
|
|
|
|
|
if (!path.isEmpty())
|
2016-10-24 19:30:24 +02:00
|
|
|
res->parseFile(path, contents);
|
2013-05-21 11:35:15 +02:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ----------------
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
\class Utils::QrcCache
|
|
|
|
|
\inmodule QtCreator
|
|
|
|
|
\brief The QrcCache class caches the contents of parsed QRC files.
|
|
|
|
|
|
|
|
|
|
\sa Utils::QrcParser
|
|
|
|
|
*/
|
|
|
|
|
|
2013-05-21 11:35:15 +02:00
|
|
|
QrcCache::QrcCache()
|
|
|
|
|
{
|
|
|
|
|
d = new Internal::QrcCachePrivate(this);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
\internal
|
|
|
|
|
*/
|
2013-05-21 11:35:15 +02:00
|
|
|
QrcCache::~QrcCache()
|
|
|
|
|
{
|
|
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
2020-02-07 15:25:09 +01:00
|
|
|
Parses the QRC file at \a path and caches the parser. If \a contents is not
|
|
|
|
|
empty, it is used as the file contents instead of reading it from the file
|
|
|
|
|
system.
|
|
|
|
|
|
|
|
|
|
Returns whether the parsing succeeded.
|
|
|
|
|
|
|
|
|
|
\sa QrcParser::errorMessages(), QrcParser::parseQrcFile()
|
2020-02-06 16:56:16 +01:00
|
|
|
*/
|
2016-10-24 19:30:24 +02:00
|
|
|
QrcParser::ConstPtr QrcCache::addPath(const QString &path, const QString &contents)
|
2013-05-21 11:35:15 +02:00
|
|
|
{
|
2016-10-24 19:30:24 +02:00
|
|
|
return d->addPath(path, contents);
|
2013-05-21 11:35:15 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Removes \a path from the cache.
|
|
|
|
|
*/
|
2013-05-21 11:35:15 +02:00
|
|
|
void QrcCache::removePath(const QString &path)
|
|
|
|
|
{
|
|
|
|
|
d->removePath(path);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
2020-02-07 15:25:09 +01:00
|
|
|
Reparses the QRC file at \a path and returns the \a contents of the file.
|
2020-02-06 16:56:16 +01:00
|
|
|
*/
|
2016-10-24 19:30:24 +02:00
|
|
|
QrcParser::ConstPtr QrcCache::updatePath(const QString &path, const QString &contents)
|
2013-05-21 11:35:15 +02:00
|
|
|
{
|
2016-10-24 19:30:24 +02:00
|
|
|
return d->updatePath(path, contents);
|
2013-05-21 11:35:15 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
2020-02-07 15:25:09 +01:00
|
|
|
Returns the cached QRC parser for the QRC file at \a path.
|
2020-02-06 16:56:16 +01:00
|
|
|
*/
|
2013-05-21 11:35:15 +02:00
|
|
|
QrcParser::ConstPtr QrcCache::parsedPath(const QString &path)
|
|
|
|
|
{
|
|
|
|
|
return d->parsedPath(path);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 16:56:16 +01:00
|
|
|
/*!
|
|
|
|
|
Clears the contents of the cache.
|
|
|
|
|
*/
|
2013-05-21 11:35:15 +02:00
|
|
|
void QrcCache::clear()
|
|
|
|
|
{
|
|
|
|
|
d->clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --------------------
|
|
|
|
|
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
|
|
QrcParserPrivate::QrcParserPrivate(QrcParser *)
|
|
|
|
|
{ }
|
|
|
|
|
|
2016-10-24 19:30:24 +02:00
|
|
|
bool QrcParserPrivate::parseFile(const QString &path, const QString &contents)
|
2013-05-21 11:35:15 +02:00
|
|
|
{
|
2016-10-24 19:30:24 +02:00
|
|
|
QDomDocument doc;
|
2016-10-21 12:19:47 +02:00
|
|
|
QDir baseDir(QFileInfo(path).path());
|
2013-05-21 11:35:15 +02:00
|
|
|
|
2016-10-24 19:30:24 +02:00
|
|
|
if (contents.isEmpty()) {
|
|
|
|
|
// Regular file
|
|
|
|
|
QFile file(path);
|
|
|
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
|
|
|
m_errorMessages.append(file.errorString());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-05-21 11:35:15 +02:00
|
|
|
|
2016-10-24 19:30:24 +02:00
|
|
|
QString error_msg;
|
|
|
|
|
int error_line, error_col;
|
|
|
|
|
if (!doc.setContent(&file, &error_msg, &error_line, &error_col)) {
|
|
|
|
|
m_errorMessages.append(tr("XML error on line %1, col %2: %3")
|
|
|
|
|
.arg(error_line).arg(error_col).arg(error_msg));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Virtual file from qmake evaluator
|
|
|
|
|
QString error_msg;
|
|
|
|
|
int error_line, error_col;
|
|
|
|
|
if (!doc.setContent(contents, &error_msg, &error_line, &error_col)) {
|
|
|
|
|
m_errorMessages.append(tr("XML error on line %1, col %2: %3")
|
|
|
|
|
.arg(error_line).arg(error_col).arg(error_msg));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-05-21 11:35:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QDomElement root = doc.firstChildElement(QLatin1String("RCC"));
|
|
|
|
|
if (root.isNull()) {
|
|
|
|
|
m_errorMessages.append(tr("The <RCC> root element is missing."));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QDomElement relt = root.firstChildElement(QLatin1String("qresource"));
|
|
|
|
|
for (; !relt.isNull(); relt = relt.nextSiblingElement(QLatin1String("qresource"))) {
|
|
|
|
|
|
|
|
|
|
QString prefix = fixPrefix(relt.attribute(QLatin1String("prefix")));
|
|
|
|
|
const QString language = relt.attribute(QLatin1String("lang"));
|
|
|
|
|
if (!m_languages.contains(language))
|
|
|
|
|
m_languages.append(language);
|
|
|
|
|
|
|
|
|
|
QDomElement felt = relt.firstChildElement(QLatin1String("file"));
|
|
|
|
|
for (; !felt.isNull(); felt = felt.nextSiblingElement(QLatin1String("file"))) {
|
|
|
|
|
const QString fileName = felt.text();
|
|
|
|
|
const QString alias = felt.attribute(QLatin1String("alias"));
|
2016-10-21 12:19:47 +02:00
|
|
|
QString filePath = baseDir.absoluteFilePath(fileName);
|
2013-05-21 11:35:15 +02:00
|
|
|
QString accessPath;
|
|
|
|
|
if (!alias.isEmpty())
|
|
|
|
|
accessPath = language + prefix + alias;
|
|
|
|
|
else
|
|
|
|
|
accessPath = language + prefix + fileName;
|
2016-03-18 17:43:33 +01:00
|
|
|
QStringList &resources = m_resources[accessPath];
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
if (!resources.contains(filePath)) {
|
2016-03-18 17:43:33 +01:00
|
|
|
resources.append(filePath);
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
QString reversePath(accessPath);
|
|
|
|
|
std::reverse(reversePath.begin(), reversePath.end());
|
|
|
|
|
if (!reversePath.endsWith('/'))
|
|
|
|
|
reversePath.append('/');
|
|
|
|
|
m_reverseResources[reversePath].append(filePath);
|
|
|
|
|
}
|
2016-03-18 17:43:33 +01:00
|
|
|
QStringList &files = m_files[filePath];
|
|
|
|
|
if (!files.contains(accessPath))
|
|
|
|
|
files.append(accessPath);
|
2013-05-21 11:35:15 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// path is assumed to be a normalized absolute path
|
|
|
|
|
QString QrcParserPrivate::firstFileAtPath(const QString &path, const QLocale &locale) const
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(path.startsWith(QLatin1Char('/')));
|
2019-03-19 10:23:42 +01:00
|
|
|
for (const QString &language : allUiLanguages(&locale)) {
|
2013-05-21 11:35:15 +02:00
|
|
|
if (m_languages.contains(language)) {
|
|
|
|
|
SMap::const_iterator res = m_resources.find(language + path);
|
|
|
|
|
if (res != m_resources.end())
|
|
|
|
|
return res.value().at(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QrcParserPrivate::collectFilesAtPath(const QString &path, QStringList *files,
|
|
|
|
|
const QLocale *locale) const
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(path.startsWith(QLatin1Char('/')));
|
2019-03-19 10:23:42 +01:00
|
|
|
for (const QString &language : allUiLanguages(locale)) {
|
2013-05-21 11:35:15 +02:00
|
|
|
if (m_languages.contains(language)) {
|
|
|
|
|
SMap::const_iterator res = m_resources.find(language + path);
|
|
|
|
|
if (res != m_resources.end())
|
|
|
|
|
(*files) << res.value();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// path is expected to be normalized and start and end with a slash
|
|
|
|
|
bool QrcParserPrivate::hasDirAtPath(const QString &path, const QLocale *locale) const
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(path.startsWith(QLatin1Char('/')));
|
|
|
|
|
QTC_CHECK(path.endsWith(QLatin1Char('/')));
|
2019-03-19 10:23:42 +01:00
|
|
|
for (const QString &language : allUiLanguages(locale)) {
|
2013-05-21 11:35:15 +02:00
|
|
|
if (m_languages.contains(language)) {
|
|
|
|
|
QString key = language + path;
|
|
|
|
|
SMap::const_iterator res = m_resources.lowerBound(key);
|
|
|
|
|
if (res != m_resources.end() && res.key().startsWith(key))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QrcParserPrivate::collectFilesInPath(const QString &path, QMap<QString,QStringList> *contents,
|
|
|
|
|
bool addDirs, const QLocale *locale) const
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(path.startsWith(QLatin1Char('/')));
|
|
|
|
|
QTC_CHECK(path.endsWith(QLatin1Char('/')));
|
|
|
|
|
SMap::const_iterator end = m_resources.end();
|
2019-03-19 10:23:42 +01:00
|
|
|
for (const QString &language : allUiLanguages(locale)) {
|
2013-05-21 11:35:15 +02:00
|
|
|
QString key = language + path;
|
|
|
|
|
SMap::const_iterator res = m_resources.lowerBound(key);
|
|
|
|
|
while (res != end && res.key().startsWith(key)) {
|
|
|
|
|
const QString &actualKey = res.key();
|
|
|
|
|
int endDir = actualKey.indexOf(QLatin1Char('/'), key.size());
|
|
|
|
|
if (endDir == -1) {
|
|
|
|
|
QString fileName = res.key().right(res.key().size()-key.size());
|
|
|
|
|
QStringList &els = (*contents)[fileName];
|
2019-03-19 10:23:42 +01:00
|
|
|
for (const QString &val : res.value())
|
2013-11-11 22:20:47 +02:00
|
|
|
if (!els.contains(val))
|
2013-05-21 11:35:15 +02:00
|
|
|
els << val;
|
|
|
|
|
++res;
|
|
|
|
|
} else {
|
|
|
|
|
QString dirName = res.key().mid(key.size(), endDir - key.size() + 1);
|
|
|
|
|
if (addDirs)
|
|
|
|
|
contents->insert(dirName, QStringList());
|
|
|
|
|
QString key2 = key + dirName;
|
|
|
|
|
do {
|
|
|
|
|
++res;
|
|
|
|
|
} while (res != end && res.key().startsWith(key2));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-18 17:43:33 +01:00
|
|
|
void QrcParserPrivate::collectResourceFilesForSourceFile(const QString &sourceFile,
|
|
|
|
|
QStringList *results,
|
|
|
|
|
const QLocale *locale) const
|
|
|
|
|
{
|
2019-01-18 20:28:55 +01:00
|
|
|
// TODO: use FileName from fileutils for file paths
|
2016-04-05 12:58:59 +02:00
|
|
|
|
2019-03-19 10:23:42 +01:00
|
|
|
const QStringList langs = allUiLanguages(locale);
|
2016-03-18 17:43:33 +01:00
|
|
|
SMap::const_iterator file = m_files.find(sourceFile);
|
|
|
|
|
if (file == m_files.end())
|
|
|
|
|
return;
|
2019-03-19 10:23:42 +01:00
|
|
|
for (const QString &resource : file.value()) {
|
|
|
|
|
for (const QString &language : langs) {
|
2016-03-18 17:43:33 +01:00
|
|
|
if (resource.startsWith(language) && !results->contains(resource))
|
|
|
|
|
results->append(resource);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
qmljs: avoid linking to files in the build directory
cmake creates a consistent uri structure in the build directory.
We use that as import path, but when we find a type in them we should
refer back to the original file, as editing those is dangerous because
any edit are lost with the next build.
To find the original file we use the qrc, as the qrc path is mostly
the same of as the uri path.
It is possible to add prefixes which would make an exact match fail,
so we compare the paths from the right to the left and find the
longest match.
To acheive this:
* QrcParser keeps a reversedResources, so the match from right can be
done efficiently, and provides a longestReverseMatches method
* the model manager keeps a list of all common prefixes of the
application paths (build directories), and identify all files in
build directories
* the method fileToSource identifies the files in the build directory
and tries to find the corresponding source file, warning if he
cannot find it
* fileToSource is used for follow Symbol and find usages
We could use fileToSource much more aggressively, to use to in editor
content for the files in the build directory, increasing the
consistency, but that is a more dangerous change for later.
Fixes: QTCREATORBUG-27173
Change-Id: Iea61b9825e5f6e433a7390cf2de9564b792458a5
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-06-17 15:46:52 +02:00
|
|
|
QrcParser::MatchResult QrcParserPrivate::longestReverseMatches(const QString &reversePath) const
|
|
|
|
|
{
|
|
|
|
|
QrcParser::MatchResult res;
|
|
|
|
|
if (reversePath.length() == 1)
|
|
|
|
|
return res;
|
|
|
|
|
auto lastMatch = m_reverseResources.end();
|
|
|
|
|
qsizetype matchedUntil = 0;
|
|
|
|
|
for (qsizetype i = 1, j = 0; i < reversePath.size(); i = j + 1) {
|
|
|
|
|
j = reversePath.indexOf(u'/', i);
|
|
|
|
|
if (j == -1)
|
|
|
|
|
j = reversePath.size() - 1;
|
|
|
|
|
auto match = m_reverseResources.lowerBound(reversePath.mid(0, j + 1));
|
|
|
|
|
QString pNow = reversePath.left(j + 1);
|
|
|
|
|
if (match == m_reverseResources.end() || match.key().left(j + 1) != pNow)
|
|
|
|
|
break;
|
|
|
|
|
++res.matchDepth;
|
|
|
|
|
matchedUntil = j + 1;
|
|
|
|
|
lastMatch = match;
|
|
|
|
|
}
|
|
|
|
|
res.reversedPaths.clear();
|
|
|
|
|
res.sourceFiles.clear();
|
|
|
|
|
for (auto it = lastMatch; it != m_reverseResources.end()
|
|
|
|
|
&& it.key().left(matchedUntil) == reversePath.left(matchedUntil);
|
|
|
|
|
++it) {
|
|
|
|
|
res.reversedPaths.append(it.key());
|
|
|
|
|
for (const QString &filePath : it.value())
|
|
|
|
|
res.sourceFiles.append(Utils::FilePath::fromString(filePath));
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-21 11:35:15 +02:00
|
|
|
QStringList QrcParserPrivate::errorMessages() const
|
|
|
|
|
{
|
|
|
|
|
return m_errorMessages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList QrcParserPrivate::languages() const
|
|
|
|
|
{
|
|
|
|
|
return m_languages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QrcParserPrivate::fixPrefix(const QString &prefix)
|
|
|
|
|
{
|
|
|
|
|
const QChar slash = QLatin1Char('/');
|
|
|
|
|
QString result = QString(slash);
|
|
|
|
|
for (int i = 0; i < prefix.size(); ++i) {
|
|
|
|
|
const QChar c = prefix.at(i);
|
|
|
|
|
if (c == slash && result.at(result.size() - 1) == slash)
|
|
|
|
|
continue;
|
|
|
|
|
result.append(c);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!result.endsWith(slash))
|
|
|
|
|
result.append(slash);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-19 10:23:42 +01:00
|
|
|
const QStringList QrcParserPrivate::allUiLanguages(const QLocale *locale) const
|
2013-05-21 11:35:15 +02:00
|
|
|
{
|
|
|
|
|
if (!locale)
|
|
|
|
|
return languages();
|
2020-06-13 23:34:35 +03:00
|
|
|
bool hasEmptyString = false;
|
|
|
|
|
const QStringList langs = locale->uiLanguages();
|
|
|
|
|
QStringList allLangs = langs;
|
|
|
|
|
for (const QString &language : langs) {
|
|
|
|
|
if (language.isEmpty())
|
|
|
|
|
hasEmptyString = true;
|
|
|
|
|
else if (language.contains('_') || language.contains('-')) {
|
|
|
|
|
const QStringList splits = QString(language).replace('_', '-').split('-');
|
2020-06-17 10:17:36 +03:00
|
|
|
if (splits.size() > 1 && !allLangs.contains(splits.at(0)))
|
2020-06-13 23:34:35 +03:00
|
|
|
allLangs.append(splits.at(0));
|
2013-05-21 11:35:15 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-06-13 23:34:35 +03:00
|
|
|
if (!hasEmptyString)
|
|
|
|
|
allLangs.append(QString());
|
|
|
|
|
return allLangs;
|
2013-05-21 11:35:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ----------------
|
|
|
|
|
|
|
|
|
|
QrcCachePrivate::QrcCachePrivate(QrcCache *)
|
|
|
|
|
{ }
|
|
|
|
|
|
2016-10-24 19:30:24 +02:00
|
|
|
QrcParser::Ptr QrcCachePrivate::addPath(const QString &path, const QString &contents)
|
2013-05-21 11:35:15 +02:00
|
|
|
{
|
|
|
|
|
QPair<QrcParser::Ptr,int> currentValue;
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker l(&m_mutex);
|
2019-07-31 17:21:41 +02:00
|
|
|
currentValue = m_cache.value(path, {QrcParser::Ptr(nullptr), 0});
|
2013-05-21 11:35:15 +02:00
|
|
|
currentValue.second += 1;
|
|
|
|
|
if (currentValue.second > 1) {
|
|
|
|
|
m_cache.insert(path, currentValue);
|
|
|
|
|
return currentValue.first;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-24 19:30:24 +02:00
|
|
|
QrcParser::Ptr newParser = QrcParser::parseQrcFile(path, contents);
|
2013-05-21 11:35:15 +02:00
|
|
|
if (!newParser->isValid())
|
2019-02-07 17:22:39 +01:00
|
|
|
qCWarning(qrcParserLog) << "adding invalid qrc " << path << " to the cache:" << newParser->errorMessages();
|
2013-05-21 11:35:15 +02:00
|
|
|
{
|
|
|
|
|
QMutexLocker l(&m_mutex);
|
2019-07-31 17:21:41 +02:00
|
|
|
QPair<QrcParser::Ptr,int> currentValue = m_cache.value(path, {QrcParser::Ptr(nullptr), 0});
|
2013-05-21 11:35:15 +02:00
|
|
|
if (currentValue.first.isNull())
|
|
|
|
|
currentValue.first = newParser;
|
|
|
|
|
currentValue.second += 1;
|
|
|
|
|
m_cache.insert(path, currentValue);
|
|
|
|
|
return currentValue.first;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QrcCachePrivate::removePath(const QString &path)
|
|
|
|
|
{
|
|
|
|
|
QPair<QrcParser::Ptr,int> currentValue;
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker l(&m_mutex);
|
2019-07-31 17:21:41 +02:00
|
|
|
currentValue = m_cache.value(path, {QrcParser::Ptr(nullptr), 0});
|
2013-05-21 11:35:15 +02:00
|
|
|
if (currentValue.second == 1) {
|
|
|
|
|
m_cache.remove(path);
|
|
|
|
|
} else if (currentValue.second > 1) {
|
|
|
|
|
currentValue.second -= 1;
|
|
|
|
|
m_cache.insert(path, currentValue);
|
|
|
|
|
} else {
|
|
|
|
|
QTC_CHECK(!m_cache.contains(path));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-24 19:30:24 +02:00
|
|
|
QrcParser::Ptr QrcCachePrivate::updatePath(const QString &path, const QString &contents)
|
2013-05-21 11:35:15 +02:00
|
|
|
{
|
2016-10-24 19:30:24 +02:00
|
|
|
QrcParser::Ptr newParser = QrcParser::parseQrcFile(path, contents);
|
2013-05-21 11:35:15 +02:00
|
|
|
{
|
|
|
|
|
QMutexLocker l(&m_mutex);
|
2019-07-31 17:21:41 +02:00
|
|
|
QPair<QrcParser::Ptr,int> currentValue = m_cache.value(path, {QrcParser::Ptr(nullptr), 0});
|
2013-05-21 11:35:15 +02:00
|
|
|
currentValue.first = newParser;
|
2013-06-03 17:06:02 +02:00
|
|
|
if (currentValue.second == 0)
|
|
|
|
|
currentValue.second = 1; // add qrc files that are not in the resources of a project
|
2013-05-21 11:35:15 +02:00
|
|
|
m_cache.insert(path, currentValue);
|
|
|
|
|
return currentValue.first;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QrcParser::Ptr QrcCachePrivate::parsedPath(const QString &path)
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker l(&m_mutex);
|
2019-07-31 17:21:41 +02:00
|
|
|
QPair<QrcParser::Ptr,int> currentValue = m_cache.value(path, {QrcParser::Ptr(nullptr), 0});
|
2013-05-21 11:35:15 +02:00
|
|
|
return currentValue.first;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QrcCachePrivate::clear()
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker l(&m_mutex);
|
|
|
|
|
m_cache.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace QmlJS
|