qdoc: Clean path names before using them
[profile/ivi/qtbase.git] / src / tools / qdoc / config.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 tools applications 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 /*
43   config.cpp
44 */
45
46 #include <QDir>
47 #include <QVariant>
48 #include <QFile>
49 #include <QTemporaryFile>
50 #include <QTextStream>
51 #include <qdebug.h>
52 #include "config.h"
53 #include <stdlib.h>
54
55 QT_BEGIN_NAMESPACE
56
57 /*
58   An entry on the MetaStack.
59  */
60 class MetaStackEntry
61 {
62 public:
63     void open();
64     void close();
65
66     QStringList accum;
67     QStringList next;
68 };
69
70 /*
71  */
72 void MetaStackEntry::open()
73 {
74     next.append(QString());
75 }
76
77 /*
78  */
79 void MetaStackEntry::close()
80 {
81     accum += next;
82     next.clear();
83 }
84
85 /*
86   ###
87 */
88 class MetaStack : private QStack<MetaStackEntry>
89 {
90 public:
91     MetaStack();
92
93     void process(QChar ch, const Location& location);
94     QStringList getExpanded(const Location& location);
95 };
96
97 MetaStack::MetaStack()
98 {
99     push(MetaStackEntry());
100     top().open();
101 }
102
103 void MetaStack::process(QChar ch, const Location& location)
104 {
105     if (ch == QLatin1Char('{')) {
106         push(MetaStackEntry());
107         top().open();
108     }
109     else if (ch == QLatin1Char('}')) {
110         if (count() == 1)
111             location.fatal(tr("Unexpected '}'"));
112
113         top().close();
114         QStringList suffixes = pop().accum;
115         QStringList prefixes = top().next;
116
117         top().next.clear();
118         QStringList::ConstIterator pre = prefixes.constBegin();
119         while (pre != prefixes.constEnd()) {
120             QStringList::ConstIterator suf = suffixes.constBegin();
121             while (suf != suffixes.constEnd()) {
122                 top().next << (*pre + *suf);
123                 ++suf;
124             }
125             ++pre;
126         }
127     }
128     else if (ch == QLatin1Char(',') && count() > 1) {
129         top().close();
130         top().open();
131     }
132     else {
133         QStringList::Iterator pre = top().next.begin();
134         while (pre != top().next.end()) {
135             *pre += ch;
136             ++pre;
137         }
138     }
139 }
140
141 QStringList MetaStack::getExpanded(const Location& location)
142 {
143     if (count() > 1)
144         location.fatal(tr("Missing '}'"));
145
146     top().close();
147     return top().accum;
148 }
149
150 QT_STATIC_CONST_IMPL QString Config::dot = QLatin1String(".");
151 bool Config::generateExamples = true;
152 QString Config::overrideOutputDir;
153 QString Config::installDir;
154 QSet<QString> Config::overrideOutputFormats;
155 QMap<QString, QString> Config::extractedDirs;
156 int Config::numInstances;
157
158 /*!
159   \class Config
160   \brief The Config class contains the configuration variables
161   for controlling how qdoc produces documentation.
162
163   Its load() function, reads, parses, and processes a qdocconf file.
164  */
165
166 /*!
167   The constructor sets the \a programName and initializes all
168   internal state variables to empty values.
169  */
170 Config::Config(const QString& programName)
171     : prog(programName)
172 {
173     loc = Location::null;
174     lastLoc = Location::null;
175     locMap.clear();
176     stringValueMap.clear();
177     stringListValueMap.clear();
178     numInstances++;
179 }
180
181 /*!
182   The destructor has nothing special to do.
183  */
184 Config::~Config()
185 {
186 }
187
188 /*!
189   Loads and parses the qdoc configuration file \a fileName.
190   This function calls the other load() function, which does
191   the loading, parsing, and processing of the configuration
192   file.
193
194   Intializes the location variables returned by location()
195   and lastLocation().
196  */
197 void Config::load(const QString& fileName)
198 {
199     load(Location::null, fileName);
200     if (loc.isEmpty()) {
201         loc = Location(fileName);
202     }
203     else {
204         loc.setEtc(true);
205     }
206     lastLoc = Location::null;
207 }
208
209 /*!
210   Writes the qdoc configuration data to the named file.
211   The previous contents of the file are overwritten.
212  */
213 void Config::unload(const QString& fileName)
214 {
215     QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
216     while (v != stringValueMap.constEnd()) {
217         qDebug() << v.key() << " = " << v.value();
218         ++v;
219     }
220     qDebug() << "fileName:" << fileName;
221 }
222 /*!
223   Joins all the strings in \a values into a single string with the
224   individual \a values separated by ' '. Then it inserts the result
225   into the string list map with \a var as the key.
226
227   It also inserts the \a values string list into a separate map,
228   also with \a var as the key.
229  */
230 void Config::setStringList(const QString& var, const QStringList& values)
231 {
232     stringValueMap[var] = values.join(QLatin1String(" "));
233     stringListValueMap[var] = values;
234 }
235
236 /*!
237   Looks up the configuarion variable \a var in the string
238   map and returns the boolean value.
239  */
240 bool Config::getBool(const QString& var) const
241 {
242     return QVariant(getString(var)).toBool();
243 }
244
245 /*!
246   Looks up the configuration variable \a var in the string list
247   map. Iterates through the string list found, interpreting each
248   string in the list as an integer and adding it to a total sum.
249   Returns the sum.
250  */
251 int Config::getInt(const QString& var) const
252 {
253     QStringList strs = getStringList(var);
254     QStringList::ConstIterator s = strs.constBegin();
255     int sum = 0;
256
257     while (s != strs.constEnd()) {
258         sum += (*s).toInt();
259         ++s;
260     }
261     return sum;
262 }
263
264 /*!
265   Function to return the correct outputdir.
266   outputdir can be set using the qdocconf or the command-line
267   variable -outputdir.
268   */
269 QString Config::getOutputDir() const
270 {
271     if (overrideOutputDir.isNull())
272         return getString(QLatin1String(CONFIG_OUTPUTDIR));
273     else
274         return overrideOutputDir;
275 }
276
277 /*!
278   Function to return the correct outputformats.
279   outputformats can be set using the qdocconf or the command-line
280   variable -outputformat.
281   */
282 QSet<QString> Config::getOutputFormats() const
283 {
284     if (overrideOutputFormats.isEmpty())
285         return getStringSet(QLatin1String(CONFIG_OUTPUTFORMATS));
286     else
287         return overrideOutputFormats;
288 }
289
290 /*!
291   First, this function looks up the configuration variable \a var
292   in the location map and, if found, sets the internal variable
293   \c{lastLoc} to the Location that \a var maps to.
294
295   Then it looks up the configuration variable \a var in the string
296   map, and returns the string that \a var maps to.
297  */
298 QString Config::getString(const QString& var) const
299 {
300     if (!locMap[var].isEmpty())
301         (Location&) lastLoc = locMap[var];
302     return stringValueMap[var];
303 }
304
305 /*!
306   Looks up the configuration variable \a var in the string
307   list map, converts the string list it maps to into a set
308   of strings, and returns the set.
309  */
310 QSet<QString> Config::getStringSet(const QString& var) const
311 {
312     return QSet<QString>::fromList(getStringList(var));
313 }
314
315 /*!
316   First, this function looks up the configuration variable \a var
317   in the location map and, if found, sets the internal variable
318   \c{lastLoc} the Location that \a var maps to.
319
320   Then it looks up the configuration variable \a var in the string
321   list map, and returns the string list that \a var maps to.
322  */
323 QStringList Config::getStringList(const QString& var) const
324 {
325     if (!locMap[var].isEmpty())
326         (Location&) lastLoc = locMap[var];
327     return stringListValueMap[var];
328 }
329
330 /*!
331   This function should only be called when the configuration
332   variable \a var maps to a string list that contains file paths.
333   It cleans the paths with QDir::cleanPath() before returning
334   them.
335
336   First, this function looks up the configuration variable \a var
337   in the location map and, if found, sets the internal variable
338   \c{lastLoc} the Location that \a var maps to.
339
340   Then it looks up the configuration variable \a var in the string
341   list map, which maps to a string list that contains file paths.
342   These paths might not be clean, so QDir::cleanPath() is called
343   for each one. The string list returned contains cleaned paths.
344  */
345 QStringList Config::getCleanPathList(const QString& var) const
346 {
347     if (!locMap[var].isEmpty())
348         (Location&) lastLoc = locMap[var];
349     QStringList t;
350     QMap<QString,QStringList>::const_iterator it = stringListValueMap.constFind(var);
351     if (it != stringListValueMap.constEnd()) {
352         const QStringList& sl = it.value();
353         if (!sl.isEmpty()) {
354             t.reserve(sl.size());
355             for (int i=0; i<sl.size(); ++i) {
356                 t.append(QDir::cleanPath(sl[i]));
357             }
358         }
359     }
360     return t;
361 }
362
363 /*!
364   Calls getRegExpList() with the control variable \a var and
365   iterates through the resulting list of regular expressions,
366   concatening them with some extras characters to form a single
367   QRegExp, which is returned/
368
369   \sa getRegExpList()
370  */
371 QRegExp Config::getRegExp(const QString& var) const
372 {
373     QString pattern;
374     QList<QRegExp> subRegExps = getRegExpList(var);
375     QList<QRegExp>::ConstIterator s = subRegExps.constBegin();
376
377     while (s != subRegExps.constEnd()) {
378         if (!(*s).isValid())
379             return *s;
380         if (!pattern.isEmpty())
381             pattern += QLatin1Char('|');
382         pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')');
383         ++s;
384     }
385     if (pattern.isEmpty())
386         pattern = QLatin1String("$x"); // cannot match
387     return QRegExp(pattern);
388 }
389
390 /*!
391   Looks up the configuration variable \a var in the string list
392   map, converts the string list to a list of regular expressions,
393   and returns it.
394  */
395 QList<QRegExp> Config::getRegExpList(const QString& var) const
396 {
397     QStringList strs = getStringList(var);
398     QStringList::ConstIterator s = strs.constBegin();
399     QList<QRegExp> regExps;
400
401     while (s != strs.constEnd()) {
402         regExps += QRegExp(*s);
403         ++s;
404     }
405     return regExps;
406 }
407
408 /*!
409   This function is slower than it could be. What it does is
410   find all the keys that begin with \a var + dot and return
411   the matching keys in a set, stripped of the matching prefix
412   and dot.
413  */
414 QSet<QString> Config::subVars(const QString& var) const
415 {
416     QSet<QString> result;
417     QString varDot = var + QLatin1Char('.');
418     QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
419     while (v != stringValueMap.constEnd()) {
420         if (v.key().startsWith(varDot)) {
421             QString subVar = v.key().mid(varDot.length());
422             int dot = subVar.indexOf(QLatin1Char('.'));
423             if (dot != -1)
424                 subVar.truncate(dot);
425             result.insert(subVar);
426         }
427         ++v;
428     }
429     return result;
430 }
431
432 /*!
433   Same as subVars(), but in this case we return a string map
434   with the matching keys (stripped of the prefix \a var and
435   mapped to their values. The pairs are inserted into \a t
436  */
437 void Config::subVarsAndValues(const QString& var, QStringMultiMap& t) const
438 {
439     QString varDot = var + QLatin1Char('.');
440     QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
441     while (v != stringValueMap.constEnd()) {
442         if (v.key().startsWith(varDot)) {
443             QString subVar = v.key().mid(varDot.length());
444             int dot = subVar.indexOf(QLatin1Char('.'));
445             if (dot != -1)
446                 subVar.truncate(dot);
447             t.insert(subVar,v.value());
448         }
449         ++v;
450     }
451 }
452
453 /*!
454   Builds and returns a list of file pathnames for the file
455   type specified by \a filesVar (e.g. "headers" or "sources").
456   The files are found in the directories specified by
457   \a dirsVar, and they are filtered by \a defaultNameFilter
458   if a better filter can't be constructed from \a filesVar.
459   The directories in \a excludedDirs are avoided. The files
460   in \a excludedFiles are not included in the return list.
461  */
462 QStringList Config::getAllFiles(const QString &filesVar,
463                                 const QString &dirsVar,
464                                 const QSet<QString> &excludedDirs,
465                                 const QSet<QString> &excludedFiles)
466 {
467     QStringList result = getStringList(filesVar);
468     QStringList dirs = getStringList(dirsVar);
469
470     QString nameFilter = getString(filesVar + dot + QLatin1String(CONFIG_FILEEXTENSIONS));
471
472     QStringList::ConstIterator d = dirs.constBegin();
473     while (d != dirs.constEnd()) {
474         result += getFilesHere(*d, nameFilter, excludedDirs, excludedFiles);
475         ++d;
476     }
477     return result;
478 }
479
480 /*!
481   \a fileName is the path of the file to find.
482
483   \a files and \a dirs are the lists where we must find the
484   components of \a fileName.
485
486   \a location is used for obtaining the file and line numbers
487   for report qdoc errors.
488  */
489 QString Config::findFile(const Location& location,
490                          const QStringList& files,
491                          const QStringList& dirs,
492                          const QString& fileName,
493                          QString& userFriendlyFilePath)
494 {
495     if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
496         userFriendlyFilePath = fileName;
497         return fileName;
498     }
499
500     QFileInfo fileInfo;
501     QStringList components = fileName.split(QLatin1Char('?'));
502     QString firstComponent = components.first();
503
504     QStringList::ConstIterator f = files.constBegin();
505     while (f != files.constEnd()) {
506         if (*f == firstComponent ||
507                 (*f).endsWith(QLatin1Char('/') + firstComponent)) {
508             fileInfo.setFile(*f);
509             if (!fileInfo.exists())
510                 location.fatal(tr("File '%1' does not exist").arg(*f));
511             break;
512         }
513         ++f;
514     }
515
516     if (fileInfo.fileName().isEmpty()) {
517         QStringList::ConstIterator d = dirs.constBegin();
518         while (d != dirs.constEnd()) {
519             fileInfo.setFile(QDir(*d), firstComponent);
520             if (fileInfo.exists()) {
521                 break;
522             }
523             ++d;
524         }
525     }
526
527     userFriendlyFilePath = QString();
528     if (!fileInfo.exists())
529         return QString();
530
531     QStringList::ConstIterator c = components.constBegin();
532     for (;;) {
533         bool isArchive = (c != components.constEnd() - 1);
534         QString userFriendly = *c;
535
536         userFriendlyFilePath += userFriendly;
537
538         if (isArchive) {
539             QString extracted = extractedDirs[fileInfo.filePath()];
540             ++c;
541             fileInfo.setFile(QDir(extracted), *c);
542         }
543         else
544             break;
545
546         userFriendlyFilePath += QLatin1Char('?');
547     }
548     return fileInfo.filePath();
549 }
550
551 /*!
552  */
553 QString Config::findFile(const Location& location,
554                          const QStringList& files,
555                          const QStringList& dirs,
556                          const QString& fileBase,
557                          const QStringList& fileExtensions,
558                          QString& userFriendlyFilePath)
559 {
560     QStringList::ConstIterator e = fileExtensions.constBegin();
561     while (e != fileExtensions.constEnd()) {
562         QString filePath = findFile(location,
563                                     files,
564                                     dirs,
565                                     fileBase + QLatin1Char('.') + *e,
566                                     userFriendlyFilePath);
567         if (!filePath.isEmpty())
568             return filePath;
569         ++e;
570     }
571     return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
572 }
573
574 /*!
575   Copies the \a sourceFilePath to the file name constructed by
576   concatenating \a targetDirPath and \a userFriendlySourceFilePath.
577   \a location is for identifying the file and line number where
578   a qdoc error occurred. The constructed output file name is
579   returned.
580  */
581 QString Config::copyFile(const Location& location,
582                          const QString& sourceFilePath,
583                          const QString& userFriendlySourceFilePath,
584                          const QString& targetDirPath)
585 {
586     QFile inFile(sourceFilePath);
587     if (!inFile.open(QFile::ReadOnly)) {
588         location.fatal(tr("Cannot open input file '%1': %2")
589                        .arg(sourceFilePath).arg(inFile.errorString()));
590         return QString();
591     }
592
593     QString outFileName = userFriendlySourceFilePath;
594     int slash = outFileName.lastIndexOf(QLatin1Char('/'));
595     if (slash != -1)
596         outFileName = outFileName.mid(slash);
597
598     QFile outFile(targetDirPath + QLatin1Char('/') + outFileName);
599     if (!outFile.open(QFile::WriteOnly)) {
600         location.fatal(tr("Cannot open output file '%1': %2")
601                        .arg(outFile.fileName()).arg(outFile.errorString()));
602         return QString();
603     }
604
605     char buffer[1024];
606     int len;
607     while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
608         outFile.write(buffer, len);
609     }
610     return outFileName;
611 }
612
613 /*!
614   Finds the largest unicode digit in \a value in the range
615   1..7 and returns it.
616  */
617 int Config::numParams(const QString& value)
618 {
619     int max = 0;
620     for (int i = 0; i != value.length(); i++) {
621         uint c = value[i].unicode();
622         if (c > 0 && c < 8)
623             max = qMax(max, (int)c);
624     }
625     return max;
626 }
627
628 /*!
629   Removes everything from \a dir. This function is recursive.
630   It doesn't remove \a dir itself, but if it was called
631   recursively, then the caller will remove \a dir.
632  */
633 bool Config::removeDirContents(const QString& dir)
634 {
635     QDir dirInfo(dir);
636     QFileInfoList entries = dirInfo.entryInfoList();
637
638     bool ok = true;
639
640     QFileInfoList::Iterator it = entries.begin();
641     while (it != entries.end()) {
642         if ((*it).isFile()) {
643             if (!dirInfo.remove((*it).fileName()))
644                 ok = false;
645         }
646         else if ((*it).isDir()) {
647             if ((*it).fileName() != QLatin1String(".") && (*it).fileName() != QLatin1String("..")) {
648                 if (removeDirContents((*it).absoluteFilePath())) {
649                     if (!dirInfo.rmdir((*it).fileName()))
650                         ok = false;
651                 }
652                 else {
653                     ok = false;
654                 }
655             }
656         }
657         ++it;
658     }
659     return ok;
660 }
661
662 /*!
663   Returns true if \a ch is a letter, number, '_', '.',
664   '{', '}', or ','.
665  */
666 bool Config::isMetaKeyChar(QChar ch)
667 {
668     return ch.isLetterOrNumber()
669             || ch == QLatin1Char('_')
670             || ch == QLatin1Char('.')
671             || ch == QLatin1Char('{')
672             || ch == QLatin1Char('}')
673             || ch == QLatin1Char(',');
674 }
675
676 /*!
677   Load, parse, and process a qdoc configuration file. This
678   function is only called by the other load() function, but
679   this one is recursive, i.e., it calls itself when it sees
680   an \c{include} statement in the qdoc configuration file.
681  */
682 void Config::load(Location location, const QString& fileName)
683 {
684     QRegExp keySyntax(QLatin1String("\\w+(?:\\.\\w+)*"));
685
686 #define SKIP_CHAR() \
687     do { \
688     location.advance(c); \
689     ++i; \
690     c = text.at(i); \
691     cc = c.unicode(); \
692 } while (0)
693
694 #define SKIP_SPACES() \
695     while (c.isSpace() && cc != '\n') \
696     SKIP_CHAR()
697
698 #define PUT_CHAR() \
699     word += c; \
700     SKIP_CHAR();
701
702     if (location.depth() > 16)
703         location.fatal(tr("Too many nested includes"));
704
705     QFile fin(fileName);
706     if (!fin.open(QFile::ReadOnly | QFile::Text)) {
707         if (!Config::installDir.isEmpty()) {
708             int prefix = location.filePath().length() - location.fileName().length();
709             fin.setFileName(Config::installDir + "/" + fileName.right(fileName.length() - prefix));
710         }
711         if (!fin.open(QFile::ReadOnly | QFile::Text))
712             location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
713     }
714
715     QTextStream stream(&fin);
716     stream.setCodec("UTF-8");
717     QString text = stream.readAll();
718     text += QLatin1String("\n\n");
719     text += QLatin1Char('\0');
720     fin.close();
721
722     location.push(fileName);
723     location.start();
724
725     int i = 0;
726     QChar c = text.at(0);
727     uint cc = c.unicode();
728     while (i < (int) text.length()) {
729         if (cc == 0)
730             ++i;
731         else if (c.isSpace()) {
732             SKIP_CHAR();
733         }
734         else if (cc == '#') {
735             do {
736                 SKIP_CHAR();
737             } while (cc != '\n');
738         }
739         else if (isMetaKeyChar(c)) {
740             Location keyLoc = location;
741             bool plus = false;
742             QString stringValue;
743             QStringList stringListValue;
744             QString word;
745             bool inQuote = false;
746             bool prevWordQuoted = true;
747             bool metWord = false;
748
749             MetaStack stack;
750             do {
751                 stack.process(c, location);
752                 SKIP_CHAR();
753             } while (isMetaKeyChar(c));
754
755             QStringList keys = stack.getExpanded(location);
756             SKIP_SPACES();
757
758             if (keys.count() == 1 && keys.first() == QLatin1String("include")) {
759                 QString includeFile;
760
761                 if (cc != '(')
762                     location.fatal(tr("Bad include syntax"));
763                 SKIP_CHAR();
764                 SKIP_SPACES();
765
766                 while (!c.isSpace() && cc != '#' && cc != ')') {
767
768                     if (cc == '$') {
769                         QString var;
770                         SKIP_CHAR();
771                         while (c.isLetterOrNumber() || cc == '_') {
772                             var += c;
773                             SKIP_CHAR();
774                         }
775                         if (!var.isEmpty()) {
776                             char *val = getenv(var.toLatin1().data());
777                             if (val == 0) {
778                                 location.fatal(tr("Environment variable '%1' undefined").arg(var));
779                             }
780                             else {
781                                 includeFile += QString::fromLatin1(val);
782                             }
783                         }
784                     } else {
785                         includeFile += c;
786                         SKIP_CHAR();
787                     }
788                 }
789                 SKIP_SPACES();
790                 if (cc != ')')
791                     location.fatal(tr("Bad include syntax"));
792                 SKIP_CHAR();
793                 SKIP_SPACES();
794                 if (cc != '#' && cc != '\n')
795                     location.fatal(tr("Trailing garbage"));
796
797                 /*
798                   Here is the recursive call.
799                  */
800                 load(location,
801                      QFileInfo(QFileInfo(fileName).dir(), includeFile)
802                      .filePath());
803             }
804             else {
805                 /*
806                   It wasn't an include statement, so it's something else.
807                  */
808                 if (cc == '+') {
809                     plus = true;
810                     SKIP_CHAR();
811                 }
812                 if (cc != '=')
813                     location.fatal(tr("Expected '=' or '+=' after key"));
814                 SKIP_CHAR();
815                 SKIP_SPACES();
816
817                 for (;;) {
818                     if (cc == '\\') {
819                         int metaCharPos;
820
821                         SKIP_CHAR();
822                         if (cc == '\n') {
823                             SKIP_CHAR();
824                         }
825                         else if (cc > '0' && cc < '8') {
826                             word += QChar(c.digitValue());
827                             SKIP_CHAR();
828                         }
829                         else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
830                             word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
831                             SKIP_CHAR();
832                         }
833                         else {
834                             PUT_CHAR();
835                         }
836                     }
837                     else if (c.isSpace() || cc == '#') {
838                         if (inQuote) {
839                             if (cc == '\n')
840                                 location.fatal(tr("Unterminated string"));
841                             PUT_CHAR();
842                         }
843                         else {
844                             if (!word.isEmpty()) {
845                                 if (metWord)
846                                     stringValue += QLatin1Char(' ');
847                                 stringValue += word;
848                                 stringListValue << word;
849                                 metWord = true;
850                                 word.clear();
851                                 prevWordQuoted = false;
852                             }
853                             if (cc == '\n' || cc == '#')
854                                 break;
855                             SKIP_SPACES();
856                         }
857                     }
858                     else if (cc == '"') {
859                         if (inQuote) {
860                             if (!prevWordQuoted)
861                                 stringValue += QLatin1Char(' ');
862                             stringValue += word;
863                             if (!word.isEmpty())
864                                 stringListValue << word;
865                             metWord = true;
866                             word.clear();
867                             prevWordQuoted = true;
868                         }
869                         inQuote = !inQuote;
870                         SKIP_CHAR();
871                     }
872                     else if (cc == '$') {
873                         QString var;
874                         SKIP_CHAR();
875                         while (c.isLetterOrNumber() || cc == '_') {
876                             var += c;
877                             SKIP_CHAR();
878                         }
879                         if (!var.isEmpty()) {
880                             char *val = getenv(var.toLatin1().data());
881                             if (val == 0) {
882                                 location.fatal(tr("Environment variable '%1' undefined").arg(var));
883                             }
884                             else {
885                                 word += QString::fromLatin1(val);
886                             }
887                         }
888                     }
889                     else {
890                         if (!inQuote && cc == '=')
891                             location.fatal(tr("Unexpected '='"));
892                         PUT_CHAR();
893                     }
894                 }
895
896                 QStringList::ConstIterator key = keys.constBegin();
897                 while (key != keys.constEnd()) {
898                     if (!keySyntax.exactMatch(*key))
899                         keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
900
901                     if (plus) {
902                         if (locMap[*key].isEmpty()) {
903                             locMap[*key] = keyLoc;
904                         }
905                         else {
906                             locMap[*key].setEtc(true);
907                         }
908                         if (stringValueMap[*key].isEmpty()) {
909                             stringValueMap[*key] = stringValue;
910                         }
911                         else {
912                             stringValueMap[*key] +=
913                                     QLatin1Char(' ') + stringValue;
914                         }
915                         stringListValueMap[*key] += stringListValue;
916                     }
917                     else {
918                         locMap[*key] = keyLoc;
919                         stringValueMap[*key] = stringValue;
920                         stringListValueMap[*key] = stringListValue;
921                     }
922                     ++key;
923                 }
924             }
925         }
926         else {
927             location.fatal(tr("Unexpected character '%1' at beginning of line")
928                            .arg(c));
929         }
930     }
931 }
932
933 QStringList Config::getFilesHere(const QString& uncleanDir,
934                                  const QString& nameFilter,
935                                  const QSet<QString> &excludedDirs,
936                                  const QSet<QString> &excludedFiles)
937 {
938     QString dir = QDir::cleanPath(uncleanDir);
939     QStringList result;
940     if (excludedDirs.contains(dir))
941         return result;
942
943     QDir dirInfo(dir);
944     QStringList fileNames;
945     QStringList::const_iterator fn;
946
947     dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' ')));
948     dirInfo.setSorting(QDir::Name);
949     dirInfo.setFilter(QDir::Files);
950     fileNames = dirInfo.entryList();
951     fn = fileNames.constBegin();
952     while (fn != fileNames.constEnd()) {
953         if (!fn->startsWith(QLatin1Char('~'))) {
954             QString s = dirInfo.filePath(*fn);
955             QString c = QDir::cleanPath(s);
956             if (!excludedFiles.contains(c)) {
957                 result.append(c);
958             }
959         }
960         ++fn;
961     }
962
963     dirInfo.setNameFilters(QStringList(QLatin1String("*")));
964     dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);
965     fileNames = dirInfo.entryList();
966     fn = fileNames.constBegin();
967     while (fn != fileNames.constEnd()) {
968         result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs, excludedFiles);
969         ++fn;
970     }
971     return result;
972 }
973
974 QT_END_NAMESPACE