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