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