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 QString Generator::baseDir_;
62 Generator* Generator::currentGenerator_;
63 QStringList Generator::exampleDirs;
64 QStringList Generator::exampleImgExts;
65 QMap<QString, QMap<QString, QString> > Generator::fmtLeftMaps;
66 QMap<QString, QMap<QString, QString> > Generator::fmtRightMaps;
67 QList<Generator *> Generator::generators;
68 QStringList Generator::imageDirs;
69 QStringList Generator::imageFiles;
70 QMap<QString, QStringList> Generator::imgFileExts;
71 QString Generator::outDir_;
72 QSet<QString> Generator::outputFormats;
73 QHash<QString, QString> Generator::outputPrefixes;
74 QString Generator::project;
75 QStringList Generator::scriptDirs;
76 QStringList Generator::scriptFiles;
77 QString Generator::sinceTitles[] =
81 " New Member Functions",
82 " New Functions in Namespaces",
83 " New Global Functions",
90 " New QML Properties",
92 " New QML Signal Handlers",
96 QStringList Generator::styleDirs;
97 QStringList Generator::styleFiles;
100 Constructs the generator base class. Prepends the newly
101 constructed generator to the list of output generators.
102 Sets a pointer to the QDoc database singleton, which is
103 available to the generator subclasses.
105 Generator::Generator()
113 inSectionHeading_(false),
114 inTableHeader_(false),
115 threeColumnEnumValueTable_(true),
118 qdb_ = QDocDatabase::qdocDB();
119 generators.prepend(this);
123 Destroys the generator after removing it from the list of
126 Generator::~Generator()
128 generators.removeAll(this);
131 void Generator::appendFullName(Text& text,
132 const Node *apparentNode,
133 const Node *relative,
134 const Node *actualNode)
137 actualNode = apparentNode;
138 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
139 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
140 << Atom(Atom::String, apparentNode->plainFullName(relative))
141 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
144 void Generator::appendFullName(Text& text,
145 const Node *apparentNode,
146 const QString& fullName,
147 const Node *actualNode)
150 actualNode = apparentNode;
151 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
152 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
153 << Atom(Atom::String, fullName)
154 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
157 void Generator::appendFullNames(Text& text, const NodeList& nodes, const Node* relative)
159 NodeList::ConstIterator n = nodes.constBegin();
161 while (n != nodes.constEnd()) {
162 appendFullName(text,*n,relative);
163 text << comma(index++,nodes.count());
168 void Generator::appendSortedNames(Text& text, const ClassNode *classe, const QList<RelatedClass> &classes)
170 QList<RelatedClass>::ConstIterator r;
171 QMap<QString,Text> classMap;
174 r = classes.constBegin();
175 while (r != classes.constEnd()) {
176 if ((*r).node->access() == Node::Public &&
177 (*r).node->status() != Node::Internal
178 && !(*r).node->doc().isEmpty()) {
180 appendFullName(className, (*r).node, classe);
181 classMap[className.toString().toLower()] = className;
186 QStringList classNames = classMap.keys();
189 foreach (const QString &className, classNames) {
190 text << classMap[className];
191 text << separator(index++, classNames.count());
195 void Generator::appendSortedQmlNames(Text& text, const Node* base, const NodeList& subs)
197 QMap<QString,Text> classMap;
200 for (int i = 0; i < subs.size(); ++i) {
202 if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
203 (base->qmlModuleIdentifier() == subs[i]->qmlModuleIdentifier())) {
204 appendFullName(t, subs[i], base);
205 classMap[t.toString().toLower()] = t;
209 QStringList names = classMap.keys();
212 foreach (const QString &name, names) {
213 text << classMap[name];
214 text << separator(index++, names.count());
218 QMultiMap<QString,QString> outFileNames;
223 void Generator::writeOutFileNames()
225 QFile* files = new QFile("/Users/msmith/depot/qt5/qtdoc/outputlist.txt");
226 files->open(QFile::WriteOnly);
227 QTextStream* filesout = new QTextStream(files);
228 QMultiMap<QString,QString>::ConstIterator i = outFileNames.begin();
229 while (i != outFileNames.end()) {
230 (*filesout) << i.key() << "\n";
238 Creates the file named \a fileName in the output directory.
239 Attaches a QTextStream to the created file, which is written
240 to all over the place using out().
242 void Generator::beginSubPage(const InnerNode* node, const QString& fileName)
244 QString path = outputDir() + QLatin1Char('/');
245 if (!node->outputSubdirectory().isEmpty())
246 path += node->outputSubdirectory() + QLatin1Char('/');
248 outFileNames.insert(fileName,fileName);
249 QFile* outFile = new QFile(path);
250 if (!outFile->open(QFile::WriteOnly))
251 node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
252 QTextStream* out = new QTextStream(outFile);
255 out->setCodec(outputCodec);
256 outStreamStack.push(out);
257 const_cast<InnerNode*>(node)->setOutputFileName(fileName);
261 Flush the text stream associated with the subpage, and
262 then pop it off the text stream stack and delete it.
263 This terminates output of the subpage.
265 void Generator::endSubPage()
267 outStreamStack.top()->flush();
268 delete outStreamStack.top()->device();
269 delete outStreamStack.pop();
272 QString Generator::fileBase(const Node *node) const
275 node = node->relates();
276 else if (!node->isInnerNode())
277 node = node->parent();
278 if (node->subType() == Node::QmlPropertyGroup) {
279 node = node->parent();
282 QString base = node->doc().baseName();
286 const Node *p = node;
289 const Node *pp = p->parent();
290 base.prepend(p->name());
291 if (!p->qmlModuleIdentifier().isEmpty())
292 base.prepend(p->qmlModuleIdentifier()+QChar('-'));
294 To avoid file name conflicts in the html directory,
295 we prepend a prefix (by default, "qml-") to the file name of QML
298 if ((p->subType() == Node::QmlClass) ||
299 (p->subType() == Node::QmlBasicType)) {
300 base.prepend(outputPrefix(QLatin1String("QML")));
302 if (!pp || pp->name().isEmpty() || pp->type() == Node::Document)
304 base.prepend(QLatin1Char('-'));
307 if (node->type() == Node::Document) {
308 if (node->subType() == Node::Collision) {
309 const NameCollisionNode* ncn = static_cast<const NameCollisionNode*>(node);
310 if (ncn->currentChild())
311 return fileBase(ncn->currentChild());
312 base.prepend("collision-");
314 //Was QDOC2_COMPAT, required for index.html
315 if (base.endsWith(".html"))
316 base.truncate(base.length() - 5);
318 if (node->subType() == Node::QmlModule) {
319 base.prepend("qmlmodule-");
321 if (node->subType() == Node::Module) {
322 base.append("-module");
326 // the code below is effectively equivalent to:
327 // base.replace(QRegExp("[^A-Za-z0-9]+"), " ");
328 // base = base.trimmed();
329 // base.replace(QLatin1Char(' '), QLatin1Char('-'));
330 // base = base.toLower();
331 // as this function accounted for ~8% of total running time
332 // we optimize a bit...
335 // +5 prevents realloc in fileName() below
336 res.reserve(base.size() + 5);
338 for (int i = 0; i != base.size(); ++i) {
339 QChar c = base.at(i);
340 uint u = c.unicode();
341 if (u >= 'A' && u <= 'Z')
343 if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
344 res += QLatin1Char(u);
348 res += QLatin1Char('-');
352 while (res.endsWith(QLatin1Char('-')))
358 If the \a node has a URL, return the URL as the file name.
359 Otherwise, construct the file name from the fileBase() and
360 the fileExtension(), and return the constructed name.
362 QString Generator::fileName(const Node* node) const
364 if (!node->url().isEmpty())
367 QString name = fileBase(node);
368 name += QLatin1Char('.');
369 name += fileExtension();
373 QMap<QString, QString>& Generator::formattingLeftMap()
375 return fmtLeftMaps[format()];
378 QMap<QString, QString>& Generator::formattingRightMap()
380 return fmtRightMaps[format()];
384 Returns the full document location.
386 QString Generator::fullDocumentLocation(const Node *node, bool subdir)
390 if (!node->url().isEmpty())
398 If the output is being sent to subdirectories of the
399 output directory, and if the subdir parameter is set,
400 prepend the subdirectory name + '/' to the result.
403 fdl = node->outputSubdirectory();
405 fdl.append(QLatin1Char('/'));
407 if (node->type() == Node::Namespace) {
409 // The root namespace has no name - check for this before creating
410 // an attribute containing the location of any documentation.
412 if (!fileBase(node).isEmpty())
413 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
417 else if (node->type() == Node::Document) {
418 if ((node->subType() == Node::QmlClass) ||
419 (node->subType() == Node::QmlBasicType)) {
420 QString fb = fileBase(node);
421 if (fb.startsWith(Generator::outputPrefix(QLatin1String("QML"))))
422 return fb + QLatin1Char('.') + currentGenerator()->fileExtension();
425 if (!node->qmlModuleName().isEmpty()) {
426 mq = node->qmlModuleIdentifier().replace(QChar('.'),QChar('-'));
427 mq = mq.toLower() + QLatin1Char('-');
429 return fdl+ Generator::outputPrefix(QLatin1String("QML")) + mq +
430 fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
434 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
437 else if (fileBase(node).isEmpty())
440 Node *parentNode = 0;
442 if ((parentNode = node->relates())) {
443 parentName = fullDocumentLocation(node->relates());
445 else if ((parentNode = node->parent())) {
446 if (parentNode->subType() == Node::QmlPropertyGroup) {
447 parentNode = parentNode->parent();
448 parentName = fullDocumentLocation(parentNode);
451 parentName = fullDocumentLocation(node->parent());
455 switch (node->type()) {
457 case Node::Namespace:
458 if (parentNode && !parentNode->name().isEmpty()) {
459 parentName.remove(QLatin1Char('.') + currentGenerator()->fileExtension());
460 parentName += QLatin1Char('-')
461 + fileBase(node).toLower() + QLatin1Char('.') + currentGenerator()->fileExtension();
463 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
468 const FunctionNode *functionNode =
469 static_cast<const FunctionNode *>(node);
471 if (functionNode->metaness() == FunctionNode::Dtor)
472 anchorRef = "#dtor." + functionNode->name().mid(1);
474 else if (functionNode->associatedProperty())
475 return fullDocumentLocation(functionNode->associatedProperty());
477 else if (functionNode->overloadNumber() > 1)
478 anchorRef = QLatin1Char('#') + functionNode->name()
479 + QLatin1Char('-') + QString::number(functionNode->overloadNumber());
481 anchorRef = QLatin1Char('#') + functionNode->name();
485 Use node->name() instead of fileBase(node) as
486 the latter returns the name in lower-case. For
487 HTML anchors, we need to preserve the case.
490 anchorRef = QLatin1Char('#') + node->name() + "-enum";
493 anchorRef = QLatin1Char('#') + node->name() + "-typedef";
496 anchorRef = QLatin1Char('#') + node->name() + "-prop";
498 case Node::QmlProperty:
499 anchorRef = QLatin1Char('#') + node->name() + "-prop";
501 case Node::QmlSignal:
502 anchorRef = QLatin1Char('#') + node->name() + "-signal";
504 case Node::QmlSignalHandler:
505 anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
507 case Node::QmlMethod:
508 anchorRef = QLatin1Char('#') + node->name() + "-method";
511 anchorRef = QLatin1Char('#') + node->name() + "-var";
515 parentName = fileBase(node);
516 parentName.replace(QLatin1Char('/'), QLatin1Char('-')).replace(QLatin1Char('.'), QLatin1Char('-'));
517 parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
524 // Various objects can be compat (deprecated) or obsolete.
525 if (node->type() != Node::Class && node->type() != Node::Namespace) {
526 switch (node->status()) {
528 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
529 "-compat." + currentGenerator()->fileExtension());
532 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
533 "-obsolete." + currentGenerator()->fileExtension());
540 return fdl + parentName.toLower() + anchorRef;
543 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
545 QList<Text> alsoList = node->doc().alsoList();
546 supplementAlsoList(node, alsoList);
548 if (!alsoList.isEmpty()) {
550 text << Atom::ParaLeft
551 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
553 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
555 for (int i = 0; i < alsoList.size(); ++i)
556 text << alsoList.at(i) << separator(i, alsoList.size());
558 text << Atom::ParaRight;
559 generateText(text, node, marker);
563 int Generator::generateAtom(const Atom * /* atom */,
564 const Node * /* relative */,
565 CodeMarker * /* marker */)
570 const Atom *Generator::generateAtomList(const Atom *atom,
571 const Node *relative,
577 if (atom->type() == Atom::FormatIf) {
578 int numAtoms0 = numAtoms;
579 bool rightFormat = canHandleFormat(atom->string());
580 atom = generateAtomList(atom->next(),
583 generate && rightFormat,
588 if (atom->type() == Atom::FormatElse) {
590 atom = generateAtomList(atom->next(),
593 generate && !rightFormat,
599 if (atom->type() == Atom::FormatEndif) {
600 if (generate && numAtoms0 == numAtoms) {
601 relative->location().warning(tr("Output format %1 not handled %2")
602 .arg(format()).arg(outFileName()));
603 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
604 generateAtomList(&unhandledFormatAtom,
613 else if (atom->type() == Atom::FormatElse ||
614 atom->type() == Atom::FormatEndif) {
620 n += generateAtom(atom, relative, marker);
630 void Generator::generateBody(const Node *node, CodeMarker *marker)
634 if (node->type() == Node::Document) {
635 const DocNode *dn = static_cast<const DocNode *>(node);
636 if ((dn->subType() == Node::File) || (dn->subType() == Node::Image)) {
640 if (node->doc().isEmpty()) {
641 if (!quiet && !node->isReimp()) { // ### might be unnecessary
642 node->location().warning(tr("No documentation for '%1'").arg(node->plainFullName()));
646 if (node->type() == Node::Function) {
647 const FunctionNode *func = static_cast<const FunctionNode *>(node);
648 if (func->reimplementedFrom() != 0)
649 generateReimplementedFrom(func, marker);
652 if (!generateText(node->doc().body(), node, marker)) {
657 if (node->type() == Node::Enum) {
658 const EnumNode *enume = (const EnumNode *) node;
660 QSet<QString> definedItems;
661 QList<EnumItem>::ConstIterator it = enume->items().constBegin();
662 while (it != enume->items().constEnd()) {
663 definedItems.insert((*it).name());
667 QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
668 QSet<QString> allItems = definedItems + documentedItems;
669 if (allItems.count() > definedItems.count() ||
670 allItems.count() > documentedItems.count()) {
671 QSet<QString>::ConstIterator a = allItems.constBegin();
672 while (a != allItems.constEnd()) {
673 if (!definedItems.contains(*a)) {
675 QString best = nearestName(*a, definedItems);
676 if (!best.isEmpty() && !documentedItems.contains(best))
677 details = tr("Maybe you meant '%1'?").arg(best);
679 node->doc().location().warning(tr("No such enum item '%1' in %2").arg(*a).arg(node->plainFullName()), details);
681 qDebug() << "VOID:" << node->name() << definedItems;
683 else if (!documentedItems.contains(*a)) {
684 node->doc().location().warning(tr("Undocumented enum item '%1' in %2").arg(*a).arg(node->plainFullName()));
690 else if (node->type() == Node::Function) {
691 const FunctionNode *func = static_cast<const FunctionNode *>(node);
692 QSet<QString> definedParams;
693 QList<Parameter>::ConstIterator p = func->parameters().constBegin();
694 while (p != func->parameters().constEnd()) {
695 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
696 && func->name() != QLatin1String("operator++")
697 && func->name() != QLatin1String("operator--")) {
698 node->doc().location().warning(tr("Missing parameter name"));
701 definedParams.insert((*p).name());
706 QSet<QString> documentedParams = func->doc().parameterNames();
707 QSet<QString> allParams = definedParams + documentedParams;
708 if (allParams.count() > definedParams.count()
709 || allParams.count() > documentedParams.count()) {
710 QSet<QString>::ConstIterator a = allParams.constBegin();
711 while (a != allParams.constEnd()) {
712 if (!definedParams.contains(*a)) {
714 QString best = nearestName(*a, definedParams);
716 details = tr("Maybe you meant '%1'?").arg(best);
718 node->doc().location().warning(
719 tr("No such parameter '%1' in %2").arg(*a).arg(node->plainFullName()),
722 else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
723 bool needWarning = (func->status() > Node::Obsolete);
724 if (func->overloadNumber() > 1) {
725 FunctionNode *primaryFunc =
726 func->parent()->findFunctionNode(func->name());
728 foreach (const Parameter ¶m,
729 primaryFunc->parameters()) {
730 if (param.name() == *a) {
737 if (needWarning && !func->isReimp())
738 node->doc().location().warning(
739 tr("Undocumented parameter '%1' in %2")
740 .arg(*a).arg(node->plainFullName()));
746 Something like this return value check should
747 be implemented at some point.
749 if (func->status() > Node::Obsolete && func->returnType() == "bool"
750 && func->reimplementedFrom() == 0 && !func->isOverload()) {
751 QString body = func->doc().body().toString();
752 if (!body.contains("return", Qt::CaseInsensitive))
753 node->doc().location().warning(tr("Undocumented return value"));
758 if (node->type() == Node::Document) {
759 const DocNode *dn = static_cast<const DocNode *>(node);
760 if (dn->subType() == Node::Example) {
761 generateExampleFiles(dn, marker);
763 else if (dn->subType() == Node::File) {
766 Doc::quoteFromFile(dn->doc().location(), quoter, dn->name());
767 QString code = quoter.quoteTo(dn->location(), QString(), QString());
768 CodeMarker *codeMarker = CodeMarker::markerForFileName(dn->name());
769 text << Atom(codeMarker->atomType(), code);
770 generateText(text, dn, codeMarker);
775 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
779 void Generator::generateExampleFiles(const DocNode *dn, CodeMarker *marker)
781 if (dn->childNodes().isEmpty())
783 generateFileList(dn, marker, Node::File, QString("Files:"));
784 generateFileList(dn, marker, Node::Image, QString("Images:"));
787 void Generator::generateDocNode(DocNode* /* dn */, CodeMarker* /* marker */)
792 This function is called when the documentation for an
793 example is being formatted. It outputs the list of source
794 files comprising the example, and the list of images used
795 by the example. The images are copied into a subtree of
796 \c{...doc/html/images/used-in-examples/...}
798 void Generator::generateFileList(const DocNode* dn,
800 Node::SubType subtype,
805 OpenedList openedList(OpenedList::Bullet);
807 text << Atom::ParaLeft << tag << Atom::ParaRight
808 << Atom(Atom::ListLeft, openedList.styleString());
810 foreach (const Node* child, dn->childNodes()) {
811 if (child->subType() == subtype) {
813 QString file = child->name();
814 if (subtype == Node::Image) {
815 if (!file.isEmpty()) {
817 QString userFriendlyFilePath;
818 QString srcPath = Config::findFile(dn->location(),
823 userFriendlyFilePath);
824 userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
826 QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
827 if (!dirInfo.mkpath(imgOutDir))
828 dn->location().fatal(tr("Cannot create output directory '%1'")
831 QString imgOutName = Config::copyFile(dn->location(),
840 text << Atom(Atom::ListItemNumber, openedList.numberString())
841 << Atom(Atom::ListItemLeft, openedList.styleString())
843 << Atom(Atom::Link, file)
844 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
846 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
848 << Atom(Atom::ListItemRight, openedList.styleString());
851 text << Atom(Atom::ListRight, openedList.styleString());
853 generateText(text, dn, marker);
856 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
858 if (!classe->derivedClasses().isEmpty()) {
860 text << Atom::ParaLeft
861 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
863 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
865 appendSortedNames(text, classe, classe->derivedClasses());
866 text << Atom::ParaRight;
867 generateText(text, classe, marker);
871 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
873 QList<RelatedClass>::ConstIterator r;
876 if (!classe->baseClasses().isEmpty()) {
878 text << Atom::ParaLeft
879 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
881 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
883 r = classe->baseClasses().constBegin();
885 while (r != classe->baseClasses().constEnd()) {
886 text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
887 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
888 << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
889 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
891 if ((*r).access == Node::Protected) {
892 text << " (protected)";
894 else if ((*r).access == Node::Private) {
895 text << " (private)";
897 text << separator(index++, classe->baseClasses().count());
900 text << Atom::ParaRight;
901 generateText(text, classe, marker);
906 Recursive writing of HTML files from the root \a node.
908 \note NameCollisionNodes are skipped here and processed
909 later. See HtmlGenerator::generateCollisionPages() for
912 void Generator::generateInnerNode(InnerNode* node)
914 if (!node->url().isNull())
917 if (node->type() == Node::Document) {
918 DocNode* docNode = static_cast<DocNode*>(node);
919 if (docNode->subType() == Node::ExternalPage)
921 if (docNode->subType() == Node::Image)
923 if (docNode->subType() == Node::QmlPropertyGroup)
925 if (docNode->subType() == Node::Page) {
926 if (node->count() > 0)
927 qDebug("PAGE %s HAS CHILDREN", qPrintable(docNode->title()));
932 Obtain a code marker for the source file.
934 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
936 if (node->parent() != 0) {
938 Skip name collision nodes here and process them
939 later in generateCollisionPages(). Each one is
940 appended to a list for later.
942 if ((node->type() == Node::Document) && (node->subType() == Node::Collision)) {
943 NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
944 collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
947 beginSubPage(node, fileName(node));
948 if (node->type() == Node::Namespace || node->type() == Node::Class) {
949 generateClassLikeNode(node, marker);
951 else if (node->type() == Node::Document) {
952 generateDocNode(static_cast<DocNode*>(node), marker);
958 NodeList::ConstIterator c = node->childNodes().constBegin();
959 while (c != node->childNodes().constEnd()) {
960 if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
961 generateInnerNode((InnerNode*)*c);
968 Generate a list of maintainers in the output
970 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
972 QStringList sl = getMetadataElements(node,"maintainer");
976 text << Atom::ParaLeft
977 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
979 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
981 for (int i = 0; i < sl.size(); ++i)
982 text << sl.at(i) << separator(i, sl.size());
984 text << Atom::ParaRight;
985 generateText(text, node, marker);
990 Output the "Inherit by" list for the QML element,
991 if it is inherited by any other elements.
993 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
998 QmlClassNode::subclasses(qcn->name(),subs);
999 if (!subs.isEmpty()) {
1001 text << Atom::ParaLeft << "Inherited by ";
1002 appendSortedQmlNames(text,qcn,subs);
1003 text << Atom::ParaRight;
1004 generateText(text, qcn, marker);
1011 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
1017 Extract sections of markup text surrounded by \e qmltext
1018 and \e endqmltext and output them.
1020 bool Generator::generateQmlText(const Text& text,
1021 const Node *relative,
1023 const QString& /* qmlName */ )
1025 const Atom* atom = text.firstAtom();
1026 bool result = false;
1029 initializeTextOutput();
1031 if (atom->type() != Atom::QmlText)
1032 atom = atom->next();
1034 atom = atom->next();
1035 while (atom && (atom->type() != Atom::EndQmlText)) {
1036 int n = 1 + generateAtom(atom, relative, marker);
1038 atom = atom->next();
1047 void Generator::generateReimplementedFrom(const FunctionNode *func,
1050 if (func->reimplementedFrom() != 0) {
1051 const FunctionNode *from = func->reimplementedFrom();
1052 if (from->access() != Node::Private &&
1053 from->parent()->access() != Node::Private) {
1055 text << Atom::ParaLeft << "Reimplemented from ";
1056 QString fullName = from->parent()->name() + "::" + from->name() + "()";
1057 appendFullName(text, from->parent(), fullName, from);
1058 text << "." << Atom::ParaRight;
1059 generateText(text, func, marker);
1064 void Generator::generateSince(const Node *node, CodeMarker *marker)
1066 if (!node->since().isEmpty()) {
1068 text << Atom::ParaLeft
1070 << typeString(node);
1071 if (node->type() == Node::Enum)
1072 text << " was introduced or modified in ";
1074 text << " was introduced in ";
1076 QStringList since = node->since().split(QLatin1Char(' '));
1077 if (since.count() == 1) {
1078 // Handle legacy use of \since <version>.
1079 if (project.isEmpty())
1083 text << " " << since[0];
1085 // Reconstruct the <project> <version> string.
1086 text << " " << since.join(' ');
1089 text << "." << Atom::ParaRight;
1090 generateText(text, node, marker);
1094 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1098 switch (node->status()) {
1099 case Node::Commendable:
1102 case Node::Preliminary:
1103 text << Atom::ParaLeft
1104 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1107 << " is under development and is subject to change."
1108 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1111 case Node::Deprecated:
1112 text << Atom::ParaLeft;
1113 if (node->isInnerNode())
1114 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1115 text << "This " << typeString(node) << " is deprecated.";
1116 if (node->isInnerNode())
1117 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1118 text << Atom::ParaRight;
1120 case Node::Obsolete:
1121 text << Atom::ParaLeft;
1122 if (node->isInnerNode())
1123 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1124 text << "This " << typeString(node) << " is obsolete.";
1125 if (node->isInnerNode())
1126 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1127 text << " It is provided to keep old source code working. "
1128 << "We strongly advise against "
1129 << "using it in new code." << Atom::ParaRight;
1132 // reimplemented in HtmlGenerator subclass
1133 if (node->isInnerNode()) {
1134 text << Atom::ParaLeft
1135 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1138 << " is part of the Qt compatibility layer."
1139 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1140 << " It is provided to keep old source code working. "
1141 << "We strongly advise against using it in new code."
1145 case Node::Internal:
1149 generateText(text, node, marker);
1152 bool Generator::generateText(const Text& text,
1153 const Node *relative,
1156 bool result = false;
1157 if (text.firstAtom() != 0) {
1159 initializeTextOutput();
1160 generateAtomList(text.firstAtom(),
1170 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1173 Node::ThreadSafeness threadSafeness = node->threadSafeness();
1176 rlink << Atom(Atom::Link,"reentrant")
1177 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1179 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1182 tlink << Atom(Atom::Link,"thread-safe")
1183 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1185 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1187 switch (threadSafeness) {
1188 case Node::UnspecifiedSafeness:
1190 case Node::NonReentrant:
1191 text << Atom::ParaLeft
1192 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1194 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1202 case Node::Reentrant:
1203 case Node::ThreadSafe:
1204 text << Atom::ParaLeft
1205 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1207 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1210 if (node->isInnerNode()) {
1211 const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1212 text << "All functions in this "
1215 if (threadSafeness == Node::ThreadSafe)
1220 bool exceptions = false;
1222 NodeList threadsafe;
1223 NodeList nonreentrant;
1224 NodeList::ConstIterator c = innerNode->childNodes().constBegin();
1225 while (c != innerNode->childNodes().constEnd()) {
1227 if ((*c)->status() != Node::Obsolete){
1228 switch ((*c)->threadSafeness()) {
1229 case Node::Reentrant:
1230 reentrant.append(*c);
1231 if (threadSafeness == Node::ThreadSafe)
1234 case Node::ThreadSafe:
1235 threadsafe.append(*c);
1236 if (threadSafeness == Node::Reentrant)
1239 case Node::NonReentrant:
1240 nonreentrant.append(*c);
1251 else if (threadSafeness == Node::Reentrant) {
1252 if (nonreentrant.isEmpty()) {
1253 if (!threadsafe.isEmpty()) {
1255 appendFullNames(text,threadsafe,innerNode);
1256 singularPlural(text,threadsafe);
1257 text << " also " << tlink << ".";
1263 text << ", except for ";
1264 appendFullNames(text,nonreentrant,innerNode);
1266 singularPlural(text,nonreentrant);
1267 text << " nonreentrant.";
1268 if (!threadsafe.isEmpty()) {
1270 appendFullNames(text,threadsafe,innerNode);
1271 singularPlural(text,threadsafe);
1272 text << " " << tlink << ".";
1276 else { // thread-safe
1277 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1278 text << ", except for ";
1279 if (!reentrant.isEmpty()) {
1280 appendFullNames(text,reentrant,innerNode);
1282 singularPlural(text,reentrant);
1283 text << " only " << rlink;
1284 if (!nonreentrant.isEmpty())
1287 if (!nonreentrant.isEmpty()) {
1288 appendFullNames(text,nonreentrant,innerNode);
1290 singularPlural(text,nonreentrant);
1291 text << " nonreentrant.";
1298 text << "This " << typeString(node) << " is ";
1299 if (threadSafeness == Node::ThreadSafe)
1305 text << Atom::ParaRight;
1307 generateText(text,node,marker);
1311 Traverses the database recursivly to generate all the documentation.
1313 void Generator::generateTree()
1315 generateInnerNode(qdb_->treeRoot());
1318 Generator *Generator::generatorForFormat(const QString& format)
1320 QList<Generator *>::ConstIterator g = generators.constBegin();
1321 while (g != generators.constEnd()) {
1322 if ((*g)->format() == format)
1330 This function can be called if getLink() returns an empty
1331 string. It tests the \a atom string to see if it is a link
1332 of the form <element> :: <name>, where <element> is a QML
1333 element or component without a module qualifier. If so, it
1334 constructs a link to the <name> clause on the disambiguation
1335 page for <element> and returns that link string. It also
1336 adds the <name> as a target in the NameCollisionNode for
1337 <element>. These clauses are then constructed when the
1338 disambiguation page is actually generated.
1340 QString Generator::getCollisionLink(const Atom* atom)
1343 if (!atom->string().contains("::"))
1345 QStringList path = atom->string().split("::");
1346 NameCollisionNode* ncn = qdb_->findCollisionNode(path[0]);
1349 if (atom->next() && atom->next()->next()) {
1350 if (atom->next()->type() == Atom::FormattingLeft &&
1351 atom->next()->next()->type() == Atom::String)
1352 label = atom->next()->next()->string();
1354 ncn->addLinkTarget(path[1],label);
1355 link = fileName(ncn);
1356 link += QLatin1Char('#');
1357 link += Doc::canonicalTitle(path[1]);
1364 Looks up the tag \a t in the map of metadata values for the
1365 current topic in \a inner. If a value for the tag is found,
1366 the value is returned.
1368 \note If \a t is found in the metadata map, it is erased.
1369 i.e. Once you call this function for a particular \a t,
1372 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1375 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1376 QStringMultiMap::iterator i = metaTagMap.find(t);
1377 if (i != metaTagMap.end()) {
1379 metaTagMap.erase(i);
1385 Looks up the tag \a t in the map of metadata values for the
1386 current topic in \a inner. If values for the tag are found,
1387 they are returned in a string list.
1389 \note If \a t is found in the metadata map, all the pairs
1390 having the key \a t are erased. i.e. Once you call this
1391 function for a particular \a t, you consume \a t.
1393 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1396 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1397 s = metaTagMap.values(t);
1399 metaTagMap.remove(t);
1404 Returns a relative path name for an image.
1406 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1408 QString userFriendlyFilePath;
1409 QString filePath = Config::findFile(
1410 relative->doc().location(), imageFiles, imageDirs, fileBase,
1411 imgFileExts[format()], userFriendlyFilePath);
1413 if (filePath.isEmpty())
1416 QString path = Config::copyFile(relative->doc().location(),
1418 userFriendlyFilePath,
1419 outputDir() + QLatin1String("/images"));
1420 QString images = "images";
1422 images.append(QLatin1Char('/'));
1423 return images + path;
1426 QString Generator::indent(int level, const QString& markedCode)
1435 while (i < (int) markedCode.length()) {
1436 if (markedCode.at(i) == QLatin1Char('\n')) {
1441 for (int j = 0; j < level; j++)
1442 t += QLatin1Char(' ');
1446 t += markedCode.at(i++);
1451 void Generator::initialize(const Config &config)
1453 outputFormats = config.getOutputFormats();
1454 if (!outputFormats.isEmpty()) {
1455 outDir_ = config.getOutputDir();
1456 baseDir_ = config.getString(CONFIG_BASEDIR);
1457 if (!baseDir_.isEmpty())
1458 config.location().warning(tr("\"basedir\" specified in config file. "
1459 "All output will be in module directories "
1460 "of the output directory"));
1461 if (outDir_.isEmpty())
1462 config.lastLocation().fatal(tr("No output directory specified in "
1463 "configuration file or on the command line"));
1466 if (dirInfo.exists(outDir_)) {
1467 if (!Config::removeDirContents(outDir_))
1468 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1471 if (!dirInfo.mkpath(outDir_))
1472 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1475 if (!dirInfo.mkdir(outDir_ + "/images"))
1476 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/images"));
1477 if (!dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1478 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/images/used-in-examples"));
1479 if (!dirInfo.mkdir(outDir_ + "/scripts"))
1480 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/scripts"));
1481 if (!dirInfo.mkdir(outDir_ + "/style"))
1482 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_ + "/style"));
1485 imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1486 imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1487 scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1488 scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1489 styleFiles = config.getCleanPathList(CONFIG_STYLES);
1490 styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1491 exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1492 exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS);
1494 QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1495 QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1496 QSet<QString>::ConstIterator f = formats.constBegin();
1497 while (f != formats.constEnd()) {
1498 imgFileExts[*f] = config.getStringList(imagesDotFileExtensions + Config::dot + *f);
1502 QList<Generator *>::ConstIterator g = generators.constBegin();
1503 while (g != generators.constEnd()) {
1504 if (outputFormats.contains((*g)->format())) {
1505 currentGenerator_ = (*g);
1506 (*g)->initializeGenerator(config);
1507 QStringList extraImages = config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
1508 QStringList::ConstIterator e = extraImages.constBegin();
1509 while (e != extraImages.constEnd()) {
1510 QString userFriendlyFilePath;
1511 QString filePath = Config::findFile(config.lastLocation(),
1515 imgFileExts[(*g)->format()],
1516 userFriendlyFilePath);
1517 if (!filePath.isEmpty())
1518 Config::copyFile(config.lastLocation(),
1520 userFriendlyFilePath,
1526 // Documentation template handling
1527 QString templateDir = config.getString((*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
1529 QStringList searchDirs;
1530 if (!templateDir.isEmpty()) {
1531 searchDirs.append(templateDir);
1533 if (!Config::installDir.isEmpty()) {
1534 searchDirs.append(Config::installDir);
1537 if (!searchDirs.isEmpty()) {
1539 QStringList scripts = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1540 e = scripts.constBegin();
1541 while (e != scripts.constEnd()) {
1542 QString userFriendlyFilePath;
1543 QString filePath = Config::findFile(config.lastLocation(),
1548 userFriendlyFilePath);
1549 if (!filePath.isEmpty())
1550 Config::copyFile(config.lastLocation(),
1552 userFriendlyFilePath,
1558 QStringList styles = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1559 e = styles.constBegin();
1560 while (e != styles.constEnd()) {
1561 QString userFriendlyFilePath;
1562 QString filePath = Config::findFile(config.lastLocation(),
1567 userFriendlyFilePath);
1568 if (!filePath.isEmpty())
1569 Config::copyFile(config.lastLocation(),
1571 userFriendlyFilePath,
1581 QRegExp secondParamAndAbove("[\2-\7]");
1582 QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1583 QSet<QString>::ConstIterator n = formattingNames.constBegin();
1584 while (n != formattingNames.constEnd()) {
1585 QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1586 QSet<QString> formats = config.subVars(formattingDotName);
1587 QSet<QString>::ConstIterator f = formats.constBegin();
1588 while (f != formats.constEnd()) {
1589 QString def = config.getString(formattingDotName + Config::dot + *f);
1590 if (!def.isEmpty()) {
1591 int numParams = Config::numParams(def);
1592 int numOccs = def.count("\1");
1593 if (numParams != 1) {
1594 config.lastLocation().warning(tr("Formatting '%1' must "
1596 "parameter (found %2)")
1597 .arg(*n).arg(numParams));
1599 else if (numOccs > 1) {
1600 config.lastLocation().fatal(tr("Formatting '%1' must "
1601 "contain exactly one "
1602 "occurrence of '\\1' "
1604 .arg(*n).arg(numOccs));
1607 int paramPos = def.indexOf("\1");
1608 fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1609 fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1617 project = config.getString(CONFIG_PROJECT);
1619 QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1620 if (!prefixes.isEmpty()) {
1621 foreach (const QString &prefix, prefixes)
1622 outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1625 outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1628 void Generator::initializeGenerator(const Config & /* config */)
1632 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1634 return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1638 Used for writing to the current output stream. Returns a
1639 reference to the current output stream, which is then used
1640 with the \c {<<} operator for writing.
1642 QTextStream &Generator::out()
1644 return *outStreamStack.top();
1647 QString Generator::outFileName()
1649 return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1652 QString Generator::outputPrefix(const QString &nodeType)
1654 return outputPrefixes[nodeType];
1657 bool Generator::parseArg(const QString& src,
1661 QStringRef* contents,
1665 #define SKIP_CHAR(c) \
1667 qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1668 if (i >= n || src[i] != c) { \
1670 qDebug() << " char '" << c << "' not found"; \
1676 #define SKIP_SPACE \
1677 while (i < n && src[i] == ' ') \
1683 // assume "<@" has been parsed outside
1687 if (tag != QStringRef(&src, i, tag.length())) {
1689 qDebug() << "tag " << tag << " not found at " << i;
1694 qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1699 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1702 // read parameter name
1704 while (i < n && src[i].isLetter())
1706 if (src[i] == '=') {
1708 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1711 // skip parameter name
1713 while (i < n && src[i] != '"')
1715 *par1 = QStringRef(&src, j, i - j);
1720 qDebug() << "no optional parameter found";
1726 // find contents up to closing "</@tag>
1729 if (i + 4 + tag.length() > n)
1733 if (src[i + 1] != '/')
1735 if (src[i + 2] != '@')
1737 if (tag != QStringRef(&src, i + 3, tag.length()))
1739 if (src[i + 3 + tag.length()] != '>')
1744 *contents = QStringRef(&src, j, i - j);
1746 i += tag.length() + 4;
1750 qDebug() << " tag " << tag << " found: pos now: " << i;
1755 QString Generator::plainCode(const QString& markedCode)
1757 QString t = markedCode;
1758 t.replace(tag, QString());
1759 t.replace(quot, QLatin1String("\""));
1760 t.replace(gt, QLatin1String(">"));
1761 t.replace(lt, QLatin1String("<"));
1762 t.replace(amp, QLatin1String("&"));
1766 void Generator::setImageFileExtensions(const QStringList& extensions)
1768 imgFileExts[format()] = extensions;
1771 void Generator::singularPlural(Text& text, const NodeList& nodes)
1773 if (nodes.count() == 1)
1779 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1782 atom = atom->next();
1783 while (atom != 0 && atom->type() != type) {
1785 atom = atom->next();
1791 Resets the variables used during text output.
1793 void Generator::initializeTextOutput()
1796 inContents_ = false;
1797 inSectionHeading_ = false;
1798 inTableHeader_ = false;
1800 threeColumnEnumValueTable_ = true;
1802 sectionNumber_.clear();
1805 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1807 if (node->type() == Node::Function) {
1808 const FunctionNode *func = static_cast<const FunctionNode *>(node);
1809 if (func->overloadNumber() == 1) {
1810 QString alternateName;
1811 const FunctionNode *alternateFunc = 0;
1813 if (func->name().startsWith("set") && func->name().size() >= 4) {
1814 alternateName = func->name()[3].toLower();
1815 alternateName += func->name().mid(4);
1816 alternateFunc = func->parent()->findFunctionNode(alternateName);
1818 if (!alternateFunc) {
1819 alternateName = "is" + func->name().mid(3);
1820 alternateFunc = func->parent()->findFunctionNode(alternateName);
1821 if (!alternateFunc) {
1822 alternateName = "has" + func->name().mid(3);
1823 alternateFunc = func->parent()->findFunctionNode(alternateName);
1827 else if (!func->name().isEmpty()) {
1828 alternateName = "set";
1829 alternateName += func->name()[0].toUpper();
1830 alternateName += func->name().mid(1);
1831 alternateFunc = func->parent()->findFunctionNode(alternateName);
1834 if (alternateFunc && alternateFunc->access() != Node::Private) {
1836 for (i = 0; i < alsoList.size(); ++i) {
1837 if (alsoList.at(i).toString().contains(alternateName))
1841 if (i == alsoList.size()) {
1842 alternateName += "()";
1845 also << Atom(Atom::Link, alternateName)
1846 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1848 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1849 alsoList.prepend(also);
1856 void Generator::terminate()
1858 QList<Generator *>::ConstIterator g = generators.constBegin();
1859 while (g != generators.constEnd()) {
1860 if (outputFormats.contains((*g)->format()))
1861 (*g)->terminateGenerator();
1865 fmtLeftMaps.clear();
1866 fmtRightMaps.clear();
1867 imgFileExts.clear();
1871 QmlClassNode::terminate();
1872 ExampleNode::terminate();
1875 void Generator::terminateGenerator()
1880 Trims trailing whitespace off the \a string and returns
1883 QString Generator::trimmedTrailing(const QString& string)
1885 QString trimmed = string;
1886 while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1887 trimmed.truncate(trimmed.length() - 1);
1891 QString Generator::typeString(const Node *node)
1893 switch (node->type()) {
1894 case Node::Namespace:
1898 case Node::Document:
1900 switch (node->subType()) {
1901 case Node::QmlClass:
1903 case Node::QmlPropertyGroup:
1904 return "property group";
1905 case Node::QmlBasicType:
1908 return "documentation";
1915 case Node::Function:
1917 case Node::Property:
1920 return "documentation";
1924 void Generator::unknownAtom(const Atom *atom)
1926 Location::internalError(tr("unknown atom type '%1' in %2 generator")
1927 .arg(atom->typeString()).arg(format()));