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