// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qqmljsresourcefilemapper_p.h" #include #include #include QT_BEGIN_NAMESPACE QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::allQmlJSFilter() { return Filter { QString(), QStringList { QStringLiteral("qml"), QStringLiteral("js"), QStringLiteral("mjs") }, Directory | Recurse }; } QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::localFileFilter(const QString &file) { return Filter { QFileInfo(file).canonicalFilePath(), QStringList(), File }; } QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::resourceFileFilter(const QString &file) { return Filter { file, QStringList(), Resource }; } QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::resourceQmlDirectoryFilter( const QString &directory) { return Filter { directory, QStringList { QStringLiteral("qml") }, Directory | Resource }; } QQmlJSResourceFileMapper::QQmlJSResourceFileMapper(const QStringList &resourceFiles) { for (const QString &fileName: resourceFiles) { QFile f(fileName); if (!f.open(QIODevice::ReadOnly)) continue; populateFromQrcFile(f); } } bool QQmlJSResourceFileMapper::isEmpty() const { return qrcPathToFileSystemPath.isEmpty(); } bool QQmlJSResourceFileMapper::isFile(QStringView resourcePath) const { for (const auto &entry : qrcPathToFileSystemPath) { if (entry.resourcePath == resourcePath) return true; } return false; } static bool hasSuffix(const QString &qrcPath, const QStringList &suffixes) { if (suffixes.isEmpty()) return true; const QString suffix = QFileInfo(qrcPath).suffix(); return suffixes.contains(suffix); } template void doFilter(const QList &qrcPathToFileSystemPath, const QQmlJSResourceFileMapper::Filter &filter, const HandleMatch &handler) { if (filter.flags & QQmlJSResourceFileMapper::Directory) { const QString terminatedDirectory = filter.path.endsWith(u'/') ? filter.path : (filter.path + u'/'); for (auto it = qrcPathToFileSystemPath.constBegin(), end = qrcPathToFileSystemPath.constEnd(); it != end; ++it) { const QString candidate = (filter.flags & QQmlJSResourceFileMapper::Resource) ? it->resourcePath : it->filePath; if (!filter.path.isEmpty() && !candidate.startsWith(terminatedDirectory)) continue; if (!hasSuffix(candidate, filter.suffixes)) continue; if ((filter.flags & QQmlJSResourceFileMapper::Recurse) // Crude. But shall we really allow slashes in QRC file names? || !candidate.mid(terminatedDirectory.size()).contains(u'/')) { if (handler(*it)) return; } } return; } if (!hasSuffix(filter.path, filter.suffixes)) return; for (auto it = qrcPathToFileSystemPath.constBegin(), end = qrcPathToFileSystemPath.constEnd(); it != end; ++it) { if (filter.flags & QQmlJSResourceFileMapper::Resource) { if (it->resourcePath == filter.path && handler(*it)) return; } else if (it->filePath == filter.path && handler(*it)) { return; } } } QList QQmlJSResourceFileMapper::filter( const QQmlJSResourceFileMapper::Filter &filter) const { QList result; doFilter(qrcPathToFileSystemPath, filter, [&](const Entry &entry) { result.append(entry); return false; }); return result; } QStringList QQmlJSResourceFileMapper::filePaths( const QQmlJSResourceFileMapper::Filter &filter) const { QStringList result; doFilter(qrcPathToFileSystemPath, filter, [&](const Entry &entry) { result.append(entry.filePath); return false; }); return result; } QStringList QQmlJSResourceFileMapper::resourcePaths( const QQmlJSResourceFileMapper::Filter &filter) const { QStringList result; doFilter(qrcPathToFileSystemPath, filter, [&](const Entry &entry) { result.append(entry.resourcePath); return false; }); return result; } QQmlJSResourceFileMapper::Entry QQmlJSResourceFileMapper::entry( const QQmlJSResourceFileMapper::Filter &filter) const { Entry result; doFilter(qrcPathToFileSystemPath, filter, [&](const Entry &entry) { result = entry; return true; }); return result; } void QQmlJSResourceFileMapper::populateFromQrcFile(QFile &file) { enum State { InitialState, InRCC, InResource, InFile }; State state = InitialState; QDir qrcDir = QFileInfo(file).absoluteDir(); QString prefix; QString currentFileName; QXmlStreamAttributes currentFileAttributes; QXmlStreamReader reader(&file); while (!reader.atEnd()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement: if (reader.name() == QStringLiteral("RCC")) { if (state != InitialState) return; state = InRCC; continue; } else if (reader.name() == QStringLiteral("qresource")) { if (state != InRCC) return; state = InResource; QXmlStreamAttributes attributes = reader.attributes(); if (attributes.hasAttribute(QStringLiteral("prefix"))) prefix = attributes.value(QStringLiteral("prefix")).toString(); if (!prefix.startsWith(QLatin1Char('/'))) prefix.prepend(QLatin1Char('/')); if (!prefix.endsWith(QLatin1Char('/'))) prefix.append(QLatin1Char('/')); continue; } else if (reader.name() == QStringLiteral("file")) { if (state != InResource) return; state = InFile; currentFileAttributes = reader.attributes(); continue; } return; case QXmlStreamReader::EndElement: if (reader.name() == QStringLiteral("file")) { if (state != InFile) return; state = InResource; continue; } else if (reader.name() == QStringLiteral("qresource")) { if (state != InResource) return; state = InRCC; continue; } else if (reader.name() == QStringLiteral("RCC")) { if (state != InRCC) return; state = InitialState; continue; } return; case QXmlStreamReader::Characters: { if (reader.isWhitespace()) break; if (state != InFile) return; currentFileName = reader.text().toString(); if (currentFileName.isEmpty()) continue; const QString fsPath = QDir::cleanPath(qrcDir.absoluteFilePath(currentFileName)); if (currentFileAttributes.hasAttribute(QStringLiteral("alias"))) currentFileName = currentFileAttributes.value(QStringLiteral("alias")).toString(); currentFileName = QDir::cleanPath(currentFileName); while (currentFileName.startsWith(QLatin1String("../"))) currentFileName.remove(0, 3); const QString qrcPath = prefix + currentFileName; if (QFile::exists(fsPath)) qrcPathToFileSystemPath.append({qrcPath, fsPath}); continue; } default: break; } } } QT_END_NAMESPACE