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;
99 void Generator::setDebugSegfaultFlag(bool b)
102 qDebug() << "DEBUG: Setting debug flag.";
104 qDebug() << "DEBUG: Clearing debug flag.";
109 Prints \a message as an aid to debugging the release version.
111 void Generator::debugSegfault(const QString& message)
114 qDebug() << "DEBUG:" << message;
118 Constructs the generator base class. Prepends the newly
119 constructed generator to the list of output generators.
120 Sets a pointer to the QDoc database singleton, which is
121 available to the generator subclasses.
123 Generator::Generator()
131 inSectionHeading_(false),
132 inTableHeader_(false),
133 threeColumnEnumValueTable_(true),
136 qdb_ = QDocDatabase::qdocDB();
137 generators.prepend(this);
141 Destroys the generator after removing it from the list of
144 Generator::~Generator()
146 generators.removeAll(this);
149 void Generator::appendFullName(Text& text,
150 const Node *apparentNode,
151 const Node *relative,
152 const Node *actualNode)
155 actualNode = apparentNode;
156 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
157 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
158 << Atom(Atom::String, apparentNode->plainFullName(relative))
159 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
162 void Generator::appendFullName(Text& text,
163 const Node *apparentNode,
164 const QString& fullName,
165 const Node *actualNode)
168 actualNode = apparentNode;
169 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
170 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
171 << Atom(Atom::String, fullName)
172 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
175 void Generator::appendFullNames(Text& text, const NodeList& nodes, const Node* relative)
177 NodeList::ConstIterator n = nodes.constBegin();
179 while (n != nodes.constEnd()) {
180 appendFullName(text,*n,relative);
181 text << comma(index++,nodes.count());
186 void Generator::appendSortedNames(Text& text, const ClassNode *classe, const QList<RelatedClass> &classes)
188 QList<RelatedClass>::ConstIterator r;
189 QMap<QString,Text> classMap;
192 r = classes.constBegin();
193 while (r != classes.constEnd()) {
194 if ((*r).node->access() == Node::Public &&
195 (*r).node->status() != Node::Internal
196 && !(*r).node->doc().isEmpty()) {
198 appendFullName(className, (*r).node, classe);
199 classMap[className.toString().toLower()] = className;
204 QStringList classNames = classMap.keys();
207 foreach (const QString &className, classNames) {
208 text << classMap[className];
209 text << separator(index++, classNames.count());
213 void Generator::appendSortedQmlNames(Text& text, const Node* base, const NodeList& subs)
215 QMap<QString,Text> classMap;
218 for (int i = 0; i < subs.size(); ++i) {
220 if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
221 (base->qmlModuleIdentifier() == subs[i]->qmlModuleIdentifier())) {
222 appendFullName(t, subs[i], base);
223 classMap[t.toString().toLower()] = t;
227 QStringList names = classMap.keys();
230 foreach (const QString &name, names) {
231 text << classMap[name];
232 text << separator(index++, names.count());
236 QMultiMap<QString,QString> outFileNames;
241 void Generator::writeOutFileNames()
243 QFile* files = new QFile("/Users/msmith/depot/qt5/qtdoc/outputlist.txt");
244 files->open(QFile::WriteOnly);
245 QTextStream* filesout = new QTextStream(files);
246 QMultiMap<QString,QString>::ConstIterator i = outFileNames.begin();
247 while (i != outFileNames.end()) {
248 (*filesout) << i.key() << "\n";
256 Creates the file named \a fileName in the output directory.
257 Attaches a QTextStream to the created file, which is written
258 to all over the place using out().
260 void Generator::beginSubPage(const InnerNode* node, const QString& fileName)
262 QString path = outputDir() + QLatin1Char('/');
263 if (!node->outputSubdirectory().isEmpty())
264 path += node->outputSubdirectory() + QLatin1Char('/');
266 Generator::debugSegfault("Writing: " + path);
267 outFileNames.insert(fileName,fileName);
268 QFile* outFile = new QFile(path);
269 if (!outFile->open(QFile::WriteOnly))
270 node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
271 QTextStream* out = new QTextStream(outFile);
274 out->setCodec(outputCodec);
275 outStreamStack.push(out);
276 const_cast<InnerNode*>(node)->setOutputFileName(fileName);
280 Flush the text stream associated with the subpage, and
281 then pop it off the text stream stack and delete it.
282 This terminates output of the subpage.
284 void Generator::endSubPage()
286 outStreamStack.top()->flush();
287 delete outStreamStack.top()->device();
288 delete outStreamStack.pop();
291 QString Generator::fileBase(const Node *node) const
294 node = node->relates();
295 else if (!node->isInnerNode())
296 node = node->parent();
297 if (node->subType() == Node::QmlPropertyGroup) {
298 node = node->parent();
301 QString base = node->doc().baseName();
305 const Node *p = node;
308 const Node *pp = p->parent();
309 base.prepend(p->name());
310 if (!p->qmlModuleIdentifier().isEmpty())
311 base.prepend(p->qmlModuleIdentifier()+QChar('-'));
313 To avoid file name conflicts in the html directory,
314 we prepend a prefix (by default, "qml-") to the file name of QML
317 if ((p->subType() == Node::QmlClass) ||
318 (p->subType() == Node::QmlBasicType)) {
319 base.prepend(outputPrefix(QLatin1String("QML")));
321 if (!pp || pp->name().isEmpty() || pp->type() == Node::Document)
323 base.prepend(QLatin1Char('-'));
326 if (node->type() == Node::Document) {
327 if (node->subType() == Node::Collision) {
328 const NameCollisionNode* ncn = static_cast<const NameCollisionNode*>(node);
329 if (ncn->currentChild())
330 return fileBase(ncn->currentChild());
331 base.prepend("collision-");
333 //Was QDOC2_COMPAT, required for index.html
334 if (base.endsWith(".html"))
335 base.truncate(base.length() - 5);
337 if (node->subType() == Node::QmlModule) {
338 base.prepend("qmlmodule-");
340 if (node->subType() == Node::Module) {
341 base.append("-module");
345 // the code below is effectively equivalent to:
346 // base.replace(QRegExp("[^A-Za-z0-9]+"), " ");
347 // base = base.trimmed();
348 // base.replace(QLatin1Char(' '), QLatin1Char('-'));
349 // base = base.toLower();
350 // as this function accounted for ~8% of total running time
351 // we optimize a bit...
354 // +5 prevents realloc in fileName() below
355 res.reserve(base.size() + 5);
357 for (int i = 0; i != base.size(); ++i) {
358 QChar c = base.at(i);
359 uint u = c.unicode();
360 if (u >= 'A' && u <= 'Z')
362 if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
363 res += QLatin1Char(u);
367 res += QLatin1Char('-');
371 while (res.endsWith(QLatin1Char('-')))
377 If the \a node has a URL, return the URL as the file name.
378 Otherwise, construct the file name from the fileBase() and
379 the fileExtension(), and return the constructed name.
381 QString Generator::fileName(const Node* node) const
383 if (!node->url().isEmpty())
386 QString name = fileBase(node);
387 name += QLatin1Char('.');
388 name += fileExtension();
392 QMap<QString, QString>& Generator::formattingLeftMap()
394 return fmtLeftMaps[format()];
397 QMap<QString, QString>& Generator::formattingRightMap()
399 return fmtRightMaps[format()];
403 Returns the full document location.
405 QString Generator::fullDocumentLocation(const Node *node, bool subdir)
409 if (!node->url().isEmpty())
417 If the output is being sent to subdirectories of the
418 output directory, and if the subdir parameter is set,
419 prepend the subdirectory name + '/' to the result.
422 fdl = node->outputSubdirectory();
424 fdl.append(QLatin1Char('/'));
426 if (node->type() == Node::Namespace) {
428 // The root namespace has no name - check for this before creating
429 // an attribute containing the location of any documentation.
431 if (!fileBase(node).isEmpty())
432 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
436 else if (node->type() == Node::Document) {
437 if ((node->subType() == Node::QmlClass) ||
438 (node->subType() == Node::QmlBasicType)) {
439 QString fb = fileBase(node);
440 if (fb.startsWith(Generator::outputPrefix(QLatin1String("QML"))))
441 return fb + QLatin1Char('.') + currentGenerator()->fileExtension();
444 if (!node->qmlModuleName().isEmpty()) {
445 mq = node->qmlModuleIdentifier().replace(QChar('.'),QChar('-'));
446 mq = mq.toLower() + QLatin1Char('-');
448 return fdl+ Generator::outputPrefix(QLatin1String("QML")) + mq +
449 fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
453 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
456 else if (fileBase(node).isEmpty())
459 Node *parentNode = 0;
461 if ((parentNode = node->relates())) {
462 parentName = fullDocumentLocation(node->relates());
464 else if ((parentNode = node->parent())) {
465 if (parentNode->subType() == Node::QmlPropertyGroup) {
466 parentNode = parentNode->parent();
467 parentName = fullDocumentLocation(parentNode);
470 parentName = fullDocumentLocation(node->parent());
474 switch (node->type()) {
476 case Node::Namespace:
477 if (parentNode && !parentNode->name().isEmpty()) {
478 parentName.remove(QLatin1Char('.') + currentGenerator()->fileExtension());
479 parentName += QLatin1Char('-')
480 + fileBase(node).toLower() + QLatin1Char('.') + currentGenerator()->fileExtension();
482 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
487 const FunctionNode *functionNode =
488 static_cast<const FunctionNode *>(node);
490 if (functionNode->metaness() == FunctionNode::Dtor)
491 anchorRef = "#dtor." + functionNode->name().mid(1);
493 else if (functionNode->associatedProperty())
494 return fullDocumentLocation(functionNode->associatedProperty());
496 else if (functionNode->overloadNumber() > 1)
497 anchorRef = QLatin1Char('#') + functionNode->name()
498 + QLatin1Char('-') + QString::number(functionNode->overloadNumber());
500 anchorRef = QLatin1Char('#') + functionNode->name();
504 Use node->name() instead of fileBase(node) as
505 the latter returns the name in lower-case. For
506 HTML anchors, we need to preserve the case.
509 anchorRef = QLatin1Char('#') + node->name() + "-enum";
512 anchorRef = QLatin1Char('#') + node->name() + "-typedef";
515 anchorRef = QLatin1Char('#') + node->name() + "-prop";
517 case Node::QmlProperty:
518 anchorRef = QLatin1Char('#') + node->name() + "-prop";
520 case Node::QmlSignal:
521 anchorRef = QLatin1Char('#') + node->name() + "-signal";
523 case Node::QmlSignalHandler:
524 anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
526 case Node::QmlMethod:
527 anchorRef = QLatin1Char('#') + node->name() + "-method";
530 anchorRef = QLatin1Char('#') + node->name() + "-var";
534 parentName = fileBase(node);
535 parentName.replace(QLatin1Char('/'), QLatin1Char('-')).replace(QLatin1Char('.'), QLatin1Char('-'));
536 parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
543 // Various objects can be compat (deprecated) or obsolete.
544 if (node->type() != Node::Class && node->type() != Node::Namespace) {
545 switch (node->status()) {
547 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
548 "-compat." + currentGenerator()->fileExtension());
551 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
552 "-obsolete." + currentGenerator()->fileExtension());
559 return fdl + parentName.toLower() + anchorRef;
562 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
564 QList<Text> alsoList = node->doc().alsoList();
565 supplementAlsoList(node, alsoList);
567 if (!alsoList.isEmpty()) {
569 text << Atom::ParaLeft
570 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
572 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
574 for (int i = 0; i < alsoList.size(); ++i)
575 text << alsoList.at(i) << separator(i, alsoList.size());
577 text << Atom::ParaRight;
578 generateText(text, node, marker);
582 int Generator::generateAtom(const Atom * /* atom */,
583 const Node * /* relative */,
584 CodeMarker * /* marker */)
589 const Atom *Generator::generateAtomList(const Atom *atom,
590 const Node *relative,
596 if (atom->type() == Atom::FormatIf) {
597 int numAtoms0 = numAtoms;
598 bool rightFormat = canHandleFormat(atom->string());
599 atom = generateAtomList(atom->next(),
602 generate && rightFormat,
607 if (atom->type() == Atom::FormatElse) {
609 atom = generateAtomList(atom->next(),
612 generate && !rightFormat,
618 if (atom->type() == Atom::FormatEndif) {
619 if (generate && numAtoms0 == numAtoms) {
620 relative->location().warning(tr("Output format %1 not handled %2")
621 .arg(format()).arg(outFileName()));
622 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
623 generateAtomList(&unhandledFormatAtom,
632 else if (atom->type() == Atom::FormatElse ||
633 atom->type() == Atom::FormatEndif) {
639 n += generateAtom(atom, relative, marker);
649 void Generator::generateBody(const Node *node, CodeMarker *marker)
653 if (node->type() == Node::Document) {
654 const DocNode *dn = static_cast<const DocNode *>(node);
655 if ((dn->subType() == Node::File) || (dn->subType() == Node::Image)) {
659 if (node->doc().isEmpty()) {
660 if (!quiet && !node->isReimp()) { // ### might be unnecessary
661 node->location().warning(tr("No documentation for '%1'").arg(node->plainFullName()));
665 if (node->type() == Node::Function) {
666 const FunctionNode *func = static_cast<const FunctionNode *>(node);
667 if (func->reimplementedFrom() != 0)
668 generateReimplementedFrom(func, marker);
671 if (!generateText(node->doc().body(), node, marker)) {
676 if (node->type() == Node::Enum) {
677 const EnumNode *enume = (const EnumNode *) node;
679 QSet<QString> definedItems;
680 QList<EnumItem>::ConstIterator it = enume->items().constBegin();
681 while (it != enume->items().constEnd()) {
682 definedItems.insert((*it).name());
686 QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
687 QSet<QString> allItems = definedItems + documentedItems;
688 if (allItems.count() > definedItems.count() ||
689 allItems.count() > documentedItems.count()) {
690 QSet<QString>::ConstIterator a = allItems.constBegin();
691 while (a != allItems.constEnd()) {
692 if (!definedItems.contains(*a)) {
694 QString best = nearestName(*a, definedItems);
695 if (!best.isEmpty() && !documentedItems.contains(best))
696 details = tr("Maybe you meant '%1'?").arg(best);
698 node->doc().location().warning(tr("No such enum item '%1' in %2").arg(*a).arg(node->plainFullName()), details);
700 qDebug() << "VOID:" << node->name() << definedItems;
702 else if (!documentedItems.contains(*a)) {
703 node->doc().location().warning(tr("Undocumented enum item '%1' in %2").arg(*a).arg(node->plainFullName()));
709 else if (node->type() == Node::Function) {
710 const FunctionNode *func = static_cast<const FunctionNode *>(node);
711 QSet<QString> definedParams;
712 QList<Parameter>::ConstIterator p = func->parameters().constBegin();
713 while (p != func->parameters().constEnd()) {
714 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
715 && func->name() != QLatin1String("operator++")
716 && func->name() != QLatin1String("operator--")) {
717 node->doc().location().warning(tr("Missing parameter name"));
720 definedParams.insert((*p).name());
725 QSet<QString> documentedParams = func->doc().parameterNames();
726 QSet<QString> allParams = definedParams + documentedParams;
727 if (allParams.count() > definedParams.count()
728 || allParams.count() > documentedParams.count()) {
729 QSet<QString>::ConstIterator a = allParams.constBegin();
730 while (a != allParams.constEnd()) {
731 if (!definedParams.contains(*a)) {
733 QString best = nearestName(*a, definedParams);
735 details = tr("Maybe you meant '%1'?").arg(best);
737 node->doc().location().warning(
738 tr("No such parameter '%1' in %2").arg(*a).arg(node->plainFullName()),
741 else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
742 bool needWarning = (func->status() > Node::Obsolete);
743 if (func->overloadNumber() > 1) {
744 FunctionNode *primaryFunc =
745 func->parent()->findFunctionNode(func->name());
747 foreach (const Parameter ¶m,
748 primaryFunc->parameters()) {
749 if (param.name() == *a) {
756 if (needWarning && !func->isReimp())
757 node->doc().location().warning(
758 tr("Undocumented parameter '%1' in %2")
759 .arg(*a).arg(node->plainFullName()));
765 Something like this return value check should
766 be implemented at some point.
768 if (func->status() > Node::Obsolete && func->returnType() == "bool"
769 && func->reimplementedFrom() == 0 && !func->isOverload()) {
770 QString body = func->doc().body().toString();
771 if (!body.contains("return", Qt::CaseInsensitive))
772 node->doc().location().warning(tr("Undocumented return value"));
777 if (node->type() == Node::Document) {
778 const DocNode *dn = static_cast<const DocNode *>(node);
779 if (dn->subType() == Node::Example) {
780 generateExampleFiles(dn, marker);
782 else if (dn->subType() == Node::File) {
785 Doc::quoteFromFile(dn->doc().location(), quoter, dn->name());
786 QString code = quoter.quoteTo(dn->location(), QString(), QString());
787 CodeMarker *codeMarker = CodeMarker::markerForFileName(dn->name());
788 text << Atom(codeMarker->atomType(), code);
789 generateText(text, dn, codeMarker);
794 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
798 void Generator::generateExampleFiles(const DocNode *dn, CodeMarker *marker)
800 if (dn->childNodes().isEmpty())
802 generateFileList(dn, marker, Node::File, QString("Files:"));
803 generateFileList(dn, marker, Node::Image, QString("Images:"));
806 void Generator::generateDocNode(DocNode* /* dn */, CodeMarker* /* marker */)
811 This function is called when the documentation for an
812 example is being formatted. It outputs the list of source
813 files comprising the example, and the list of images used
814 by the example. The images are copied into a subtree of
815 \c{...doc/html/images/used-in-examples/...}
817 void Generator::generateFileList(const DocNode* dn,
819 Node::SubType subtype,
824 OpenedList openedList(OpenedList::Bullet);
826 text << Atom::ParaLeft << tag << Atom::ParaRight
827 << Atom(Atom::ListLeft, openedList.styleString());
829 foreach (const Node* child, dn->childNodes()) {
830 if (child->subType() == subtype) {
832 QString file = child->name();
833 if (subtype == Node::Image) {
834 if (!file.isEmpty()) {
836 QString userFriendlyFilePath;
837 QString srcPath = Config::findFile(dn->location(),
842 userFriendlyFilePath);
843 userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
845 QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
846 if (!dirInfo.mkpath(imgOutDir))
847 dn->location().fatal(tr("Cannot create output directory '%1'")
850 QString imgOutName = Config::copyFile(dn->location(),
859 text << Atom(Atom::ListItemNumber, openedList.numberString())
860 << Atom(Atom::ListItemLeft, openedList.styleString())
862 << Atom(Atom::Link, file)
863 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
865 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
867 << Atom(Atom::ListItemRight, openedList.styleString());
870 text << Atom(Atom::ListRight, openedList.styleString());
872 generateText(text, dn, marker);
875 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
877 if (!classe->derivedClasses().isEmpty()) {
879 text << Atom::ParaLeft
880 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
882 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
884 appendSortedNames(text, classe, classe->derivedClasses());
885 text << Atom::ParaRight;
886 generateText(text, classe, marker);
890 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
892 QList<RelatedClass>::ConstIterator r;
895 if (!classe->baseClasses().isEmpty()) {
897 text << Atom::ParaLeft
898 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
900 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
902 r = classe->baseClasses().constBegin();
904 while (r != classe->baseClasses().constEnd()) {
905 text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
906 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
907 << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
908 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
910 if ((*r).access == Node::Protected) {
911 text << " (protected)";
913 else if ((*r).access == Node::Private) {
914 text << " (private)";
916 text << separator(index++, classe->baseClasses().count());
919 text << Atom::ParaRight;
920 generateText(text, classe, marker);
925 Recursive writing of HTML files from the root \a node.
927 \note NameCollisionNodes are skipped here and processed
928 later. See HtmlGenerator::generateCollisionPages() for
931 void Generator::generateInnerNode(InnerNode* node)
933 if (!node->url().isNull())
936 if (node->type() == Node::Document) {
937 DocNode* docNode = static_cast<DocNode*>(node);
938 if (docNode->subType() == Node::ExternalPage)
940 if (docNode->subType() == Node::Image)
942 if (docNode->subType() == Node::QmlPropertyGroup)
944 if (docNode->subType() == Node::Page) {
945 if (node->count() > 0)
946 qDebug("PAGE %s HAS CHILDREN", qPrintable(docNode->title()));
951 Obtain a code marker for the source file.
953 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
955 if (node->parent() != 0) {
957 Skip name collision nodes here and process them
958 later in generateCollisionPages(). Each one is
959 appended to a list for later.
961 if ((node->type() == Node::Document) && (node->subType() == Node::Collision)) {
962 NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
963 collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
966 beginSubPage(node, fileName(node));
967 if (node->type() == Node::Namespace || node->type() == Node::Class) {
968 generateClassLikeNode(node, marker);
970 else if (node->type() == Node::Document) {
971 generateDocNode(static_cast<DocNode*>(node), marker);
977 NodeList::ConstIterator c = node->childNodes().constBegin();
978 while (c != node->childNodes().constEnd()) {
979 if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
980 generateInnerNode((InnerNode*)*c);
987 Generate a list of maintainers in the output
989 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
991 QStringList sl = getMetadataElements(node,"maintainer");
995 text << Atom::ParaLeft
996 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
998 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
1000 for (int i = 0; i < sl.size(); ++i)
1001 text << sl.at(i) << separator(i, sl.size());
1003 text << Atom::ParaRight;
1004 generateText(text, node, marker);
1009 Output the "Inherit by" list for the QML element,
1010 if it is inherited by any other elements.
1012 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
1017 QmlClassNode::subclasses(qcn->name(),subs);
1018 if (!subs.isEmpty()) {
1020 text << Atom::ParaLeft << "Inherited by ";
1021 appendSortedQmlNames(text,qcn,subs);
1022 text << Atom::ParaRight;
1023 generateText(text, qcn, marker);
1030 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
1036 Extract sections of markup text surrounded by \e qmltext
1037 and \e endqmltext and output them.
1039 bool Generator::generateQmlText(const Text& text,
1040 const Node *relative,
1042 const QString& /* qmlName */ )
1044 const Atom* atom = text.firstAtom();
1045 bool result = false;
1048 initializeTextOutput();
1050 if (atom->type() != Atom::QmlText)
1051 atom = atom->next();
1053 atom = atom->next();
1054 while (atom && (atom->type() != Atom::EndQmlText)) {
1055 int n = 1 + generateAtom(atom, relative, marker);
1057 atom = atom->next();
1066 void Generator::generateReimplementedFrom(const FunctionNode *func,
1069 if (func->reimplementedFrom() != 0) {
1070 const FunctionNode *from = func->reimplementedFrom();
1071 if (from->access() != Node::Private &&
1072 from->parent()->access() != Node::Private) {
1074 text << Atom::ParaLeft << "Reimplemented from ";
1075 QString fullName = from->parent()->name() + "::" + from->name() + "()";
1076 appendFullName(text, from->parent(), fullName, from);
1077 text << "." << Atom::ParaRight;
1078 generateText(text, func, marker);
1083 void Generator::generateSince(const Node *node, CodeMarker *marker)
1085 if (!node->since().isEmpty()) {
1087 text << Atom::ParaLeft
1089 << typeString(node);
1090 if (node->type() == Node::Enum)
1091 text << " was introduced or modified in ";
1093 text << " was introduced in ";
1095 QStringList since = node->since().split(QLatin1Char(' '));
1096 if (since.count() == 1) {
1097 // Handle legacy use of \since <version>.
1098 if (project.isEmpty())
1102 text << " " << since[0];
1104 // Reconstruct the <project> <version> string.
1105 text << " " << since.join(' ');
1108 text << "." << Atom::ParaRight;
1109 generateText(text, node, marker);
1113 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1117 switch (node->status()) {
1118 case Node::Commendable:
1121 case Node::Preliminary:
1122 text << Atom::ParaLeft
1123 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1126 << " is under development and is subject to change."
1127 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1130 case Node::Deprecated:
1131 text << Atom::ParaLeft;
1132 if (node->isInnerNode())
1133 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1134 text << "This " << typeString(node) << " is deprecated.";
1135 if (node->isInnerNode())
1136 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1137 text << Atom::ParaRight;
1139 case Node::Obsolete:
1140 text << Atom::ParaLeft;
1141 if (node->isInnerNode())
1142 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1143 text << "This " << typeString(node) << " is obsolete.";
1144 if (node->isInnerNode())
1145 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1146 text << " It is provided to keep old source code working. "
1147 << "We strongly advise against "
1148 << "using it in new code." << Atom::ParaRight;
1151 // reimplemented in HtmlGenerator subclass
1152 if (node->isInnerNode()) {
1153 text << Atom::ParaLeft
1154 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1157 << " is part of the Qt compatibility layer."
1158 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1159 << " It is provided to keep old source code working. "
1160 << "We strongly advise against using it in new code."
1164 case Node::Internal:
1168 generateText(text, node, marker);
1171 bool Generator::generateText(const Text& text,
1172 const Node *relative,
1175 bool result = false;
1176 if (text.firstAtom() != 0) {
1178 initializeTextOutput();
1179 generateAtomList(text.firstAtom(),
1189 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1192 Node::ThreadSafeness threadSafeness = node->threadSafeness();
1195 rlink << Atom(Atom::Link,"reentrant")
1196 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1198 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1201 tlink << Atom(Atom::Link,"thread-safe")
1202 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1204 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1206 switch (threadSafeness) {
1207 case Node::UnspecifiedSafeness:
1209 case Node::NonReentrant:
1210 text << Atom::ParaLeft
1211 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1213 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1221 case Node::Reentrant:
1222 case Node::ThreadSafe:
1223 text << Atom::ParaLeft
1224 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1226 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1229 if (node->isInnerNode()) {
1230 const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1231 text << "All functions in this "
1234 if (threadSafeness == Node::ThreadSafe)
1239 bool exceptions = false;
1241 NodeList threadsafe;
1242 NodeList nonreentrant;
1243 NodeList::ConstIterator c = innerNode->childNodes().constBegin();
1244 while (c != innerNode->childNodes().constEnd()) {
1246 if ((*c)->status() != Node::Obsolete){
1247 switch ((*c)->threadSafeness()) {
1248 case Node::Reentrant:
1249 reentrant.append(*c);
1250 if (threadSafeness == Node::ThreadSafe)
1253 case Node::ThreadSafe:
1254 threadsafe.append(*c);
1255 if (threadSafeness == Node::Reentrant)
1258 case Node::NonReentrant:
1259 nonreentrant.append(*c);
1270 else if (threadSafeness == Node::Reentrant) {
1271 if (nonreentrant.isEmpty()) {
1272 if (!threadsafe.isEmpty()) {
1274 appendFullNames(text,threadsafe,innerNode);
1275 singularPlural(text,threadsafe);
1276 text << " also " << tlink << ".";
1282 text << ", except for ";
1283 appendFullNames(text,nonreentrant,innerNode);
1285 singularPlural(text,nonreentrant);
1286 text << " nonreentrant.";
1287 if (!threadsafe.isEmpty()) {
1289 appendFullNames(text,threadsafe,innerNode);
1290 singularPlural(text,threadsafe);
1291 text << " " << tlink << ".";
1295 else { // thread-safe
1296 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1297 text << ", except for ";
1298 if (!reentrant.isEmpty()) {
1299 appendFullNames(text,reentrant,innerNode);
1301 singularPlural(text,reentrant);
1302 text << " only " << rlink;
1303 if (!nonreentrant.isEmpty())
1306 if (!nonreentrant.isEmpty()) {
1307 appendFullNames(text,nonreentrant,innerNode);
1309 singularPlural(text,nonreentrant);
1310 text << " nonreentrant.";
1317 text << "This " << typeString(node) << " is ";
1318 if (threadSafeness == Node::ThreadSafe)
1324 text << Atom::ParaRight;
1326 generateText(text,node,marker);
1330 Traverses the database recursivly to generate all the documentation.
1332 void Generator::generateTree()
1334 generateInnerNode(qdb_->treeRoot());
1337 Generator *Generator::generatorForFormat(const QString& format)
1339 QList<Generator *>::ConstIterator g = generators.constBegin();
1340 while (g != generators.constEnd()) {
1341 if ((*g)->format() == format)
1349 This function can be called if getLink() returns an empty
1350 string. It tests the \a atom string to see if it is a link
1351 of the form <element> :: <name>, where <element> is a QML
1352 element or component without a module qualifier. If so, it
1353 constructs a link to the <name> clause on the disambiguation
1354 page for <element> and returns that link string. It also
1355 adds the <name> as a target in the NameCollisionNode for
1356 <element>. These clauses are then constructed when the
1357 disambiguation page is actually generated.
1359 QString Generator::getCollisionLink(const Atom* atom)
1362 if (!atom->string().contains("::"))
1364 QStringList path = atom->string().split("::");
1365 NameCollisionNode* ncn = qdb_->findCollisionNode(path[0]);
1368 if (atom->next() && atom->next()->next()) {
1369 if (atom->next()->type() == Atom::FormattingLeft &&
1370 atom->next()->next()->type() == Atom::String)
1371 label = atom->next()->next()->string();
1373 ncn->addLinkTarget(path[1],label);
1374 link = fileName(ncn);
1375 link += QLatin1Char('#');
1376 link += Doc::canonicalTitle(path[1]);
1383 Looks up the tag \a t in the map of metadata values for the
1384 current topic in \a inner. If a value for the tag is found,
1385 the value is returned.
1387 \note If \a t is found in the metadata map, it is erased.
1388 i.e. Once you call this function for a particular \a t,
1391 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1394 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1395 QStringMultiMap::iterator i = metaTagMap.find(t);
1396 if (i != metaTagMap.end()) {
1398 metaTagMap.erase(i);
1404 Looks up the tag \a t in the map of metadata values for the
1405 current topic in \a inner. If values for the tag are found,
1406 they are returned in a string list.
1408 \note If \a t is found in the metadata map, all the pairs
1409 having the key \a t are erased. i.e. Once you call this
1410 function for a particular \a t, you consume \a t.
1412 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1415 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1416 s = metaTagMap.values(t);
1418 metaTagMap.remove(t);
1423 Returns a relative path name for an image.
1425 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1427 QString userFriendlyFilePath;
1428 QString filePath = Config::findFile(relative->doc().location(),
1432 imgFileExts[format()],
1433 userFriendlyFilePath);
1435 if (filePath.isEmpty())
1438 QString path = Config::copyFile(relative->doc().location(),
1440 userFriendlyFilePath,
1441 outputDir() + QLatin1String("/images"));
1442 QString images = "images";
1444 images.append(QLatin1Char('/'));
1445 return images + path;
1448 QString Generator::indent(int level, const QString& markedCode)
1457 while (i < (int) markedCode.length()) {
1458 if (markedCode.at(i) == QLatin1Char('\n')) {
1463 for (int j = 0; j < level; j++)
1464 t += QLatin1Char(' ');
1468 t += markedCode.at(i++);
1473 void Generator::initialize(const Config &config)
1475 outputFormats = config.getOutputFormats();
1476 if (!outputFormats.isEmpty()) {
1477 outDir_ = config.getOutputDir();
1479 if (outDir_.isEmpty())
1480 config.lastLocation().fatal(tr("No output directory specified in "
1481 "configuration file or on the command line"));
1484 if (dirInfo.exists(outDir_)) {
1485 if (!Config::removeDirContents(outDir_))
1486 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1489 if (!dirInfo.mkpath(outDir_))
1490 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1493 if (!dirInfo.mkdir(outDir_ + "/images"))
1494 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/images"));
1495 if (!dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1496 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/images/used-in-examples"));
1497 if (!dirInfo.mkdir(outDir_ + "/scripts"))
1498 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/scripts"));
1499 if (!dirInfo.mkdir(outDir_ + "/style"))
1500 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/style"));
1503 imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1504 imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1505 scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1506 scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1507 styleFiles = config.getCleanPathList(CONFIG_STYLES);
1508 styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1509 exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1510 exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS);
1512 QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1513 QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1514 QSet<QString>::ConstIterator f = formats.constBegin();
1515 while (f != formats.constEnd()) {
1516 imgFileExts[*f] = config.getStringList(imagesDotFileExtensions + Config::dot + *f);
1520 QList<Generator *>::ConstIterator g = generators.constBegin();
1521 while (g != generators.constEnd()) {
1522 if (outputFormats.contains((*g)->format())) {
1523 currentGenerator_ = (*g);
1524 (*g)->initializeGenerator(config);
1525 QStringList extraImages = config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
1526 QStringList::ConstIterator e = extraImages.constBegin();
1527 while (e != extraImages.constEnd()) {
1528 QString userFriendlyFilePath;
1529 QString filePath = Config::findFile(config.lastLocation(),
1533 imgFileExts[(*g)->format()],
1534 userFriendlyFilePath);
1535 if (!filePath.isEmpty())
1536 Config::copyFile(config.lastLocation(),
1538 userFriendlyFilePath,
1544 // Documentation template handling
1545 QString templateDir = config.getString((*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
1547 QStringList searchDirs;
1548 if (!templateDir.isEmpty()) {
1549 searchDirs.append(templateDir);
1551 if (!Config::installDir.isEmpty()) {
1552 searchDirs.append(Config::installDir);
1555 if (!searchDirs.isEmpty()) {
1557 QStringList scripts = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1558 e = scripts.constBegin();
1559 while (e != scripts.constEnd()) {
1560 QString userFriendlyFilePath;
1561 QString filePath = Config::findFile(config.lastLocation(),
1566 userFriendlyFilePath);
1567 if (!filePath.isEmpty())
1568 Config::copyFile(config.lastLocation(),
1570 userFriendlyFilePath,
1576 QStringList styles = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1577 e = styles.constBegin();
1578 while (e != styles.constEnd()) {
1579 QString userFriendlyFilePath;
1580 QString filePath = Config::findFile(config.lastLocation(),
1585 userFriendlyFilePath);
1586 if (!filePath.isEmpty())
1587 Config::copyFile(config.lastLocation(),
1589 userFriendlyFilePath,
1599 QRegExp secondParamAndAbove("[\2-\7]");
1600 QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1601 QSet<QString>::ConstIterator n = formattingNames.constBegin();
1602 while (n != formattingNames.constEnd()) {
1603 QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1604 QSet<QString> formats = config.subVars(formattingDotName);
1605 QSet<QString>::ConstIterator f = formats.constBegin();
1606 while (f != formats.constEnd()) {
1607 QString def = config.getString(formattingDotName + Config::dot + *f);
1608 if (!def.isEmpty()) {
1609 int numParams = Config::numParams(def);
1610 int numOccs = def.count("\1");
1611 if (numParams != 1) {
1612 config.lastLocation().warning(tr("Formatting '%1' must "
1614 "parameter (found %2)")
1615 .arg(*n).arg(numParams));
1617 else if (numOccs > 1) {
1618 config.lastLocation().fatal(tr("Formatting '%1' must "
1619 "contain exactly one "
1620 "occurrence of '\\1' "
1622 .arg(*n).arg(numOccs));
1625 int paramPos = def.indexOf("\1");
1626 fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1627 fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1635 project = config.getString(CONFIG_PROJECT);
1637 QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1638 if (!prefixes.isEmpty()) {
1639 foreach (const QString &prefix, prefixes)
1640 outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1643 outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1647 Appends each directory path in \a moreImageDirs to the
1648 list of image directories.
1650 void Generator::augmentImageDirs(QSet<QString>& moreImageDirs)
1652 if (moreImageDirs.isEmpty())
1654 QSet<QString>::const_iterator i = moreImageDirs.begin();
1655 while (i != moreImageDirs.end()) {
1656 imageDirs.append(*i);
1661 void Generator::initializeGenerator(const Config & /* config */)
1665 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1667 return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1671 Used for writing to the current output stream. Returns a
1672 reference to the current output stream, which is then used
1673 with the \c {<<} operator for writing.
1675 QTextStream &Generator::out()
1677 return *outStreamStack.top();
1680 QString Generator::outFileName()
1682 return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1685 QString Generator::outputPrefix(const QString &nodeType)
1687 return outputPrefixes[nodeType];
1690 bool Generator::parseArg(const QString& src,
1694 QStringRef* contents,
1698 #define SKIP_CHAR(c) \
1700 qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1701 if (i >= n || src[i] != c) { \
1703 qDebug() << " char '" << c << "' not found"; \
1709 #define SKIP_SPACE \
1710 while (i < n && src[i] == ' ') \
1716 // assume "<@" has been parsed outside
1720 if (tag != QStringRef(&src, i, tag.length())) {
1722 qDebug() << "tag " << tag << " not found at " << i;
1727 qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1732 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1735 // read parameter name
1737 while (i < n && src[i].isLetter())
1739 if (src[i] == '=') {
1741 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1744 // skip parameter name
1746 while (i < n && src[i] != '"')
1748 *par1 = QStringRef(&src, j, i - j);
1753 qDebug() << "no optional parameter found";
1759 // find contents up to closing "</@tag>
1762 if (i + 4 + tag.length() > n)
1766 if (src[i + 1] != '/')
1768 if (src[i + 2] != '@')
1770 if (tag != QStringRef(&src, i + 3, tag.length()))
1772 if (src[i + 3 + tag.length()] != '>')
1777 *contents = QStringRef(&src, j, i - j);
1779 i += tag.length() + 4;
1783 qDebug() << " tag " << tag << " found: pos now: " << i;
1788 QString Generator::plainCode(const QString& markedCode)
1790 QString t = markedCode;
1791 t.replace(tag, QString());
1792 t.replace(quot, QLatin1String("\""));
1793 t.replace(gt, QLatin1String(">"));
1794 t.replace(lt, QLatin1String("<"));
1795 t.replace(amp, QLatin1String("&"));
1799 void Generator::setImageFileExtensions(const QStringList& extensions)
1801 imgFileExts[format()] = extensions;
1804 void Generator::singularPlural(Text& text, const NodeList& nodes)
1806 if (nodes.count() == 1)
1812 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1815 atom = atom->next();
1816 while (atom != 0 && atom->type() != type) {
1818 atom = atom->next();
1824 Resets the variables used during text output.
1826 void Generator::initializeTextOutput()
1829 inContents_ = false;
1830 inSectionHeading_ = false;
1831 inTableHeader_ = false;
1833 threeColumnEnumValueTable_ = true;
1835 sectionNumber_.clear();
1838 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1840 if (node->type() == Node::Function) {
1841 const FunctionNode *func = static_cast<const FunctionNode *>(node);
1842 if (func->overloadNumber() == 1) {
1843 QString alternateName;
1844 const FunctionNode *alternateFunc = 0;
1846 if (func->name().startsWith("set") && func->name().size() >= 4) {
1847 alternateName = func->name()[3].toLower();
1848 alternateName += func->name().mid(4);
1849 alternateFunc = func->parent()->findFunctionNode(alternateName);
1851 if (!alternateFunc) {
1852 alternateName = "is" + func->name().mid(3);
1853 alternateFunc = func->parent()->findFunctionNode(alternateName);
1854 if (!alternateFunc) {
1855 alternateName = "has" + func->name().mid(3);
1856 alternateFunc = func->parent()->findFunctionNode(alternateName);
1860 else if (!func->name().isEmpty()) {
1861 alternateName = "set";
1862 alternateName += func->name()[0].toUpper();
1863 alternateName += func->name().mid(1);
1864 alternateFunc = func->parent()->findFunctionNode(alternateName);
1867 if (alternateFunc && alternateFunc->access() != Node::Private) {
1869 for (i = 0; i < alsoList.size(); ++i) {
1870 if (alsoList.at(i).toString().contains(alternateName))
1874 if (i == alsoList.size()) {
1875 alternateName += "()";
1878 also << Atom(Atom::Link, alternateName)
1879 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1881 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1882 alsoList.prepend(also);
1889 void Generator::terminate()
1891 QList<Generator *>::ConstIterator g = generators.constBegin();
1892 while (g != generators.constEnd()) {
1893 if (outputFormats.contains((*g)->format()))
1894 (*g)->terminateGenerator();
1898 fmtLeftMaps.clear();
1899 fmtRightMaps.clear();
1900 imgFileExts.clear();
1904 QmlClassNode::terminate();
1905 ExampleNode::terminate();
1908 void Generator::terminateGenerator()
1913 Trims trailing whitespace off the \a string and returns
1916 QString Generator::trimmedTrailing(const QString& string)
1918 QString trimmed = string;
1919 while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1920 trimmed.truncate(trimmed.length() - 1);
1924 QString Generator::typeString(const Node *node)
1926 switch (node->type()) {
1927 case Node::Namespace:
1931 case Node::Document:
1933 switch (node->subType()) {
1934 case Node::QmlClass:
1936 case Node::QmlPropertyGroup:
1937 return "property group";
1938 case Node::QmlBasicType:
1941 return "documentation";
1948 case Node::Function:
1950 case Node::Property:
1953 return "documentation";
1957 void Generator::unknownAtom(const Atom *atom)
1959 Location::internalError(tr("unknown atom type '%1' in %2 generator")
1960 .arg(atom->typeString()).arg(format()));