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
47QT_BEGIN_NAMESPACE
48
49enum {
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
58void 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
66void RCCResourceLibrary::writeByteArray(const QByteArray &other)
67{
68 m_out.append(a: other);
69}
70
71static 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
83class RCCFileInfo
84{
85public:
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
103public:
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
123RCCFileInfo::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
140RCCFileInfo::~RCCFileInfo()
141{
142 qDeleteAll(c: m_children);
143}
144
145QString 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
153void 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
204qint64 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
270qint64 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
318RCCResourceLibrary::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
330RCCResourceLibrary::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
345RCCResourceLibrary::~RCCResourceLibrary()
346{
347 delete m_root;
348}
349
350enum RCCXmlTag {
351 RccTag,
352 ResourceTag,
353 FileTag
354};
355
356bool 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
569bool 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
609void 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
620bool 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
661QStringList 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
683static 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
699RCCResourceLibrary::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
707bool 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
739void 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
753void 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
764void 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
779bool RCCResourceLibrary::writeHeader()
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
803bool 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
839bool 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
874static 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
879bool 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
932void 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
943void 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
954bool 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
1038QT_END_NAMESPACE
1039

source code of qttools/src/designer/src/lib/shared/rcc.cpp