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