1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Designer of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | /* Note: This is a copy of qtbase/src/tools/rcc/rcc.cpp. */ |
30 | |
31 | #include "rcc_p.h" |
32 | |
33 | #include <QtCore/qbytearray.h> |
34 | #include <QtCore/qdatetime.h> |
35 | #include <QtCore/qdebug.h> |
36 | #include <QtCore/qdir.h> |
37 | #include <QtCore/qdiriterator.h> |
38 | #include <QtCore/qfile.h> |
39 | #include <QtCore/qiodevice.h> |
40 | #include <QtCore/qlocale.h> |
41 | #include <QtCore/qregexp.h> |
42 | #include <QtCore/qstack.h> |
43 | #include <QtCore/qxmlstream.h> |
44 | |
45 | #include <algorithm> |
46 | |
47 | QT_BEGIN_NAMESPACE |
48 | |
49 | enum { |
50 | CONSTANT_USENAMESPACE = 1, |
51 | CONSTANT_COMPRESSLEVEL_DEFAULT = -1, |
52 | CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 |
53 | }; |
54 | |
55 | |
56 | #define writeString(s) write(s, sizeof(s)) |
57 | |
58 | void RCCResourceLibrary::write(const char *str, int len) |
59 | { |
60 | --len; // trailing \0 on string literals... |
61 | int n = m_out.size(); |
62 | m_out.resize(size: n + len); |
63 | memcpy(dest: m_out.data() + n, src: str, n: len); |
64 | } |
65 | |
66 | void RCCResourceLibrary::writeByteArray(const QByteArray &other) |
67 | { |
68 | m_out.append(a: other); |
69 | } |
70 | |
71 | static inline QString msgOpenReadFailed(const QString &fname, const QString &why) |
72 | { |
73 | return QString::fromUtf8(str: "Unable to open %1 for reading: %2\n" ).arg(a: fname).arg(a: why); |
74 | } |
75 | |
76 | |
77 | /////////////////////////////////////////////////////////// |
78 | // |
79 | // RCCFileInfo |
80 | // |
81 | /////////////////////////////////////////////////////////// |
82 | |
83 | class RCCFileInfo |
84 | { |
85 | public: |
86 | enum Flags |
87 | { |
88 | NoFlags = 0x00, |
89 | Compressed = 0x01, |
90 | Directory = 0x02 |
91 | }; |
92 | |
93 | RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(), |
94 | QLocale::Language language = QLocale::C, |
95 | QLocale::Country country = QLocale::AnyCountry, |
96 | uint flags = NoFlags, |
97 | int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT, |
98 | int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT); |
99 | ~RCCFileInfo(); |
100 | |
101 | QString resourceName() const; |
102 | |
103 | public: |
104 | qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage); |
105 | qint64 writeDataName(RCCResourceLibrary &, qint64 offset); |
106 | void writeDataInfo(RCCResourceLibrary &lib); |
107 | |
108 | int m_flags; |
109 | QString m_name; |
110 | QLocale::Language m_language; |
111 | QLocale::Country m_country; |
112 | QFileInfo m_fileInfo; |
113 | RCCFileInfo *m_parent; |
114 | QHash<QString, RCCFileInfo*> m_children; |
115 | int m_compressLevel; |
116 | int m_compressThreshold; |
117 | |
118 | qint64 m_nameOffset; |
119 | qint64 m_dataOffset; |
120 | qint64 m_childOffset; |
121 | }; |
122 | |
123 | RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, |
124 | QLocale::Language language, QLocale::Country country, uint flags, |
125 | int compressLevel, int compressThreshold) |
126 | { |
127 | m_name = name; |
128 | m_fileInfo = fileInfo; |
129 | m_language = language; |
130 | m_country = country; |
131 | m_flags = flags; |
132 | m_parent = nullptr; |
133 | m_nameOffset = 0; |
134 | m_dataOffset = 0; |
135 | m_childOffset = 0; |
136 | m_compressLevel = compressLevel; |
137 | m_compressThreshold = compressThreshold; |
138 | } |
139 | |
140 | RCCFileInfo::~RCCFileInfo() |
141 | { |
142 | qDeleteAll(c: m_children); |
143 | } |
144 | |
145 | QString RCCFileInfo::resourceName() const |
146 | { |
147 | QString resource = m_name; |
148 | for (RCCFileInfo *p = m_parent; p; p = p->m_parent) |
149 | resource = resource.prepend(s: p->m_name + QLatin1Char('/')); |
150 | return QLatin1Char(':') + resource; |
151 | } |
152 | |
153 | void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) |
154 | { |
155 | const bool text = (lib.m_format == RCCResourceLibrary::C_Code); |
156 | //some info |
157 | if (text) { |
158 | if (m_language != QLocale::C) { |
159 | lib.writeString(" // " ); |
160 | lib.writeByteArray(other: resourceName().toLocal8Bit()); |
161 | lib.writeString(" [" ); |
162 | lib.writeByteArray(other: QByteArray::number(m_country)); |
163 | lib.writeString("::" ); |
164 | lib.writeByteArray(other: QByteArray::number(m_language)); |
165 | lib.writeString("[\n " ); |
166 | } else { |
167 | lib.writeString(" // " ); |
168 | lib.writeByteArray(other: resourceName().toLocal8Bit()); |
169 | lib.writeString("\n " ); |
170 | } |
171 | } |
172 | |
173 | //pointer data |
174 | if (m_flags & RCCFileInfo::Directory) { |
175 | // name offset |
176 | lib.writeNumber4(number: m_nameOffset); |
177 | |
178 | // flags |
179 | lib.writeNumber2(number: m_flags); |
180 | |
181 | // child count |
182 | lib.writeNumber4(number: m_children.size()); |
183 | |
184 | // first child offset |
185 | lib.writeNumber4(number: m_childOffset); |
186 | } else { |
187 | // name offset |
188 | lib.writeNumber4(number: m_nameOffset); |
189 | |
190 | // flags |
191 | lib.writeNumber2(number: m_flags); |
192 | |
193 | // locale |
194 | lib.writeNumber2(number: m_country); |
195 | lib.writeNumber2(number: m_language); |
196 | |
197 | //data offset |
198 | lib.writeNumber4(number: m_dataOffset); |
199 | } |
200 | if (text) |
201 | lib.writeChar(c: '\n'); |
202 | } |
203 | |
204 | qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, |
205 | QString *errorMessage) |
206 | { |
207 | const bool text = (lib.m_format == RCCResourceLibrary::C_Code); |
208 | |
209 | //capture the offset |
210 | m_dataOffset = offset; |
211 | |
212 | //find the data to be written |
213 | QFile file(m_fileInfo.absoluteFilePath()); |
214 | if (!file.open(flags: QFile::ReadOnly)) { |
215 | *errorMessage = msgOpenReadFailed(fname: m_fileInfo.absoluteFilePath(), why: file.errorString()); |
216 | return 0; |
217 | } |
218 | QByteArray data = file.readAll(); |
219 | |
220 | #ifndef QT_NO_COMPRESS |
221 | // Check if compression is useful for this file |
222 | if (m_compressLevel != 0 && data.size() != 0) { |
223 | QByteArray compressed = |
224 | qCompress(data: reinterpret_cast<uchar *>(data.data()), nbytes: data.size(), compressionLevel: m_compressLevel); |
225 | |
226 | int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); |
227 | if (compressRatio >= m_compressThreshold) { |
228 | data = compressed; |
229 | m_flags |= Compressed; |
230 | } |
231 | } |
232 | #endif // QT_NO_COMPRESS |
233 | |
234 | // some info |
235 | if (text) { |
236 | lib.writeString(" // " ); |
237 | lib.writeByteArray(other: m_fileInfo.absoluteFilePath().toLocal8Bit()); |
238 | lib.writeString("\n " ); |
239 | } |
240 | |
241 | // write the length |
242 | |
243 | lib.writeNumber4(number: data.size()); |
244 | if (text) |
245 | lib.writeString("\n " ); |
246 | offset += 4; |
247 | |
248 | // write the payload |
249 | const char *p = data.constData(); |
250 | if (text) { |
251 | for (int i = data.size(), j = 0; --i >= 0; --j) { |
252 | lib.writeHex(number: *p++); |
253 | if (j == 0) { |
254 | lib.writeString("\n " ); |
255 | j = 16; |
256 | } |
257 | } |
258 | } else { |
259 | for (int i = data.size(); --i >= 0; ) |
260 | lib.writeChar(c: *p++); |
261 | } |
262 | offset += data.size(); |
263 | |
264 | // done |
265 | if (text) |
266 | lib.writeString("\n " ); |
267 | return offset; |
268 | } |
269 | |
270 | qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) |
271 | { |
272 | const bool text = (lib.m_format == RCCResourceLibrary::C_Code); |
273 | |
274 | // capture the offset |
275 | m_nameOffset = offset; |
276 | |
277 | // some info |
278 | if (text) { |
279 | lib.writeString(" // " ); |
280 | lib.writeByteArray(other: m_name.toLocal8Bit()); |
281 | lib.writeString("\n " ); |
282 | } |
283 | |
284 | // write the length |
285 | lib.writeNumber2(number: m_name.length()); |
286 | if (text) |
287 | lib.writeString("\n " ); |
288 | offset += 2; |
289 | |
290 | // write the hash |
291 | lib.writeNumber4(number: qt_hash(key: m_name)); |
292 | if (text) |
293 | lib.writeString("\n " ); |
294 | offset += 4; |
295 | |
296 | // write the m_name |
297 | const QChar *unicode = m_name.unicode(); |
298 | for (int i = 0; i < m_name.length(); ++i) { |
299 | lib.writeNumber2(number: unicode[i].unicode()); |
300 | if (text && i % 16 == 0) |
301 | lib.writeString("\n " ); |
302 | } |
303 | offset += m_name.length()*2; |
304 | |
305 | // done |
306 | if (text) |
307 | lib.writeString("\n " ); |
308 | return offset; |
309 | } |
310 | |
311 | |
312 | /////////////////////////////////////////////////////////// |
313 | // |
314 | // RCCResourceLibrary |
315 | // |
316 | /////////////////////////////////////////////////////////// |
317 | |
318 | RCCResourceLibrary::Strings::Strings() : |
319 | TAG_RCC(QLatin1String("RCC" )), |
320 | TAG_RESOURCE(QLatin1String("qresource" )), |
321 | TAG_FILE(QLatin1String("file" )), |
322 | ATTRIBUTE_LANG(QLatin1String("lang" )), |
323 | ATTRIBUTE_PREFIX(QLatin1String("prefix" )), |
324 | ATTRIBUTE_ALIAS(QLatin1String("alias" )), |
325 | ATTRIBUTE_THRESHOLD(QLatin1String("threshold" )), |
326 | ATTRIBUTE_COMPRESS(QLatin1String("compress" )) |
327 | { |
328 | } |
329 | |
330 | RCCResourceLibrary::RCCResourceLibrary() |
331 | : m_root(nullptr), |
332 | m_format(C_Code), |
333 | m_verbose(false), |
334 | m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), |
335 | m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), |
336 | m_treeOffset(0), |
337 | m_namesOffset(0), |
338 | m_dataOffset(0), |
339 | m_useNameSpace(CONSTANT_USENAMESPACE), |
340 | m_errorDevice(0) |
341 | { |
342 | m_out.reserve(asize: 30 * 1000 * 1000); |
343 | } |
344 | |
345 | RCCResourceLibrary::~RCCResourceLibrary() |
346 | { |
347 | delete m_root; |
348 | } |
349 | |
350 | enum RCCXmlTag { |
351 | RccTag, |
352 | ResourceTag, |
353 | FileTag |
354 | }; |
355 | |
356 | bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, |
357 | const QString &fname, QString currentPath, bool ignoreErrors) |
358 | { |
359 | Q_ASSERT(m_errorDevice); |
360 | const QChar slash = QLatin1Char('/'); |
361 | if (!currentPath.isEmpty() && !currentPath.endsWith(c: slash)) |
362 | currentPath += slash; |
363 | |
364 | QXmlStreamReader reader(inputDevice); |
365 | QStack<RCCXmlTag> tokens; |
366 | |
367 | QString prefix; |
368 | QLocale::Language language = QLocale::c().language(); |
369 | QLocale::Country country = QLocale::c().country(); |
370 | QString alias; |
371 | int compressLevel = m_compressLevel; |
372 | int compressThreshold = m_compressThreshold; |
373 | |
374 | while (!reader.atEnd()) { |
375 | QXmlStreamReader::TokenType t = reader.readNext(); |
376 | switch (t) { |
377 | case QXmlStreamReader::StartElement: |
378 | if (reader.name() == m_strings.TAG_RCC) { |
379 | if (!tokens.isEmpty()) |
380 | reader.raiseError(message: QLatin1String("expected <RCC> tag" )); |
381 | else |
382 | tokens.push(t: RccTag); |
383 | } else if (reader.name() == m_strings.TAG_RESOURCE) { |
384 | if (tokens.isEmpty() || tokens.top() != RccTag) { |
385 | reader.raiseError(message: QLatin1String("unexpected <RESOURCE> tag" )); |
386 | } else { |
387 | tokens.push(t: ResourceTag); |
388 | |
389 | QXmlStreamAttributes attributes = reader.attributes(); |
390 | language = QLocale::c().language(); |
391 | country = QLocale::c().country(); |
392 | |
393 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_LANG)) { |
394 | QString attribute = attributes.value(qualifiedName: m_strings.ATTRIBUTE_LANG).toString(); |
395 | QLocale lang = QLocale(attribute); |
396 | language = lang.language(); |
397 | if (2 == attribute.length()) { |
398 | // Language only |
399 | country = QLocale::AnyCountry; |
400 | } else { |
401 | country = lang.country(); |
402 | } |
403 | } |
404 | |
405 | prefix.clear(); |
406 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_PREFIX)) |
407 | prefix = attributes.value(qualifiedName: m_strings.ATTRIBUTE_PREFIX).toString(); |
408 | if (!prefix.startsWith(c: slash)) |
409 | prefix.prepend(c: slash); |
410 | if (!prefix.endsWith(c: slash)) |
411 | prefix += slash; |
412 | } |
413 | } else if (reader.name() == m_strings.TAG_FILE) { |
414 | if (tokens.isEmpty() || tokens.top() != ResourceTag) { |
415 | reader.raiseError(message: QLatin1String("unexpected <FILE> tag" )); |
416 | } else { |
417 | tokens.push(t: FileTag); |
418 | |
419 | QXmlStreamAttributes attributes = reader.attributes(); |
420 | alias.clear(); |
421 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_ALIAS)) |
422 | alias = attributes.value(qualifiedName: m_strings.ATTRIBUTE_ALIAS).toString(); |
423 | |
424 | compressLevel = m_compressLevel; |
425 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_COMPRESS)) |
426 | compressLevel = attributes.value(qualifiedName: m_strings.ATTRIBUTE_COMPRESS).toString().toInt(); |
427 | |
428 | compressThreshold = m_compressThreshold; |
429 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_THRESHOLD)) |
430 | compressThreshold = attributes.value(qualifiedName: m_strings.ATTRIBUTE_THRESHOLD).toString().toInt(); |
431 | |
432 | // Special case for -no-compress. Overrides all other settings. |
433 | if (m_compressLevel == -2) |
434 | compressLevel = 0; |
435 | } |
436 | } else { |
437 | reader.raiseError(message: QString(QLatin1String("unexpected tag: %1" )).arg(a: reader.name().toString())); |
438 | } |
439 | break; |
440 | |
441 | case QXmlStreamReader::EndElement: |
442 | if (reader.name() == m_strings.TAG_RCC) { |
443 | if (!tokens.isEmpty() && tokens.top() == RccTag) |
444 | tokens.pop(); |
445 | else |
446 | reader.raiseError(message: QLatin1String("unexpected closing tag" )); |
447 | } else if (reader.name() == m_strings.TAG_RESOURCE) { |
448 | if (!tokens.isEmpty() && tokens.top() == ResourceTag) |
449 | tokens.pop(); |
450 | else |
451 | reader.raiseError(message: QLatin1String("unexpected closing tag" )); |
452 | } else if (reader.name() == m_strings.TAG_FILE) { |
453 | if (!tokens.isEmpty() && tokens.top() == FileTag) |
454 | tokens.pop(); |
455 | else |
456 | reader.raiseError(message: QLatin1String("unexpected closing tag" )); |
457 | } |
458 | break; |
459 | |
460 | case QXmlStreamReader::Characters: |
461 | if (reader.isWhitespace()) |
462 | break; |
463 | if (tokens.isEmpty() || tokens.top() != FileTag) { |
464 | reader.raiseError(message: QLatin1String("unexpected text" )); |
465 | } else { |
466 | QString fileName = reader.text().toString(); |
467 | if (fileName.isEmpty()) { |
468 | const QString msg = QString::fromLatin1(str: "RCC: Warning: Null node in XML of '%1'\n" ).arg(a: fname); |
469 | m_errorDevice->write(data: msg.toUtf8()); |
470 | } |
471 | |
472 | if (alias.isNull()) |
473 | alias = fileName; |
474 | |
475 | alias = QDir::cleanPath(path: alias); |
476 | while (alias.startsWith(s: QLatin1String("../" ))) |
477 | alias.remove(i: 0, len: 3); |
478 | alias = QDir::cleanPath(path: m_resourceRoot) + prefix + alias; |
479 | |
480 | QString absFileName = fileName; |
481 | if (QDir::isRelativePath(path: absFileName)) |
482 | absFileName.prepend(s: currentPath); |
483 | QFileInfo file(absFileName); |
484 | if (!file.exists()) { |
485 | m_failedResources.push_back(t: absFileName); |
486 | const QString msg = QString::fromLatin1(str: "RCC: Error in '%1': Cannot find file '%2'\n" ).arg(a: fname).arg(a: fileName); |
487 | m_errorDevice->write(data: msg.toUtf8()); |
488 | if (ignoreErrors) |
489 | continue; |
490 | else |
491 | return false; |
492 | } else if (file.isFile()) { |
493 | const bool arc = |
494 | addFile(alias, |
495 | file: RCCFileInfo(alias.section(asep: slash, astart: -1), |
496 | file, |
497 | language, |
498 | country, |
499 | RCCFileInfo::NoFlags, |
500 | compressLevel, |
501 | compressThreshold) |
502 | ); |
503 | if (!arc) |
504 | m_failedResources.push_back(t: absFileName); |
505 | } else { |
506 | QDir dir; |
507 | if (file.isDir()) { |
508 | dir.setPath(file.filePath()); |
509 | } else { |
510 | dir.setPath(file.path()); |
511 | dir.setNameFilters(QStringList(file.fileName())); |
512 | if (alias.endsWith(s: file.fileName())) |
513 | alias = alias.left(n: alias.length()-file.fileName().length()); |
514 | } |
515 | if (!alias.endsWith(c: slash)) |
516 | alias += slash; |
517 | QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); |
518 | while (it.hasNext()) { |
519 | it.next(); |
520 | QFileInfo child(it.fileInfo()); |
521 | if (child.fileName() != QLatin1String("." ) && child.fileName() != QLatin1String(".." )) { |
522 | const bool arc = |
523 | addFile(alias: alias + child.fileName(), |
524 | file: RCCFileInfo(child.fileName(), |
525 | child, |
526 | language, |
527 | country, |
528 | RCCFileInfo::NoFlags, |
529 | compressLevel, |
530 | compressThreshold) |
531 | ); |
532 | if (!arc) |
533 | m_failedResources.push_back(t: child.fileName()); |
534 | } |
535 | } |
536 | } |
537 | } |
538 | break; |
539 | |
540 | default: |
541 | break; |
542 | } |
543 | } |
544 | |
545 | if (reader.hasError()) { |
546 | if (ignoreErrors) |
547 | return true; |
548 | int errorLine = reader.lineNumber(); |
549 | int errorColumn = reader.columnNumber(); |
550 | QString errorMessage = reader.errorString(); |
551 | QString msg = QString::fromLatin1(str: "RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n" ).arg(a: fname).arg(a: errorLine).arg(a: errorColumn).arg(a: errorMessage); |
552 | m_errorDevice->write(data: msg.toUtf8()); |
553 | return false; |
554 | } |
555 | |
556 | if (m_root == nullptr) { |
557 | const QString msg = QString::fromUtf8(str: "RCC: Warning: No resources in '%1'.\n" ).arg(a: fname); |
558 | m_errorDevice->write(data: msg.toUtf8()); |
559 | if (!ignoreErrors && m_format == Binary) { |
560 | // create dummy entry, otherwise loading with QResource will crash |
561 | m_root = new RCCFileInfo(QString(), QFileInfo(), |
562 | QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); |
563 | } |
564 | } |
565 | |
566 | return true; |
567 | } |
568 | |
569 | bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file) |
570 | { |
571 | Q_ASSERT(m_errorDevice); |
572 | if (file.m_fileInfo.size() > 0xffffffff) { |
573 | const QString msg = QString::fromUtf8(str: "File too big: %1\n" ).arg(a: file.m_fileInfo.absoluteFilePath()); |
574 | m_errorDevice->write(data: msg.toUtf8()); |
575 | return false; |
576 | } |
577 | if (!m_root) |
578 | m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); |
579 | |
580 | RCCFileInfo *parent = m_root; |
581 | const QStringList nodes = alias.split(sep: QLatin1Char('/')); |
582 | for (int i = 1; i < nodes.size()-1; ++i) { |
583 | const QString node = nodes.at(i); |
584 | if (node.isEmpty()) |
585 | continue; |
586 | if (!parent->m_children.contains(akey: node)) { |
587 | RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); |
588 | s->m_parent = parent; |
589 | parent->m_children.insert(akey: node, avalue: s); |
590 | parent = s; |
591 | } else { |
592 | parent = *parent->m_children.constFind(akey: node); |
593 | } |
594 | } |
595 | |
596 | const QString filename = nodes.at(i: nodes.size()-1); |
597 | RCCFileInfo *s = new RCCFileInfo(file); |
598 | s->m_parent = parent; |
599 | if (parent->m_children.contains(akey: filename)) { |
600 | for (const QString &fileName : qAsConst(t&: m_fileNames)) { |
601 | qWarning(msg: "%s: Warning: potential duplicate alias detected: '%s'" , |
602 | qPrintable(fileName), qPrintable(filename)); |
603 | } |
604 | } |
605 | parent->m_children.insert(akey: filename, avalue: s); |
606 | return true; |
607 | } |
608 | |
609 | void RCCResourceLibrary::reset() |
610 | { |
611 | if (m_root) { |
612 | delete m_root; |
613 | m_root = nullptr; |
614 | } |
615 | m_errorDevice = nullptr; |
616 | m_failedResources.clear(); |
617 | } |
618 | |
619 | |
620 | bool RCCResourceLibrary::readFiles(bool ignoreErrors, QIODevice &errorDevice) |
621 | { |
622 | reset(); |
623 | m_errorDevice = &errorDevice; |
624 | //read in data |
625 | if (m_verbose) { |
626 | const QString msg = QString::fromUtf8(str: "Processing %1 files [%2]\n" ) |
627 | .arg(a: m_fileNames.size()).arg(a: static_cast<int>(ignoreErrors)); |
628 | m_errorDevice->write(data: msg.toUtf8()); |
629 | } |
630 | for (int i = 0; i < m_fileNames.size(); ++i) { |
631 | QFile fileIn; |
632 | QString fname = m_fileNames.at(i); |
633 | QString pwd; |
634 | if (fname == QLatin1String("-" )) { |
635 | fname = QLatin1String("(stdin)" ); |
636 | pwd = QDir::currentPath(); |
637 | fileIn.setFileName(fname); |
638 | if (!fileIn.open(stdin, ioFlags: QIODevice::ReadOnly)) { |
639 | m_errorDevice->write(data: msgOpenReadFailed(fname, why: fileIn.errorString()).toUtf8()); |
640 | return false; |
641 | } |
642 | } else { |
643 | pwd = QFileInfo(fname).path(); |
644 | fileIn.setFileName(fname); |
645 | if (!fileIn.open(flags: QIODevice::ReadOnly)) { |
646 | m_errorDevice->write(data: msgOpenReadFailed(fname, why: fileIn.errorString()).toUtf8()); |
647 | return false; |
648 | } |
649 | } |
650 | if (m_verbose) { |
651 | const QString msg = QString::fromUtf8(str: "Interpreting %1\n" ).arg(a: fname); |
652 | m_errorDevice->write(data: msg.toUtf8()); |
653 | } |
654 | |
655 | if (!interpretResourceFile(inputDevice: &fileIn, fname, currentPath: pwd, ignoreErrors)) |
656 | return false; |
657 | } |
658 | return true; |
659 | } |
660 | |
661 | QStringList RCCResourceLibrary::dataFiles() const |
662 | { |
663 | QStringList ret; |
664 | QStack<RCCFileInfo*> pending; |
665 | |
666 | if (!m_root) |
667 | return ret; |
668 | pending.push(t: m_root); |
669 | while (!pending.isEmpty()) { |
670 | RCCFileInfo *file = pending.pop(); |
671 | for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); |
672 | it != file->m_children.end(); ++it) { |
673 | RCCFileInfo *child = it.value(); |
674 | if (child->m_flags & RCCFileInfo::Directory) |
675 | pending.push(t: child); |
676 | ret.append(t: child->m_fileInfo.filePath()); |
677 | } |
678 | } |
679 | return ret; |
680 | } |
681 | |
682 | // Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion |
683 | static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) |
684 | { |
685 | const QChar slash = QLatin1Char('/'); |
686 | for (auto it = m_root->m_children.constBegin(), cend = m_root->m_children.constEnd(); it != cend; ++it) { |
687 | const RCCFileInfo *child = it.value(); |
688 | QString childName = path; |
689 | childName += slash; |
690 | childName += child->m_name; |
691 | if (child->m_flags & RCCFileInfo::Directory) { |
692 | resourceDataFileMapRecursion(m_root: child, path: childName, m); |
693 | } else { |
694 | m.insert(akey: childName, avalue: child->m_fileInfo.filePath()); |
695 | } |
696 | } |
697 | } |
698 | |
699 | RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const |
700 | { |
701 | ResourceDataFileMap rc; |
702 | if (m_root) |
703 | resourceDataFileMapRecursion(m_root, path: QString(QLatin1Char(':')), m&: rc); |
704 | return rc; |
705 | } |
706 | |
707 | bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &errorDevice) |
708 | { |
709 | m_errorDevice = &errorDevice; |
710 | //write out |
711 | if (m_verbose) |
712 | m_errorDevice->write(data: "Outputting code\n" ); |
713 | if (!writeHeader()) { |
714 | m_errorDevice->write(data: "Could not write header\n" ); |
715 | return false; |
716 | } |
717 | if (m_root) { |
718 | if (!writeDataBlobs()) { |
719 | m_errorDevice->write(data: "Could not write data blobs.\n" ); |
720 | return false; |
721 | } |
722 | if (!writeDataNames()) { |
723 | m_errorDevice->write(data: "Could not write file names\n" ); |
724 | return false; |
725 | } |
726 | if (!writeDataStructure()) { |
727 | m_errorDevice->write(data: "Could not write data tree\n" ); |
728 | return false; |
729 | } |
730 | } |
731 | if (!writeInitializer()) { |
732 | m_errorDevice->write(data: "Could not write footer\n" ); |
733 | return false; |
734 | } |
735 | outDevice.write(data: m_out.constData(), len: m_out.size()); |
736 | return true; |
737 | } |
738 | |
739 | void RCCResourceLibrary::writeHex(quint8 tmp) |
740 | { |
741 | const char digits[] = "0123456789abcdef" ; |
742 | writeChar(c: '0'); |
743 | writeChar(c: 'x'); |
744 | if (tmp < 16) { |
745 | writeChar(c: digits[tmp]); |
746 | } else { |
747 | writeChar(c: digits[tmp >> 4]); |
748 | writeChar(c: digits[tmp & 0xf]); |
749 | } |
750 | writeChar(c: ','); |
751 | } |
752 | |
753 | void RCCResourceLibrary::writeNumber2(quint16 number) |
754 | { |
755 | if (m_format == RCCResourceLibrary::Binary) { |
756 | writeChar(c: number >> 8); |
757 | writeChar(c: number); |
758 | } else { |
759 | writeHex(tmp: number >> 8); |
760 | writeHex(tmp: number); |
761 | } |
762 | } |
763 | |
764 | void RCCResourceLibrary::writeNumber4(quint32 number) |
765 | { |
766 | if (m_format == RCCResourceLibrary::Binary) { |
767 | writeChar(c: number >> 24); |
768 | writeChar(c: number >> 16); |
769 | writeChar(c: number >> 8); |
770 | writeChar(c: number); |
771 | } else { |
772 | writeHex(tmp: number >> 24); |
773 | writeHex(tmp: number >> 16); |
774 | writeHex(tmp: number >> 8); |
775 | writeHex(tmp: number); |
776 | } |
777 | } |
778 | |
779 | bool RCCResourceLibrary::() |
780 | { |
781 | if (m_format == C_Code) { |
782 | writeString("/****************************************************************************\n" ); |
783 | writeString("** Resource object code\n" ); |
784 | writeString("**\n" ); |
785 | writeString("** Created: " ); |
786 | writeByteArray(other: QDateTime::currentDateTime().toString().toLatin1()); |
787 | writeString("\n** by: The Resource Compiler for Qt version " ); |
788 | writeByteArray(QT_VERSION_STR); |
789 | writeString("\n**\n" ); |
790 | writeString("** WARNING! All changes made in this file will be lost!\n" ); |
791 | writeString( "*****************************************************************************/\n\n" ); |
792 | writeString("#include <QtCore/qglobal.h>\n\n" ); |
793 | } else if (m_format == Binary) { |
794 | writeString("qres" ); |
795 | writeNumber4(number: 0); |
796 | writeNumber4(number: 0); |
797 | writeNumber4(number: 0); |
798 | writeNumber4(number: 0); |
799 | } |
800 | return true; |
801 | } |
802 | |
803 | bool RCCResourceLibrary::writeDataBlobs() |
804 | { |
805 | Q_ASSERT(m_errorDevice); |
806 | if (m_format == C_Code) |
807 | writeString("static const unsigned char qt_resource_data[] = {\n" ); |
808 | else if (m_format == Binary) |
809 | m_dataOffset = m_out.size(); |
810 | QStack<RCCFileInfo*> pending; |
811 | |
812 | if (!m_root) |
813 | return false; |
814 | |
815 | pending.push(t: m_root); |
816 | qint64 offset = 0; |
817 | QString errorMessage; |
818 | while (!pending.isEmpty()) { |
819 | RCCFileInfo *file = pending.pop(); |
820 | for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); |
821 | it != file->m_children.end(); ++it) { |
822 | RCCFileInfo *child = it.value(); |
823 | if (child->m_flags & RCCFileInfo::Directory) |
824 | pending.push(t: child); |
825 | else { |
826 | offset = child->writeDataBlob(lib&: *this, offset, errorMessage: &errorMessage); |
827 | if (offset == 0) { |
828 | m_errorDevice->write(data: errorMessage.toUtf8()); |
829 | return false; |
830 | } |
831 | } |
832 | } |
833 | } |
834 | if (m_format == C_Code) |
835 | writeString("\n};\n\n" ); |
836 | return true; |
837 | } |
838 | |
839 | bool RCCResourceLibrary::writeDataNames() |
840 | { |
841 | if (m_format == C_Code) |
842 | writeString("static const unsigned char qt_resource_name[] = {\n" ); |
843 | else if (m_format == Binary) |
844 | m_namesOffset = m_out.size(); |
845 | |
846 | QHash<QString, int> names; |
847 | QStack<RCCFileInfo*> pending; |
848 | |
849 | if (!m_root) |
850 | return false; |
851 | |
852 | pending.push(t: m_root); |
853 | qint64 offset = 0; |
854 | while (!pending.isEmpty()) { |
855 | RCCFileInfo *file = pending.pop(); |
856 | for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); |
857 | it != file->m_children.end(); ++it) { |
858 | RCCFileInfo *child = it.value(); |
859 | if (child->m_flags & RCCFileInfo::Directory) |
860 | pending.push(t: child); |
861 | if (names.contains(akey: child->m_name)) { |
862 | child->m_nameOffset = names.value(akey: child->m_name); |
863 | } else { |
864 | names.insert(akey: child->m_name, avalue: offset); |
865 | offset = child->writeDataName(lib&: *this, offset); |
866 | } |
867 | } |
868 | } |
869 | if (m_format == C_Code) |
870 | writeString("\n};\n\n" ); |
871 | return true; |
872 | } |
873 | |
874 | static bool qt_rcc_compare_hash(const RCCFileInfo *left, const RCCFileInfo *right) |
875 | { |
876 | return qt_hash(key: left->m_name) < qt_hash(key: right->m_name); |
877 | } |
878 | |
879 | bool RCCResourceLibrary::writeDataStructure() |
880 | { |
881 | if (m_format == C_Code) |
882 | writeString("static const unsigned char qt_resource_struct[] = {\n" ); |
883 | else if (m_format == Binary) |
884 | m_treeOffset = m_out.size(); |
885 | QStack<RCCFileInfo*> pending; |
886 | |
887 | if (!m_root) |
888 | return false; |
889 | |
890 | //calculate the child offsets (flat) |
891 | pending.push(t: m_root); |
892 | int offset = 1; |
893 | while (!pending.isEmpty()) { |
894 | RCCFileInfo *file = pending.pop(); |
895 | file->m_childOffset = offset; |
896 | |
897 | //sort by hash value for binary lookup |
898 | auto children = file->m_children.values(); |
899 | std::sort(first: children.begin(), last: children.end(), comp: qt_rcc_compare_hash); |
900 | |
901 | //write out the actual data now |
902 | for (RCCFileInfo *child : children) { |
903 | ++offset; |
904 | if (child->m_flags & RCCFileInfo::Directory) |
905 | pending.push(t: child); |
906 | } |
907 | } |
908 | |
909 | //write out the structure (ie iterate again!) |
910 | pending.push(t: m_root); |
911 | m_root->writeDataInfo(lib&: *this); |
912 | while (!pending.isEmpty()) { |
913 | RCCFileInfo *file = pending.pop(); |
914 | |
915 | //sort by hash value for binary lookup |
916 | auto children = file->m_children.values(); |
917 | std::sort(first: children.begin(), last: children.end(), comp: qt_rcc_compare_hash); |
918 | |
919 | //write out the actual data now |
920 | for (RCCFileInfo *child : children) { |
921 | child->writeDataInfo(lib&: *this); |
922 | if (child->m_flags & RCCFileInfo::Directory) |
923 | pending.push(t: child); |
924 | } |
925 | } |
926 | if (m_format == C_Code) |
927 | writeString("\n};\n\n" ); |
928 | |
929 | return true; |
930 | } |
931 | |
932 | void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name) |
933 | { |
934 | if (m_useNameSpace) { |
935 | writeString("QT_MANGLE_NAMESPACE(" ); |
936 | writeByteArray(other: name); |
937 | writeChar(c: ')'); |
938 | } else { |
939 | writeByteArray(other: name); |
940 | } |
941 | } |
942 | |
943 | void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name) |
944 | { |
945 | if (m_useNameSpace) { |
946 | writeString("QT_PREPEND_NAMESPACE(" ); |
947 | writeByteArray(other: name); |
948 | writeChar(c: ')'); |
949 | } else { |
950 | writeByteArray(other: name); |
951 | } |
952 | } |
953 | |
954 | bool RCCResourceLibrary::writeInitializer() |
955 | { |
956 | if (m_format == C_Code) { |
957 | //write("\nQT_BEGIN_NAMESPACE\n"); |
958 | QString initName = m_initName; |
959 | if (!initName.isEmpty()) { |
960 | initName.prepend(c: QLatin1Char('_')); |
961 | initName.replace(rx: QRegExp(QLatin1String("[^a-zA-Z0-9_]" )), after: QLatin1String("_" )); |
962 | } |
963 | |
964 | //init |
965 | if (m_useNameSpace) |
966 | writeString("QT_BEGIN_NAMESPACE\n\n" ); |
967 | if (m_root) { |
968 | writeString("extern Q_CORE_EXPORT bool qRegisterResourceData\n " |
969 | "(int, const unsigned char *, " |
970 | "const unsigned char *, const unsigned char *);\n\n" ); |
971 | writeString("extern Q_CORE_EXPORT bool qUnregisterResourceData\n " |
972 | "(int, const unsigned char *, " |
973 | "const unsigned char *, const unsigned char *);\n\n" ); |
974 | } |
975 | if (m_useNameSpace) |
976 | writeString("QT_END_NAMESPACE\n\n\n" ); |
977 | QString initResources = QLatin1String("qInitResources" ); |
978 | initResources += initName; |
979 | writeString("int " ); |
980 | writeMangleNamespaceFunction(name: initResources.toLatin1()); |
981 | writeString("()\n{\n" ); |
982 | |
983 | if (m_root) { |
984 | writeString(" " ); |
985 | writeAddNamespaceFunction(name: "qRegisterResourceData" ); |
986 | writeString("\n (0x01, qt_resource_struct, " |
987 | "qt_resource_name, qt_resource_data);\n" ); |
988 | } |
989 | writeString(" return 1;\n" ); |
990 | writeString("}\n\n" ); |
991 | writeString("Q_CONSTRUCTOR_FUNCTION(" ); |
992 | writeMangleNamespaceFunction(name: initResources.toLatin1()); |
993 | writeString(")\n\n" ); |
994 | |
995 | //cleanup |
996 | QString cleanResources = QLatin1String("qCleanupResources" ); |
997 | cleanResources += initName; |
998 | writeString("int " ); |
999 | writeMangleNamespaceFunction(name: cleanResources.toLatin1()); |
1000 | writeString("()\n{\n" ); |
1001 | if (m_root) { |
1002 | writeString(" " ); |
1003 | writeAddNamespaceFunction(name: "qUnregisterResourceData" ); |
1004 | writeString("\n (0x01, qt_resource_struct, " |
1005 | "qt_resource_name, qt_resource_data);\n" ); |
1006 | } |
1007 | writeString(" return 1;\n" ); |
1008 | writeString("}\n\n" ); |
1009 | writeString("Q_DESTRUCTOR_FUNCTION(" ); |
1010 | writeMangleNamespaceFunction(name: cleanResources.toLatin1()); |
1011 | writeString(")\n\n" ); |
1012 | } else if (m_format == Binary) { |
1013 | int i = 4; |
1014 | char *p = m_out.data(); |
1015 | p[i++] = 0; // 0x01 |
1016 | p[i++] = 0; |
1017 | p[i++] = 0; |
1018 | p[i++] = 1; |
1019 | |
1020 | p[i++] = (m_treeOffset >> 24) & 0xff; |
1021 | p[i++] = (m_treeOffset >> 16) & 0xff; |
1022 | p[i++] = (m_treeOffset >> 8) & 0xff; |
1023 | p[i++] = (m_treeOffset >> 0) & 0xff; |
1024 | |
1025 | p[i++] = (m_dataOffset >> 24) & 0xff; |
1026 | p[i++] = (m_dataOffset >> 16) & 0xff; |
1027 | p[i++] = (m_dataOffset >> 8) & 0xff; |
1028 | p[i++] = (m_dataOffset >> 0) & 0xff; |
1029 | |
1030 | p[i++] = (m_namesOffset >> 24) & 0xff; |
1031 | p[i++] = (m_namesOffset >> 16) & 0xff; |
1032 | p[i++] = (m_namesOffset >> 8) & 0xff; |
1033 | p[i++] = (m_namesOffset >> 0) & 0xff; |
1034 | } |
1035 | return true; |
1036 | } |
1037 | |
1038 | QT_END_NAMESPACE |
1039 | |