1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the tools applications of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
40 ****************************************************************************/
47 #include "codemarker.h"
49 #include "ditaxmlgenerator.h"
51 #include "editdistance.h"
52 #include "generator.h"
53 #include "openedlist.h"
55 #include "separator.h"
56 #include "tokenizer.h"
57 #include "qdocdatabase.h"
61 Generator* Generator::currentGenerator_;
62 QStringList Generator::exampleDirs;
63 QStringList Generator::exampleImgExts;
64 QMap<QString, QMap<QString, QString> > Generator::fmtLeftMaps;
65 QMap<QString, QMap<QString, QString> > Generator::fmtRightMaps;
66 QList<Generator *> Generator::generators;
67 QStringList Generator::imageDirs;
68 QStringList Generator::imageFiles;
69 QMap<QString, QStringList> Generator::imgFileExts;
70 QString Generator::outDir_;
71 QSet<QString> Generator::outputFormats;
72 QHash<QString, QString> Generator::outputPrefixes;
73 QString Generator::project;
74 QStringList Generator::scriptDirs;
75 QStringList Generator::scriptFiles;
76 QString Generator::sinceTitles[] =
80 " New Member Functions",
81 " New Functions in Namespaces",
82 " New Global Functions",
89 " New QML Properties",
91 " New QML Signal Handlers",
95 QStringList Generator::styleDirs;
96 QStringList Generator::styleFiles;
97 bool Generator::debugging_ = false;
98 bool Generator::noLinkErrors_ = false;
99 Generator::Passes Generator::qdocPass_ = Both;
101 void Generator::setDebugSegfaultFlag(bool b)
104 qDebug() << "DEBUG: Setting debug flag.";
106 qDebug() << "DEBUG: Clearing debug flag.";
111 Prints \a message as an aid to debugging the release version.
113 void Generator::debugSegfault(const QString& message)
116 qDebug() << "DEBUG:" << message;
120 Constructs the generator base class. Prepends the newly
121 constructed generator to the list of output generators.
122 Sets a pointer to the QDoc database singleton, which is
123 available to the generator subclasses.
125 Generator::Generator()
133 inSectionHeading_(false),
134 inTableHeader_(false),
135 threeColumnEnumValueTable_(true),
138 qdb_ = QDocDatabase::qdocDB();
139 generators.prepend(this);
143 Destroys the generator after removing it from the list of
146 Generator::~Generator()
148 generators.removeAll(this);
151 void Generator::appendFullName(Text& text,
152 const Node *apparentNode,
153 const Node *relative,
154 const Node *actualNode)
157 actualNode = apparentNode;
158 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
159 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
160 << Atom(Atom::String, apparentNode->plainFullName(relative))
161 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
164 void Generator::appendFullName(Text& text,
165 const Node *apparentNode,
166 const QString& fullName,
167 const Node *actualNode)
170 actualNode = apparentNode;
171 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
172 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
173 << Atom(Atom::String, fullName)
174 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
177 void Generator::appendFullNames(Text& text, const NodeList& nodes, const Node* relative)
179 NodeList::ConstIterator n = nodes.constBegin();
181 while (n != nodes.constEnd()) {
182 appendFullName(text,*n,relative);
183 text << comma(index++,nodes.count());
188 void Generator::appendSortedNames(Text& text, const ClassNode *classe, const QList<RelatedClass> &classes)
190 QList<RelatedClass>::ConstIterator r;
191 QMap<QString,Text> classMap;
194 r = classes.constBegin();
195 while (r != classes.constEnd()) {
196 if ((*r).node->access() == Node::Public &&
197 (*r).node->status() != Node::Internal
198 && !(*r).node->doc().isEmpty()) {
200 appendFullName(className, (*r).node, classe);
201 classMap[className.toString().toLower()] = className;
206 QStringList classNames = classMap.keys();
209 foreach (const QString &className, classNames) {
210 text << classMap[className];
211 text << separator(index++, classNames.count());
215 void Generator::appendSortedQmlNames(Text& text, const Node* base, const NodeList& subs)
217 QMap<QString,Text> classMap;
220 for (int i = 0; i < subs.size(); ++i) {
222 if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
223 (base->qmlModuleIdentifier() == subs[i]->qmlModuleIdentifier())) {
224 appendFullName(t, subs[i], base);
225 classMap[t.toString().toLower()] = t;
229 QStringList names = classMap.keys();
232 foreach (const QString &name, names) {
233 text << classMap[name];
234 text << separator(index++, names.count());
238 QMultiMap<QString,QString> outFileNames;
243 void Generator::writeOutFileNames()
245 QFile* files = new QFile("/Users/msmith/depot/qt5/qtdoc/outputlist.txt");
246 files->open(QFile::WriteOnly);
247 QTextStream* filesout = new QTextStream(files);
248 QMultiMap<QString,QString>::ConstIterator i = outFileNames.begin();
249 while (i != outFileNames.end()) {
250 (*filesout) << i.key() << "\n";
258 Creates the file named \a fileName in the output directory.
259 Attaches a QTextStream to the created file, which is written
260 to all over the place using out().
262 void Generator::beginSubPage(const InnerNode* node, const QString& fileName)
264 QString path = outputDir() + QLatin1Char('/');
265 if (!node->outputSubdirectory().isEmpty())
266 path += node->outputSubdirectory() + QLatin1Char('/');
268 Generator::debugSegfault("Writing: " + path);
269 outFileNames.insert(fileName,fileName);
270 QFile* outFile = new QFile(path);
271 if (!outFile->open(QFile::WriteOnly))
272 node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
273 QTextStream* out = new QTextStream(outFile);
276 out->setCodec(outputCodec);
277 outStreamStack.push(out);
278 const_cast<InnerNode*>(node)->setOutputFileName(fileName);
282 Flush the text stream associated with the subpage, and
283 then pop it off the text stream stack and delete it.
284 This terminates output of the subpage.
286 void Generator::endSubPage()
288 outStreamStack.top()->flush();
289 delete outStreamStack.top()->device();
290 delete outStreamStack.pop();
293 QString Generator::fileBase(const Node *node) const
296 node = node->relates();
297 else if (!node->isInnerNode())
298 node = node->parent();
299 if (node->subType() == Node::QmlPropertyGroup) {
300 node = node->parent();
303 QString base = node->doc().baseName();
307 const Node *p = node;
310 const Node *pp = p->parent();
311 base.prepend(p->name());
312 if (!p->qmlModuleIdentifier().isEmpty())
313 base.prepend(p->qmlModuleIdentifier()+QChar('-'));
315 To avoid file name conflicts in the html directory,
316 we prepend a prefix (by default, "qml-") to the file name of QML
319 if ((p->subType() == Node::QmlClass) ||
320 (p->subType() == Node::QmlBasicType)) {
321 base.prepend(outputPrefix(QLatin1String("QML")));
323 if (!pp || pp->name().isEmpty() || pp->type() == Node::Document)
325 base.prepend(QLatin1Char('-'));
328 if (node->type() == Node::Document) {
329 if (node->subType() == Node::Collision) {
330 const NameCollisionNode* ncn = static_cast<const NameCollisionNode*>(node);
331 if (ncn->currentChild())
332 return fileBase(ncn->currentChild());
333 base.prepend("collision-");
335 //Was QDOC2_COMPAT, required for index.html
336 if (base.endsWith(".html"))
337 base.truncate(base.length() - 5);
339 if (node->subType() == Node::QmlModule) {
340 base.prepend("qmlmodule-");
342 if (node->subType() == Node::Module) {
343 base.append("-module");
347 // the code below is effectively equivalent to:
348 // base.replace(QRegExp("[^A-Za-z0-9]+"), " ");
349 // base = base.trimmed();
350 // base.replace(QLatin1Char(' '), QLatin1Char('-'));
351 // base = base.toLower();
352 // as this function accounted for ~8% of total running time
353 // we optimize a bit...
356 // +5 prevents realloc in fileName() below
357 res.reserve(base.size() + 5);
359 for (int i = 0; i != base.size(); ++i) {
360 QChar c = base.at(i);
361 uint u = c.unicode();
362 if (u >= 'A' && u <= 'Z')
364 if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
365 res += QLatin1Char(u);
369 res += QLatin1Char('-');
373 while (res.endsWith(QLatin1Char('-')))
379 If the \a node has a URL, return the URL as the file name.
380 Otherwise, construct the file name from the fileBase() and
381 the fileExtension(), and return the constructed name.
383 QString Generator::fileName(const Node* node) const
385 if (!node->url().isEmpty())
388 QString name = fileBase(node);
389 name += QLatin1Char('.');
390 name += fileExtension();
394 QMap<QString, QString>& Generator::formattingLeftMap()
396 return fmtLeftMaps[format()];
399 QMap<QString, QString>& Generator::formattingRightMap()
401 return fmtRightMaps[format()];
405 Returns the full document location.
407 QString Generator::fullDocumentLocation(const Node *node, bool subdir)
411 if (!node->url().isEmpty())
419 If the output is being sent to subdirectories of the
420 output directory, and if the subdir parameter is set,
421 prepend the subdirectory name + '/' to the result.
424 fdl = node->outputSubdirectory();
426 fdl.append(QLatin1Char('/'));
428 if (node->type() == Node::Namespace) {
430 // The root namespace has no name - check for this before creating
431 // an attribute containing the location of any documentation.
433 if (!fileBase(node).isEmpty())
434 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
438 else if (node->type() == Node::Document) {
439 if ((node->subType() == Node::QmlClass) ||
440 (node->subType() == Node::QmlBasicType)) {
441 QString fb = fileBase(node);
442 if (fb.startsWith(Generator::outputPrefix(QLatin1String("QML"))))
443 return fb + QLatin1Char('.') + currentGenerator()->fileExtension();
446 if (!node->qmlModuleName().isEmpty()) {
447 mq = node->qmlModuleIdentifier().replace(QChar('.'),QChar('-'));
448 mq = mq.toLower() + QLatin1Char('-');
450 return fdl+ Generator::outputPrefix(QLatin1String("QML")) + mq +
451 fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
455 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
458 else if (fileBase(node).isEmpty())
461 Node *parentNode = 0;
463 if ((parentNode = node->relates())) {
464 parentName = fullDocumentLocation(node->relates());
466 else if ((parentNode = node->parent())) {
467 if (parentNode->subType() == Node::QmlPropertyGroup) {
468 parentNode = parentNode->parent();
469 parentName = fullDocumentLocation(parentNode);
472 parentName = fullDocumentLocation(node->parent());
476 switch (node->type()) {
478 case Node::Namespace:
479 if (parentNode && !parentNode->name().isEmpty()) {
480 parentName.remove(QLatin1Char('.') + currentGenerator()->fileExtension());
481 parentName += QLatin1Char('-')
482 + fileBase(node).toLower() + QLatin1Char('.') + currentGenerator()->fileExtension();
484 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
489 const FunctionNode *functionNode =
490 static_cast<const FunctionNode *>(node);
492 if (functionNode->metaness() == FunctionNode::Dtor)
493 anchorRef = "#dtor." + functionNode->name().mid(1);
495 else if (functionNode->associatedProperty())
496 return fullDocumentLocation(functionNode->associatedProperty());
498 else if (functionNode->overloadNumber() > 1)
499 anchorRef = QLatin1Char('#') + functionNode->name()
500 + QLatin1Char('-') + QString::number(functionNode->overloadNumber());
502 anchorRef = QLatin1Char('#') + functionNode->name();
506 Use node->name() instead of fileBase(node) as
507 the latter returns the name in lower-case. For
508 HTML anchors, we need to preserve the case.
511 anchorRef = QLatin1Char('#') + node->name() + "-enum";
514 anchorRef = QLatin1Char('#') + node->name() + "-typedef";
517 anchorRef = QLatin1Char('#') + node->name() + "-prop";
519 case Node::QmlProperty:
520 anchorRef = QLatin1Char('#') + node->name() + "-prop";
522 case Node::QmlSignal:
523 anchorRef = QLatin1Char('#') + node->name() + "-signal";
525 case Node::QmlSignalHandler:
526 anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
528 case Node::QmlMethod:
529 anchorRef = QLatin1Char('#') + node->name() + "-method";
532 anchorRef = QLatin1Char('#') + node->name() + "-var";
536 parentName = fileBase(node);
537 parentName.replace(QLatin1Char('/'), QLatin1Char('-')).replace(QLatin1Char('.'), QLatin1Char('-'));
538 parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
545 // Various objects can be compat (deprecated) or obsolete.
546 if (node->type() != Node::Class && node->type() != Node::Namespace) {
547 switch (node->status()) {
549 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
550 "-compat." + currentGenerator()->fileExtension());
553 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
554 "-obsolete." + currentGenerator()->fileExtension());
561 return fdl + parentName.toLower() + anchorRef;
564 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
566 QList<Text> alsoList = node->doc().alsoList();
567 supplementAlsoList(node, alsoList);
569 if (!alsoList.isEmpty()) {
571 text << Atom::ParaLeft
572 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
574 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
576 for (int i = 0; i < alsoList.size(); ++i)
577 text << alsoList.at(i) << separator(i, alsoList.size());
579 text << Atom::ParaRight;
580 generateText(text, node, marker);
584 int Generator::generateAtom(const Atom * /* atom */,
585 const Node * /* relative */,
586 CodeMarker * /* marker */)
591 const Atom *Generator::generateAtomList(const Atom *atom,
592 const Node *relative,
598 if (atom->type() == Atom::FormatIf) {
599 int numAtoms0 = numAtoms;
600 bool rightFormat = canHandleFormat(atom->string());
601 atom = generateAtomList(atom->next(),
604 generate && rightFormat,
609 if (atom->type() == Atom::FormatElse) {
611 atom = generateAtomList(atom->next(),
614 generate && !rightFormat,
620 if (atom->type() == Atom::FormatEndif) {
621 if (generate && numAtoms0 == numAtoms) {
622 relative->location().warning(tr("Output format %1 not handled %2")
623 .arg(format()).arg(outFileName()));
624 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
625 generateAtomList(&unhandledFormatAtom,
634 else if (atom->type() == Atom::FormatElse ||
635 atom->type() == Atom::FormatEndif) {
641 n += generateAtom(atom, relative, marker);
651 void Generator::generateBody(const Node *node, CodeMarker *marker)
655 if (node->type() == Node::Document) {
656 const DocNode *dn = static_cast<const DocNode *>(node);
657 if ((dn->subType() == Node::File) || (dn->subType() == Node::Image)) {
661 if (node->doc().isEmpty()) {
662 if (!quiet && !node->isReimp()) { // ### might be unnecessary
663 node->location().warning(tr("No documentation for '%1'").arg(node->plainFullName()));
667 if (node->type() == Node::Function) {
668 const FunctionNode *func = static_cast<const FunctionNode *>(node);
669 if (func->reimplementedFrom() != 0)
670 generateReimplementedFrom(func, marker);
673 if (!generateText(node->doc().body(), node, marker)) {
678 if (node->type() == Node::Enum) {
679 const EnumNode *enume = (const EnumNode *) node;
681 QSet<QString> definedItems;
682 QList<EnumItem>::ConstIterator it = enume->items().constBegin();
683 while (it != enume->items().constEnd()) {
684 definedItems.insert((*it).name());
688 QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
689 QSet<QString> allItems = definedItems + documentedItems;
690 if (allItems.count() > definedItems.count() ||
691 allItems.count() > documentedItems.count()) {
692 QSet<QString>::ConstIterator a = allItems.constBegin();
693 while (a != allItems.constEnd()) {
694 if (!definedItems.contains(*a)) {
696 QString best = nearestName(*a, definedItems);
697 if (!best.isEmpty() && !documentedItems.contains(best))
698 details = tr("Maybe you meant '%1'?").arg(best);
700 node->doc().location().warning(tr("No such enum item '%1' in %2").arg(*a).arg(node->plainFullName()), details);
702 qDebug() << "VOID:" << node->name() << definedItems;
704 else if (!documentedItems.contains(*a)) {
705 node->doc().location().warning(tr("Undocumented enum item '%1' in %2").arg(*a).arg(node->plainFullName()));
711 else if (node->type() == Node::Function) {
712 const FunctionNode *func = static_cast<const FunctionNode *>(node);
713 QSet<QString> definedParams;
714 QList<Parameter>::ConstIterator p = func->parameters().constBegin();
715 while (p != func->parameters().constEnd()) {
716 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
717 && func->name() != QLatin1String("operator++")
718 && func->name() != QLatin1String("operator--")) {
719 node->doc().location().warning(tr("Missing parameter name"));
722 definedParams.insert((*p).name());
727 QSet<QString> documentedParams = func->doc().parameterNames();
728 QSet<QString> allParams = definedParams + documentedParams;
729 if (allParams.count() > definedParams.count()
730 || allParams.count() > documentedParams.count()) {
731 QSet<QString>::ConstIterator a = allParams.constBegin();
732 while (a != allParams.constEnd()) {
733 if (!definedParams.contains(*a)) {
735 QString best = nearestName(*a, definedParams);
737 details = tr("Maybe you meant '%1'?").arg(best);
739 node->doc().location().warning(
740 tr("No such parameter '%1' in %2").arg(*a).arg(node->plainFullName()),
743 else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
744 bool needWarning = (func->status() > Node::Obsolete);
745 if (func->overloadNumber() > 1) {
746 FunctionNode *primaryFunc =
747 func->parent()->findFunctionNode(func->name());
749 foreach (const Parameter ¶m,
750 primaryFunc->parameters()) {
751 if (param.name() == *a) {
758 if (needWarning && !func->isReimp())
759 node->doc().location().warning(
760 tr("Undocumented parameter '%1' in %2")
761 .arg(*a).arg(node->plainFullName()));
767 Something like this return value check should
768 be implemented at some point.
770 if (func->status() > Node::Obsolete && func->returnType() == "bool"
771 && func->reimplementedFrom() == 0 && !func->isOverload()) {
772 QString body = func->doc().body().toString();
773 if (!body.contains("return", Qt::CaseInsensitive))
774 node->doc().location().warning(tr("Undocumented return value"));
779 if (node->type() == Node::Document) {
780 const DocNode *dn = static_cast<const DocNode *>(node);
781 if (dn->subType() == Node::Example) {
782 generateExampleFiles(dn, marker);
784 else if (dn->subType() == Node::File) {
787 Doc::quoteFromFile(dn->doc().location(), quoter, dn->name());
788 QString code = quoter.quoteTo(dn->location(), QString(), QString());
789 CodeMarker *codeMarker = CodeMarker::markerForFileName(dn->name());
790 text << Atom(codeMarker->atomType(), code);
791 generateText(text, dn, codeMarker);
796 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
800 void Generator::generateExampleFiles(const DocNode *dn, CodeMarker *marker)
802 if (dn->childNodes().isEmpty())
804 generateFileList(dn, marker, Node::File, QString("Files:"));
805 generateFileList(dn, marker, Node::Image, QString("Images:"));
808 void Generator::generateDocNode(DocNode* /* dn */, CodeMarker* /* marker */)
813 This function is called when the documentation for an
814 example is being formatted. It outputs the list of source
815 files comprising the example, and the list of images used
816 by the example. The images are copied into a subtree of
817 \c{...doc/html/images/used-in-examples/...}
819 void Generator::generateFileList(const DocNode* dn,
821 Node::SubType subtype,
826 OpenedList openedList(OpenedList::Bullet);
828 text << Atom::ParaLeft << tag << Atom::ParaRight
829 << Atom(Atom::ListLeft, openedList.styleString());
831 foreach (const Node* child, dn->childNodes()) {
832 if (child->subType() == subtype) {
834 QString file = child->name();
835 if (subtype == Node::Image) {
836 if (!file.isEmpty()) {
838 QString userFriendlyFilePath;
839 QString srcPath = Config::findFile(dn->location(),
844 userFriendlyFilePath);
845 userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
847 QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
848 if (!dirInfo.mkpath(imgOutDir))
849 dn->location().fatal(tr("Cannot create output directory '%1'")
852 QString imgOutName = Config::copyFile(dn->location(),
861 text << Atom(Atom::ListItemNumber, openedList.numberString())
862 << Atom(Atom::ListItemLeft, openedList.styleString())
864 << Atom(Atom::Link, file)
865 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
867 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
869 << Atom(Atom::ListItemRight, openedList.styleString());
872 text << Atom(Atom::ListRight, openedList.styleString());
874 generateText(text, dn, marker);
877 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
879 if (!classe->derivedClasses().isEmpty()) {
881 text << Atom::ParaLeft
882 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
884 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
886 appendSortedNames(text, classe, classe->derivedClasses());
887 text << Atom::ParaRight;
888 generateText(text, classe, marker);
892 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
894 QList<RelatedClass>::ConstIterator r;
897 if (!classe->baseClasses().isEmpty()) {
899 text << Atom::ParaLeft
900 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
902 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
904 r = classe->baseClasses().constBegin();
906 while (r != classe->baseClasses().constEnd()) {
907 text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
908 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
909 << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
910 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
912 if ((*r).access == Node::Protected) {
913 text << " (protected)";
915 else if ((*r).access == Node::Private) {
916 text << " (private)";
918 text << separator(index++, classe->baseClasses().count());
921 text << Atom::ParaRight;
922 generateText(text, classe, marker);
927 Recursive writing of HTML files from the root \a node.
929 \note NameCollisionNodes are skipped here and processed
930 later. See HtmlGenerator::generateCollisionPages() for
933 void Generator::generateInnerNode(InnerNode* node)
935 if (!node->url().isNull())
938 if (node->type() == Node::Document) {
939 DocNode* docNode = static_cast<DocNode*>(node);
940 if (docNode->subType() == Node::ExternalPage)
942 if (docNode->subType() == Node::Image)
944 if (docNode->subType() == Node::QmlPropertyGroup)
946 if (docNode->subType() == Node::Page) {
947 if (node->count() > 0)
948 qDebug("PAGE %s HAS CHILDREN", qPrintable(docNode->title()));
953 Obtain a code marker for the source file.
955 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
957 if (node->parent() != 0) {
959 Skip name collision nodes here and process them
960 later in generateCollisionPages(). Each one is
961 appended to a list for later.
963 if ((node->type() == Node::Document) && (node->subType() == Node::Collision)) {
964 NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
965 collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
968 beginSubPage(node, fileName(node));
969 if (node->type() == Node::Namespace || node->type() == Node::Class) {
970 generateClassLikeNode(node, marker);
972 else if (node->type() == Node::Document) {
973 generateDocNode(static_cast<DocNode*>(node), marker);
979 NodeList::ConstIterator c = node->childNodes().constBegin();
980 while (c != node->childNodes().constEnd()) {
981 if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
982 generateInnerNode((InnerNode*)*c);
989 Generate a list of maintainers in the output
991 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
993 QStringList sl = getMetadataElements(node,"maintainer");
997 text << Atom::ParaLeft
998 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1000 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
1002 for (int i = 0; i < sl.size(); ++i)
1003 text << sl.at(i) << separator(i, sl.size());
1005 text << Atom::ParaRight;
1006 generateText(text, node, marker);
1011 Output the "Inherit by" list for the QML element,
1012 if it is inherited by any other elements.
1014 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
1019 QmlClassNode::subclasses(qcn->name(),subs);
1020 if (!subs.isEmpty()) {
1022 text << Atom::ParaLeft << "Inherited by ";
1023 appendSortedQmlNames(text,qcn,subs);
1024 text << Atom::ParaRight;
1025 generateText(text, qcn, marker);
1032 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
1038 Extract sections of markup text surrounded by \e qmltext
1039 and \e endqmltext and output them.
1041 bool Generator::generateQmlText(const Text& text,
1042 const Node *relative,
1044 const QString& /* qmlName */ )
1046 const Atom* atom = text.firstAtom();
1047 bool result = false;
1050 initializeTextOutput();
1052 if (atom->type() != Atom::QmlText)
1053 atom = atom->next();
1055 atom = atom->next();
1056 while (atom && (atom->type() != Atom::EndQmlText)) {
1057 int n = 1 + generateAtom(atom, relative, marker);
1059 atom = atom->next();
1068 void Generator::generateReimplementedFrom(const FunctionNode *func,
1071 if (func->reimplementedFrom() != 0) {
1072 const FunctionNode *from = func->reimplementedFrom();
1073 if (from->access() != Node::Private &&
1074 from->parent()->access() != Node::Private) {
1076 text << Atom::ParaLeft << "Reimplemented from ";
1077 QString fullName = from->parent()->name() + "::" + from->name() + "()";
1078 appendFullName(text, from->parent(), fullName, from);
1079 text << "." << Atom::ParaRight;
1080 generateText(text, func, marker);
1085 void Generator::generateSince(const Node *node, CodeMarker *marker)
1087 if (!node->since().isEmpty()) {
1089 text << Atom::ParaLeft
1091 << typeString(node);
1092 if (node->type() == Node::Enum)
1093 text << " was introduced or modified in ";
1095 text << " was introduced in ";
1097 QStringList since = node->since().split(QLatin1Char(' '));
1098 if (since.count() == 1) {
1099 // Handle legacy use of \since <version>.
1100 if (project.isEmpty())
1104 text << " " << since[0];
1106 // Reconstruct the <project> <version> string.
1107 text << " " << since.join(' ');
1110 text << "." << Atom::ParaRight;
1111 generateText(text, node, marker);
1115 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1119 switch (node->status()) {
1120 case Node::Commendable:
1123 case Node::Preliminary:
1124 text << Atom::ParaLeft
1125 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1128 << " is under development and is subject to change."
1129 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1132 case Node::Deprecated:
1133 text << Atom::ParaLeft;
1134 if (node->isInnerNode())
1135 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1136 text << "This " << typeString(node) << " is deprecated.";
1137 if (node->isInnerNode())
1138 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1139 text << Atom::ParaRight;
1141 case Node::Obsolete:
1142 text << Atom::ParaLeft;
1143 if (node->isInnerNode())
1144 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1145 text << "This " << typeString(node) << " is obsolete.";
1146 if (node->isInnerNode())
1147 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1148 text << " It is provided to keep old source code working. "
1149 << "We strongly advise against "
1150 << "using it in new code." << Atom::ParaRight;
1153 // reimplemented in HtmlGenerator subclass
1154 if (node->isInnerNode()) {
1155 text << Atom::ParaLeft
1156 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1159 << " is part of the Qt compatibility layer."
1160 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1161 << " It is provided to keep old source code working. "
1162 << "We strongly advise against using it in new code."
1166 case Node::Internal:
1170 generateText(text, node, marker);
1173 bool Generator::generateText(const Text& text,
1174 const Node *relative,
1177 bool result = false;
1178 if (text.firstAtom() != 0) {
1180 initializeTextOutput();
1181 generateAtomList(text.firstAtom(),
1191 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1194 Node::ThreadSafeness threadSafeness = node->threadSafeness();
1197 rlink << Atom(Atom::Link,"reentrant")
1198 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1200 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1203 tlink << Atom(Atom::Link,"thread-safe")
1204 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1206 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1208 switch (threadSafeness) {
1209 case Node::UnspecifiedSafeness:
1211 case Node::NonReentrant:
1212 text << Atom::ParaLeft
1213 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1215 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1223 case Node::Reentrant:
1224 case Node::ThreadSafe:
1225 text << Atom::ParaLeft
1226 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1228 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1231 if (node->isInnerNode()) {
1232 const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1233 text << "All functions in this "
1236 if (threadSafeness == Node::ThreadSafe)
1241 bool exceptions = false;
1243 NodeList threadsafe;
1244 NodeList nonreentrant;
1245 NodeList::ConstIterator c = innerNode->childNodes().constBegin();
1246 while (c != innerNode->childNodes().constEnd()) {
1248 if ((*c)->status() != Node::Obsolete){
1249 switch ((*c)->threadSafeness()) {
1250 case Node::Reentrant:
1251 reentrant.append(*c);
1252 if (threadSafeness == Node::ThreadSafe)
1255 case Node::ThreadSafe:
1256 threadsafe.append(*c);
1257 if (threadSafeness == Node::Reentrant)
1260 case Node::NonReentrant:
1261 nonreentrant.append(*c);
1272 else if (threadSafeness == Node::Reentrant) {
1273 if (nonreentrant.isEmpty()) {
1274 if (!threadsafe.isEmpty()) {
1276 appendFullNames(text,threadsafe,innerNode);
1277 singularPlural(text,threadsafe);
1278 text << " also " << tlink << ".";
1284 text << ", except for ";
1285 appendFullNames(text,nonreentrant,innerNode);
1287 singularPlural(text,nonreentrant);
1288 text << " nonreentrant.";
1289 if (!threadsafe.isEmpty()) {
1291 appendFullNames(text,threadsafe,innerNode);
1292 singularPlural(text,threadsafe);
1293 text << " " << tlink << ".";
1297 else { // thread-safe
1298 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1299 text << ", except for ";
1300 if (!reentrant.isEmpty()) {
1301 appendFullNames(text,reentrant,innerNode);
1303 singularPlural(text,reentrant);
1304 text << " only " << rlink;
1305 if (!nonreentrant.isEmpty())
1308 if (!nonreentrant.isEmpty()) {
1309 appendFullNames(text,nonreentrant,innerNode);
1311 singularPlural(text,nonreentrant);
1312 text << " nonreentrant.";
1319 text << "This " << typeString(node) << " is ";
1320 if (threadSafeness == Node::ThreadSafe)
1326 text << Atom::ParaRight;
1328 generateText(text,node,marker);
1332 Traverses the database recursivly to generate all the documentation.
1334 void Generator::generateTree()
1336 generateInnerNode(qdb_->treeRoot());
1339 Generator *Generator::generatorForFormat(const QString& format)
1341 QList<Generator *>::ConstIterator g = generators.constBegin();
1342 while (g != generators.constEnd()) {
1343 if ((*g)->format() == format)
1351 This function can be called if getLink() returns an empty
1352 string. It tests the \a atom string to see if it is a link
1353 of the form <element> :: <name>, where <element> is a QML
1354 element or component without a module qualifier. If so, it
1355 constructs a link to the <name> clause on the disambiguation
1356 page for <element> and returns that link string. It also
1357 adds the <name> as a target in the NameCollisionNode for
1358 <element>. These clauses are then constructed when the
1359 disambiguation page is actually generated.
1361 QString Generator::getCollisionLink(const Atom* atom)
1364 if (!atom->string().contains("::"))
1366 QStringList path = atom->string().split("::");
1367 NameCollisionNode* ncn = qdb_->findCollisionNode(path[0]);
1370 if (atom->next() && atom->next()->next()) {
1371 if (atom->next()->type() == Atom::FormattingLeft &&
1372 atom->next()->next()->type() == Atom::String)
1373 label = atom->next()->next()->string();
1375 ncn->addLinkTarget(path[1],label);
1376 link = fileName(ncn);
1377 link += QLatin1Char('#');
1378 link += Doc::canonicalTitle(path[1]);
1385 Looks up the tag \a t in the map of metadata values for the
1386 current topic in \a inner. If a value for the tag is found,
1387 the value is returned.
1389 \note If \a t is found in the metadata map, it is erased.
1390 i.e. Once you call this function for a particular \a t,
1393 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1396 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1397 QStringMultiMap::iterator i = metaTagMap.find(t);
1398 if (i != metaTagMap.end()) {
1400 metaTagMap.erase(i);
1406 Looks up the tag \a t in the map of metadata values for the
1407 current topic in \a inner. If values for the tag are found,
1408 they are returned in a string list.
1410 \note If \a t is found in the metadata map, all the pairs
1411 having the key \a t are erased. i.e. Once you call this
1412 function for a particular \a t, you consume \a t.
1414 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1417 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1418 s = metaTagMap.values(t);
1420 metaTagMap.remove(t);
1425 Returns a relative path name for an image.
1427 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1429 QString userFriendlyFilePath;
1430 QString filePath = Config::findFile(relative->doc().location(),
1434 imgFileExts[format()],
1435 userFriendlyFilePath);
1437 if (filePath.isEmpty())
1440 QString path = Config::copyFile(relative->doc().location(),
1442 userFriendlyFilePath,
1443 outputDir() + QLatin1String("/images"));
1444 QString images = "images";
1446 images.append(QLatin1Char('/'));
1447 return images + path;
1450 QString Generator::indent(int level, const QString& markedCode)
1459 while (i < (int) markedCode.length()) {
1460 if (markedCode.at(i) == QLatin1Char('\n')) {
1465 for (int j = 0; j < level; j++)
1466 t += QLatin1Char(' ');
1470 t += markedCode.at(i++);
1475 void Generator::initialize(const Config &config)
1477 outputFormats = config.getOutputFormats();
1478 if (!outputFormats.isEmpty()) {
1479 outDir_ = config.getOutputDir();
1481 if (outDir_.isEmpty())
1482 config.lastLocation().fatal(tr("No output directory specified in "
1483 "configuration file or on the command line"));
1486 if (dirInfo.exists(outDir_)) {
1487 if (!runGenerateOnly()) {
1488 if (!Config::removeDirContents(outDir_))
1489 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1493 if (!dirInfo.mkpath(outDir_))
1494 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1497 if (!dirInfo.exists(outDir_ + "/images") && !dirInfo.mkdir(outDir_ + "/images"))
1498 config.lastLocation().fatal(tr("Cannot create images directory '%1'").arg(outDir_ + "/images"));
1499 if (!dirInfo.exists(outDir_ + "/images/used-in-examples") && !dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1500 config.lastLocation().fatal(tr("Cannot create images used in examples directory '%1'").arg(outDir_ + "/images/used-in-examples"));
1501 if (!dirInfo.exists(outDir_ + "/scripts") && !dirInfo.mkdir(outDir_ + "/scripts"))
1502 config.lastLocation().fatal(tr("Cannot create scripts directory '%1'").arg(outDir_ + "/scripts"));
1503 if (!dirInfo.exists(outDir_ + "/style") && !dirInfo.mkdir(outDir_ + "/style"))
1504 config.lastLocation().fatal(tr("Cannot create style directory '%1'").arg(outDir_ + "/style"));
1507 imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1508 imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1509 scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1510 scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1511 styleFiles = config.getCleanPathList(CONFIG_STYLES);
1512 styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1513 exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1514 exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS);
1516 QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1517 QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1518 QSet<QString>::ConstIterator f = formats.constBegin();
1519 while (f != formats.constEnd()) {
1520 imgFileExts[*f] = config.getStringList(imagesDotFileExtensions + Config::dot + *f);
1524 QList<Generator *>::ConstIterator g = generators.constBegin();
1525 while (g != generators.constEnd()) {
1526 if (outputFormats.contains((*g)->format())) {
1527 currentGenerator_ = (*g);
1528 (*g)->initializeGenerator(config);
1529 QStringList extraImages = config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
1530 QStringList::ConstIterator e = extraImages.constBegin();
1531 while (e != extraImages.constEnd()) {
1532 QString userFriendlyFilePath;
1533 QString filePath = Config::findFile(config.lastLocation(),
1537 imgFileExts[(*g)->format()],
1538 userFriendlyFilePath);
1539 if (!filePath.isEmpty())
1540 Config::copyFile(config.lastLocation(),
1542 userFriendlyFilePath,
1548 // Documentation template handling
1549 QString templateDir = config.getString((*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
1551 QStringList searchDirs;
1552 if (!templateDir.isEmpty()) {
1553 searchDirs.append(templateDir);
1555 if (!Config::installDir.isEmpty()) {
1556 searchDirs.append(Config::installDir);
1559 if (!searchDirs.isEmpty()) {
1561 QStringList scripts = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1562 e = scripts.constBegin();
1563 while (e != scripts.constEnd()) {
1564 QString userFriendlyFilePath;
1565 QString filePath = Config::findFile(config.lastLocation(),
1570 userFriendlyFilePath);
1571 if (!filePath.isEmpty())
1572 Config::copyFile(config.lastLocation(),
1574 userFriendlyFilePath,
1580 QStringList styles = config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1581 e = styles.constBegin();
1582 while (e != styles.constEnd()) {
1583 QString userFriendlyFilePath;
1584 QString filePath = Config::findFile(config.lastLocation(),
1589 userFriendlyFilePath);
1590 if (!filePath.isEmpty())
1591 Config::copyFile(config.lastLocation(),
1593 userFriendlyFilePath,
1603 QRegExp secondParamAndAbove("[\2-\7]");
1604 QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1605 QSet<QString>::ConstIterator n = formattingNames.constBegin();
1606 while (n != formattingNames.constEnd()) {
1607 QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1608 QSet<QString> formats = config.subVars(formattingDotName);
1609 QSet<QString>::ConstIterator f = formats.constBegin();
1610 while (f != formats.constEnd()) {
1611 QString def = config.getString(formattingDotName + Config::dot + *f);
1612 if (!def.isEmpty()) {
1613 int numParams = Config::numParams(def);
1614 int numOccs = def.count("\1");
1615 if (numParams != 1) {
1616 config.lastLocation().warning(tr("Formatting '%1' must "
1618 "parameter (found %2)")
1619 .arg(*n).arg(numParams));
1621 else if (numOccs > 1) {
1622 config.lastLocation().fatal(tr("Formatting '%1' must "
1623 "contain exactly one "
1624 "occurrence of '\\1' "
1626 .arg(*n).arg(numOccs));
1629 int paramPos = def.indexOf("\1");
1630 fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1631 fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1639 project = config.getString(CONFIG_PROJECT);
1641 QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1642 if (!prefixes.isEmpty()) {
1643 foreach (const QString &prefix, prefixes)
1644 outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1647 outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1648 noLinkErrors_ = config.getBool(QLatin1String(CONFIG_NOLINKERRORS));
1652 Appends each directory path in \a moreImageDirs to the
1653 list of image directories.
1655 void Generator::augmentImageDirs(QSet<QString>& moreImageDirs)
1657 if (moreImageDirs.isEmpty())
1659 QSet<QString>::const_iterator i = moreImageDirs.begin();
1660 while (i != moreImageDirs.end()) {
1661 imageDirs.append(*i);
1666 void Generator::initializeGenerator(const Config & /* config */)
1670 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1672 return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1676 Used for writing to the current output stream. Returns a
1677 reference to the current output stream, which is then used
1678 with the \c {<<} operator for writing.
1680 QTextStream &Generator::out()
1682 return *outStreamStack.top();
1685 QString Generator::outFileName()
1687 return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1690 QString Generator::outputPrefix(const QString &nodeType)
1692 return outputPrefixes[nodeType];
1695 bool Generator::parseArg(const QString& src,
1699 QStringRef* contents,
1703 #define SKIP_CHAR(c) \
1705 qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1706 if (i >= n || src[i] != c) { \
1708 qDebug() << " char '" << c << "' not found"; \
1714 #define SKIP_SPACE \
1715 while (i < n && src[i] == ' ') \
1721 // assume "<@" has been parsed outside
1725 if (tag != QStringRef(&src, i, tag.length())) {
1727 qDebug() << "tag " << tag << " not found at " << i;
1732 qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1737 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1740 // read parameter name
1742 while (i < n && src[i].isLetter())
1744 if (src[i] == '=') {
1746 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1749 // skip parameter name
1751 while (i < n && src[i] != '"')
1753 *par1 = QStringRef(&src, j, i - j);
1758 qDebug() << "no optional parameter found";
1764 // find contents up to closing "</@tag>
1767 if (i + 4 + tag.length() > n)
1771 if (src[i + 1] != '/')
1773 if (src[i + 2] != '@')
1775 if (tag != QStringRef(&src, i + 3, tag.length()))
1777 if (src[i + 3 + tag.length()] != '>')
1782 *contents = QStringRef(&src, j, i - j);
1784 i += tag.length() + 4;
1788 qDebug() << " tag " << tag << " found: pos now: " << i;
1793 QString Generator::plainCode(const QString& markedCode)
1795 QString t = markedCode;
1796 t.replace(tag, QString());
1797 t.replace(quot, QLatin1String("\""));
1798 t.replace(gt, QLatin1String(">"));
1799 t.replace(lt, QLatin1String("<"));
1800 t.replace(amp, QLatin1String("&"));
1804 void Generator::setImageFileExtensions(const QStringList& extensions)
1806 imgFileExts[format()] = extensions;
1809 void Generator::singularPlural(Text& text, const NodeList& nodes)
1811 if (nodes.count() == 1)
1817 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1820 atom = atom->next();
1821 while (atom != 0 && atom->type() != type) {
1823 atom = atom->next();
1829 Resets the variables used during text output.
1831 void Generator::initializeTextOutput()
1834 inContents_ = false;
1835 inSectionHeading_ = false;
1836 inTableHeader_ = false;
1838 threeColumnEnumValueTable_ = true;
1840 sectionNumber_.clear();
1843 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1845 if (node->type() == Node::Function) {
1846 const FunctionNode *func = static_cast<const FunctionNode *>(node);
1847 if (func->overloadNumber() == 1) {
1848 QString alternateName;
1849 const FunctionNode *alternateFunc = 0;
1851 if (func->name().startsWith("set") && func->name().size() >= 4) {
1852 alternateName = func->name()[3].toLower();
1853 alternateName += func->name().mid(4);
1854 alternateFunc = func->parent()->findFunctionNode(alternateName);
1856 if (!alternateFunc) {
1857 alternateName = "is" + func->name().mid(3);
1858 alternateFunc = func->parent()->findFunctionNode(alternateName);
1859 if (!alternateFunc) {
1860 alternateName = "has" + func->name().mid(3);
1861 alternateFunc = func->parent()->findFunctionNode(alternateName);
1865 else if (!func->name().isEmpty()) {
1866 alternateName = "set";
1867 alternateName += func->name()[0].toUpper();
1868 alternateName += func->name().mid(1);
1869 alternateFunc = func->parent()->findFunctionNode(alternateName);
1872 if (alternateFunc && alternateFunc->access() != Node::Private) {
1874 for (i = 0; i < alsoList.size(); ++i) {
1875 if (alsoList.at(i).toString().contains(alternateName))
1879 if (i == alsoList.size()) {
1880 alternateName += "()";
1883 also << Atom(Atom::Link, alternateName)
1884 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1886 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1887 alsoList.prepend(also);
1894 void Generator::terminate()
1896 QList<Generator *>::ConstIterator g = generators.constBegin();
1897 while (g != generators.constEnd()) {
1898 if (outputFormats.contains((*g)->format()))
1899 (*g)->terminateGenerator();
1903 fmtLeftMaps.clear();
1904 fmtRightMaps.clear();
1905 imgFileExts.clear();
1909 QmlClassNode::terminate();
1910 ExampleNode::terminate();
1913 void Generator::terminateGenerator()
1918 Trims trailing whitespace off the \a string and returns
1921 QString Generator::trimmedTrailing(const QString& string)
1923 QString trimmed = string;
1924 while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1925 trimmed.truncate(trimmed.length() - 1);
1929 QString Generator::typeString(const Node *node)
1931 switch (node->type()) {
1932 case Node::Namespace:
1936 case Node::Document:
1938 switch (node->subType()) {
1939 case Node::QmlClass:
1941 case Node::QmlPropertyGroup:
1942 return "property group";
1943 case Node::QmlBasicType:
1946 return "documentation";
1953 case Node::Function:
1955 case Node::Property:
1958 return "documentation";
1962 void Generator::unknownAtom(const Atom *atom)
1964 Location::internalError(tr("unknown atom type '%1' in %2 generator")
1965 .arg(atom->typeString()).arg(format()));