1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the tools applications of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
46 #ifdef DEBUG_MULTIPLE_QDOCCONF_FILES
50 #include "codemarker.h"
53 #include "editdistance.h"
54 #include "generator.h"
56 #include "openedlist.h"
58 #include "separator.h"
59 #include "tokenizer.h"
60 #include "ditaxmlgenerator.h"
64 QList<Generator *> Generator::generators;
65 QMap<QString, QMap<QString, QString> > Generator::fmtLeftMaps;
66 QMap<QString, QMap<QString, QString> > Generator::fmtRightMaps;
67 QMap<QString, QStringList> Generator::imgFileExts;
68 QSet<QString> Generator::outputFormats;
69 QStringList Generator::imageFiles;
70 QStringList Generator::imageDirs;
71 QStringList Generator::exampleDirs;
72 QStringList Generator::exampleImgExts;
73 QStringList Generator::scriptFiles;
74 QStringList Generator::scriptDirs;
75 QStringList Generator::styleFiles;
76 QStringList Generator::styleDirs;
77 QString Generator::outDir_;
78 QString Generator::baseDir_;
79 QString Generator::project;
80 QHash<QString, QString> Generator::outputPrefixes;
82 QString Generator::sinceTitles[] =
86 " New Member Functions",
87 " New Functions in Namespaces",
88 " New Global Functions",
95 " New QML Properties",
97 " New QML Signal Handlers",
102 static void singularPlural(Text& text, const NodeList& nodes)
104 if (nodes.count() == 1)
110 Generator::Generator()
117 generators.prepend(this);
120 Generator::~Generator()
122 generators.removeAll(this);
125 void Generator::initializeGenerator(const Config & /* config */)
129 void Generator::terminateGenerator()
133 void Generator::initialize(const Config &config)
135 outputFormats = config.getOutputFormats();
136 if (!outputFormats.isEmpty()) {
137 outDir_ = config.getOutputDir();
138 baseDir_ = config.getString(CONFIG_BASEDIR);
139 if (!baseDir_.isEmpty())
140 config.location().warning(tr("\"basedir\" specified in config file. "
141 "All output will be in module directories of the output directory"));
142 if (outDir_.isEmpty())
143 config.lastLocation().fatal(tr("No output directory specified in configuration file or on the command line"));
146 if (dirInfo.exists(outDir_)) {
147 if (!Config::removeDirContents(outDir_))
148 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
151 if (!dirInfo.mkpath(outDir_))
152 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
155 if (!dirInfo.mkdir(outDir_ + "/images"))
156 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
157 .arg(outDir_ + "/images"));
158 if (!dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
159 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
160 .arg(outDir_ + "/images/used-in-examples"));
161 if (!dirInfo.mkdir(outDir_ + "/scripts"))
162 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
163 .arg(outDir_ + "/scripts"));
164 if (!dirInfo.mkdir(outDir_ + "/style"))
165 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
166 .arg(outDir_ + "/style"));
169 imageFiles = config.getCleanPathList(CONFIG_IMAGES);
170 imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
171 scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
172 scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
173 styleFiles = config.getCleanPathList(CONFIG_STYLES);
174 styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
175 exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
176 exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot +
177 CONFIG_IMAGEEXTENSIONS);
179 QString imagesDotFileExtensions =
180 CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
181 QSet<QString> formats = config.subVars(imagesDotFileExtensions);
182 QSet<QString>::ConstIterator f = formats.begin();
183 while (f != formats.end()) {
184 imgFileExts[*f] = config.getStringList(imagesDotFileExtensions +
189 QList<Generator *>::ConstIterator g = generators.begin();
190 while (g != generators.end()) {
191 if (outputFormats.contains((*g)->format())) {
192 (*g)->initializeGenerator(config);
193 QStringList extraImages =
194 config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
195 QStringList::ConstIterator e = extraImages.begin();
196 while (e != extraImages.end()) {
197 QString userFriendlyFilePath;
198 QString filePath = Config::findFile(config.lastLocation(),
202 imgFileExts[(*g)->format()],
203 userFriendlyFilePath);
204 if (!filePath.isEmpty())
205 Config::copyFile(config.lastLocation(),
207 userFriendlyFilePath,
213 // Documentation template handling
214 QString templateDir = config.getString(
215 (*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
217 if (!templateDir.isEmpty()) {
219 QStringList searchDirs = QStringList() << templateDir;
220 QStringList scripts =
221 config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
223 while (e != scripts.end()) {
224 QString userFriendlyFilePath;
225 QString filePath = Config::findFile(config.lastLocation(),
230 userFriendlyFilePath);
231 if (!filePath.isEmpty())
232 Config::copyFile(config.lastLocation(),
234 userFriendlyFilePath,
241 config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
243 while (e != styles.end()) {
244 QString userFriendlyFilePath;
245 QString filePath = Config::findFile(config.lastLocation(),
250 userFriendlyFilePath);
251 if (!filePath.isEmpty())
252 Config::copyFile(config.lastLocation(),
254 userFriendlyFilePath,
264 QRegExp secondParamAndAbove("[\2-\7]");
265 QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
266 QSet<QString>::ConstIterator n = formattingNames.begin();
267 while (n != formattingNames.end()) {
268 QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
270 QSet<QString> formats = config.subVars(formattingDotName);
271 QSet<QString>::ConstIterator f = formats.begin();
272 while (f != formats.end()) {
273 QString def = config.getString(formattingDotName +
275 if (!def.isEmpty()) {
276 int numParams = Config::numParams(def);
277 int numOccs = def.count("\1");
279 if (numParams != 1) {
280 config.lastLocation().warning(tr("Formatting '%1' must "
282 "parameter (found %2)")
283 .arg(*n).arg(numParams));
285 else if (numOccs > 1) {
286 config.lastLocation().fatal(tr("Formatting '%1' must "
287 "contain exactly one "
288 "occurrence of '\\1' "
290 .arg(*n).arg(numOccs));
293 int paramPos = def.indexOf("\1");
294 fmtLeftMaps[*f].insert(*n, def.left(paramPos));
295 fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
303 project = config.getString(CONFIG_PROJECT);
305 QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
306 if (!prefixes.isEmpty()) {
307 foreach (QString prefix, prefixes)
308 outputPrefixes[prefix] = config.getString(
309 CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
311 outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
314 void Generator::terminate()
316 QList<Generator *>::ConstIterator g = generators.begin();
317 while (g != generators.end()) {
318 if (outputFormats.contains((*g)->format()))
319 (*g)->terminateGenerator();
324 fmtRightMaps.clear();
329 QmlClassNode::terminate();
330 ExampleNode::terminate();
333 Generator *Generator::generatorForFormat(const QString& format)
335 QList<Generator *>::ConstIterator g = generators.begin();
336 while (g != generators.end()) {
337 if ((*g)->format() == format)
344 void Generator::startText(const Node * /* relative */,
345 CodeMarker * /* marker */)
349 void Generator::endText(const Node * /* relative */,
350 CodeMarker * /* marker */)
354 int Generator::generateAtom(const Atom * /* atom */,
355 const Node * /* relative */,
356 CodeMarker * /* marker */)
361 void Generator::generateClassLikeNode(const InnerNode * /* classe */,
362 CodeMarker * /* marker */)
366 void Generator::generateFakeNode(const FakeNode * /* fake */,
367 CodeMarker * /* marker */)
371 bool Generator::generateText(const Text& text,
372 const Node *relative,
376 if (text.firstAtom() != 0) {
378 startText(relative, marker);
379 generateAtomList(text.firstAtom(),
384 endText(relative, marker);
392 Extract sections of markup text surrounded by \e qmltext
393 and \e endqmltext and output them.
395 bool Generator::generateQmlText(const Text& text,
396 const Node *relative,
398 const QString& /* qmlName */ )
400 const Atom* atom = text.firstAtom();
404 startText(relative, marker);
406 if (atom->type() != Atom::QmlText)
410 while (atom && (atom->type() != Atom::EndQmlText)) {
411 int n = 1 + generateAtom(atom, relative, marker);
417 endText(relative, marker);
424 void Generator::generateBody(const Node *node, CodeMarker *marker)
428 if (node->type() == Node::Fake) {
429 const FakeNode *fake = static_cast<const FakeNode *>(node);
430 if (fake->subType() == Node::Example) {
431 generateExampleFiles(fake, marker);
433 else if ((fake->subType() == Node::File) || (fake->subType() == Node::Image)) {
437 if (node->doc().isEmpty()) {
438 if (!quiet && !node->isReimp()) { // ### might be unnecessary
439 node->location().warning(tr("No documentation for '%1'")
440 .arg(marker->plainFullName(node)));
444 if (node->type() == Node::Function) {
445 const FunctionNode *func = static_cast<const FunctionNode *>(node);
446 if (func->reimplementedFrom() != 0)
447 generateReimplementedFrom(func, marker);
450 if (!generateText(node->doc().body(), node, marker)) {
455 if (node->type() == Node::Enum) {
456 const EnumNode *enume = (const EnumNode *) node;
458 QSet<QString> definedItems;
459 QList<EnumItem>::ConstIterator it = enume->items().begin();
460 while (it != enume->items().end()) {
461 definedItems.insert((*it).name());
465 QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
466 QSet<QString> allItems = definedItems + documentedItems;
467 if (allItems.count() > definedItems.count() ||
468 allItems.count() > documentedItems.count()) {
469 QSet<QString>::ConstIterator a = allItems.begin();
470 while (a != allItems.end()) {
471 if (!definedItems.contains(*a)) {
473 QString best = nearestName(*a, definedItems);
474 if (!best.isEmpty() && !documentedItems.contains(best))
475 details = tr("Maybe you meant '%1'?").arg(best);
477 node->doc().location().warning(
478 tr("No such enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
481 else if (!documentedItems.contains(*a)) {
482 node->doc().location().warning(
483 tr("Undocumented enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)));
489 else if (node->type() == Node::Function) {
490 const FunctionNode *func = static_cast<const FunctionNode *>(node);
491 QSet<QString> definedParams;
492 QList<Parameter>::ConstIterator p = func->parameters().begin();
493 while (p != func->parameters().end()) {
494 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
495 && func->name() != QLatin1String("operator++")
496 && func->name() != QLatin1String("operator--")) {
497 node->doc().location().warning(tr("Missing parameter name"));
500 definedParams.insert((*p).name());
505 QSet<QString> documentedParams = func->doc().parameterNames();
506 QSet<QString> allParams = definedParams + documentedParams;
507 if (allParams.count() > definedParams.count()
508 || allParams.count() > documentedParams.count()) {
509 QSet<QString>::ConstIterator a = allParams.begin();
510 while (a != allParams.end()) {
511 if (!definedParams.contains(*a)) {
513 QString best = nearestName(*a, definedParams);
515 details = tr("Maybe you meant '%1'?").arg(best);
517 node->doc().location().warning(
518 tr("No such parameter '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
521 else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
522 bool needWarning = (func->status() > Node::Obsolete);
523 if (func->overloadNumber() > 1) {
524 FunctionNode *primaryFunc =
525 func->parent()->findFunctionNode(func->name());
527 foreach (const Parameter ¶m,
528 primaryFunc->parameters()) {
529 if (param.name() == *a) {
536 if (needWarning && !func->isReimp())
537 node->doc().location().warning(
538 tr("Undocumented parameter '%1' in %2")
539 .arg(*a).arg(marker->plainFullName(node)));
545 Something like this return value check should
546 be implemented at some point.
548 if (func->status() > Node::Obsolete && func->returnType() == "bool"
549 && func->reimplementedFrom() == 0 && !func->isOverload()) {
550 QString body = func->doc().body().toString();
551 if (!body.contains("return", Qt::CaseInsensitive))
552 node->doc().location().warning(tr("Undocumented return value"));
557 if (node->type() == Node::Fake) {
558 const FakeNode *fake = static_cast<const FakeNode *>(node);
559 if (fake->subType() == Node::File) {
562 Doc::quoteFromFile(fake->doc().location(), quoter, fake->name());
563 QString code = quoter.quoteTo(fake->location(), "", "");
564 CodeMarker *codeMarker = CodeMarker::markerForFileName(fake->name());
565 text << Atom(codeMarker->atomType(), code);
566 generateText(text, fake, codeMarker);
571 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
573 QList<Text> alsoList = node->doc().alsoList();
574 supplementAlsoList(node, alsoList);
576 if (!alsoList.isEmpty()) {
578 text << Atom::ParaLeft
579 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
581 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
583 for (int i = 0; i < alsoList.size(); ++i)
584 text << alsoList.at(i) << separator(i, alsoList.size());
586 text << Atom::ParaRight;
587 generateText(text, node, marker);
592 Generate a list of maintainers in the output
594 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
596 QStringList sl = getMetadataElements(node,"maintainer");
600 text << Atom::ParaLeft
601 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
603 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
605 for (int i = 0; i < sl.size(); ++i)
606 text << sl.at(i) << separator(i, sl.size());
608 text << Atom::ParaRight;
609 generateText(text, node, marker);
613 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
615 QList<RelatedClass>::ConstIterator r;
618 if (!classe->baseClasses().isEmpty()) {
620 text << Atom::ParaLeft
621 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
623 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
625 r = classe->baseClasses().begin();
627 while (r != classe->baseClasses().end()) {
628 text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
629 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
630 << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
631 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
633 if ((*r).access == Node::Protected) {
634 text << " (protected)";
636 else if ((*r).access == Node::Private) {
637 text << " (private)";
639 text << separator(index++, classe->baseClasses().count());
642 text << Atom::ParaRight;
643 generateText(text, classe, marker);
650 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
656 void Generator::generateInheritedBy(const ClassNode *classe,
659 if (!classe->derivedClasses().isEmpty()) {
661 text << Atom::ParaLeft
662 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
664 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
666 appendSortedNames(text, classe, classe->derivedClasses(), marker);
667 text << Atom::ParaRight;
668 generateText(text, classe, marker);
673 This function is called when the documentation for an
674 example is being formatted. It outputs the list of source
675 files comprising the example, and the list of images used
676 by the example. The images are copied into a subtree of
677 \c{...doc/html/images/used-in-examples/...}
679 void Generator::generateFileList(const FakeNode* fake,
681 Node::SubType subtype,
686 OpenedList openedList(OpenedList::Bullet);
688 text << Atom::ParaLeft << tag << Atom::ParaRight
689 << Atom(Atom::ListLeft, openedList.styleString());
691 foreach (const Node* child, fake->childNodes()) {
692 if (child->subType() == subtype) {
694 QString file = child->name();
695 if (subtype == Node::Image) {
696 if (!file.isEmpty()) {
698 QString userFriendlyFilePath;
699 QString srcPath = Config::findFile(fake->location(),
704 userFriendlyFilePath);
705 userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
707 QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
708 if (!dirInfo.mkpath(imgOutDir))
709 fake->location().fatal(tr("Cannot create output directory '%1'")
712 QString imgOutName = Config::copyFile(fake->location(),
721 text << Atom(Atom::ListItemNumber, openedList.numberString())
722 << Atom(Atom::ListItemLeft, openedList.styleString())
724 << Atom(Atom::Link, file)
725 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
727 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
729 << Atom(Atom::ListItemRight, openedList.styleString());
732 text << Atom(Atom::ListRight, openedList.styleString());
734 generateText(text, fake, marker);
737 void Generator::generateExampleFiles(const FakeNode *fake, CodeMarker *marker)
739 if (fake->childNodes().isEmpty())
741 generateFileList(fake, marker, Node::File, QString("Files:"));
742 generateFileList(fake, marker, Node::Image, QString("Images:"));
745 QString Generator::indent(int level, const QString& markedCode)
754 while (i < (int) markedCode.length()) {
755 if (markedCode.at(i) == QLatin1Char('\n')) {
760 for (int j = 0; j < level; j++)
761 t += QLatin1Char(' ');
765 t += markedCode.at(i++);
770 QString Generator::plainCode(const QString& markedCode)
772 QString t = markedCode;
773 t.replace(tag, QString());
774 t.replace(quot, QLatin1String("\""));
775 t.replace(gt, QLatin1String(">"));
776 t.replace(lt, QLatin1String("<"));
777 t.replace(amp, QLatin1String("&"));
781 QString Generator::typeString(const Node *node)
783 switch (node->type()) {
784 case Node::Namespace:
790 switch (node->subType()) {
793 case Node::QmlPropertyGroup:
794 return "property group";
795 case Node::QmlBasicType:
798 return "documentation";
810 return "documentation";
815 Returns a relative path name for an image.
817 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
819 QString userFriendlyFilePath;
820 QString filePath = Config::findFile(
821 relative->doc().location(), imageFiles, imageDirs, fileBase,
822 imgFileExts[format()], userFriendlyFilePath);
824 if (filePath.isEmpty())
827 QString path = Config::copyFile(relative->doc().location(),
829 userFriendlyFilePath,
830 outputDir() + QLatin1String("/images"));
831 QString images = "images";
833 images.append(QLatin1Char('/'));
834 return images + path;
837 void Generator::setImageFileExtensions(const QStringList& extensions)
839 imgFileExts[format()] = extensions;
842 void Generator::unknownAtom(const Atom *atom)
844 Location::internalError(tr("unknown atom type '%1' in %2 generator")
845 .arg(atom->typeString()).arg(format()));
848 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
850 return atom->next() != 0 && atom->next()->type() == expectedAtomType;
853 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
855 if (node->type() == Node::Function) {
856 const FunctionNode *func = static_cast<const FunctionNode *>(node);
857 if (func->overloadNumber() == 1) {
858 QString alternateName;
859 const FunctionNode *alternateFunc = 0;
861 if (func->name().startsWith("set") && func->name().size() >= 4) {
862 alternateName = func->name()[3].toLower();
863 alternateName += func->name().mid(4);
864 alternateFunc = func->parent()->findFunctionNode(alternateName);
866 if (!alternateFunc) {
867 alternateName = "is" + func->name().mid(3);
868 alternateFunc = func->parent()->findFunctionNode(alternateName);
869 if (!alternateFunc) {
870 alternateName = "has" + func->name().mid(3);
871 alternateFunc = func->parent()->findFunctionNode(alternateName);
875 else if (!func->name().isEmpty()) {
876 alternateName = "set";
877 alternateName += func->name()[0].toUpper();
878 alternateName += func->name().mid(1);
879 alternateFunc = func->parent()->findFunctionNode(alternateName);
882 if (alternateFunc && alternateFunc->access() != Node::Private) {
884 for (i = 0; i < alsoList.size(); ++i) {
885 if (alsoList.at(i).toString().contains(alternateName))
889 if (i == alsoList.size()) {
890 alternateName += "()";
893 also << Atom(Atom::Link, alternateName)
894 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
896 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
897 alsoList.prepend(also);
904 QMap<QString, QString>& Generator::formattingLeftMap()
906 return fmtLeftMaps[format()];
909 QMap<QString, QString>& Generator::formattingRightMap()
911 return fmtRightMaps[format()];
915 Trims trailimng whitespace off the \a string and returns
918 QString Generator::trimmedTrailing(const QString& string)
920 QString trimmed = string;
921 while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
922 trimmed.truncate(trimmed.length() - 1);
926 void Generator::generateStatus(const Node *node, CodeMarker *marker)
930 switch (node->status()) {
931 case Node::Commendable:
934 case Node::Preliminary:
935 text << Atom::ParaLeft
936 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
939 << " is under development and is subject to change."
940 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
943 case Node::Deprecated:
944 text << Atom::ParaLeft;
945 if (node->isInnerNode())
946 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
947 text << "This " << typeString(node) << " is deprecated.";
948 if (node->isInnerNode())
949 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
950 text << Atom::ParaRight;
953 text << Atom::ParaLeft;
954 if (node->isInnerNode())
955 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
956 text << "This " << typeString(node) << " is obsolete.";
957 if (node->isInnerNode())
958 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
959 text << " It is provided to keep old source code working. "
960 << "We strongly advise against "
961 << "using it in new code." << Atom::ParaRight;
964 // reimplemented in HtmlGenerator subclass
965 if (node->isInnerNode()) {
966 text << Atom::ParaLeft
967 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
970 << " is part of the Qt 3 compatibility layer."
971 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
972 << " It is provided to keep old source code working. "
973 << "We strongly advise against "
974 << "using it in new code. See "
975 << Atom(Atom::AutoLink, "Porting to Qt 4")
976 << " for more information."
984 generateText(text, node, marker);
987 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
991 Node::ThreadSafeness threadSafeness = node->threadSafeness();
994 rlink << Atom(Atom::Link,"reentrant")
995 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
997 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1000 tlink << Atom(Atom::Link,"thread-safe")
1001 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1003 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1005 switch (threadSafeness) {
1006 case Node::UnspecifiedSafeness:
1008 case Node::NonReentrant:
1009 text << Atom::ParaLeft
1010 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1012 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1020 case Node::Reentrant:
1021 case Node::ThreadSafe:
1022 text << Atom::ParaLeft
1023 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1025 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1028 if (node->isInnerNode()) {
1029 const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1030 text << "All functions in this "
1033 if (threadSafeness == Node::ThreadSafe)
1038 bool exceptions = false;
1040 NodeList threadsafe;
1041 NodeList nonreentrant;
1042 NodeList::ConstIterator c = innerNode->childNodes().begin();
1043 while (c != innerNode->childNodes().end()) {
1045 if ((*c)->status() != Node::Obsolete){
1046 switch ((*c)->threadSafeness()) {
1047 case Node::Reentrant:
1048 reentrant.append(*c);
1049 if (threadSafeness == Node::ThreadSafe)
1052 case Node::ThreadSafe:
1053 threadsafe.append(*c);
1054 if (threadSafeness == Node::Reentrant)
1057 case Node::NonReentrant:
1058 nonreentrant.append(*c);
1069 else if (threadSafeness == Node::Reentrant) {
1070 if (nonreentrant.isEmpty()) {
1071 if (!threadsafe.isEmpty()) {
1073 appendFullNames(text,threadsafe,innerNode,marker);
1074 singularPlural(text,threadsafe);
1075 text << " also " << tlink << ".";
1081 text << ", except for ";
1082 appendFullNames(text,nonreentrant,innerNode,marker);
1084 singularPlural(text,nonreentrant);
1085 text << " nonreentrant.";
1086 if (!threadsafe.isEmpty()) {
1088 appendFullNames(text,threadsafe,innerNode,marker);
1089 singularPlural(text,threadsafe);
1090 text << " " << tlink << ".";
1094 else { // thread-safe
1095 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1096 text << ", except for ";
1097 if (!reentrant.isEmpty()) {
1098 appendFullNames(text,reentrant,innerNode,marker);
1100 singularPlural(text,reentrant);
1101 text << " only " << rlink;
1102 if (!nonreentrant.isEmpty())
1105 if (!nonreentrant.isEmpty()) {
1106 appendFullNames(text,nonreentrant,innerNode,marker);
1108 singularPlural(text,nonreentrant);
1109 text << " nonreentrant.";
1116 text << "This " << typeString(node) << " is ";
1117 if (threadSafeness == Node::ThreadSafe)
1123 text << Atom::ParaRight;
1125 generateText(text,node,marker);
1128 void Generator::generateSince(const Node *node, CodeMarker *marker)
1130 if (!node->since().isEmpty()) {
1132 text << Atom::ParaLeft
1134 << typeString(node);
1135 if (node->type() == Node::Enum)
1136 text << " was introduced or modified in ";
1138 text << " was introduced in ";
1140 QStringList since = node->since().split(QLatin1Char(' '));
1141 if (since.count() == 1) {
1142 // Handle legacy use of \since <version>.
1143 if (project.isEmpty())
1147 text << " " << since[0];
1149 // Reconstruct the <project> <version> string.
1150 text << " " << since.join(" ");
1153 text << "." << Atom::ParaRight;
1154 generateText(text, node, marker);
1158 void Generator::generateReimplementedFrom(const FunctionNode *func,
1161 if (func->reimplementedFrom() != 0) {
1162 const FunctionNode *from = func->reimplementedFrom();
1163 if (from->access() != Node::Private &&
1164 from->parent()->access() != Node::Private) {
1166 text << Atom::ParaLeft << "Reimplemented from ";
1167 QString fullName = from->parent()->name() + "::" + from->name() + "()";
1168 appendFullName(text, from->parent(), fullName, from);
1169 text << "." << Atom::ParaRight;
1170 generateText(text, func, marker);
1175 const Atom *Generator::generateAtomList(const Atom *atom,
1176 const Node *relative,
1182 if (atom->type() == Atom::FormatIf) {
1183 int numAtoms0 = numAtoms;
1184 bool rightFormat = canHandleFormat(atom->string());
1185 atom = generateAtomList(atom->next(),
1188 generate && rightFormat,
1193 if (atom->type() == Atom::FormatElse) {
1195 atom = generateAtomList(atom->next(),
1198 generate && !rightFormat,
1204 if (atom->type() == Atom::FormatEndif) {
1205 if (generate && numAtoms0 == numAtoms) {
1206 relative->location().warning(tr("Output format %1 not handled %2")
1207 .arg(format()).arg(outFileName()));
1208 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
1209 generateAtomList(&unhandledFormatAtom,
1215 atom = atom->next();
1218 else if (atom->type() == Atom::FormatElse ||
1219 atom->type() == Atom::FormatEndif) {
1225 n += generateAtom(atom, relative, marker);
1229 atom = atom->next();
1235 void Generator::appendFullName(Text& text,
1236 const Node *apparentNode,
1237 const Node *relative,
1239 const Node *actualNode)
1241 if (actualNode == 0)
1242 actualNode = apparentNode;
1243 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
1244 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1245 << Atom(Atom::String, marker->plainFullName(apparentNode, relative))
1246 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1249 void Generator::appendFullName(Text& text,
1250 const Node *apparentNode,
1251 const QString& fullName,
1252 const Node *actualNode)
1254 if (actualNode == 0)
1255 actualNode = apparentNode;
1256 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
1257 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1258 << Atom(Atom::String, fullName)
1259 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1262 void Generator::appendFullNames(Text& text,
1263 const NodeList& nodes,
1264 const Node* relative,
1267 NodeList::ConstIterator n = nodes.begin();
1269 while (n != nodes.end()) {
1270 appendFullName(text,*n,relative,marker);
1271 text << comma(index++,nodes.count());
1276 void Generator::appendSortedNames(Text& text,
1277 const ClassNode *classe,
1278 const QList<RelatedClass> &classes,
1281 QList<RelatedClass>::ConstIterator r;
1282 QMap<QString,Text> classMap;
1285 r = classes.begin();
1286 while (r != classes.end()) {
1287 if ((*r).node->access() == Node::Public &&
1288 (*r).node->status() != Node::Internal
1289 && !(*r).node->doc().isEmpty()) {
1291 appendFullName(className, (*r).node, classe, marker);
1292 classMap[className.toString().toLower()] = className;
1297 QStringList classNames = classMap.keys();
1300 foreach (const QString &className, classNames) {
1301 text << classMap[className];
1302 text << separator(index++, classNames.count());
1306 void Generator::appendSortedQmlNames(Text& text,
1308 const NodeList& subs,
1311 QMap<QString,Text> classMap;
1314 for (int i = 0; i < subs.size(); ++i) {
1316 if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
1317 (base->qmlModuleIdentifier() == subs[i]->qmlModuleIdentifier())) {
1318 appendFullName(t, subs[i], base, marker);
1319 classMap[t.toString().toLower()] = t;
1323 QStringList names = classMap.keys();
1326 foreach (const QString &name, names) {
1327 text << classMap[name];
1328 text << separator(index++, names.count());
1332 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1335 atom = atom->next();
1336 while (atom != 0 && atom->type() != type) {
1338 atom = atom->next();
1343 QString Generator::fullName(const Node *node,
1344 const Node *relative,
1345 CodeMarker *marker) const
1347 if (node->type() == Node::Fake) {
1348 const FakeNode* fn = static_cast<const FakeNode *>(node);
1350 // Removed for QTBUG-22870
1351 if (!fn->qmlModuleIdentifier().isEmpty())
1352 return fn->qmlModuleIdentifier() + QLatin1Char(' ') + fn->title();
1356 else if (node->type() == Node::Class &&
1357 !(static_cast<const ClassNode *>(node))->serviceName().isEmpty())
1358 return (static_cast<const ClassNode *>(node))->serviceName();
1360 return marker->plainFullName(node, relative);
1363 QString Generator::outputPrefix(const QString &nodeType)
1365 return outputPrefixes[nodeType];
1369 Looks up the tag \a t in the map of metadata values for the
1370 current topic in \a inner. If a value for the tag is found,
1371 the value is returned.
1373 \note If \a t is found in the metadata map, it is erased.
1374 i.e. Once you call this function for a particular \a t,
1377 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1380 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1381 QStringMultiMap::iterator i = metaTagMap.find(t);
1382 if (i != metaTagMap.end()) {
1384 metaTagMap.erase(i);
1390 Looks up the tag \a t in the map of metadata values for the
1391 current topic in \a inner. If values for the tag are found,
1392 they are returned in a string list.
1394 \note If \a t is found in the metadata map, all the pairs
1395 having the key \a t are erased. i.e. Once you call this
1396 function for a particular \a t, you consume \a t.
1398 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1401 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1402 s = metaTagMap.values(t);
1404 metaTagMap.remove(t);
1409 For generating the "New Classes... in 4.6" section on the
1410 What's New in 4.6" page.
1412 void Generator::findAllSince(const InnerNode *node)
1414 NodeList::const_iterator child = node->childNodes().constBegin();
1416 // Traverse the tree, starting at the node supplied.
1418 while (child != node->childNodes().constEnd()) {
1420 QString sinceString = (*child)->since();
1422 if (((*child)->access() != Node::Private) && !sinceString.isEmpty()) {
1424 // Insert a new entry into each map for each new since string found.
1425 NewSinceMaps::iterator nsmap = newSinceMaps.find(sinceString);
1426 if (nsmap == newSinceMaps.end())
1427 nsmap = newSinceMaps.insert(sinceString,NodeMultiMap());
1429 NewClassMaps::iterator ncmap = newClassMaps.find(sinceString);
1430 if (ncmap == newClassMaps.end())
1431 ncmap = newClassMaps.insert(sinceString,NodeMap());
1433 NewClassMaps::iterator nqcmap = newQmlClassMaps.find(sinceString);
1434 if (nqcmap == newQmlClassMaps.end())
1435 nqcmap = newQmlClassMaps.insert(sinceString,NodeMap());
1437 if ((*child)->type() == Node::Function) {
1438 // Insert functions into the general since map.
1439 FunctionNode *func = static_cast<FunctionNode *>(*child);
1440 if ((func->status() > Node::Obsolete) &&
1441 (func->metaness() != FunctionNode::Ctor) &&
1442 (func->metaness() != FunctionNode::Dtor)) {
1443 nsmap.value().insert(func->name(),(*child));
1446 else if ((*child)->url().isEmpty()) {
1447 if ((*child)->type() == Node::Class && !(*child)->doc().isEmpty()) {
1448 // Insert classes into the since and class maps.
1449 QString className = (*child)->name();
1450 if ((*child)->parent() &&
1451 (*child)->parent()->type() == Node::Namespace &&
1452 !(*child)->parent()->name().isEmpty())
1453 className = (*child)->parent()->name()+"::"+className;
1455 nsmap.value().insert(className,(*child));
1456 ncmap.value().insert(className,(*child));
1458 else if ((*child)->subType() == Node::QmlClass) {
1459 // Insert QML elements into the since and element maps.
1460 QString className = (*child)->name();
1461 if ((*child)->parent() &&
1462 (*child)->parent()->type() == Node::Namespace &&
1463 !(*child)->parent()->name().isEmpty())
1464 className = (*child)->parent()->name()+"::"+className;
1466 nsmap.value().insert(className,(*child));
1467 nqcmap.value().insert(className,(*child));
1469 else if ((*child)->type() == Node::QmlProperty) {
1470 // Insert QML properties into the since map.
1471 QString propertyName = (*child)->name();
1472 nsmap.value().insert(propertyName,(*child));
1476 // Insert external documents into the general since map.
1477 QString name = (*child)->name();
1478 if ((*child)->parent() &&
1479 (*child)->parent()->type() == Node::Namespace &&
1480 !(*child)->parent()->name().isEmpty())
1481 name = (*child)->parent()->name()+"::"+name;
1483 nsmap.value().insert(name,(*child));
1486 // Find child nodes with since commands.
1487 if ((*child)->isInnerNode()) {
1488 findAllSince(static_cast<InnerNode *>(*child));