QDoc: Only print modulename::type on collision pages.
[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         // Only print modulename::type on collision pages.
626         if (!fn->qmlModuleIdentifier().isEmpty() && relative != 0 && relative->isCollisionNode())
627             return fn->qmlModuleIdentifier() + "::" + fn->title();
628
629         return fn->title();
630     }
631     else if (node->type() == Node::Class &&
632              !(static_cast<const ClassNode *>(node))->serviceName().isEmpty())
633         return (static_cast<const ClassNode *>(node))->serviceName();
634     else
635         return marker->plainFullName(node, relative);
636 }
637
638 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
639 {
640     QList<Text> alsoList = node->doc().alsoList();
641     supplementAlsoList(node, alsoList);
642
643     if (!alsoList.isEmpty()) {
644         Text text;
645         text << Atom::ParaLeft
646              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
647              << "See also "
648              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
649
650         for (int i = 0; i < alsoList.size(); ++i)
651             text << alsoList.at(i) << separator(i, alsoList.size());
652
653         text << Atom::ParaRight;
654         generateText(text, node, marker);
655     }
656 }
657
658 int Generator::generateAtom(const Atom * /* atom */,
659                             const Node * /* relative */,
660                             CodeMarker * /* marker */)
661 {
662     return 0;
663 }
664
665 const Atom *Generator::generateAtomList(const Atom *atom,
666                                         const Node *relative,
667                                         CodeMarker *marker,
668                                         bool generate,
669                                         int &numAtoms)
670 {
671     while (atom) {
672         if (atom->type() == Atom::FormatIf) {
673             int numAtoms0 = numAtoms;
674             bool rightFormat = canHandleFormat(atom->string());
675             atom = generateAtomList(atom->next(),
676                                     relative,
677                                     marker,
678                                     generate && rightFormat,
679                                     numAtoms);
680             if (!atom)
681                 return 0;
682
683             if (atom->type() == Atom::FormatElse) {
684                 ++numAtoms;
685                 atom = generateAtomList(atom->next(),
686                                         relative,
687                                         marker,
688                                         generate && !rightFormat,
689                                         numAtoms);
690                 if (!atom)
691                     return 0;
692             }
693
694             if (atom->type() == Atom::FormatEndif) {
695                 if (generate && numAtoms0 == numAtoms) {
696                     relative->location().warning(tr("Output format %1 not handled %2")
697                                                  .arg(format()).arg(outFileName()));
698                     Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
699                     generateAtomList(&unhandledFormatAtom,
700                                      relative,
701                                      marker,
702                                      generate,
703                                      numAtoms);
704                 }
705                 atom = atom->next();
706             }
707         }
708         else if (atom->type() == Atom::FormatElse ||
709                  atom->type() == Atom::FormatEndif) {
710             return atom;
711         }
712         else {
713             int n = 1;
714             if (generate) {
715                 n += generateAtom(atom, relative, marker);
716                 numAtoms += n;
717             }
718             while (n-- > 0)
719                 atom = atom->next();
720         }
721     }
722     return 0;
723 }
724
725 void Generator::generateBody(const Node *node, CodeMarker *marker)
726 {
727     bool quiet = false;
728
729     if (node->type() == Node::Fake) {
730         const FakeNode *fake = static_cast<const FakeNode *>(node);
731         if ((fake->subType() == Node::File) || (fake->subType() == Node::Image)) {
732             quiet = true;
733         }
734     }
735     if (node->doc().isEmpty()) {
736         if (!quiet && !node->isReimp()) { // ### might be unnecessary
737             node->location().warning(tr("No documentation for '%1'")
738                                      .arg(marker->plainFullName(node)));
739         }
740     }
741     else {
742         if (node->type() == Node::Function) {
743             const FunctionNode *func = static_cast<const FunctionNode *>(node);
744             if (func->reimplementedFrom() != 0)
745                 generateReimplementedFrom(func, marker);
746         }
747
748         if (!generateText(node->doc().body(), node, marker)) {
749             if (node->isReimp())
750                 return;
751         }
752
753         if (node->type() == Node::Enum) {
754             const EnumNode *enume = (const EnumNode *) node;
755
756             QSet<QString> definedItems;
757             QList<EnumItem>::ConstIterator it = enume->items().constBegin();
758             while (it != enume->items().constEnd()) {
759                 definedItems.insert((*it).name());
760                 ++it;
761             }
762
763             QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
764             QSet<QString> allItems = definedItems + documentedItems;
765             if (allItems.count() > definedItems.count() ||
766                     allItems.count() > documentedItems.count()) {
767                 QSet<QString>::ConstIterator a = allItems.constBegin();
768                 while (a != allItems.constEnd()) {
769                     if (!definedItems.contains(*a)) {
770                         QString details;
771                         QString best = nearestName(*a, definedItems);
772                         if (!best.isEmpty() && !documentedItems.contains(best))
773                             details = tr("Maybe you meant '%1'?").arg(best);
774
775                         node->doc().location().warning(
776                                     tr("No such enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
777                                     details);
778                         if (*a == "Void")
779                             qDebug() << "VOID:" << node->name() << definedItems;
780                     }
781                     else if (!documentedItems.contains(*a)) {
782                         node->doc().location().warning(
783                                     tr("Undocumented enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)));
784                     }
785                     ++a;
786                 }
787             }
788         }
789         else if (node->type() == Node::Function) {
790             const FunctionNode *func = static_cast<const FunctionNode *>(node);
791             QSet<QString> definedParams;
792             QList<Parameter>::ConstIterator p = func->parameters().constBegin();
793             while (p != func->parameters().constEnd()) {
794                 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
795                         && func->name() != QLatin1String("operator++")
796                         && func->name() != QLatin1String("operator--")) {
797                     node->doc().location().warning(tr("Missing parameter name"));
798                 }
799                 else {
800                     definedParams.insert((*p).name());
801                 }
802                 ++p;
803             }
804
805             QSet<QString> documentedParams = func->doc().parameterNames();
806             QSet<QString> allParams = definedParams + documentedParams;
807             if (allParams.count() > definedParams.count()
808                     || allParams.count() > documentedParams.count()) {
809                 QSet<QString>::ConstIterator a = allParams.constBegin();
810                 while (a != allParams.constEnd()) {
811                     if (!definedParams.contains(*a)) {
812                         QString details;
813                         QString best = nearestName(*a, definedParams);
814                         if (!best.isEmpty())
815                             details = tr("Maybe you meant '%1'?").arg(best);
816
817                         node->doc().location().warning(
818                                     tr("No such parameter '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
819                                     details);
820                     }
821                     else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
822                         bool needWarning = (func->status() > Node::Obsolete);
823                         if (func->overloadNumber() > 1) {
824                             FunctionNode *primaryFunc =
825                                     func->parent()->findFunctionNode(func->name());
826                             if (primaryFunc) {
827                                 foreach (const Parameter &param,
828                                          primaryFunc->parameters()) {
829                                     if (param.name() == *a) {
830                                         needWarning = false;
831                                         break;
832                                     }
833                                 }
834                             }
835                         }
836                         if (needWarning && !func->isReimp())
837                             node->doc().location().warning(
838                                         tr("Undocumented parameter '%1' in %2")
839                                         .arg(*a).arg(marker->plainFullName(node)));
840                     }
841                     ++a;
842                 }
843             }
844             /*
845               Something like this return value check should
846               be implemented at some point.
847             */
848             if (func->status() > Node::Obsolete && func->returnType() == "bool"
849                     && func->reimplementedFrom() == 0 && !func->isOverload()) {
850                 QString body = func->doc().body().toString();
851                 if (!body.contains("return", Qt::CaseInsensitive))
852                     node->doc().location().warning(tr("Undocumented return value"));
853             }
854         }
855     }
856
857     if (node->type() == Node::Fake) {
858         const FakeNode *fake = static_cast<const FakeNode *>(node);
859         if (fake->subType() == Node::Example) {
860             generateExampleFiles(fake, marker);
861         }
862         else if (fake->subType() == Node::File) {
863             Text text;
864             Quoter quoter;
865             Doc::quoteFromFile(fake->doc().location(), quoter, fake->name());
866             QString code = quoter.quoteTo(fake->location(), QString(), QString());
867             CodeMarker *codeMarker = CodeMarker::markerForFileName(fake->name());
868             text << Atom(codeMarker->atomType(), code);
869             generateText(text, fake, codeMarker);
870         }
871     }
872 }
873
874 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
875 {
876 }
877
878 void Generator::generateExampleFiles(const FakeNode *fake, CodeMarker *marker)
879 {
880     if (fake->childNodes().isEmpty())
881         return;
882     generateFileList(fake, marker, Node::File, QString("Files:"));
883     generateFileList(fake, marker, Node::Image, QString("Images:"));
884 }
885
886 void Generator::generateFakeNode(FakeNode* /* fake */, CodeMarker* /* marker */)
887 {
888 }
889
890 /*!
891   This function is called when the documentation for an
892   example is being formatted. It outputs the list of source
893   files comprising the example, and the list of images used
894   by the example. The images are copied into a subtree of
895   \c{...doc/html/images/used-in-examples/...}
896  */
897 void Generator::generateFileList(const FakeNode* fake,
898                                  CodeMarker* marker,
899                                  Node::SubType subtype,
900                                  const QString& tag)
901 {
902     int count = 0;
903     Text text;
904     OpenedList openedList(OpenedList::Bullet);
905
906     text << Atom::ParaLeft << tag << Atom::ParaRight
907          << Atom(Atom::ListLeft, openedList.styleString());
908
909     foreach (const Node* child, fake->childNodes()) {
910         if (child->subType() == subtype) {
911             ++count;
912             QString file = child->name();
913             if (subtype == Node::Image) {
914                 if (!file.isEmpty()) {
915                     QDir dirInfo;
916                     QString userFriendlyFilePath;
917                     QString srcPath = Config::findFile(fake->location(),
918                                                        QStringList(),
919                                                        exampleDirs,
920                                                        file,
921                                                        exampleImgExts,
922                                                        userFriendlyFilePath);
923                     userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
924
925                     QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
926                     if (!dirInfo.mkpath(imgOutDir))
927                         fake->location().fatal(tr("Cannot create output directory '%1'")
928                                                .arg(imgOutDir));
929
930                     QString imgOutName = Config::copyFile(fake->location(),
931                                                           srcPath,
932                                                           file,
933                                                           imgOutDir);
934                 }
935
936             }
937
938             openedList.next();
939             text << Atom(Atom::ListItemNumber, openedList.numberString())
940                  << Atom(Atom::ListItemLeft, openedList.styleString())
941                  << Atom::ParaLeft
942                  << Atom(Atom::Link, file)
943                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
944                  << file
945                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
946                  << Atom::ParaRight
947                  << Atom(Atom::ListItemRight, openedList.styleString());
948         }
949     }
950     text << Atom(Atom::ListRight, openedList.styleString());
951     if (count > 0)
952         generateText(text, fake, marker);
953 }
954
955 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
956 {
957     if (!classe->derivedClasses().isEmpty()) {
958         Text text;
959         text << Atom::ParaLeft
960              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
961              << "Inherited by: "
962              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
963
964         appendSortedNames(text, classe, classe->derivedClasses(), marker);
965         text << Atom::ParaRight;
966         generateText(text, classe, marker);
967     }
968 }
969
970 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
971 {
972     QList<RelatedClass>::ConstIterator r;
973     int index;
974
975     if (!classe->baseClasses().isEmpty()) {
976         Text text;
977         text << Atom::ParaLeft
978              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
979              << "Inherits: "
980              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
981
982         r = classe->baseClasses().constBegin();
983         index = 0;
984         while (r != classe->baseClasses().constEnd()) {
985             text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
986                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
987                  << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
988                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
989
990             if ((*r).access == Node::Protected) {
991                 text << " (protected)";
992             }
993             else if ((*r).access == Node::Private) {
994                 text << " (private)";
995             }
996             text << separator(index++, classe->baseClasses().count());
997             ++r;
998         }
999         text << Atom::ParaRight;
1000         generateText(text, classe, marker);
1001     }
1002 }
1003
1004 /*!
1005   Recursive writing of HTML files from the root \a node.
1006
1007   \note NameCollisionNodes are skipped here and processed
1008   later. See HtmlGenerator::generateCollisionPages() for
1009   more on this.
1010  */
1011 void Generator::generateInnerNode(InnerNode* node)
1012 {
1013     if (!node->url().isNull())
1014         return;
1015
1016     if (node->type() == Node::Fake) {
1017         FakeNode* fakeNode = static_cast<FakeNode*>(node);
1018         if (fakeNode->subType() == Node::ExternalPage)
1019             return;
1020         if (fakeNode->subType() == Node::Image)
1021             return;
1022         if (fakeNode->subType() == Node::QmlPropertyGroup)
1023             return;
1024         if (fakeNode->subType() == Node::Page) {
1025             if (node->count() > 0)
1026                 qDebug("PAGE %s HAS CHILDREN", qPrintable(fakeNode->title()));
1027         }
1028     }
1029
1030     /*
1031       Obtain a code marker for the source file.
1032      */
1033     CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
1034
1035     if (node->parent() != 0) {
1036         /*
1037           Skip name collision nodes here and process them
1038           later in generateCollisionPages(). Each one is
1039           appended to a list for later.
1040          */
1041         if ((node->type() == Node::Fake) && (node->subType() == Node::Collision)) {
1042             NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
1043             collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
1044         }
1045         else {
1046             beginSubPage(node, fileName(node));
1047             if (node->type() == Node::Namespace || node->type() == Node::Class) {
1048                 generateClassLikeNode(node, marker);
1049             }
1050             else if (node->type() == Node::Fake) {
1051                 generateFakeNode(static_cast<FakeNode*>(node), marker);
1052             }
1053             endSubPage();
1054         }
1055     }
1056
1057     NodeList::ConstIterator c = node->childNodes().constBegin();
1058     while (c != node->childNodes().constEnd()) {
1059         if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
1060             generateInnerNode((InnerNode*)*c);
1061         }
1062         ++c;
1063     }
1064 }
1065
1066 /*!
1067   Generate a list of maintainers in the output
1068  */
1069 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
1070 {
1071     QStringList sl = getMetadataElements(node,"maintainer");
1072
1073     if (!sl.isEmpty()) {
1074         Text text;
1075         text << Atom::ParaLeft
1076              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1077              << "Maintained by: "
1078              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
1079
1080         for (int i = 0; i < sl.size(); ++i)
1081             text << sl.at(i) << separator(i, sl.size());
1082
1083         text << Atom::ParaRight;
1084         generateText(text, node, marker);
1085     }
1086 }
1087
1088 /*!
1089   Output the "Inherit by" list for the QML element,
1090   if it is inherited by any other elements.
1091  */
1092 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
1093                                               CodeMarker* marker)
1094 {
1095     if (qcn) {
1096         NodeList subs;
1097         QmlClassNode::subclasses(qcn->name(),subs);
1098         if (!subs.isEmpty()) {
1099             Text text;
1100             text << Atom::ParaLeft << "Inherited by ";
1101             appendSortedQmlNames(text,qcn,subs,marker);
1102             text << Atom::ParaRight;
1103             generateText(text, qcn, marker);
1104         }
1105     }
1106 }
1107
1108 /*!
1109  */
1110 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
1111 {
1112     // stub.
1113 }
1114
1115 /*!
1116   Extract sections of markup text surrounded by \e qmltext
1117   and \e endqmltext and output them.
1118  */
1119 bool Generator::generateQmlText(const Text& text,
1120                                 const Node *relative,
1121                                 CodeMarker *marker,
1122                                 const QString& /* qmlName */ )
1123 {
1124     const Atom* atom = text.firstAtom();
1125     bool result = false;
1126
1127     if (atom != 0) {
1128         startText(relative, marker);
1129         while (atom) {
1130             if (atom->type() != Atom::QmlText)
1131                 atom = atom->next();
1132             else {
1133                 atom = atom->next();
1134                 while (atom && (atom->type() != Atom::EndQmlText)) {
1135                     int n = 1 + generateAtom(atom, relative, marker);
1136                     while (n-- > 0)
1137                         atom = atom->next();
1138                 }
1139             }
1140         }
1141         endText(relative, marker);
1142         result = true;
1143     }
1144     return result;
1145 }
1146
1147 void Generator::generateReimplementedFrom(const FunctionNode *func,
1148                                           CodeMarker *marker)
1149 {
1150     if (func->reimplementedFrom() != 0) {
1151         const FunctionNode *from = func->reimplementedFrom();
1152         if (from->access() != Node::Private &&
1153                 from->parent()->access() != Node::Private) {
1154             Text text;
1155             text << Atom::ParaLeft << "Reimplemented from ";
1156             QString fullName =  from->parent()->name() + "::" + from->name() + "()";
1157             appendFullName(text, from->parent(), fullName, from);
1158             text << "." << Atom::ParaRight;
1159             generateText(text, func, marker);
1160         }
1161     }
1162 }
1163
1164 void Generator::generateSince(const Node *node, CodeMarker *marker)
1165 {
1166     if (!node->since().isEmpty()) {
1167         Text text;
1168         text << Atom::ParaLeft
1169              << "This "
1170              << typeString(node);
1171         if (node->type() == Node::Enum)
1172             text << " was introduced or modified in ";
1173         else
1174             text << " was introduced in ";
1175
1176         QStringList since = node->since().split(QLatin1Char(' '));
1177         if (since.count() == 1) {
1178             // Handle legacy use of \since <version>.
1179             if (project.isEmpty())
1180                 text << "version";
1181             else
1182                 text << project;
1183             text << " " << since[0];
1184         } else {
1185             // Reconstruct the <project> <version> string.
1186             text << " " << since.join(" ");
1187         }
1188
1189         text << "." << Atom::ParaRight;
1190         generateText(text, node, marker);
1191     }
1192 }
1193
1194 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1195 {
1196     Text text;
1197
1198     switch (node->status()) {
1199     case Node::Commendable:
1200     case Node::Main:
1201         break;
1202     case Node::Preliminary:
1203         text << Atom::ParaLeft
1204              << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1205              << "This "
1206              << typeString(node)
1207              << " is under development and is subject to change."
1208              << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1209              << Atom::ParaRight;
1210         break;
1211     case Node::Deprecated:
1212         text << Atom::ParaLeft;
1213         if (node->isInnerNode())
1214             text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1215         text << "This " << typeString(node) << " is deprecated.";
1216         if (node->isInnerNode())
1217             text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1218         text << Atom::ParaRight;
1219         break;
1220     case Node::Obsolete:
1221         text << Atom::ParaLeft;
1222         if (node->isInnerNode())
1223             text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1224         text << "This " << typeString(node) << " is obsolete.";
1225         if (node->isInnerNode())
1226             text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1227         text << " It is provided to keep old source code working. "
1228              << "We strongly advise against "
1229              << "using it in new code." << Atom::ParaRight;
1230         break;
1231     case Node::Compat:
1232         // reimplemented in HtmlGenerator subclass
1233         if (node->isInnerNode()) {
1234             text << Atom::ParaLeft
1235                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1236                  << "This "
1237                  << typeString(node)
1238                  << " is part of the Qt compatibility layer."
1239                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1240                  << " It is provided to keep old source code working. "
1241                  << "We strongly advise against using it in new code."
1242                  << Atom::ParaRight;
1243         }
1244         break;
1245     case Node::Internal:
1246     default:
1247         break;
1248     }
1249     generateText(text, node, marker);
1250 }
1251
1252 bool Generator::generateText(const Text& text,
1253                              const Node *relative,
1254                              CodeMarker *marker)
1255 {
1256     bool result = false;
1257     if (text.firstAtom() != 0) {
1258         int numAtoms = 0;
1259         startText(relative, marker);
1260         generateAtomList(text.firstAtom(),
1261                          relative,
1262                          marker,
1263                          true,
1264                          numAtoms);
1265         endText(relative, marker);
1266         result = true;
1267     }
1268     return result;
1269 }
1270
1271 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1272 {
1273     Text text;
1274     Node::ThreadSafeness threadSafeness = node->threadSafeness();
1275
1276     Text rlink;
1277     rlink << Atom(Atom::Link,"reentrant")
1278           << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1279           << "reentrant"
1280           << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1281
1282     Text tlink;
1283     tlink << Atom(Atom::Link,"thread-safe")
1284           << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1285           << "thread-safe"
1286           << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1287
1288     switch (threadSafeness) {
1289     case Node::UnspecifiedSafeness:
1290         break;
1291     case Node::NonReentrant:
1292         text << Atom::ParaLeft
1293              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1294              << "Warning:"
1295              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1296              << " This "
1297              << typeString(node)
1298              << " is not "
1299              << rlink
1300              << "."
1301              << Atom::ParaRight;
1302         break;
1303     case Node::Reentrant:
1304     case Node::ThreadSafe:
1305         text << Atom::ParaLeft
1306              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1307              << "Note:"
1308              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1309              << " ";
1310
1311         if (node->isInnerNode()) {
1312             const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1313             text << "All functions in this "
1314                  << typeString(node)
1315                  << " are ";
1316             if (threadSafeness == Node::ThreadSafe)
1317                 text << tlink;
1318             else
1319                 text << rlink;
1320
1321             bool exceptions = false;
1322             NodeList reentrant;
1323             NodeList threadsafe;
1324             NodeList nonreentrant;
1325             NodeList::ConstIterator c = innerNode->childNodes().constBegin();
1326             while (c != innerNode->childNodes().constEnd()) {
1327
1328                 if ((*c)->status() != Node::Obsolete){
1329                     switch ((*c)->threadSafeness()) {
1330                     case Node::Reentrant:
1331                         reentrant.append(*c);
1332                         if (threadSafeness == Node::ThreadSafe)
1333                             exceptions = true;
1334                         break;
1335                     case Node::ThreadSafe:
1336                         threadsafe.append(*c);
1337                         if (threadSafeness == Node::Reentrant)
1338                             exceptions = true;
1339                         break;
1340                     case Node::NonReentrant:
1341                         nonreentrant.append(*c);
1342                         exceptions = true;
1343                         break;
1344                     default:
1345                         break;
1346                     }
1347                 }
1348                 ++c;
1349             }
1350             if (!exceptions)
1351                 text << ".";
1352             else if (threadSafeness == Node::Reentrant) {
1353                 if (nonreentrant.isEmpty()) {
1354                     if (!threadsafe.isEmpty()) {
1355                         text << ", but ";
1356                         appendFullNames(text,threadsafe,innerNode,marker);
1357                         singularPlural(text,threadsafe);
1358                         text << " also " << tlink << ".";
1359                     }
1360                     else
1361                         text << ".";
1362                 }
1363                 else {
1364                     text << ", except for ";
1365                     appendFullNames(text,nonreentrant,innerNode,marker);
1366                     text << ", which";
1367                     singularPlural(text,nonreentrant);
1368                     text << " nonreentrant.";
1369                     if (!threadsafe.isEmpty()) {
1370                         text << " ";
1371                         appendFullNames(text,threadsafe,innerNode,marker);
1372                         singularPlural(text,threadsafe);
1373                         text << " " << tlink << ".";
1374                     }
1375                 }
1376             }
1377             else { // thread-safe
1378                 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1379                     text << ", except for ";
1380                     if (!reentrant.isEmpty()) {
1381                         appendFullNames(text,reentrant,innerNode,marker);
1382                         text << ", which";
1383                         singularPlural(text,reentrant);
1384                         text << " only " << rlink;
1385                         if (!nonreentrant.isEmpty())
1386                             text << ", and ";
1387                     }
1388                     if (!nonreentrant.isEmpty()) {
1389                         appendFullNames(text,nonreentrant,innerNode,marker);
1390                         text << ", which";
1391                         singularPlural(text,nonreentrant);
1392                         text << " nonreentrant.";
1393                     }
1394                     text << ".";
1395                 }
1396             }
1397         }
1398         else {
1399             text << "This " << typeString(node) << " is ";
1400             if (threadSafeness == Node::ThreadSafe)
1401                 text << tlink;
1402             else
1403                 text << rlink;
1404             text << ".";
1405         }
1406         text << Atom::ParaRight;
1407     }
1408     generateText(text,node,marker);
1409 }
1410
1411 /*!
1412   This function is recursive.
1413  */
1414 void Generator::generateTree(Tree *tree)
1415 {
1416     tree_ = tree;
1417     generateInnerNode(tree->root());
1418 }
1419
1420 Generator *Generator::generatorForFormat(const QString& format)
1421 {
1422     QList<Generator *>::ConstIterator g = generators.constBegin();
1423     while (g != generators.constEnd()) {
1424         if ((*g)->format() == format)
1425             return *g;
1426         ++g;
1427     }
1428     return 0;
1429 }
1430
1431 /*!
1432   This function can be called if getLink() returns an empty
1433   string. It tests the \a atom string to see if it is a link
1434   of the form <element> :: <name>, where <element> is a QML
1435   element or component without a module qualifier. If so, it
1436   constructs a link to the <name> clause on the disambiguation
1437   page for <element> and returns that link string. It also
1438   adds the <name> as a target in the NameCollisionNode for
1439   <element>. These clauses are then constructed when the
1440   disambiguation page is actually generated.
1441  */
1442 QString Generator::getCollisionLink(const Atom* atom)
1443 {
1444     QString link;
1445     if (!atom->string().contains("::"))
1446         return link;
1447     QStringList path = atom->string().split("::");
1448     NameCollisionNode* ncn = tree_->findCollisionNode(path[0]);
1449     if (ncn) {
1450         QString label;
1451         if (atom->next() && atom->next()->next()) {
1452             if (atom->next()->type() == Atom::FormattingLeft &&
1453                     atom->next()->next()->type() == Atom::String)
1454                 label = atom->next()->next()->string();
1455         }
1456         ncn->addLinkTarget(path[1],label);
1457         link = fileName(ncn);
1458         link += QLatin1Char('#');
1459         link += Doc::canonicalTitle(path[1]);
1460     }
1461     return link;
1462 }
1463
1464
1465 /*!
1466   Looks up the tag \a t in the map of metadata values for the
1467   current topic in \a inner. If a value for the tag is found,
1468   the value is returned.
1469
1470   \note If \a t is found in the metadata map, it is erased.
1471   i.e. Once you call this function for a particular \a t,
1472   you consume \a t.
1473  */
1474 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1475 {
1476     QString s;
1477     QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1478     QStringMultiMap::iterator i = metaTagMap.find(t);
1479     if (i != metaTagMap.end()) {
1480         s = i.value();
1481         metaTagMap.erase(i);
1482     }
1483     return s;
1484 }
1485
1486 /*!
1487   Looks up the tag \a t in the map of metadata values for the
1488   current topic in \a inner. If values for the tag are found,
1489   they are returned in a string list.
1490
1491   \note If \a t is found in the metadata map, all the pairs
1492   having the key \a t are erased. i.e. Once you call this
1493   function for a particular \a t, you consume \a t.
1494  */
1495 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1496 {
1497     QStringList s;
1498     QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1499     s = metaTagMap.values(t);
1500     if (!s.isEmpty())
1501         metaTagMap.remove(t);
1502     return s;
1503 }
1504
1505 /*!
1506   Returns a relative path name for an image.
1507  */
1508 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1509 {
1510     QString userFriendlyFilePath;
1511     QString filePath = Config::findFile(
1512                 relative->doc().location(), imageFiles, imageDirs, fileBase,
1513                 imgFileExts[format()], userFriendlyFilePath);
1514
1515     if (filePath.isEmpty())
1516         return QString();
1517
1518     QString path = Config::copyFile(relative->doc().location(),
1519                                     filePath,
1520                                     userFriendlyFilePath,
1521                                     outputDir() + QLatin1String("/images"));
1522     QString images = "images";
1523     if (path[0] != '/')
1524         images.append(QLatin1Char('/'));
1525     return images + path;
1526 }
1527
1528 QString Generator::indent(int level, const QString& markedCode)
1529 {
1530     if (level == 0)
1531         return markedCode;
1532
1533     QString t;
1534     int column = 0;
1535
1536     int i = 0;
1537     while (i < (int) markedCode.length()) {
1538         if (markedCode.at(i) == QLatin1Char('\n')) {
1539             column = 0;
1540         }
1541         else {
1542             if (column == 0) {
1543                 for (int j = 0; j < level; j++)
1544                     t += QLatin1Char(' ');
1545             }
1546             column++;
1547         }
1548         t += markedCode.at(i++);
1549     }
1550     return t;
1551 }
1552
1553 void Generator::initialize(const Config &config)
1554 {
1555     outputFormats = config.getOutputFormats();
1556     if (!outputFormats.isEmpty()) {
1557         outDir_ = config.getOutputDir();
1558         baseDir_ = config.getString(CONFIG_BASEDIR);
1559         if (!baseDir_.isEmpty())
1560             config.location().warning(tr("\"basedir\" specified in config file. "
1561                                          "All output will be in module directories of the output directory"));
1562         if (outDir_.isEmpty())
1563             config.lastLocation().fatal(tr("No output directory specified in configuration file or on the command line"));
1564
1565         QDir dirInfo;
1566         if (dirInfo.exists(outDir_)) {
1567             if (!Config::removeDirContents(outDir_))
1568                 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1569         }
1570         else {
1571             if (!dirInfo.mkpath(outDir_))
1572                 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1573         }
1574
1575         if (!dirInfo.mkdir(outDir_ + "/images"))
1576             config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1577                                         .arg(outDir_ + "/images"));
1578         if (!dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1579             config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1580                                         .arg(outDir_ + "/images/used-in-examples"));
1581         if (!dirInfo.mkdir(outDir_ + "/scripts"))
1582             config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1583                                         .arg(outDir_ + "/scripts"));
1584         if (!dirInfo.mkdir(outDir_ + "/style"))
1585             config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1586                                         .arg(outDir_ + "/style"));
1587     }
1588
1589     imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1590     imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1591     scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1592     scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1593     styleFiles = config.getCleanPathList(CONFIG_STYLES);
1594     styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1595     exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1596     exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot +
1597                                           CONFIG_IMAGEEXTENSIONS);
1598
1599     QString imagesDotFileExtensions =
1600             CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1601     QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1602     QSet<QString>::ConstIterator f = formats.constBegin();
1603     while (f != formats.constEnd()) {
1604         imgFileExts[*f] = config.getStringList(imagesDotFileExtensions +
1605                                                Config::dot + *f);
1606         ++f;
1607     }
1608
1609     QList<Generator *>::ConstIterator g = generators.constBegin();
1610     while (g != generators.constEnd()) {
1611         if (outputFormats.contains((*g)->format())) {
1612             currentGenerator_ = (*g);
1613             (*g)->initializeGenerator(config);
1614             QStringList extraImages =
1615                     config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
1616             QStringList::ConstIterator e = extraImages.constBegin();
1617             while (e != extraImages.constEnd()) {
1618                 QString userFriendlyFilePath;
1619                 QString filePath = Config::findFile(config.lastLocation(),
1620                                                     imageFiles,
1621                                                     imageDirs,
1622                                                     *e,
1623                                                     imgFileExts[(*g)->format()],
1624                                                     userFriendlyFilePath);
1625                 if (!filePath.isEmpty())
1626                     Config::copyFile(config.lastLocation(),
1627                                      filePath,
1628                                      userFriendlyFilePath,
1629                                      (*g)->outputDir() +
1630                                      "/images");
1631                 ++e;
1632             }
1633
1634             // Documentation template handling
1635             QString templateDir = config.getString(
1636                         (*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
1637
1638             if (!templateDir.isEmpty()) {
1639                 QStringList noExts;
1640                 QStringList searchDirs = QStringList() << templateDir;
1641                 QStringList scripts =
1642                         config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1643                 e = scripts.constBegin();
1644                 while (e != scripts.constEnd()) {
1645                     QString userFriendlyFilePath;
1646                     QString filePath = Config::findFile(config.lastLocation(),
1647                                                         scriptFiles,
1648                                                         searchDirs,
1649                                                         *e,
1650                                                         noExts,
1651                                                         userFriendlyFilePath);
1652                     if (!filePath.isEmpty())
1653                         Config::copyFile(config.lastLocation(),
1654                                          filePath,
1655                                          userFriendlyFilePath,
1656                                          (*g)->outputDir() +
1657                                          "/scripts");
1658                     ++e;
1659                 }
1660
1661                 if (!Config::installDir.isEmpty()) {
1662                     searchDirs.append(Config::installDir);
1663                 }
1664
1665                 QStringList styles =
1666                         config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1667                 e = styles.constBegin();
1668                 while (e != styles.constEnd()) {
1669                     QString userFriendlyFilePath;
1670                     QString filePath = Config::findFile(config.lastLocation(),
1671                                                         styleFiles,
1672                                                         searchDirs,
1673                                                         *e,
1674                                                         noExts,
1675                                                         userFriendlyFilePath);
1676                     if (!filePath.isEmpty())
1677                         Config::copyFile(config.lastLocation(),
1678                                          filePath,
1679                                          userFriendlyFilePath,
1680                                          (*g)->outputDir() +
1681                                          "/style");
1682                     ++e;
1683                 }
1684             }
1685         }
1686         ++g;
1687     }
1688
1689     QRegExp secondParamAndAbove("[\2-\7]");
1690     QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1691     QSet<QString>::ConstIterator n = formattingNames.constBegin();
1692     while (n != formattingNames.constEnd()) {
1693         QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1694
1695         QSet<QString> formats = config.subVars(formattingDotName);
1696         QSet<QString>::ConstIterator f = formats.constBegin();
1697         while (f != formats.constEnd()) {
1698             QString def = config.getString(formattingDotName +
1699                                            Config::dot + *f);
1700             if (!def.isEmpty()) {
1701                 int numParams = Config::numParams(def);
1702                 int numOccs = def.count("\1");
1703
1704                 if (numParams != 1) {
1705                     config.lastLocation().warning(tr("Formatting '%1' must "
1706                                                      "have exactly one "
1707                                                      "parameter (found %2)")
1708                                                   .arg(*n).arg(numParams));
1709                 }
1710                 else if (numOccs > 1) {
1711                     config.lastLocation().fatal(tr("Formatting '%1' must "
1712                                                    "contain exactly one "
1713                                                    "occurrence of '\\1' "
1714                                                    "(found %2)")
1715                                                 .arg(*n).arg(numOccs));
1716                 }
1717                 else {
1718                     int paramPos = def.indexOf("\1");
1719                     fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1720                     fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1721                 }
1722             }
1723             ++f;
1724         }
1725         ++n;
1726     }
1727
1728     project = config.getString(CONFIG_PROJECT);
1729
1730     QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1731     if (!prefixes.isEmpty()) {
1732         foreach (QString prefix, prefixes)
1733             outputPrefixes[prefix] = config.getString(
1734                         CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1735     } else
1736         outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1737 }
1738
1739 void Generator::initializeGenerator(const Config & /* config */)
1740 {
1741 }
1742
1743 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1744 {
1745     return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1746 }
1747
1748 /*!
1749   Used for writing to the current output stream. Returns a
1750   reference to the current output stream, which is then used
1751   with the \c {<<} operator for writing.
1752  */
1753 QTextStream &Generator::out()
1754 {
1755     return *outStreamStack.top();
1756 }
1757
1758 QString Generator::outFileName()
1759 {
1760     return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1761 }
1762
1763 QString Generator::outputPrefix(const QString &nodeType)
1764 {
1765     return outputPrefixes[nodeType];
1766 }
1767
1768 bool Generator::parseArg(const QString& src,
1769                              const QString& tag,
1770                              int* pos,
1771                              int n,
1772                              QStringRef* contents,
1773                              QStringRef* par1,
1774                              bool debug)
1775 {
1776 #define SKIP_CHAR(c) \
1777     if (debug) \
1778     qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1779     if (i >= n || src[i] != c) { \
1780     if (debug) \
1781     qDebug() << " char '" << c << "' not found"; \
1782     return false; \
1783 } \
1784     ++i;
1785
1786
1787 #define SKIP_SPACE \
1788     while (i < n && src[i] == ' ') \
1789     ++i;
1790
1791     int i = *pos;
1792     int j = i;
1793
1794     // assume "<@" has been parsed outside
1795     //SKIP_CHAR('<');
1796     //SKIP_CHAR('@');
1797
1798     if (tag != QStringRef(&src, i, tag.length())) {
1799         if (0 && debug)
1800             qDebug() << "tag " << tag << " not found at " << i;
1801         return false;
1802     }
1803
1804     if (debug)
1805         qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1806
1807     // skip tag
1808     i += tag.length();
1809
1810     // parse stuff like:  linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1811     if (par1) {
1812         SKIP_SPACE;
1813         // read parameter name
1814         j = i;
1815         while (i < n && src[i].isLetter())
1816             ++i;
1817         if (src[i] == '=') {
1818             if (debug)
1819                 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1820             SKIP_CHAR('=');
1821             SKIP_CHAR('"');
1822             // skip parameter name
1823             j = i;
1824             while (i < n && src[i] != '"')
1825                 ++i;
1826             *par1 = QStringRef(&src, j, i - j);
1827             SKIP_CHAR('"');
1828             SKIP_SPACE;
1829         } else {
1830             if (debug)
1831                 qDebug() << "no optional parameter found";
1832         }
1833     }
1834     SKIP_SPACE;
1835     SKIP_CHAR('>');
1836
1837     // find contents up to closing "</@tag>
1838     j = i;
1839     for (; true; ++i) {
1840         if (i + 4 + tag.length() > n)
1841             return false;
1842         if (src[i] != '<')
1843             continue;
1844         if (src[i + 1] != '/')
1845             continue;
1846         if (src[i + 2] != '@')
1847             continue;
1848         if (tag != QStringRef(&src, i + 3, tag.length()))
1849             continue;
1850         if (src[i + 3 + tag.length()] != '>')
1851             continue;
1852         break;
1853     }
1854
1855     *contents = QStringRef(&src, j, i - j);
1856
1857     i += tag.length() + 4;
1858
1859     *pos = i;
1860     if (debug)
1861         qDebug() << " tag " << tag << " found: pos now: " << i;
1862     return true;
1863 #undef SKIP_CHAR
1864 }
1865
1866 QString Generator::plainCode(const QString& markedCode)
1867 {
1868     QString t = markedCode;
1869     t.replace(tag, QString());
1870     t.replace(quot, QLatin1String("\""));
1871     t.replace(gt, QLatin1String(">"));
1872     t.replace(lt, QLatin1String("<"));
1873     t.replace(amp, QLatin1String("&"));
1874     return t;
1875 }
1876
1877 void Generator::setImageFileExtensions(const QStringList& extensions)
1878 {
1879     imgFileExts[format()] = extensions;
1880 }
1881
1882 void Generator::singularPlural(Text& text, const NodeList& nodes)
1883 {
1884     if (nodes.count() == 1)
1885         text << " is";
1886     else
1887         text << " are";
1888 }
1889
1890 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1891 {
1892     int skipAhead = 0;
1893     atom = atom->next();
1894     while (atom != 0 && atom->type() != type) {
1895         skipAhead++;
1896         atom = atom->next();
1897     }
1898     return skipAhead;
1899 }
1900
1901 void Generator::startText(const Node * /* relative */,
1902                           CodeMarker * /* marker */)
1903 {
1904 }
1905
1906 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1907 {
1908     if (node->type() == Node::Function) {
1909         const FunctionNode *func = static_cast<const FunctionNode *>(node);
1910         if (func->overloadNumber() == 1) {
1911             QString alternateName;
1912             const FunctionNode *alternateFunc = 0;
1913
1914             if (func->name().startsWith("set") && func->name().size() >= 4) {
1915                 alternateName = func->name()[3].toLower();
1916                 alternateName += func->name().mid(4);
1917                 alternateFunc = func->parent()->findFunctionNode(alternateName);
1918
1919                 if (!alternateFunc) {
1920                     alternateName = "is" + func->name().mid(3);
1921                     alternateFunc = func->parent()->findFunctionNode(alternateName);
1922                     if (!alternateFunc) {
1923                         alternateName = "has" + func->name().mid(3);
1924                         alternateFunc = func->parent()->findFunctionNode(alternateName);
1925                     }
1926                 }
1927             }
1928             else if (!func->name().isEmpty()) {
1929                 alternateName = "set";
1930                 alternateName += func->name()[0].toUpper();
1931                 alternateName += func->name().mid(1);
1932                 alternateFunc = func->parent()->findFunctionNode(alternateName);
1933             }
1934
1935             if (alternateFunc && alternateFunc->access() != Node::Private) {
1936                 int i;
1937                 for (i = 0; i < alsoList.size(); ++i) {
1938                     if (alsoList.at(i).toString().contains(alternateName))
1939                         break;
1940                 }
1941
1942                 if (i == alsoList.size()) {
1943                     alternateName += "()";
1944
1945                     Text also;
1946                     also << Atom(Atom::Link, alternateName)
1947                          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1948                          << alternateName
1949                          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1950                     alsoList.prepend(also);
1951                 }
1952             }
1953         }
1954     }
1955 }
1956
1957 void Generator::terminate()
1958 {
1959     QList<Generator *>::ConstIterator g = generators.constBegin();
1960     while (g != generators.constEnd()) {
1961         if (outputFormats.contains((*g)->format()))
1962             (*g)->terminateGenerator();
1963         ++g;
1964     }
1965
1966     fmtLeftMaps.clear();
1967     fmtRightMaps.clear();
1968     imgFileExts.clear();
1969     imageFiles.clear();
1970     imageDirs.clear();
1971     outDir_.clear();
1972     QmlClassNode::terminate();
1973     ExampleNode::terminate();
1974 }
1975
1976 void Generator::terminateGenerator()
1977 {
1978 }
1979
1980 /*!
1981   Trims trailing whitespace off the \a string and returns
1982   the trimmed string.
1983  */
1984 QString Generator::trimmedTrailing(const QString& string)
1985 {
1986     QString trimmed = string;
1987     while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1988         trimmed.truncate(trimmed.length() - 1);
1989     return trimmed;
1990 }
1991
1992 QString Generator::typeString(const Node *node)
1993 {
1994     switch (node->type()) {
1995     case Node::Namespace:
1996         return "namespace";
1997     case Node::Class:
1998         return "class";
1999     case Node::Fake:
2000     {
2001         switch (node->subType()) {
2002         case Node::QmlClass:
2003             return "type";
2004         case Node::QmlPropertyGroup:
2005             return "property group";
2006         case Node::QmlBasicType:
2007             return "type";
2008         default:
2009             return "documentation";
2010         }
2011     }
2012     case Node::Enum:
2013         return "enum";
2014     case Node::Typedef:
2015         return "typedef";
2016     case Node::Function:
2017         return "function";
2018     case Node::Property:
2019         return "property";
2020     default:
2021         return "documentation";
2022     }
2023 }
2024
2025 void Generator::unknownAtom(const Atom *atom)
2026 {
2027     Location::internalError(tr("unknown atom type '%1' in %2 generator")
2028                             .arg(atom->typeString()).arg(format()));
2029 }
2030
2031 QT_END_NAMESPACE