1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmljsresourcefilemapper_p.h"
5
6#include <QFileInfo>
7#include <QDir>
8#include <QXmlStreamReader>
9
10QT_BEGIN_NAMESPACE
11
12QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::allQmlJSFilter() {
13 return Filter {
14 .path: QString(),
15 .suffixes: QStringList { QStringLiteral("qml"), QStringLiteral("js"), QStringLiteral("mjs") },
16 .flags: Directory | Recurse
17 };
18}
19
20QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::localFileFilter(const QString &file)
21{
22 return Filter {
23 .path: QFileInfo(file).canonicalFilePath(),
24 .suffixes: QStringList(),
25 .flags: File
26 };
27}
28
29QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::resourceFileFilter(const QString &file)
30{
31 return Filter {
32 .path: file,
33 .suffixes: QStringList(),
34 .flags: Resource
35 };
36}
37
38QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::resourceQmlDirectoryFilter(
39 const QString &directory)
40{
41 return Filter {
42 .path: directory,
43 .suffixes: QStringList { QStringLiteral("qml") },
44 .flags: Directory | Resource
45 };
46}
47
48QQmlJSResourceFileMapper::QQmlJSResourceFileMapper(const QStringList &resourceFiles)
49{
50 for (const QString &fileName: resourceFiles) {
51 QFile f(fileName);
52 if (!f.open(flags: QIODevice::ReadOnly))
53 continue;
54 populateFromQrcFile(file&: f);
55 }
56}
57
58bool QQmlJSResourceFileMapper::isEmpty() const
59{
60 return qrcPathToFileSystemPath.isEmpty();
61}
62
63bool QQmlJSResourceFileMapper::isFile(const QString &resourcePath) const
64{
65 for (const auto &entry : qrcPathToFileSystemPath) {
66 if (entry.resourcePath == resourcePath)
67 return true;
68 }
69 return false;
70}
71
72static bool hasSuffix(const QString &qrcPath, const QStringList &suffixes)
73{
74 if (suffixes.isEmpty())
75 return true;
76 const QString suffix = QFileInfo(qrcPath).suffix();
77 return suffixes.contains(str: suffix);
78}
79
80template<typename HandleMatch>
81void doFilter(const QList<QQmlJSResourceFileMapper::Entry> &qrcPathToFileSystemPath,
82 const QQmlJSResourceFileMapper::Filter &filter,
83 const HandleMatch &handler)
84{
85 if (filter.flags & QQmlJSResourceFileMapper::Directory) {
86 const QString terminatedDirectory = filter.path.endsWith(c: u'/')
87 ? filter.path : (filter.path + u'/');
88
89 for (auto it = qrcPathToFileSystemPath.constBegin(),
90 end = qrcPathToFileSystemPath.constEnd(); it != end; ++it) {
91
92 const QString candidate = (filter.flags & QQmlJSResourceFileMapper::Resource)
93 ? it->resourcePath
94 : it->filePath;
95
96 if (!filter.path.isEmpty() && !candidate.startsWith(s: terminatedDirectory))
97 continue;
98
99 if (!hasSuffix(qrcPath: candidate, suffixes: filter.suffixes))
100 continue;
101
102 if ((filter.flags & QQmlJSResourceFileMapper::Recurse)
103 // Crude. But shall we really allow slashes in QRC file names?
104 || !candidate.mid(position: terminatedDirectory.size()).contains(c: u'/')) {
105 if (handler(*it))
106 return;
107 }
108 }
109 return;
110 }
111
112 if (!hasSuffix(qrcPath: filter.path, suffixes: filter.suffixes))
113 return;
114
115 for (auto it = qrcPathToFileSystemPath.constBegin(),
116 end = qrcPathToFileSystemPath.constEnd(); it != end; ++it) {
117 if (filter.flags & QQmlJSResourceFileMapper::Resource) {
118 if (it->resourcePath == filter.path && handler(*it))
119 return;
120 } else if (it->filePath == filter.path && handler(*it)) {
121 return;
122 }
123 }
124}
125
126QList<QQmlJSResourceFileMapper::Entry> QQmlJSResourceFileMapper::filter(
127 const QQmlJSResourceFileMapper::Filter &filter) const
128{
129 QList<Entry> result;
130 doFilter(qrcPathToFileSystemPath, filter, handler: [&](const Entry &entry) {
131 result.append(t: entry);
132 return false;
133 });
134 return result;
135}
136
137QStringList QQmlJSResourceFileMapper::filePaths(
138 const QQmlJSResourceFileMapper::Filter &filter) const
139{
140 QStringList result;
141 doFilter(qrcPathToFileSystemPath, filter, handler: [&](const Entry &entry) {
142 result.append(t: entry.filePath);
143 return false;
144 });
145 return result;
146}
147
148QStringList QQmlJSResourceFileMapper::resourcePaths(
149 const QQmlJSResourceFileMapper::Filter &filter) const
150{
151 QStringList result;
152 doFilter(qrcPathToFileSystemPath, filter, handler: [&](const Entry &entry) {
153 result.append(t: entry.resourcePath);
154 return false;
155 });
156 return result;
157}
158
159QQmlJSResourceFileMapper::Entry QQmlJSResourceFileMapper::entry(
160 const QQmlJSResourceFileMapper::Filter &filter) const
161{
162 Entry result;
163 doFilter(qrcPathToFileSystemPath, filter, handler: [&](const Entry &entry) {
164 result = entry;
165 return true;
166 });
167 return result;
168}
169
170void QQmlJSResourceFileMapper::populateFromQrcFile(QFile &file)
171{
172 enum State {
173 InitialState,
174 InRCC,
175 InResource,
176 InFile
177 };
178 State state = InitialState;
179
180 QDir qrcDir = QFileInfo(file).absoluteDir();
181
182 QString prefix;
183 QString currentFileName;
184 QXmlStreamAttributes currentFileAttributes;
185
186 QXmlStreamReader reader(&file);
187 while (!reader.atEnd()) {
188 switch (reader.readNext()) {
189 case QXmlStreamReader::StartElement:
190 if (reader.name() == QStringLiteral("RCC")) {
191 if (state != InitialState)
192 return;
193 state = InRCC;
194 continue;
195 } else if (reader.name() == QStringLiteral("qresource")) {
196 if (state != InRCC)
197 return;
198 state = InResource;
199 QXmlStreamAttributes attributes = reader.attributes();
200 if (attributes.hasAttribute(QStringLiteral("prefix")))
201 prefix = attributes.value(QStringLiteral("prefix")).toString();
202 if (!prefix.startsWith(c: QLatin1Char('/')))
203 prefix.prepend(c: QLatin1Char('/'));
204 if (!prefix.endsWith(c: QLatin1Char('/')))
205 prefix.append(c: QLatin1Char('/'));
206 continue;
207 } else if (reader.name() == QStringLiteral("file")) {
208 if (state != InResource)
209 return;
210 state = InFile;
211 currentFileAttributes = reader.attributes();
212 continue;
213 }
214 return;
215
216 case QXmlStreamReader::EndElement:
217 if (reader.name() == QStringLiteral("file")) {
218 if (state != InFile)
219 return;
220 state = InResource;
221 continue;
222 } else if (reader.name() == QStringLiteral("qresource")) {
223 if (state != InResource)
224 return;
225 state = InRCC;
226 continue;
227 } else if (reader.name() == QStringLiteral("RCC")) {
228 if (state != InRCC)
229 return;
230 state = InitialState;
231 continue;
232 }
233 return;
234
235 case QXmlStreamReader::Characters: {
236 if (reader.isWhitespace())
237 break;
238 if (state != InFile)
239 return;
240 currentFileName = reader.text().toString();
241 if (currentFileName.isEmpty())
242 continue;
243
244 const QString fsPath = QDir::cleanPath(path: qrcDir.absoluteFilePath(fileName: currentFileName));
245
246 if (currentFileAttributes.hasAttribute(QStringLiteral("alias")))
247 currentFileName = currentFileAttributes.value(QStringLiteral("alias")).toString();
248
249 currentFileName = QDir::cleanPath(path: currentFileName);
250 while (currentFileName.startsWith(s: QLatin1String("../")))
251 currentFileName.remove(i: 0, len: 3);
252
253 const QString qrcPath = prefix + currentFileName;
254 if (QFile::exists(fileName: fsPath))
255 qrcPathToFileSystemPath.append(t: {.resourcePath: qrcPath, .filePath: fsPath});
256 continue;
257 }
258
259 default: break;
260 }
261 }
262}
263
264QT_END_NAMESPACE
265

source code of qtdeclarative/src/qmlcompiler/qqmljsresourcefilemapper.cpp