qdoc: Implements the -no-link-errors option
[profile/ivi/qtbase.git] / src / tools / qdoc / generator.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 ** 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 Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/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 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 /*
43   generator.cpp
44 */
45 #include <qdir.h>
46 #include <qdebug.h>
47 #include "codemarker.h"
48 #include "config.h"
49 #include "ditaxmlgenerator.h"
50 #include "doc.h"
51 #include "editdistance.h"
52 #include "generator.h"
53 #include "openedlist.h"
54 #include "quoter.h"
55 #include "separator.h"
56 #include "tokenizer.h"
57 #include "qdocdatabase.h"
58
59 QT_BEGIN_NAMESPACE
60
61 Generator* Generator::currentGenerator_;
62 QStringList Generator::exampleDirs;
63 QStringList Generator::exampleImgExts;
64 QMap<QString, QMap<QString, QString> > Generator::fmtLeftMaps;
65 QMap<QString, QMap<QString, QString> > Generator::fmtRightMaps;
66 QList<Generator *> Generator::generators;
67 QStringList Generator::imageDirs;
68 QStringList Generator::imageFiles;
69 QMap<QString, QStringList> Generator::imgFileExts;
70 QString Generator::outDir_;
71 QSet<QString> Generator::outputFormats;
72 QHash<QString, QString> Generator::outputPrefixes;
73 QString Generator::project;
74 QStringList Generator::scriptDirs;
75 QStringList Generator::scriptFiles;
76 QString Generator::sinceTitles[] =
77 {
78     "    New Namespaces",
79     "    New Classes",
80     "    New Member Functions",
81     "    New Functions in Namespaces",
82     "    New Global Functions",
83     "    New Macros",
84     "    New Enum Types",
85     "    New Typedefs",
86     "    New Properties",
87     "    New Variables",
88     "    New QML Types",
89     "    New QML Properties",
90     "    New QML Signals",
91     "    New QML Signal Handlers",
92     "    New QML Methods",
93     ""
94 };
95 QStringList Generator::styleDirs;
96 QStringList Generator::styleFiles;
97 bool Generator::debugging_ = false;
98 bool Generator::noLinkErrors_ = false;
99
100 void Generator::setDebugSegfaultFlag(bool b)
101 {
102     if (b)
103         qDebug() << "DEBUG: Setting debug flag.";
104     else
105         qDebug() << "DEBUG: Clearing debug flag.";
106     debugging_ = b;
107 }
108
109 /*!
110   Prints \a message as an aid to debugging the release version.
111  */
112 void Generator::debugSegfault(const QString& message)
113 {
114     if (debugging())
115         qDebug() << "DEBUG:" << message;
116 }
117
118 /*!
119   Constructs the generator base class. Prepends the newly
120   constructed generator to the list of output generators.
121   Sets a pointer to the QDoc database singleton, which is
122   available to the generator subclasses.
123  */
124 Generator::Generator()
125     : amp("&amp;"),
126       gt("&gt;"),
127       lt("&lt;"),
128       quot("&quot;"),
129       tag("</?@[^>]*>"),
130       inLink_(false),
131       inContents_(false),
132       inSectionHeading_(false),
133       inTableHeader_(false),
134       threeColumnEnumValueTable_(true),
135       numTableRows_(0)
136 {
137     qdb_ = QDocDatabase::qdocDB();
138     generators.prepend(this);
139 }
140
141 /*!
142   Destroys the generator after removing it from the list of
143   output generators.
144  */
145 Generator::~Generator()
146 {
147     generators.removeAll(this);
148 }
149
150 void Generator::appendFullName(Text& text,
151                                const Node *apparentNode,
152                                const Node *relative,
153                                const Node *actualNode)
154 {
155     if (actualNode == 0)
156         actualNode = apparentNode;
157     text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
158          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
159          << Atom(Atom::String, apparentNode->plainFullName(relative))
160          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
161 }
162
163 void Generator::appendFullName(Text& text,
164                                const Node *apparentNode,
165                                const QString& fullName,
166                                const Node *actualNode)
167 {
168     if (actualNode == 0)
169         actualNode = apparentNode;
170     text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
171          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
172          << Atom(Atom::String, fullName)
173          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
174 }
175
176 void Generator::appendFullNames(Text& text, const NodeList& nodes, const Node* relative)
177 {
178     NodeList::ConstIterator n = nodes.constBegin();
179     int index = 0;
180     while (n != nodes.constEnd()) {
181         appendFullName(text,*n,relative);
182         text << comma(index++,nodes.count());
183         ++n;
184     }
185 }
186
187 void Generator::appendSortedNames(Text& text, const ClassNode *classe, const QList<RelatedClass> &classes)
188 {
189     QList<RelatedClass>::ConstIterator r;
190     QMap<QString,Text> classMap;
191     int index = 0;
192
193     r = classes.constBegin();
194     while (r != classes.constEnd()) {
195         if ((*r).node->access() == Node::Public &&
196                 (*r).node->status() != Node::Internal
197                 && !(*r).node->doc().isEmpty()) {
198             Text className;
199             appendFullName(className, (*r).node, classe);
200             classMap[className.toString().toLower()] = className;
201         }
202         ++r;
203     }
204
205     QStringList classNames = classMap.keys();
206     classNames.sort();
207
208     foreach (const QString &className, classNames) {
209         text << classMap[className];
210         text << separator(index++, classNames.count());
211     }
212 }
213
214 void Generator::appendSortedQmlNames(Text& text, const Node* base, const NodeList& subs)
215 {
216     QMap<QString,Text> classMap;
217     int index = 0;
218
219     for (int i = 0; i < subs.size(); ++i) {
220         Text t;
221         if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
222                 (base->qmlModuleIdentifier() == subs[i]->qmlModuleIdentifier())) {
223             appendFullName(t, subs[i], base);
224             classMap[t.toString().toLower()] = t;
225         }
226     }
227
228     QStringList names = classMap.keys();
229     names.sort();
230
231     foreach (const QString &name, names) {
232         text << classMap[name];
233         text << separator(index++, names.count());
234     }
235 }
236
237 QMultiMap<QString,QString> outFileNames;
238
239 /*!
240   For debugging qdoc.
241  */
242 void Generator::writeOutFileNames()
243 {
244     QFile* files = new QFile("/Users/msmith/depot/qt5/qtdoc/outputlist.txt");
245     files->open(QFile::WriteOnly);
246     QTextStream* filesout = new QTextStream(files);
247     QMultiMap<QString,QString>::ConstIterator i = outFileNames.begin();
248     while (i != outFileNames.end()) {
249         (*filesout) << i.key() << "\n";
250         ++i;
251     }
252     filesout->flush();
253     files->close();
254 }
255
256 /*!
257   Creates the file named \a fileName in the output directory.
258   Attaches a QTextStream to the created file, which is written
259   to all over the place using out().
260  */
261 void Generator::beginSubPage(const InnerNode* node, const QString& fileName)
262 {
263     QString path = outputDir() + QLatin1Char('/');
264     if (!node->outputSubdirectory().isEmpty())
265         path += node->outputSubdirectory() + QLatin1Char('/');
266     path += fileName;
267     Generator::debugSegfault("Writing: " + path);
268     outFileNames.insert(fileName,fileName);
269     QFile* outFile = new QFile(path);
270     if (!outFile->open(QFile::WriteOnly))
271         node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
272     QTextStream* out = new QTextStream(outFile);
273
274     if (outputCodec)
275         out->setCodec(outputCodec);
276     outStreamStack.push(out);
277     const_cast<InnerNode*>(node)->setOutputFileName(fileName);
278 }
279
280 /*!
281   Flush the text stream associated with the subpage, and
282   then pop it off the text stream stack and delete it.
283   This terminates output of the subpage.
284  */
285 void Generator::endSubPage()
286 {
287     outStreamStack.top()->flush();
288     delete outStreamStack.top()->device();
289     delete outStreamStack.pop();
290 }
291
292 QString Generator::fileBase(const Node *node) const
293 {
294     if (node->relates())
295         node = node->relates();
296     else if (!node->isInnerNode())
297         node = node->parent();
298     if (node->subType() == Node::QmlPropertyGroup) {
299         node = node->parent();
300     }
301
302     QString base = node->doc().baseName();
303     if (!base.isEmpty())
304         return base;
305
306     const Node *p = node;
307
308     forever {
309         const Node *pp = p->parent();
310         base.prepend(p->name());
311         if (!p->qmlModuleIdentifier().isEmpty())
312             base.prepend(p->qmlModuleIdentifier()+QChar('-'));
313         /*
314           To avoid file name conflicts in the html directory,
315           we prepend a prefix (by default, "qml-") to the file name of QML
316           element doc files.
317          */
318         if ((p->subType() == Node::QmlClass) ||
319                 (p->subType() == Node::QmlBasicType)) {
320             base.prepend(outputPrefix(QLatin1String("QML")));
321         }
322         if (!pp || pp->name().isEmpty() || pp->type() == Node::Document)
323             break;
324         base.prepend(QLatin1Char('-'));
325         p = pp;
326     }
327     if (node->type() == Node::Document) {
328         if (node->subType() == Node::Collision) {
329             const NameCollisionNode* ncn = static_cast<const NameCollisionNode*>(node);
330             if (ncn->currentChild())
331                 return fileBase(ncn->currentChild());
332             base.prepend("collision-");
333         }
334         //Was QDOC2_COMPAT, required for index.html
335         if (base.endsWith(".html"))
336             base.truncate(base.length() - 5);
337
338         if (node->subType() == Node::QmlModule) {
339             base.prepend("qmlmodule-");
340         }
341         if (node->subType() == Node::Module) {
342             base.append("-module");
343         }
344     }
345
346     // the code below is effectively equivalent to:
347     //   base.replace(QRegExp("[^A-Za-z0-9]+"), " ");
348     //   base = base.trimmed();
349     //   base.replace(QLatin1Char(' '), QLatin1Char('-'));
350     //   base = base.toLower();
351     // as this function accounted for ~8% of total running time
352     // we optimize a bit...
353
354     QString res;
355     // +5 prevents realloc in fileName() below
356     res.reserve(base.size() + 5);
357     bool begun = false;
358     for (int i = 0; i != base.size(); ++i) {
359         QChar c = base.at(i);
360         uint u = c.unicode();
361         if (u >= 'A' && u <= 'Z')
362             u -= 'A' - 'a';
363         if ((u >= 'a' &&  u <= 'z') || (u >= '0' && u <= '9')) {
364             res += QLatin1Char(u);
365             begun = true;
366         }
367         else if (begun) {
368             res += QLatin1Char('-');
369             begun = false;
370         }
371     }
372     while (res.endsWith(QLatin1Char('-')))
373         res.chop(1);
374     return res;
375 }
376
377 /*!
378   If the \a node has a URL, return the URL as the file name.
379   Otherwise, construct the file name from the fileBase() and
380   the fileExtension(), and return the constructed name.
381  */
382 QString Generator::fileName(const Node* node) const
383 {
384     if (!node->url().isEmpty())
385         return node->url();
386
387     QString name = fileBase(node);
388     name += QLatin1Char('.');
389     name += fileExtension();
390     return name;
391 }
392
393 QMap<QString, QString>& Generator::formattingLeftMap()
394 {
395     return fmtLeftMaps[format()];
396 }
397
398 QMap<QString, QString>& Generator::formattingRightMap()
399 {
400     return fmtRightMaps[format()];
401 }
402
403 /*!
404   Returns the full document location.
405  */
406 QString Generator::fullDocumentLocation(const Node *node, bool subdir)
407 {
408     if (!node)
409         return QString();
410     if (!node->url().isEmpty())
411         return node->url();
412
413     QString parentName;
414     QString anchorRef;
415     QString fdl;
416
417     /*
418       If the output is being sent to subdirectories of the
419       output directory, and if the subdir parameter is set,
420       prepend the subdirectory name + '/' to the result.
421      */
422     if (subdir) {
423         fdl = node->outputSubdirectory();
424         if (!fdl.isEmpty())
425             fdl.append(QLatin1Char('/'));
426     }
427     if (node->type() == Node::Namespace) {
428
429         // The root namespace has no name - check for this before creating
430         // an attribute containing the location of any documentation.
431
432         if (!fileBase(node).isEmpty())
433             parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
434         else
435             return QString();
436     }
437     else if (node->type() == Node::Document) {
438         if ((node->subType() == Node::QmlClass) ||
439                 (node->subType() == Node::QmlBasicType)) {
440             QString fb = fileBase(node);
441             if (fb.startsWith(Generator::outputPrefix(QLatin1String("QML"))))
442                 return fb + QLatin1Char('.') + currentGenerator()->fileExtension();
443             else {
444                 QString mq;
445                 if (!node->qmlModuleName().isEmpty()) {
446                     mq = node->qmlModuleIdentifier().replace(QChar('.'),QChar('-'));
447                     mq = mq.toLower() + QLatin1Char('-');
448                 }
449                 return fdl+ Generator::outputPrefix(QLatin1String("QML")) + mq +
450                         fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
451             }
452         }
453         else {
454             parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
455         }
456     }
457     else if (fileBase(node).isEmpty())
458         return QString();
459
460     Node *parentNode = 0;
461
462     if ((parentNode = node->relates())) {
463         parentName = fullDocumentLocation(node->relates());
464     }
465     else if ((parentNode = node->parent())) {
466         if (parentNode->subType() == Node::QmlPropertyGroup) {
467             parentNode = parentNode->parent();
468             parentName = fullDocumentLocation(parentNode);
469         }
470         else {
471             parentName = fullDocumentLocation(node->parent());
472         }
473     }
474
475     switch (node->type()) {
476     case Node::Class:
477     case Node::Namespace:
478         if (parentNode && !parentNode->name().isEmpty()) {
479             parentName.remove(QLatin1Char('.') + currentGenerator()->fileExtension());
480             parentName +=  QLatin1Char('-')
481                     + fileBase(node).toLower() + QLatin1Char('.') + currentGenerator()->fileExtension();
482         } else {
483             parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
484         }
485         break;
486     case Node::Function:
487     {
488         const FunctionNode *functionNode =
489                 static_cast<const FunctionNode *>(node);
490
491         if (functionNode->metaness() == FunctionNode::Dtor)
492             anchorRef = "#dtor." + functionNode->name().mid(1);
493
494         else if (functionNode->associatedProperty())
495             return fullDocumentLocation(functionNode->associatedProperty());
496
497         else if (functionNode->overloadNumber() > 1)
498             anchorRef = QLatin1Char('#') + functionNode->name()
499                     + QLatin1Char('-') + QString::number(functionNode->overloadNumber());
500         else
501             anchorRef = QLatin1Char('#') + functionNode->name();
502         break;
503     }
504     /*
505       Use node->name() instead of fileBase(node) as
506       the latter returns the name in lower-case. For
507       HTML anchors, we need to preserve the case.
508     */
509     case Node::Enum:
510         anchorRef = QLatin1Char('#') + node->name() + "-enum";
511         break;
512     case Node::Typedef:
513         anchorRef = QLatin1Char('#') + node->name() + "-typedef";
514         break;
515     case Node::Property:
516         anchorRef = QLatin1Char('#') + node->name() + "-prop";
517         break;
518     case Node::QmlProperty:
519         anchorRef = QLatin1Char('#') + node->name() + "-prop";
520         break;
521     case Node::QmlSignal:
522         anchorRef = QLatin1Char('#') + node->name() + "-signal";
523         break;
524     case Node::QmlSignalHandler:
525         anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
526         break;
527     case Node::QmlMethod:
528         anchorRef = QLatin1Char('#') + node->name() + "-method";
529         break;
530     case Node::Variable:
531         anchorRef = QLatin1Char('#') + node->name() + "-var";
532         break;
533     case Node::Document:
534     {
535         parentName = fileBase(node);
536         parentName.replace(QLatin1Char('/'), QLatin1Char('-')).replace(QLatin1Char('.'), QLatin1Char('-'));
537         parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
538     }
539         break;
540     default:
541         break;
542     }
543
544     // Various objects can be compat (deprecated) or obsolete.
545     if (node->type() != Node::Class && node->type() != Node::Namespace) {
546         switch (node->status()) {
547         case Node::Compat:
548             parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
549                                "-compat." + currentGenerator()->fileExtension());
550             break;
551         case Node::Obsolete:
552             parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
553                                "-obsolete." + currentGenerator()->fileExtension());
554             break;
555         default:
556             ;
557         }
558     }
559
560     return fdl + parentName.toLower() + anchorRef;
561 }
562
563 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
564 {
565     QList<Text> alsoList = node->doc().alsoList();
566     supplementAlsoList(node, alsoList);
567
568     if (!alsoList.isEmpty()) {
569         Text text;
570         text << Atom::ParaLeft
571              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
572              << "See also "
573              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
574
575         for (int i = 0; i < alsoList.size(); ++i)
576             text << alsoList.at(i) << separator(i, alsoList.size());
577
578         text << Atom::ParaRight;
579         generateText(text, node, marker);
580     }
581 }
582
583 int Generator::generateAtom(const Atom * /* atom */,
584                             const Node * /* relative */,
585                             CodeMarker * /* marker */)
586 {
587     return 0;
588 }
589
590 const Atom *Generator::generateAtomList(const Atom *atom,
591                                         const Node *relative,
592                                         CodeMarker *marker,
593                                         bool generate,
594                                         int &numAtoms)
595 {
596     while (atom) {
597         if (atom->type() == Atom::FormatIf) {
598             int numAtoms0 = numAtoms;
599             bool rightFormat = canHandleFormat(atom->string());
600             atom = generateAtomList(atom->next(),
601                                     relative,
602                                     marker,
603                                     generate && rightFormat,
604                                     numAtoms);
605             if (!atom)
606                 return 0;
607
608             if (atom->type() == Atom::FormatElse) {
609                 ++numAtoms;
610                 atom = generateAtomList(atom->next(),
611                                         relative,
612                                         marker,
613                                         generate && !rightFormat,
614                                         numAtoms);
615                 if (!atom)
616                     return 0;
617             }
618
619             if (atom->type() == Atom::FormatEndif) {
620                 if (generate && numAtoms0 == numAtoms) {
621                     relative->location().warning(tr("Output format %1 not handled %2")
622                                                  .arg(format()).arg(outFileName()));
623                     Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
624                     generateAtomList(&unhandledFormatAtom,
625                                      relative,
626                                      marker,
627                                      generate,
628                                      numAtoms);
629                 }
630                 atom = atom->next();
631             }
632         }
633         else if (atom->type() == Atom::FormatElse ||
634                  atom->type() == Atom::FormatEndif) {
635             return atom;
636         }
637         else {
638             int n = 1;
639             if (generate) {
640                 n += generateAtom(atom, relative, marker);
641                 numAtoms += n;
642             }
643             while (n-- > 0)
644                 atom = atom->next();
645         }
646     }
647     return 0;
648 }
649
650 void Generator::generateBody(const Node *node, CodeMarker *marker)
651 {
652     bool quiet = false;
653
654     if (node->type() == Node::Document) {
655         const DocNode *dn = static_cast<const DocNode *>(node);
656         if ((dn->subType() == Node::File) || (dn->subType() == Node::Image)) {
657             quiet = true;
658         }
659     }
660     if (node->doc().isEmpty()) {
661         if (!quiet && !node->isReimp()) { // ### might be unnecessary
662             node->location().warning(tr("No documentation for '%1'").arg(node->plainFullName()));
663         }
664     }
665     else {
666         if (node->type() == Node::Function) {
667             const FunctionNode *func = static_cast<const FunctionNode *>(node);
668             if (func->reimplementedFrom() != 0)
669                 generateReimplementedFrom(func, marker);
670         }
671
672         if (!generateText(node->doc().body(), node, marker)) {
673             if (node->isReimp())
674                 return;
675         }
676
677         if (node->type() == Node::Enum) {
678             const EnumNode *enume = (const EnumNode *) node;
679
680             QSet<QString> definedItems;
681             QList<EnumItem>::ConstIterator it = enume->items().constBegin();
682             while (it != enume->items().constEnd()) {
683                 definedItems.insert((*it).name());
684                 ++it;
685             }
686
687             QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
688             QSet<QString> allItems = definedItems + documentedItems;
689             if (allItems.count() > definedItems.count() ||
690                     allItems.count() > documentedItems.count()) {
691                 QSet<QString>::ConstIterator a = allItems.constBegin();
692                 while (a != allItems.constEnd()) {
693                     if (!definedItems.contains(*a)) {
694                         QString details;
695                         QString best = nearestName(*a, definedItems);
696                         if (!best.isEmpty() && !documentedItems.contains(best))
697                             details = tr("Maybe you meant '%1'?").arg(best);
698
699                         node->doc().location().warning(tr("No such enum item '%1' in %2").arg(*a).arg(node->plainFullName()), details);
700                         if (*a == "Void")
701                             qDebug() << "VOID:" << node->name() << definedItems;
702                     }
703                     else if (!documentedItems.contains(*a)) {
704                         node->doc().location().warning(tr("Undocumented enum item '%1' in %2").arg(*a).arg(node->plainFullName()));
705                     }
706                     ++a;
707                 }
708             }
709         }
710         else if (node->type() == Node::Function) {
711             const FunctionNode *func = static_cast<const FunctionNode *>(node);
712             QSet<QString> definedParams;
713             QList<Parameter>::ConstIterator p = func->parameters().constBegin();
714             while (p != func->parameters().constEnd()) {
715                 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
716                         && func->name() != QLatin1String("operator++")
717                         && func->name() != QLatin1String("operator--")) {
718                     node->doc().location().warning(tr("Missing parameter name"));
719                 }
720                 else {
721                     definedParams.insert((*p).name());
722                 }
723                 ++p;
724             }
725
726             QSet<QString> documentedParams = func->doc().parameterNames();
727             QSet<QString> allParams = definedParams + documentedParams;
728             if (allParams.count() > definedParams.count()
729                     || allParams.count() > documentedParams.count()) {
730                 QSet<QString>::ConstIterator a = allParams.constBegin();
731                 while (a != allParams.constEnd()) {
732                     if (!definedParams.contains(*a)) {
733                         QString details;
734                         QString best = nearestName(*a, definedParams);
735                         if (!best.isEmpty())
736                             details = tr("Maybe you meant '%1'?").arg(best);
737
738                         node->doc().location().warning(
739                                     tr("No such parameter '%1' in %2").arg(*a).arg(node->plainFullName()),
740                                     details);
741                     }
742                     else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
743                         bool needWarning = (func->status() > Node::Obsolete);
744                         if (func->overloadNumber() > 1) {
745                             FunctionNode *primaryFunc =
746                                     func->parent()->findFunctionNode(func->name());
747                             if (primaryFunc) {
748                                 foreach (const Parameter &param,
749                                          primaryFunc->parameters()) {
750                                     if (param.name() == *a) {
751                                         needWarning = false;
752                                         break;
753                                     }
754                                 }
755                             }
756                         }
757                         if (needWarning && !func->isReimp())
758                             node->doc().location().warning(
759                                         tr("Undocumented parameter '%1' in %2")
760                                         .arg(*a).arg(node->plainFullName()));
761                     }
762                     ++a;
763                 }
764             }
765             /*
766               Something like this return value check should
767               be implemented at some point.
768             */
769             if (func->status() > Node::Obsolete && func->returnType() == "bool"
770                     && func->reimplementedFrom() == 0 && !func->isOverload()) {
771                 QString body = func->doc().body().toString();
772                 if (!body.contains("return", Qt::CaseInsensitive))
773                     node->doc().location().warning(tr("Undocumented return value"));
774             }
775         }
776     }
777
778     if (node->type() == Node::Document) {
779         const DocNode *dn = static_cast<const DocNode *>(node);
780         if (dn->subType() == Node::Example) {
781             generateExampleFiles(dn, marker);
782         }
783         else if (dn->subType() == Node::File) {
784             Text text;
785             Quoter quoter;
786             Doc::quoteFromFile(dn->doc().location(), quoter, dn->name());
787             QString code = quoter.quoteTo(dn->location(), QString(), QString());
788             CodeMarker *codeMarker = CodeMarker::markerForFileName(dn->name());
789             text << Atom(codeMarker->atomType(), code);
790             generateText(text, dn, codeMarker);
791         }
792     }
793 }
794
795 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
796 {
797 }
798
799 void Generator::generateExampleFiles(const DocNode *dn, CodeMarker *marker)
800 {
801     if (dn->childNodes().isEmpty())
802         return;
803     generateFileList(dn, marker, Node::File, QString("Files:"));
804     generateFileList(dn, marker, Node::Image, QString("Images:"));
805 }
806
807 void Generator::generateDocNode(DocNode* /* dn */, CodeMarker* /* marker */)
808 {
809 }
810
811 /*!
812   This function is called when the documentation for an
813   example is being formatted. It outputs the list of source
814   files comprising the example, and the list of images used
815   by the example. The images are copied into a subtree of
816   \c{...doc/html/images/used-in-examples/...}
817  */
818 void Generator::generateFileList(const DocNode* dn,
819                                  CodeMarker* marker,
820                                  Node::SubType subtype,
821                                  const QString& tag)
822 {
823     int count = 0;
824     Text text;
825     OpenedList openedList(OpenedList::Bullet);
826
827     text << Atom::ParaLeft << tag << Atom::ParaRight
828          << Atom(Atom::ListLeft, openedList.styleString());
829
830     foreach (const Node* child, dn->childNodes()) {
831         if (child->subType() == subtype) {
832             ++count;
833             QString file = child->name();
834             if (subtype == Node::Image) {
835                 if (!file.isEmpty()) {
836                     QDir dirInfo;
837                     QString userFriendlyFilePath;
838                     QString srcPath = Config::findFile(dn->location(),
839                                                        QStringList(),
840                                                        exampleDirs,
841                                                        file,
842                                                        exampleImgExts,
843                                                        userFriendlyFilePath);
844                     userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
845
846                     QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
847                     if (!dirInfo.mkpath(imgOutDir))
848                         dn->location().fatal(tr("Cannot create output directory '%1'")
849                                                .arg(imgOutDir));
850
851                     QString imgOutName = Config::copyFile(dn->location(),
852                                                           srcPath,
853                                                           file,
854                                                           imgOutDir);
855                 }
856
857             }
858
859             openedList.next();
860             text << Atom(Atom::ListItemNumber, openedList.numberString())
861                  << Atom(Atom::ListItemLeft, openedList.styleString())
862                  << Atom::ParaLeft
863                  << Atom(Atom::Link, file)
864                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
865                  << file
866                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
867                  << Atom::ParaRight
868                  << Atom(Atom::ListItemRight, openedList.styleString());
869         }
870     }
871     text << Atom(Atom::ListRight, openedList.styleString());
872     if (count > 0)
873         generateText(text, dn, marker);
874 }
875
876 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
877 {
878     if (!classe->derivedClasses().isEmpty()) {
879         Text text;
880         text << Atom::ParaLeft
881              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
882              << "Inherited by: "
883              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
884
885         appendSortedNames(text, classe, classe->derivedClasses());
886         text << Atom::ParaRight;
887         generateText(text, classe, marker);
888     }
889 }
890
891 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
892 {
893     QList<RelatedClass>::ConstIterator r;
894     int index;
895
896     if (!classe->baseClasses().isEmpty()) {
897         Text text;
898         text << Atom::ParaLeft
899              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
900              << "Inherits: "
901              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
902
903         r = classe->baseClasses().constBegin();
904         index = 0;
905         while (r != classe->baseClasses().constEnd()) {
906             text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
907                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
908                  << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
909                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
910
911             if ((*r).access == Node::Protected) {
912                 text << " (protected)";
913             }
914             else if ((*r).access == Node::Private) {
915                 text << " (private)";
916             }
917             text << separator(index++, classe->baseClasses().count());
918             ++r;
919         }
920         text << Atom::ParaRight;
921         generateText(text, classe, marker);
922     }
923 }
924
925 /*!
926   Recursive writing of HTML files from the root \a node.
927
928   \note NameCollisionNodes are skipped here and processed
929   later. See HtmlGenerator::generateCollisionPages() for
930   more on this.
931  */
932 void Generator::generateInnerNode(InnerNode* node)
933 {
934     if (!node->url().isNull())
935         return;
936
937     if (node->type() == Node::Document) {
938         DocNode* docNode = static_cast<DocNode*>(node);
939         if (docNode->subType() == Node::ExternalPage)
940             return;
941         if (docNode->subType() == Node::Image)
942             return;
943         if (docNode->subType() == Node::QmlPropertyGroup)
944             return;
945         if (docNode->subType() == Node::Page) {
946             if (node->count() > 0)
947                 qDebug("PAGE %s HAS CHILDREN", qPrintable(docNode->title()));
948         }
949     }
950
951     /*
952       Obtain a code marker for the source file.
953      */
954     CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
955
956     if (node->parent() != 0) {
957         /*
958           Skip name collision nodes here and process them
959           later in generateCollisionPages(). Each one is
960           appended to a list for later.
961          */
962         if ((node->type() == Node::Document) && (node->subType() == Node::Collision)) {
963             NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
964             collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
965         }
966         else {
967             beginSubPage(node, fileName(node));
968             if (node->type() == Node::Namespace || node->type() == Node::Class) {
969                 generateClassLikeNode(node, marker);
970             }
971             else if (node->type() == Node::Document) {
972                 generateDocNode(static_cast<DocNode*>(node), marker);
973             }
974             endSubPage();
975         }
976     }
977
978     NodeList::ConstIterator c = node->childNodes().constBegin();
979     while (c != node->childNodes().constEnd()) {
980         if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
981             generateInnerNode((InnerNode*)*c);
982         }
983         ++c;
984     }
985 }
986
987 /*!
988   Generate a list of maintainers in the output
989  */
990 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
991 {
992     QStringList sl = getMetadataElements(node,"maintainer");
993
994     if (!sl.isEmpty()) {
995         Text text;
996         text << Atom::ParaLeft
997              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
998              << "Maintained by: "
999              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
1000
1001         for (int i = 0; i < sl.size(); ++i)
1002             text << sl.at(i) << separator(i, sl.size());
1003
1004         text << Atom::ParaRight;
1005         generateText(text, node, marker);
1006     }
1007 }
1008
1009 /*!
1010   Output the "Inherit by" list for the QML element,
1011   if it is inherited by any other elements.
1012  */
1013 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
1014                                               CodeMarker* marker)
1015 {
1016     if (qcn) {
1017         NodeList subs;
1018         QmlClassNode::subclasses(qcn->name(),subs);
1019         if (!subs.isEmpty()) {
1020             Text text;
1021             text << Atom::ParaLeft << "Inherited by ";
1022             appendSortedQmlNames(text,qcn,subs);
1023             text << Atom::ParaRight;
1024             generateText(text, qcn, marker);
1025         }
1026     }
1027 }
1028
1029 /*!
1030  */
1031 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
1032 {
1033     // stub.
1034 }
1035
1036 /*!
1037   Extract sections of markup text surrounded by \e qmltext
1038   and \e endqmltext and output them.
1039  */
1040 bool Generator::generateQmlText(const Text& text,
1041                                 const Node *relative,
1042                                 CodeMarker *marker,
1043                                 const QString& /* qmlName */ )
1044 {
1045     const Atom* atom = text.firstAtom();
1046     bool result = false;
1047
1048     if (atom != 0) {
1049         initializeTextOutput();
1050         while (atom) {
1051             if (atom->type() != Atom::QmlText)
1052                 atom = atom->next();
1053             else {
1054                 atom = atom->next();
1055                 while (atom && (atom->type() != Atom::EndQmlText)) {
1056                     int n = 1 + generateAtom(atom, relative, marker);
1057                     while (n-- > 0)
1058                         atom = atom->next();
1059                 }
1060             }
1061         }
1062         result = true;
1063     }
1064     return result;
1065 }
1066
1067 void Generator::generateReimplementedFrom(const FunctionNode *func,
1068                                           CodeMarker *marker)
1069 {
1070     if (func->reimplementedFrom() != 0) {
1071         const FunctionNode *from = func->reimplementedFrom();
1072         if (from->access() != Node::Private &&
1073                 from->parent()->access() != Node::Private) {
1074             Text text;
1075             text << Atom::ParaLeft << "Reimplemented from ";
1076             QString fullName =  from->parent()->name() + "::" + from->name() + "()";
1077             appendFullName(text, from->parent(), fullName, from);
1078             text << "." << Atom::ParaRight;
1079             generateText(text, func, marker);
1080         }
1081     }
1082 }
1083
1084 void Generator::generateSince(const Node *node, CodeMarker *marker)
1085 {
1086     if (!node->since().isEmpty()) {
1087         Text text;
1088         text << Atom::ParaLeft
1089              << "This "
1090              << typeString(node);
1091         if (node->type() == Node::Enum)
1092             text << " was introduced or modified in ";
1093         else
1094             text << " was introduced in ";
1095
1096         QStringList since = node->since().split(QLatin1Char(' '));
1097         if (since.count() == 1) {
1098             // Handle legacy use of \since <version>.
1099             if (project.isEmpty())
1100                 text << "version";
1101             else
1102                 text << project;
1103             text << " " << since[0];
1104         } else {
1105             // Reconstruct the <project> <version> string.
1106             text << " " << since.join(' ');
1107         }
1108
1109         text << "." << Atom::ParaRight;
1110         generateText(text, node, marker);
1111     }
1112 }
1113
1114 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1115 {
1116     Text text;
1117
1118     switch (node->status()) {
1119     case Node::Commendable:
1120     case Node::Main:
1121         break;
1122     case Node::Preliminary:
1123         text << Atom::ParaLeft
1124              << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1125              << "This "
1126              << typeString(node)
1127              << " is under development and is subject to change."
1128              << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1129              << Atom::ParaRight;
1130         break;
1131     case Node::Deprecated:
1132         text << Atom::ParaLeft;
1133         if (node->isInnerNode())
1134             text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1135         text << "This " << typeString(node) << " is deprecated.";
1136         if (node->isInnerNode())
1137             text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1138         text << Atom::ParaRight;
1139         break;
1140     case Node::Obsolete:
1141         text << Atom::ParaLeft;
1142         if (node->isInnerNode())
1143             text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1144         text << "This " << typeString(node) << " is obsolete.";
1145         if (node->isInnerNode())
1146             text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1147         text << " It is provided to keep old source code working. "
1148              << "We strongly advise against "
1149              << "using it in new code." << Atom::ParaRight;
1150         break;
1151     case Node::Compat:
1152         // reimplemented in HtmlGenerator subclass
1153         if (node->isInnerNode()) {
1154             text << Atom::ParaLeft
1155                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1156                  << "This "
1157                  << typeString(node)
1158                  << " is part of the Qt compatibility layer."
1159                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1160                  << " It is provided to keep old source code working. "
1161                  << "We strongly advise against using it in new code."
1162                  << Atom::ParaRight;
1163         }
1164         break;
1165     case Node::Internal:
1166     default:
1167         break;
1168     }
1169     generateText(text, node, marker);
1170 }
1171
1172 bool Generator::generateText(const Text& text,
1173                              const Node *relative,
1174                              CodeMarker *marker)
1175 {
1176     bool result = false;
1177     if (text.firstAtom() != 0) {
1178         int numAtoms = 0;
1179         initializeTextOutput();
1180         generateAtomList(text.firstAtom(),
1181                          relative,
1182                          marker,
1183                          true,
1184                          numAtoms);
1185         result = true;
1186     }
1187     return result;
1188 }
1189
1190 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1191 {
1192     Text text;
1193     Node::ThreadSafeness threadSafeness = node->threadSafeness();
1194
1195     Text rlink;
1196     rlink << Atom(Atom::Link,"reentrant")
1197           << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1198           << "reentrant"
1199           << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1200
1201     Text tlink;
1202     tlink << Atom(Atom::Link,"thread-safe")
1203           << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1204           << "thread-safe"
1205           << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1206
1207     switch (threadSafeness) {
1208     case Node::UnspecifiedSafeness:
1209         break;
1210     case Node::NonReentrant:
1211         text << Atom::ParaLeft
1212              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1213              << "Warning:"
1214              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1215              << " This "
1216              << typeString(node)
1217              << " is not "
1218              << rlink
1219              << "."
1220              << Atom::ParaRight;
1221         break;
1222     case Node::Reentrant:
1223     case Node::ThreadSafe:
1224         text << Atom::ParaLeft
1225              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1226              << "Note:"
1227              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1228              << " ";
1229
1230         if (node->isInnerNode()) {
1231             const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1232             text << "All functions in this "
1233                  << typeString(node)
1234                  << " are ";
1235             if (threadSafeness == Node::ThreadSafe)
1236                 text << tlink;
1237             else
1238                 text << rlink;
1239
1240             bool exceptions = false;
1241             NodeList reentrant;
1242             NodeList threadsafe;
1243             NodeList nonreentrant;
1244             NodeList::ConstIterator c = innerNode->childNodes().constBegin();
1245             while (c != innerNode->childNodes().constEnd()) {
1246
1247                 if ((*c)->status() != Node::Obsolete){
1248                     switch ((*c)->threadSafeness()) {
1249                     case Node::Reentrant:
1250                         reentrant.append(*c);
1251                         if (threadSafeness == Node::ThreadSafe)
1252                             exceptions = true;
1253                         break;
1254                     case Node::ThreadSafe:
1255                         threadsafe.append(*c);
1256                         if (threadSafeness == Node::Reentrant)
1257                             exceptions = true;
1258                         break;
1259                     case Node::NonReentrant:
1260                         nonreentrant.append(*c);
1261                         exceptions = true;
1262                         break;
1263                     default:
1264                         break;
1265                     }
1266                 }
1267                 ++c;
1268             }
1269             if (!exceptions)
1270                 text << ".";
1271             else if (threadSafeness == Node::Reentrant) {
1272                 if (nonreentrant.isEmpty()) {
1273                     if (!threadsafe.isEmpty()) {
1274                         text << ", but ";
1275                         appendFullNames(text,threadsafe,innerNode);
1276                         singularPlural(text,threadsafe);
1277                         text << " also " << tlink << ".";
1278                     }
1279                     else
1280                         text << ".";
1281                 }
1282                 else {
1283                     text << ", except for ";
1284                     appendFullNames(text,nonreentrant,innerNode);
1285                     text << ", which";
1286                     singularPlural(text,nonreentrant);
1287                     text << " nonreentrant.";
1288                     if (!threadsafe.isEmpty()) {
1289                         text << " ";
1290                         appendFullNames(text,threadsafe,innerNode);
1291                         singularPlural(text,threadsafe);
1292                         text << " " << tlink << ".";
1293                     }
1294                 }
1295             }
1296             else { // thread-safe
1297                 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1298                     text << ", except for ";
1299                     if (!reentrant.isEmpty()) {
1300                         appendFullNames(text,reentrant,innerNode);
1301                         text << ", which";
1302                         singularPlural(text,reentrant);
1303                         text << " only " << rlink;
1304                         if (!nonreentrant.isEmpty())
1305                             text << ", and ";
1306                     }
1307                     if (!nonreentrant.isEmpty()) {
1308                         appendFullNames(text,nonreentrant,innerNode);
1309                         text << ", which";
1310                         singularPlural(text,nonreentrant);
1311                         text << " nonreentrant.";
1312                     }
1313                     text << ".";
1314                 }
1315             }
1316         }
1317         else {
1318             text << "This " << typeString(node) << " is ";
1319             if (threadSafeness == Node::ThreadSafe)
1320                 text << tlink;
1321             else
1322                 text << rlink;
1323             text << ".";
1324         }
1325         text << Atom::ParaRight;
1326     }
1327     generateText(text,node,marker);
1328 }
1329
1330 /*!
1331   Traverses the database recursivly to generate all the documentation.
1332  */
1333 void Generator::generateTree()
1334 {
1335     generateInnerNode(qdb_->treeRoot());
1336 }
1337
1338 Generator *Generator::generatorForFormat(const QString& format)
1339 {
1340     QList<Generator *>::ConstIterator g = generators.constBegin();
1341     while (g != generators.constEnd()) {
1342         if ((*g)->format() == format)
1343             return *g;
1344         ++g;
1345     }
1346     return 0;
1347 }
1348
1349 /*!
1350   This function can be called if getLink() returns an empty
1351   string. It tests the \a atom string to see if it is a link
1352   of the form <element> :: <name>, where <element> is a QML
1353   element or component without a module qualifier. If so, it
1354   constructs a link to the <name> clause on the disambiguation
1355   page for <element> and returns that link string. It also
1356   adds the <name> as a target in the NameCollisionNode for
1357   <element>. These clauses are then constructed when the
1358   disambiguation page is actually generated.
1359  */
1360 QString Generator::getCollisionLink(const Atom* atom)
1361 {
1362     QString link;
1363     if (!atom->string().contains("::"))
1364         return link;
1365     QStringList path = atom->string().split("::");
1366     NameCollisionNode* ncn = qdb_->findCollisionNode(path[0]);
1367     if (ncn) {
1368         QString label;
1369         if (atom->next() && atom->next()->next()) {
1370             if (atom->next()->type() == Atom::FormattingLeft &&
1371                     atom->next()->next()->type() == Atom::String)
1372                 label = atom->next()->next()->string();
1373         }
1374         ncn->addLinkTarget(path[1],label);
1375         link = fileName(ncn);
1376         link += QLatin1Char('#');
1377         link += Doc::canonicalTitle(path[1]);
1378     }
1379     return link;
1380 }
1381
1382
1383 /*!
1384   Looks up the tag \a t in the map of metadata values for the
1385   current topic in \a inner. If a value for the tag is found,
1386   the value is returned.
1387
1388   \note If \a t is found in the metadata map, it is erased.
1389   i.e. Once you call this function for a particular \a t,
1390   you consume \a t.
1391  */
1392 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1393 {
1394     QString s;
1395     QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1396     QStringMultiMap::iterator i = metaTagMap.find(t);
1397     if (i != metaTagMap.end()) {
1398         s = i.value();
1399         metaTagMap.erase(i);
1400     }
1401     return s;
1402 }
1403
1404 /*!
1405   Looks up the tag \a t in the map of metadata values for the
1406   current topic in \a inner. If values for the tag are found,
1407   they are returned in a string list.
1408
1409   \note If \a t is found in the metadata map, all the pairs
1410   having the key \a t are erased. i.e. Once you call this
1411   function for a particular \a t, you consume \a t.
1412  */
1413 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1414 {
1415     QStringList s;
1416     QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1417     s = metaTagMap.values(t);
1418     if (!s.isEmpty())
1419         metaTagMap.remove(t);
1420     return s;
1421 }
1422
1423 /*!
1424   Returns a relative path name for an image.
1425  */
1426 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1427 {
1428     QString userFriendlyFilePath;
1429     QString filePath = Config::findFile(relative->doc().location(),
1430                                         imageFiles,
1431                                         imageDirs,
1432                                         fileBase,
1433                                         imgFileExts[format()],
1434                                         userFriendlyFilePath);
1435
1436     if (filePath.isEmpty())
1437         return QString();
1438
1439     QString path = Config::copyFile(relative->doc().location(),
1440                                     filePath,
1441                                     userFriendlyFilePath,
1442                                     outputDir() + QLatin1String("/images"));
1443     QString images = "images";
1444     if (path[0] != '/')
1445         images.append(QLatin1Char('/'));
1446     return images + path;
1447 }
1448
1449 QString Generator::indent(int level, const QString& markedCode)
1450 {
1451     if (level == 0)
1452         return markedCode;
1453
1454     QString t;
1455     int column = 0;
1456
1457     int i = 0;
1458     while (i < (int) markedCode.length()) {
1459         if (markedCode.at(i) == QLatin1Char('\n')) {
1460             column = 0;
1461         }
1462         else {
1463             if (column == 0) {
1464                 for (int j = 0; j < level; j++)
1465                     t += QLatin1Char(' ');
1466             }
1467             column++;
1468         }
1469         t += markedCode.at(i++);
1470     }
1471     return t;
1472 }
1473
1474 void Generator::initialize(const Config &config)
1475 {
1476     outputFormats = config.getOutputFormats();
1477     if (!outputFormats.isEmpty()) {
1478         outDir_ = config.getOutputDir();
1479
1480         if (outDir_.isEmpty())
1481             config.lastLocation().fatal(tr("No output directory specified in "
1482                                            "configuration file or on the command line"));
1483
1484         QDir dirInfo;
1485         if (dirInfo.exists(outDir_)) {
1486             if (!Config::removeDirContents(outDir_))
1487                 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1488         }
1489         else {
1490             if (!dirInfo.mkpath(outDir_))
1491                 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1492         }
1493
1494         if (!dirInfo.mkdir(outDir_ + "/images"))
1495             config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/images"));
1496         if (!dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1497             config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/images/used-in-examples"));
1498         if (!dirInfo.mkdir(outDir_ + "/scripts"))
1499             config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/scripts"));
1500         if (!dirInfo.mkdir(outDir_ + "/style"))
1501             config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/style"));
1502     }
1503
1504     imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1505     imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1506     scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1507     scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1508     styleFiles = config.getCleanPathList(CONFIG_STYLES);
1509     styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1510     exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1511     exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS);
1512
1513     QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1514     QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1515     QSet<QString>::ConstIterator f = formats.constBegin();
1516     while (f != formats.constEnd()) {
1517         imgFileExts[*f] = config.getStringList(imagesDotFileExtensions + Config::dot + *f);
1518         ++f;
1519     }
1520
1521     QList<Generator *>::ConstIterator g = generators.constBegin();
1522     while (g != generators.constEnd()) {
1523         if (outputFormats.contains((*g)->format())) {
1524             currentGenerator_ = (*g);
1525             (*g)->initializeGenerator(config);
1526             QStringList extraImages = config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
1527             QStringList::ConstIterator e = extraImages.constBegin();
1528             while (e != extraImages.constEnd()) {
1529                 QString userFriendlyFilePath;
1530                 QString filePath = Config::findFile(config.lastLocation(),
1531                                                     imageFiles,
1532                                                     imageDirs,
1533                                                     *e,
1534                                                     imgFileExts[(*g)->format()],
1535                                                     userFriendlyFilePath);
1536                 if (!filePath.isEmpty())
1537                     Config::copyFile(config.lastLocation(),
1538                                      filePath,
1539                                      userFriendlyFilePath,
1540                                      (*g)->outputDir() +
1541                                      "/images");
1542                 ++e;
1543             }
1544
1545             // Documentation template handling
1546             QString templateDir = config.getString((*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
1547
1548             QStringList searchDirs;
1549             if (!templateDir.isEmpty()) {
1550                 searchDirs.append(templateDir);
1551             }
1552             if (!Config::installDir.isEmpty()) {
1553                 searchDirs.append(Config::installDir);
1554             }
1555
1556             if (!searchDirs.isEmpty()) {
1557                 QStringList noExts;
1558                 QStringList scripts = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1559                 e = scripts.constBegin();
1560                 while (e != scripts.constEnd()) {
1561                     QString userFriendlyFilePath;
1562                     QString filePath = Config::findFile(config.lastLocation(),
1563                                                         scriptFiles,
1564                                                         searchDirs,
1565                                                         *e,
1566                                                         noExts,
1567                                                         userFriendlyFilePath);
1568                     if (!filePath.isEmpty())
1569                         Config::copyFile(config.lastLocation(),
1570                                          filePath,
1571                                          userFriendlyFilePath,
1572                                          (*g)->outputDir() +
1573                                          "/scripts");
1574                     ++e;
1575                 }
1576
1577                 QStringList styles = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1578                 e = styles.constBegin();
1579                 while (e != styles.constEnd()) {
1580                     QString userFriendlyFilePath;
1581                     QString filePath = Config::findFile(config.lastLocation(),
1582                                                         styleFiles,
1583                                                         searchDirs,
1584                                                         *e,
1585                                                         noExts,
1586                                                         userFriendlyFilePath);
1587                     if (!filePath.isEmpty())
1588                         Config::copyFile(config.lastLocation(),
1589                                          filePath,
1590                                          userFriendlyFilePath,
1591                                          (*g)->outputDir() +
1592                                          "/style");
1593                     ++e;
1594                 }
1595             }
1596         }
1597         ++g;
1598     }
1599
1600     QRegExp secondParamAndAbove("[\2-\7]");
1601     QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1602     QSet<QString>::ConstIterator n = formattingNames.constBegin();
1603     while (n != formattingNames.constEnd()) {
1604         QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1605         QSet<QString> formats = config.subVars(formattingDotName);
1606         QSet<QString>::ConstIterator f = formats.constBegin();
1607         while (f != formats.constEnd()) {
1608             QString def = config.getString(formattingDotName + Config::dot + *f);
1609             if (!def.isEmpty()) {
1610                 int numParams = Config::numParams(def);
1611                 int numOccs = def.count("\1");
1612                 if (numParams != 1) {
1613                     config.lastLocation().warning(tr("Formatting '%1' must "
1614                                                      "have exactly one "
1615                                                      "parameter (found %2)")
1616                                                   .arg(*n).arg(numParams));
1617                 }
1618                 else if (numOccs > 1) {
1619                     config.lastLocation().fatal(tr("Formatting '%1' must "
1620                                                    "contain exactly one "
1621                                                    "occurrence of '\\1' "
1622                                                    "(found %2)")
1623                                                 .arg(*n).arg(numOccs));
1624                 }
1625                 else {
1626                     int paramPos = def.indexOf("\1");
1627                     fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1628                     fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1629                 }
1630             }
1631             ++f;
1632         }
1633         ++n;
1634     }
1635
1636     project = config.getString(CONFIG_PROJECT);
1637
1638     QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1639     if (!prefixes.isEmpty()) {
1640         foreach (const QString &prefix, prefixes)
1641             outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1642     }
1643     else
1644         outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1645     noLinkErrors_ = config.getBool(QLatin1String(CONFIG_NOLINKERRORS));
1646 }
1647
1648 /*!
1649   Appends each directory path in \a moreImageDirs to the
1650   list of image directories.
1651  */
1652 void Generator::augmentImageDirs(QSet<QString>& moreImageDirs)
1653 {
1654     if (moreImageDirs.isEmpty())
1655         return;
1656     QSet<QString>::const_iterator i = moreImageDirs.begin();
1657     while (i != moreImageDirs.end()) {
1658         imageDirs.append(*i);
1659         ++i;
1660     }
1661 }
1662
1663 void Generator::initializeGenerator(const Config & /* config */)
1664 {
1665 }
1666
1667 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1668 {
1669     return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1670 }
1671
1672 /*!
1673   Used for writing to the current output stream. Returns a
1674   reference to the current output stream, which is then used
1675   with the \c {<<} operator for writing.
1676  */
1677 QTextStream &Generator::out()
1678 {
1679     return *outStreamStack.top();
1680 }
1681
1682 QString Generator::outFileName()
1683 {
1684     return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1685 }
1686
1687 QString Generator::outputPrefix(const QString &nodeType)
1688 {
1689     return outputPrefixes[nodeType];
1690 }
1691
1692 bool Generator::parseArg(const QString& src,
1693                              const QString& tag,
1694                              int* pos,
1695                              int n,
1696                              QStringRef* contents,
1697                              QStringRef* par1,
1698                              bool debug)
1699 {
1700 #define SKIP_CHAR(c) \
1701     if (debug) \
1702     qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1703     if (i >= n || src[i] != c) { \
1704     if (debug) \
1705     qDebug() << " char '" << c << "' not found"; \
1706     return false; \
1707 } \
1708     ++i;
1709
1710
1711 #define SKIP_SPACE \
1712     while (i < n && src[i] == ' ') \
1713     ++i;
1714
1715     int i = *pos;
1716     int j = i;
1717
1718     // assume "<@" has been parsed outside
1719     //SKIP_CHAR('<');
1720     //SKIP_CHAR('@');
1721
1722     if (tag != QStringRef(&src, i, tag.length())) {
1723         if (0 && debug)
1724             qDebug() << "tag " << tag << " not found at " << i;
1725         return false;
1726     }
1727
1728     if (debug)
1729         qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1730
1731     // skip tag
1732     i += tag.length();
1733
1734     // parse stuff like:  linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1735     if (par1) {
1736         SKIP_SPACE;
1737         // read parameter name
1738         j = i;
1739         while (i < n && src[i].isLetter())
1740             ++i;
1741         if (src[i] == '=') {
1742             if (debug)
1743                 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1744             SKIP_CHAR('=');
1745             SKIP_CHAR('"');
1746             // skip parameter name
1747             j = i;
1748             while (i < n && src[i] != '"')
1749                 ++i;
1750             *par1 = QStringRef(&src, j, i - j);
1751             SKIP_CHAR('"');
1752             SKIP_SPACE;
1753         } else {
1754             if (debug)
1755                 qDebug() << "no optional parameter found";
1756         }
1757     }
1758     SKIP_SPACE;
1759     SKIP_CHAR('>');
1760
1761     // find contents up to closing "</@tag>
1762     j = i;
1763     for (; true; ++i) {
1764         if (i + 4 + tag.length() > n)
1765             return false;
1766         if (src[i] != '<')
1767             continue;
1768         if (src[i + 1] != '/')
1769             continue;
1770         if (src[i + 2] != '@')
1771             continue;
1772         if (tag != QStringRef(&src, i + 3, tag.length()))
1773             continue;
1774         if (src[i + 3 + tag.length()] != '>')
1775             continue;
1776         break;
1777     }
1778
1779     *contents = QStringRef(&src, j, i - j);
1780
1781     i += tag.length() + 4;
1782
1783     *pos = i;
1784     if (debug)
1785         qDebug() << " tag " << tag << " found: pos now: " << i;
1786     return true;
1787 #undef SKIP_CHAR
1788 }
1789
1790 QString Generator::plainCode(const QString& markedCode)
1791 {
1792     QString t = markedCode;
1793     t.replace(tag, QString());
1794     t.replace(quot, QLatin1String("\""));
1795     t.replace(gt, QLatin1String(">"));
1796     t.replace(lt, QLatin1String("<"));
1797     t.replace(amp, QLatin1String("&"));
1798     return t;
1799 }
1800
1801 void Generator::setImageFileExtensions(const QStringList& extensions)
1802 {
1803     imgFileExts[format()] = extensions;
1804 }
1805
1806 void Generator::singularPlural(Text& text, const NodeList& nodes)
1807 {
1808     if (nodes.count() == 1)
1809         text << " is";
1810     else
1811         text << " are";
1812 }
1813
1814 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1815 {
1816     int skipAhead = 0;
1817     atom = atom->next();
1818     while (atom != 0 && atom->type() != type) {
1819         skipAhead++;
1820         atom = atom->next();
1821     }
1822     return skipAhead;
1823 }
1824
1825 /*!
1826   Resets the variables used during text output.
1827  */
1828 void Generator::initializeTextOutput()
1829 {
1830     inLink_ = false;
1831     inContents_ = false;
1832     inSectionHeading_ = false;
1833     inTableHeader_ = false;
1834     numTableRows_ = 0;
1835     threeColumnEnumValueTable_ = true;
1836     link_.clear();
1837     sectionNumber_.clear();
1838 }
1839
1840 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1841 {
1842     if (node->type() == Node::Function) {
1843         const FunctionNode *func = static_cast<const FunctionNode *>(node);
1844         if (func->overloadNumber() == 1) {
1845             QString alternateName;
1846             const FunctionNode *alternateFunc = 0;
1847
1848             if (func->name().startsWith("set") && func->name().size() >= 4) {
1849                 alternateName = func->name()[3].toLower();
1850                 alternateName += func->name().mid(4);
1851                 alternateFunc = func->parent()->findFunctionNode(alternateName);
1852
1853                 if (!alternateFunc) {
1854                     alternateName = "is" + func->name().mid(3);
1855                     alternateFunc = func->parent()->findFunctionNode(alternateName);
1856                     if (!alternateFunc) {
1857                         alternateName = "has" + func->name().mid(3);
1858                         alternateFunc = func->parent()->findFunctionNode(alternateName);
1859                     }
1860                 }
1861             }
1862             else if (!func->name().isEmpty()) {
1863                 alternateName = "set";
1864                 alternateName += func->name()[0].toUpper();
1865                 alternateName += func->name().mid(1);
1866                 alternateFunc = func->parent()->findFunctionNode(alternateName);
1867             }
1868
1869             if (alternateFunc && alternateFunc->access() != Node::Private) {
1870                 int i;
1871                 for (i = 0; i < alsoList.size(); ++i) {
1872                     if (alsoList.at(i).toString().contains(alternateName))
1873                         break;
1874                 }
1875
1876                 if (i == alsoList.size()) {
1877                     alternateName += "()";
1878
1879                     Text also;
1880                     also << Atom(Atom::Link, alternateName)
1881                          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1882                          << alternateName
1883                          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1884                     alsoList.prepend(also);
1885                 }
1886             }
1887         }
1888     }
1889 }
1890
1891 void Generator::terminate()
1892 {
1893     QList<Generator *>::ConstIterator g = generators.constBegin();
1894     while (g != generators.constEnd()) {
1895         if (outputFormats.contains((*g)->format()))
1896             (*g)->terminateGenerator();
1897         ++g;
1898     }
1899
1900     fmtLeftMaps.clear();
1901     fmtRightMaps.clear();
1902     imgFileExts.clear();
1903     imageFiles.clear();
1904     imageDirs.clear();
1905     outDir_.clear();
1906     QmlClassNode::terminate();
1907     ExampleNode::terminate();
1908 }
1909
1910 void Generator::terminateGenerator()
1911 {
1912 }
1913
1914 /*!
1915   Trims trailing whitespace off the \a string and returns
1916   the trimmed string.
1917  */
1918 QString Generator::trimmedTrailing(const QString& string)
1919 {
1920     QString trimmed = string;
1921     while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1922         trimmed.truncate(trimmed.length() - 1);
1923     return trimmed;
1924 }
1925
1926 QString Generator::typeString(const Node *node)
1927 {
1928     switch (node->type()) {
1929     case Node::Namespace:
1930         return "namespace";
1931     case Node::Class:
1932         return "class";
1933     case Node::Document:
1934     {
1935         switch (node->subType()) {
1936         case Node::QmlClass:
1937             return "type";
1938         case Node::QmlPropertyGroup:
1939             return "property group";
1940         case Node::QmlBasicType:
1941             return "type";
1942         default:
1943             return "documentation";
1944         }
1945     }
1946     case Node::Enum:
1947         return "enum";
1948     case Node::Typedef:
1949         return "typedef";
1950     case Node::Function:
1951         return "function";
1952     case Node::Property:
1953         return "property";
1954     default:
1955         return "documentation";
1956     }
1957 }
1958
1959 void Generator::unknownAtom(const Atom *atom)
1960 {
1961     Location::internalError(tr("unknown atom type '%1' in %2 generator")
1962                             .arg(atom->typeString()).arg(format()));
1963 }
1964
1965 QT_END_NAMESPACE