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