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