1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 /*
43   generator.cpp
44 */
45 #include <qdir.h>
46 #ifdef DEBUG_MULTIPLE_QDOCCONF_FILES
47 #include <qdebug.h>
48 #endif
49 #include "codemarker.h"
50 #include "config.h"
51 #include "doc.h"
52 #include "editdistance.h"
53 #include "generator.h"
54 #include "node.h"
55 #include "openedlist.h"
56 #include "quoter.h"
57 #include "separator.h"
58 #include "tokenizer.h"
59 #include "ditaxmlgenerator.h"
60 
61 QT_BEGIN_NAMESPACE
62 
63 QList<Generator *> Generator::generators;
64 QMap<QString, QMap<QString, QString> > Generator::fmtLeftMaps;
65 QMap<QString, QMap<QString, QString> > Generator::fmtRightMaps;
66 QMap<QString, QStringList> Generator::imgFileExts;
67 QSet<QString> Generator::outputFormats;
68 QStringList Generator::imageFiles;
69 QStringList Generator::imageDirs;
70 QStringList Generator::exampleDirs;
71 QStringList Generator::exampleImgExts;
72 QStringList Generator::scriptFiles;
73 QStringList Generator::scriptDirs;
74 QStringList Generator::styleFiles;
75 QStringList Generator::styleDirs;
76 QString Generator::outDir;
77 QString Generator::project;
78 QHash<QString, QString> Generator::outputPrefixes;
79 
80 QString Generator::sinceTitles[] =
81     {
82         "    New Namespaces",
83         "    New Classes",
84         "    New Member Functions",
85         "    New Functions in Namespaces",
86         "    New Global Functions",
87         "    New Macros",
88         "    New Enum Types",
89         "    New Typedefs",
90         "    New Properties",
91         "    New Variables",
92         "    New QML Elements",
93         "    New QML Properties",
94         "    New QML Signals",
95         "    New QML Methods",
96         ""
97     };
98 
singularPlural(Text & text,const NodeList & nodes)99 static void singularPlural(Text& text, const NodeList& nodes)
100 {
101     if (nodes.count() == 1)
102         text << " is";
103     else
104         text << " are";
105 }
106 
Generator()107 Generator::Generator()
108     : amp("&amp;"),
109       lt("&lt;"),
110       gt("&gt;"),
111       quot("&quot;"),
112       tag("</?@[^>]*>")
113 {
114     generators.prepend(this);
115 }
116 
~Generator()117 Generator::~Generator()
118 {
119     generators.removeAll(this);
120 }
121 
initializeGenerator(const Config &)122 void Generator::initializeGenerator(const Config & /* config */)
123 {
124 }
125 
terminateGenerator()126 void Generator::terminateGenerator()
127 {
128 }
129 
initialize(const Config & config)130 void Generator::initialize(const Config &config)
131 {
132     outputFormats = config.getStringSet(CONFIG_OUTPUTFORMATS);
133     if (!outputFormats.isEmpty()) {
134         outDir = config.getString(CONFIG_OUTPUTDIR);
135         if (outDir.isEmpty())
136             config.lastLocation().fatal(tr("No output directory specified in configuration file"));
137 
138         QDir dirInfo;
139         if (dirInfo.exists(outDir)) {
140             if (!Config::removeDirContents(outDir))
141                 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir));
142         }
143         else {
144             if (!dirInfo.mkpath(outDir))
145                 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir));
146         }
147 
148         if (!dirInfo.mkdir(outDir + "/images"))
149             config.lastLocation().fatal(tr("Cannot create output directory '%1'")
150                                         .arg(outDir + "/images"));
151         if (!dirInfo.mkdir(outDir + "/images/used-in-examples"))
152             config.lastLocation().fatal(tr("Cannot create output directory '%1'")
153                                         .arg(outDir + "/images/used-in-examples"));
154         if (!dirInfo.mkdir(outDir + "/scripts"))
155             config.lastLocation().fatal(tr("Cannot create output directory '%1'")
156                                         .arg(outDir + "/scripts"));
157         if (!dirInfo.mkdir(outDir + "/style"))
158             config.lastLocation().fatal(tr("Cannot create output directory '%1'")
159                                         .arg(outDir + "/style"));
160     }
161 
162     imageFiles = config.getStringList(CONFIG_IMAGES);
163     imageDirs = config.getStringList(CONFIG_IMAGEDIRS);
164     scriptFiles = config.getStringList(CONFIG_SCRIPTS);
165     scriptDirs = config.getStringList(CONFIG_SCRIPTDIRS);
166     styleFiles = config.getStringList(CONFIG_STYLES);
167     styleDirs = config.getStringList(CONFIG_STYLEDIRS);
168     exampleDirs = config.getStringList(CONFIG_EXAMPLEDIRS);
169     exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot +
170                                           CONFIG_IMAGEEXTENSIONS);
171 
172     QString imagesDotFileExtensions =
173         CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
174     QSet<QString> formats = config.subVars(imagesDotFileExtensions);
175     QSet<QString>::ConstIterator f = formats.begin();
176     while (f != formats.end()) {
177         imgFileExts[*f] = config.getStringList(imagesDotFileExtensions +
178                                                Config::dot + *f);
179         ++f;
180     }
181 
182     QList<Generator *>::ConstIterator g = generators.begin();
183     while (g != generators.end()) {
184         if (outputFormats.contains((*g)->format())) {
185             (*g)->initializeGenerator(config);
186             QStringList extraImages =
187                 config.getStringList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
188             QStringList::ConstIterator e = extraImages.begin();
189             while (e != extraImages.end()) {
190                 QString userFriendlyFilePath;
191                 QString filePath = Config::findFile(config.lastLocation(),
192                                                     imageFiles,
193                                                     imageDirs,
194                                                     *e,
195                                                     imgFileExts[(*g)->format()],
196                                                     userFriendlyFilePath);
197                 if (!filePath.isEmpty())
198                     Config::copyFile(config.lastLocation(),
199                                      filePath,
200                                      userFriendlyFilePath,
201                                      (*g)->outputDir() +
202                                      "/images");
203                 ++e;
204             }
205 
206             // Documentation template handling
207             QString templateDir = config.getString(
208                 (*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
209 
210             if (!templateDir.isEmpty()) {
211                 QStringList noExts;
212                 QStringList searchDirs = QStringList() << templateDir;
213                 QStringList scripts =
214                     config.getStringList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
215                 e = scripts.begin();
216                 while (e != scripts.end()) {
217                     QString userFriendlyFilePath;
218                     QString filePath = Config::findFile(config.lastLocation(),
219                                                         scriptFiles,
220                                                         searchDirs,
221                                                         *e,
222                                                         noExts,
223                                                         userFriendlyFilePath);
224                     if (!filePath.isEmpty())
225                         Config::copyFile(config.lastLocation(),
226                                          filePath,
227                                          userFriendlyFilePath,
228                                          (*g)->outputDir() +
229                                          "/scripts");
230                     ++e;
231                 }
232 
233                 QStringList styles =
234                     config.getStringList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
235                 e = styles.begin();
236                 while (e != styles.end()) {
237                     QString userFriendlyFilePath;
238                     QString filePath = Config::findFile(config.lastLocation(),
239                                                         styleFiles,
240                                                         searchDirs,
241                                                         *e,
242                                                         noExts,
243                                                         userFriendlyFilePath);
244                     if (!filePath.isEmpty())
245                         Config::copyFile(config.lastLocation(),
246                                          filePath,
247                                          userFriendlyFilePath,
248                                          (*g)->outputDir() +
249                                          "/style");
250                     ++e;
251                 }
252             }
253         }
254         ++g;
255     }
256 
257     QRegExp secondParamAndAbove("[\2-\7]");
258     QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
259     QSet<QString>::ConstIterator n = formattingNames.begin();
260     while (n != formattingNames.end()) {
261         QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
262 
263         QSet<QString> formats = config.subVars(formattingDotName);
264         QSet<QString>::ConstIterator f = formats.begin();
265         while (f != formats.end()) {
266             QString def = config.getString(formattingDotName +
267                                            Config::dot + *f);
268             if (!def.isEmpty()) {
269                 int numParams = Config::numParams(def);
270                 int numOccs = def.count("\1");
271 
272                 if (numParams != 1) {
273                     config.lastLocation().warning(tr("Formatting '%1' must "
274                                                      "have exactly one "
275                                                      "parameter (found %2)")
276                                                   .arg(*n).arg(numParams));
277                 }
278                 else if (numOccs > 1) {
279                     config.lastLocation().fatal(tr("Formatting '%1' must "
280                                                    "contain exactly one "
281                                                    "occurrence of '\\1' "
282                                                    "(found %2)")
283                                                 .arg(*n).arg(numOccs));
284                 }
285                 else {
286                     int paramPos = def.indexOf("\1");
287                     fmtLeftMaps[*f].insert(*n, def.left(paramPos));
288                     fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
289                 }
290             }
291             ++f;
292         }
293         ++n;
294     }
295 
296     project = config.getString(CONFIG_PROJECT);
297 
298     QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
299     if (!prefixes.isEmpty()) {
300         foreach (QString prefix, prefixes)
301             outputPrefixes[prefix] = config.getString(
302                 CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
303     } else
304         outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
305 }
306 
terminate()307 void Generator::terminate()
308 {
309     QList<Generator *>::ConstIterator g = generators.begin();
310     while (g != generators.end()) {
311         if (outputFormats.contains((*g)->format()))
312             (*g)->terminateGenerator();
313         ++g;
314     }
315 
316     fmtLeftMaps.clear();
317     fmtRightMaps.clear();
318     imgFileExts.clear();
319     imageFiles.clear();
320     imageDirs.clear();
321     outDir = "";
322     QmlClassNode::clear();
323 }
324 
generatorForFormat(const QString & format)325 Generator *Generator::generatorForFormat(const QString& format)
326 {
327     QList<Generator *>::ConstIterator g = generators.begin();
328     while (g != generators.end()) {
329         if ((*g)->format() == format)
330             return *g;
331         ++g;
332     }
333     return 0;
334 }
335 
startText(const Node *,CodeMarker *)336 void Generator::startText(const Node * /* relative */,
337                           CodeMarker * /* marker */)
338 {
339 }
340 
endText(const Node *,CodeMarker *)341 void Generator::endText(const Node * /* relative */,
342                         CodeMarker * /* marker */)
343 {
344 }
345 
generateAtom(const Atom *,const Node *,CodeMarker *)346 int Generator::generateAtom(const Atom * /* atom */,
347                             const Node * /* relative */,
348                             CodeMarker * /* marker */)
349 {
350     return 0;
351 }
352 
generateClassLikeNode(const InnerNode *,CodeMarker *)353 void Generator::generateClassLikeNode(const InnerNode * /* classe */,
354                                       CodeMarker * /* marker */)
355 {
356 }
357 
generateFakeNode(const FakeNode *,CodeMarker *)358 void Generator::generateFakeNode(const FakeNode * /* fake */,
359                                  CodeMarker * /* marker */)
360 {
361 }
362 
generateText(const Text & text,const Node * relative,CodeMarker * marker)363 bool Generator::generateText(const Text& text,
364                              const Node *relative,
365                              CodeMarker *marker)
366 {
367     bool result = false;
368     if (text.firstAtom() != 0) {
369         int numAtoms = 0;
370         startText(relative, marker);
371         generateAtomList(text.firstAtom(),
372                          relative,
373                          marker,
374                          true,
375                          numAtoms);
376         endText(relative, marker);
377         result = true;
378     }
379     return result;
380 }
381 
382 #ifdef QDOC_QML
383 /*!
384   Extract sections of markup text surrounded by \e qmltext
385   and \e endqmltext and output them.
386  */
generateQmlText(const Text & text,const Node * relative,CodeMarker * marker,const QString &)387 bool Generator::generateQmlText(const Text& text,
388                                 const Node *relative,
389                                 CodeMarker *marker,
390                                 const QString& /* qmlName */ )
391 {
392     const Atom* atom = text.firstAtom();
393     bool result = false;
394 
395     if (atom != 0) {
396         startText(relative, marker);
397         while (atom) {
398             if (atom->type() != Atom::QmlText)
399                 atom = atom->next();
400             else {
401                 atom = atom->next();
402                 while (atom && (atom->type() != Atom::EndQmlText)) {
403                     int n = 1 + generateAtom(atom, relative, marker);
404                     while (n-- > 0)
405                         atom = atom->next();
406                 }
407             }
408         }
409         endText(relative, marker);
410         result = true;
411     }
412     return result;
413 }
414 #endif
415 
generateBody(const Node * node,CodeMarker * marker)416 void Generator::generateBody(const Node *node, CodeMarker *marker)
417 {
418     bool quiet = false;
419 
420     if (node->type() == Node::Fake) {
421         const FakeNode *fake = static_cast<const FakeNode *>(node);
422         if (fake->subType() == Node::Example) {
423             generateExampleFiles(fake, marker);
424         }
425         else if ((fake->subType() == Node::File) || (fake->subType() == Node::Image)) {
426             quiet = true;
427         }
428     }
429 
430     if (node->doc().isEmpty()) {
431         if (!quiet && !node->isReimp()) { // ### might be unnecessary
432             node->location().warning(tr("No documentation for '%1'")
433                             .arg(marker->plainFullName(node)));
434         }
435     }
436     else {
437         if (node->type() == Node::Function) {
438             const FunctionNode *func = static_cast<const FunctionNode *>(node);
439             if (func->reimplementedFrom() != 0)
440                 generateReimplementedFrom(func, marker);
441         }
442 
443         if (!generateText(node->doc().body(), node, marker)) {
444             if (node->isReimp())
445                 return;
446         }
447 
448         if (node->type() == Node::Enum) {
449             const EnumNode *enume = (const EnumNode *) node;
450 
451             QSet<QString> definedItems;
452             QList<EnumItem>::ConstIterator it = enume->items().begin();
453             while (it != enume->items().end()) {
454                 definedItems.insert((*it).name());
455                 ++it;
456             }
457 
458             QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
459             QSet<QString> allItems = definedItems + documentedItems;
460             if (allItems.count() > definedItems.count() ||
461                  allItems.count() > documentedItems.count()) {
462                 QSet<QString>::ConstIterator a = allItems.begin();
463                 while (a != allItems.end()) {
464                     if (!definedItems.contains(*a)) {
465                         QString details;
466                         QString best = nearestName(*a, definedItems);
467                         if (!best.isEmpty() && !documentedItems.contains(best))
468                             details = tr("Maybe you meant '%1'?").arg(best);
469 
470                         node->doc().location().warning(
471                             tr("No such enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
472                             details);
473                     }
474                     else if (!documentedItems.contains(*a)) {
475                         node->doc().location().warning(
476                             tr("Undocumented enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)));
477                     }
478                     ++a;
479                 }
480             }
481         }
482         else if (node->type() == Node::Function) {
483             const FunctionNode *func = static_cast<const FunctionNode *>(node);
484             QSet<QString> definedParams;
485             QList<Parameter>::ConstIterator p = func->parameters().begin();
486             while (p != func->parameters().end()) {
487                 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
488                         && func->name() != QLatin1String("operator++")
489                         && func->name() != QLatin1String("operator--")) {
490                     node->doc().location().warning(tr("Missing parameter name"));
491                 }
492                 else {
493                     definedParams.insert((*p).name());
494                 }
495                 ++p;
496             }
497 
498             QSet<QString> documentedParams = func->doc().parameterNames();
499             QSet<QString> allParams = definedParams + documentedParams;
500             if (allParams.count() > definedParams.count()
501                     || allParams.count() > documentedParams.count()) {
502                 QSet<QString>::ConstIterator a = allParams.begin();
503                 while (a != allParams.end()) {
504                     if (!definedParams.contains(*a)) {
505                         QString details;
506                         QString best = nearestName(*a, definedParams);
507                         if (!best.isEmpty())
508                             details = tr("Maybe you meant '%1'?").arg(best);
509 
510                         node->doc().location().warning(
511                             tr("No such parameter '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
512                             details);
513                     }
514                     else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
515                         bool needWarning = (func->status() > Node::Obsolete);
516                         if (func->overloadNumber() > 1) {
517                             FunctionNode *primaryFunc =
518                                     func->parent()->findFunctionNode(func->name());
519                             if (primaryFunc) {
520                                 foreach (const Parameter &param,
521                                          primaryFunc->parameters()) {
522                                     if (param.name() == *a) {
523                                         needWarning = false;
524                                         break;
525                                     }
526                                 }
527                             }
528                         }
529                         if (needWarning && !func->isReimp())
530                             node->doc().location().warning(
531                                 tr("Undocumented parameter '%1' in %2")
532                                 .arg(*a).arg(marker->plainFullName(node)));
533                     }
534                     ++a;
535                 }
536             }
537             /*
538               Something like this return value check should
539               be implemented at some point.
540             */
541             if (func->status() > Node::Obsolete && func->returnType() == "bool"
542                     && func->reimplementedFrom() == 0 && !func->isOverload()) {
543                 QString body = func->doc().body().toString();
544                 if (!body.contains("return", Qt::CaseInsensitive))
545                     node->doc().location().warning(tr("Undocumented return value"));
546             }
547         }
548     }
549 
550     if (node->type() == Node::Fake) {
551         const FakeNode *fake = static_cast<const FakeNode *>(node);
552         if (fake->subType() == Node::File) {
553             Text text;
554             Quoter quoter;
555             Doc::quoteFromFile(fake->doc().location(), quoter, fake->name());
556             QString code = quoter.quoteTo(fake->location(), "", "");
557             CodeMarker *codeMarker = CodeMarker::markerForFileName(fake->name());
558             text << Atom(codeMarker->atomType(), code);
559             generateText(text, fake, codeMarker);
560         }
561     }
562 }
563 
generateAlsoList(const Node * node,CodeMarker * marker)564 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
565 {
566     QList<Text> alsoList = node->doc().alsoList();
567     supplementAlsoList(node, alsoList);
568 
569     if (!alsoList.isEmpty()) {
570         Text text;
571         text << Atom::ParaLeft
572              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
573              << "See also "
574              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
575 
576         for (int i = 0; i < alsoList.size(); ++i)
577             text << alsoList.at(i) << separator(i, alsoList.size());
578 
579         text << Atom::ParaRight;
580         generateText(text, node, marker);
581     }
582 }
583 
584 /*!
585   Generate a list of maintainers in the output
586  */
generateMaintainerList(const InnerNode * node,CodeMarker * marker)587 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
588 {
589     QStringList sl = getMetadataElements(node,"maintainer");
590 
591     if (!sl.isEmpty()) {
592         Text text;
593         text << Atom::ParaLeft
594              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
595              << "Maintained by: "
596              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
597 
598         for (int i = 0; i < sl.size(); ++i)
599             text << sl.at(i) << separator(i, sl.size());
600 
601         text << Atom::ParaRight;
602         generateText(text, node, marker);
603     }
604 }
605 
generateInherits(const ClassNode * classe,CodeMarker * marker)606 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
607 {
608     QList<RelatedClass>::ConstIterator r;
609     int index;
610 
611     if (!classe->baseClasses().isEmpty()) {
612         Text text;
613         text << Atom::ParaLeft
614              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
615              << "Inherits: "
616              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
617 
618         r = classe->baseClasses().begin();
619         index = 0;
620         while (r != classe->baseClasses().end()) {
621             text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
622                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
623                  << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
624                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
625 
626             if ((*r).access == Node::Protected) {
627                 text << " (protected)";
628             }
629             else if ((*r).access == Node::Private) {
630                 text << " (private)";
631             }
632             text << separator(index++, classe->baseClasses().count());
633             ++r;
634         }
635         text << Atom::ParaRight;
636         generateText(text, classe, marker);
637     }
638 }
639 
640 #ifdef QDOC_QML
641 /*!
642  */
generateQmlInherits(const QmlClassNode *,CodeMarker *)643 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
644 {
645     // stub.
646 }
647 #endif
648 
generateInheritedBy(const ClassNode * classe,CodeMarker * marker)649 void Generator::generateInheritedBy(const ClassNode *classe,
650                                     CodeMarker *marker)
651 {
652     if (!classe->derivedClasses().isEmpty()) {
653         Text text;
654         text << Atom::ParaLeft
655              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
656              << "Inherited by: "
657              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
658 
659         appendSortedNames(text, classe, classe->derivedClasses(), marker);
660         text << Atom::ParaRight;
661         generateText(text, classe, marker);
662     }
663 }
664 
665 /*!
666   This function is called when the documentation for an
667   example is being formatted. It outputs the list of source
668   files comprising the example, and the list of images used
669   by the example. The images are copied into a subtree of
670   \c{...doc/html/images/used-in-examples/...}
671  */
generateFileList(const FakeNode * fake,CodeMarker * marker,Node::SubType subtype,const QString & tag)672 void Generator::generateFileList(const FakeNode* fake,
673                                  CodeMarker* marker,
674                                  Node::SubType subtype,
675                                  const QString& tag)
676 {
677     int count = 0;
678     Text text;
679     OpenedList openedList(OpenedList::Bullet);
680 
681     text << Atom::ParaLeft << tag << Atom::ParaRight
682          << Atom(Atom::ListLeft, openedList.styleString());
683 
684     foreach (const Node* child, fake->childNodes()) {
685         if (child->subType() == subtype) {
686             ++count;
687             QString file = child->name();
688             if (subtype == Node::Image) {
689                 if (!file.isEmpty()) {
690                     QDir dirInfo;
691                     QString userFriendlyFilePath;
692                     QString srcPath = Config::findFile(fake->location(),
693                                                        QStringList(),
694                                                        exampleDirs,
695                                                        file,
696                                                        exampleImgExts,
697                                                        userFriendlyFilePath);
698                     userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
699 
700                     QString imgOutDir = outDir + "/images/used-in-examples/" + userFriendlyFilePath;
701                     if (!dirInfo.mkpath(imgOutDir))
702                         fake->location().fatal(tr("Cannot create output directory '%1'")
703                                                .arg(imgOutDir));
704 
705                     QString imgOutName = Config::copyFile(fake->location(),
706                                                           srcPath,
707                                                           file,
708                                                           imgOutDir);
709                 }
710 
711             }
712 
713             openedList.next();
714             text << Atom(Atom::ListItemNumber, openedList.numberString())
715                  << Atom(Atom::ListItemLeft, openedList.styleString())
716                  << Atom::ParaLeft
717                  << Atom(Atom::Link, file)
718                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
719                  << file
720                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
721                  << Atom::ParaRight
722                  << Atom(Atom::ListItemRight, openedList.styleString());
723         }
724     }
725     text << Atom(Atom::ListRight, openedList.styleString());
726     if (count > 0)
727         generateText(text, fake, marker);
728 }
729 
generateExampleFiles(const FakeNode * fake,CodeMarker * marker)730 void Generator::generateExampleFiles(const FakeNode *fake, CodeMarker *marker)
731 {
732     if (fake->childNodes().isEmpty())
733         return;
734     generateFileList(fake, marker, Node::File, QString("Files:"));
735     generateFileList(fake, marker, Node::Image, QString("Images:"));
736 }
737 
indent(int level,const QString & markedCode)738 QString Generator::indent(int level, const QString& markedCode)
739 {
740     if (level == 0)
741         return markedCode;
742 
743     QString t;
744     int column = 0;
745 
746     int i = 0;
747     while (i < (int) markedCode.length()) {
748         if (markedCode.at(i) == QLatin1Char('\n')) {
749             column = 0;
750         }
751         else {
752             if (column == 0) {
753                 for (int j = 0; j < level; j++)
754                     t += QLatin1Char(' ');
755             }
756             column++;
757         }
758         t += markedCode.at(i++);
759     }
760     return t;
761 }
762 
plainCode(const QString & markedCode)763 QString Generator::plainCode(const QString& markedCode)
764 {
765     QString t = markedCode;
766     t.replace(tag, QString());
767     t.replace(quot, QLatin1String("\""));
768     t.replace(gt, QLatin1String(">"));
769     t.replace(lt, QLatin1String("<"));
770     t.replace(amp, QLatin1String("&"));
771     return t;
772 }
773 
typeString(const Node * node)774 QString Generator::typeString(const Node *node)
775 {
776     switch (node->type()) {
777     case Node::Namespace:
778         return "namespace";
779     case Node::Class:
780         return "class";
781     case Node::Fake:
782     {
783         switch (node->subType()) {
784         case Node::QmlClass:
785             return "element";
786         case Node::QmlPropertyGroup:
787             return "property group";
788         case Node::QmlBasicType:
789             return "type";
790         default:
791             return "documentation";
792         }
793     }
794     case Node::Enum:
795         return "enum";
796     case Node::Typedef:
797         return "typedef";
798     case Node::Function:
799         return "function";
800     case Node::Property:
801         return "property";
802     default:
803         return "documentation";
804     }
805 }
806 
imageFileName(const Node * relative,const QString & fileBase)807 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
808 {
809     QString userFriendlyFilePath;
810     QString filePath = Config::findFile(
811         relative->doc().location(), imageFiles, imageDirs, fileBase,
812         imgFileExts[format()], userFriendlyFilePath);
813 
814     if (filePath.isEmpty())
815         return QString();
816 
817     QString path = Config::copyFile(relative->doc().location(),
818                                     filePath,
819                                     userFriendlyFilePath,
820                                     outputDir() + QLatin1String("/images"));
821     if (path[0] != '/')
822         return QLatin1String("images/") + path;
823     return QLatin1String("images") + path;
824 }
825 
setImageFileExtensions(const QStringList & extensions)826 void Generator::setImageFileExtensions(const QStringList& extensions)
827 {
828     imgFileExts[format()] = extensions;
829 }
830 
unknownAtom(const Atom * atom)831 void Generator::unknownAtom(const Atom *atom)
832 {
833     Location::internalError(tr("unknown atom type '%1' in %2 generator")
834                             .arg(atom->typeString()).arg(format()));
835 }
836 
matchAhead(const Atom * atom,Atom::Type expectedAtomType)837 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
838 {
839     return atom->next() != 0 && atom->next()->type() == expectedAtomType;
840 }
841 
supplementAlsoList(const Node * node,QList<Text> & alsoList)842 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
843 {
844     if (node->type() == Node::Function) {
845         const FunctionNode *func = static_cast<const FunctionNode *>(node);
846         if (func->overloadNumber() == 1) {
847             QString alternateName;
848             const FunctionNode *alternateFunc = 0;
849 
850             if (func->name().startsWith("set") && func->name().size() >= 4) {
851                 alternateName = func->name()[3].toLower();
852                 alternateName += func->name().mid(4);
853                 alternateFunc = func->parent()->findFunctionNode(alternateName);
854 
855                 if (!alternateFunc) {
856                     alternateName = "is" + func->name().mid(3);
857                     alternateFunc = func->parent()->findFunctionNode(alternateName);
858                     if (!alternateFunc) {
859                         alternateName = "has" + func->name().mid(3);
860                         alternateFunc = func->parent()->findFunctionNode(alternateName);
861                     }
862                 }
863             }
864             else if (!func->name().isEmpty()) {
865                 alternateName = "set";
866                 alternateName += func->name()[0].toUpper();
867                 alternateName += func->name().mid(1);
868                 alternateFunc = func->parent()->findFunctionNode(alternateName);
869             }
870 
871             if (alternateFunc && alternateFunc->access() != Node::Private) {
872                 int i;
873                 for (i = 0; i < alsoList.size(); ++i) {
874                     if (alsoList.at(i).toString().contains(alternateName))
875                         break;
876                 }
877 
878                 if (i == alsoList.size()) {
879                     alternateName += "()";
880 
881                     Text also;
882                     also << Atom(Atom::Link, alternateName)
883                          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
884                          << alternateName
885                          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
886                     alsoList.prepend(also);
887                 }
888             }
889         }
890     }
891 }
892 
formattingLeftMap()893 QMap<QString, QString>& Generator::formattingLeftMap()
894 {
895     return fmtLeftMaps[format()];
896 }
897 
formattingRightMap()898 QMap<QString, QString>& Generator::formattingRightMap()
899 {
900     return fmtRightMaps[format()];
901 }
902 
903 /*
904   Trims trailimng whitespace off the \a string and returns
905   the trimmed string.
906  */
trimmedTrailing(const QString & string)907 QString Generator::trimmedTrailing(const QString& string)
908 {
909     QString trimmed = string;
910     while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
911         trimmed.truncate(trimmed.length() - 1);
912     return trimmed;
913 }
914 
generateStatus(const Node * node,CodeMarker * marker)915 void Generator::generateStatus(const Node *node, CodeMarker *marker)
916 {
917     Text text;
918 
919     switch (node->status()) {
920     case Node::Commendable:
921     case Node::Main:
922         break;
923     case Node::Preliminary:
924         text << Atom::ParaLeft
925              << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
926              << "This "
927              << typeString(node)
928              << " is under development and is subject to change."
929              << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
930              << Atom::ParaRight;
931         break;
932     case Node::Deprecated:
933         text << Atom::ParaLeft;
934         if (node->isInnerNode())
935             text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
936         text << "This " << typeString(node) << " is deprecated.";
937         if (node->isInnerNode())
938             text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
939         text << Atom::ParaRight;
940         break;
941     case Node::Obsolete:
942         text << Atom::ParaLeft;
943         if (node->isInnerNode())
944             text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
945         text << "This " << typeString(node) << " is obsolete.";
946         if (node->isInnerNode())
947             text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
948         text << " It is provided to keep old source code working. "
949              << "We strongly advise against "
950              << "using it in new code." << Atom::ParaRight;
951         break;
952     case Node::Compat:
953         // reimplemented in HtmlGenerator subclass
954         if (node->isInnerNode()) {
955             text << Atom::ParaLeft
956                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
957                  << "This "
958                  << typeString(node)
959                  << " is part of the Qt 3 compatibility layer."
960                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
961                  << " It is provided to keep old source code working. "
962                  << "We strongly advise against "
963                  << "using it in new code. See "
964                  << Atom(Atom::AutoLink, "Porting to Qt 4")
965                  << " for more information."
966                  << Atom::ParaRight;
967         }
968         break;
969     case Node::Internal:
970     default:
971         break;
972     }
973     generateText(text, node, marker);
974 }
975 
generateThreadSafeness(const Node * node,CodeMarker * marker)976 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
977 {
978     Text text;
979     Text theStockLink;
980     Node::ThreadSafeness threadSafeness = node->threadSafeness();
981 
982     Text rlink;
983     rlink << Atom(Atom::Link,"reentrant")
984           << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
985           << "reentrant"
986           << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
987 
988     Text tlink;
989     tlink << Atom(Atom::Link,"thread-safe")
990           << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
991           << "thread-safe"
992           << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
993 
994     switch (threadSafeness) {
995     case Node::UnspecifiedSafeness:
996         break;
997     case Node::NonReentrant:
998         text << Atom::ParaLeft
999              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1000              << "Warning:"
1001              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1002              << " This "
1003              << typeString(node)
1004              << " is not "
1005              << rlink
1006              << "."
1007              << Atom::ParaRight;
1008         break;
1009     case Node::Reentrant:
1010     case Node::ThreadSafe:
1011         text << Atom::ParaLeft
1012              << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1013              << "Note:"
1014              << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1015              << " ";
1016 
1017         if (node->isInnerNode()) {
1018             const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1019             text << "All functions in this "
1020                  << typeString(node)
1021                  << " are ";
1022             if (threadSafeness == Node::ThreadSafe)
1023                 text << tlink;
1024             else
1025                 text << rlink;
1026 
1027             bool exceptions = false;
1028             NodeList reentrant;
1029             NodeList threadsafe;
1030             NodeList nonreentrant;
1031             NodeList::ConstIterator c = innerNode->childNodes().begin();
1032             while (c != innerNode->childNodes().end()) {
1033 
1034                 if ((*c)->status() != Node::Obsolete){
1035                     switch ((*c)->threadSafeness()) {
1036                     case Node::Reentrant:
1037                         reentrant.append(*c);
1038                         if (threadSafeness == Node::ThreadSafe)
1039                             exceptions = true;
1040                         break;
1041                     case Node::ThreadSafe:
1042                         threadsafe.append(*c);
1043                         if (threadSafeness == Node::Reentrant)
1044                             exceptions = true;
1045                         break;
1046                     case Node::NonReentrant:
1047                         nonreentrant.append(*c);
1048                         exceptions = true;
1049                         break;
1050                     default:
1051                         break;
1052                     }
1053                 }
1054                 ++c;
1055             }
1056             if (!exceptions)
1057                 text << ".";
1058             else if (threadSafeness == Node::Reentrant) {
1059                 if (nonreentrant.isEmpty()) {
1060                     if (!threadsafe.isEmpty()) {
1061                         text << ", but ";
1062                         appendFullNames(text,threadsafe,innerNode,marker);
1063                         singularPlural(text,threadsafe);
1064                         text << " also " << tlink << ".";
1065                     }
1066                     else
1067                         text << ".";
1068                 }
1069                 else {
1070                     text << ", except for ";
1071                     appendFullNames(text,nonreentrant,innerNode,marker);
1072                     text << ", which";
1073                     singularPlural(text,nonreentrant);
1074                     text << " nonreentrant.";
1075                     if (!threadsafe.isEmpty()) {
1076                         text << " ";
1077                         appendFullNames(text,threadsafe,innerNode,marker);
1078                         singularPlural(text,threadsafe);
1079                         text << " " << tlink << ".";
1080                     }
1081                 }
1082             }
1083             else { // thread-safe
1084                 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1085                     text << ", except for ";
1086                     if (!reentrant.isEmpty()) {
1087                         appendFullNames(text,reentrant,innerNode,marker);
1088                         text << ", which";
1089                         singularPlural(text,reentrant);
1090                         text << " only " << rlink;
1091                         if (!nonreentrant.isEmpty())
1092                             text << ", and ";
1093                     }
1094                     if (!nonreentrant.isEmpty()) {
1095                         appendFullNames(text,nonreentrant,innerNode,marker);
1096                         text << ", which";
1097                         singularPlural(text,nonreentrant);
1098                         text << " nonreentrant.";
1099                     }
1100                     text << ".";
1101                 }
1102             }
1103         }
1104         else {
1105             text << "This " << typeString(node) << " is ";
1106             if (threadSafeness == Node::ThreadSafe)
1107                 text << tlink;
1108             else
1109                 text << rlink;
1110             text << ".";
1111         }
1112         text << Atom::ParaRight;
1113     }
1114     generateText(text,node,marker);
1115 }
1116 
generateSince(const Node * node,CodeMarker * marker)1117 void Generator::generateSince(const Node *node, CodeMarker *marker)
1118 {
1119     if (!node->since().isEmpty()) {
1120         Text text;
1121         text << Atom::ParaLeft
1122              << "This "
1123              << typeString(node);
1124         if (node->type() == Node::Enum)
1125             text << " was introduced or modified in ";
1126         else
1127             text << " was introduced in ";
1128 
1129         QStringList since = node->since().split(" ");
1130         if (since.count() == 1) {
1131             // Handle legacy use of \since <version>.
1132             if (project.isEmpty())
1133                  text << "version";
1134             else
1135                  text << project;
1136             text << " " << since[0];
1137         } else {
1138             // Reconstruct the <project> <version> string.
1139             text << " " << since.join(" ");
1140         }
1141 
1142         text << "." << Atom::ParaRight;
1143         generateText(text, node, marker);
1144     }
1145 }
1146 
generateReimplementedFrom(const FunctionNode * func,CodeMarker * marker)1147 void Generator::generateReimplementedFrom(const FunctionNode *func,
1148                                           CodeMarker *marker)
1149 {
1150     if (func->reimplementedFrom() != 0) {
1151         const FunctionNode *from = func->reimplementedFrom();
1152         if (from->access() != Node::Private &&
1153             from->parent()->access() != Node::Private) {
1154             Text text;
1155             text << Atom::ParaLeft << "Reimplemented from ";
1156             QString fullName =  from->parent()->name() + "::" + from->name() + "()";
1157             appendFullName(text, from->parent(), fullName, from);
1158             text << "." << Atom::ParaRight;
1159             generateText(text, func, marker);
1160         }
1161     }
1162 }
1163 
generateAtomList(const Atom * atom,const Node * relative,CodeMarker * marker,bool generate,int & numAtoms)1164 const Atom *Generator::generateAtomList(const Atom *atom,
1165                                         const Node *relative,
1166                                         CodeMarker *marker,
1167                                         bool generate,
1168                                         int &numAtoms)
1169 {
1170     while (atom) {
1171         if (atom->type() == Atom::FormatIf) {
1172             int numAtoms0 = numAtoms;
1173             bool rightFormat = canHandleFormat(atom->string());
1174             atom = generateAtomList(atom->next(),
1175                                     relative,
1176                                     marker,
1177                                     generate && rightFormat,
1178                                     numAtoms);
1179             if (!atom)
1180                 return 0;
1181 
1182             if (atom->type() == Atom::FormatElse) {
1183                 ++numAtoms;
1184                 atom = generateAtomList(atom->next(),
1185                                         relative,
1186                                         marker,
1187                                         generate && !rightFormat,
1188                                         numAtoms);
1189                 if (!atom)
1190                     return 0;
1191             }
1192 
1193             if (atom->type() == Atom::FormatEndif) {
1194                 if (generate && numAtoms0 == numAtoms) {
1195                     relative->location().warning(tr("Output format %1 not handled %2")
1196                                                  .arg(format()).arg(outFileName()));
1197                     Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
1198                     generateAtomList(&unhandledFormatAtom,
1199                                      relative,
1200                                      marker,
1201                                      generate,
1202                                      numAtoms);
1203                 }
1204                 atom = atom->next();
1205             }
1206         }
1207         else if (atom->type() == Atom::FormatElse ||
1208                  atom->type() == Atom::FormatEndif) {
1209             return atom;
1210         }
1211         else {
1212             int n = 1;
1213             if (generate) {
1214                 n += generateAtom(atom, relative, marker);
1215                 numAtoms += n;
1216             }
1217             while (n-- > 0)
1218                 atom = atom->next();
1219         }
1220     }
1221     return 0;
1222 }
1223 
appendFullName(Text & text,const Node * apparentNode,const Node * relative,CodeMarker * marker,const Node * actualNode)1224 void Generator::appendFullName(Text& text,
1225                                const Node *apparentNode,
1226                                const Node *relative,
1227                                CodeMarker *marker,
1228                                const Node *actualNode)
1229 {
1230     if (actualNode == 0)
1231         actualNode = apparentNode;
1232     text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
1233          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1234          << Atom(Atom::String, marker->plainFullName(apparentNode, relative))
1235          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1236 }
1237 
appendFullName(Text & text,const Node * apparentNode,const QString & fullName,const Node * actualNode)1238 void Generator::appendFullName(Text& text,
1239                                const Node *apparentNode,
1240                                const QString& fullName,
1241                                const Node *actualNode)
1242 {
1243     if (actualNode == 0)
1244         actualNode = apparentNode;
1245     text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
1246          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1247          << Atom(Atom::String, fullName)
1248          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1249 }
1250 
appendFullNames(Text & text,const NodeList & nodes,const Node * relative,CodeMarker * marker)1251 void Generator::appendFullNames(Text& text,
1252                                 const NodeList& nodes,
1253                                 const Node* relative,
1254                                 CodeMarker* marker)
1255 {
1256     NodeList::ConstIterator n = nodes.begin();
1257     int index = 0;
1258     while (n != nodes.end()) {
1259         appendFullName(text,*n,relative,marker);
1260         text << comma(index++,nodes.count());
1261         ++n;
1262     }
1263 }
1264 
appendSortedNames(Text & text,const ClassNode * classe,const QList<RelatedClass> & classes,CodeMarker * marker)1265 void Generator::appendSortedNames(Text& text,
1266                                   const ClassNode *classe,
1267                                   const QList<RelatedClass> &classes,
1268                                   CodeMarker *marker)
1269 {
1270     QList<RelatedClass>::ConstIterator r;
1271     QMap<QString,Text> classMap;
1272     int index = 0;
1273 
1274     r = classes.begin();
1275     while (r != classes.end()) {
1276         if ((*r).node->access() == Node::Public &&
1277             (*r).node->status() != Node::Internal
1278             && !(*r).node->doc().isEmpty()) {
1279             Text className;
1280             appendFullName(className, (*r).node, classe, marker);
1281             classMap[className.toString().toLower()] = className;
1282         }
1283         ++r;
1284     }
1285 
1286     QStringList classNames = classMap.keys();
1287     classNames.sort();
1288 
1289     foreach (const QString &className, classNames) {
1290         text << classMap[className];
1291         text << separator(index++, classNames.count());
1292     }
1293 }
1294 
appendSortedQmlNames(Text & text,const Node * base,const NodeList & subs,CodeMarker * marker)1295 void Generator::appendSortedQmlNames(Text& text,
1296                                      const Node* base,
1297                                      const NodeList& subs,
1298                                      CodeMarker *marker)
1299 {
1300     QMap<QString,Text> classMap;
1301     int index = 0;
1302 
1303 #ifdef DEBUG_MULTIPLE_QDOCCONF_FILES
1304     qDebug() << "Generator::appendSortedQmlNames():" << base->name() << "is inherited by...";
1305 #endif
1306     for (int i = 0; i < subs.size(); ++i) {
1307         Text t;
1308 #ifdef DEBUG_MULTIPLE_QDOCCONF_FILES
1309         qDebug() << "    " << subs[i]->name();
1310 #endif
1311         appendFullName(t, subs[i], base, marker);
1312         classMap[t.toString().toLower()] = t;
1313     }
1314 
1315     QStringList names = classMap.keys();
1316     names.sort();
1317 
1318     foreach (const QString &name, names) {
1319         text << classMap[name];
1320         text << separator(index++, names.count());
1321     }
1322 }
1323 
skipAtoms(const Atom * atom,Atom::Type type) const1324 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1325 {
1326     int skipAhead = 0;
1327     atom = atom->next();
1328     while (atom != 0 && atom->type() != type) {
1329         skipAhead++;
1330         atom = atom->next();
1331     }
1332     return skipAhead;
1333 }
1334 
fullName(const Node * node,const Node * relative,CodeMarker * marker) const1335 QString Generator::fullName(const Node *node,
1336                             const Node *relative,
1337                             CodeMarker *marker) const
1338 {
1339     if (node->type() == Node::Fake)
1340         return static_cast<const FakeNode *>(node)->title();
1341     else if (node->type() == Node::Class &&
1342         !(static_cast<const ClassNode *>(node))->serviceName().isEmpty())
1343         return (static_cast<const ClassNode *>(node))->serviceName();
1344     else
1345         return marker->plainFullName(node, relative);
1346 }
1347 
outputPrefix(const QString & nodeType)1348 QString Generator::outputPrefix(const QString &nodeType)
1349 {
1350     return outputPrefixes[nodeType];
1351 }
1352 
1353 /*!
1354   Looks up the tag \a t in the map of metadata values for the
1355   current topic in \a inner. If a value for the tag is found,
1356   the value is returned.
1357 
1358   \note If \a t is found in the metadata map, it is erased.
1359   i.e. Once you call this function for a particular \a t,
1360   you consume \a t.
1361  */
getMetadataElement(const InnerNode * inner,const QString & t)1362 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1363 {
1364     QString s;
1365     QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1366     QStringMultiMap::iterator i = metaTagMap.find(t);
1367     if (i != metaTagMap.end()) {
1368         s = i.value();
1369         metaTagMap.erase(i);
1370     }
1371     return s;
1372 }
1373 
1374 /*!
1375   Looks up the tag \a t in the map of metadata values for the
1376   current topic in \a inner. If values for the tag are found,
1377   they are returned in a string list.
1378 
1379   \note If \a t is found in the metadata map, all the pairs
1380   having the key \a t are erased. i.e. Once you call this
1381   function for a particular \a t, you consume \a t.
1382  */
getMetadataElements(const InnerNode * inner,const QString & t)1383 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1384 {
1385     QStringList s;
1386     QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1387     s = metaTagMap.values(t);
1388     if (!s.isEmpty())
1389         metaTagMap.remove(t);
1390     return s;
1391 }
1392 
1393 /*!
1394   For generating the "New Classes... in 4.6" section on the
1395   What's New in 4.6" page.
1396  */
findAllSince(const InnerNode * node)1397 void Generator::findAllSince(const InnerNode *node)
1398 {
1399     NodeList::const_iterator child = node->childNodes().constBegin();
1400 
1401     // Traverse the tree, starting at the node supplied.
1402 
1403     while (child != node->childNodes().constEnd()) {
1404 
1405         QString sinceString = (*child)->since();
1406 
1407         if (((*child)->access() != Node::Private) && !sinceString.isEmpty()) {
1408 
1409             // Insert a new entry into each map for each new since string found.
1410             NewSinceMaps::iterator nsmap = newSinceMaps.find(sinceString);
1411             if (nsmap == newSinceMaps.end())
1412                 nsmap = newSinceMaps.insert(sinceString,NodeMultiMap());
1413 
1414             NewClassMaps::iterator ncmap = newClassMaps.find(sinceString);
1415             if (ncmap == newClassMaps.end())
1416                 ncmap = newClassMaps.insert(sinceString,NodeMap());
1417 
1418             NewClassMaps::iterator nqcmap = newQmlClassMaps.find(sinceString);
1419             if (nqcmap == newQmlClassMaps.end())
1420                 nqcmap = newQmlClassMaps.insert(sinceString,NodeMap());
1421 
1422             if ((*child)->type() == Node::Function) {
1423                 // Insert functions into the general since map.
1424                 FunctionNode *func = static_cast<FunctionNode *>(*child);
1425                 if ((func->status() > Node::Obsolete) &&
1426                     (func->metaness() != FunctionNode::Ctor) &&
1427                     (func->metaness() != FunctionNode::Dtor)) {
1428                     nsmap.value().insert(func->name(),(*child));
1429                 }
1430             }
1431             else if ((*child)->url().isEmpty()) {
1432                 if ((*child)->type() == Node::Class && !(*child)->doc().isEmpty()) {
1433                     // Insert classes into the since and class maps.
1434                     QString className = (*child)->name();
1435                     if ((*child)->parent() &&
1436                         (*child)->parent()->type() == Node::Namespace &&
1437                         !(*child)->parent()->name().isEmpty())
1438                         className = (*child)->parent()->name()+"::"+className;
1439 
1440                     nsmap.value().insert(className,(*child));
1441                     ncmap.value().insert(className,(*child));
1442                 }
1443                 else if ((*child)->subType() == Node::QmlClass) {
1444                     // Insert QML elements into the since and element maps.
1445                     QString className = (*child)->name();
1446                     if ((*child)->parent() &&
1447                         (*child)->parent()->type() == Node::Namespace &&
1448                         !(*child)->parent()->name().isEmpty())
1449                         className = (*child)->parent()->name()+"::"+className;
1450 
1451                     nsmap.value().insert(className,(*child));
1452                     nqcmap.value().insert(className,(*child));
1453                 }
1454                 else if ((*child)->type() == Node::QmlProperty) {
1455                     // Insert QML properties into the since map.
1456                     QString propertyName = (*child)->name();
1457                     nsmap.value().insert(propertyName,(*child));
1458                 }
1459             }
1460             else {
1461                 // Insert external documents into the general since map.
1462                 QString name = (*child)->name();
1463                 if ((*child)->parent() &&
1464                     (*child)->parent()->type() == Node::Namespace &&
1465                     !(*child)->parent()->name().isEmpty())
1466                     name = (*child)->parent()->name()+"::"+name;
1467 
1468                 nsmap.value().insert(name,(*child));
1469             }
1470 
1471             // Find child nodes with since commands.
1472             if ((*child)->isInnerNode()) {
1473                 findAllSince(static_cast<InnerNode *>(*child));
1474             }
1475         }
1476         ++child;
1477     }
1478 }
1479 
1480 QT_END_NAMESPACE
1481