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