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 ** 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 Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
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 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
40 ****************************************************************************/
47 #include "codemarker.h"
49 #include "ditaxmlgenerator.h"
51 #include "editdistance.h"
52 #include "generator.h"
53 #include "openedlist.h"
55 #include "separator.h"
56 #include "tokenizer.h"
57 #include "qdocdatabase.h"
61 Generator* Generator::currentGenerator_;
62 QStringList Generator::exampleDirs;
63 QStringList Generator::exampleImgExts;
64 QMap<QString, QMap<QString, QString> > Generator::fmtLeftMaps;
65 QMap<QString, QMap<QString, QString> > Generator::fmtRightMaps;
66 QList<Generator *> Generator::generators;
67 QStringList Generator::imageDirs;
68 QStringList Generator::imageFiles;
69 QMap<QString, QStringList> Generator::imgFileExts;
70 QString Generator::outDir_;
71 QSet<QString> Generator::outputFormats;
72 QHash<QString, QString> Generator::outputPrefixes;
73 QString Generator::project;
74 QStringList Generator::scriptDirs;
75 QStringList Generator::scriptFiles;
76 QString Generator::sinceTitles[] =
80 " New Member Functions",
81 " New Functions in Namespaces",
82 " New Global Functions",
89 " New QML Properties",
91 " New QML Signal Handlers",
95 QStringList Generator::styleDirs;
96 QStringList Generator::styleFiles;
97 bool Generator::debugging_ = false;
98 bool Generator::noLinkErrors_ = false;
99 Generator::Passes Generator::qdocPass_ = Both;
101 void Generator::setDebugSegfaultFlag(bool b)
104 qDebug() << "DEBUG: Setting debug flag.";
106 qDebug() << "DEBUG: Clearing debug flag.";
111 Prints \a message as an aid to debugging the release version.
113 void Generator::debugSegfault(const QString& message)
116 qDebug() << "DEBUG:" << message;
120 Constructs the generator base class. Prepends the newly
121 constructed generator to the list of output generators.
122 Sets a pointer to the QDoc database singleton, which is
123 available to the generator subclasses.
125 Generator::Generator()
133 inSectionHeading_(false),
134 inTableHeader_(false),
135 threeColumnEnumValueTable_(true),
138 qdb_ = QDocDatabase::qdocDB();
139 generators.prepend(this);
143 Destroys the generator after removing it from the list of
146 Generator::~Generator()
148 generators.removeAll(this);
151 void Generator::appendFullName(Text& text,
152 const Node *apparentNode,
153 const Node *relative,
154 const Node *actualNode)
157 actualNode = apparentNode;
158 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
159 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
160 << Atom(Atom::String, apparentNode->plainFullName(relative))
161 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
164 void Generator::appendFullName(Text& text,
165 const Node *apparentNode,
166 const QString& fullName,
167 const Node *actualNode)
170 actualNode = apparentNode;
171 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
172 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
173 << Atom(Atom::String, fullName)
174 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
177 void Generator::appendFullNames(Text& text, const NodeList& nodes, const Node* relative)
179 NodeList::ConstIterator n = nodes.constBegin();
181 while (n != nodes.constEnd()) {
182 appendFullName(text,*n,relative);
183 text << comma(index++,nodes.count());
188 void Generator::appendSortedNames(Text& text, const ClassNode *classe, const QList<RelatedClass> &classes)
190 QList<RelatedClass>::ConstIterator r;
191 QMap<QString,Text> classMap;
194 r = classes.constBegin();
195 while (r != classes.constEnd()) {
196 if ((*r).node->access() == Node::Public &&
197 (*r).node->status() != Node::Internal
198 && !(*r).node->doc().isEmpty()) {
200 appendFullName(className, (*r).node, classe);
201 classMap[className.toString().toLower()] = className;
206 QStringList classNames = classMap.keys();
209 foreach (const QString &className, classNames) {
210 text << classMap[className];
211 text << separator(index++, classNames.count());
215 void Generator::appendSortedQmlNames(Text& text, const Node* base, const NodeList& subs)
217 QMap<QString,Text> classMap;
220 for (int i = 0; i < subs.size(); ++i) {
222 if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
223 (base->qmlModuleIdentifier() == subs[i]->qmlModuleIdentifier())) {
224 appendFullName(t, subs[i], base);
225 classMap[t.toString().toLower()] = t;
229 QStringList names = classMap.keys();
232 foreach (const QString &name, names) {
233 text << classMap[name];
234 text << separator(index++, names.count());
238 QMultiMap<QString,QString> outFileNames;
243 void Generator::writeOutFileNames()
245 QFile* files = new QFile("/Users/msmith/depot/qt5/qtdoc/outputlist.txt");
246 files->open(QFile::WriteOnly);
247 QTextStream* filesout = new QTextStream(files);
248 QMultiMap<QString,QString>::ConstIterator i = outFileNames.begin();
249 while (i != outFileNames.end()) {
250 (*filesout) << i.key() << "\n";
258 Creates the file named \a fileName in the output directory.
259 Attaches a QTextStream to the created file, which is written
260 to all over the place using out().
262 void Generator::beginSubPage(const InnerNode* node, const QString& fileName)
264 QString path = outputDir() + QLatin1Char('/');
265 if (!node->outputSubdirectory().isEmpty())
266 path += node->outputSubdirectory() + QLatin1Char('/');
268 Generator::debugSegfault("Writing: " + path);
269 outFileNames.insert(fileName,fileName);
270 QFile* outFile = new QFile(path);
271 if (!outFile->open(QFile::WriteOnly))
272 node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
273 QTextStream* out = new QTextStream(outFile);
276 out->setCodec(outputCodec);
277 outStreamStack.push(out);
278 const_cast<InnerNode*>(node)->setOutputFileName(fileName);
282 Flush the text stream associated with the subpage, and
283 then pop it off the text stream stack and delete it.
284 This terminates output of the subpage.
286 void Generator::endSubPage()
288 outStreamStack.top()->flush();
289 delete outStreamStack.top()->device();
290 delete outStreamStack.pop();
293 QString Generator::fileBase(const Node *node) const
296 node = node->relates();
297 else if (!node->isInnerNode())
298 node = node->parent();
299 if (node->subType() == Node::QmlPropertyGroup) {
300 node = node->parent();
303 QString base = node->doc().baseName();
307 const Node *p = node;
310 const Node *pp = p->parent();
311 base.prepend(p->name());
312 if (!p->qmlModuleIdentifier().isEmpty())
313 base.prepend(p->qmlModuleIdentifier()+QChar('-'));
315 To avoid file name conflicts in the html directory,
316 we prepend a prefix (by default, "qml-") to the file name of QML
319 if ((p->subType() == Node::QmlClass) ||
320 (p->subType() == Node::QmlBasicType)) {
321 base.prepend(outputPrefix(QLatin1String("QML")));
323 if (!pp || pp->name().isEmpty() || pp->type() == Node::Document)
325 base.prepend(QLatin1Char('-'));
328 if (node->type() == Node::Document) {
329 if (node->subType() == Node::Collision) {
330 const NameCollisionNode* ncn = static_cast<const NameCollisionNode*>(node);
331 if (ncn->currentChild())
332 return fileBase(ncn->currentChild());
333 base.prepend("collision-");
335 //Was QDOC2_COMPAT, required for index.html
336 if (base.endsWith(".html"))
337 base.truncate(base.length() - 5);
339 if (node->subType() == Node::QmlModule) {
340 base.prepend("qmlmodule-");
342 if (node->subType() == Node::Module) {
343 base.append("-module");
347 // the code below is effectively equivalent to:
348 // base.replace(QRegExp("[^A-Za-z0-9]+"), " ");
349 // base = base.trimmed();
350 // base.replace(QLatin1Char(' '), QLatin1Char('-'));
351 // base = base.toLower();
352 // as this function accounted for ~8% of total running time
353 // we optimize a bit...
356 // +5 prevents realloc in fileName() below
357 res.reserve(base.size() + 5);
359 for (int i = 0; i != base.size(); ++i) {
360 QChar c = base.at(i);
361 uint u = c.unicode();
362 if (u >= 'A' && u <= 'Z')
364 if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
365 res += QLatin1Char(u);
369 res += QLatin1Char('-');
373 while (res.endsWith(QLatin1Char('-')))
379 If the \a node has a URL, return the URL as the file name.
380 Otherwise, construct the file name from the fileBase() and
381 the fileExtension(), and return the constructed name.
383 QString Generator::fileName(const Node* node) const
385 if (!node->url().isEmpty())
388 QString name = fileBase(node);
389 name += QLatin1Char('.');
390 name += fileExtension();
394 QMap<QString, QString>& Generator::formattingLeftMap()
396 return fmtLeftMaps[format()];
399 QMap<QString, QString>& Generator::formattingRightMap()
401 return fmtRightMaps[format()];
405 Returns the full document location.
407 QString Generator::fullDocumentLocation(const Node *node, bool subdir)
411 if (!node->url().isEmpty())
419 If the output is being sent to subdirectories of the
420 output directory, and if the subdir parameter is set,
421 prepend the subdirectory name + '/' to the result.
424 fdl = node->outputSubdirectory();
426 fdl.append(QLatin1Char('/'));
428 if (node->type() == Node::Namespace) {
430 // The root namespace has no name - check for this before creating
431 // an attribute containing the location of any documentation.
433 if (!fileBase(node).isEmpty())
434 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
438 else if (node->type() == Node::Document) {
439 if ((node->subType() == Node::QmlClass) ||
440 (node->subType() == Node::QmlBasicType)) {
441 QString fb = fileBase(node);
442 if (fb.startsWith(Generator::outputPrefix(QLatin1String("QML"))))
443 return fb + QLatin1Char('.') + currentGenerator()->fileExtension();
446 if (!node->qmlModuleName().isEmpty()) {
447 mq = node->qmlModuleIdentifier().replace(QChar('.'),QChar('-'));
448 mq = mq.toLower() + QLatin1Char('-');
450 return fdl+ Generator::outputPrefix(QLatin1String("QML")) + mq +
451 fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
455 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
458 else if (fileBase(node).isEmpty())
461 Node *parentNode = 0;
463 if ((parentNode = node->relates())) {
464 parentName = fullDocumentLocation(node->relates());
466 else if ((parentNode = node->parent())) {
467 if (parentNode->subType() == Node::QmlPropertyGroup) {
468 parentNode = parentNode->parent();
469 parentName = fullDocumentLocation(parentNode);
472 parentName = fullDocumentLocation(node->parent());
476 switch (node->type()) {
478 case Node::Namespace:
479 if (parentNode && !parentNode->name().isEmpty()) {
480 parentName.remove(QLatin1Char('.') + currentGenerator()->fileExtension());
481 parentName += QLatin1Char('-')
482 + fileBase(node).toLower() + QLatin1Char('.') + currentGenerator()->fileExtension();
484 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
489 const FunctionNode *functionNode =
490 static_cast<const FunctionNode *>(node);
492 if (functionNode->metaness() == FunctionNode::Dtor)
493 anchorRef = "#dtor." + functionNode->name().mid(1);
495 else if (functionNode->associatedProperty())
496 return fullDocumentLocation(functionNode->associatedProperty());
498 else if (functionNode->overloadNumber() > 1)
499 anchorRef = QLatin1Char('#') + functionNode->name()
500 + QLatin1Char('-') + QString::number(functionNode->overloadNumber());
502 anchorRef = QLatin1Char('#') + functionNode->name();
506 Use node->name() instead of fileBase(node) as
507 the latter returns the name in lower-case. For
508 HTML anchors, we need to preserve the case.
511 anchorRef = QLatin1Char('#') + node->name() + "-enum";
514 anchorRef = QLatin1Char('#') + node->name() + "-typedef";
517 anchorRef = QLatin1Char('#') + node->name() + "-prop";
519 case Node::QmlProperty:
520 anchorRef = QLatin1Char('#') + node->name() + "-prop";
522 case Node::QmlSignal:
523 anchorRef = QLatin1Char('#') + node->name() + "-signal";
525 case Node::QmlSignalHandler:
526 anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
528 case Node::QmlMethod:
529 anchorRef = QLatin1Char('#') + node->name() + "-method";
532 anchorRef = QLatin1Char('#') + node->name() + "-var";
536 parentName = fileBase(node);
537 parentName.replace(QLatin1Char('/'), QLatin1Char('-')).replace(QLatin1Char('.'), QLatin1Char('-'));
538 parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
545 // Various objects can be compat (deprecated) or obsolete.
546 if (node->type() != Node::Class && node->type() != Node::Namespace) {
547 switch (node->status()) {
549 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
550 "-compat." + currentGenerator()->fileExtension());
553 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
554 "-obsolete." + currentGenerator()->fileExtension());
561 return fdl + parentName.toLower() + anchorRef;
564 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
566 QList<Text> alsoList = node->doc().alsoList();
567 supplementAlsoList(node, alsoList);
569 if (!alsoList.isEmpty()) {
571 text << Atom::ParaLeft
572 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
574 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
576 for (int i = 0; i < alsoList.size(); ++i)
577 text << alsoList.at(i) << separator(i, alsoList.size());
579 text << Atom::ParaRight;
580 generateText(text, node, marker);
584 int Generator::generateAtom(const Atom * /* atom */,
585 const Node * /* relative */,
586 CodeMarker * /* marker */)
591 const Atom *Generator::generateAtomList(const Atom *atom,
592 const Node *relative,
598 if (atom->type() == Atom::FormatIf) {
599 int numAtoms0 = numAtoms;
600 bool rightFormat = canHandleFormat(atom->string());
601 atom = generateAtomList(atom->next(),
604 generate && rightFormat,
609 if (atom->type() == Atom::FormatElse) {
611 atom = generateAtomList(atom->next(),
614 generate && !rightFormat,
620 if (atom->type() == Atom::FormatEndif) {
621 if (generate && numAtoms0 == numAtoms) {
622 relative->location().warning(tr("Output format %1 not handled %2")
623 .arg(format()).arg(outFileName()));
624 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
625 generateAtomList(&unhandledFormatAtom,
634 else if (atom->type() == Atom::FormatElse ||
635 atom->type() == Atom::FormatEndif) {
641 n += generateAtom(atom, relative, marker);
651 void Generator::generateBody(const Node *node, CodeMarker *marker)
655 if (node->type() == Node::Document) {
656 const DocNode *dn = static_cast<const DocNode *>(node);
657 if ((dn->subType() == Node::File) || (dn->subType() == Node::Image)) {
661 if (node->doc().isEmpty()) {
662 if (!quiet && !node->isReimp()) { // ### might be unnecessary
663 node->location().warning(tr("No documentation for '%1'").arg(node->plainFullName()));
667 if (node->type() == Node::Function) {
668 const FunctionNode *func = static_cast<const FunctionNode *>(node);
669 if (func->reimplementedFrom() != 0)
670 generateReimplementedFrom(func, marker);
673 if (!generateText(node->doc().body(), node, marker)) {
678 if (node->type() == Node::Enum) {
679 const EnumNode *enume = (const EnumNode *) node;
681 QSet<QString> definedItems;
682 QList<EnumItem>::ConstIterator it = enume->items().constBegin();
683 while (it != enume->items().constEnd()) {
684 definedItems.insert((*it).name());
688 QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
689 QSet<QString> allItems = definedItems + documentedItems;
690 if (allItems.count() > definedItems.count() ||
691 allItems.count() > documentedItems.count()) {
692 QSet<QString>::ConstIterator a = allItems.constBegin();
693 while (a != allItems.constEnd()) {
694 if (!definedItems.contains(*a)) {
696 QString best = nearestName(*a, definedItems);
697 if (!best.isEmpty() && !documentedItems.contains(best))
698 details = tr("Maybe you meant '%1'?").arg(best);
700 node->doc().location().warning(tr("No such enum item '%1' in %2")
701 .arg(*a).arg(node->plainFullName()), details);
703 qDebug() << "VOID:" << node->name() << definedItems;
705 else if (!documentedItems.contains(*a)) {
706 node->doc().location().warning(tr("Undocumented enum item '%1' in %2")
707 .arg(*a).arg(node->plainFullName()));
713 else if (node->type() == Node::Function) {
714 const FunctionNode *func = static_cast<const FunctionNode *>(node);
715 QSet<QString> definedParams;
716 QList<Parameter>::ConstIterator p = func->parameters().constBegin();
717 while (p != func->parameters().constEnd()) {
718 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
719 && func->name() != QLatin1String("operator++")
720 && func->name() != QLatin1String("operator--")) {
721 node->doc().location().warning(tr("Missing parameter name"));
724 definedParams.insert((*p).name());
729 QSet<QString> documentedParams = func->doc().parameterNames();
730 QSet<QString> allParams = definedParams + documentedParams;
731 if (allParams.count() > definedParams.count()
732 || allParams.count() > documentedParams.count()) {
733 QSet<QString>::ConstIterator a = allParams.constBegin();
734 while (a != allParams.constEnd()) {
735 if (!definedParams.contains(*a)) {
737 QString best = nearestName(*a, definedParams);
739 details = tr("Maybe you meant '%1'?").arg(best);
741 node->doc().location().warning(
742 tr("No such parameter '%1' in %2").arg(*a).arg(node->plainFullName()),
745 else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
746 bool needWarning = (func->status() > Node::Obsolete);
747 if (func->overloadNumber() > 1) {
748 FunctionNode *primaryFunc =
749 func->parent()->findFunctionNode(func->name());
751 foreach (const Parameter ¶m,
752 primaryFunc->parameters()) {
753 if (param.name() == *a) {
760 if (needWarning && !func->isReimp())
761 node->doc().location().warning(
762 tr("Undocumented parameter '%1' in %2")
763 .arg(*a).arg(node->plainFullName()));
769 Something like this return value check should
770 be implemented at some point.
772 if (func->status() > Node::Obsolete && func->returnType() == "bool"
773 && func->reimplementedFrom() == 0 && !func->isOverload()) {
774 QString body = func->doc().body().toString();
775 if (!body.contains("return", Qt::CaseInsensitive))
776 node->doc().location().warning(tr("Undocumented return value"));
781 if (node->type() == Node::Document) {
782 const DocNode *dn = static_cast<const DocNode *>(node);
783 if (dn->subType() == Node::Example) {
784 generateExampleFiles(dn, marker);
786 else if (dn->subType() == Node::File) {
789 Doc::quoteFromFile(dn->doc().location(), quoter, dn->name());
790 QString code = quoter.quoteTo(dn->location(), QString(), QString());
791 CodeMarker *codeMarker = CodeMarker::markerForFileName(dn->name());
792 text << Atom(codeMarker->atomType(), code);
793 generateText(text, dn, codeMarker);
798 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
802 void Generator::generateExampleFiles(const DocNode *dn, CodeMarker *marker)
804 if (dn->childNodes().isEmpty())
806 generateFileList(dn, marker, Node::File, QString("Files:"));
807 generateFileList(dn, marker, Node::Image, QString("Images:"));
810 void Generator::generateDocNode(DocNode* /* dn */, CodeMarker* /* marker */)
815 This function is called when the documentation for an
816 example is being formatted. It outputs the list of source
817 files comprising the example, and the list of images used
818 by the example. The images are copied into a subtree of
819 \c{...doc/html/images/used-in-examples/...}
821 void Generator::generateFileList(const DocNode* dn,
823 Node::SubType subtype,
828 OpenedList openedList(OpenedList::Bullet);
830 text << Atom::ParaLeft << tag << Atom::ParaRight
831 << Atom(Atom::ListLeft, openedList.styleString());
833 foreach (const Node* child, dn->childNodes()) {
834 if (child->subType() == subtype) {
836 QString file = child->name();
837 if (subtype == Node::Image) {
838 if (!file.isEmpty()) {
840 QString userFriendlyFilePath;
841 QString srcPath = Config::findFile(dn->location(),
846 userFriendlyFilePath);
847 userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
849 QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
850 if (!dirInfo.mkpath(imgOutDir))
851 dn->location().fatal(tr("Cannot create output directory '%1'")
854 QString imgOutName = Config::copyFile(dn->location(),
863 text << Atom(Atom::ListItemNumber, openedList.numberString())
864 << Atom(Atom::ListItemLeft, openedList.styleString())
866 << Atom(Atom::Link, file)
867 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
869 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
871 << Atom(Atom::ListItemRight, openedList.styleString());
874 text << Atom(Atom::ListRight, openedList.styleString());
876 generateText(text, dn, marker);
879 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
881 if (!classe->derivedClasses().isEmpty()) {
883 text << Atom::ParaLeft
884 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
886 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
888 appendSortedNames(text, classe, classe->derivedClasses());
889 text << Atom::ParaRight;
890 generateText(text, classe, marker);
894 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
896 QList<RelatedClass>::ConstIterator r;
899 if (!classe->baseClasses().isEmpty()) {
901 text << Atom::ParaLeft
902 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
904 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
906 r = classe->baseClasses().constBegin();
908 while (r != classe->baseClasses().constEnd()) {
909 text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
910 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
911 << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
912 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
914 if ((*r).access == Node::Protected) {
915 text << " (protected)";
917 else if ((*r).access == Node::Private) {
918 text << " (private)";
920 text << separator(index++, classe->baseClasses().count());
923 text << Atom::ParaRight;
924 generateText(text, classe, marker);
929 Recursive writing of HTML files from the root \a node.
931 \note NameCollisionNodes are skipped here and processed
932 later. See HtmlGenerator::generateCollisionPages() for
935 void Generator::generateInnerNode(InnerNode* node)
937 if (!node->url().isNull())
940 if (node->type() == Node::Document) {
941 DocNode* docNode = static_cast<DocNode*>(node);
942 if (docNode->subType() == Node::ExternalPage)
944 if (docNode->subType() == Node::Image)
946 if (docNode->subType() == Node::QmlPropertyGroup)
948 if (docNode->subType() == Node::Page) {
949 if (node->count() > 0)
950 qDebug("PAGE %s HAS CHILDREN", qPrintable(docNode->title()));
955 Obtain a code marker for the source file.
957 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
959 if (node->parent() != 0) {
961 Skip name collision nodes here and process them
962 later in generateCollisionPages(). Each one is
963 appended to a list for later.
965 if ((node->type() == Node::Document) && (node->subType() == Node::Collision)) {
966 NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
967 collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
970 beginSubPage(node, fileName(node));
971 if (node->type() == Node::Namespace || node->type() == Node::Class) {
972 generateClassLikeNode(node, marker);
974 else if (node->type() == Node::Document) {
975 generateDocNode(static_cast<DocNode*>(node), marker);
981 NodeList::ConstIterator c = node->childNodes().constBegin();
982 while (c != node->childNodes().constEnd()) {
983 if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
984 generateInnerNode((InnerNode*)*c);
991 Generate a list of maintainers in the output
993 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
995 QStringList sl = getMetadataElements(node,"maintainer");
999 text << Atom::ParaLeft
1000 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1001 << "Maintained by: "
1002 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
1004 for (int i = 0; i < sl.size(); ++i)
1005 text << sl.at(i) << separator(i, sl.size());
1007 text << Atom::ParaRight;
1008 generateText(text, node, marker);
1013 Output the "Inherit by" list for the QML element,
1014 if it is inherited by any other elements.
1016 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
1021 QmlClassNode::subclasses(qcn->name(),subs);
1022 if (!subs.isEmpty()) {
1024 text << Atom::ParaLeft << "Inherited by ";
1025 appendSortedQmlNames(text,qcn,subs);
1026 text << Atom::ParaRight;
1027 generateText(text, qcn, marker);
1034 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
1040 Extract sections of markup text surrounded by \e qmltext
1041 and \e endqmltext and output them.
1043 bool Generator::generateQmlText(const Text& text,
1044 const Node *relative,
1046 const QString& /* qmlName */ )
1048 const Atom* atom = text.firstAtom();
1049 bool result = false;
1052 initializeTextOutput();
1054 if (atom->type() != Atom::QmlText)
1055 atom = atom->next();
1057 atom = atom->next();
1058 while (atom && (atom->type() != Atom::EndQmlText)) {
1059 int n = 1 + generateAtom(atom, relative, marker);
1061 atom = atom->next();
1070 void Generator::generateReimplementedFrom(const FunctionNode *func,
1073 if (func->reimplementedFrom() != 0) {
1074 const FunctionNode *from = func->reimplementedFrom();
1075 if (from->access() != Node::Private &&
1076 from->parent()->access() != Node::Private) {
1078 text << Atom::ParaLeft << "Reimplemented from ";
1079 QString fullName = from->parent()->name() + "::" + from->name() + "()";
1080 appendFullName(text, from->parent(), fullName, from);
1081 text << "." << Atom::ParaRight;
1082 generateText(text, func, marker);
1087 void Generator::generateSince(const Node *node, CodeMarker *marker)
1089 if (!node->since().isEmpty()) {
1091 text << Atom::ParaLeft
1093 << typeString(node);
1094 if (node->type() == Node::Enum)
1095 text << " was introduced or modified in ";
1097 text << " was introduced in ";
1099 QStringList since = node->since().split(QLatin1Char(' '));
1100 if (since.count() == 1) {
1101 // Handle legacy use of \since <version>.
1102 if (project.isEmpty())
1106 text << " " << since[0];
1108 // Reconstruct the <project> <version> string.
1109 text << " " << since.join(' ');
1112 text << "." << Atom::ParaRight;
1113 generateText(text, node, marker);
1117 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1121 switch (node->status()) {
1122 case Node::Commendable:
1125 case Node::Preliminary:
1126 text << Atom::ParaLeft
1127 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1130 << " is under development and is subject to change."
1131 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1134 case Node::Deprecated:
1135 text << Atom::ParaLeft;
1136 if (node->isInnerNode())
1137 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1138 text << "This " << typeString(node) << " is deprecated.";
1139 if (node->isInnerNode())
1140 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1141 text << Atom::ParaRight;
1143 case Node::Obsolete:
1144 text << Atom::ParaLeft;
1145 if (node->isInnerNode())
1146 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1147 text << "This " << typeString(node) << " is obsolete.";
1148 if (node->isInnerNode())
1149 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1150 text << " It is provided to keep old source code working. "
1151 << "We strongly advise against "
1152 << "using it in new code." << Atom::ParaRight;
1155 // reimplemented in HtmlGenerator subclass
1156 if (node->isInnerNode()) {
1157 text << Atom::ParaLeft
1158 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1161 << " is part of the Qt compatibility layer."
1162 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1163 << " It is provided to keep old source code working. "
1164 << "We strongly advise against using it in new code."
1168 case Node::Internal:
1172 generateText(text, node, marker);
1175 bool Generator::generateText(const Text& text,
1176 const Node *relative,
1179 bool result = false;
1180 if (text.firstAtom() != 0) {
1182 initializeTextOutput();
1183 generateAtomList(text.firstAtom(),
1193 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1196 Node::ThreadSafeness threadSafeness = node->threadSafeness();
1199 rlink << Atom(Atom::Link,"reentrant")
1200 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1202 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1205 tlink << Atom(Atom::Link,"thread-safe")
1206 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1208 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1210 switch (threadSafeness) {
1211 case Node::UnspecifiedSafeness:
1213 case Node::NonReentrant:
1214 text << Atom::ParaLeft
1215 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1217 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1225 case Node::Reentrant:
1226 case Node::ThreadSafe:
1227 text << Atom::ParaLeft
1228 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1230 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1233 if (node->isInnerNode()) {
1234 const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1235 text << "All functions in this "
1238 if (threadSafeness == Node::ThreadSafe)
1243 bool exceptions = false;
1245 NodeList threadsafe;
1246 NodeList nonreentrant;
1247 NodeList::ConstIterator c = innerNode->childNodes().constBegin();
1248 while (c != innerNode->childNodes().constEnd()) {
1250 if ((*c)->status() != Node::Obsolete){
1251 switch ((*c)->threadSafeness()) {
1252 case Node::Reentrant:
1253 reentrant.append(*c);
1254 if (threadSafeness == Node::ThreadSafe)
1257 case Node::ThreadSafe:
1258 threadsafe.append(*c);
1259 if (threadSafeness == Node::Reentrant)
1262 case Node::NonReentrant:
1263 nonreentrant.append(*c);
1274 else if (threadSafeness == Node::Reentrant) {
1275 if (nonreentrant.isEmpty()) {
1276 if (!threadsafe.isEmpty()) {
1278 appendFullNames(text,threadsafe,innerNode);
1279 singularPlural(text,threadsafe);
1280 text << " also " << tlink << ".";
1286 text << ", except for ";
1287 appendFullNames(text,nonreentrant,innerNode);
1289 singularPlural(text,nonreentrant);
1290 text << " nonreentrant.";
1291 if (!threadsafe.isEmpty()) {
1293 appendFullNames(text,threadsafe,innerNode);
1294 singularPlural(text,threadsafe);
1295 text << " " << tlink << ".";
1299 else { // thread-safe
1300 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1301 text << ", except for ";
1302 if (!reentrant.isEmpty()) {
1303 appendFullNames(text,reentrant,innerNode);
1305 singularPlural(text,reentrant);
1306 text << " only " << rlink;
1307 if (!nonreentrant.isEmpty())
1310 if (!nonreentrant.isEmpty()) {
1311 appendFullNames(text,nonreentrant,innerNode);
1313 singularPlural(text,nonreentrant);
1314 text << " nonreentrant.";
1321 text << "This " << typeString(node) << " is ";
1322 if (threadSafeness == Node::ThreadSafe)
1328 text << Atom::ParaRight;
1330 generateText(text,node,marker);
1334 Traverses the database recursivly to generate all the documentation.
1336 void Generator::generateTree()
1338 generateInnerNode(qdb_->treeRoot());
1341 Generator *Generator::generatorForFormat(const QString& format)
1343 QList<Generator *>::ConstIterator g = generators.constBegin();
1344 while (g != generators.constEnd()) {
1345 if ((*g)->format() == format)
1353 This function can be called if getLink() returns an empty
1354 string. It tests the \a atom string to see if it is a link
1355 of the form <element> :: <name>, where <element> is a QML
1356 element or component without a module qualifier. If so, it
1357 constructs a link to the <name> clause on the disambiguation
1358 page for <element> and returns that link string. It also
1359 adds the <name> as a target in the NameCollisionNode for
1360 <element>. These clauses are then constructed when the
1361 disambiguation page is actually generated.
1363 QString Generator::getCollisionLink(const Atom* atom)
1366 if (!atom->string().contains("::"))
1368 QStringList path = atom->string().split("::");
1369 NameCollisionNode* ncn = qdb_->findCollisionNode(path[0]);
1372 if (atom->next() && atom->next()->next()) {
1373 if (atom->next()->type() == Atom::FormattingLeft &&
1374 atom->next()->next()->type() == Atom::String)
1375 label = atom->next()->next()->string();
1377 ncn->addLinkTarget(path[1],label);
1378 link = fileName(ncn);
1379 link += QLatin1Char('#');
1380 link += Doc::canonicalTitle(path[1]);
1387 Looks up the tag \a t in the map of metadata values for the
1388 current topic in \a inner. If a value for the tag is found,
1389 the value is returned.
1391 \note If \a t is found in the metadata map, it is erased.
1392 i.e. Once you call this function for a particular \a t,
1395 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1398 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1399 QStringMultiMap::iterator i = metaTagMap.find(t);
1400 if (i != metaTagMap.end()) {
1402 metaTagMap.erase(i);
1408 Looks up the tag \a t in the map of metadata values for the
1409 current topic in \a inner. If values for the tag are found,
1410 they are returned in a string list.
1412 \note If \a t is found in the metadata map, all the pairs
1413 having the key \a t are erased. i.e. Once you call this
1414 function for a particular \a t, you consume \a t.
1416 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1419 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1420 s = metaTagMap.values(t);
1422 metaTagMap.remove(t);
1427 Returns a relative path name for an image.
1429 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1431 QString userFriendlyFilePath;
1432 QString filePath = Config::findFile(relative->doc().location(),
1436 imgFileExts[format()],
1437 userFriendlyFilePath);
1439 if (filePath.isEmpty())
1442 QString path = Config::copyFile(relative->doc().location(),
1444 userFriendlyFilePath,
1445 outputDir() + QLatin1String("/images"));
1446 QString images = "images";
1448 images.append(QLatin1Char('/'));
1449 return images + path;
1452 QString Generator::indent(int level, const QString& markedCode)
1461 while (i < (int) markedCode.length()) {
1462 if (markedCode.at(i) == QLatin1Char('\n')) {
1467 for (int j = 0; j < level; j++)
1468 t += QLatin1Char(' ');
1472 t += markedCode.at(i++);
1477 void Generator::initialize(const Config &config)
1479 outputFormats = config.getOutputFormats();
1480 if (!outputFormats.isEmpty()) {
1481 outDir_ = config.getOutputDir();
1483 if (outDir_.isEmpty())
1484 config.lastLocation().fatal(tr("No output directory specified in "
1485 "configuration file or on the command line"));
1488 if (dirInfo.exists(outDir_)) {
1489 if (!runGenerateOnly()) {
1490 if (!Config::removeDirContents(outDir_))
1491 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1495 if (!dirInfo.mkpath(outDir_))
1496 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1499 if (!dirInfo.exists(outDir_ + "/images") && !dirInfo.mkdir(outDir_ + "/images"))
1500 config.lastLocation().fatal(tr("Cannot create images directory '%1'").arg(outDir_ + "/images"));
1501 if (!dirInfo.exists(outDir_ + "/images/used-in-examples") && !dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1502 config.lastLocation().fatal(tr("Cannot create images used in examples directory '%1'").arg(outDir_ + "/images/used-in-examples"));
1503 if (!dirInfo.exists(outDir_ + "/scripts") && !dirInfo.mkdir(outDir_ + "/scripts"))
1504 config.lastLocation().fatal(tr("Cannot create scripts directory '%1'").arg(outDir_ + "/scripts"));
1505 if (!dirInfo.exists(outDir_ + "/style") && !dirInfo.mkdir(outDir_ + "/style"))
1506 config.lastLocation().fatal(tr("Cannot create style directory '%1'").arg(outDir_ + "/style"));
1509 imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1510 imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1511 scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1512 scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1513 styleFiles = config.getCleanPathList(CONFIG_STYLES);
1514 styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1515 exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1516 exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS);
1518 QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1519 QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1520 QSet<QString>::ConstIterator f = formats.constBegin();
1521 while (f != formats.constEnd()) {
1522 imgFileExts[*f] = config.getStringList(imagesDotFileExtensions + Config::dot + *f);
1526 QList<Generator *>::ConstIterator g = generators.constBegin();
1527 while (g != generators.constEnd()) {
1528 if (outputFormats.contains((*g)->format())) {
1529 currentGenerator_ = (*g);
1530 (*g)->initializeGenerator(config);
1531 QStringList extraImages = config.getCleanPathList((*g)->format() +
1533 CONFIG_EXTRAIMAGES);
1534 QStringList::ConstIterator e = extraImages.constBegin();
1535 while (e != extraImages.constEnd()) {
1536 QString filePath = *e;
1537 if (!filePath.isEmpty())
1538 Config::copyFile(config.lastLocation(), filePath, filePath,
1539 (*g)->outputDir() + "/images");
1543 // Documentation template handling
1544 QStringList scripts = config.getPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1545 e = scripts.constBegin();
1546 while (e != scripts.constEnd()) {
1547 QString filePath = *e;
1548 if (!filePath.isEmpty())
1549 Config::copyFile(config.lastLocation(), filePath, filePath,
1550 (*g)->outputDir() + "/scripts");
1554 QStringList styles = config.getPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1555 e = styles.constBegin();
1556 while (e != styles.constEnd()) {
1557 QString filePath = *e;
1558 if (!filePath.isEmpty())
1559 Config::copyFile(config.lastLocation(), filePath, filePath,
1560 (*g)->outputDir() + "/style");
1567 QRegExp secondParamAndAbove("[\2-\7]");
1568 QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1569 QSet<QString>::ConstIterator n = formattingNames.constBegin();
1570 while (n != formattingNames.constEnd()) {
1571 QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1572 QSet<QString> formats = config.subVars(formattingDotName);
1573 QSet<QString>::ConstIterator f = formats.constBegin();
1574 while (f != formats.constEnd()) {
1575 QString def = config.getString(formattingDotName + Config::dot + *f);
1576 if (!def.isEmpty()) {
1577 int numParams = Config::numParams(def);
1578 int numOccs = def.count("\1");
1579 if (numParams != 1) {
1580 config.lastLocation().warning(tr("Formatting '%1' must "
1582 "parameter (found %2)")
1583 .arg(*n).arg(numParams));
1585 else if (numOccs > 1) {
1586 config.lastLocation().fatal(tr("Formatting '%1' must "
1587 "contain exactly one "
1588 "occurrence of '\\1' "
1590 .arg(*n).arg(numOccs));
1593 int paramPos = def.indexOf("\1");
1594 fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1595 fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1603 project = config.getString(CONFIG_PROJECT);
1605 QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1606 if (!prefixes.isEmpty()) {
1607 foreach (const QString &prefix, prefixes)
1608 outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1611 outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1612 noLinkErrors_ = config.getBool(QLatin1String(CONFIG_NOLINKERRORS));
1616 Appends each directory path in \a moreImageDirs to the
1617 list of image directories.
1619 void Generator::augmentImageDirs(QSet<QString>& moreImageDirs)
1621 if (moreImageDirs.isEmpty())
1623 QSet<QString>::const_iterator i = moreImageDirs.begin();
1624 while (i != moreImageDirs.end()) {
1625 imageDirs.append(*i);
1630 void Generator::initializeGenerator(const Config & /* config */)
1634 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1636 return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1640 Used for writing to the current output stream. Returns a
1641 reference to the current output stream, which is then used
1642 with the \c {<<} operator for writing.
1644 QTextStream &Generator::out()
1646 return *outStreamStack.top();
1649 QString Generator::outFileName()
1651 return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1654 QString Generator::outputPrefix(const QString &nodeType)
1656 return outputPrefixes[nodeType];
1659 bool Generator::parseArg(const QString& src,
1663 QStringRef* contents,
1667 #define SKIP_CHAR(c) \
1669 qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1670 if (i >= n || src[i] != c) { \
1672 qDebug() << " char '" << c << "' not found"; \
1678 #define SKIP_SPACE \
1679 while (i < n && src[i] == ' ') \
1685 // assume "<@" has been parsed outside
1689 if (tag != QStringRef(&src, i, tag.length())) {
1691 qDebug() << "tag " << tag << " not found at " << i;
1696 qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1701 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1704 // read parameter name
1706 while (i < n && src[i].isLetter())
1708 if (src[i] == '=') {
1710 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1713 // skip parameter name
1715 while (i < n && src[i] != '"')
1717 *par1 = QStringRef(&src, j, i - j);
1722 qDebug() << "no optional parameter found";
1728 // find contents up to closing "</@tag>
1731 if (i + 4 + tag.length() > n)
1735 if (src[i + 1] != '/')
1737 if (src[i + 2] != '@')
1739 if (tag != QStringRef(&src, i + 3, tag.length()))
1741 if (src[i + 3 + tag.length()] != '>')
1746 *contents = QStringRef(&src, j, i - j);
1748 i += tag.length() + 4;
1752 qDebug() << " tag " << tag << " found: pos now: " << i;
1757 QString Generator::plainCode(const QString& markedCode)
1759 QString t = markedCode;
1760 t.replace(tag, QString());
1761 t.replace(quot, QLatin1String("\""));
1762 t.replace(gt, QLatin1String(">"));
1763 t.replace(lt, QLatin1String("<"));
1764 t.replace(amp, QLatin1String("&"));
1768 void Generator::setImageFileExtensions(const QStringList& extensions)
1770 imgFileExts[format()] = extensions;
1773 void Generator::singularPlural(Text& text, const NodeList& nodes)
1775 if (nodes.count() == 1)
1781 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1784 atom = atom->next();
1785 while (atom != 0 && atom->type() != type) {
1787 atom = atom->next();
1793 Resets the variables used during text output.
1795 void Generator::initializeTextOutput()
1798 inContents_ = false;
1799 inSectionHeading_ = false;
1800 inTableHeader_ = false;
1802 threeColumnEnumValueTable_ = true;
1804 sectionNumber_.clear();
1807 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1809 if (node->type() == Node::Function) {
1810 const FunctionNode *func = static_cast<const FunctionNode *>(node);
1811 if (func->overloadNumber() == 1) {
1812 QString alternateName;
1813 const FunctionNode *alternateFunc = 0;
1815 if (func->name().startsWith("set") && func->name().size() >= 4) {
1816 alternateName = func->name()[3].toLower();
1817 alternateName += func->name().mid(4);
1818 alternateFunc = func->parent()->findFunctionNode(alternateName);
1820 if (!alternateFunc) {
1821 alternateName = "is" + func->name().mid(3);
1822 alternateFunc = func->parent()->findFunctionNode(alternateName);
1823 if (!alternateFunc) {
1824 alternateName = "has" + func->name().mid(3);
1825 alternateFunc = func->parent()->findFunctionNode(alternateName);
1829 else if (!func->name().isEmpty()) {
1830 alternateName = "set";
1831 alternateName += func->name()[0].toUpper();
1832 alternateName += func->name().mid(1);
1833 alternateFunc = func->parent()->findFunctionNode(alternateName);
1836 if (alternateFunc && alternateFunc->access() != Node::Private) {
1838 for (i = 0; i < alsoList.size(); ++i) {
1839 if (alsoList.at(i).toString().contains(alternateName))
1843 if (i == alsoList.size()) {
1844 alternateName += "()";
1847 also << Atom(Atom::Link, alternateName)
1848 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1850 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1851 alsoList.prepend(also);
1858 void Generator::terminate()
1860 QList<Generator *>::ConstIterator g = generators.constBegin();
1861 while (g != generators.constEnd()) {
1862 if (outputFormats.contains((*g)->format()))
1863 (*g)->terminateGenerator();
1867 fmtLeftMaps.clear();
1868 fmtRightMaps.clear();
1869 imgFileExts.clear();
1873 QmlClassNode::terminate();
1874 ExampleNode::terminate();
1877 void Generator::terminateGenerator()
1882 Trims trailing whitespace off the \a string and returns
1885 QString Generator::trimmedTrailing(const QString& string)
1887 QString trimmed = string;
1888 while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1889 trimmed.truncate(trimmed.length() - 1);
1893 QString Generator::typeString(const Node *node)
1895 switch (node->type()) {
1896 case Node::Namespace:
1900 case Node::Document:
1902 switch (node->subType()) {
1903 case Node::QmlClass:
1905 case Node::QmlPropertyGroup:
1906 return "property group";
1907 case Node::QmlBasicType:
1910 return "documentation";
1917 case Node::Function:
1919 case Node::Property:
1922 return "documentation";
1926 void Generator::unknownAtom(const Atom *atom)
1928 Location::internalError(tr("unknown atom type '%1' in %2 generator")
1929 .arg(atom->typeString()).arg(format()));