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