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