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