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 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
47 #include "codemarker.h"
49 #include "ditaxmlgenerator.h"
51 #include "editdistance.h"
52 #include "generator.h"
54 #include "openedlist.h"
56 #include "separator.h"
57 #include "tokenizer.h"
62 QString Generator::baseDir_;
63 Generator* Generator::currentGenerator_;
64 QStringList Generator::exampleDirs;
65 QStringList Generator::exampleImgExts;
66 QMap<QString, QMap<QString, QString> > Generator::fmtLeftMaps;
67 QMap<QString, QMap<QString, QString> > Generator::fmtRightMaps;
68 QList<Generator *> Generator::generators;
69 QStringList Generator::imageDirs;
70 QStringList Generator::imageFiles;
71 QMap<QString, QStringList> Generator::imgFileExts;
72 QString Generator::outDir_;
73 QSet<QString> Generator::outputFormats;
74 QHash<QString, QString> Generator::outputPrefixes;
75 QString Generator::project;
76 QStringList Generator::scriptDirs;
77 QStringList Generator::scriptFiles;
78 QString Generator::sinceTitles[] =
82 " New Member Functions",
83 " New Functions in Namespaces",
84 " New Global Functions",
91 " New QML Properties",
93 " New QML Signal Handlers",
97 QStringList Generator::styleDirs;
98 QStringList Generator::styleFiles;
101 Generator::Generator()
109 generators.prepend(this);
112 Generator::~Generator()
114 generators.removeAll(this);
117 void Generator::appendFullName(Text& text,
118 const Node *apparentNode,
119 const Node *relative,
121 const Node *actualNode)
124 actualNode = apparentNode;
125 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
126 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
127 << Atom(Atom::String, marker->plainFullName(apparentNode, relative))
128 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
131 void Generator::appendFullName(Text& text,
132 const Node *apparentNode,
133 const QString& fullName,
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, fullName)
141 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
144 void Generator::appendFullNames(Text& text,
145 const NodeList& nodes,
146 const Node* relative,
149 NodeList::ConstIterator n = nodes.constBegin();
151 while (n != nodes.constEnd()) {
152 appendFullName(text,*n,relative,marker);
153 text << comma(index++,nodes.count());
158 void Generator::appendSortedNames(Text& text,
159 const ClassNode *classe,
160 const QList<RelatedClass> &classes,
163 QList<RelatedClass>::ConstIterator r;
164 QMap<QString,Text> classMap;
167 r = classes.constBegin();
168 while (r != classes.constEnd()) {
169 if ((*r).node->access() == Node::Public &&
170 (*r).node->status() != Node::Internal
171 && !(*r).node->doc().isEmpty()) {
173 appendFullName(className, (*r).node, classe, marker);
174 classMap[className.toString().toLower()] = className;
179 QStringList classNames = classMap.keys();
182 foreach (const QString &className, classNames) {
183 text << classMap[className];
184 text << separator(index++, classNames.count());
188 void Generator::appendSortedQmlNames(Text& text,
190 const NodeList& subs,
193 QMap<QString,Text> classMap;
196 for (int i = 0; i < subs.size(); ++i) {
198 if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
199 (base->qmlModuleIdentifier() == subs[i]->qmlModuleIdentifier())) {
200 appendFullName(t, subs[i], base, marker);
201 classMap[t.toString().toLower()] = t;
205 QStringList names = classMap.keys();
208 foreach (const QString &name, names) {
209 text << classMap[name];
210 text << separator(index++, names.count());
215 Creates the file named \a fileName in the output directory.
216 Attaches a QTextStream to the created file, which is written
217 to all over the place using out().
219 void Generator::beginSubPage(const InnerNode* node, const QString& fileName)
221 QString path = outputDir() + QLatin1Char('/');
222 if (!node->outputSubdirectory().isEmpty())
223 path += node->outputSubdirectory() + QLatin1Char('/');
225 QFile* outFile = new QFile(path);
226 if (!outFile->open(QFile::WriteOnly))
227 node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
228 QTextStream* out = new QTextStream(outFile);
231 out->setCodec(outputCodec);
232 outStreamStack.push(out);
233 const_cast<InnerNode*>(node)->setOutputFileName(fileName);
237 Flush the text stream associated with the subpage, and
238 then pop it off the text stream stack and delete it.
239 This terminates output of the subpage.
241 void Generator::endSubPage()
243 outStreamStack.top()->flush();
244 delete outStreamStack.top()->device();
245 delete outStreamStack.pop();
248 void Generator::endText(const Node * /* relative */,
249 CodeMarker * /* marker */)
253 QString Generator::fileBase(const Node *node) const
256 node = node->relates();
257 else if (!node->isInnerNode())
258 node = node->parent();
259 if (node->subType() == Node::QmlPropertyGroup) {
260 node = node->parent();
263 QString base = node->doc().baseName();
267 const Node *p = node;
270 const Node *pp = p->parent();
271 base.prepend(p->name());
272 if (!p->qmlModuleIdentifier().isEmpty())
273 base.prepend(p->qmlModuleIdentifier()+QChar('-'));
275 To avoid file name conflicts in the html directory,
276 we prepend a prefix (by default, "qml-") to the file name of QML
279 if ((p->subType() == Node::QmlClass) ||
280 (p->subType() == Node::QmlBasicType)) {
281 base.prepend(outputPrefix(QLatin1String("QML")));
283 if (!pp || pp->name().isEmpty() || pp->type() == Node::Fake)
285 base.prepend(QLatin1Char('-'));
288 if (node->type() == Node::Fake) {
289 if (node->subType() == Node::Collision) {
290 const NameCollisionNode* ncn = static_cast<const NameCollisionNode*>(node);
291 if (ncn->currentChild())
292 return fileBase(ncn->currentChild());
293 base.prepend("collision-");
295 //Was QDOC2_COMPAT, required for index.html
296 if (base.endsWith(".html"))
297 base.truncate(base.length() - 5);
299 if (node->subType() == Node::QmlModule) {
300 base.prepend("qmlmodule-");
302 if (node->subType() == Node::Module) {
303 base.append("-module");
307 // the code below is effectively equivalent to:
308 // base.replace(QRegExp("[^A-Za-z0-9]+"), " ");
309 // base = base.trimmed();
310 // base.replace(QLatin1Char(' '), QLatin1Char('-'));
311 // base = base.toLower();
312 // as this function accounted for ~8% of total running time
313 // we optimize a bit...
316 // +5 prevents realloc in fileName() below
317 res.reserve(base.size() + 5);
319 for (int i = 0; i != base.size(); ++i) {
320 QChar c = base.at(i);
321 uint u = c.unicode();
322 if (u >= 'A' && u <= 'Z')
324 if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
325 res += QLatin1Char(u);
329 res += QLatin1Char('-');
333 while (res.endsWith(QLatin1Char('-')))
339 If the \a node has a URL, return the URL as the file name.
340 Otherwise, construct the file name from the fileBase() and
341 the fileExtension(), and return the constructed name.
343 QString Generator::fileName(const Node* node) const
345 if (!node->url().isEmpty())
348 QString name = fileBase(node);
349 name += QLatin1Char('.');
350 name += fileExtension();
355 For generating the "New Classes... in x.y" section on the
356 What's New in Qt x.y" page.
358 void Generator::findAllSince(const InnerNode *node)
360 NodeList::const_iterator child = node->childNodes().constBegin();
362 // Traverse the tree, starting at the node supplied.
364 while (child != node->childNodes().constEnd()) {
366 QString sinceString = (*child)->since();
368 if (((*child)->access() != Node::Private) && !sinceString.isEmpty()) {
370 // Insert a new entry into each map for each new since string found.
371 NewSinceMaps::iterator nsmap = newSinceMaps.find(sinceString);
372 if (nsmap == newSinceMaps.end())
373 nsmap = newSinceMaps.insert(sinceString,NodeMultiMap());
375 NewClassMaps::iterator ncmap = newClassMaps.find(sinceString);
376 if (ncmap == newClassMaps.end())
377 ncmap = newClassMaps.insert(sinceString,NodeMap());
379 NewClassMaps::iterator nqcmap = newQmlClassMaps.find(sinceString);
380 if (nqcmap == newQmlClassMaps.end())
381 nqcmap = newQmlClassMaps.insert(sinceString,NodeMap());
383 if ((*child)->type() == Node::Function) {
384 // Insert functions into the general since map.
385 FunctionNode *func = static_cast<FunctionNode *>(*child);
386 if ((func->status() > Node::Obsolete) &&
387 (func->metaness() != FunctionNode::Ctor) &&
388 (func->metaness() != FunctionNode::Dtor)) {
389 nsmap.value().insert(func->name(),(*child));
392 else if ((*child)->url().isEmpty()) {
393 if ((*child)->type() == Node::Class && !(*child)->doc().isEmpty()) {
394 // Insert classes into the since and class maps.
395 QString className = (*child)->name();
396 if ((*child)->parent() &&
397 (*child)->parent()->type() == Node::Namespace &&
398 !(*child)->parent()->name().isEmpty())
399 className = (*child)->parent()->name()+"::"+className;
401 nsmap.value().insert(className,(*child));
402 ncmap.value().insert(className,(*child));
404 else if ((*child)->subType() == Node::QmlClass) {
405 // Insert QML elements into the since and element maps.
406 QString className = (*child)->name();
407 if ((*child)->parent() &&
408 (*child)->parent()->type() == Node::Namespace &&
409 !(*child)->parent()->name().isEmpty())
410 className = (*child)->parent()->name()+"::"+className;
412 nsmap.value().insert(className,(*child));
413 nqcmap.value().insert(className,(*child));
415 else if ((*child)->type() == Node::QmlProperty) {
416 // Insert QML properties into the since map.
417 QString propertyName = (*child)->name();
418 nsmap.value().insert(propertyName,(*child));
422 // Insert external documents into the general since map.
423 QString name = (*child)->name();
424 if ((*child)->parent() &&
425 (*child)->parent()->type() == Node::Namespace &&
426 !(*child)->parent()->name().isEmpty())
427 name = (*child)->parent()->name()+"::"+name;
429 nsmap.value().insert(name,(*child));
432 // Find child nodes with since commands.
433 if ((*child)->isInnerNode()) {
434 findAllSince(static_cast<InnerNode *>(*child));
441 QMap<QString, QString>& Generator::formattingLeftMap()
443 return fmtLeftMaps[format()];
446 QMap<QString, QString>& Generator::formattingRightMap()
448 return fmtRightMaps[format()];
452 Returns the full document location.
454 QString Generator::fullDocumentLocation(const Node *node, bool subdir)
458 if (!node->url().isEmpty())
466 If the output is being sent to subdirectories of the
467 output directory, and if the subdir parameter is set,
468 prepend the subdirectory name + '/' to the result.
471 fdl = node->outputSubdirectory();
473 fdl.append(QLatin1Char('/'));
475 if (node->type() == Node::Namespace) {
477 // The root namespace has no name - check for this before creating
478 // an attribute containing the location of any documentation.
480 if (!node->fileBase().isEmpty())
481 parentName = node->fileBase() + QLatin1Char('.') + currentGenerator()->fileExtension();
485 else if (node->type() == Node::Fake) {
486 if ((node->subType() == Node::QmlClass) ||
487 (node->subType() == Node::QmlBasicType)) {
488 QString fb = node->fileBase();
489 if (fb.startsWith(Generator::outputPrefix(QLatin1String("QML"))))
490 return fb + QLatin1Char('.') + currentGenerator()->fileExtension();
493 if (!node->qmlModuleName().isEmpty()) {
494 mq = node->qmlModuleIdentifier().replace(QChar('.'),QChar('-'));
495 mq = mq.toLower() + QLatin1Char('-');
497 return fdl+ Generator::outputPrefix(QLatin1String("QML")) + mq +
498 node->fileBase() + QLatin1Char('.') + currentGenerator()->fileExtension();
502 parentName = node->fileBase() + QLatin1Char('.') + currentGenerator()->fileExtension();
504 else if (node->fileBase().isEmpty())
507 Node *parentNode = 0;
509 if ((parentNode = node->relates())) {
510 parentName = fullDocumentLocation(node->relates());
512 else if ((parentNode = node->parent())) {
513 if (parentNode->subType() == Node::QmlPropertyGroup) {
514 parentNode = parentNode->parent();
515 parentName = fullDocumentLocation(parentNode);
518 parentName = fullDocumentLocation(node->parent());
521 switch (node->type()) {
523 case Node::Namespace:
524 if (parentNode && !parentNode->name().isEmpty()) {
525 parentName.remove(QLatin1Char('.') + currentGenerator()->fileExtension());
526 parentName += QLatin1Char('-')
527 + node->fileBase().toLower() + QLatin1Char('.') + currentGenerator()->fileExtension();
529 parentName = node->fileBase() + QLatin1Char('.') + currentGenerator()->fileExtension();
535 Functions can be destructors, overloaded, or
536 have associated properties.
538 const FunctionNode *functionNode =
539 static_cast<const FunctionNode *>(node);
541 if (functionNode->metaness() == FunctionNode::Dtor)
542 anchorRef = "#dtor." + functionNode->name().mid(1);
544 else if (functionNode->associatedProperty())
545 return fullDocumentLocation(functionNode->associatedProperty());
547 else if (functionNode->overloadNumber() > 1)
548 anchorRef = QLatin1Char('#') + functionNode->name()
549 + QLatin1Char('-') + QString::number(functionNode->overloadNumber());
551 anchorRef = QLatin1Char('#') + functionNode->name();
555 Use node->name() instead of node->fileBase() as
556 the latter returns the name in lower-case. For
557 HTML anchors, we need to preserve the case.
561 anchorRef = QLatin1Char('#') + node->name() + "-enum";
564 anchorRef = QLatin1Char('#') + node->name() + "-typedef";
567 anchorRef = QLatin1Char('#') + node->name() + "-prop";
569 case Node::QmlProperty:
570 anchorRef = QLatin1Char('#') + node->name() + "-prop";
572 case Node::QmlSignal:
573 anchorRef = QLatin1Char('#') + node->name() + "-signal";
575 case Node::QmlSignalHandler:
576 anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
578 case Node::QmlMethod:
579 anchorRef = QLatin1Char('#') + node->name() + "-method";
582 anchorRef = QLatin1Char('#') + node->name() + "-var";
587 Use node->fileBase() for fake nodes because they are represented
588 by pages whose file names are lower-case.
590 parentName = node->fileBase();
591 parentName.replace(QLatin1Char('/'), QLatin1Char('-')).replace(QLatin1Char('.'), QLatin1Char('-'));
592 parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
599 // Various objects can be compat (deprecated) or obsolete.
600 if (node->type() != Node::Class && node->type() != Node::Namespace) {
601 switch (node->status()) {
603 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
604 "-compat." + currentGenerator()->fileExtension());
607 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
608 "-obsolete." + currentGenerator()->fileExtension());
615 return fdl + parentName.toLower() + anchorRef;
618 QString Generator::fullName(const Node *node,
619 const Node *relative,
620 CodeMarker *marker) const
622 if (node->type() == Node::Fake) {
623 const FakeNode* fn = static_cast<const FakeNode *>(node);
625 // Only print modulename::type on collision pages.
626 if (!fn->qmlModuleIdentifier().isEmpty() && relative != 0 && relative->isCollisionNode())
627 return fn->qmlModuleIdentifier() + "::" + fn->title();
631 else if (node->type() == Node::Class &&
632 !(static_cast<const ClassNode *>(node))->serviceName().isEmpty())
633 return (static_cast<const ClassNode *>(node))->serviceName();
635 return marker->plainFullName(node, relative);
638 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
640 QList<Text> alsoList = node->doc().alsoList();
641 supplementAlsoList(node, alsoList);
643 if (!alsoList.isEmpty()) {
645 text << Atom::ParaLeft
646 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
648 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
650 for (int i = 0; i < alsoList.size(); ++i)
651 text << alsoList.at(i) << separator(i, alsoList.size());
653 text << Atom::ParaRight;
654 generateText(text, node, marker);
658 int Generator::generateAtom(const Atom * /* atom */,
659 const Node * /* relative */,
660 CodeMarker * /* marker */)
665 const Atom *Generator::generateAtomList(const Atom *atom,
666 const Node *relative,
672 if (atom->type() == Atom::FormatIf) {
673 int numAtoms0 = numAtoms;
674 bool rightFormat = canHandleFormat(atom->string());
675 atom = generateAtomList(atom->next(),
678 generate && rightFormat,
683 if (atom->type() == Atom::FormatElse) {
685 atom = generateAtomList(atom->next(),
688 generate && !rightFormat,
694 if (atom->type() == Atom::FormatEndif) {
695 if (generate && numAtoms0 == numAtoms) {
696 relative->location().warning(tr("Output format %1 not handled %2")
697 .arg(format()).arg(outFileName()));
698 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
699 generateAtomList(&unhandledFormatAtom,
708 else if (atom->type() == Atom::FormatElse ||
709 atom->type() == Atom::FormatEndif) {
715 n += generateAtom(atom, relative, marker);
725 void Generator::generateBody(const Node *node, CodeMarker *marker)
729 if (node->type() == Node::Fake) {
730 const FakeNode *fake = static_cast<const FakeNode *>(node);
731 if ((fake->subType() == Node::File) || (fake->subType() == Node::Image)) {
735 if (node->doc().isEmpty()) {
736 if (!quiet && !node->isReimp()) { // ### might be unnecessary
737 node->location().warning(tr("No documentation for '%1'")
738 .arg(marker->plainFullName(node)));
742 if (node->type() == Node::Function) {
743 const FunctionNode *func = static_cast<const FunctionNode *>(node);
744 if (func->reimplementedFrom() != 0)
745 generateReimplementedFrom(func, marker);
748 if (!generateText(node->doc().body(), node, marker)) {
753 if (node->type() == Node::Enum) {
754 const EnumNode *enume = (const EnumNode *) node;
756 QSet<QString> definedItems;
757 QList<EnumItem>::ConstIterator it = enume->items().constBegin();
758 while (it != enume->items().constEnd()) {
759 definedItems.insert((*it).name());
763 QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
764 QSet<QString> allItems = definedItems + documentedItems;
765 if (allItems.count() > definedItems.count() ||
766 allItems.count() > documentedItems.count()) {
767 QSet<QString>::ConstIterator a = allItems.constBegin();
768 while (a != allItems.constEnd()) {
769 if (!definedItems.contains(*a)) {
771 QString best = nearestName(*a, definedItems);
772 if (!best.isEmpty() && !documentedItems.contains(best))
773 details = tr("Maybe you meant '%1'?").arg(best);
775 node->doc().location().warning(
776 tr("No such enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
779 qDebug() << "VOID:" << node->name() << definedItems;
781 else if (!documentedItems.contains(*a)) {
782 node->doc().location().warning(
783 tr("Undocumented enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)));
789 else if (node->type() == Node::Function) {
790 const FunctionNode *func = static_cast<const FunctionNode *>(node);
791 QSet<QString> definedParams;
792 QList<Parameter>::ConstIterator p = func->parameters().constBegin();
793 while (p != func->parameters().constEnd()) {
794 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
795 && func->name() != QLatin1String("operator++")
796 && func->name() != QLatin1String("operator--")) {
797 node->doc().location().warning(tr("Missing parameter name"));
800 definedParams.insert((*p).name());
805 QSet<QString> documentedParams = func->doc().parameterNames();
806 QSet<QString> allParams = definedParams + documentedParams;
807 if (allParams.count() > definedParams.count()
808 || allParams.count() > documentedParams.count()) {
809 QSet<QString>::ConstIterator a = allParams.constBegin();
810 while (a != allParams.constEnd()) {
811 if (!definedParams.contains(*a)) {
813 QString best = nearestName(*a, definedParams);
815 details = tr("Maybe you meant '%1'?").arg(best);
817 node->doc().location().warning(
818 tr("No such parameter '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
821 else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
822 bool needWarning = (func->status() > Node::Obsolete);
823 if (func->overloadNumber() > 1) {
824 FunctionNode *primaryFunc =
825 func->parent()->findFunctionNode(func->name());
827 foreach (const Parameter ¶m,
828 primaryFunc->parameters()) {
829 if (param.name() == *a) {
836 if (needWarning && !func->isReimp())
837 node->doc().location().warning(
838 tr("Undocumented parameter '%1' in %2")
839 .arg(*a).arg(marker->plainFullName(node)));
845 Something like this return value check should
846 be implemented at some point.
848 if (func->status() > Node::Obsolete && func->returnType() == "bool"
849 && func->reimplementedFrom() == 0 && !func->isOverload()) {
850 QString body = func->doc().body().toString();
851 if (!body.contains("return", Qt::CaseInsensitive))
852 node->doc().location().warning(tr("Undocumented return value"));
857 if (node->type() == Node::Fake) {
858 const FakeNode *fake = static_cast<const FakeNode *>(node);
859 if (fake->subType() == Node::Example) {
860 generateExampleFiles(fake, marker);
862 else if (fake->subType() == Node::File) {
865 Doc::quoteFromFile(fake->doc().location(), quoter, fake->name());
866 QString code = quoter.quoteTo(fake->location(), QString(), QString());
867 CodeMarker *codeMarker = CodeMarker::markerForFileName(fake->name());
868 text << Atom(codeMarker->atomType(), code);
869 generateText(text, fake, codeMarker);
874 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
878 void Generator::generateExampleFiles(const FakeNode *fake, CodeMarker *marker)
880 if (fake->childNodes().isEmpty())
882 generateFileList(fake, marker, Node::File, QString("Files:"));
883 generateFileList(fake, marker, Node::Image, QString("Images:"));
886 void Generator::generateFakeNode(FakeNode* /* fake */, CodeMarker* /* marker */)
891 This function is called when the documentation for an
892 example is being formatted. It outputs the list of source
893 files comprising the example, and the list of images used
894 by the example. The images are copied into a subtree of
895 \c{...doc/html/images/used-in-examples/...}
897 void Generator::generateFileList(const FakeNode* fake,
899 Node::SubType subtype,
904 OpenedList openedList(OpenedList::Bullet);
906 text << Atom::ParaLeft << tag << Atom::ParaRight
907 << Atom(Atom::ListLeft, openedList.styleString());
909 foreach (const Node* child, fake->childNodes()) {
910 if (child->subType() == subtype) {
912 QString file = child->name();
913 if (subtype == Node::Image) {
914 if (!file.isEmpty()) {
916 QString userFriendlyFilePath;
917 QString srcPath = Config::findFile(fake->location(),
922 userFriendlyFilePath);
923 userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
925 QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
926 if (!dirInfo.mkpath(imgOutDir))
927 fake->location().fatal(tr("Cannot create output directory '%1'")
930 QString imgOutName = Config::copyFile(fake->location(),
939 text << Atom(Atom::ListItemNumber, openedList.numberString())
940 << Atom(Atom::ListItemLeft, openedList.styleString())
942 << Atom(Atom::Link, file)
943 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
945 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
947 << Atom(Atom::ListItemRight, openedList.styleString());
950 text << Atom(Atom::ListRight, openedList.styleString());
952 generateText(text, fake, marker);
955 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
957 if (!classe->derivedClasses().isEmpty()) {
959 text << Atom::ParaLeft
960 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
962 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
964 appendSortedNames(text, classe, classe->derivedClasses(), marker);
965 text << Atom::ParaRight;
966 generateText(text, classe, marker);
970 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
972 QList<RelatedClass>::ConstIterator r;
975 if (!classe->baseClasses().isEmpty()) {
977 text << Atom::ParaLeft
978 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
980 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
982 r = classe->baseClasses().constBegin();
984 while (r != classe->baseClasses().constEnd()) {
985 text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
986 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
987 << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
988 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
990 if ((*r).access == Node::Protected) {
991 text << " (protected)";
993 else if ((*r).access == Node::Private) {
994 text << " (private)";
996 text << separator(index++, classe->baseClasses().count());
999 text << Atom::ParaRight;
1000 generateText(text, classe, marker);
1005 Recursive writing of HTML files from the root \a node.
1007 \note NameCollisionNodes are skipped here and processed
1008 later. See HtmlGenerator::generateCollisionPages() for
1011 void Generator::generateInnerNode(InnerNode* node)
1013 if (!node->url().isNull())
1016 if (node->type() == Node::Fake) {
1017 FakeNode* fakeNode = static_cast<FakeNode*>(node);
1018 if (fakeNode->subType() == Node::ExternalPage)
1020 if (fakeNode->subType() == Node::Image)
1022 if (fakeNode->subType() == Node::QmlPropertyGroup)
1024 if (fakeNode->subType() == Node::Page) {
1025 if (node->count() > 0)
1026 qDebug("PAGE %s HAS CHILDREN", qPrintable(fakeNode->title()));
1031 Obtain a code marker for the source file.
1033 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
1035 if (node->parent() != 0) {
1037 Skip name collision nodes here and process them
1038 later in generateCollisionPages(). Each one is
1039 appended to a list for later.
1041 if ((node->type() == Node::Fake) && (node->subType() == Node::Collision)) {
1042 NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
1043 collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
1046 beginSubPage(node, fileName(node));
1047 if (node->type() == Node::Namespace || node->type() == Node::Class) {
1048 generateClassLikeNode(node, marker);
1050 else if (node->type() == Node::Fake) {
1051 generateFakeNode(static_cast<FakeNode*>(node), marker);
1057 NodeList::ConstIterator c = node->childNodes().constBegin();
1058 while (c != node->childNodes().constEnd()) {
1059 if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
1060 generateInnerNode((InnerNode*)*c);
1067 Generate a list of maintainers in the output
1069 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
1071 QStringList sl = getMetadataElements(node,"maintainer");
1073 if (!sl.isEmpty()) {
1075 text << Atom::ParaLeft
1076 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1077 << "Maintained by: "
1078 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
1080 for (int i = 0; i < sl.size(); ++i)
1081 text << sl.at(i) << separator(i, sl.size());
1083 text << Atom::ParaRight;
1084 generateText(text, node, marker);
1089 Output the "Inherit by" list for the QML element,
1090 if it is inherited by any other elements.
1092 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
1097 QmlClassNode::subclasses(qcn->name(),subs);
1098 if (!subs.isEmpty()) {
1100 text << Atom::ParaLeft << "Inherited by ";
1101 appendSortedQmlNames(text,qcn,subs,marker);
1102 text << Atom::ParaRight;
1103 generateText(text, qcn, marker);
1110 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
1116 Extract sections of markup text surrounded by \e qmltext
1117 and \e endqmltext and output them.
1119 bool Generator::generateQmlText(const Text& text,
1120 const Node *relative,
1122 const QString& /* qmlName */ )
1124 const Atom* atom = text.firstAtom();
1125 bool result = false;
1128 startText(relative, marker);
1130 if (atom->type() != Atom::QmlText)
1131 atom = atom->next();
1133 atom = atom->next();
1134 while (atom && (atom->type() != Atom::EndQmlText)) {
1135 int n = 1 + generateAtom(atom, relative, marker);
1137 atom = atom->next();
1141 endText(relative, marker);
1147 void Generator::generateReimplementedFrom(const FunctionNode *func,
1150 if (func->reimplementedFrom() != 0) {
1151 const FunctionNode *from = func->reimplementedFrom();
1152 if (from->access() != Node::Private &&
1153 from->parent()->access() != Node::Private) {
1155 text << Atom::ParaLeft << "Reimplemented from ";
1156 QString fullName = from->parent()->name() + "::" + from->name() + "()";
1157 appendFullName(text, from->parent(), fullName, from);
1158 text << "." << Atom::ParaRight;
1159 generateText(text, func, marker);
1164 void Generator::generateSince(const Node *node, CodeMarker *marker)
1166 if (!node->since().isEmpty()) {
1168 text << Atom::ParaLeft
1170 << typeString(node);
1171 if (node->type() == Node::Enum)
1172 text << " was introduced or modified in ";
1174 text << " was introduced in ";
1176 QStringList since = node->since().split(QLatin1Char(' '));
1177 if (since.count() == 1) {
1178 // Handle legacy use of \since <version>.
1179 if (project.isEmpty())
1183 text << " " << since[0];
1185 // Reconstruct the <project> <version> string.
1186 text << " " << since.join(" ");
1189 text << "." << Atom::ParaRight;
1190 generateText(text, node, marker);
1194 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1198 switch (node->status()) {
1199 case Node::Commendable:
1202 case Node::Preliminary:
1203 text << Atom::ParaLeft
1204 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1207 << " is under development and is subject to change."
1208 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1211 case Node::Deprecated:
1212 text << Atom::ParaLeft;
1213 if (node->isInnerNode())
1214 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1215 text << "This " << typeString(node) << " is deprecated.";
1216 if (node->isInnerNode())
1217 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1218 text << Atom::ParaRight;
1220 case Node::Obsolete:
1221 text << Atom::ParaLeft;
1222 if (node->isInnerNode())
1223 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1224 text << "This " << typeString(node) << " is obsolete.";
1225 if (node->isInnerNode())
1226 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1227 text << " It is provided to keep old source code working. "
1228 << "We strongly advise against "
1229 << "using it in new code." << Atom::ParaRight;
1232 // reimplemented in HtmlGenerator subclass
1233 if (node->isInnerNode()) {
1234 text << Atom::ParaLeft
1235 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1238 << " is part of the Qt compatibility layer."
1239 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1240 << " It is provided to keep old source code working. "
1241 << "We strongly advise against using it in new code."
1245 case Node::Internal:
1249 generateText(text, node, marker);
1252 bool Generator::generateText(const Text& text,
1253 const Node *relative,
1256 bool result = false;
1257 if (text.firstAtom() != 0) {
1259 startText(relative, marker);
1260 generateAtomList(text.firstAtom(),
1265 endText(relative, marker);
1271 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1274 Node::ThreadSafeness threadSafeness = node->threadSafeness();
1277 rlink << Atom(Atom::Link,"reentrant")
1278 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1280 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1283 tlink << Atom(Atom::Link,"thread-safe")
1284 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1286 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1288 switch (threadSafeness) {
1289 case Node::UnspecifiedSafeness:
1291 case Node::NonReentrant:
1292 text << Atom::ParaLeft
1293 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1295 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1303 case Node::Reentrant:
1304 case Node::ThreadSafe:
1305 text << Atom::ParaLeft
1306 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1308 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1311 if (node->isInnerNode()) {
1312 const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1313 text << "All functions in this "
1316 if (threadSafeness == Node::ThreadSafe)
1321 bool exceptions = false;
1323 NodeList threadsafe;
1324 NodeList nonreentrant;
1325 NodeList::ConstIterator c = innerNode->childNodes().constBegin();
1326 while (c != innerNode->childNodes().constEnd()) {
1328 if ((*c)->status() != Node::Obsolete){
1329 switch ((*c)->threadSafeness()) {
1330 case Node::Reentrant:
1331 reentrant.append(*c);
1332 if (threadSafeness == Node::ThreadSafe)
1335 case Node::ThreadSafe:
1336 threadsafe.append(*c);
1337 if (threadSafeness == Node::Reentrant)
1340 case Node::NonReentrant:
1341 nonreentrant.append(*c);
1352 else if (threadSafeness == Node::Reentrant) {
1353 if (nonreentrant.isEmpty()) {
1354 if (!threadsafe.isEmpty()) {
1356 appendFullNames(text,threadsafe,innerNode,marker);
1357 singularPlural(text,threadsafe);
1358 text << " also " << tlink << ".";
1364 text << ", except for ";
1365 appendFullNames(text,nonreentrant,innerNode,marker);
1367 singularPlural(text,nonreentrant);
1368 text << " nonreentrant.";
1369 if (!threadsafe.isEmpty()) {
1371 appendFullNames(text,threadsafe,innerNode,marker);
1372 singularPlural(text,threadsafe);
1373 text << " " << tlink << ".";
1377 else { // thread-safe
1378 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1379 text << ", except for ";
1380 if (!reentrant.isEmpty()) {
1381 appendFullNames(text,reentrant,innerNode,marker);
1383 singularPlural(text,reentrant);
1384 text << " only " << rlink;
1385 if (!nonreentrant.isEmpty())
1388 if (!nonreentrant.isEmpty()) {
1389 appendFullNames(text,nonreentrant,innerNode,marker);
1391 singularPlural(text,nonreentrant);
1392 text << " nonreentrant.";
1399 text << "This " << typeString(node) << " is ";
1400 if (threadSafeness == Node::ThreadSafe)
1406 text << Atom::ParaRight;
1408 generateText(text,node,marker);
1412 This function is recursive.
1414 void Generator::generateTree(Tree *tree)
1417 generateInnerNode(tree->root());
1420 Generator *Generator::generatorForFormat(const QString& format)
1422 QList<Generator *>::ConstIterator g = generators.constBegin();
1423 while (g != generators.constEnd()) {
1424 if ((*g)->format() == format)
1432 This function can be called if getLink() returns an empty
1433 string. It tests the \a atom string to see if it is a link
1434 of the form <element> :: <name>, where <element> is a QML
1435 element or component without a module qualifier. If so, it
1436 constructs a link to the <name> clause on the disambiguation
1437 page for <element> and returns that link string. It also
1438 adds the <name> as a target in the NameCollisionNode for
1439 <element>. These clauses are then constructed when the
1440 disambiguation page is actually generated.
1442 QString Generator::getCollisionLink(const Atom* atom)
1445 if (!atom->string().contains("::"))
1447 QStringList path = atom->string().split("::");
1448 NameCollisionNode* ncn = tree_->findCollisionNode(path[0]);
1451 if (atom->next() && atom->next()->next()) {
1452 if (atom->next()->type() == Atom::FormattingLeft &&
1453 atom->next()->next()->type() == Atom::String)
1454 label = atom->next()->next()->string();
1456 ncn->addLinkTarget(path[1],label);
1457 link = fileName(ncn);
1458 link += QLatin1Char('#');
1459 link += Doc::canonicalTitle(path[1]);
1466 Looks up the tag \a t in the map of metadata values for the
1467 current topic in \a inner. If a value for the tag is found,
1468 the value is returned.
1470 \note If \a t is found in the metadata map, it is erased.
1471 i.e. Once you call this function for a particular \a t,
1474 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1477 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1478 QStringMultiMap::iterator i = metaTagMap.find(t);
1479 if (i != metaTagMap.end()) {
1481 metaTagMap.erase(i);
1487 Looks up the tag \a t in the map of metadata values for the
1488 current topic in \a inner. If values for the tag are found,
1489 they are returned in a string list.
1491 \note If \a t is found in the metadata map, all the pairs
1492 having the key \a t are erased. i.e. Once you call this
1493 function for a particular \a t, you consume \a t.
1495 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1498 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1499 s = metaTagMap.values(t);
1501 metaTagMap.remove(t);
1506 Returns a relative path name for an image.
1508 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1510 QString userFriendlyFilePath;
1511 QString filePath = Config::findFile(
1512 relative->doc().location(), imageFiles, imageDirs, fileBase,
1513 imgFileExts[format()], userFriendlyFilePath);
1515 if (filePath.isEmpty())
1518 QString path = Config::copyFile(relative->doc().location(),
1520 userFriendlyFilePath,
1521 outputDir() + QLatin1String("/images"));
1522 QString images = "images";
1524 images.append(QLatin1Char('/'));
1525 return images + path;
1528 QString Generator::indent(int level, const QString& markedCode)
1537 while (i < (int) markedCode.length()) {
1538 if (markedCode.at(i) == QLatin1Char('\n')) {
1543 for (int j = 0; j < level; j++)
1544 t += QLatin1Char(' ');
1548 t += markedCode.at(i++);
1553 void Generator::initialize(const Config &config)
1555 outputFormats = config.getOutputFormats();
1556 if (!outputFormats.isEmpty()) {
1557 outDir_ = config.getOutputDir();
1558 baseDir_ = config.getString(CONFIG_BASEDIR);
1559 if (!baseDir_.isEmpty())
1560 config.location().warning(tr("\"basedir\" specified in config file. "
1561 "All output will be in module directories of the output directory"));
1562 if (outDir_.isEmpty())
1563 config.lastLocation().fatal(tr("No output directory specified in configuration file or on the command line"));
1566 if (dirInfo.exists(outDir_)) {
1567 if (!Config::removeDirContents(outDir_))
1568 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1571 if (!dirInfo.mkpath(outDir_))
1572 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1575 if (!dirInfo.mkdir(outDir_ + "/images"))
1576 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1577 .arg(outDir_ + "/images"));
1578 if (!dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1579 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1580 .arg(outDir_ + "/images/used-in-examples"));
1581 if (!dirInfo.mkdir(outDir_ + "/scripts"))
1582 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1583 .arg(outDir_ + "/scripts"));
1584 if (!dirInfo.mkdir(outDir_ + "/style"))
1585 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1586 .arg(outDir_ + "/style"));
1589 imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1590 imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1591 scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1592 scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1593 styleFiles = config.getCleanPathList(CONFIG_STYLES);
1594 styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1595 exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1596 exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot +
1597 CONFIG_IMAGEEXTENSIONS);
1599 QString imagesDotFileExtensions =
1600 CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1601 QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1602 QSet<QString>::ConstIterator f = formats.constBegin();
1603 while (f != formats.constEnd()) {
1604 imgFileExts[*f] = config.getStringList(imagesDotFileExtensions +
1609 QList<Generator *>::ConstIterator g = generators.constBegin();
1610 while (g != generators.constEnd()) {
1611 if (outputFormats.contains((*g)->format())) {
1612 currentGenerator_ = (*g);
1613 (*g)->initializeGenerator(config);
1614 QStringList extraImages =
1615 config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
1616 QStringList::ConstIterator e = extraImages.constBegin();
1617 while (e != extraImages.constEnd()) {
1618 QString userFriendlyFilePath;
1619 QString filePath = Config::findFile(config.lastLocation(),
1623 imgFileExts[(*g)->format()],
1624 userFriendlyFilePath);
1625 if (!filePath.isEmpty())
1626 Config::copyFile(config.lastLocation(),
1628 userFriendlyFilePath,
1634 // Documentation template handling
1635 QString templateDir = config.getString(
1636 (*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
1638 if (!templateDir.isEmpty()) {
1640 QStringList searchDirs = QStringList() << templateDir;
1641 QStringList scripts =
1642 config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1643 e = scripts.constBegin();
1644 while (e != scripts.constEnd()) {
1645 QString userFriendlyFilePath;
1646 QString filePath = Config::findFile(config.lastLocation(),
1651 userFriendlyFilePath);
1652 if (!filePath.isEmpty())
1653 Config::copyFile(config.lastLocation(),
1655 userFriendlyFilePath,
1661 if (!Config::installDir.isEmpty()) {
1662 searchDirs.append(Config::installDir);
1665 QStringList styles =
1666 config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1667 e = styles.constBegin();
1668 while (e != styles.constEnd()) {
1669 QString userFriendlyFilePath;
1670 QString filePath = Config::findFile(config.lastLocation(),
1675 userFriendlyFilePath);
1676 if (!filePath.isEmpty())
1677 Config::copyFile(config.lastLocation(),
1679 userFriendlyFilePath,
1689 QRegExp secondParamAndAbove("[\2-\7]");
1690 QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1691 QSet<QString>::ConstIterator n = formattingNames.constBegin();
1692 while (n != formattingNames.constEnd()) {
1693 QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1695 QSet<QString> formats = config.subVars(formattingDotName);
1696 QSet<QString>::ConstIterator f = formats.constBegin();
1697 while (f != formats.constEnd()) {
1698 QString def = config.getString(formattingDotName +
1700 if (!def.isEmpty()) {
1701 int numParams = Config::numParams(def);
1702 int numOccs = def.count("\1");
1704 if (numParams != 1) {
1705 config.lastLocation().warning(tr("Formatting '%1' must "
1707 "parameter (found %2)")
1708 .arg(*n).arg(numParams));
1710 else if (numOccs > 1) {
1711 config.lastLocation().fatal(tr("Formatting '%1' must "
1712 "contain exactly one "
1713 "occurrence of '\\1' "
1715 .arg(*n).arg(numOccs));
1718 int paramPos = def.indexOf("\1");
1719 fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1720 fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1728 project = config.getString(CONFIG_PROJECT);
1730 QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1731 if (!prefixes.isEmpty()) {
1732 foreach (QString prefix, prefixes)
1733 outputPrefixes[prefix] = config.getString(
1734 CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1736 outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1739 void Generator::initializeGenerator(const Config & /* config */)
1743 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1745 return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1749 Used for writing to the current output stream. Returns a
1750 reference to the current output stream, which is then used
1751 with the \c {<<} operator for writing.
1753 QTextStream &Generator::out()
1755 return *outStreamStack.top();
1758 QString Generator::outFileName()
1760 return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1763 QString Generator::outputPrefix(const QString &nodeType)
1765 return outputPrefixes[nodeType];
1768 bool Generator::parseArg(const QString& src,
1772 QStringRef* contents,
1776 #define SKIP_CHAR(c) \
1778 qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1779 if (i >= n || src[i] != c) { \
1781 qDebug() << " char '" << c << "' not found"; \
1787 #define SKIP_SPACE \
1788 while (i < n && src[i] == ' ') \
1794 // assume "<@" has been parsed outside
1798 if (tag != QStringRef(&src, i, tag.length())) {
1800 qDebug() << "tag " << tag << " not found at " << i;
1805 qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1810 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1813 // read parameter name
1815 while (i < n && src[i].isLetter())
1817 if (src[i] == '=') {
1819 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1822 // skip parameter name
1824 while (i < n && src[i] != '"')
1826 *par1 = QStringRef(&src, j, i - j);
1831 qDebug() << "no optional parameter found";
1837 // find contents up to closing "</@tag>
1840 if (i + 4 + tag.length() > n)
1844 if (src[i + 1] != '/')
1846 if (src[i + 2] != '@')
1848 if (tag != QStringRef(&src, i + 3, tag.length()))
1850 if (src[i + 3 + tag.length()] != '>')
1855 *contents = QStringRef(&src, j, i - j);
1857 i += tag.length() + 4;
1861 qDebug() << " tag " << tag << " found: pos now: " << i;
1866 QString Generator::plainCode(const QString& markedCode)
1868 QString t = markedCode;
1869 t.replace(tag, QString());
1870 t.replace(quot, QLatin1String("\""));
1871 t.replace(gt, QLatin1String(">"));
1872 t.replace(lt, QLatin1String("<"));
1873 t.replace(amp, QLatin1String("&"));
1877 void Generator::setImageFileExtensions(const QStringList& extensions)
1879 imgFileExts[format()] = extensions;
1882 void Generator::singularPlural(Text& text, const NodeList& nodes)
1884 if (nodes.count() == 1)
1890 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1893 atom = atom->next();
1894 while (atom != 0 && atom->type() != type) {
1896 atom = atom->next();
1901 void Generator::startText(const Node * /* relative */,
1902 CodeMarker * /* marker */)
1906 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1908 if (node->type() == Node::Function) {
1909 const FunctionNode *func = static_cast<const FunctionNode *>(node);
1910 if (func->overloadNumber() == 1) {
1911 QString alternateName;
1912 const FunctionNode *alternateFunc = 0;
1914 if (func->name().startsWith("set") && func->name().size() >= 4) {
1915 alternateName = func->name()[3].toLower();
1916 alternateName += func->name().mid(4);
1917 alternateFunc = func->parent()->findFunctionNode(alternateName);
1919 if (!alternateFunc) {
1920 alternateName = "is" + func->name().mid(3);
1921 alternateFunc = func->parent()->findFunctionNode(alternateName);
1922 if (!alternateFunc) {
1923 alternateName = "has" + func->name().mid(3);
1924 alternateFunc = func->parent()->findFunctionNode(alternateName);
1928 else if (!func->name().isEmpty()) {
1929 alternateName = "set";
1930 alternateName += func->name()[0].toUpper();
1931 alternateName += func->name().mid(1);
1932 alternateFunc = func->parent()->findFunctionNode(alternateName);
1935 if (alternateFunc && alternateFunc->access() != Node::Private) {
1937 for (i = 0; i < alsoList.size(); ++i) {
1938 if (alsoList.at(i).toString().contains(alternateName))
1942 if (i == alsoList.size()) {
1943 alternateName += "()";
1946 also << Atom(Atom::Link, alternateName)
1947 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1949 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1950 alsoList.prepend(also);
1957 void Generator::terminate()
1959 QList<Generator *>::ConstIterator g = generators.constBegin();
1960 while (g != generators.constEnd()) {
1961 if (outputFormats.contains((*g)->format()))
1962 (*g)->terminateGenerator();
1966 fmtLeftMaps.clear();
1967 fmtRightMaps.clear();
1968 imgFileExts.clear();
1972 QmlClassNode::terminate();
1973 ExampleNode::terminate();
1976 void Generator::terminateGenerator()
1981 Trims trailing whitespace off the \a string and returns
1984 QString Generator::trimmedTrailing(const QString& string)
1986 QString trimmed = string;
1987 while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1988 trimmed.truncate(trimmed.length() - 1);
1992 QString Generator::typeString(const Node *node)
1994 switch (node->type()) {
1995 case Node::Namespace:
2001 switch (node->subType()) {
2002 case Node::QmlClass:
2004 case Node::QmlPropertyGroup:
2005 return "property group";
2006 case Node::QmlBasicType:
2009 return "documentation";
2016 case Node::Function:
2018 case Node::Property:
2021 return "documentation";
2025 void Generator::unknownAtom(const Atom *atom)
2027 Location::internalError(tr("unknown atom type '%1' in %2 generator")
2028 .arg(atom->typeString()).arg(format()));