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