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