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;
99 Constructs the generator base class. Prepends the newly
100 constructed generator to the list of output generators.
101 Sets a pointer to the QDoc database singleton, which is
102 available to the generator subclasses.
104 Generator::Generator()
112 inSectionHeading_(false),
113 inTableHeader_(false),
114 threeColumnEnumValueTable_(true),
117 qdb_ = QDocDatabase::qdocDB();
118 generators.prepend(this);
122 Destroys the generator after removing it from the list of
125 Generator::~Generator()
127 generators.removeAll(this);
130 void Generator::appendFullName(Text& text,
131 const Node *apparentNode,
132 const Node *relative,
133 const Node *actualNode)
136 actualNode = apparentNode;
137 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
138 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
139 << Atom(Atom::String, apparentNode->plainFullName(relative))
140 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
143 void Generator::appendFullName(Text& text,
144 const Node *apparentNode,
145 const QString& fullName,
146 const Node *actualNode)
149 actualNode = apparentNode;
150 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
151 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
152 << Atom(Atom::String, fullName)
153 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
156 void Generator::appendFullNames(Text& text, const NodeList& nodes, const Node* relative)
158 NodeList::ConstIterator n = nodes.constBegin();
160 while (n != nodes.constEnd()) {
161 appendFullName(text,*n,relative);
162 text << comma(index++,nodes.count());
167 void Generator::appendSortedNames(Text& text, const ClassNode *classe, const QList<RelatedClass> &classes)
169 QList<RelatedClass>::ConstIterator r;
170 QMap<QString,Text> classMap;
173 r = classes.constBegin();
174 while (r != classes.constEnd()) {
175 if ((*r).node->access() == Node::Public &&
176 (*r).node->status() != Node::Internal
177 && !(*r).node->doc().isEmpty()) {
179 appendFullName(className, (*r).node, classe);
180 classMap[className.toString().toLower()] = className;
185 QStringList classNames = classMap.keys();
188 foreach (const QString &className, classNames) {
189 text << classMap[className];
190 text << separator(index++, classNames.count());
194 void Generator::appendSortedQmlNames(Text& text, const Node* base, const NodeList& subs)
196 QMap<QString,Text> classMap;
199 for (int i = 0; i < subs.size(); ++i) {
201 if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
202 (base->qmlModuleIdentifier() == subs[i]->qmlModuleIdentifier())) {
203 appendFullName(t, subs[i], base);
204 classMap[t.toString().toLower()] = t;
208 QStringList names = classMap.keys();
211 foreach (const QString &name, names) {
212 text << classMap[name];
213 text << separator(index++, names.count());
217 QMultiMap<QString,QString> outFileNames;
222 void Generator::writeOutFileNames()
224 QFile* files = new QFile("/Users/msmith/depot/qt5/qtdoc/outputlist.txt");
225 files->open(QFile::WriteOnly);
226 QTextStream* filesout = new QTextStream(files);
227 QMultiMap<QString,QString>::ConstIterator i = outFileNames.begin();
228 while (i != outFileNames.end()) {
229 (*filesout) << i.key() << "\n";
237 Creates the file named \a fileName in the output directory.
238 Attaches a QTextStream to the created file, which is written
239 to all over the place using out().
241 void Generator::beginSubPage(const InnerNode* node, const QString& fileName)
243 QString path = outputDir() + QLatin1Char('/');
244 if (!node->outputSubdirectory().isEmpty())
245 path += node->outputSubdirectory() + QLatin1Char('/');
247 outFileNames.insert(fileName,fileName);
248 QFile* outFile = new QFile(path);
249 if (!outFile->open(QFile::WriteOnly))
250 node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
251 QTextStream* out = new QTextStream(outFile);
254 out->setCodec(outputCodec);
255 outStreamStack.push(out);
256 const_cast<InnerNode*>(node)->setOutputFileName(fileName);
260 Flush the text stream associated with the subpage, and
261 then pop it off the text stream stack and delete it.
262 This terminates output of the subpage.
264 void Generator::endSubPage()
266 outStreamStack.top()->flush();
267 delete outStreamStack.top()->device();
268 delete outStreamStack.pop();
271 QString Generator::fileBase(const Node *node) const
274 node = node->relates();
275 else if (!node->isInnerNode())
276 node = node->parent();
277 if (node->subType() == Node::QmlPropertyGroup) {
278 node = node->parent();
281 QString base = node->doc().baseName();
285 const Node *p = node;
288 const Node *pp = p->parent();
289 base.prepend(p->name());
290 if (!p->qmlModuleIdentifier().isEmpty())
291 base.prepend(p->qmlModuleIdentifier()+QChar('-'));
293 To avoid file name conflicts in the html directory,
294 we prepend a prefix (by default, "qml-") to the file name of QML
297 if ((p->subType() == Node::QmlClass) ||
298 (p->subType() == Node::QmlBasicType)) {
299 base.prepend(outputPrefix(QLatin1String("QML")));
301 if (!pp || pp->name().isEmpty() || pp->type() == Node::Document)
303 base.prepend(QLatin1Char('-'));
306 if (node->type() == Node::Document) {
307 if (node->subType() == Node::Collision) {
308 const NameCollisionNode* ncn = static_cast<const NameCollisionNode*>(node);
309 if (ncn->currentChild())
310 return fileBase(ncn->currentChild());
311 base.prepend("collision-");
313 //Was QDOC2_COMPAT, required for index.html
314 if (base.endsWith(".html"))
315 base.truncate(base.length() - 5);
317 if (node->subType() == Node::QmlModule) {
318 base.prepend("qmlmodule-");
320 if (node->subType() == Node::Module) {
321 base.append("-module");
325 // the code below is effectively equivalent to:
326 // base.replace(QRegExp("[^A-Za-z0-9]+"), " ");
327 // base = base.trimmed();
328 // base.replace(QLatin1Char(' '), QLatin1Char('-'));
329 // base = base.toLower();
330 // as this function accounted for ~8% of total running time
331 // we optimize a bit...
334 // +5 prevents realloc in fileName() below
335 res.reserve(base.size() + 5);
337 for (int i = 0; i != base.size(); ++i) {
338 QChar c = base.at(i);
339 uint u = c.unicode();
340 if (u >= 'A' && u <= 'Z')
342 if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
343 res += QLatin1Char(u);
347 res += QLatin1Char('-');
351 while (res.endsWith(QLatin1Char('-')))
357 If the \a node has a URL, return the URL as the file name.
358 Otherwise, construct the file name from the fileBase() and
359 the fileExtension(), and return the constructed name.
361 QString Generator::fileName(const Node* node) const
363 if (!node->url().isEmpty())
366 QString name = fileBase(node);
367 name += QLatin1Char('.');
368 name += fileExtension();
372 QMap<QString, QString>& Generator::formattingLeftMap()
374 return fmtLeftMaps[format()];
377 QMap<QString, QString>& Generator::formattingRightMap()
379 return fmtRightMaps[format()];
383 Returns the full document location.
385 QString Generator::fullDocumentLocation(const Node *node, bool subdir)
389 if (!node->url().isEmpty())
397 If the output is being sent to subdirectories of the
398 output directory, and if the subdir parameter is set,
399 prepend the subdirectory name + '/' to the result.
402 fdl = node->outputSubdirectory();
404 fdl.append(QLatin1Char('/'));
406 if (node->type() == Node::Namespace) {
408 // The root namespace has no name - check for this before creating
409 // an attribute containing the location of any documentation.
411 if (!fileBase(node).isEmpty())
412 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
416 else if (node->type() == Node::Document) {
417 if ((node->subType() == Node::QmlClass) ||
418 (node->subType() == Node::QmlBasicType)) {
419 QString fb = fileBase(node);
420 if (fb.startsWith(Generator::outputPrefix(QLatin1String("QML"))))
421 return fb + QLatin1Char('.') + currentGenerator()->fileExtension();
424 if (!node->qmlModuleName().isEmpty()) {
425 mq = node->qmlModuleIdentifier().replace(QChar('.'),QChar('-'));
426 mq = mq.toLower() + QLatin1Char('-');
428 return fdl+ Generator::outputPrefix(QLatin1String("QML")) + mq +
429 fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
433 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
436 else if (fileBase(node).isEmpty())
439 Node *parentNode = 0;
441 if ((parentNode = node->relates())) {
442 parentName = fullDocumentLocation(node->relates());
444 else if ((parentNode = node->parent())) {
445 if (parentNode->subType() == Node::QmlPropertyGroup) {
446 parentNode = parentNode->parent();
447 parentName = fullDocumentLocation(parentNode);
450 parentName = fullDocumentLocation(node->parent());
454 switch (node->type()) {
456 case Node::Namespace:
457 if (parentNode && !parentNode->name().isEmpty()) {
458 parentName.remove(QLatin1Char('.') + currentGenerator()->fileExtension());
459 parentName += QLatin1Char('-')
460 + fileBase(node).toLower() + QLatin1Char('.') + currentGenerator()->fileExtension();
462 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
467 const FunctionNode *functionNode =
468 static_cast<const FunctionNode *>(node);
470 if (functionNode->metaness() == FunctionNode::Dtor)
471 anchorRef = "#dtor." + functionNode->name().mid(1);
473 else if (functionNode->associatedProperty())
474 return fullDocumentLocation(functionNode->associatedProperty());
476 else if (functionNode->overloadNumber() > 1)
477 anchorRef = QLatin1Char('#') + functionNode->name()
478 + QLatin1Char('-') + QString::number(functionNode->overloadNumber());
480 anchorRef = QLatin1Char('#') + functionNode->name();
484 Use node->name() instead of fileBase(node) as
485 the latter returns the name in lower-case. For
486 HTML anchors, we need to preserve the case.
489 anchorRef = QLatin1Char('#') + node->name() + "-enum";
492 anchorRef = QLatin1Char('#') + node->name() + "-typedef";
495 anchorRef = QLatin1Char('#') + node->name() + "-prop";
497 case Node::QmlProperty:
498 anchorRef = QLatin1Char('#') + node->name() + "-prop";
500 case Node::QmlSignal:
501 anchorRef = QLatin1Char('#') + node->name() + "-signal";
503 case Node::QmlSignalHandler:
504 anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
506 case Node::QmlMethod:
507 anchorRef = QLatin1Char('#') + node->name() + "-method";
510 anchorRef = QLatin1Char('#') + node->name() + "-var";
514 parentName = fileBase(node);
515 parentName.replace(QLatin1Char('/'), QLatin1Char('-')).replace(QLatin1Char('.'), QLatin1Char('-'));
516 parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
523 // Various objects can be compat (deprecated) or obsolete.
524 if (node->type() != Node::Class && node->type() != Node::Namespace) {
525 switch (node->status()) {
527 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
528 "-compat." + currentGenerator()->fileExtension());
531 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
532 "-obsolete." + currentGenerator()->fileExtension());
539 return fdl + parentName.toLower() + anchorRef;
542 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
544 QList<Text> alsoList = node->doc().alsoList();
545 supplementAlsoList(node, alsoList);
547 if (!alsoList.isEmpty()) {
549 text << Atom::ParaLeft
550 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
552 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
554 for (int i = 0; i < alsoList.size(); ++i)
555 text << alsoList.at(i) << separator(i, alsoList.size());
557 text << Atom::ParaRight;
558 generateText(text, node, marker);
562 int Generator::generateAtom(const Atom * /* atom */,
563 const Node * /* relative */,
564 CodeMarker * /* marker */)
569 const Atom *Generator::generateAtomList(const Atom *atom,
570 const Node *relative,
576 if (atom->type() == Atom::FormatIf) {
577 int numAtoms0 = numAtoms;
578 bool rightFormat = canHandleFormat(atom->string());
579 atom = generateAtomList(atom->next(),
582 generate && rightFormat,
587 if (atom->type() == Atom::FormatElse) {
589 atom = generateAtomList(atom->next(),
592 generate && !rightFormat,
598 if (atom->type() == Atom::FormatEndif) {
599 if (generate && numAtoms0 == numAtoms) {
600 relative->location().warning(tr("Output format %1 not handled %2")
601 .arg(format()).arg(outFileName()));
602 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
603 generateAtomList(&unhandledFormatAtom,
612 else if (atom->type() == Atom::FormatElse ||
613 atom->type() == Atom::FormatEndif) {
619 n += generateAtom(atom, relative, marker);
629 void Generator::generateBody(const Node *node, CodeMarker *marker)
633 if (node->type() == Node::Document) {
634 const DocNode *dn = static_cast<const DocNode *>(node);
635 if ((dn->subType() == Node::File) || (dn->subType() == Node::Image)) {
639 if (node->doc().isEmpty()) {
640 if (!quiet && !node->isReimp()) { // ### might be unnecessary
641 node->location().warning(tr("No documentation for '%1'").arg(node->plainFullName()));
645 if (node->type() == Node::Function) {
646 const FunctionNode *func = static_cast<const FunctionNode *>(node);
647 if (func->reimplementedFrom() != 0)
648 generateReimplementedFrom(func, marker);
651 if (!generateText(node->doc().body(), node, marker)) {
656 if (node->type() == Node::Enum) {
657 const EnumNode *enume = (const EnumNode *) node;
659 QSet<QString> definedItems;
660 QList<EnumItem>::ConstIterator it = enume->items().constBegin();
661 while (it != enume->items().constEnd()) {
662 definedItems.insert((*it).name());
666 QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
667 QSet<QString> allItems = definedItems + documentedItems;
668 if (allItems.count() > definedItems.count() ||
669 allItems.count() > documentedItems.count()) {
670 QSet<QString>::ConstIterator a = allItems.constBegin();
671 while (a != allItems.constEnd()) {
672 if (!definedItems.contains(*a)) {
674 QString best = nearestName(*a, definedItems);
675 if (!best.isEmpty() && !documentedItems.contains(best))
676 details = tr("Maybe you meant '%1'?").arg(best);
678 node->doc().location().warning(tr("No such enum item '%1' in %2").arg(*a).arg(node->plainFullName()), details);
680 qDebug() << "VOID:" << node->name() << definedItems;
682 else if (!documentedItems.contains(*a)) {
683 node->doc().location().warning(tr("Undocumented enum item '%1' in %2").arg(*a).arg(node->plainFullName()));
689 else if (node->type() == Node::Function) {
690 const FunctionNode *func = static_cast<const FunctionNode *>(node);
691 QSet<QString> definedParams;
692 QList<Parameter>::ConstIterator p = func->parameters().constBegin();
693 while (p != func->parameters().constEnd()) {
694 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
695 && func->name() != QLatin1String("operator++")
696 && func->name() != QLatin1String("operator--")) {
697 node->doc().location().warning(tr("Missing parameter name"));
700 definedParams.insert((*p).name());
705 QSet<QString> documentedParams = func->doc().parameterNames();
706 QSet<QString> allParams = definedParams + documentedParams;
707 if (allParams.count() > definedParams.count()
708 || allParams.count() > documentedParams.count()) {
709 QSet<QString>::ConstIterator a = allParams.constBegin();
710 while (a != allParams.constEnd()) {
711 if (!definedParams.contains(*a)) {
713 QString best = nearestName(*a, definedParams);
715 details = tr("Maybe you meant '%1'?").arg(best);
717 node->doc().location().warning(
718 tr("No such parameter '%1' in %2").arg(*a).arg(node->plainFullName()),
721 else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
722 bool needWarning = (func->status() > Node::Obsolete);
723 if (func->overloadNumber() > 1) {
724 FunctionNode *primaryFunc =
725 func->parent()->findFunctionNode(func->name());
727 foreach (const Parameter ¶m,
728 primaryFunc->parameters()) {
729 if (param.name() == *a) {
736 if (needWarning && !func->isReimp())
737 node->doc().location().warning(
738 tr("Undocumented parameter '%1' in %2")
739 .arg(*a).arg(node->plainFullName()));
745 Something like this return value check should
746 be implemented at some point.
748 if (func->status() > Node::Obsolete && func->returnType() == "bool"
749 && func->reimplementedFrom() == 0 && !func->isOverload()) {
750 QString body = func->doc().body().toString();
751 if (!body.contains("return", Qt::CaseInsensitive))
752 node->doc().location().warning(tr("Undocumented return value"));
757 if (node->type() == Node::Document) {
758 const DocNode *dn = static_cast<const DocNode *>(node);
759 if (dn->subType() == Node::Example) {
760 generateExampleFiles(dn, marker);
762 else if (dn->subType() == Node::File) {
765 Doc::quoteFromFile(dn->doc().location(), quoter, dn->name());
766 QString code = quoter.quoteTo(dn->location(), QString(), QString());
767 CodeMarker *codeMarker = CodeMarker::markerForFileName(dn->name());
768 text << Atom(codeMarker->atomType(), code);
769 generateText(text, dn, codeMarker);
774 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
778 void Generator::generateExampleFiles(const DocNode *dn, CodeMarker *marker)
780 if (dn->childNodes().isEmpty())
782 generateFileList(dn, marker, Node::File, QString("Files:"));
783 generateFileList(dn, marker, Node::Image, QString("Images:"));
786 void Generator::generateDocNode(DocNode* /* dn */, CodeMarker* /* marker */)
791 This function is called when the documentation for an
792 example is being formatted. It outputs the list of source
793 files comprising the example, and the list of images used
794 by the example. The images are copied into a subtree of
795 \c{...doc/html/images/used-in-examples/...}
797 void Generator::generateFileList(const DocNode* dn,
799 Node::SubType subtype,
804 OpenedList openedList(OpenedList::Bullet);
806 text << Atom::ParaLeft << tag << Atom::ParaRight
807 << Atom(Atom::ListLeft, openedList.styleString());
809 foreach (const Node* child, dn->childNodes()) {
810 if (child->subType() == subtype) {
812 QString file = child->name();
813 if (subtype == Node::Image) {
814 if (!file.isEmpty()) {
816 QString userFriendlyFilePath;
817 QString srcPath = Config::findFile(dn->location(),
822 userFriendlyFilePath);
823 userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
825 QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
826 if (!dirInfo.mkpath(imgOutDir))
827 dn->location().fatal(tr("Cannot create output directory '%1'")
830 QString imgOutName = Config::copyFile(dn->location(),
839 text << Atom(Atom::ListItemNumber, openedList.numberString())
840 << Atom(Atom::ListItemLeft, openedList.styleString())
842 << Atom(Atom::Link, file)
843 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
845 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
847 << Atom(Atom::ListItemRight, openedList.styleString());
850 text << Atom(Atom::ListRight, openedList.styleString());
852 generateText(text, dn, marker);
855 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
857 if (!classe->derivedClasses().isEmpty()) {
859 text << Atom::ParaLeft
860 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
862 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
864 appendSortedNames(text, classe, classe->derivedClasses());
865 text << Atom::ParaRight;
866 generateText(text, classe, marker);
870 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
872 QList<RelatedClass>::ConstIterator r;
875 if (!classe->baseClasses().isEmpty()) {
877 text << Atom::ParaLeft
878 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
880 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
882 r = classe->baseClasses().constBegin();
884 while (r != classe->baseClasses().constEnd()) {
885 text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
886 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
887 << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
888 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
890 if ((*r).access == Node::Protected) {
891 text << " (protected)";
893 else if ((*r).access == Node::Private) {
894 text << " (private)";
896 text << separator(index++, classe->baseClasses().count());
899 text << Atom::ParaRight;
900 generateText(text, classe, marker);
905 Recursive writing of HTML files from the root \a node.
907 \note NameCollisionNodes are skipped here and processed
908 later. See HtmlGenerator::generateCollisionPages() for
911 void Generator::generateInnerNode(InnerNode* node)
913 if (!node->url().isNull())
916 if (node->type() == Node::Document) {
917 DocNode* docNode = static_cast<DocNode*>(node);
918 if (docNode->subType() == Node::ExternalPage)
920 if (docNode->subType() == Node::Image)
922 if (docNode->subType() == Node::QmlPropertyGroup)
924 if (docNode->subType() == Node::Page) {
925 if (node->count() > 0)
926 qDebug("PAGE %s HAS CHILDREN", qPrintable(docNode->title()));
931 Obtain a code marker for the source file.
933 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
935 if (node->parent() != 0) {
937 Skip name collision nodes here and process them
938 later in generateCollisionPages(). Each one is
939 appended to a list for later.
941 if ((node->type() == Node::Document) && (node->subType() == Node::Collision)) {
942 NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
943 collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
946 beginSubPage(node, fileName(node));
947 if (node->type() == Node::Namespace || node->type() == Node::Class) {
948 generateClassLikeNode(node, marker);
950 else if (node->type() == Node::Document) {
951 generateDocNode(static_cast<DocNode*>(node), marker);
957 NodeList::ConstIterator c = node->childNodes().constBegin();
958 while (c != node->childNodes().constEnd()) {
959 if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
960 generateInnerNode((InnerNode*)*c);
967 Generate a list of maintainers in the output
969 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
971 QStringList sl = getMetadataElements(node,"maintainer");
975 text << Atom::ParaLeft
976 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
978 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
980 for (int i = 0; i < sl.size(); ++i)
981 text << sl.at(i) << separator(i, sl.size());
983 text << Atom::ParaRight;
984 generateText(text, node, marker);
989 Output the "Inherit by" list for the QML element,
990 if it is inherited by any other elements.
992 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
997 QmlClassNode::subclasses(qcn->name(),subs);
998 if (!subs.isEmpty()) {
1000 text << Atom::ParaLeft << "Inherited by ";
1001 appendSortedQmlNames(text,qcn,subs);
1002 text << Atom::ParaRight;
1003 generateText(text, qcn, marker);
1010 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
1016 Extract sections of markup text surrounded by \e qmltext
1017 and \e endqmltext and output them.
1019 bool Generator::generateQmlText(const Text& text,
1020 const Node *relative,
1022 const QString& /* qmlName */ )
1024 const Atom* atom = text.firstAtom();
1025 bool result = false;
1028 initializeTextOutput();
1030 if (atom->type() != Atom::QmlText)
1031 atom = atom->next();
1033 atom = atom->next();
1034 while (atom && (atom->type() != Atom::EndQmlText)) {
1035 int n = 1 + generateAtom(atom, relative, marker);
1037 atom = atom->next();
1046 void Generator::generateReimplementedFrom(const FunctionNode *func,
1049 if (func->reimplementedFrom() != 0) {
1050 const FunctionNode *from = func->reimplementedFrom();
1051 if (from->access() != Node::Private &&
1052 from->parent()->access() != Node::Private) {
1054 text << Atom::ParaLeft << "Reimplemented from ";
1055 QString fullName = from->parent()->name() + "::" + from->name() + "()";
1056 appendFullName(text, from->parent(), fullName, from);
1057 text << "." << Atom::ParaRight;
1058 generateText(text, func, marker);
1063 void Generator::generateSince(const Node *node, CodeMarker *marker)
1065 if (!node->since().isEmpty()) {
1067 text << Atom::ParaLeft
1069 << typeString(node);
1070 if (node->type() == Node::Enum)
1071 text << " was introduced or modified in ";
1073 text << " was introduced in ";
1075 QStringList since = node->since().split(QLatin1Char(' '));
1076 if (since.count() == 1) {
1077 // Handle legacy use of \since <version>.
1078 if (project.isEmpty())
1082 text << " " << since[0];
1084 // Reconstruct the <project> <version> string.
1085 text << " " << since.join(' ');
1088 text << "." << Atom::ParaRight;
1089 generateText(text, node, marker);
1093 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1097 switch (node->status()) {
1098 case Node::Commendable:
1101 case Node::Preliminary:
1102 text << Atom::ParaLeft
1103 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1106 << " is under development and is subject to change."
1107 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1110 case Node::Deprecated:
1111 text << Atom::ParaLeft;
1112 if (node->isInnerNode())
1113 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1114 text << "This " << typeString(node) << " is deprecated.";
1115 if (node->isInnerNode())
1116 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1117 text << Atom::ParaRight;
1119 case Node::Obsolete:
1120 text << Atom::ParaLeft;
1121 if (node->isInnerNode())
1122 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1123 text << "This " << typeString(node) << " is obsolete.";
1124 if (node->isInnerNode())
1125 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1126 text << " It is provided to keep old source code working. "
1127 << "We strongly advise against "
1128 << "using it in new code." << Atom::ParaRight;
1131 // reimplemented in HtmlGenerator subclass
1132 if (node->isInnerNode()) {
1133 text << Atom::ParaLeft
1134 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1137 << " is part of the Qt compatibility layer."
1138 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1139 << " It is provided to keep old source code working. "
1140 << "We strongly advise against using it in new code."
1144 case Node::Internal:
1148 generateText(text, node, marker);
1151 bool Generator::generateText(const Text& text,
1152 const Node *relative,
1155 bool result = false;
1156 if (text.firstAtom() != 0) {
1158 initializeTextOutput();
1159 generateAtomList(text.firstAtom(),
1169 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1172 Node::ThreadSafeness threadSafeness = node->threadSafeness();
1175 rlink << Atom(Atom::Link,"reentrant")
1176 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1178 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1181 tlink << Atom(Atom::Link,"thread-safe")
1182 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1184 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1186 switch (threadSafeness) {
1187 case Node::UnspecifiedSafeness:
1189 case Node::NonReentrant:
1190 text << Atom::ParaLeft
1191 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1193 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1201 case Node::Reentrant:
1202 case Node::ThreadSafe:
1203 text << Atom::ParaLeft
1204 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1206 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1209 if (node->isInnerNode()) {
1210 const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1211 text << "All functions in this "
1214 if (threadSafeness == Node::ThreadSafe)
1219 bool exceptions = false;
1221 NodeList threadsafe;
1222 NodeList nonreentrant;
1223 NodeList::ConstIterator c = innerNode->childNodes().constBegin();
1224 while (c != innerNode->childNodes().constEnd()) {
1226 if ((*c)->status() != Node::Obsolete){
1227 switch ((*c)->threadSafeness()) {
1228 case Node::Reentrant:
1229 reentrant.append(*c);
1230 if (threadSafeness == Node::ThreadSafe)
1233 case Node::ThreadSafe:
1234 threadsafe.append(*c);
1235 if (threadSafeness == Node::Reentrant)
1238 case Node::NonReentrant:
1239 nonreentrant.append(*c);
1250 else if (threadSafeness == Node::Reentrant) {
1251 if (nonreentrant.isEmpty()) {
1252 if (!threadsafe.isEmpty()) {
1254 appendFullNames(text,threadsafe,innerNode);
1255 singularPlural(text,threadsafe);
1256 text << " also " << tlink << ".";
1262 text << ", except for ";
1263 appendFullNames(text,nonreentrant,innerNode);
1265 singularPlural(text,nonreentrant);
1266 text << " nonreentrant.";
1267 if (!threadsafe.isEmpty()) {
1269 appendFullNames(text,threadsafe,innerNode);
1270 singularPlural(text,threadsafe);
1271 text << " " << tlink << ".";
1275 else { // thread-safe
1276 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1277 text << ", except for ";
1278 if (!reentrant.isEmpty()) {
1279 appendFullNames(text,reentrant,innerNode);
1281 singularPlural(text,reentrant);
1282 text << " only " << rlink;
1283 if (!nonreentrant.isEmpty())
1286 if (!nonreentrant.isEmpty()) {
1287 appendFullNames(text,nonreentrant,innerNode);
1289 singularPlural(text,nonreentrant);
1290 text << " nonreentrant.";
1297 text << "This " << typeString(node) << " is ";
1298 if (threadSafeness == Node::ThreadSafe)
1304 text << Atom::ParaRight;
1306 generateText(text,node,marker);
1310 Traverses the database recursivly to generate all the documentation.
1312 void Generator::generateTree()
1314 generateInnerNode(qdb_->treeRoot());
1317 Generator *Generator::generatorForFormat(const QString& format)
1319 QList<Generator *>::ConstIterator g = generators.constBegin();
1320 while (g != generators.constEnd()) {
1321 if ((*g)->format() == format)
1329 This function can be called if getLink() returns an empty
1330 string. It tests the \a atom string to see if it is a link
1331 of the form <element> :: <name>, where <element> is a QML
1332 element or component without a module qualifier. If so, it
1333 constructs a link to the <name> clause on the disambiguation
1334 page for <element> and returns that link string. It also
1335 adds the <name> as a target in the NameCollisionNode for
1336 <element>. These clauses are then constructed when the
1337 disambiguation page is actually generated.
1339 QString Generator::getCollisionLink(const Atom* atom)
1342 if (!atom->string().contains("::"))
1344 QStringList path = atom->string().split("::");
1345 NameCollisionNode* ncn = qdb_->findCollisionNode(path[0]);
1348 if (atom->next() && atom->next()->next()) {
1349 if (atom->next()->type() == Atom::FormattingLeft &&
1350 atom->next()->next()->type() == Atom::String)
1351 label = atom->next()->next()->string();
1353 ncn->addLinkTarget(path[1],label);
1354 link = fileName(ncn);
1355 link += QLatin1Char('#');
1356 link += Doc::canonicalTitle(path[1]);
1363 Looks up the tag \a t in the map of metadata values for the
1364 current topic in \a inner. If a value for the tag is found,
1365 the value is returned.
1367 \note If \a t is found in the metadata map, it is erased.
1368 i.e. Once you call this function for a particular \a t,
1371 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1374 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1375 QStringMultiMap::iterator i = metaTagMap.find(t);
1376 if (i != metaTagMap.end()) {
1378 metaTagMap.erase(i);
1384 Looks up the tag \a t in the map of metadata values for the
1385 current topic in \a inner. If values for the tag are found,
1386 they are returned in a string list.
1388 \note If \a t is found in the metadata map, all the pairs
1389 having the key \a t are erased. i.e. Once you call this
1390 function for a particular \a t, you consume \a t.
1392 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1395 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1396 s = metaTagMap.values(t);
1398 metaTagMap.remove(t);
1403 Returns a relative path name for an image.
1405 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1407 QString userFriendlyFilePath;
1408 QString filePath = Config::findFile(relative->doc().location(),
1412 imgFileExts[format()],
1413 userFriendlyFilePath);
1415 if (filePath.isEmpty())
1418 QString path = Config::copyFile(relative->doc().location(),
1420 userFriendlyFilePath,
1421 outputDir() + QLatin1String("/images"));
1422 QString images = "images";
1424 images.append(QLatin1Char('/'));
1425 return images + path;
1428 QString Generator::indent(int level, const QString& markedCode)
1437 while (i < (int) markedCode.length()) {
1438 if (markedCode.at(i) == QLatin1Char('\n')) {
1443 for (int j = 0; j < level; j++)
1444 t += QLatin1Char(' ');
1448 t += markedCode.at(i++);
1453 void Generator::initialize(const Config &config)
1455 outputFormats = config.getOutputFormats();
1456 if (!outputFormats.isEmpty()) {
1457 outDir_ = config.getOutputDir();
1459 if (outDir_.isEmpty())
1460 config.lastLocation().fatal(tr("No output directory specified in "
1461 "configuration file or on the command line"));
1464 if (dirInfo.exists(outDir_)) {
1465 if (!Config::removeDirContents(outDir_))
1466 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1469 if (!dirInfo.mkpath(outDir_))
1470 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1473 if (!dirInfo.mkdir(outDir_ + "/images"))
1474 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/images"));
1475 if (!dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1476 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/images/used-in-examples"));
1477 if (!dirInfo.mkdir(outDir_ + "/scripts"))
1478 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/scripts"));
1479 if (!dirInfo.mkdir(outDir_ + "/style"))
1480 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/style"));
1483 imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1484 imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1485 scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1486 scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1487 styleFiles = config.getCleanPathList(CONFIG_STYLES);
1488 styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1489 exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1490 exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS);
1492 QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1493 QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1494 QSet<QString>::ConstIterator f = formats.constBegin();
1495 while (f != formats.constEnd()) {
1496 imgFileExts[*f] = config.getStringList(imagesDotFileExtensions + Config::dot + *f);
1500 QList<Generator *>::ConstIterator g = generators.constBegin();
1501 while (g != generators.constEnd()) {
1502 if (outputFormats.contains((*g)->format())) {
1503 currentGenerator_ = (*g);
1504 (*g)->initializeGenerator(config);
1505 QStringList extraImages = config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
1506 QStringList::ConstIterator e = extraImages.constBegin();
1507 while (e != extraImages.constEnd()) {
1508 QString userFriendlyFilePath;
1509 QString filePath = Config::findFile(config.lastLocation(),
1513 imgFileExts[(*g)->format()],
1514 userFriendlyFilePath);
1515 if (!filePath.isEmpty())
1516 Config::copyFile(config.lastLocation(),
1518 userFriendlyFilePath,
1524 // Documentation template handling
1525 QString templateDir = config.getString((*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
1527 QStringList searchDirs;
1528 if (!templateDir.isEmpty()) {
1529 searchDirs.append(templateDir);
1531 if (!Config::installDir.isEmpty()) {
1532 searchDirs.append(Config::installDir);
1535 if (!searchDirs.isEmpty()) {
1537 QStringList scripts = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1538 e = scripts.constBegin();
1539 while (e != scripts.constEnd()) {
1540 QString userFriendlyFilePath;
1541 QString filePath = Config::findFile(config.lastLocation(),
1546 userFriendlyFilePath);
1547 if (!filePath.isEmpty())
1548 Config::copyFile(config.lastLocation(),
1550 userFriendlyFilePath,
1556 QStringList styles = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1557 e = styles.constBegin();
1558 while (e != styles.constEnd()) {
1559 QString userFriendlyFilePath;
1560 QString filePath = Config::findFile(config.lastLocation(),
1565 userFriendlyFilePath);
1566 if (!filePath.isEmpty())
1567 Config::copyFile(config.lastLocation(),
1569 userFriendlyFilePath,
1579 QRegExp secondParamAndAbove("[\2-\7]");
1580 QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1581 QSet<QString>::ConstIterator n = formattingNames.constBegin();
1582 while (n != formattingNames.constEnd()) {
1583 QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1584 QSet<QString> formats = config.subVars(formattingDotName);
1585 QSet<QString>::ConstIterator f = formats.constBegin();
1586 while (f != formats.constEnd()) {
1587 QString def = config.getString(formattingDotName + Config::dot + *f);
1588 if (!def.isEmpty()) {
1589 int numParams = Config::numParams(def);
1590 int numOccs = def.count("\1");
1591 if (numParams != 1) {
1592 config.lastLocation().warning(tr("Formatting '%1' must "
1594 "parameter (found %2)")
1595 .arg(*n).arg(numParams));
1597 else if (numOccs > 1) {
1598 config.lastLocation().fatal(tr("Formatting '%1' must "
1599 "contain exactly one "
1600 "occurrence of '\\1' "
1602 .arg(*n).arg(numOccs));
1605 int paramPos = def.indexOf("\1");
1606 fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1607 fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1615 project = config.getString(CONFIG_PROJECT);
1617 QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1618 if (!prefixes.isEmpty()) {
1619 foreach (const QString &prefix, prefixes)
1620 outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1623 outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1627 Appends each directory path in \a moreImageDirs to the
1628 list of image directories.
1630 void Generator::augmentImageDirs(QSet<QString>& moreImageDirs)
1632 if (moreImageDirs.isEmpty())
1634 QSet<QString>::const_iterator i = moreImageDirs.begin();
1635 while (i != moreImageDirs.end()) {
1636 imageDirs.append(*i);
1641 void Generator::initializeGenerator(const Config & /* config */)
1645 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1647 return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1651 Used for writing to the current output stream. Returns a
1652 reference to the current output stream, which is then used
1653 with the \c {<<} operator for writing.
1655 QTextStream &Generator::out()
1657 return *outStreamStack.top();
1660 QString Generator::outFileName()
1662 return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1665 QString Generator::outputPrefix(const QString &nodeType)
1667 return outputPrefixes[nodeType];
1670 bool Generator::parseArg(const QString& src,
1674 QStringRef* contents,
1678 #define SKIP_CHAR(c) \
1680 qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1681 if (i >= n || src[i] != c) { \
1683 qDebug() << " char '" << c << "' not found"; \
1689 #define SKIP_SPACE \
1690 while (i < n && src[i] == ' ') \
1696 // assume "<@" has been parsed outside
1700 if (tag != QStringRef(&src, i, tag.length())) {
1702 qDebug() << "tag " << tag << " not found at " << i;
1707 qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1712 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1715 // read parameter name
1717 while (i < n && src[i].isLetter())
1719 if (src[i] == '=') {
1721 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1724 // skip parameter name
1726 while (i < n && src[i] != '"')
1728 *par1 = QStringRef(&src, j, i - j);
1733 qDebug() << "no optional parameter found";
1739 // find contents up to closing "</@tag>
1742 if (i + 4 + tag.length() > n)
1746 if (src[i + 1] != '/')
1748 if (src[i + 2] != '@')
1750 if (tag != QStringRef(&src, i + 3, tag.length()))
1752 if (src[i + 3 + tag.length()] != '>')
1757 *contents = QStringRef(&src, j, i - j);
1759 i += tag.length() + 4;
1763 qDebug() << " tag " << tag << " found: pos now: " << i;
1768 QString Generator::plainCode(const QString& markedCode)
1770 QString t = markedCode;
1771 t.replace(tag, QString());
1772 t.replace(quot, QLatin1String("\""));
1773 t.replace(gt, QLatin1String(">"));
1774 t.replace(lt, QLatin1String("<"));
1775 t.replace(amp, QLatin1String("&"));
1779 void Generator::setImageFileExtensions(const QStringList& extensions)
1781 imgFileExts[format()] = extensions;
1784 void Generator::singularPlural(Text& text, const NodeList& nodes)
1786 if (nodes.count() == 1)
1792 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1795 atom = atom->next();
1796 while (atom != 0 && atom->type() != type) {
1798 atom = atom->next();
1804 Resets the variables used during text output.
1806 void Generator::initializeTextOutput()
1809 inContents_ = false;
1810 inSectionHeading_ = false;
1811 inTableHeader_ = false;
1813 threeColumnEnumValueTable_ = true;
1815 sectionNumber_.clear();
1818 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1820 if (node->type() == Node::Function) {
1821 const FunctionNode *func = static_cast<const FunctionNode *>(node);
1822 if (func->overloadNumber() == 1) {
1823 QString alternateName;
1824 const FunctionNode *alternateFunc = 0;
1826 if (func->name().startsWith("set") && func->name().size() >= 4) {
1827 alternateName = func->name()[3].toLower();
1828 alternateName += func->name().mid(4);
1829 alternateFunc = func->parent()->findFunctionNode(alternateName);
1831 if (!alternateFunc) {
1832 alternateName = "is" + func->name().mid(3);
1833 alternateFunc = func->parent()->findFunctionNode(alternateName);
1834 if (!alternateFunc) {
1835 alternateName = "has" + func->name().mid(3);
1836 alternateFunc = func->parent()->findFunctionNode(alternateName);
1840 else if (!func->name().isEmpty()) {
1841 alternateName = "set";
1842 alternateName += func->name()[0].toUpper();
1843 alternateName += func->name().mid(1);
1844 alternateFunc = func->parent()->findFunctionNode(alternateName);
1847 if (alternateFunc && alternateFunc->access() != Node::Private) {
1849 for (i = 0; i < alsoList.size(); ++i) {
1850 if (alsoList.at(i).toString().contains(alternateName))
1854 if (i == alsoList.size()) {
1855 alternateName += "()";
1858 also << Atom(Atom::Link, alternateName)
1859 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1861 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1862 alsoList.prepend(also);
1869 void Generator::terminate()
1871 QList<Generator *>::ConstIterator g = generators.constBegin();
1872 while (g != generators.constEnd()) {
1873 if (outputFormats.contains((*g)->format()))
1874 (*g)->terminateGenerator();
1878 fmtLeftMaps.clear();
1879 fmtRightMaps.clear();
1880 imgFileExts.clear();
1884 QmlClassNode::terminate();
1885 ExampleNode::terminate();
1888 void Generator::terminateGenerator()
1893 Trims trailing whitespace off the \a string and returns
1896 QString Generator::trimmedTrailing(const QString& string)
1898 QString trimmed = string;
1899 while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1900 trimmed.truncate(trimmed.length() - 1);
1904 QString Generator::typeString(const Node *node)
1906 switch (node->type()) {
1907 case Node::Namespace:
1911 case Node::Document:
1913 switch (node->subType()) {
1914 case Node::QmlClass:
1916 case Node::QmlPropertyGroup:
1917 return "property group";
1918 case Node::QmlBasicType:
1921 return "documentation";
1928 case Node::Function:
1930 case Node::Property:
1933 return "documentation";
1937 void Generator::unknownAtom(const Atom *atom)
1939 Location::internalError(tr("unknown atom type '%1' in %2 generator")
1940 .arg(atom->typeString()).arg(format()));