source: trunk/tools/qdoc3/generator.cpp@ 1054

Last change on this file since 1054 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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