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;
100 void Generator::setDebugSegfaultFlag(bool b)
103 qDebug() << "DEBUG: Setting debug flag.";
105 qDebug() << "DEBUG: Clearing debug flag.";
110 Prints \a message as an aid to debugging the release version.
112 void Generator::debugSegfault(const QString& message)
115 qDebug() << "DEBUG:" << message;
119 Constructs the generator base class. Prepends the newly
120 constructed generator to the list of output generators.
121 Sets a pointer to the QDoc database singleton, which is
122 available to the generator subclasses.
124 Generator::Generator()
132 inSectionHeading_(false),
133 inTableHeader_(false),
134 threeColumnEnumValueTable_(true),
137 qdb_ = QDocDatabase::qdocDB();
138 generators.prepend(this);
142 Destroys the generator after removing it from the list of
145 Generator::~Generator()
147 generators.removeAll(this);
150 void Generator::appendFullName(Text& text,
151 const Node *apparentNode,
152 const Node *relative,
153 const Node *actualNode)
156 actualNode = apparentNode;
157 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
158 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
159 << Atom(Atom::String, apparentNode->plainFullName(relative))
160 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
163 void Generator::appendFullName(Text& text,
164 const Node *apparentNode,
165 const QString& fullName,
166 const Node *actualNode)
169 actualNode = apparentNode;
170 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
171 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
172 << Atom(Atom::String, fullName)
173 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
176 void Generator::appendFullNames(Text& text, const NodeList& nodes, const Node* relative)
178 NodeList::ConstIterator n = nodes.constBegin();
180 while (n != nodes.constEnd()) {
181 appendFullName(text,*n,relative);
182 text << comma(index++,nodes.count());
187 void Generator::appendSortedNames(Text& text, const ClassNode *classe, const QList<RelatedClass> &classes)
189 QList<RelatedClass>::ConstIterator r;
190 QMap<QString,Text> classMap;
193 r = classes.constBegin();
194 while (r != classes.constEnd()) {
195 if ((*r).node->access() == Node::Public &&
196 (*r).node->status() != Node::Internal
197 && !(*r).node->doc().isEmpty()) {
199 appendFullName(className, (*r).node, classe);
200 classMap[className.toString().toLower()] = className;
205 QStringList classNames = classMap.keys();
208 foreach (const QString &className, classNames) {
209 text << classMap[className];
210 text << separator(index++, classNames.count());
214 void Generator::appendSortedQmlNames(Text& text, const Node* base, const NodeList& subs)
216 QMap<QString,Text> classMap;
219 for (int i = 0; i < subs.size(); ++i) {
221 if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
222 (base->qmlModuleIdentifier() == subs[i]->qmlModuleIdentifier())) {
223 appendFullName(t, subs[i], base);
224 classMap[t.toString().toLower()] = t;
228 QStringList names = classMap.keys();
231 foreach (const QString &name, names) {
232 text << classMap[name];
233 text << separator(index++, names.count());
237 QMultiMap<QString,QString> outFileNames;
242 void Generator::writeOutFileNames()
244 QFile* files = new QFile("/Users/msmith/depot/qt5/qtdoc/outputlist.txt");
245 files->open(QFile::WriteOnly);
246 QTextStream* filesout = new QTextStream(files);
247 QMultiMap<QString,QString>::ConstIterator i = outFileNames.begin();
248 while (i != outFileNames.end()) {
249 (*filesout) << i.key() << "\n";
257 Creates the file named \a fileName in the output directory.
258 Attaches a QTextStream to the created file, which is written
259 to all over the place using out().
261 void Generator::beginSubPage(const InnerNode* node, const QString& fileName)
263 QString path = outputDir() + QLatin1Char('/');
264 if (!node->outputSubdirectory().isEmpty())
265 path += node->outputSubdirectory() + QLatin1Char('/');
267 Generator::debugSegfault("Writing: " + path);
268 outFileNames.insert(fileName,fileName);
269 QFile* outFile = new QFile(path);
270 if (!outFile->open(QFile::WriteOnly))
271 node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
272 QTextStream* out = new QTextStream(outFile);
275 out->setCodec(outputCodec);
276 outStreamStack.push(out);
277 const_cast<InnerNode*>(node)->setOutputFileName(fileName);
281 Flush the text stream associated with the subpage, and
282 then pop it off the text stream stack and delete it.
283 This terminates output of the subpage.
285 void Generator::endSubPage()
287 outStreamStack.top()->flush();
288 delete outStreamStack.top()->device();
289 delete outStreamStack.pop();
292 QString Generator::fileBase(const Node *node) const
295 node = node->relates();
296 else if (!node->isInnerNode())
297 node = node->parent();
298 if (node->subType() == Node::QmlPropertyGroup) {
299 node = node->parent();
302 QString base = node->doc().baseName();
306 const Node *p = node;
309 const Node *pp = p->parent();
310 base.prepend(p->name());
311 if (!p->qmlModuleIdentifier().isEmpty())
312 base.prepend(p->qmlModuleIdentifier()+QChar('-'));
314 To avoid file name conflicts in the html directory,
315 we prepend a prefix (by default, "qml-") to the file name of QML
318 if ((p->subType() == Node::QmlClass) ||
319 (p->subType() == Node::QmlBasicType)) {
320 base.prepend(outputPrefix(QLatin1String("QML")));
322 if (!pp || pp->name().isEmpty() || pp->type() == Node::Document)
324 base.prepend(QLatin1Char('-'));
327 if (node->type() == Node::Document) {
328 if (node->subType() == Node::Collision) {
329 const NameCollisionNode* ncn = static_cast<const NameCollisionNode*>(node);
330 if (ncn->currentChild())
331 return fileBase(ncn->currentChild());
332 base.prepend("collision-");
334 //Was QDOC2_COMPAT, required for index.html
335 if (base.endsWith(".html"))
336 base.truncate(base.length() - 5);
338 if (node->subType() == Node::QmlModule) {
339 base.prepend("qmlmodule-");
341 if (node->subType() == Node::Module) {
342 base.append("-module");
346 // the code below is effectively equivalent to:
347 // base.replace(QRegExp("[^A-Za-z0-9]+"), " ");
348 // base = base.trimmed();
349 // base.replace(QLatin1Char(' '), QLatin1Char('-'));
350 // base = base.toLower();
351 // as this function accounted for ~8% of total running time
352 // we optimize a bit...
355 // +5 prevents realloc in fileName() below
356 res.reserve(base.size() + 5);
358 for (int i = 0; i != base.size(); ++i) {
359 QChar c = base.at(i);
360 uint u = c.unicode();
361 if (u >= 'A' && u <= 'Z')
363 if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
364 res += QLatin1Char(u);
368 res += QLatin1Char('-');
372 while (res.endsWith(QLatin1Char('-')))
378 If the \a node has a URL, return the URL as the file name.
379 Otherwise, construct the file name from the fileBase() and
380 the fileExtension(), and return the constructed name.
382 QString Generator::fileName(const Node* node) const
384 if (!node->url().isEmpty())
387 QString name = fileBase(node);
388 name += QLatin1Char('.');
389 name += fileExtension();
393 QMap<QString, QString>& Generator::formattingLeftMap()
395 return fmtLeftMaps[format()];
398 QMap<QString, QString>& Generator::formattingRightMap()
400 return fmtRightMaps[format()];
404 Returns the full document location.
406 QString Generator::fullDocumentLocation(const Node *node, bool subdir)
410 if (!node->url().isEmpty())
418 If the output is being sent to subdirectories of the
419 output directory, and if the subdir parameter is set,
420 prepend the subdirectory name + '/' to the result.
423 fdl = node->outputSubdirectory();
425 fdl.append(QLatin1Char('/'));
427 if (node->type() == Node::Namespace) {
429 // The root namespace has no name - check for this before creating
430 // an attribute containing the location of any documentation.
432 if (!fileBase(node).isEmpty())
433 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
437 else if (node->type() == Node::Document) {
438 if ((node->subType() == Node::QmlClass) ||
439 (node->subType() == Node::QmlBasicType)) {
440 QString fb = fileBase(node);
441 if (fb.startsWith(Generator::outputPrefix(QLatin1String("QML"))))
442 return fb + QLatin1Char('.') + currentGenerator()->fileExtension();
445 if (!node->qmlModuleName().isEmpty()) {
446 mq = node->qmlModuleIdentifier().replace(QChar('.'),QChar('-'));
447 mq = mq.toLower() + QLatin1Char('-');
449 return fdl+ Generator::outputPrefix(QLatin1String("QML")) + mq +
450 fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
454 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
457 else if (fileBase(node).isEmpty())
460 Node *parentNode = 0;
462 if ((parentNode = node->relates())) {
463 parentName = fullDocumentLocation(node->relates());
465 else if ((parentNode = node->parent())) {
466 if (parentNode->subType() == Node::QmlPropertyGroup) {
467 parentNode = parentNode->parent();
468 parentName = fullDocumentLocation(parentNode);
471 parentName = fullDocumentLocation(node->parent());
475 switch (node->type()) {
477 case Node::Namespace:
478 if (parentNode && !parentNode->name().isEmpty()) {
479 parentName.remove(QLatin1Char('.') + currentGenerator()->fileExtension());
480 parentName += QLatin1Char('-')
481 + fileBase(node).toLower() + QLatin1Char('.') + currentGenerator()->fileExtension();
483 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
488 const FunctionNode *functionNode =
489 static_cast<const FunctionNode *>(node);
491 if (functionNode->metaness() == FunctionNode::Dtor)
492 anchorRef = "#dtor." + functionNode->name().mid(1);
494 else if (functionNode->associatedProperty())
495 return fullDocumentLocation(functionNode->associatedProperty());
497 else if (functionNode->overloadNumber() > 1)
498 anchorRef = QLatin1Char('#') + functionNode->name()
499 + QLatin1Char('-') + QString::number(functionNode->overloadNumber());
501 anchorRef = QLatin1Char('#') + functionNode->name();
505 Use node->name() instead of fileBase(node) as
506 the latter returns the name in lower-case. For
507 HTML anchors, we need to preserve the case.
510 anchorRef = QLatin1Char('#') + node->name() + "-enum";
513 anchorRef = QLatin1Char('#') + node->name() + "-typedef";
516 anchorRef = QLatin1Char('#') + node->name() + "-prop";
518 case Node::QmlProperty:
519 anchorRef = QLatin1Char('#') + node->name() + "-prop";
521 case Node::QmlSignal:
522 anchorRef = QLatin1Char('#') + node->name() + "-signal";
524 case Node::QmlSignalHandler:
525 anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
527 case Node::QmlMethod:
528 anchorRef = QLatin1Char('#') + node->name() + "-method";
531 anchorRef = QLatin1Char('#') + node->name() + "-var";
535 parentName = fileBase(node);
536 parentName.replace(QLatin1Char('/'), QLatin1Char('-')).replace(QLatin1Char('.'), QLatin1Char('-'));
537 parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
544 // Various objects can be compat (deprecated) or obsolete.
545 if (node->type() != Node::Class && node->type() != Node::Namespace) {
546 switch (node->status()) {
548 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
549 "-compat." + currentGenerator()->fileExtension());
552 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
553 "-obsolete." + currentGenerator()->fileExtension());
560 return fdl + parentName.toLower() + anchorRef;
563 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
565 QList<Text> alsoList = node->doc().alsoList();
566 supplementAlsoList(node, alsoList);
568 if (!alsoList.isEmpty()) {
570 text << Atom::ParaLeft
571 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
573 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
575 for (int i = 0; i < alsoList.size(); ++i)
576 text << alsoList.at(i) << separator(i, alsoList.size());
578 text << Atom::ParaRight;
579 generateText(text, node, marker);
583 int Generator::generateAtom(const Atom * /* atom */,
584 const Node * /* relative */,
585 CodeMarker * /* marker */)
590 const Atom *Generator::generateAtomList(const Atom *atom,
591 const Node *relative,
597 if (atom->type() == Atom::FormatIf) {
598 int numAtoms0 = numAtoms;
599 bool rightFormat = canHandleFormat(atom->string());
600 atom = generateAtomList(atom->next(),
603 generate && rightFormat,
608 if (atom->type() == Atom::FormatElse) {
610 atom = generateAtomList(atom->next(),
613 generate && !rightFormat,
619 if (atom->type() == Atom::FormatEndif) {
620 if (generate && numAtoms0 == numAtoms) {
621 relative->location().warning(tr("Output format %1 not handled %2")
622 .arg(format()).arg(outFileName()));
623 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
624 generateAtomList(&unhandledFormatAtom,
633 else if (atom->type() == Atom::FormatElse ||
634 atom->type() == Atom::FormatEndif) {
640 n += generateAtom(atom, relative, marker);
650 void Generator::generateBody(const Node *node, CodeMarker *marker)
654 if (node->type() == Node::Document) {
655 const DocNode *dn = static_cast<const DocNode *>(node);
656 if ((dn->subType() == Node::File) || (dn->subType() == Node::Image)) {
660 if (node->doc().isEmpty()) {
661 if (!quiet && !node->isReimp()) { // ### might be unnecessary
662 node->location().warning(tr("No documentation for '%1'").arg(node->plainFullName()));
666 if (node->type() == Node::Function) {
667 const FunctionNode *func = static_cast<const FunctionNode *>(node);
668 if (func->reimplementedFrom() != 0)
669 generateReimplementedFrom(func, marker);
672 if (!generateText(node->doc().body(), node, marker)) {
677 if (node->type() == Node::Enum) {
678 const EnumNode *enume = (const EnumNode *) node;
680 QSet<QString> definedItems;
681 QList<EnumItem>::ConstIterator it = enume->items().constBegin();
682 while (it != enume->items().constEnd()) {
683 definedItems.insert((*it).name());
687 QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
688 QSet<QString> allItems = definedItems + documentedItems;
689 if (allItems.count() > definedItems.count() ||
690 allItems.count() > documentedItems.count()) {
691 QSet<QString>::ConstIterator a = allItems.constBegin();
692 while (a != allItems.constEnd()) {
693 if (!definedItems.contains(*a)) {
695 QString best = nearestName(*a, definedItems);
696 if (!best.isEmpty() && !documentedItems.contains(best))
697 details = tr("Maybe you meant '%1'?").arg(best);
699 node->doc().location().warning(tr("No such enum item '%1' in %2").arg(*a).arg(node->plainFullName()), details);
701 qDebug() << "VOID:" << node->name() << definedItems;
703 else if (!documentedItems.contains(*a)) {
704 node->doc().location().warning(tr("Undocumented enum item '%1' in %2").arg(*a).arg(node->plainFullName()));
710 else if (node->type() == Node::Function) {
711 const FunctionNode *func = static_cast<const FunctionNode *>(node);
712 QSet<QString> definedParams;
713 QList<Parameter>::ConstIterator p = func->parameters().constBegin();
714 while (p != func->parameters().constEnd()) {
715 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
716 && func->name() != QLatin1String("operator++")
717 && func->name() != QLatin1String("operator--")) {
718 node->doc().location().warning(tr("Missing parameter name"));
721 definedParams.insert((*p).name());
726 QSet<QString> documentedParams = func->doc().parameterNames();
727 QSet<QString> allParams = definedParams + documentedParams;
728 if (allParams.count() > definedParams.count()
729 || allParams.count() > documentedParams.count()) {
730 QSet<QString>::ConstIterator a = allParams.constBegin();
731 while (a != allParams.constEnd()) {
732 if (!definedParams.contains(*a)) {
734 QString best = nearestName(*a, definedParams);
736 details = tr("Maybe you meant '%1'?").arg(best);
738 node->doc().location().warning(
739 tr("No such parameter '%1' in %2").arg(*a).arg(node->plainFullName()),
742 else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
743 bool needWarning = (func->status() > Node::Obsolete);
744 if (func->overloadNumber() > 1) {
745 FunctionNode *primaryFunc =
746 func->parent()->findFunctionNode(func->name());
748 foreach (const Parameter ¶m,
749 primaryFunc->parameters()) {
750 if (param.name() == *a) {
757 if (needWarning && !func->isReimp())
758 node->doc().location().warning(
759 tr("Undocumented parameter '%1' in %2")
760 .arg(*a).arg(node->plainFullName()));
766 Something like this return value check should
767 be implemented at some point.
769 if (func->status() > Node::Obsolete && func->returnType() == "bool"
770 && func->reimplementedFrom() == 0 && !func->isOverload()) {
771 QString body = func->doc().body().toString();
772 if (!body.contains("return", Qt::CaseInsensitive))
773 node->doc().location().warning(tr("Undocumented return value"));
778 if (node->type() == Node::Document) {
779 const DocNode *dn = static_cast<const DocNode *>(node);
780 if (dn->subType() == Node::Example) {
781 generateExampleFiles(dn, marker);
783 else if (dn->subType() == Node::File) {
786 Doc::quoteFromFile(dn->doc().location(), quoter, dn->name());
787 QString code = quoter.quoteTo(dn->location(), QString(), QString());
788 CodeMarker *codeMarker = CodeMarker::markerForFileName(dn->name());
789 text << Atom(codeMarker->atomType(), code);
790 generateText(text, dn, codeMarker);
795 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
799 void Generator::generateExampleFiles(const DocNode *dn, CodeMarker *marker)
801 if (dn->childNodes().isEmpty())
803 generateFileList(dn, marker, Node::File, QString("Files:"));
804 generateFileList(dn, marker, Node::Image, QString("Images:"));
807 void Generator::generateDocNode(DocNode* /* dn */, CodeMarker* /* marker */)
812 This function is called when the documentation for an
813 example is being formatted. It outputs the list of source
814 files comprising the example, and the list of images used
815 by the example. The images are copied into a subtree of
816 \c{...doc/html/images/used-in-examples/...}
818 void Generator::generateFileList(const DocNode* dn,
820 Node::SubType subtype,
825 OpenedList openedList(OpenedList::Bullet);
827 text << Atom::ParaLeft << tag << Atom::ParaRight
828 << Atom(Atom::ListLeft, openedList.styleString());
830 foreach (const Node* child, dn->childNodes()) {
831 if (child->subType() == subtype) {
833 QString file = child->name();
834 if (subtype == Node::Image) {
835 if (!file.isEmpty()) {
837 QString userFriendlyFilePath;
838 QString srcPath = Config::findFile(dn->location(),
843 userFriendlyFilePath);
844 userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
846 QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
847 if (!dirInfo.mkpath(imgOutDir))
848 dn->location().fatal(tr("Cannot create output directory '%1'")
851 QString imgOutName = Config::copyFile(dn->location(),
860 text << Atom(Atom::ListItemNumber, openedList.numberString())
861 << Atom(Atom::ListItemLeft, openedList.styleString())
863 << Atom(Atom::Link, file)
864 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
866 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
868 << Atom(Atom::ListItemRight, openedList.styleString());
871 text << Atom(Atom::ListRight, openedList.styleString());
873 generateText(text, dn, marker);
876 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
878 if (!classe->derivedClasses().isEmpty()) {
880 text << Atom::ParaLeft
881 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
883 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
885 appendSortedNames(text, classe, classe->derivedClasses());
886 text << Atom::ParaRight;
887 generateText(text, classe, marker);
891 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
893 QList<RelatedClass>::ConstIterator r;
896 if (!classe->baseClasses().isEmpty()) {
898 text << Atom::ParaLeft
899 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
901 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
903 r = classe->baseClasses().constBegin();
905 while (r != classe->baseClasses().constEnd()) {
906 text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
907 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
908 << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
909 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
911 if ((*r).access == Node::Protected) {
912 text << " (protected)";
914 else if ((*r).access == Node::Private) {
915 text << " (private)";
917 text << separator(index++, classe->baseClasses().count());
920 text << Atom::ParaRight;
921 generateText(text, classe, marker);
926 Recursive writing of HTML files from the root \a node.
928 \note NameCollisionNodes are skipped here and processed
929 later. See HtmlGenerator::generateCollisionPages() for
932 void Generator::generateInnerNode(InnerNode* node)
934 if (!node->url().isNull())
937 if (node->type() == Node::Document) {
938 DocNode* docNode = static_cast<DocNode*>(node);
939 if (docNode->subType() == Node::ExternalPage)
941 if (docNode->subType() == Node::Image)
943 if (docNode->subType() == Node::QmlPropertyGroup)
945 if (docNode->subType() == Node::Page) {
946 if (node->count() > 0)
947 qDebug("PAGE %s HAS CHILDREN", qPrintable(docNode->title()));
952 Obtain a code marker for the source file.
954 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
956 if (node->parent() != 0) {
958 Skip name collision nodes here and process them
959 later in generateCollisionPages(). Each one is
960 appended to a list for later.
962 if ((node->type() == Node::Document) && (node->subType() == Node::Collision)) {
963 NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
964 collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
967 beginSubPage(node, fileName(node));
968 if (node->type() == Node::Namespace || node->type() == Node::Class) {
969 generateClassLikeNode(node, marker);
971 else if (node->type() == Node::Document) {
972 generateDocNode(static_cast<DocNode*>(node), marker);
978 NodeList::ConstIterator c = node->childNodes().constBegin();
979 while (c != node->childNodes().constEnd()) {
980 if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
981 generateInnerNode((InnerNode*)*c);
988 Generate a list of maintainers in the output
990 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
992 QStringList sl = getMetadataElements(node,"maintainer");
996 text << Atom::ParaLeft
997 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
999 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
1001 for (int i = 0; i < sl.size(); ++i)
1002 text << sl.at(i) << separator(i, sl.size());
1004 text << Atom::ParaRight;
1005 generateText(text, node, marker);
1010 Output the "Inherit by" list for the QML element,
1011 if it is inherited by any other elements.
1013 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
1018 QmlClassNode::subclasses(qcn->name(),subs);
1019 if (!subs.isEmpty()) {
1021 text << Atom::ParaLeft << "Inherited by ";
1022 appendSortedQmlNames(text,qcn,subs);
1023 text << Atom::ParaRight;
1024 generateText(text, qcn, marker);
1031 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
1037 Extract sections of markup text surrounded by \e qmltext
1038 and \e endqmltext and output them.
1040 bool Generator::generateQmlText(const Text& text,
1041 const Node *relative,
1043 const QString& /* qmlName */ )
1045 const Atom* atom = text.firstAtom();
1046 bool result = false;
1049 initializeTextOutput();
1051 if (atom->type() != Atom::QmlText)
1052 atom = atom->next();
1054 atom = atom->next();
1055 while (atom && (atom->type() != Atom::EndQmlText)) {
1056 int n = 1 + generateAtom(atom, relative, marker);
1058 atom = atom->next();
1067 void Generator::generateReimplementedFrom(const FunctionNode *func,
1070 if (func->reimplementedFrom() != 0) {
1071 const FunctionNode *from = func->reimplementedFrom();
1072 if (from->access() != Node::Private &&
1073 from->parent()->access() != Node::Private) {
1075 text << Atom::ParaLeft << "Reimplemented from ";
1076 QString fullName = from->parent()->name() + "::" + from->name() + "()";
1077 appendFullName(text, from->parent(), fullName, from);
1078 text << "." << Atom::ParaRight;
1079 generateText(text, func, marker);
1084 void Generator::generateSince(const Node *node, CodeMarker *marker)
1086 if (!node->since().isEmpty()) {
1088 text << Atom::ParaLeft
1090 << typeString(node);
1091 if (node->type() == Node::Enum)
1092 text << " was introduced or modified in ";
1094 text << " was introduced in ";
1096 QStringList since = node->since().split(QLatin1Char(' '));
1097 if (since.count() == 1) {
1098 // Handle legacy use of \since <version>.
1099 if (project.isEmpty())
1103 text << " " << since[0];
1105 // Reconstruct the <project> <version> string.
1106 text << " " << since.join(' ');
1109 text << "." << Atom::ParaRight;
1110 generateText(text, node, marker);
1114 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1118 switch (node->status()) {
1119 case Node::Commendable:
1122 case Node::Preliminary:
1123 text << Atom::ParaLeft
1124 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1127 << " is under development and is subject to change."
1128 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1131 case Node::Deprecated:
1132 text << Atom::ParaLeft;
1133 if (node->isInnerNode())
1134 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1135 text << "This " << typeString(node) << " is deprecated.";
1136 if (node->isInnerNode())
1137 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1138 text << Atom::ParaRight;
1140 case Node::Obsolete:
1141 text << Atom::ParaLeft;
1142 if (node->isInnerNode())
1143 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1144 text << "This " << typeString(node) << " is obsolete.";
1145 if (node->isInnerNode())
1146 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1147 text << " It is provided to keep old source code working. "
1148 << "We strongly advise against "
1149 << "using it in new code." << Atom::ParaRight;
1152 // reimplemented in HtmlGenerator subclass
1153 if (node->isInnerNode()) {
1154 text << Atom::ParaLeft
1155 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1158 << " is part of the Qt compatibility layer."
1159 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1160 << " It is provided to keep old source code working. "
1161 << "We strongly advise against using it in new code."
1165 case Node::Internal:
1169 generateText(text, node, marker);
1172 bool Generator::generateText(const Text& text,
1173 const Node *relative,
1176 bool result = false;
1177 if (text.firstAtom() != 0) {
1179 initializeTextOutput();
1180 generateAtomList(text.firstAtom(),
1190 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1193 Node::ThreadSafeness threadSafeness = node->threadSafeness();
1196 rlink << Atom(Atom::Link,"reentrant")
1197 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1199 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1202 tlink << Atom(Atom::Link,"thread-safe")
1203 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1205 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1207 switch (threadSafeness) {
1208 case Node::UnspecifiedSafeness:
1210 case Node::NonReentrant:
1211 text << Atom::ParaLeft
1212 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1214 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1222 case Node::Reentrant:
1223 case Node::ThreadSafe:
1224 text << Atom::ParaLeft
1225 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1227 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1230 if (node->isInnerNode()) {
1231 const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1232 text << "All functions in this "
1235 if (threadSafeness == Node::ThreadSafe)
1240 bool exceptions = false;
1242 NodeList threadsafe;
1243 NodeList nonreentrant;
1244 NodeList::ConstIterator c = innerNode->childNodes().constBegin();
1245 while (c != innerNode->childNodes().constEnd()) {
1247 if ((*c)->status() != Node::Obsolete){
1248 switch ((*c)->threadSafeness()) {
1249 case Node::Reentrant:
1250 reentrant.append(*c);
1251 if (threadSafeness == Node::ThreadSafe)
1254 case Node::ThreadSafe:
1255 threadsafe.append(*c);
1256 if (threadSafeness == Node::Reentrant)
1259 case Node::NonReentrant:
1260 nonreentrant.append(*c);
1271 else if (threadSafeness == Node::Reentrant) {
1272 if (nonreentrant.isEmpty()) {
1273 if (!threadsafe.isEmpty()) {
1275 appendFullNames(text,threadsafe,innerNode);
1276 singularPlural(text,threadsafe);
1277 text << " also " << tlink << ".";
1283 text << ", except for ";
1284 appendFullNames(text,nonreentrant,innerNode);
1286 singularPlural(text,nonreentrant);
1287 text << " nonreentrant.";
1288 if (!threadsafe.isEmpty()) {
1290 appendFullNames(text,threadsafe,innerNode);
1291 singularPlural(text,threadsafe);
1292 text << " " << tlink << ".";
1296 else { // thread-safe
1297 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1298 text << ", except for ";
1299 if (!reentrant.isEmpty()) {
1300 appendFullNames(text,reentrant,innerNode);
1302 singularPlural(text,reentrant);
1303 text << " only " << rlink;
1304 if (!nonreentrant.isEmpty())
1307 if (!nonreentrant.isEmpty()) {
1308 appendFullNames(text,nonreentrant,innerNode);
1310 singularPlural(text,nonreentrant);
1311 text << " nonreentrant.";
1318 text << "This " << typeString(node) << " is ";
1319 if (threadSafeness == Node::ThreadSafe)
1325 text << Atom::ParaRight;
1327 generateText(text,node,marker);
1331 Traverses the database recursivly to generate all the documentation.
1333 void Generator::generateTree()
1335 generateInnerNode(qdb_->treeRoot());
1338 Generator *Generator::generatorForFormat(const QString& format)
1340 QList<Generator *>::ConstIterator g = generators.constBegin();
1341 while (g != generators.constEnd()) {
1342 if ((*g)->format() == format)
1350 This function can be called if getLink() returns an empty
1351 string. It tests the \a atom string to see if it is a link
1352 of the form <element> :: <name>, where <element> is a QML
1353 element or component without a module qualifier. If so, it
1354 constructs a link to the <name> clause on the disambiguation
1355 page for <element> and returns that link string. It also
1356 adds the <name> as a target in the NameCollisionNode for
1357 <element>. These clauses are then constructed when the
1358 disambiguation page is actually generated.
1360 QString Generator::getCollisionLink(const Atom* atom)
1363 if (!atom->string().contains("::"))
1365 QStringList path = atom->string().split("::");
1366 NameCollisionNode* ncn = qdb_->findCollisionNode(path[0]);
1369 if (atom->next() && atom->next()->next()) {
1370 if (atom->next()->type() == Atom::FormattingLeft &&
1371 atom->next()->next()->type() == Atom::String)
1372 label = atom->next()->next()->string();
1374 ncn->addLinkTarget(path[1],label);
1375 link = fileName(ncn);
1376 link += QLatin1Char('#');
1377 link += Doc::canonicalTitle(path[1]);
1384 Looks up the tag \a t in the map of metadata values for the
1385 current topic in \a inner. If a value for the tag is found,
1386 the value is returned.
1388 \note If \a t is found in the metadata map, it is erased.
1389 i.e. Once you call this function for a particular \a t,
1392 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1395 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1396 QStringMultiMap::iterator i = metaTagMap.find(t);
1397 if (i != metaTagMap.end()) {
1399 metaTagMap.erase(i);
1405 Looks up the tag \a t in the map of metadata values for the
1406 current topic in \a inner. If values for the tag are found,
1407 they are returned in a string list.
1409 \note If \a t is found in the metadata map, all the pairs
1410 having the key \a t are erased. i.e. Once you call this
1411 function for a particular \a t, you consume \a t.
1413 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1416 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1417 s = metaTagMap.values(t);
1419 metaTagMap.remove(t);
1424 Returns a relative path name for an image.
1426 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1428 QString userFriendlyFilePath;
1429 QString filePath = Config::findFile(relative->doc().location(),
1433 imgFileExts[format()],
1434 userFriendlyFilePath);
1436 if (filePath.isEmpty())
1439 QString path = Config::copyFile(relative->doc().location(),
1441 userFriendlyFilePath,
1442 outputDir() + QLatin1String("/images"));
1443 QString images = "images";
1445 images.append(QLatin1Char('/'));
1446 return images + path;
1449 QString Generator::indent(int level, const QString& markedCode)
1458 while (i < (int) markedCode.length()) {
1459 if (markedCode.at(i) == QLatin1Char('\n')) {
1464 for (int j = 0; j < level; j++)
1465 t += QLatin1Char(' ');
1469 t += markedCode.at(i++);
1474 void Generator::initialize(const Config &config)
1476 outputFormats = config.getOutputFormats();
1477 if (!outputFormats.isEmpty()) {
1478 outDir_ = config.getOutputDir();
1480 if (outDir_.isEmpty())
1481 config.lastLocation().fatal(tr("No output directory specified in "
1482 "configuration file or on the command line"));
1485 if (dirInfo.exists(outDir_)) {
1486 if (!Config::removeDirContents(outDir_))
1487 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1490 if (!dirInfo.mkpath(outDir_))
1491 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1494 if (!dirInfo.mkdir(outDir_ + "/images"))
1495 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/images"));
1496 if (!dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1497 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/images/used-in-examples"));
1498 if (!dirInfo.mkdir(outDir_ + "/scripts"))
1499 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/scripts"));
1500 if (!dirInfo.mkdir(outDir_ + "/style"))
1501 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/style"));
1504 imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1505 imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1506 scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1507 scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1508 styleFiles = config.getCleanPathList(CONFIG_STYLES);
1509 styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1510 exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1511 exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS);
1513 QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1514 QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1515 QSet<QString>::ConstIterator f = formats.constBegin();
1516 while (f != formats.constEnd()) {
1517 imgFileExts[*f] = config.getStringList(imagesDotFileExtensions + Config::dot + *f);
1521 QList<Generator *>::ConstIterator g = generators.constBegin();
1522 while (g != generators.constEnd()) {
1523 if (outputFormats.contains((*g)->format())) {
1524 currentGenerator_ = (*g);
1525 (*g)->initializeGenerator(config);
1526 QStringList extraImages = config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
1527 QStringList::ConstIterator e = extraImages.constBegin();
1528 while (e != extraImages.constEnd()) {
1529 QString userFriendlyFilePath;
1530 QString filePath = Config::findFile(config.lastLocation(),
1534 imgFileExts[(*g)->format()],
1535 userFriendlyFilePath);
1536 if (!filePath.isEmpty())
1537 Config::copyFile(config.lastLocation(),
1539 userFriendlyFilePath,
1545 // Documentation template handling
1546 QString templateDir = config.getString((*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
1548 QStringList searchDirs;
1549 if (!templateDir.isEmpty()) {
1550 searchDirs.append(templateDir);
1552 if (!Config::installDir.isEmpty()) {
1553 searchDirs.append(Config::installDir);
1556 if (!searchDirs.isEmpty()) {
1558 QStringList scripts = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1559 e = scripts.constBegin();
1560 while (e != scripts.constEnd()) {
1561 QString userFriendlyFilePath;
1562 QString filePath = Config::findFile(config.lastLocation(),
1567 userFriendlyFilePath);
1568 if (!filePath.isEmpty())
1569 Config::copyFile(config.lastLocation(),
1571 userFriendlyFilePath,
1577 QStringList styles = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1578 e = styles.constBegin();
1579 while (e != styles.constEnd()) {
1580 QString userFriendlyFilePath;
1581 QString filePath = Config::findFile(config.lastLocation(),
1586 userFriendlyFilePath);
1587 if (!filePath.isEmpty())
1588 Config::copyFile(config.lastLocation(),
1590 userFriendlyFilePath,
1600 QRegExp secondParamAndAbove("[\2-\7]");
1601 QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1602 QSet<QString>::ConstIterator n = formattingNames.constBegin();
1603 while (n != formattingNames.constEnd()) {
1604 QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1605 QSet<QString> formats = config.subVars(formattingDotName);
1606 QSet<QString>::ConstIterator f = formats.constBegin();
1607 while (f != formats.constEnd()) {
1608 QString def = config.getString(formattingDotName + Config::dot + *f);
1609 if (!def.isEmpty()) {
1610 int numParams = Config::numParams(def);
1611 int numOccs = def.count("\1");
1612 if (numParams != 1) {
1613 config.lastLocation().warning(tr("Formatting '%1' must "
1615 "parameter (found %2)")
1616 .arg(*n).arg(numParams));
1618 else if (numOccs > 1) {
1619 config.lastLocation().fatal(tr("Formatting '%1' must "
1620 "contain exactly one "
1621 "occurrence of '\\1' "
1623 .arg(*n).arg(numOccs));
1626 int paramPos = def.indexOf("\1");
1627 fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1628 fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1636 project = config.getString(CONFIG_PROJECT);
1638 QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1639 if (!prefixes.isEmpty()) {
1640 foreach (const QString &prefix, prefixes)
1641 outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1644 outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1645 noLinkErrors_ = config.getBool(QLatin1String(CONFIG_NOLINKERRORS));
1649 Appends each directory path in \a moreImageDirs to the
1650 list of image directories.
1652 void Generator::augmentImageDirs(QSet<QString>& moreImageDirs)
1654 if (moreImageDirs.isEmpty())
1656 QSet<QString>::const_iterator i = moreImageDirs.begin();
1657 while (i != moreImageDirs.end()) {
1658 imageDirs.append(*i);
1663 void Generator::initializeGenerator(const Config & /* config */)
1667 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1669 return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1673 Used for writing to the current output stream. Returns a
1674 reference to the current output stream, which is then used
1675 with the \c {<<} operator for writing.
1677 QTextStream &Generator::out()
1679 return *outStreamStack.top();
1682 QString Generator::outFileName()
1684 return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1687 QString Generator::outputPrefix(const QString &nodeType)
1689 return outputPrefixes[nodeType];
1692 bool Generator::parseArg(const QString& src,
1696 QStringRef* contents,
1700 #define SKIP_CHAR(c) \
1702 qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1703 if (i >= n || src[i] != c) { \
1705 qDebug() << " char '" << c << "' not found"; \
1711 #define SKIP_SPACE \
1712 while (i < n && src[i] == ' ') \
1718 // assume "<@" has been parsed outside
1722 if (tag != QStringRef(&src, i, tag.length())) {
1724 qDebug() << "tag " << tag << " not found at " << i;
1729 qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1734 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1737 // read parameter name
1739 while (i < n && src[i].isLetter())
1741 if (src[i] == '=') {
1743 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1746 // skip parameter name
1748 while (i < n && src[i] != '"')
1750 *par1 = QStringRef(&src, j, i - j);
1755 qDebug() << "no optional parameter found";
1761 // find contents up to closing "</@tag>
1764 if (i + 4 + tag.length() > n)
1768 if (src[i + 1] != '/')
1770 if (src[i + 2] != '@')
1772 if (tag != QStringRef(&src, i + 3, tag.length()))
1774 if (src[i + 3 + tag.length()] != '>')
1779 *contents = QStringRef(&src, j, i - j);
1781 i += tag.length() + 4;
1785 qDebug() << " tag " << tag << " found: pos now: " << i;
1790 QString Generator::plainCode(const QString& markedCode)
1792 QString t = markedCode;
1793 t.replace(tag, QString());
1794 t.replace(quot, QLatin1String("\""));
1795 t.replace(gt, QLatin1String(">"));
1796 t.replace(lt, QLatin1String("<"));
1797 t.replace(amp, QLatin1String("&"));
1801 void Generator::setImageFileExtensions(const QStringList& extensions)
1803 imgFileExts[format()] = extensions;
1806 void Generator::singularPlural(Text& text, const NodeList& nodes)
1808 if (nodes.count() == 1)
1814 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1817 atom = atom->next();
1818 while (atom != 0 && atom->type() != type) {
1820 atom = atom->next();
1826 Resets the variables used during text output.
1828 void Generator::initializeTextOutput()
1831 inContents_ = false;
1832 inSectionHeading_ = false;
1833 inTableHeader_ = false;
1835 threeColumnEnumValueTable_ = true;
1837 sectionNumber_.clear();
1840 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1842 if (node->type() == Node::Function) {
1843 const FunctionNode *func = static_cast<const FunctionNode *>(node);
1844 if (func->overloadNumber() == 1) {
1845 QString alternateName;
1846 const FunctionNode *alternateFunc = 0;
1848 if (func->name().startsWith("set") && func->name().size() >= 4) {
1849 alternateName = func->name()[3].toLower();
1850 alternateName += func->name().mid(4);
1851 alternateFunc = func->parent()->findFunctionNode(alternateName);
1853 if (!alternateFunc) {
1854 alternateName = "is" + func->name().mid(3);
1855 alternateFunc = func->parent()->findFunctionNode(alternateName);
1856 if (!alternateFunc) {
1857 alternateName = "has" + func->name().mid(3);
1858 alternateFunc = func->parent()->findFunctionNode(alternateName);
1862 else if (!func->name().isEmpty()) {
1863 alternateName = "set";
1864 alternateName += func->name()[0].toUpper();
1865 alternateName += func->name().mid(1);
1866 alternateFunc = func->parent()->findFunctionNode(alternateName);
1869 if (alternateFunc && alternateFunc->access() != Node::Private) {
1871 for (i = 0; i < alsoList.size(); ++i) {
1872 if (alsoList.at(i).toString().contains(alternateName))
1876 if (i == alsoList.size()) {
1877 alternateName += "()";
1880 also << Atom(Atom::Link, alternateName)
1881 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1883 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1884 alsoList.prepend(also);
1891 void Generator::terminate()
1893 QList<Generator *>::ConstIterator g = generators.constBegin();
1894 while (g != generators.constEnd()) {
1895 if (outputFormats.contains((*g)->format()))
1896 (*g)->terminateGenerator();
1900 fmtLeftMaps.clear();
1901 fmtRightMaps.clear();
1902 imgFileExts.clear();
1906 QmlClassNode::terminate();
1907 ExampleNode::terminate();
1910 void Generator::terminateGenerator()
1915 Trims trailing whitespace off the \a string and returns
1918 QString Generator::trimmedTrailing(const QString& string)
1920 QString trimmed = string;
1921 while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1922 trimmed.truncate(trimmed.length() - 1);
1926 QString Generator::typeString(const Node *node)
1928 switch (node->type()) {
1929 case Node::Namespace:
1933 case Node::Document:
1935 switch (node->subType()) {
1936 case Node::QmlClass:
1938 case Node::QmlPropertyGroup:
1939 return "property group";
1940 case Node::QmlBasicType:
1943 return "documentation";
1950 case Node::Function:
1952 case Node::Property:
1955 return "documentation";
1959 void Generator::unknownAtom(const Atom *atom)
1961 Location::internalError(tr("unknown atom type '%1' in %2 generator")
1962 .arg(atom->typeString()).arg(format()));