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 // Removed for QTBUG-22870
626 // Unremoved by mws 30/03/12
627 if (!fn->qmlModuleIdentifier().isEmpty())
628 return fn->qmlModuleIdentifier() + "::" + fn->title();
632 else if (node->type() == Node::Class &&
633 !(static_cast<const ClassNode *>(node))->serviceName().isEmpty())
634 return (static_cast<const ClassNode *>(node))->serviceName();
636 return marker->plainFullName(node, relative);
639 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
641 QList<Text> alsoList = node->doc().alsoList();
642 supplementAlsoList(node, alsoList);
644 if (!alsoList.isEmpty()) {
646 text << Atom::ParaLeft
647 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
649 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
651 for (int i = 0; i < alsoList.size(); ++i)
652 text << alsoList.at(i) << separator(i, alsoList.size());
654 text << Atom::ParaRight;
655 generateText(text, node, marker);
659 int Generator::generateAtom(const Atom * /* atom */,
660 const Node * /* relative */,
661 CodeMarker * /* marker */)
666 const Atom *Generator::generateAtomList(const Atom *atom,
667 const Node *relative,
673 if (atom->type() == Atom::FormatIf) {
674 int numAtoms0 = numAtoms;
675 bool rightFormat = canHandleFormat(atom->string());
676 atom = generateAtomList(atom->next(),
679 generate && rightFormat,
684 if (atom->type() == Atom::FormatElse) {
686 atom = generateAtomList(atom->next(),
689 generate && !rightFormat,
695 if (atom->type() == Atom::FormatEndif) {
696 if (generate && numAtoms0 == numAtoms) {
697 relative->location().warning(tr("Output format %1 not handled %2")
698 .arg(format()).arg(outFileName()));
699 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
700 generateAtomList(&unhandledFormatAtom,
709 else if (atom->type() == Atom::FormatElse ||
710 atom->type() == Atom::FormatEndif) {
716 n += generateAtom(atom, relative, marker);
726 void Generator::generateBody(const Node *node, CodeMarker *marker)
730 if (node->type() == Node::Fake) {
731 const FakeNode *fake = static_cast<const FakeNode *>(node);
732 if ((fake->subType() == Node::File) || (fake->subType() == Node::Image)) {
736 if (node->doc().isEmpty()) {
737 if (!quiet && !node->isReimp()) { // ### might be unnecessary
738 node->location().warning(tr("No documentation for '%1'")
739 .arg(marker->plainFullName(node)));
743 if (node->type() == Node::Function) {
744 const FunctionNode *func = static_cast<const FunctionNode *>(node);
745 if (func->reimplementedFrom() != 0)
746 generateReimplementedFrom(func, marker);
749 if (!generateText(node->doc().body(), node, marker)) {
754 if (node->type() == Node::Enum) {
755 const EnumNode *enume = (const EnumNode *) node;
757 QSet<QString> definedItems;
758 QList<EnumItem>::ConstIterator it = enume->items().constBegin();
759 while (it != enume->items().constEnd()) {
760 definedItems.insert((*it).name());
764 QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
765 QSet<QString> allItems = definedItems + documentedItems;
766 if (allItems.count() > definedItems.count() ||
767 allItems.count() > documentedItems.count()) {
768 QSet<QString>::ConstIterator a = allItems.constBegin();
769 while (a != allItems.constEnd()) {
770 if (!definedItems.contains(*a)) {
772 QString best = nearestName(*a, definedItems);
773 if (!best.isEmpty() && !documentedItems.contains(best))
774 details = tr("Maybe you meant '%1'?").arg(best);
776 node->doc().location().warning(
777 tr("No such enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
780 qDebug() << "VOID:" << node->name() << definedItems;
782 else if (!documentedItems.contains(*a)) {
783 node->doc().location().warning(
784 tr("Undocumented enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)));
790 else if (node->type() == Node::Function) {
791 const FunctionNode *func = static_cast<const FunctionNode *>(node);
792 QSet<QString> definedParams;
793 QList<Parameter>::ConstIterator p = func->parameters().constBegin();
794 while (p != func->parameters().constEnd()) {
795 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
796 && func->name() != QLatin1String("operator++")
797 && func->name() != QLatin1String("operator--")) {
798 node->doc().location().warning(tr("Missing parameter name"));
801 definedParams.insert((*p).name());
806 QSet<QString> documentedParams = func->doc().parameterNames();
807 QSet<QString> allParams = definedParams + documentedParams;
808 if (allParams.count() > definedParams.count()
809 || allParams.count() > documentedParams.count()) {
810 QSet<QString>::ConstIterator a = allParams.constBegin();
811 while (a != allParams.constEnd()) {
812 if (!definedParams.contains(*a)) {
814 QString best = nearestName(*a, definedParams);
816 details = tr("Maybe you meant '%1'?").arg(best);
818 node->doc().location().warning(
819 tr("No such parameter '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
822 else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
823 bool needWarning = (func->status() > Node::Obsolete);
824 if (func->overloadNumber() > 1) {
825 FunctionNode *primaryFunc =
826 func->parent()->findFunctionNode(func->name());
828 foreach (const Parameter ¶m,
829 primaryFunc->parameters()) {
830 if (param.name() == *a) {
837 if (needWarning && !func->isReimp())
838 node->doc().location().warning(
839 tr("Undocumented parameter '%1' in %2")
840 .arg(*a).arg(marker->plainFullName(node)));
846 Something like this return value check should
847 be implemented at some point.
849 if (func->status() > Node::Obsolete && func->returnType() == "bool"
850 && func->reimplementedFrom() == 0 && !func->isOverload()) {
851 QString body = func->doc().body().toString();
852 if (!body.contains("return", Qt::CaseInsensitive))
853 node->doc().location().warning(tr("Undocumented return value"));
858 if (node->type() == Node::Fake) {
859 const FakeNode *fake = static_cast<const FakeNode *>(node);
860 if (fake->subType() == Node::Example) {
861 generateExampleFiles(fake, marker);
863 else if (fake->subType() == Node::File) {
866 Doc::quoteFromFile(fake->doc().location(), quoter, fake->name());
867 QString code = quoter.quoteTo(fake->location(), QString(), QString());
868 CodeMarker *codeMarker = CodeMarker::markerForFileName(fake->name());
869 text << Atom(codeMarker->atomType(), code);
870 generateText(text, fake, codeMarker);
875 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
879 void Generator::generateExampleFiles(const FakeNode *fake, CodeMarker *marker)
881 if (fake->childNodes().isEmpty())
883 generateFileList(fake, marker, Node::File, QString("Files:"));
884 generateFileList(fake, marker, Node::Image, QString("Images:"));
887 void Generator::generateFakeNode(FakeNode* /* fake */, CodeMarker* /* marker */)
892 This function is called when the documentation for an
893 example is being formatted. It outputs the list of source
894 files comprising the example, and the list of images used
895 by the example. The images are copied into a subtree of
896 \c{...doc/html/images/used-in-examples/...}
898 void Generator::generateFileList(const FakeNode* fake,
900 Node::SubType subtype,
905 OpenedList openedList(OpenedList::Bullet);
907 text << Atom::ParaLeft << tag << Atom::ParaRight
908 << Atom(Atom::ListLeft, openedList.styleString());
910 foreach (const Node* child, fake->childNodes()) {
911 if (child->subType() == subtype) {
913 QString file = child->name();
914 if (subtype == Node::Image) {
915 if (!file.isEmpty()) {
917 QString userFriendlyFilePath;
918 QString srcPath = Config::findFile(fake->location(),
923 userFriendlyFilePath);
924 userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
926 QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
927 if (!dirInfo.mkpath(imgOutDir))
928 fake->location().fatal(tr("Cannot create output directory '%1'")
931 QString imgOutName = Config::copyFile(fake->location(),
940 text << Atom(Atom::ListItemNumber, openedList.numberString())
941 << Atom(Atom::ListItemLeft, openedList.styleString())
943 << Atom(Atom::Link, file)
944 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
946 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
948 << Atom(Atom::ListItemRight, openedList.styleString());
951 text << Atom(Atom::ListRight, openedList.styleString());
953 generateText(text, fake, marker);
956 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
958 if (!classe->derivedClasses().isEmpty()) {
960 text << Atom::ParaLeft
961 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
963 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
965 appendSortedNames(text, classe, classe->derivedClasses(), marker);
966 text << Atom::ParaRight;
967 generateText(text, classe, marker);
971 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
973 QList<RelatedClass>::ConstIterator r;
976 if (!classe->baseClasses().isEmpty()) {
978 text << Atom::ParaLeft
979 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
981 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
983 r = classe->baseClasses().constBegin();
985 while (r != classe->baseClasses().constEnd()) {
986 text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
987 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
988 << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
989 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
991 if ((*r).access == Node::Protected) {
992 text << " (protected)";
994 else if ((*r).access == Node::Private) {
995 text << " (private)";
997 text << separator(index++, classe->baseClasses().count());
1000 text << Atom::ParaRight;
1001 generateText(text, classe, marker);
1006 Recursive writing of HTML files from the root \a node.
1008 \note NameCollisionNodes are skipped here and processed
1009 later. See HtmlGenerator::generateCollisionPages() for
1012 void Generator::generateInnerNode(InnerNode* node)
1014 if (!node->url().isNull())
1017 if (node->type() == Node::Fake) {
1018 FakeNode* fakeNode = static_cast<FakeNode*>(node);
1019 if (fakeNode->subType() == Node::ExternalPage)
1021 if (fakeNode->subType() == Node::Image)
1023 if (fakeNode->subType() == Node::QmlPropertyGroup)
1025 if (fakeNode->subType() == Node::Page) {
1026 if (node->count() > 0)
1027 qDebug("PAGE %s HAS CHILDREN", qPrintable(fakeNode->title()));
1032 Obtain a code marker for the source file.
1034 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
1036 if (node->parent() != 0) {
1038 Skip name collision nodes here and process them
1039 later in generateCollisionPages(). Each one is
1040 appended to a list for later.
1042 if ((node->type() == Node::Fake) && (node->subType() == Node::Collision)) {
1043 NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
1044 collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
1047 beginSubPage(node, fileName(node));
1048 if (node->type() == Node::Namespace || node->type() == Node::Class) {
1049 generateClassLikeNode(node, marker);
1051 else if (node->type() == Node::Fake) {
1052 generateFakeNode(static_cast<FakeNode*>(node), marker);
1058 NodeList::ConstIterator c = node->childNodes().constBegin();
1059 while (c != node->childNodes().constEnd()) {
1060 if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
1061 generateInnerNode((InnerNode*)*c);
1068 Generate a list of maintainers in the output
1070 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
1072 QStringList sl = getMetadataElements(node,"maintainer");
1074 if (!sl.isEmpty()) {
1076 text << Atom::ParaLeft
1077 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1078 << "Maintained by: "
1079 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
1081 for (int i = 0; i < sl.size(); ++i)
1082 text << sl.at(i) << separator(i, sl.size());
1084 text << Atom::ParaRight;
1085 generateText(text, node, marker);
1090 Output the "Inherit by" list for the QML element,
1091 if it is inherited by any other elements.
1093 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
1098 QmlClassNode::subclasses(qcn->name(),subs);
1099 if (!subs.isEmpty()) {
1101 text << Atom::ParaLeft << "Inherited by ";
1102 appendSortedQmlNames(text,qcn,subs,marker);
1103 text << Atom::ParaRight;
1104 generateText(text, qcn, marker);
1111 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
1117 Extract sections of markup text surrounded by \e qmltext
1118 and \e endqmltext and output them.
1120 bool Generator::generateQmlText(const Text& text,
1121 const Node *relative,
1123 const QString& /* qmlName */ )
1125 const Atom* atom = text.firstAtom();
1126 bool result = false;
1129 startText(relative, marker);
1131 if (atom->type() != Atom::QmlText)
1132 atom = atom->next();
1134 atom = atom->next();
1135 while (atom && (atom->type() != Atom::EndQmlText)) {
1136 int n = 1 + generateAtom(atom, relative, marker);
1138 atom = atom->next();
1142 endText(relative, marker);
1148 void Generator::generateReimplementedFrom(const FunctionNode *func,
1151 if (func->reimplementedFrom() != 0) {
1152 const FunctionNode *from = func->reimplementedFrom();
1153 if (from->access() != Node::Private &&
1154 from->parent()->access() != Node::Private) {
1156 text << Atom::ParaLeft << "Reimplemented from ";
1157 QString fullName = from->parent()->name() + "::" + from->name() + "()";
1158 appendFullName(text, from->parent(), fullName, from);
1159 text << "." << Atom::ParaRight;
1160 generateText(text, func, marker);
1165 void Generator::generateSince(const Node *node, CodeMarker *marker)
1167 if (!node->since().isEmpty()) {
1169 text << Atom::ParaLeft
1171 << typeString(node);
1172 if (node->type() == Node::Enum)
1173 text << " was introduced or modified in ";
1175 text << " was introduced in ";
1177 QStringList since = node->since().split(QLatin1Char(' '));
1178 if (since.count() == 1) {
1179 // Handle legacy use of \since <version>.
1180 if (project.isEmpty())
1184 text << " " << since[0];
1186 // Reconstruct the <project> <version> string.
1187 text << " " << since.join(" ");
1190 text << "." << Atom::ParaRight;
1191 generateText(text, node, marker);
1195 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1199 switch (node->status()) {
1200 case Node::Commendable:
1203 case Node::Preliminary:
1204 text << Atom::ParaLeft
1205 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1208 << " is under development and is subject to change."
1209 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1212 case Node::Deprecated:
1213 text << Atom::ParaLeft;
1214 if (node->isInnerNode())
1215 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1216 text << "This " << typeString(node) << " is deprecated.";
1217 if (node->isInnerNode())
1218 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1219 text << Atom::ParaRight;
1221 case Node::Obsolete:
1222 text << Atom::ParaLeft;
1223 if (node->isInnerNode())
1224 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1225 text << "This " << typeString(node) << " is obsolete.";
1226 if (node->isInnerNode())
1227 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1228 text << " It is provided to keep old source code working. "
1229 << "We strongly advise against "
1230 << "using it in new code." << Atom::ParaRight;
1233 // reimplemented in HtmlGenerator subclass
1234 if (node->isInnerNode()) {
1235 text << Atom::ParaLeft
1236 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1239 << " is part of the Qt compatibility layer."
1240 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1241 << " It is provided to keep old source code working. "
1242 << "We strongly advise against using it in new code."
1246 case Node::Internal:
1250 generateText(text, node, marker);
1253 bool Generator::generateText(const Text& text,
1254 const Node *relative,
1257 bool result = false;
1258 if (text.firstAtom() != 0) {
1260 startText(relative, marker);
1261 generateAtomList(text.firstAtom(),
1266 endText(relative, marker);
1272 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1275 Node::ThreadSafeness threadSafeness = node->threadSafeness();
1278 rlink << Atom(Atom::Link,"reentrant")
1279 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1281 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1284 tlink << Atom(Atom::Link,"thread-safe")
1285 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1287 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1289 switch (threadSafeness) {
1290 case Node::UnspecifiedSafeness:
1292 case Node::NonReentrant:
1293 text << Atom::ParaLeft
1294 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1296 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1304 case Node::Reentrant:
1305 case Node::ThreadSafe:
1306 text << Atom::ParaLeft
1307 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1309 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1312 if (node->isInnerNode()) {
1313 const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1314 text << "All functions in this "
1317 if (threadSafeness == Node::ThreadSafe)
1322 bool exceptions = false;
1324 NodeList threadsafe;
1325 NodeList nonreentrant;
1326 NodeList::ConstIterator c = innerNode->childNodes().constBegin();
1327 while (c != innerNode->childNodes().constEnd()) {
1329 if ((*c)->status() != Node::Obsolete){
1330 switch ((*c)->threadSafeness()) {
1331 case Node::Reentrant:
1332 reentrant.append(*c);
1333 if (threadSafeness == Node::ThreadSafe)
1336 case Node::ThreadSafe:
1337 threadsafe.append(*c);
1338 if (threadSafeness == Node::Reentrant)
1341 case Node::NonReentrant:
1342 nonreentrant.append(*c);
1353 else if (threadSafeness == Node::Reentrant) {
1354 if (nonreentrant.isEmpty()) {
1355 if (!threadsafe.isEmpty()) {
1357 appendFullNames(text,threadsafe,innerNode,marker);
1358 singularPlural(text,threadsafe);
1359 text << " also " << tlink << ".";
1365 text << ", except for ";
1366 appendFullNames(text,nonreentrant,innerNode,marker);
1368 singularPlural(text,nonreentrant);
1369 text << " nonreentrant.";
1370 if (!threadsafe.isEmpty()) {
1372 appendFullNames(text,threadsafe,innerNode,marker);
1373 singularPlural(text,threadsafe);
1374 text << " " << tlink << ".";
1378 else { // thread-safe
1379 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1380 text << ", except for ";
1381 if (!reentrant.isEmpty()) {
1382 appendFullNames(text,reentrant,innerNode,marker);
1384 singularPlural(text,reentrant);
1385 text << " only " << rlink;
1386 if (!nonreentrant.isEmpty())
1389 if (!nonreentrant.isEmpty()) {
1390 appendFullNames(text,nonreentrant,innerNode,marker);
1392 singularPlural(text,nonreentrant);
1393 text << " nonreentrant.";
1400 text << "This " << typeString(node) << " is ";
1401 if (threadSafeness == Node::ThreadSafe)
1407 text << Atom::ParaRight;
1409 generateText(text,node,marker);
1413 This function is recursive.
1415 void Generator::generateTree(Tree *tree)
1418 generateInnerNode(tree->root());
1421 Generator *Generator::generatorForFormat(const QString& format)
1423 QList<Generator *>::ConstIterator g = generators.constBegin();
1424 while (g != generators.constEnd()) {
1425 if ((*g)->format() == format)
1433 This function can be called if getLink() returns an empty
1434 string. It tests the \a atom string to see if it is a link
1435 of the form <element> :: <name>, where <element> is a QML
1436 element or component without a module qualifier. If so, it
1437 constructs a link to the <name> clause on the disambiguation
1438 page for <element> and returns that link string. It also
1439 adds the <name> as a target in the NameCollisionNode for
1440 <element>. These clauses are then constructed when the
1441 disambiguation page is actually generated.
1443 QString Generator::getCollisionLink(const Atom* atom)
1446 if (!atom->string().contains("::"))
1448 QStringList path = atom->string().split("::");
1449 NameCollisionNode* ncn = tree_->findCollisionNode(path[0]);
1452 if (atom->next() && atom->next()->next()) {
1453 if (atom->next()->type() == Atom::FormattingLeft &&
1454 atom->next()->next()->type() == Atom::String)
1455 label = atom->next()->next()->string();
1457 ncn->addLinkTarget(path[1],label);
1458 link = fileName(ncn);
1459 link += QLatin1Char('#');
1460 link += Doc::canonicalTitle(path[1]);
1467 Looks up the tag \a t in the map of metadata values for the
1468 current topic in \a inner. If a value for the tag is found,
1469 the value is returned.
1471 \note If \a t is found in the metadata map, it is erased.
1472 i.e. Once you call this function for a particular \a t,
1475 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1478 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1479 QStringMultiMap::iterator i = metaTagMap.find(t);
1480 if (i != metaTagMap.end()) {
1482 metaTagMap.erase(i);
1488 Looks up the tag \a t in the map of metadata values for the
1489 current topic in \a inner. If values for the tag are found,
1490 they are returned in a string list.
1492 \note If \a t is found in the metadata map, all the pairs
1493 having the key \a t are erased. i.e. Once you call this
1494 function for a particular \a t, you consume \a t.
1496 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1499 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1500 s = metaTagMap.values(t);
1502 metaTagMap.remove(t);
1507 Returns a relative path name for an image.
1509 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1511 QString userFriendlyFilePath;
1512 QString filePath = Config::findFile(
1513 relative->doc().location(), imageFiles, imageDirs, fileBase,
1514 imgFileExts[format()], userFriendlyFilePath);
1516 if (filePath.isEmpty())
1519 QString path = Config::copyFile(relative->doc().location(),
1521 userFriendlyFilePath,
1522 outputDir() + QLatin1String("/images"));
1523 QString images = "images";
1525 images.append(QLatin1Char('/'));
1526 return images + path;
1529 QString Generator::indent(int level, const QString& markedCode)
1538 while (i < (int) markedCode.length()) {
1539 if (markedCode.at(i) == QLatin1Char('\n')) {
1544 for (int j = 0; j < level; j++)
1545 t += QLatin1Char(' ');
1549 t += markedCode.at(i++);
1554 void Generator::initialize(const Config &config)
1556 outputFormats = config.getOutputFormats();
1557 if (!outputFormats.isEmpty()) {
1558 outDir_ = config.getOutputDir();
1559 baseDir_ = config.getString(CONFIG_BASEDIR);
1560 if (!baseDir_.isEmpty())
1561 config.location().warning(tr("\"basedir\" specified in config file. "
1562 "All output will be in module directories of the output directory"));
1563 if (outDir_.isEmpty())
1564 config.lastLocation().fatal(tr("No output directory specified in configuration file or on the command line"));
1567 if (dirInfo.exists(outDir_)) {
1568 if (!Config::removeDirContents(outDir_))
1569 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1572 if (!dirInfo.mkpath(outDir_))
1573 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1576 if (!dirInfo.mkdir(outDir_ + "/images"))
1577 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1578 .arg(outDir_ + "/images"));
1579 if (!dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1580 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1581 .arg(outDir_ + "/images/used-in-examples"));
1582 if (!dirInfo.mkdir(outDir_ + "/scripts"))
1583 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1584 .arg(outDir_ + "/scripts"));
1585 if (!dirInfo.mkdir(outDir_ + "/style"))
1586 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1587 .arg(outDir_ + "/style"));
1590 imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1591 imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1592 scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1593 scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1594 styleFiles = config.getCleanPathList(CONFIG_STYLES);
1595 styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1596 exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1597 exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot +
1598 CONFIG_IMAGEEXTENSIONS);
1600 QString imagesDotFileExtensions =
1601 CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1602 QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1603 QSet<QString>::ConstIterator f = formats.constBegin();
1604 while (f != formats.constEnd()) {
1605 imgFileExts[*f] = config.getStringList(imagesDotFileExtensions +
1610 QList<Generator *>::ConstIterator g = generators.constBegin();
1611 while (g != generators.constEnd()) {
1612 if (outputFormats.contains((*g)->format())) {
1613 currentGenerator_ = (*g);
1614 (*g)->initializeGenerator(config);
1615 QStringList extraImages =
1616 config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
1617 QStringList::ConstIterator e = extraImages.constBegin();
1618 while (e != extraImages.constEnd()) {
1619 QString userFriendlyFilePath;
1620 QString filePath = Config::findFile(config.lastLocation(),
1624 imgFileExts[(*g)->format()],
1625 userFriendlyFilePath);
1626 if (!filePath.isEmpty())
1627 Config::copyFile(config.lastLocation(),
1629 userFriendlyFilePath,
1635 // Documentation template handling
1636 QString templateDir = config.getString(
1637 (*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
1639 if (!templateDir.isEmpty()) {
1641 QStringList searchDirs = QStringList() << templateDir;
1642 QStringList scripts =
1643 config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1644 e = scripts.constBegin();
1645 while (e != scripts.constEnd()) {
1646 QString userFriendlyFilePath;
1647 QString filePath = Config::findFile(config.lastLocation(),
1652 userFriendlyFilePath);
1653 if (!filePath.isEmpty())
1654 Config::copyFile(config.lastLocation(),
1656 userFriendlyFilePath,
1662 QStringList styles =
1663 config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1664 e = styles.constBegin();
1665 while (e != styles.constEnd()) {
1666 QString userFriendlyFilePath;
1667 QString filePath = Config::findFile(config.lastLocation(),
1672 userFriendlyFilePath);
1673 if (!filePath.isEmpty())
1674 Config::copyFile(config.lastLocation(),
1676 userFriendlyFilePath,
1686 QRegExp secondParamAndAbove("[\2-\7]");
1687 QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1688 QSet<QString>::ConstIterator n = formattingNames.constBegin();
1689 while (n != formattingNames.constEnd()) {
1690 QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1692 QSet<QString> formats = config.subVars(formattingDotName);
1693 QSet<QString>::ConstIterator f = formats.constBegin();
1694 while (f != formats.constEnd()) {
1695 QString def = config.getString(formattingDotName +
1697 if (!def.isEmpty()) {
1698 int numParams = Config::numParams(def);
1699 int numOccs = def.count("\1");
1701 if (numParams != 1) {
1702 config.lastLocation().warning(tr("Formatting '%1' must "
1704 "parameter (found %2)")
1705 .arg(*n).arg(numParams));
1707 else if (numOccs > 1) {
1708 config.lastLocation().fatal(tr("Formatting '%1' must "
1709 "contain exactly one "
1710 "occurrence of '\\1' "
1712 .arg(*n).arg(numOccs));
1715 int paramPos = def.indexOf("\1");
1716 fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1717 fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1725 project = config.getString(CONFIG_PROJECT);
1727 QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1728 if (!prefixes.isEmpty()) {
1729 foreach (QString prefix, prefixes)
1730 outputPrefixes[prefix] = config.getString(
1731 CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1733 outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1736 void Generator::initializeGenerator(const Config & /* config */)
1740 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1742 return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1746 Used for writing to the current output stream. Returns a
1747 reference to the current output stream, which is then used
1748 with the \c {<<} operator for writing.
1750 QTextStream &Generator::out()
1752 return *outStreamStack.top();
1755 QString Generator::outFileName()
1757 return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1760 QString Generator::outputPrefix(const QString &nodeType)
1762 return outputPrefixes[nodeType];
1765 bool Generator::parseArg(const QString& src,
1769 QStringRef* contents,
1773 #define SKIP_CHAR(c) \
1775 qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1776 if (i >= n || src[i] != c) { \
1778 qDebug() << " char '" << c << "' not found"; \
1784 #define SKIP_SPACE \
1785 while (i < n && src[i] == ' ') \
1791 // assume "<@" has been parsed outside
1795 if (tag != QStringRef(&src, i, tag.length())) {
1797 qDebug() << "tag " << tag << " not found at " << i;
1802 qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1807 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1810 // read parameter name
1812 while (i < n && src[i].isLetter())
1814 if (src[i] == '=') {
1816 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1819 // skip parameter name
1821 while (i < n && src[i] != '"')
1823 *par1 = QStringRef(&src, j, i - j);
1828 qDebug() << "no optional parameter found";
1834 // find contents up to closing "</@tag>
1837 if (i + 4 + tag.length() > n)
1841 if (src[i + 1] != '/')
1843 if (src[i + 2] != '@')
1845 if (tag != QStringRef(&src, i + 3, tag.length()))
1847 if (src[i + 3 + tag.length()] != '>')
1852 *contents = QStringRef(&src, j, i - j);
1854 i += tag.length() + 4;
1858 qDebug() << " tag " << tag << " found: pos now: " << i;
1863 QString Generator::plainCode(const QString& markedCode)
1865 QString t = markedCode;
1866 t.replace(tag, QString());
1867 t.replace(quot, QLatin1String("\""));
1868 t.replace(gt, QLatin1String(">"));
1869 t.replace(lt, QLatin1String("<"));
1870 t.replace(amp, QLatin1String("&"));
1874 void Generator::setImageFileExtensions(const QStringList& extensions)
1876 imgFileExts[format()] = extensions;
1879 void Generator::singularPlural(Text& text, const NodeList& nodes)
1881 if (nodes.count() == 1)
1887 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1890 atom = atom->next();
1891 while (atom != 0 && atom->type() != type) {
1893 atom = atom->next();
1898 void Generator::startText(const Node * /* relative */,
1899 CodeMarker * /* marker */)
1903 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1905 if (node->type() == Node::Function) {
1906 const FunctionNode *func = static_cast<const FunctionNode *>(node);
1907 if (func->overloadNumber() == 1) {
1908 QString alternateName;
1909 const FunctionNode *alternateFunc = 0;
1911 if (func->name().startsWith("set") && func->name().size() >= 4) {
1912 alternateName = func->name()[3].toLower();
1913 alternateName += func->name().mid(4);
1914 alternateFunc = func->parent()->findFunctionNode(alternateName);
1916 if (!alternateFunc) {
1917 alternateName = "is" + func->name().mid(3);
1918 alternateFunc = func->parent()->findFunctionNode(alternateName);
1919 if (!alternateFunc) {
1920 alternateName = "has" + func->name().mid(3);
1921 alternateFunc = func->parent()->findFunctionNode(alternateName);
1925 else if (!func->name().isEmpty()) {
1926 alternateName = "set";
1927 alternateName += func->name()[0].toUpper();
1928 alternateName += func->name().mid(1);
1929 alternateFunc = func->parent()->findFunctionNode(alternateName);
1932 if (alternateFunc && alternateFunc->access() != Node::Private) {
1934 for (i = 0; i < alsoList.size(); ++i) {
1935 if (alsoList.at(i).toString().contains(alternateName))
1939 if (i == alsoList.size()) {
1940 alternateName += "()";
1943 also << Atom(Atom::Link, alternateName)
1944 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1946 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1947 alsoList.prepend(also);
1954 void Generator::terminate()
1956 QList<Generator *>::ConstIterator g = generators.constBegin();
1957 while (g != generators.constEnd()) {
1958 if (outputFormats.contains((*g)->format()))
1959 (*g)->terminateGenerator();
1963 fmtLeftMaps.clear();
1964 fmtRightMaps.clear();
1965 imgFileExts.clear();
1969 QmlClassNode::terminate();
1970 ExampleNode::terminate();
1973 void Generator::terminateGenerator()
1978 Trims trailing whitespace off the \a string and returns
1981 QString Generator::trimmedTrailing(const QString& string)
1983 QString trimmed = string;
1984 while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1985 trimmed.truncate(trimmed.length() - 1);
1989 QString Generator::typeString(const Node *node)
1991 switch (node->type()) {
1992 case Node::Namespace:
1998 switch (node->subType()) {
1999 case Node::QmlClass:
2001 case Node::QmlPropertyGroup:
2002 return "property group";
2003 case Node::QmlBasicType:
2006 return "documentation";
2013 case Node::Function:
2015 case Node::Property:
2018 return "documentation";
2022 void Generator::unknownAtom(const Atom *atom)
2024 Location::internalError(tr("unknown atom type '%1' in %2 generator")
2025 .arg(atom->typeString()).arg(format()));