1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt for Python.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "qtdocgenerator.h"
30 #include "ctypenames.h"
31 #include <abstractmetalang.h>
32 #include <messages.h>
33 #include <propertyspec.h>
34 #include <reporthandler.h>
35 #include <typesystem.h>
36 #include <qtdocparser.h>
37 #include <doxygenparser.h>
38 #include <typedatabase.h>
39 #include <algorithm>
40 #include <QtCore/QStack>
41 #include <QtCore/QRegularExpression>
42 #include <QtCore/QTextStream>
43 #include <QtCore/QXmlStreamReader>
44 #include <QtCore/QFile>
45 #include <QtCore/QDir>
46 #include <fileout.h>
47 #include <limits>
48 
49 static Indentor INDENT;
50 
additionalDocumentationOption()51 static inline QString additionalDocumentationOption() { return QStringLiteral("additional-documentation"); }
52 
nameAttribute()53 static inline QString nameAttribute() { return QStringLiteral("name"); }
titleAttribute()54 static inline QString titleAttribute() { return QStringLiteral("title"); }
fullTitleAttribute()55 static inline QString fullTitleAttribute() { return QStringLiteral("fulltitle"); }
briefAttribute()56 static inline QString briefAttribute() { return QStringLiteral("brief"); }
briefStartElement()57 static inline QString briefStartElement() { return QStringLiteral("<brief>"); }
briefEndElement()58 static inline QString briefEndElement() { return QStringLiteral("</brief>"); }
59 
none()60 static inline QString none() { return QStringLiteral("None"); }
61 
stripPythonQualifiers(QString * s)62 static void stripPythonQualifiers(QString *s)
63 {
64     const int lastSep = s->lastIndexOf(QLatin1Char('.'));
65     if (lastSep != -1)
66         s->remove(0, lastSep + 1);
67 }
68 
shouldSkip(const AbstractMetaFunction * func)69 static bool shouldSkip(const AbstractMetaFunction* func)
70 {
71     // Constructors go to separate section
72     if (DocParser::skipForQuery(func) || func->isConstructor())
73         return true;
74 
75     // Search a const clone (QImage::bits() vs QImage::bits() const)
76     if (func->isConstant())
77         return false;
78 
79     const AbstractMetaArgumentList funcArgs = func->arguments();
80     const AbstractMetaFunctionList &ownerFunctions = func->ownerClass()->functions();
81     for (AbstractMetaFunction *f : ownerFunctions) {
82         if (f != func
83             && f->isConstant()
84             && f->name() == func->name()
85             && f->arguments().count() == funcArgs.count()) {
86             // Compare each argument
87             bool cloneFound = true;
88 
89             const AbstractMetaArgumentList fargs = f->arguments();
90             for (int i = 0, max = funcArgs.count(); i < max; ++i) {
91                 if (funcArgs.at(i)->type()->typeEntry() != fargs.at(i)->type()->typeEntry()) {
92                     cloneFound = false;
93                     break;
94                 }
95             }
96             if (cloneFound)
97                 return true;
98         }
99     }
100     return false;
101 }
102 
functionSort(const AbstractMetaFunction * func1,const AbstractMetaFunction * func2)103 static bool functionSort(const AbstractMetaFunction* func1, const AbstractMetaFunction* func2)
104 {
105     return func1->name() < func2->name();
106 }
107 
108 class Pad
109 {
110 public:
Pad(char c,int count)111     explicit Pad(char c, int count) : m_char(c), m_count(count) {}
112 
write(QTextStream & str) const113     void write(QTextStream &str) const
114     {
115         for (int i = 0; i < m_count; ++i)
116             str << m_char;
117     }
118 
119 private:
120     const char m_char;
121     const int m_count;
122 };
123 
operator <<(QTextStream & str,const Pad & pad)124 inline QTextStream &operator<<(QTextStream &str, const Pad &pad)
125 {
126     pad.write(str);
127     return str;
128 }
129 
130 template <class String>
writeEscapedRstText(QTextStream & str,const String & s)131 static int writeEscapedRstText(QTextStream &str, const String &s)
132 {
133     int escaped = 0;
134     for (const QChar &c : s) {
135         switch (c.unicode()) {
136         case '*':
137         case '`':
138         case '_':
139         case '\\':
140             str << '\\';
141             ++escaped;
142             break;
143         }
144         str << c;
145     }
146     return s.size() + escaped;
147 }
148 
149 class escape
150 {
151 public:
escape(const QStringRef & s)152     explicit escape(const QStringRef &s) : m_string(s) {}
153 
write(QTextStream & str) const154     void write(QTextStream &str) const { writeEscapedRstText(str, m_string); }
155 
156 private:
157     const QStringRef m_string;
158 };
159 
operator <<(QTextStream & str,const escape & e)160 inline QTextStream &operator<<(QTextStream &str, const escape &e)
161 {
162     e.write(str);
163     return str;
164 }
165 
166 // Return last character of a QString-buffered stream.
lastChar(const QTextStream & str)167 static QChar lastChar(const QTextStream &str)
168 {
169     const QString *string = str.string();
170     Q_ASSERT(string);
171     return string->isEmpty() ? QChar() : *(string->crbegin());
172 }
173 
ensureEndl(QTextStream & s)174 static QTextStream &ensureEndl(QTextStream &s)
175 {
176     if (lastChar(s) != QLatin1Char('\n'))
177         s << Qt::endl;
178     return s;
179 }
180 
versionOf(const TypeEntry * te)181 static inline QVersionNumber versionOf(const TypeEntry *te)
182 {
183     if (te) {
184         const auto version = te->version();
185         if (!version.isNull() && version > QVersionNumber(0, 0))
186             return version;
187     }
188     return QVersionNumber();
189 }
190 
191 struct rstVersionAdded
192 {
rstVersionAddedrstVersionAdded193     explicit rstVersionAdded(const QVersionNumber &v) : m_version(v) {}
194 
195     const QVersionNumber m_version;
196 };
197 
operator <<(QTextStream & s,const rstVersionAdded & v)198 static QTextStream &operator<<(QTextStream &s, const rstVersionAdded &v)
199 {
200     s << ".. versionadded:: "<< v.m_version.toString() << "\n\n";
201     return s;
202 }
203 
rstDeprecationNote(const char * what)204 static QByteArray rstDeprecationNote(const char *what)
205 {
206     return QByteArrayLiteral(".. note:: This ")
207         + what + QByteArrayLiteral(" is deprecated.\n\n");
208 }
209 
210 // RST anchor string: Anything else but letters, numbers, '_' or '.' replaced by '-'
isValidRstLabelChar(QChar c)211 static inline bool isValidRstLabelChar(QChar c)
212 {
213     return c.isLetterOrNumber() || c == QLatin1Char('_') || c == QLatin1Char('.');
214 }
215 
toRstLabel(QString s)216 static QString toRstLabel(QString s)
217 {
218     for (int i = 0, size = s.size(); i < size; ++i) {
219         if (!isValidRstLabelChar(s.at(i)))
220             s[i] = QLatin1Char('-');
221     }
222     return s;
223 }
224 
225 class rstLabel
226 {
227 public:
rstLabel(const QString & l)228     explicit rstLabel(const QString &l) : m_label(l) {}
229 
operator <<(QTextStream & str,const rstLabel & a)230     friend QTextStream &operator<<(QTextStream &str, const rstLabel &a)
231     {
232         str << ".. _" << toRstLabel(a.m_label) << ":\n\n";
233         return str;
234     }
235 
236 private:
237     const QString &m_label;
238 };
239 
240 struct QtXmlToSphinx::LinkContext
241 {
242     enum Type
243     {
244         Method = 0x1, Function = 0x2,
245         FunctionMask = Method | Function,
246         Class = 0x4, Attribute = 0x8, Module = 0x10,
247         Reference = 0x20, External= 0x40
248     };
249 
250     enum Flags { InsideBold = 0x1, InsideItalic = 0x2 };
251 
LinkContextQtXmlToSphinx::LinkContext252     explicit LinkContext(const QString &ref) : linkRef(ref) {}
253 
254     QString linkRef;
255     QString linkText;
256     Type type = Reference;
257     int flags = 0;
258 };
259 
linkKeyWord(QtXmlToSphinx::LinkContext::Type type)260 static const char *linkKeyWord(QtXmlToSphinx::LinkContext::Type type)
261 {
262     switch (type) {
263     case QtXmlToSphinx::LinkContext::Method:
264         return ":meth:";
265     case QtXmlToSphinx::LinkContext::Function:
266         return ":func:";
267     case QtXmlToSphinx::LinkContext::Class:
268         return ":class:";
269     case QtXmlToSphinx::LinkContext::Attribute:
270         return ":attr:";
271     case QtXmlToSphinx::LinkContext::Module:
272         return ":mod:";
273     case QtXmlToSphinx::LinkContext::Reference:
274         return ":ref:";
275     case QtXmlToSphinx::LinkContext::External:
276         break;
277     case QtXmlToSphinx::LinkContext::FunctionMask:
278         break;
279      }
280     return "";
281 }
282 
operator <<(QTextStream & str,const QtXmlToSphinx::LinkContext & linkContext)283 QTextStream &operator<<(QTextStream &str, const QtXmlToSphinx::LinkContext &linkContext)
284 {
285     // Temporarily turn off bold/italic since links do not work within
286     if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideBold)
287         str << "**";
288     else if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideItalic)
289         str << '*';
290     str << ' ' << linkKeyWord(linkContext.type) << '`';
291     const bool isExternal = linkContext.type == QtXmlToSphinx::LinkContext::External;
292     if (!linkContext.linkText.isEmpty()) {
293         writeEscapedRstText(str, linkContext.linkText);
294         if (isExternal && !linkContext.linkText.endsWith(QLatin1Char(' ')))
295             str << ' ';
296         str << '<';
297     }
298     // Convert page titles to RST labels
299     str << (linkContext.type == QtXmlToSphinx::LinkContext::Reference
300         ? toRstLabel(linkContext.linkRef) : linkContext.linkRef);
301     if (!linkContext.linkText.isEmpty())
302         str << '>';
303     str << '`';
304     if (isExternal)
305         str << '_';
306     str << ' ';
307     if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideBold)
308         str << "**";
309     else if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideItalic)
310         str << '*';
311     return str;
312 }
313 
QtXmlToSphinx(QtDocGenerator * generator,const QString & doc,const QString & context)314 QtXmlToSphinx::QtXmlToSphinx(QtDocGenerator* generator, const QString& doc, const QString& context)
315         : m_tableHasHeader(false), m_context(context), m_generator(generator), m_insideBold(false), m_insideItalic(false)
316 {
317     m_handlerMap.insert(QLatin1String("heading"), &QtXmlToSphinx::handleHeadingTag);
318     m_handlerMap.insert(QLatin1String("brief"), &QtXmlToSphinx::handleParaTag);
319     m_handlerMap.insert(QLatin1String("para"), &QtXmlToSphinx::handleParaTag);
320     m_handlerMap.insert(QLatin1String("italic"), &QtXmlToSphinx::handleItalicTag);
321     m_handlerMap.insert(QLatin1String("bold"), &QtXmlToSphinx::handleBoldTag);
322     m_handlerMap.insert(QLatin1String("see-also"), &QtXmlToSphinx::handleSeeAlsoTag);
323     m_handlerMap.insert(QLatin1String("snippet"), &QtXmlToSphinx::handleSnippetTag);
324     m_handlerMap.insert(QLatin1String("dots"), &QtXmlToSphinx::handleDotsTag);
325     m_handlerMap.insert(QLatin1String("codeline"), &QtXmlToSphinx::handleDotsTag);
326     m_handlerMap.insert(QLatin1String("table"), &QtXmlToSphinx::handleTableTag);
327     m_handlerMap.insert(QLatin1String("header"), &QtXmlToSphinx::handleRowTag);
328     m_handlerMap.insert(QLatin1String("row"), &QtXmlToSphinx::handleRowTag);
329     m_handlerMap.insert(QLatin1String("item"), &QtXmlToSphinx::handleItemTag);
330     m_handlerMap.insert(QLatin1String("argument"), &QtXmlToSphinx::handleArgumentTag);
331     m_handlerMap.insert(QLatin1String("teletype"), &QtXmlToSphinx::handleArgumentTag);
332     m_handlerMap.insert(QLatin1String("link"), &QtXmlToSphinx::handleLinkTag);
333     m_handlerMap.insert(QLatin1String("inlineimage"), &QtXmlToSphinx::handleInlineImageTag);
334     m_handlerMap.insert(QLatin1String("image"), &QtXmlToSphinx::handleImageTag);
335     m_handlerMap.insert(QLatin1String("list"), &QtXmlToSphinx::handleListTag);
336     m_handlerMap.insert(QLatin1String("term"), &QtXmlToSphinx::handleTermTag);
337     m_handlerMap.insert(QLatin1String("raw"), &QtXmlToSphinx::handleRawTag);
338     m_handlerMap.insert(QLatin1String("underline"), &QtXmlToSphinx::handleItalicTag);
339     m_handlerMap.insert(QLatin1String("superscript"), &QtXmlToSphinx::handleSuperScriptTag);
340     m_handlerMap.insert(QLatin1String("code"), &QtXmlToSphinx::handleCodeTag);
341     m_handlerMap.insert(QLatin1String("badcode"), &QtXmlToSphinx::handleCodeTag);
342     m_handlerMap.insert(QLatin1String("legalese"), &QtXmlToSphinx::handleCodeTag);
343     m_handlerMap.insert(QLatin1String("rst"), &QtXmlToSphinx::handleRstPassTroughTag);
344     m_handlerMap.insert(QLatin1String("section"), &QtXmlToSphinx::handleAnchorTag);
345     m_handlerMap.insert(QLatin1String("quotefile"), &QtXmlToSphinx::handleQuoteFileTag);
346 
347     // ignored tags
348     m_handlerMap.insert(QLatin1String("generatedlist"), &QtXmlToSphinx::handleIgnoredTag);
349     m_handlerMap.insert(QLatin1String("tableofcontents"), &QtXmlToSphinx::handleIgnoredTag);
350     m_handlerMap.insert(QLatin1String("quotefromfile"), &QtXmlToSphinx::handleIgnoredTag);
351     m_handlerMap.insert(QLatin1String("skipto"), &QtXmlToSphinx::handleIgnoredTag);
352     m_handlerMap.insert(QLatin1String("target"), &QtXmlToSphinx::handleTargetTag);
353     m_handlerMap.insert(QLatin1String("page"), &QtXmlToSphinx::handlePageTag);
354     m_handlerMap.insert(QLatin1String("group"), &QtXmlToSphinx::handlePageTag);
355 
356     // useless tags
357     m_handlerMap.insert(QLatin1String("description"), &QtXmlToSphinx::handleUselessTag);
358     m_handlerMap.insert(QLatin1String("definition"), &QtXmlToSphinx::handleUselessTag);
359     m_handlerMap.insert(QLatin1String("printuntil"), &QtXmlToSphinx::handleUselessTag);
360     m_handlerMap.insert(QLatin1String("relation"), &QtXmlToSphinx::handleUselessTag);
361 
362     // Doxygen tags
363     m_handlerMap.insert(QLatin1String("title"), &QtXmlToSphinx::handleHeadingTag);
364     m_handlerMap.insert(QLatin1String("ref"), &QtXmlToSphinx::handleParaTag);
365     m_handlerMap.insert(QLatin1String("computeroutput"), &QtXmlToSphinx::handleParaTag);
366     m_handlerMap.insert(QLatin1String("detaileddescription"), &QtXmlToSphinx::handleParaTag);
367     m_handlerMap.insert(QLatin1String("name"), &QtXmlToSphinx::handleParaTag);
368     m_handlerMap.insert(QLatin1String("listitem"), &QtXmlToSphinx::handleItemTag);
369     m_handlerMap.insert(QLatin1String("parametername"), &QtXmlToSphinx::handleItemTag);
370     m_handlerMap.insert(QLatin1String("parameteritem"), &QtXmlToSphinx::handleItemTag);
371     m_handlerMap.insert(QLatin1String("ulink"), &QtXmlToSphinx::handleLinkTag);
372     m_handlerMap.insert(QLatin1String("itemizedlist"), &QtXmlToSphinx::handleListTag);
373     m_handlerMap.insert(QLatin1String("parameternamelist"), &QtXmlToSphinx::handleListTag);
374     m_handlerMap.insert(QLatin1String("parameterlist"), &QtXmlToSphinx::handleListTag);
375 
376     // Doxygen ignored tags
377     m_handlerMap.insert(QLatin1String("highlight"), &QtXmlToSphinx::handleIgnoredTag);
378     m_handlerMap.insert(QLatin1String("linebreak"), &QtXmlToSphinx::handleIgnoredTag);
379     m_handlerMap.insert(QLatin1String("programlisting"), &QtXmlToSphinx::handleIgnoredTag);
380     m_handlerMap.insert(QLatin1String("xreftitle"), &QtXmlToSphinx::handleIgnoredTag);
381     m_handlerMap.insert(QLatin1String("sp"), &QtXmlToSphinx::handleIgnoredTag);
382     m_handlerMap.insert(QLatin1String("entry"), &QtXmlToSphinx::handleIgnoredTag);
383     m_handlerMap.insert(QLatin1String("simplesect"), &QtXmlToSphinx::handleIgnoredTag);
384     m_handlerMap.insert(QLatin1String("verbatim"), &QtXmlToSphinx::handleIgnoredTag);
385     m_handlerMap.insert(QLatin1String("xrefsect"), &QtXmlToSphinx::handleIgnoredTag);
386     m_handlerMap.insert(QLatin1String("xrefdescription"), &QtXmlToSphinx::handleIgnoredTag);
387 
388     m_result = transform(doc);
389 }
390 
pushOutputBuffer()391 void QtXmlToSphinx::pushOutputBuffer()
392 {
393     auto *buffer = new QString();
394     m_buffers << buffer;
395     m_output.setString(buffer);
396 }
397 
popOutputBuffer()398 QString QtXmlToSphinx::popOutputBuffer()
399 {
400     Q_ASSERT(!m_buffers.isEmpty());
401     QString* str = m_buffers.pop();
402     QString strcpy(*str);
403     delete str;
404     m_output.setString(m_buffers.isEmpty() ? 0 : m_buffers.top());
405     return strcpy;
406 }
407 
expandFunction(const QString & function) const408 QString QtXmlToSphinx::expandFunction(const QString& function) const
409 {
410     const int firstDot = function.indexOf(QLatin1Char('.'));
411     const AbstractMetaClass *metaClass = nullptr;
412     if (firstDot != -1) {
413         const QStringRef className = function.leftRef(firstDot);
414         for (const AbstractMetaClass *cls : m_generator->classes()) {
415             if (cls->name() == className) {
416                 metaClass = cls;
417                 break;
418             }
419         }
420     }
421 
422     return metaClass
423         ? metaClass->typeEntry()->qualifiedTargetLangName()
424           + function.right(function.size() - firstDot)
425         : function;
426 }
427 
resolveContextForMethod(const QString & methodName) const428 QString QtXmlToSphinx::resolveContextForMethod(const QString& methodName) const
429 {
430     const QStringRef currentClass = m_context.splitRef(QLatin1Char('.')).constLast();
431 
432     const AbstractMetaClass *metaClass = nullptr;
433     for (const AbstractMetaClass *cls : m_generator->classes()) {
434         if (cls->name() == currentClass) {
435             metaClass = cls;
436             break;
437         }
438     }
439 
440     if (metaClass) {
441         AbstractMetaFunctionList funcList;
442         const AbstractMetaFunctionList &methods = metaClass->queryFunctionsByName(methodName);
443         for (AbstractMetaFunction *func : methods) {
444             if (methodName == func->name())
445                 funcList.append(func);
446         }
447 
448         const AbstractMetaClass *implementingClass = nullptr;
449         for (AbstractMetaFunction *func : qAsConst(funcList)) {
450             implementingClass = func->implementingClass();
451             if (implementingClass->name() == currentClass)
452                 break;
453         }
454 
455         if (implementingClass)
456             return implementingClass->typeEntry()->qualifiedTargetLangName();
457     }
458 
459     return QLatin1Char('~') + m_context;
460 }
461 
transform(const QString & doc)462 QString QtXmlToSphinx::transform(const QString& doc)
463 {
464     Q_ASSERT(m_buffers.isEmpty());
465     Indentation indentation(INDENT);
466     if (doc.trimmed().isEmpty())
467         return doc;
468 
469     pushOutputBuffer();
470 
471     QXmlStreamReader reader(doc);
472 
473     while (!reader.atEnd()) {
474         QXmlStreamReader::TokenType token = reader.readNext();
475         if (reader.hasError()) {
476             QString message;
477             QTextStream(&message) << "XML Error "
478                 << reader.errorString() << " at " << reader.lineNumber()
479                 << ':' << reader.columnNumber() << '\n' << doc;
480             m_output << INDENT << message;
481             qCWarning(lcShibokenDoc).noquote().nospace() << message;
482             break;
483         }
484 
485         if (token == QXmlStreamReader::StartElement) {
486             QStringRef tagName = reader.name();
487             TagHandler handler = m_handlerMap.value(tagName.toString(), &QtXmlToSphinx::handleUnknownTag);
488             if (!m_handlers.isEmpty() && ( (m_handlers.top() == &QtXmlToSphinx::handleIgnoredTag) ||
489                                            (m_handlers.top() == &QtXmlToSphinx::handleRawTag)) )
490                 handler = &QtXmlToSphinx::handleIgnoredTag;
491 
492             m_handlers.push(handler);
493         }
494         if (!m_handlers.isEmpty())
495             (this->*(m_handlers.top()))(reader);
496 
497         if (token == QXmlStreamReader::EndElement) {
498             m_handlers.pop();
499             m_lastTagName = reader.name().toString();
500         }
501     }
502 
503     if (!m_inlineImages.isEmpty()) {
504         // Write out inline image definitions stored in handleInlineImageTag().
505         m_output << Qt::endl;
506         for (const InlineImage &img : qAsConst(m_inlineImages))
507             m_output << ".. |" << img.tag << "| image:: " << img.href << Qt::endl;
508         m_output << Qt::endl;
509         m_inlineImages.clear();
510     }
511 
512     m_output.flush();
513     QString retval = popOutputBuffer();
514     Q_ASSERT(m_buffers.isEmpty());
515     return retval;
516 }
517 
resolveFile(const QStringList & locations,const QString & path)518 static QString resolveFile(const QStringList &locations, const QString &path)
519 {
520     for (QString location : locations) {
521         location.append(QLatin1Char('/'));
522         location.append(path);
523         if (QFileInfo::exists(location))
524             return location;
525     }
526     return QString();
527 }
528 
readFromLocations(const QStringList & locations,const QString & path,const QString & identifier,QString * errorMessage)529 QString QtXmlToSphinx::readFromLocations(const QStringList &locations, const QString &path,
530                                          const QString &identifier, QString *errorMessage)
531 {
532     QString resolvedPath;
533     if (path.endsWith(QLatin1String(".cpp"))) {
534         const QString pySnippet = path.left(path.size() - 3) + QLatin1String("py");
535         resolvedPath = resolveFile(locations, pySnippet);
536     }
537     if (resolvedPath.isEmpty())
538         resolvedPath = resolveFile(locations, path);
539     if (resolvedPath.isEmpty()) {
540         QTextStream(errorMessage) << "Could not resolve \"" << path << "\" in \""
541            << locations.join(QLatin1String("\", \""));
542         return QString(); // null
543     }
544     qCDebug(lcShibokenDoc).noquote().nospace() << "snippet file " << path
545         << " [" << identifier << ']' << " resolved to " << resolvedPath;
546     return readFromLocation(resolvedPath, identifier, errorMessage);
547 }
548 
readFromLocation(const QString & location,const QString & identifier,QString * errorMessage)549 QString QtXmlToSphinx::readFromLocation(const QString &location, const QString &identifier,
550                                         QString *errorMessage)
551 {
552     QFile inputFile;
553     inputFile.setFileName(location);
554     if (!inputFile.open(QIODevice::ReadOnly)) {
555         QTextStream(errorMessage) << "Could not read code snippet file: "
556             << QDir::toNativeSeparators(inputFile.fileName())
557             << ": " << inputFile.errorString();
558         return QString(); // null
559     }
560 
561     QString code = QLatin1String(""); // non-null
562     if (identifier.isEmpty()) {
563         while (!inputFile.atEnd())
564             code += QString::fromUtf8(inputFile.readLine());
565         return code;
566     }
567 
568     const QRegularExpression searchString(QLatin1String("//!\\s*\\[")
569                                           + identifier + QLatin1String("\\]"));
570     Q_ASSERT(searchString.isValid());
571     static const QRegularExpression codeSnippetCode(QLatin1String("//!\\s*\\[[\\w\\d\\s]+\\]"));
572     Q_ASSERT(codeSnippetCode.isValid());
573 
574     bool getCode = false;
575 
576     while (!inputFile.atEnd()) {
577         QString line = QString::fromUtf8(inputFile.readLine());
578         if (getCode && !line.contains(searchString)) {
579             line.remove(codeSnippetCode);
580             code += line;
581         } else if (line.contains(searchString)) {
582             if (getCode)
583                 break;
584             getCode = true;
585         }
586     }
587 
588     if (!getCode) {
589         QTextStream(errorMessage) << "Code snippet file found ("
590             << QDir::toNativeSeparators(location) << "), but snippet ["
591             << identifier << "] not found.";
592         return QString(); // null
593     }
594 
595     return code;
596 }
597 
handleHeadingTag(QXmlStreamReader & reader)598 void QtXmlToSphinx::handleHeadingTag(QXmlStreamReader& reader)
599 {
600     static int headingSize = 0;
601     static char type;
602     static char types[] = { '-', '^' };
603     QXmlStreamReader::TokenType token = reader.tokenType();
604     if (token == QXmlStreamReader::StartElement) {
605         uint typeIdx = reader.attributes().value(QLatin1String("level")).toUInt();
606         if (typeIdx >= sizeof(types))
607             type = types[sizeof(types)-1];
608         else
609             type = types[typeIdx];
610     } else if (token == QXmlStreamReader::EndElement) {
611         m_output << Pad(type, headingSize) << Qt::endl << Qt::endl;
612     } else if (token == QXmlStreamReader::Characters) {
613         m_output << Qt::endl << Qt::endl;
614         headingSize = writeEscapedRstText(m_output, reader.text().trimmed());
615         m_output << Qt::endl;
616     }
617 }
618 
handleParaTag(QXmlStreamReader & reader)619 void QtXmlToSphinx::handleParaTag(QXmlStreamReader& reader)
620 {
621     QXmlStreamReader::TokenType token = reader.tokenType();
622     if (token == QXmlStreamReader::StartElement) {
623         pushOutputBuffer();
624     } else if (token == QXmlStreamReader::EndElement) {
625         QString result = popOutputBuffer().simplified();
626         if (result.startsWith(QLatin1String("**Warning:**")))
627             result.replace(0, 12, QLatin1String(".. warning:: "));
628         else if (result.startsWith(QLatin1String("**Note:**")))
629             result.replace(0, 9, QLatin1String(".. note:: "));
630 
631         m_output << INDENT << result << Qt::endl << Qt::endl;
632     } else if (token == QXmlStreamReader::Characters) {
633         const QStringRef text = reader.text();
634         const QChar end = lastChar(m_output);
635         if (!text.isEmpty() && INDENT.indent == 0 && !end.isNull()) {
636             QChar start = text[0];
637             if ((end == QLatin1Char('*') || end == QLatin1Char('`')) && start != QLatin1Char(' ') && !start.isPunct())
638                 m_output << '\\';
639         }
640         m_output << INDENT << escape(text);
641     }
642 }
643 
handleItalicTag(QXmlStreamReader & reader)644 void QtXmlToSphinx::handleItalicTag(QXmlStreamReader& reader)
645 {
646     QXmlStreamReader::TokenType token = reader.tokenType();
647     if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement) {
648         m_insideItalic = !m_insideItalic;
649         m_output << '*';
650     } else if (token == QXmlStreamReader::Characters) {
651         m_output << escape(reader.text().trimmed());
652     }
653 }
654 
handleBoldTag(QXmlStreamReader & reader)655 void QtXmlToSphinx::handleBoldTag(QXmlStreamReader& reader)
656 {
657     QXmlStreamReader::TokenType token = reader.tokenType();
658     if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement) {
659         m_insideBold = !m_insideBold;
660         m_output << "**";
661     } else if (token == QXmlStreamReader::Characters) {
662         m_output << escape(reader.text().trimmed());
663     }
664 }
665 
handleArgumentTag(QXmlStreamReader & reader)666 void QtXmlToSphinx::handleArgumentTag(QXmlStreamReader& reader)
667 {
668     QXmlStreamReader::TokenType token = reader.tokenType();
669     if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement)
670         m_output << "``";
671     else if (token == QXmlStreamReader::Characters)
672         m_output << reader.text().trimmed();
673 }
674 
functionLinkType()675 static inline QString functionLinkType() { return QStringLiteral("function"); }
classLinkType()676 static inline QString classLinkType() { return QStringLiteral("class"); }
677 
fixLinkType(const QStringRef & type)678 static inline QString fixLinkType(const QStringRef &type)
679 {
680     // TODO: create a flag PROPERTY-AS-FUNCTION to ask if the properties
681     // are recognized as such or not in the binding
682     if (type == QLatin1String("property"))
683         return functionLinkType();
684     if (type == QLatin1String("typedef"))
685         return classLinkType();
686     return type.toString();
687 }
688 
linkSourceAttribute(const QString & type)689 static inline QString linkSourceAttribute(const QString &type)
690 {
691     if (type == functionLinkType() || type == classLinkType())
692         return QLatin1String("raw");
693     return type == QLatin1String("enum") || type == QLatin1String("page")
694         ? type : QLatin1String("href");
695 }
696 
697 // "See also" links may appear as nested links:
698 //     <see-also>QAbstractXmlReceiver<link raw="isValid()" href="qxmlquery.html#isValid" type="function">isValid()</link>
699 //     which is handled in handleLinkTag
700 // or direct text:
701 //     <see-also>rootIsDecorated()</see-also>
702 //     which is handled here.
703 
handleSeeAlsoTag(QXmlStreamReader & reader)704 void QtXmlToSphinx::handleSeeAlsoTag(QXmlStreamReader& reader)
705 {
706     switch (reader.tokenType()) {
707     case QXmlStreamReader::StartElement:
708         m_output << INDENT << ".. seealso:: ";
709         break;
710     case QXmlStreamReader::Characters: {
711         // Direct embedded link: <see-also>rootIsDecorated()</see-also>
712         const QStringRef textR = reader.text().trimmed();
713         if (!textR.isEmpty()) {
714             const QString text = textR.toString();
715             if (m_seeAlsoContext.isNull()) {
716                 const QString type = text.endsWith(QLatin1String("()"))
717                     ? functionLinkType() : classLinkType();
718                 m_seeAlsoContext.reset(handleLinkStart(type, text));
719             }
720             handleLinkText(m_seeAlsoContext.data(), text);
721         }
722     }
723         break;
724     case QXmlStreamReader::EndElement:
725         if (!m_seeAlsoContext.isNull()) { // direct, no nested </link> seen
726             handleLinkEnd(m_seeAlsoContext.data());
727             m_seeAlsoContext.reset();
728         }
729         m_output << Qt::endl << Qt::endl;
730         break;
731     default:
732         break;
733     }
734 }
735 
fallbackPathAttribute()736 static inline QString fallbackPathAttribute() { return QStringLiteral("path"); }
737 
snippetComparison()738 static inline bool snippetComparison()
739 {
740     return ReportHandler::debugLevel() >= ReportHandler::FullDebug;
741 }
742 
743 template <class Indent> // const char*/class Indentor
formatSnippet(QTextStream & str,Indent indent,const QString & snippet)744 void formatSnippet(QTextStream &str, Indent indent, const QString &snippet)
745 {
746     const QVector<QStringRef> lines = snippet.splitRef(QLatin1Char('\n'));
747     for (const QStringRef &line : lines) {
748         if (!line.trimmed().isEmpty())
749             str << indent << line;
750         str << Qt::endl;
751     }
752 }
753 
msgSnippetComparison(const QString & location,const QString & identifier,const QString & pythonCode,const QString & fallbackCode)754 static QString msgSnippetComparison(const QString &location, const QString &identifier,
755                                     const QString &pythonCode, const QString &fallbackCode)
756 {
757     QString result;
758     QTextStream str(&result);
759     str << "Python snippet " << location;
760     if (!identifier.isEmpty())
761         str << " [" << identifier << ']';
762     str << ":\n";
763     formatSnippet(str, "  ", pythonCode);
764     str << "Corresponding fallback snippet:\n";
765     formatSnippet(str, "  ", fallbackCode);
766     str << "-- end --\n";
767     return result;
768 }
769 
handleSnippetTag(QXmlStreamReader & reader)770 void QtXmlToSphinx::handleSnippetTag(QXmlStreamReader& reader)
771 {
772     QXmlStreamReader::TokenType token = reader.tokenType();
773     if (token == QXmlStreamReader::StartElement) {
774         const bool consecutiveSnippet = m_lastTagName == QLatin1String("snippet")
775             || m_lastTagName == QLatin1String("dots") || m_lastTagName == QLatin1String("codeline");
776         if (consecutiveSnippet) {
777             m_output.flush();
778             m_output.string()->chop(2);
779         }
780         QString location = reader.attributes().value(QLatin1String("location")).toString();
781         QString identifier = reader.attributes().value(QLatin1String("identifier")).toString();
782         QString errorMessage;
783         const QString pythonCode =
784             readFromLocations(m_generator->codeSnippetDirs(), location, identifier, &errorMessage);
785         if (!errorMessage.isEmpty())
786             qCWarning(lcShibokenDoc, "%s", qPrintable(msgTagWarning(reader, m_context, m_lastTagName, errorMessage)));
787         // Fall back to C++ snippet when "path" attribute is present.
788         // Also read fallback snippet when comparison is desired.
789         QString fallbackCode;
790         if ((pythonCode.isEmpty() || snippetComparison())
791             && reader.attributes().hasAttribute(fallbackPathAttribute())) {
792             const QString fallback = reader.attributes().value(fallbackPathAttribute()).toString();
793             if (QFileInfo::exists(fallback)) {
794                 if (pythonCode.isEmpty())
795                     qCWarning(lcShibokenDoc, "%s", qPrintable(msgFallbackWarning(reader, m_context, m_lastTagName, location, identifier, fallback)));
796                 fallbackCode = readFromLocation(fallback, identifier, &errorMessage);
797                 if (!errorMessage.isEmpty())
798                     qCWarning(lcShibokenDoc, "%s", qPrintable(msgTagWarning(reader, m_context, m_lastTagName, errorMessage)));
799             }
800         }
801 
802         if (!pythonCode.isEmpty() && !fallbackCode.isEmpty() && snippetComparison())
803             qCDebug(lcShibokenDoc, "%s", qPrintable(msgSnippetComparison(location, identifier, pythonCode, fallbackCode)));
804 
805         if (!consecutiveSnippet)
806             m_output << INDENT << "::\n\n";
807 
808         Indentation indentation(INDENT);
809         const QString code = pythonCode.isEmpty() ? fallbackCode : pythonCode;
810         if (code.isEmpty())
811             m_output << INDENT << "<Code snippet \"" << location << ':' << identifier << "\" not found>\n";
812         else
813             formatSnippet(m_output, INDENT, code);
814         m_output << Qt::endl;
815     }
816 }
handleDotsTag(QXmlStreamReader & reader)817 void QtXmlToSphinx::handleDotsTag(QXmlStreamReader& reader)
818 {
819     QXmlStreamReader::TokenType token = reader.tokenType();
820     if (token == QXmlStreamReader::StartElement) {
821         const bool consecutiveSnippet = m_lastTagName == QLatin1String("snippet")
822             || m_lastTagName == QLatin1String("dots") || m_lastTagName == QLatin1String("codeline");
823         if (consecutiveSnippet) {
824             m_output.flush();
825             m_output.string()->chop(2);
826         } else {
827             m_output << INDENT << "::\n\n";
828         }
829         Indentation indentation(INDENT);
830         pushOutputBuffer();
831         m_output << INDENT;
832         int indent = reader.attributes().value(QLatin1String("indent")).toInt();
833         for (int i = 0; i < indent; ++i)
834             m_output << ' ';
835     } else if (token == QXmlStreamReader::Characters) {
836         m_output << reader.text().toString();
837     } else if (token == QXmlStreamReader::EndElement) {
838         m_output << popOutputBuffer() << "\n\n\n";
839     }
840 }
841 
handleTableTag(QXmlStreamReader & reader)842 void QtXmlToSphinx::handleTableTag(QXmlStreamReader& reader)
843 {
844     QXmlStreamReader::TokenType token = reader.tokenType();
845     if (token == QXmlStreamReader::StartElement) {
846         m_currentTable.clear();
847         m_tableHasHeader = false;
848     } else if (token == QXmlStreamReader::EndElement) {
849         // write the table on m_output
850         m_currentTable.setHeaderEnabled(m_tableHasHeader);
851         m_currentTable.normalize();
852         m_output << ensureEndl << m_currentTable;
853         m_currentTable.clear();
854     }
855 }
856 
handleTermTag(QXmlStreamReader & reader)857 void QtXmlToSphinx::handleTermTag(QXmlStreamReader& reader)
858 {
859     QXmlStreamReader::TokenType token = reader.tokenType();
860     if (token == QXmlStreamReader::StartElement) {
861         pushOutputBuffer();
862     } else if (token == QXmlStreamReader::Characters) {
863         m_output << reader.text().toString().replace(QLatin1String("::"), QLatin1String("."));
864     } else if (token == QXmlStreamReader::EndElement) {
865         TableCell cell;
866         cell.data = popOutputBuffer().trimmed();
867         m_currentTable.appendRow(TableRow(1, cell));
868     }
869 }
870 
871 
handleItemTag(QXmlStreamReader & reader)872 void QtXmlToSphinx::handleItemTag(QXmlStreamReader& reader)
873 {
874     QXmlStreamReader::TokenType token = reader.tokenType();
875     if (token == QXmlStreamReader::StartElement) {
876         if (m_currentTable.isEmpty())
877             m_currentTable.appendRow({});
878         TableRow& row = m_currentTable.last();
879         TableCell cell;
880         cell.colSpan = reader.attributes().value(QLatin1String("colspan")).toShort();
881         cell.rowSpan = reader.attributes().value(QLatin1String("rowspan")).toShort();
882         row << cell;
883         pushOutputBuffer();
884     } else if (token == QXmlStreamReader::EndElement) {
885         QString data = popOutputBuffer().trimmed();
886         if (!m_currentTable.isEmpty()) {
887             TableRow& row = m_currentTable.last();
888             if (!row.isEmpty())
889                 row.last().data = data;
890         }
891     }
892 }
893 
handleRowTag(QXmlStreamReader & reader)894 void QtXmlToSphinx::handleRowTag(QXmlStreamReader& reader)
895 {
896     QXmlStreamReader::TokenType token = reader.tokenType();
897     if (token == QXmlStreamReader::StartElement) {
898         m_tableHasHeader = reader.name() == QLatin1String("header");
899         m_currentTable.appendRow({});
900     }
901 }
902 
903 enum ListType { BulletList, OrderedList, EnumeratedList };
904 
webXmlListType(const QStringRef & t)905 static inline ListType webXmlListType(const QStringRef &t)
906 {
907     if (t == QLatin1String("enum"))
908         return EnumeratedList;
909     if (t == QLatin1String("ordered"))
910         return OrderedList;
911     return BulletList;
912 }
913 
handleListTag(QXmlStreamReader & reader)914 void QtXmlToSphinx::handleListTag(QXmlStreamReader& reader)
915 {
916     // BUG We do not support a list inside a table cell
917     static ListType listType = BulletList;
918     QXmlStreamReader::TokenType token = reader.tokenType();
919     if (token == QXmlStreamReader::StartElement) {
920         listType = webXmlListType(reader.attributes().value(QLatin1String("type")));
921         if (listType == EnumeratedList) {
922             m_currentTable.appendRow(TableRow{TableCell(QLatin1String("Constant")),
923                                               TableCell(QLatin1String("Description"))});
924             m_tableHasHeader = true;
925         }
926         INDENT.indent--;
927     } else if (token == QXmlStreamReader::EndElement) {
928         INDENT.indent++;
929         if (!m_currentTable.isEmpty()) {
930             switch (listType) {
931             case BulletList:
932             case OrderedList: {
933                 m_output << Qt::endl;
934                 const char *separator = listType == BulletList ? "* " : "#. ";
935                 const char *indent    = listType == BulletList ? "  " : "   ";
936                 for (const TableCell &cell : m_currentTable.constFirst()) {
937                     const QVector<QStringRef> itemLines = cell.data.splitRef(QLatin1Char('\n'));
938                     m_output << INDENT << separator << itemLines.constFirst() << Qt::endl;
939                     for (int i = 1, max = itemLines.count(); i < max; ++i)
940                         m_output << INDENT << indent << itemLines[i] << Qt::endl;
941                 }
942                 m_output << Qt::endl;
943             }
944                 break;
945             case EnumeratedList:
946                 m_currentTable.setHeaderEnabled(m_tableHasHeader);
947                 m_currentTable.normalize();
948                 m_output << ensureEndl << m_currentTable;
949                 break;
950             }
951         }
952         m_currentTable.clear();
953     }
954 }
955 
handleLinkTag(QXmlStreamReader & reader)956 void QtXmlToSphinx::handleLinkTag(QXmlStreamReader& reader)
957 {
958     switch (reader.tokenType()) {
959     case QXmlStreamReader::StartElement: {
960         // <link> embedded in <see-also> means the characters of <see-also> are no link.
961         m_seeAlsoContext.reset();
962         const QString type = fixLinkType(reader.attributes().value(QLatin1String("type")));
963         const QString ref = reader.attributes().value(linkSourceAttribute(type)).toString();
964         m_linkContext.reset(handleLinkStart(type, ref));
965     }
966         break;
967     case QXmlStreamReader::Characters:
968         Q_ASSERT(!m_linkContext.isNull());
969         handleLinkText(m_linkContext.data(), reader.text().toString());
970         break;
971     case QXmlStreamReader::EndElement:
972         Q_ASSERT(!m_linkContext.isNull());
973         handleLinkEnd(m_linkContext.data());
974         m_linkContext.reset();
975         break;
976     default:
977         break;
978     }
979 }
980 
handleLinkStart(const QString & type,QString ref) const981 QtXmlToSphinx::LinkContext *QtXmlToSphinx::handleLinkStart(const QString &type, QString ref) const
982 {
983     ref.replace(QLatin1String("::"), QLatin1String("."));
984     ref.remove(QLatin1String("()"));
985     auto *result = new LinkContext(ref);
986 
987     if (m_insideBold)
988         result->flags |= LinkContext::InsideBold;
989     else if (m_insideItalic)
990         result->flags |= LinkContext::InsideItalic;
991 
992     if (type == functionLinkType() && !m_context.isEmpty()) {
993         result->type = LinkContext::Method;
994         const QVector<QStringRef> rawlinklist = result->linkRef.splitRef(QLatin1Char('.'));
995         if (rawlinklist.size() == 1 || rawlinklist.constFirst() == m_context) {
996             QString context = resolveContextForMethod(rawlinklist.constLast().toString());
997             if (!result->linkRef.startsWith(context))
998                 result->linkRef.prepend(context + QLatin1Char('.'));
999         } else {
1000             result->linkRef = expandFunction(result->linkRef);
1001         }
1002     } else if (type == functionLinkType() && m_context.isEmpty()) {
1003         result->type = LinkContext::Function;
1004     } else if (type == classLinkType()) {
1005         result->type = LinkContext::Class;
1006         if (const TypeEntry *type = TypeDatabase::instance()->findType(result->linkRef)) {
1007             result->linkRef = type->qualifiedTargetLangName();
1008         } else { // fall back to the old heuristic if the type wasn't found.
1009             const QVector<QStringRef> rawlinklist = result->linkRef.splitRef(QLatin1Char('.'));
1010             QStringList splittedContext = m_context.split(QLatin1Char('.'));
1011             if (rawlinklist.size() == 1 || rawlinklist.constFirst() == splittedContext.constLast()) {
1012                 splittedContext.removeLast();
1013                 result->linkRef.prepend(QLatin1Char('~') + splittedContext.join(QLatin1Char('.'))
1014                                                  + QLatin1Char('.'));
1015             }
1016         }
1017     } else if (type == QLatin1String("enum")) {
1018         result->type = LinkContext::Attribute;
1019     } else if (type == QLatin1String("page")) {
1020         // Module, external web page or reference
1021         if (result->linkRef == m_generator->moduleName())
1022             result->type = LinkContext::Module;
1023         else if (result->linkRef.startsWith(QLatin1String("http")))
1024             result->type = LinkContext::External;
1025         else
1026             result->type = LinkContext::Reference;
1027     } else if (type == QLatin1String("external")) {
1028         result->type = LinkContext::External;
1029     } else {
1030         result->type = LinkContext::Reference;
1031     }
1032     return result;
1033 }
1034 
1035 // <link raw="Model/View Classes" href="model-view-programming.html#model-view-classes"
1036 //  type="page" page="Model/View Programming">Model/View Classes</link>
1037 // <link type="page" page="http://doc.qt.io/qt-5/class.html">QML types</link>
1038 // <link raw="Qt Quick" href="qtquick-index.html" type="page" page="Qt Quick">Qt Quick</link>
1039 // <link raw="QObject" href="qobject.html" type="class">QObject</link>
1040 // <link raw="Qt::Window" href="qt.html#WindowType-enum" type="enum" enum="Qt::WindowType">Qt::Window</link>
1041 // <link raw="QNetworkSession::reject()" href="qnetworksession.html#reject" type="function">QNetworkSession::reject()</link>
1042 
fixLinkText(const QtXmlToSphinx::LinkContext * linkContext,QString linktext)1043 static QString fixLinkText(const QtXmlToSphinx::LinkContext *linkContext,
1044                            QString linktext)
1045 {
1046     if (linkContext->type == QtXmlToSphinx::LinkContext::External
1047         || linkContext->type == QtXmlToSphinx::LinkContext::Reference) {
1048         return linktext;
1049     }
1050     // For the language reference documentation, strip the module name.
1051     // Clear the link text if that matches the function/class/enumeration name.
1052     const int lastSep = linktext.lastIndexOf(QLatin1String("::"));
1053     if (lastSep != -1)
1054         linktext.remove(0, lastSep + 2);
1055     else
1056          stripPythonQualifiers(&linktext);
1057     if (linkContext->linkRef == linktext)
1058         return QString();
1059     if ((linkContext->type & QtXmlToSphinx::LinkContext::FunctionMask) != 0
1060         && (linkContext->linkRef + QLatin1String("()")) == linktext) {
1061         return QString();
1062     }
1063     return  linktext;
1064 }
1065 
handleLinkText(LinkContext * linkContext,const QString & linktext) const1066 void QtXmlToSphinx::handleLinkText(LinkContext *linkContext, const QString &linktext) const
1067 {
1068     linkContext->linkText = fixLinkText(linkContext, linktext);
1069 }
1070 
handleLinkEnd(LinkContext * linkContext)1071 void QtXmlToSphinx::handleLinkEnd(LinkContext *linkContext)
1072 {
1073     m_output << *linkContext;
1074 }
1075 
1076 // Copy images that are placed in a subdirectory "images" under the webxml files
1077 // by qdoc to a matching subdirectory under the "rst/PySide2/<module>" directory
copyImage(const QString & href,const QString & docDataDir,const QString & context,const QString & outputDir,QString * errorMessage)1078 static bool copyImage(const QString &href, const QString &docDataDir,
1079                       const QString &context, const QString &outputDir,
1080                       QString *errorMessage)
1081 {
1082     const QChar slash = QLatin1Char('/');
1083     const int lastSlash = href.lastIndexOf(slash);
1084     const QString imagePath = lastSlash != -1 ? href.left(lastSlash) : QString();
1085     const QString imageFileName = lastSlash != -1 ? href.right(href.size() - lastSlash - 1) : href;
1086     QFileInfo imageSource(docDataDir + slash + href);
1087     if (!imageSource.exists()) {
1088         QTextStream(errorMessage) << "Image " << href << " does not exist in "
1089             << QDir::toNativeSeparators(docDataDir);
1090         return false;
1091     }
1092     // Determine directory from context, "Pyside2.QtGui.QPainter" ->"Pyside2/QtGui".
1093     // FIXME: Not perfect yet, should have knowledge about namespaces (DataVis3D) or
1094     // nested classes "Pyside2.QtGui.QTouchEvent.QTouchPoint".
1095     QString relativeTargetDir = context;
1096     const int lastDot = relativeTargetDir.lastIndexOf(QLatin1Char('.'));
1097     if (lastDot != -1)
1098         relativeTargetDir.truncate(lastDot);
1099     relativeTargetDir.replace(QLatin1Char('.'), slash);
1100     if (!imagePath.isEmpty())
1101         relativeTargetDir += slash + imagePath;
1102 
1103     const QString targetDir = outputDir + slash + relativeTargetDir;
1104     const QString targetFileName = targetDir + slash + imageFileName;
1105     if (QFileInfo::exists(targetFileName))
1106         return true;
1107     if (!QFileInfo::exists(targetDir)) {
1108         const QDir outDir(outputDir);
1109         if (!outDir.mkpath(relativeTargetDir)) {
1110             QTextStream(errorMessage) << "Cannot create " << QDir::toNativeSeparators(relativeTargetDir)
1111                 << " under " << QDir::toNativeSeparators(outputDir);
1112             return false;
1113         }
1114     }
1115 
1116     QFile source(imageSource.absoluteFilePath());
1117     if (!source.copy(targetFileName)) {
1118         QTextStream(errorMessage) << "Cannot copy " << QDir::toNativeSeparators(source.fileName())
1119             << " to " << QDir::toNativeSeparators(targetFileName) << ": "
1120             << source.errorString();
1121         return false;
1122     }
1123     qCDebug(lcShibokenDoc()).noquote().nospace() << __FUNCTION__ << " href=\""
1124         << href << "\", context=\"" << context << "\", docDataDir=\""
1125         << docDataDir << "\", outputDir=\"" << outputDir << "\", copied \""
1126         << source.fileName() << "\"->\"" << targetFileName << '"';
1127     return true;
1128 }
1129 
copyImage(const QString & href) const1130 bool QtXmlToSphinx::copyImage(const QString &href) const
1131 {
1132     QString errorMessage;
1133     const bool result =
1134         ::copyImage(href, m_generator->docDataDir(), m_context,
1135                     m_generator->outputDirectory(), &errorMessage);
1136     if (!result)
1137         qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage));
1138     return result;
1139 }
1140 
handleImageTag(QXmlStreamReader & reader)1141 void QtXmlToSphinx::handleImageTag(QXmlStreamReader& reader)
1142 {
1143     if (reader.tokenType() != QXmlStreamReader::StartElement)
1144         return;
1145     const QString href = reader.attributes().value(QLatin1String("href")).toString();
1146     if (copyImage(href))
1147         m_output << INDENT << ".. image:: " <<  href << Qt::endl << Qt::endl;
1148 }
1149 
handleInlineImageTag(QXmlStreamReader & reader)1150 void QtXmlToSphinx::handleInlineImageTag(QXmlStreamReader& reader)
1151 {
1152     if (reader.tokenType() != QXmlStreamReader::StartElement)
1153         return;
1154     const QString href = reader.attributes().value(QLatin1String("href")).toString();
1155     if (!copyImage(href))
1156         return;
1157     // Handle inline images by substitution references. Insert a unique tag
1158     // enclosed by '|' and define it further down. Determine tag from the base
1159     //file name with number.
1160     QString tag = href;
1161     int pos = tag.lastIndexOf(QLatin1Char('/'));
1162     if (pos != -1)
1163         tag.remove(0, pos + 1);
1164     pos = tag.indexOf(QLatin1Char('.'));
1165     if (pos != -1)
1166         tag.truncate(pos);
1167     tag += QString::number(m_inlineImages.size() + 1);
1168     m_inlineImages.append(InlineImage{tag, href});
1169     m_output << '|' << tag << '|' << ' ';
1170 }
1171 
handleRawTag(QXmlStreamReader & reader)1172 void QtXmlToSphinx::handleRawTag(QXmlStreamReader& reader)
1173 {
1174     QXmlStreamReader::TokenType token = reader.tokenType();
1175     if (token == QXmlStreamReader::StartElement) {
1176         QString format = reader.attributes().value(QLatin1String("format")).toString();
1177         m_output << INDENT << ".. raw:: " << format.toLower() << Qt::endl << Qt::endl;
1178     } else if (token == QXmlStreamReader::Characters) {
1179         const QVector<QStringRef> lst(reader.text().split(QLatin1Char('\n')));
1180         for (const QStringRef &row : lst)
1181             m_output << INDENT << INDENT << row << Qt::endl;
1182     } else if (token == QXmlStreamReader::EndElement) {
1183         m_output << Qt::endl << Qt::endl;
1184     }
1185 }
1186 
handleCodeTag(QXmlStreamReader & reader)1187 void QtXmlToSphinx::handleCodeTag(QXmlStreamReader& reader)
1188 {
1189     QXmlStreamReader::TokenType token = reader.tokenType();
1190     if (token == QXmlStreamReader::StartElement) {
1191         m_output << INDENT << "::\n\n";
1192         INDENT.indent++;
1193     } else if (token == QXmlStreamReader::Characters) {
1194         const QVector<QStringRef> lst(reader.text().split(QLatin1Char('\n')));
1195         for (const QStringRef &row : lst)
1196             m_output << INDENT << INDENT << row << Qt::endl;
1197     } else if (token == QXmlStreamReader::EndElement) {
1198         m_output << Qt::endl << Qt::endl;
1199         INDENT.indent--;
1200     }
1201 }
1202 
handleUnknownTag(QXmlStreamReader & reader)1203 void QtXmlToSphinx::handleUnknownTag(QXmlStreamReader& reader)
1204 {
1205     QXmlStreamReader::TokenType token = reader.tokenType();
1206     if (token == QXmlStreamReader::StartElement)
1207         qCDebug(lcShibokenDoc).noquote().nospace() << "Unknown QtDoc tag: \"" << reader.name().toString() << "\".";
1208 }
1209 
handleSuperScriptTag(QXmlStreamReader & reader)1210 void QtXmlToSphinx::handleSuperScriptTag(QXmlStreamReader& reader)
1211 {
1212     QXmlStreamReader::TokenType token = reader.tokenType();
1213     if (token == QXmlStreamReader::StartElement) {
1214         m_output << " :sup:`";
1215         pushOutputBuffer();
1216     } else if (token == QXmlStreamReader::Characters) {
1217         m_output << reader.text().toString();
1218     } else if (token == QXmlStreamReader::EndElement) {
1219         m_output << popOutputBuffer();
1220         m_output << '`';
1221     }
1222 }
1223 
handlePageTag(QXmlStreamReader & reader)1224 void QtXmlToSphinx::handlePageTag(QXmlStreamReader &reader)
1225 {
1226     if (reader.tokenType() != QXmlStreamReader::StartElement)
1227         return;
1228 
1229     const QStringRef title = reader.attributes().value(titleAttribute());
1230     if (!title.isEmpty())
1231         m_output << rstLabel(title.toString());
1232 
1233     const QStringRef fullTitle = reader.attributes().value(fullTitleAttribute());
1234     const int size = fullTitle.isEmpty()
1235        ? writeEscapedRstText(m_output, title)
1236        : writeEscapedRstText(m_output, fullTitle);
1237 
1238     m_output << Qt::endl << Pad('*', size) << Qt::endl << Qt::endl;
1239 }
1240 
handleTargetTag(QXmlStreamReader & reader)1241 void QtXmlToSphinx::handleTargetTag(QXmlStreamReader &reader)
1242 {
1243     if (reader.tokenType() != QXmlStreamReader::StartElement)
1244         return;
1245     const QStringRef name = reader.attributes().value(nameAttribute());
1246     if (!name.isEmpty())
1247         m_output << INDENT << rstLabel(name.toString());
1248 }
1249 
handleIgnoredTag(QXmlStreamReader &)1250 void QtXmlToSphinx::handleIgnoredTag(QXmlStreamReader&)
1251 {
1252 }
1253 
handleUselessTag(QXmlStreamReader &)1254 void QtXmlToSphinx::handleUselessTag(QXmlStreamReader&)
1255 {
1256     // Tag "description" just marks the init of "Detailed description" title.
1257     // Tag "definition" just marks enums. We have a different way to process them.
1258 }
1259 
handleAnchorTag(QXmlStreamReader & reader)1260 void QtXmlToSphinx::handleAnchorTag(QXmlStreamReader& reader)
1261 {
1262     QXmlStreamReader::TokenType token = reader.tokenType();
1263     if (token == QXmlStreamReader::StartElement) {
1264         QString anchor;
1265         if (reader.attributes().hasAttribute(QLatin1String("id")))
1266             anchor = reader.attributes().value(QLatin1String("id")).toString();
1267         else if (reader.attributes().hasAttribute(QLatin1String("name")))
1268             anchor = reader.attributes().value(QLatin1String("name")).toString();
1269         if (!anchor.isEmpty() && m_opened_anchor != anchor) {
1270             m_opened_anchor = anchor;
1271             if (!m_context.isEmpty())
1272                 anchor.prepend(m_context + QLatin1Char('_'));
1273             m_output << INDENT << rstLabel(anchor);
1274         }
1275    } else if (token == QXmlStreamReader::EndElement) {
1276        m_opened_anchor.clear();
1277    }
1278 }
1279 
handleRstPassTroughTag(QXmlStreamReader & reader)1280 void QtXmlToSphinx::handleRstPassTroughTag(QXmlStreamReader& reader)
1281 {
1282     if (reader.tokenType() == QXmlStreamReader::Characters)
1283         m_output << reader.text();
1284 }
1285 
handleQuoteFileTag(QXmlStreamReader & reader)1286 void QtXmlToSphinx::handleQuoteFileTag(QXmlStreamReader& reader)
1287 {
1288     QXmlStreamReader::TokenType token = reader.tokenType();
1289     if (token == QXmlStreamReader::Characters) {
1290         QString location = reader.text().toString();
1291         location.prepend(m_generator->libSourceDir() + QLatin1Char('/'));
1292         QString errorMessage;
1293         QString code = readFromLocation(location, QString(), &errorMessage);
1294         if (!errorMessage.isEmpty())
1295             qCWarning(lcShibokenDoc, "%s", qPrintable(msgTagWarning(reader, m_context, m_lastTagName, errorMessage)));
1296         m_output << INDENT << "::\n\n";
1297         Indentation indentation(INDENT);
1298         if (code.isEmpty())
1299             m_output << INDENT << "<Code snippet \"" << location << "\" not found>\n";
1300         else
1301             formatCode(m_output, code, INDENT);
1302         m_output << Qt::endl;
1303     }
1304 }
1305 
convertToRst(QtDocGenerator * generator,const QString & sourceFileName,const QString & targetFileName,const QString & context,QString * errorMessage)1306 bool QtXmlToSphinx::convertToRst(QtDocGenerator *generator,
1307                                  const QString &sourceFileName,
1308                                  const QString &targetFileName,
1309                                  const QString &context, QString *errorMessage)
1310 {
1311     QFile sourceFile(sourceFileName);
1312     if (!sourceFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1313         if (errorMessage)
1314             *errorMessage = msgCannotOpenForReading(sourceFile);
1315         return false;
1316     }
1317     const QString doc = QString::fromUtf8(sourceFile.readAll());
1318     sourceFile.close();
1319 
1320     FileOut targetFile(targetFileName);
1321     QtXmlToSphinx x(generator, doc, context);
1322     targetFile.stream << x;
1323     return targetFile.done(errorMessage) != FileOut::Failure;
1324 }
1325 
normalize()1326 void QtXmlToSphinx::Table::normalize()
1327 {
1328     if (m_normalized || isEmpty())
1329         return;
1330 
1331     //QDoc3 generates tables with wrong number of columns. We have to
1332     //check and if necessary, merge the last columns.
1333     int maxCols = -1;
1334     for (const auto &row : qAsConst(m_rows)) {
1335         if (row.count() > maxCols)
1336             maxCols = row.count();
1337     }
1338     if (maxCols <= 0)
1339         return;
1340     // add col spans
1341     for (int row = 0; row < m_rows.count(); ++row) {
1342         for (int col = 0; col < m_rows.at(row).count(); ++col) {
1343             QtXmlToSphinx::TableCell& cell = m_rows[row][col];
1344             bool mergeCols = (col >= maxCols);
1345             if (cell.colSpan > 0) {
1346                 QtXmlToSphinx::TableCell newCell;
1347                 newCell.colSpan = -1;
1348                 for (int i = 0, max = cell.colSpan-1; i < max; ++i) {
1349                     m_rows[row].insert(col + 1, newCell);
1350                 }
1351                 cell.colSpan = 0;
1352                 col++;
1353             } else if (mergeCols) {
1354                 m_rows[row][maxCols - 1].data += QLatin1Char(' ') + cell.data;
1355             }
1356         }
1357     }
1358 
1359     // row spans
1360     const int numCols = m_rows.constFirst().count();
1361     for (int col = 0; col < numCols; ++col) {
1362         for (int row = 0; row < m_rows.count(); ++row) {
1363             if (col < m_rows[row].count()) {
1364                 QtXmlToSphinx::TableCell& cell = m_rows[row][col];
1365                 if (cell.rowSpan > 0) {
1366                     QtXmlToSphinx::TableCell newCell;
1367                     newCell.rowSpan = -1;
1368                     int targetRow = row + 1;
1369                     const int targetEndRow =
1370                         std::min(targetRow + cell.rowSpan - 1, m_rows.count());
1371                     cell.rowSpan = 0;
1372                     for ( ; targetRow < targetEndRow; ++targetRow)
1373                         m_rows[targetRow].insert(col, newCell);
1374                     row++;
1375                 }
1376             }
1377         }
1378     }
1379     m_normalized = true;
1380 }
1381 
operator <<(QTextStream & s,const QtXmlToSphinx::Table & table)1382 QTextStream& operator<<(QTextStream& s, const QtXmlToSphinx::Table &table)
1383 {
1384     table.format(s);
1385     return s;
1386 }
1387 
format(QTextStream & s) const1388 void QtXmlToSphinx::Table::format (QTextStream& s) const
1389 {
1390     if (isEmpty())
1391         return;
1392 
1393     if (!isNormalized()) {
1394         qCDebug(lcShibokenDoc) << "Attempt to print an unnormalized table!";
1395         return;
1396     }
1397 
1398     // calc width and height of each column and row
1399     const int headerColumnCount = m_rows.constFirst().count();
1400     QVector<int> colWidths(headerColumnCount);
1401     QVector<int> rowHeights(m_rows.count());
1402     for (int i = 0, maxI = m_rows.count(); i < maxI; ++i) {
1403         const QtXmlToSphinx::TableRow& row = m_rows.at(i);
1404         for (int j = 0, maxJ = std::min(row.count(), colWidths.size()); j < maxJ; ++j) {
1405             const QVector<QStringRef> rowLines = row[j].data.splitRef(QLatin1Char('\n')); // cache this would be a good idea
1406             for (const QStringRef &str : rowLines)
1407                 colWidths[j] = std::max(colWidths[j], str.count());
1408             rowHeights[i] = std::max(rowHeights[i], row[j].data.count(QLatin1Char('\n')) + 1);
1409         }
1410     }
1411 
1412     if (!*std::max_element(colWidths.begin(), colWidths.end()))
1413         return; // empty table (table with empty cells)
1414 
1415     // create a horizontal line to be used later.
1416     QString horizontalLine = QLatin1String("+");
1417     for (int i = 0, max = colWidths.count(); i < max; ++i) {
1418         horizontalLine += QString(colWidths.at(i), QLatin1Char('-'));
1419         horizontalLine += QLatin1Char('+');
1420     }
1421 
1422     // write table rows
1423     for (int i = 0, maxI = m_rows.count(); i < maxI; ++i) { // for each row
1424         const QtXmlToSphinx::TableRow& row = m_rows.at(i);
1425 
1426         // print line
1427         s << INDENT << '+';
1428         for (int col = 0; col < headerColumnCount; ++col) {
1429             char c;
1430             if (col >= row.length() || row[col].rowSpan == -1)
1431                 c = ' ';
1432             else if (i == 1 && hasHeader())
1433                 c = '=';
1434             else
1435                 c = '-';
1436             s << Pad(c, colWidths.at(col)) << '+';
1437         }
1438         s << Qt::endl;
1439 
1440 
1441         // Print the table cells
1442         for (int rowLine = 0; rowLine < rowHeights[i]; ++rowLine) { // for each line in a row
1443             int j = 0;
1444             for (int maxJ = std::min(row.count(), headerColumnCount); j < maxJ; ++j) { // for each column
1445                 const QtXmlToSphinx::TableCell& cell = row[j];
1446                 const QVector<QStringRef> rowLines = cell.data.splitRef(QLatin1Char('\n')); // FIXME: Cache this!!!
1447                 if (!j) // First column, so we need print the identation
1448                     s << INDENT;
1449 
1450                 if (!j || !cell.colSpan)
1451                     s << '|';
1452                 else
1453                     s << ' ';
1454                 if (rowLine < rowLines.count())
1455                     s << qSetFieldWidth(colWidths[j]) << Qt::left << rowLines.at(rowLine) << qSetFieldWidth(0);
1456                 else
1457                     s << Pad(' ', colWidths.at(j));
1458             }
1459             for ( ; j < headerColumnCount; ++j) // pad
1460                 s << '|' << Pad(' ', colWidths.at(j));
1461             s << "|\n";
1462         }
1463     }
1464     s << INDENT << horizontalLine << Qt::endl << Qt::endl;
1465 }
1466 
getFuncName(const AbstractMetaFunction * cppFunc)1467 static QString getFuncName(const AbstractMetaFunction* cppFunc) {
1468     static bool hashInitialized = false;
1469     static QHash<QString, QString> operatorsHash;
1470     if (!hashInitialized) {
1471         operatorsHash.insert(QLatin1String("operator+"), QLatin1String("__add__"));
1472         operatorsHash.insert(QLatin1String("operator+="), QLatin1String("__iadd__"));
1473         operatorsHash.insert(QLatin1String("operator-"), QLatin1String("__sub__"));
1474         operatorsHash.insert(QLatin1String("operator-="), QLatin1String("__isub__"));
1475         operatorsHash.insert(QLatin1String("operator*"), QLatin1String("__mul__"));
1476         operatorsHash.insert(QLatin1String("operator*="), QLatin1String("__imul__"));
1477         operatorsHash.insert(QLatin1String("operator/"), QLatin1String("__div__"));
1478         operatorsHash.insert(QLatin1String("operator/="), QLatin1String("__idiv__"));
1479         operatorsHash.insert(QLatin1String("operator%"), QLatin1String("__mod__"));
1480         operatorsHash.insert(QLatin1String("operator%="), QLatin1String("__imod__"));
1481         operatorsHash.insert(QLatin1String("operator<<"), QLatin1String("__lshift__"));
1482         operatorsHash.insert(QLatin1String("operator<<="), QLatin1String("__ilshift__"));
1483         operatorsHash.insert(QLatin1String("operator>>"), QLatin1String("__rshift__"));
1484         operatorsHash.insert(QLatin1String("operator>>="), QLatin1String("__irshift__"));
1485         operatorsHash.insert(QLatin1String("operator&"), QLatin1String("__and__"));
1486         operatorsHash.insert(QLatin1String("operator&="), QLatin1String("__iand__"));
1487         operatorsHash.insert(QLatin1String("operator|"), QLatin1String("__or__"));
1488         operatorsHash.insert(QLatin1String("operator|="), QLatin1String("__ior__"));
1489         operatorsHash.insert(QLatin1String("operator^"), QLatin1String("__xor__"));
1490         operatorsHash.insert(QLatin1String("operator^="), QLatin1String("__ixor__"));
1491         operatorsHash.insert(QLatin1String("operator=="), QLatin1String("__eq__"));
1492         operatorsHash.insert(QLatin1String("operator!="), QLatin1String("__ne__"));
1493         operatorsHash.insert(QLatin1String("operator<"), QLatin1String("__lt__"));
1494         operatorsHash.insert(QLatin1String("operator<="), QLatin1String("__le__"));
1495         operatorsHash.insert(QLatin1String("operator>"), QLatin1String("__gt__"));
1496         operatorsHash.insert(QLatin1String("operator>="), QLatin1String("__ge__"));
1497         hashInitialized = true;
1498     }
1499 
1500     QHash<QString, QString>::const_iterator it = operatorsHash.constFind(cppFunc->name());
1501     QString result = it != operatorsHash.cend() ? it.value() : cppFunc->name();
1502     result.replace(QLatin1String("::"), QLatin1String("."));
1503     return result;
1504 }
1505 
QtDocGenerator()1506 QtDocGenerator::QtDocGenerator() : m_docParser(nullptr)
1507 {
1508 }
1509 
~QtDocGenerator()1510 QtDocGenerator::~QtDocGenerator()
1511 {
1512     delete m_docParser;
1513 }
1514 
fileNameSuffix() const1515 QString QtDocGenerator::fileNameSuffix() const
1516 {
1517     return QLatin1String(".rst");
1518 }
1519 
shouldGenerate(const AbstractMetaClass * cls) const1520 bool QtDocGenerator::shouldGenerate(const AbstractMetaClass *cls) const
1521 {
1522     return Generator::shouldGenerate(cls)
1523         && cls->typeEntry()->type() != TypeEntry::SmartPointerType;
1524 }
1525 
fileNameForContext(const GeneratorContext & context) const1526 QString QtDocGenerator::fileNameForContext(const GeneratorContext &context) const
1527 {
1528     const AbstractMetaClass *metaClass = context.metaClass();
1529     if (!context.forSmartPointer()) {
1530         return metaClass->name() + fileNameSuffix();
1531     }
1532     const AbstractMetaType *smartPointerType = context.preciseType();
1533     QString fileNameBase = getFileNameBaseForSmartPointer(smartPointerType, metaClass);
1534     return fileNameBase + fileNameSuffix();
1535 }
1536 
writeFormattedText(QTextStream & s,const Documentation & doc,const AbstractMetaClass * metaClass,Documentation::Type docType)1537 void QtDocGenerator::writeFormattedText(QTextStream &s, const Documentation &doc,
1538                                         const AbstractMetaClass *metaClass,
1539                                         Documentation::Type docType)
1540 {
1541     QString metaClassName;
1542 
1543     if (metaClass)
1544         metaClassName = metaClass->fullName();
1545 
1546     if (doc.format() == Documentation::Native) {
1547         QtXmlToSphinx x(this,doc.value(docType), metaClassName);
1548         s << x;
1549     } else {
1550         const QString &value = doc.value(docType);
1551         const QVector<QStringRef> lines = value.splitRef(QLatin1Char('\n'));
1552         int typesystemIndentation = std::numeric_limits<int>::max();
1553         // check how many spaces must be removed from the beginning of each line
1554         for (const QStringRef &line : lines) {
1555             const auto it = std::find_if(line.cbegin(), line.cend(),
1556                                          [] (QChar c) { return !c.isSpace(); });
1557             if (it != line.cend())
1558                 typesystemIndentation = qMin(typesystemIndentation, int(it - line.cbegin()));
1559         }
1560         if (typesystemIndentation == std::numeric_limits<int>::max())
1561             typesystemIndentation = 0;
1562         for (const QStringRef &line : lines) {
1563             s << INDENT
1564                 << (typesystemIndentation > 0 && typesystemIndentation < line.size()
1565                     ? line.right(line.size() - typesystemIndentation) : line)
1566                 << Qt::endl;
1567         }
1568     }
1569 
1570     s << Qt::endl;
1571 }
1572 
writeInheritedByList(QTextStream & s,const AbstractMetaClass * metaClass,const AbstractMetaClassList & allClasses)1573 static void writeInheritedByList(QTextStream& s, const AbstractMetaClass* metaClass, const AbstractMetaClassList& allClasses)
1574 {
1575     AbstractMetaClassList res;
1576     for (AbstractMetaClass *c : allClasses) {
1577         if (c != metaClass && c->inheritsFrom(metaClass))
1578             res << c;
1579     }
1580 
1581     if (res.isEmpty())
1582         return;
1583 
1584     s << "**Inherited by:** ";
1585     QStringList classes;
1586     for (AbstractMetaClass *c : qAsConst(res))
1587         classes << QLatin1String(":ref:`") + c->name() + QLatin1Char('`');
1588     s << classes.join(QLatin1String(", ")) << Qt::endl << Qt::endl;
1589 }
1590 
1591 // Extract the <brief> section from a WebXML (class) documentation and remove it
1592 // from the source.
extractBrief(Documentation * sourceDoc,Documentation * brief)1593 static bool extractBrief(Documentation *sourceDoc, Documentation *brief)
1594 {
1595     if (sourceDoc->format() != Documentation::Native)
1596         return false;
1597     QString value = sourceDoc->value();
1598     const int briefStart = value.indexOf(briefStartElement());
1599     if (briefStart < 0)
1600         return false;
1601     const int briefEnd = value.indexOf(briefEndElement(), briefStart + briefStartElement().size());
1602     if (briefEnd < briefStart)
1603         return false;
1604     const int briefLength = briefEnd + briefEndElement().size() - briefStart;
1605     brief->setFormat(Documentation::Native);
1606     QString briefValue = value.mid(briefStart, briefLength);
1607     briefValue.insert(briefValue.size() - briefEndElement().size(),
1608                       QLatin1String("<rst> More_...</rst>"));
1609     brief->setValue(briefValue);
1610     value.remove(briefStart, briefLength);
1611     sourceDoc->setValue(value);
1612     return true;
1613 }
1614 
generateClass(QTextStream & s,const GeneratorContext & classContext)1615 void QtDocGenerator::generateClass(QTextStream &s, const GeneratorContext &classContext)
1616 {
1617     const AbstractMetaClass *metaClass = classContext.metaClass();
1618     qCDebug(lcShibokenDoc).noquote().nospace() << "Generating Documentation for " << metaClass->fullName();
1619 
1620     m_packages[metaClass->package()] << fileNameForContext(classContext);
1621 
1622     m_docParser->setPackageName(metaClass->package());
1623     m_docParser->fillDocumentation(const_cast<AbstractMetaClass*>(metaClass));
1624 
1625     QString className = metaClass->name();
1626     s << ".. _" << className << ":" << "\n\n";
1627     s << ".. currentmodule:: " << metaClass->package() << "\n\n\n";
1628 
1629     s << className << Qt::endl;
1630     s << Pad('*', className.count()) << Qt::endl << Qt::endl;
1631 
1632     auto documentation = metaClass->documentation();
1633     Documentation brief;
1634     if (extractBrief(&documentation, &brief))
1635         writeFormattedText(s, brief.value(), metaClass);
1636 
1637     s << ".. inheritance-diagram:: " << metaClass->fullName() << Qt::endl
1638       << "    :parts: 2" << Qt::endl << Qt::endl;
1639     // TODO: This would be a parameter in the future...
1640 
1641 
1642     writeInheritedByList(s, metaClass, classes());
1643 
1644     const auto version = versionOf(metaClass->typeEntry());
1645     if (!version.isNull())
1646         s << rstVersionAdded(version);
1647     if (metaClass->attributes().testFlag(AbstractMetaAttributes::Deprecated))
1648         s << rstDeprecationNote("class");
1649 
1650     writeFunctionList(s, metaClass);
1651 
1652     //Function list
1653     AbstractMetaFunctionList functionList = metaClass->functions();
1654     std::sort(functionList.begin(), functionList.end(), functionSort);
1655 
1656     s << "\nDetailed Description\n"
1657            "--------------------\n\n"
1658         << ".. _More:\n";
1659 
1660     writeInjectDocumentation(s, TypeSystem::DocModificationPrepend, metaClass, nullptr);
1661     if (!writeInjectDocumentation(s, TypeSystem::DocModificationReplace, metaClass, nullptr))
1662         writeFormattedText(s, documentation.value(), metaClass);
1663 
1664     if (!metaClass->isNamespace())
1665         writeConstructors(s, metaClass);
1666     writeEnums(s, metaClass);
1667     if (!metaClass->isNamespace())
1668         writeFields(s, metaClass);
1669 
1670 
1671     QStringList uniqueFunctions;
1672     for (AbstractMetaFunction *func : qAsConst(functionList)) {
1673         if (shouldSkip(func))
1674             continue;
1675 
1676         if (func->isStatic())
1677             s <<  ".. staticmethod:: ";
1678         else
1679             s <<  ".. method:: ";
1680 
1681         writeFunction(s, metaClass, func, !uniqueFunctions.contains(func->name()));
1682         uniqueFunctions.append(func->name());
1683     }
1684 
1685     writeInjectDocumentation(s, TypeSystem::DocModificationAppend, metaClass, nullptr);
1686 }
1687 
writeFunctionList(QTextStream & s,const AbstractMetaClass * cppClass)1688 void QtDocGenerator::writeFunctionList(QTextStream& s, const AbstractMetaClass* cppClass)
1689 {
1690     QStringList functionList;
1691     QStringList virtualList;
1692     QStringList signalList;
1693     QStringList slotList;
1694     QStringList staticFunctionList;
1695 
1696     const AbstractMetaFunctionList &classFunctions = cppClass->functions();
1697     for (AbstractMetaFunction *func : classFunctions) {
1698         if (shouldSkip(func))
1699             continue;
1700 
1701         QString className;
1702         if (!func->isConstructor())
1703             className = cppClass->fullName() + QLatin1Char('.');
1704         else if (func->implementingClass() && func->implementingClass()->enclosingClass())
1705             className = func->implementingClass()->enclosingClass()->fullName() + QLatin1Char('.');
1706         QString funcName = getFuncName(func);
1707 
1708         QString str = QLatin1String("def :meth:`");
1709 
1710         str += funcName;
1711         str += QLatin1Char('<');
1712         if (!funcName.startsWith(className))
1713             str += className;
1714         str += funcName;
1715         str += QLatin1String(">` (");
1716         str += parseArgDocStyle(cppClass, func);
1717         str += QLatin1Char(')');
1718 
1719         if (func->isStatic())
1720             staticFunctionList << str;
1721         else if (func->isVirtual())
1722             virtualList << str;
1723         else if (func->isSignal())
1724             signalList << str;
1725         else if (func->isSlot())
1726             slotList << str;
1727         else
1728             functionList << str;
1729     }
1730 
1731     if (!functionList.isEmpty() || !staticFunctionList.isEmpty()) {
1732         QtXmlToSphinx::Table functionTable;
1733 
1734         s << "\nSynopsis\n--------\n\n";
1735 
1736         writeFunctionBlock(s, QLatin1String("Functions"), functionList);
1737         writeFunctionBlock(s, QLatin1String("Virtual functions"), virtualList);
1738         writeFunctionBlock(s, QLatin1String("Slots"), slotList);
1739         writeFunctionBlock(s, QLatin1String("Signals"), signalList);
1740         writeFunctionBlock(s, QLatin1String("Static functions"), staticFunctionList);
1741     }
1742 }
1743 
writeFunctionBlock(QTextStream & s,const QString & title,QStringList & functions)1744 void QtDocGenerator::writeFunctionBlock(QTextStream& s, const QString& title, QStringList& functions)
1745 {
1746     if (!functions.isEmpty()) {
1747         s << title << Qt::endl
1748           << QString(title.size(), QLatin1Char('^')) << Qt::endl;
1749 
1750         std::sort(functions.begin(), functions.end());
1751 
1752         s << ".. container:: function_list\n\n";
1753         Indentation indentation(INDENT);
1754         for (const QString &func : qAsConst(functions))
1755             s << INDENT << '*' << ' ' << func << Qt::endl;
1756 
1757         s << Qt::endl << Qt::endl;
1758     }
1759 }
1760 
writeEnums(QTextStream & s,const AbstractMetaClass * cppClass)1761 void QtDocGenerator::writeEnums(QTextStream& s, const AbstractMetaClass* cppClass)
1762 {
1763     static const QString section_title = QLatin1String(".. attribute:: ");
1764 
1765     for (AbstractMetaEnum *en : cppClass->enums()) {
1766         s << section_title << cppClass->fullName() << '.' << en->name() << Qt::endl << Qt::endl;
1767         writeFormattedText(s, en->documentation().value(), cppClass);
1768         const auto version = versionOf(en->typeEntry());
1769         if (!version.isNull())
1770             s << rstVersionAdded(version);
1771     }
1772 
1773 }
1774 
writeFields(QTextStream & s,const AbstractMetaClass * cppClass)1775 void QtDocGenerator::writeFields(QTextStream& s, const AbstractMetaClass* cppClass)
1776 {
1777     static const QString section_title = QLatin1String(".. attribute:: ");
1778 
1779     const AbstractMetaFieldList &fields = cppClass->fields();
1780     for (AbstractMetaField *field : fields) {
1781         s << section_title << cppClass->fullName() << "." << field->name() << Qt::endl << Qt::endl;
1782         //TODO: request for member ‘documentation’ is ambiguous
1783         writeFormattedText(s, field->AbstractMetaAttributes::documentation().value(), cppClass);
1784     }
1785 }
1786 
writeConstructors(QTextStream & s,const AbstractMetaClass * cppClass)1787 void QtDocGenerator::writeConstructors(QTextStream& s, const AbstractMetaClass* cppClass)
1788 {
1789     static const QString sectionTitle = QLatin1String(".. class:: ");
1790 
1791     AbstractMetaFunctionList lst = cppClass->queryFunctions(AbstractMetaClass::Constructors | AbstractMetaClass::Visible);
1792     for (int i = lst.size() - 1; i >= 0; --i) {
1793         if (lst.at(i)->isModifiedRemoved() || lst.at(i)->functionType() == AbstractMetaFunction::MoveConstructorFunction)
1794             lst.removeAt(i);
1795     }
1796 
1797     bool first = true;
1798     QHash<QString, AbstractMetaArgument*> arg_map;
1799 
1800     IndentorBase<1> indent1;
1801     indent1.indent = INDENT.total();
1802     if (lst.isEmpty()) {
1803         s << sectionTitle << cppClass->fullName();
1804     } else {
1805         for (AbstractMetaFunction *func : qAsConst(lst)) {
1806             s << indent1;
1807             if (first) {
1808                 first = false;
1809                 s << sectionTitle;
1810                 indent1.indent += sectionTitle.size();
1811             }
1812             s << functionSignature(cppClass, func) << "\n\n";
1813 
1814             const auto version = versionOf(func->typeEntry());
1815             if (!version.isNull())
1816                 s << indent1 << rstVersionAdded(version);
1817             if (func->attributes().testFlag(AbstractMetaAttributes::Deprecated))
1818                 s << indent1 << rstDeprecationNote("constructor");
1819 
1820             const AbstractMetaArgumentList &arguments = func->arguments();
1821             for (AbstractMetaArgument *arg : arguments) {
1822                 if (!arg_map.contains(arg->name())) {
1823                     arg_map.insert(arg->name(), arg);
1824                 }
1825             }
1826         }
1827     }
1828 
1829     s << Qt::endl;
1830 
1831     for (QHash<QString, AbstractMetaArgument*>::const_iterator it = arg_map.cbegin(), end = arg_map.cend(); it != end; ++it) {
1832         Indentation indentation(INDENT, 2);
1833         writeParameterType(s, cppClass, it.value());
1834     }
1835 
1836     s << Qt::endl;
1837 
1838     for (AbstractMetaFunction *func : qAsConst(lst))
1839         writeFormattedText(s, func->documentation().value(), cppClass);
1840 }
1841 
parseArgDocStyle(const AbstractMetaClass *,const AbstractMetaFunction * func)1842 QString QtDocGenerator::parseArgDocStyle(const AbstractMetaClass* /* cppClass */,
1843                                          const AbstractMetaFunction* func)
1844 {
1845     QString ret;
1846     int optArgs = 0;
1847 
1848     const AbstractMetaArgumentList &arguments = func->arguments();
1849     for (AbstractMetaArgument *arg : arguments) {
1850 
1851         if (func->argumentRemoved(arg->argumentIndex() + 1))
1852             continue;
1853 
1854         bool thisIsoptional = !arg->defaultValueExpression().isEmpty();
1855         if (optArgs || thisIsoptional) {
1856             ret += QLatin1Char('[');
1857             optArgs++;
1858         }
1859 
1860         if (arg->argumentIndex() > 0)
1861             ret += QLatin1String(", ");
1862 
1863         ret += arg->name();
1864 
1865         if (thisIsoptional) {
1866             QString defValue = arg->defaultValueExpression();
1867             if (defValue == QLatin1String("QString()")) {
1868                 defValue = QLatin1String("\"\"");
1869             } else if (defValue == QLatin1String("QStringList()")
1870                        || defValue.startsWith(QLatin1String("QVector"))
1871                        || defValue.startsWith(QLatin1String("QList"))) {
1872                 defValue = QLatin1String("list()");
1873             } else if (defValue == QLatin1String("QVariant()")) {
1874                 defValue = none();
1875             } else {
1876                 defValue.replace(QLatin1String("::"), QLatin1String("."));
1877                 if (defValue == QLatin1String("nullptr"))
1878                     defValue = none();
1879                 else if (defValue == QLatin1String("0") && arg->type()->isObject())
1880                     defValue = none();
1881             }
1882             ret += QLatin1Char('=') + defValue;
1883         }
1884     }
1885 
1886     ret += QString(optArgs, QLatin1Char(']'));
1887     return ret;
1888 }
1889 
writeDocSnips(QTextStream & s,const CodeSnipList & codeSnips,TypeSystem::CodeSnipPosition position,TypeSystem::Language language)1890 void QtDocGenerator::writeDocSnips(QTextStream &s,
1891                                  const CodeSnipList &codeSnips,
1892                                  TypeSystem::CodeSnipPosition position,
1893                                  TypeSystem::Language language)
1894 {
1895     Indentation indentation(INDENT);
1896     QStringList invalidStrings;
1897     const static QString startMarkup = QLatin1String("[sphinx-begin]");
1898     const static QString endMarkup = QLatin1String("[sphinx-end]");
1899 
1900     invalidStrings << QLatin1String("*") << QLatin1String("//") << QLatin1String("/*") << QLatin1String("*/");
1901 
1902     for (const CodeSnip &snip : codeSnips) {
1903         if ((snip.position != position) ||
1904             !(snip.language & language))
1905             continue;
1906 
1907         QString code = snip.code();
1908         while (code.contains(startMarkup) && code.contains(endMarkup)) {
1909             int startBlock = code.indexOf(startMarkup) + startMarkup.size();
1910             int endBlock = code.indexOf(endMarkup);
1911 
1912             if ((startBlock == -1) || (endBlock == -1))
1913                 break;
1914 
1915             QString codeBlock = code.mid(startBlock, endBlock - startBlock);
1916             const QStringList rows = codeBlock.split(QLatin1Char('\n'));
1917             int currentRow = 0;
1918             int offset = 0;
1919 
1920             for (QString row : rows) {
1921                 for (const QString &invalidString : qAsConst(invalidStrings))
1922                     row.remove(invalidString);
1923 
1924                 if (row.trimmed().size() == 0) {
1925                     if (currentRow == 0)
1926                         continue;
1927                     s << Qt::endl;
1928                 }
1929 
1930                 if (currentRow == 0) {
1931                     //find offset
1932                     for (auto c : row) {
1933                         if (c == QLatin1Char(' '))
1934                             offset++;
1935                         else if (c == QLatin1Char('\n'))
1936                             offset = 0;
1937                         else
1938                             break;
1939                     }
1940                 }
1941                 s << row.midRef(offset) << Qt::endl;
1942                 currentRow++;
1943             }
1944 
1945             code = code.mid(endBlock+endMarkup.size());
1946         }
1947     }
1948 }
1949 
writeInjectDocumentation(QTextStream & s,TypeSystem::DocModificationMode mode,const AbstractMetaClass * cppClass,const AbstractMetaFunction * func)1950 bool QtDocGenerator::writeInjectDocumentation(QTextStream& s,
1951                                             TypeSystem::DocModificationMode mode,
1952                                             const AbstractMetaClass* cppClass,
1953                                             const AbstractMetaFunction* func)
1954 {
1955     Indentation indentation(INDENT);
1956     bool didSomething = false;
1957 
1958     const DocModificationList &mods = cppClass->typeEntry()->docModifications();
1959     for (const DocModification &mod : mods) {
1960         if (mod.mode() == mode) {
1961             bool modOk = func ? mod.signature() == func->minimalSignature() : mod.signature().isEmpty();
1962 
1963             if (modOk) {
1964                 Documentation doc;
1965                 Documentation::Format fmt;
1966 
1967                 if (mod.format() == TypeSystem::NativeCode)
1968                     fmt = Documentation::Native;
1969                 else if (mod.format() == TypeSystem::TargetLangCode)
1970                     fmt = Documentation::Target;
1971                 else
1972                     continue;
1973 
1974                 doc.setValue(mod.code(), Documentation::Detailed, fmt);
1975                 writeFormattedText(s, doc.value(), cppClass);
1976                 didSomething = true;
1977             }
1978         }
1979     }
1980 
1981     s << Qt::endl;
1982 
1983     // TODO: Deprecate the use of doc string on glue code.
1984     //       This is pre "add-function" and "inject-documentation" tags.
1985     const TypeSystem::CodeSnipPosition pos = mode == TypeSystem::DocModificationPrepend
1986         ? TypeSystem::CodeSnipPositionBeginning : TypeSystem::CodeSnipPositionEnd;
1987     if (func)
1988         writeDocSnips(s, func->injectedCodeSnips(), pos, TypeSystem::TargetLangCode);
1989     else
1990         writeDocSnips(s, cppClass->typeEntry()->codeSnips(), pos, TypeSystem::TargetLangCode);
1991     return didSomething;
1992 }
1993 
functionSignature(const AbstractMetaClass * cppClass,const AbstractMetaFunction * func)1994 QString QtDocGenerator::functionSignature(const AbstractMetaClass* cppClass, const AbstractMetaFunction* func)
1995 {
1996     QString funcName;
1997 
1998     funcName = cppClass->fullName();
1999     if (!func->isConstructor())
2000         funcName += QLatin1Char('.') + getFuncName(func);
2001 
2002     return funcName + QLatin1Char('(') + parseArgDocStyle(cppClass, func)
2003         + QLatin1Char(')');
2004 }
2005 
translateToPythonType(const AbstractMetaType * type,const AbstractMetaClass * cppClass)2006 QString QtDocGenerator::translateToPythonType(const AbstractMetaType* type, const AbstractMetaClass* cppClass)
2007 {
2008     static const QStringList nativeTypes = {boolT(), floatT(), intT(),
2009         QLatin1String("object"),
2010         QLatin1String("str")
2011     };
2012     const QString name = type->name();
2013     if (nativeTypes.contains(name))
2014         return name;
2015 
2016     static const QMap<QString, QString> typeMap = {
2017         { QLatin1String("PyObject"), QLatin1String("object") },
2018         { QLatin1String("QString"), QLatin1String("str") },
2019         { QLatin1String("uchar"), QLatin1String("str") },
2020         { QLatin1String("QStringList"), QLatin1String("list of strings") },
2021         { qVariantT(), QLatin1String("object") },
2022         { QLatin1String("quint32"), intT() },
2023         { QLatin1String("uint32_t"), intT() },
2024         { QLatin1String("quint64"), intT() },
2025         { QLatin1String("qint64"), intT() },
2026         { QLatin1String("size_t"), intT() },
2027         { QLatin1String("int64_t"), intT() },
2028         { QLatin1String("qreal"), floatT() }
2029     };
2030     const auto found = typeMap.find(name);
2031     if (found != typeMap.end())
2032         return found.value();
2033 
2034     QString strType;
2035     if (type->isConstant() && name == QLatin1String("char") && type->indirections() == 1) {
2036         strType = QLatin1String("str");
2037     } else if (name.startsWith(unsignedShortT())) {
2038         strType = intT();
2039     } else if (name.startsWith(unsignedT())) { // uint and ulong
2040         strType = intT();
2041     } else if (type->isContainer()) {
2042         QString strType = translateType(type, cppClass, Options(ExcludeConst) | ExcludeReference);
2043         strType.remove(QLatin1Char('*'));
2044         strType.remove(QLatin1Char('>'));
2045         strType.remove(QLatin1Char('<'));
2046         strType.replace(QLatin1String("::"), QLatin1String("."));
2047         if (strType.contains(QLatin1String("QList")) || strType.contains(QLatin1String("QVector"))) {
2048             strType.replace(QLatin1String("QList"), QLatin1String("list of "));
2049             strType.replace(QLatin1String("QVector"), QLatin1String("list of "));
2050         } else if (strType.contains(QLatin1String("QHash")) || strType.contains(QLatin1String("QMap"))) {
2051             strType.remove(QLatin1String("QHash"));
2052             strType.remove(QLatin1String("QMap"));
2053             QStringList types = strType.split(QLatin1Char(','));
2054             strType = QString::fromLatin1("Dictionary with keys of type %1 and values of type %2.")
2055                                          .arg(types[0], types[1]);
2056         }
2057     } else {
2058         const AbstractMetaClass *k = AbstractMetaClass::findClass(classes(), type->typeEntry());
2059         strType = k ? k->fullName() : type->name();
2060         strType = QStringLiteral(":any:`") + strType + QLatin1Char('`');
2061     }
2062     return strType;
2063 }
2064 
writeParameterType(QTextStream & s,const AbstractMetaClass * cppClass,const AbstractMetaArgument * arg)2065 void QtDocGenerator::writeParameterType(QTextStream& s, const AbstractMetaClass* cppClass, const AbstractMetaArgument* arg)
2066 {
2067     s << INDENT << ":param " << arg->name() << ": "
2068       << translateToPythonType(arg->type(), cppClass) << Qt::endl;
2069 }
2070 
writeFunctionParametersType(QTextStream & s,const AbstractMetaClass * cppClass,const AbstractMetaFunction * func)2071 void QtDocGenerator::writeFunctionParametersType(QTextStream &s, const AbstractMetaClass *cppClass,
2072                                                  const AbstractMetaFunction *func)
2073 {
2074     s << Qt::endl;
2075     const AbstractMetaArgumentList &funcArgs = func->arguments();
2076     for (AbstractMetaArgument *arg : funcArgs) {
2077 
2078         if (func->argumentRemoved(arg->argumentIndex() + 1))
2079             continue;
2080 
2081         writeParameterType(s, cppClass, arg);
2082     }
2083 
2084     if (!func->isConstructor() && !func->isVoid()) {
2085 
2086         QString retType;
2087         // check if the return type was modified
2088         const FunctionModificationList &mods = func->modifications();
2089         for (const FunctionModification &mod : mods) {
2090             for (const ArgumentModification &argMod : mod.argument_mods) {
2091                 if (argMod.index == 0) {
2092                     retType = argMod.modified_type;
2093                     break;
2094                 }
2095             }
2096         }
2097 
2098         if (retType.isEmpty())
2099             retType = translateToPythonType(func->type(), cppClass);
2100         s << INDENT << ":rtype: " << retType << Qt::endl;
2101     }
2102     s << Qt::endl;
2103 }
2104 
writeFunction(QTextStream & s,const AbstractMetaClass * cppClass,const AbstractMetaFunction * func,bool indexed)2105 void QtDocGenerator::writeFunction(QTextStream& s, const AbstractMetaClass* cppClass,
2106                                    const AbstractMetaFunction* func, bool indexed)
2107 {
2108     s << functionSignature(cppClass, func);
2109 
2110     {
2111         Indentation indentation(INDENT);
2112         if (!indexed)
2113             s << QLatin1Char('\n') << INDENT << QLatin1String(":noindex:");
2114         s << "\n\n";
2115         writeFunctionParametersType(s, cppClass, func);
2116         const auto version = versionOf(func->typeEntry());
2117         if (!version.isNull())
2118             s << INDENT << rstVersionAdded(version);
2119         if (func->attributes().testFlag(AbstractMetaAttributes::Deprecated))
2120             s << INDENT << rstDeprecationNote("function");
2121     }
2122     writeInjectDocumentation(s, TypeSystem::DocModificationPrepend, cppClass, func);
2123     if (!writeInjectDocumentation(s, TypeSystem::DocModificationReplace, cppClass, func)) {
2124         writeFormattedText(s, func->documentation(), cppClass, Documentation::Brief);
2125         writeFormattedText(s, func->documentation(), cppClass, Documentation::Detailed);
2126     }
2127     writeInjectDocumentation(s, TypeSystem::DocModificationAppend, cppClass, func);
2128 }
2129 
writeFancyToc(QTextStream & s,const QStringList & items,int cols=2)2130 static void writeFancyToc(QTextStream& s, const QStringList& items, int cols = 2)
2131 {
2132     using TocMap = QMap<QChar, QStringList>;
2133     TocMap tocMap;
2134     QChar Q = QLatin1Char('Q');
2135     QChar idx;
2136     for (QString item : items) {
2137         if (item.isEmpty())
2138             continue;
2139         item.chop(4); // Remove the .rst extension
2140         // skip namespace if necessary
2141         const QString className = item.split(QLatin1Char('.')).last();
2142         if (className.startsWith(Q) && className.length() > 1)
2143             idx = className[1];
2144         else
2145             idx = className[0];
2146         tocMap[idx] << item;
2147     }
2148     QtXmlToSphinx::Table table;
2149     QtXmlToSphinx::TableRow row;
2150 
2151     int itemsPerCol = (items.size() + tocMap.size()*2) / cols;
2152     QString currentColData;
2153     int i = 0;
2154     QTextStream ss(&currentColData);
2155     QMutableMapIterator<QChar, QStringList> it(tocMap);
2156     while (it.hasNext()) {
2157         it.next();
2158         std::sort(it.value().begin(), it.value().end());
2159 
2160         if (i)
2161             ss << Qt::endl;
2162 
2163         ss << "**" << it.key() << "**\n\n";
2164         i += 2; // a letter title is equivalent to two entries in space
2165         for (const QString &item : qAsConst(it.value())) {
2166             ss << "* :doc:`" << item << "`\n";
2167             ++i;
2168 
2169             // end of column detected!
2170             if (i > itemsPerCol) {
2171                 ss.flush();
2172                 QtXmlToSphinx::TableCell cell(currentColData);
2173                 row << cell;
2174                 currentColData.clear();
2175                 i = 0;
2176             }
2177         }
2178     }
2179     if (i) {
2180         ss.flush();
2181         QtXmlToSphinx::TableCell cell(currentColData);
2182         row << cell;
2183         currentColData.clear();
2184         i = 0;
2185     }
2186     table.appendRow(row);
2187     table.normalize();
2188     s << ".. container:: pysidetoc\n\n";
2189     s << table;
2190 }
2191 
finishGeneration()2192 bool QtDocGenerator::finishGeneration()
2193 {
2194     if (!classes().isEmpty())
2195         writeModuleDocumentation();
2196     if (!m_additionalDocumentationList.isEmpty())
2197         writeAdditionalDocumentation();
2198     return true;
2199 }
2200 
writeModuleDocumentation()2201 void QtDocGenerator::writeModuleDocumentation()
2202 {
2203     QMap<QString, QStringList>::iterator it = m_packages.begin();
2204     for (; it != m_packages.end(); ++it) {
2205         QString key = it.key();
2206         key.replace(QLatin1Char('.'), QLatin1Char('/'));
2207         QString outputDir = outputDirectory() + QLatin1Char('/') + key;
2208         FileOut output(outputDir + QLatin1String("/index.rst"));
2209         QTextStream& s = output.stream;
2210 
2211         s << ".. module:: " << it.key() << Qt::endl << Qt::endl;
2212 
2213         const QString &title = it.key();
2214         s << title << Qt::endl;
2215         s << Pad('*', title.length()) << Qt::endl << Qt::endl;
2216 
2217         /* Avoid showing "Detailed Description for *every* class in toc tree */
2218         Indentation indentation(INDENT);
2219         // Store the it.key() in a QString so that it can be stripped off unwanted
2220         // information when neeeded. For example, the RST files in the extras directory
2221         // doesn't include the PySide# prefix in their names.
2222         const QString moduleName = it.key();
2223         const int lastIndex = moduleName.lastIndexOf(QLatin1Char('.'));
2224 
2225         // Search for extra-sections
2226         if (!m_extraSectionDir.isEmpty()) {
2227             QDir extraSectionDir(m_extraSectionDir);
2228             if (!extraSectionDir.exists())
2229                 qCWarning(lcShibokenDoc) << m_extraSectionDir << "doesn't exist";
2230 
2231             QStringList fileList = extraSectionDir.entryList(QStringList() << (moduleName.mid(lastIndex + 1) + QLatin1String("?*.rst")), QDir::Files);
2232             QStringList::iterator it2 = fileList.begin();
2233             for (; it2 != fileList.end(); ++it2) {
2234                 QString origFileName(*it2);
2235                 it2->remove(0, moduleName.indexOf(QLatin1Char('.')));
2236                 QString newFilePath = outputDir + QLatin1Char('/') + *it2;
2237                 if (QFile::exists(newFilePath))
2238                     QFile::remove(newFilePath);
2239                 if (!QFile::copy(m_extraSectionDir + QLatin1Char('/') + origFileName, newFilePath)) {
2240                     qCDebug(lcShibokenDoc).noquote().nospace() << "Error copying extra doc "
2241                         << QDir::toNativeSeparators(m_extraSectionDir + QLatin1Char('/') + origFileName)
2242                         << " to " << QDir::toNativeSeparators(newFilePath);
2243                 }
2244             }
2245             it.value().append(fileList);
2246         }
2247 
2248         writeFancyToc(s, it.value());
2249 
2250         s << INDENT << ".. container:: hide\n\n" << indent(INDENT)
2251             << INDENT << ".. toctree::\n" << indent(INDENT)
2252             << INDENT << ":maxdepth: 1\n\n";
2253         for (const QString &className : qAsConst(it.value()))
2254             s << INDENT << className << Qt::endl;
2255         s << "\n\n" << outdent(INDENT) << outdent(INDENT)
2256             << "Detailed Description\n--------------------\n\n";
2257 
2258         // module doc is always wrong and C++istic, so go straight to the extra directory!
2259         QFile moduleDoc(m_extraSectionDir + QLatin1Char('/') + moduleName.mid(lastIndex + 1) + QLatin1String(".rst"));
2260         if (moduleDoc.open(QIODevice::ReadOnly | QIODevice::Text)) {
2261             s << moduleDoc.readAll();
2262             moduleDoc.close();
2263         } else {
2264             // try the normal way
2265             Documentation moduleDoc = m_docParser->retrieveModuleDocumentation(it.key());
2266             if (moduleDoc.format() == Documentation::Native) {
2267                 QString context = it.key();
2268                 stripPythonQualifiers(&context);
2269                 QtXmlToSphinx x(this, moduleDoc.value(), context);
2270                 s << x;
2271             } else {
2272                 s << moduleDoc.value();
2273             }
2274         }
2275     }
2276 }
2277 
msgNonExistentAdditionalDocFile(const QString & dir,const QString & fileName)2278 static inline QString msgNonExistentAdditionalDocFile(const QString &dir,
2279                                                       const QString &fileName)
2280 {
2281     const QString result = QLatin1Char('"') + fileName
2282         + QLatin1String("\" does not exist in ")
2283         + QDir::toNativeSeparators(dir) + QLatin1Char('.');
2284     return result;
2285 }
2286 
writeAdditionalDocumentation()2287 void QtDocGenerator::writeAdditionalDocumentation()
2288 {
2289     QFile additionalDocumentationFile(m_additionalDocumentationList);
2290     if (!additionalDocumentationFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
2291         qCWarning(lcShibokenDoc, "%s",
2292                   qPrintable(msgCannotOpenForReading(additionalDocumentationFile)));
2293         return;
2294     }
2295 
2296     QDir outDir(outputDirectory());
2297     const QString rstSuffix = fileNameSuffix();
2298 
2299     QString errorMessage;
2300     int successCount = 0;
2301     int count = 0;
2302 
2303     QString targetDir = outDir.absolutePath();
2304 
2305     while (!additionalDocumentationFile.atEnd()) {
2306         const QByteArray lineBA = additionalDocumentationFile.readLine().trimmed();
2307         if (lineBA.isEmpty() || lineBA.startsWith('#'))
2308             continue;
2309         const QString line = QFile::decodeName(lineBA);
2310         // Parse "[directory]" specification
2311         if (line.size() > 2 && line.startsWith(QLatin1Char('[')) && line.endsWith(QLatin1Char(']'))) {
2312             const QString dir = line.mid(1, line.size() - 2);
2313             if (dir.isEmpty() || dir == QLatin1String(".")) {
2314                 targetDir = outDir.absolutePath();
2315             } else {
2316                 if (!outDir.exists(dir) && !outDir.mkdir(dir)) {
2317                     qCWarning(lcShibokenDoc, "Cannot create directory %s under %s",
2318                               qPrintable(dir),
2319                               qPrintable(QDir::toNativeSeparators(outputDirectory())));
2320                     break;
2321                 }
2322                 targetDir = outDir.absoluteFilePath(dir);
2323             }
2324         } else {
2325             // Normal file entry
2326             QFileInfo fi(m_docDataDir + QLatin1Char('/') + line);
2327             if (fi.isFile()) {
2328                 const QString rstFileName = fi.baseName() + rstSuffix;
2329                 const QString rstFile = targetDir + QLatin1Char('/') + rstFileName;
2330                 const QString context = targetDir.mid(targetDir.lastIndexOf(QLatin1Char('/')) + 1);
2331                 if (QtXmlToSphinx::convertToRst(this, fi.absoluteFilePath(),
2332                                                 rstFile, context, &errorMessage)) {
2333                     ++successCount;
2334                     qCDebug(lcShibokenDoc).nospace().noquote() << __FUNCTION__
2335                         << " converted " << fi.fileName()
2336                         << ' ' << rstFileName;
2337                 } else {
2338                     qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage));
2339                 }
2340             } else {
2341                 qCWarning(lcShibokenDoc, "%s",
2342                           qPrintable(msgNonExistentAdditionalDocFile(m_docDataDir, line)));
2343             }
2344             ++count;
2345         }
2346     }
2347     additionalDocumentationFile.close();
2348 
2349     qCInfo(lcShibokenDoc, "Created %d/%d additional documentation files.",
2350            successCount, count);
2351 }
2352 
2353 #ifdef __WIN32__
2354 #   define PATH_SEP ';'
2355 #else
2356 #   define PATH_SEP ':'
2357 #endif
2358 
doSetup()2359 bool QtDocGenerator::doSetup()
2360 {
2361     if (m_codeSnippetDirs.isEmpty())
2362         m_codeSnippetDirs = m_libSourceDir.split(QLatin1Char(PATH_SEP));
2363 
2364     if (!m_docParser)
2365         m_docParser = new QtDocParser;
2366 
2367     if (m_libSourceDir.isEmpty() || m_docDataDir.isEmpty()) {
2368         qCWarning(lcShibokenDoc) << "Documentation data dir and/or Qt source dir not informed, "
2369                                  "documentation will not be extracted from Qt sources.";
2370         return false;
2371     }
2372 
2373     m_docParser->setDocumentationDataDirectory(m_docDataDir);
2374     m_docParser->setLibrarySourceDirectory(m_libSourceDir);
2375     return true;
2376 }
2377 
2378 
options() const2379 Generator::OptionDescriptions QtDocGenerator::options() const
2380 {
2381     return OptionDescriptions()
2382         << qMakePair(QLatin1String("doc-parser=<parser>"),
2383                      QLatin1String("The documentation parser used to interpret the documentation\n"
2384                                    "input files (qdoc|doxygen)"))
2385         << qMakePair(QLatin1String("documentation-code-snippets-dir=<dir>"),
2386                      QLatin1String("Directory used to search code snippets used by the documentation"))
2387         << qMakePair(QLatin1String("documentation-data-dir=<dir>"),
2388                      QLatin1String("Directory with XML files generated by documentation tool"))
2389         << qMakePair(QLatin1String("documentation-extra-sections-dir=<dir>"),
2390                      QLatin1String("Directory used to search for extra documentation sections"))
2391         << qMakePair(QLatin1String("library-source-dir=<dir>"),
2392                      QLatin1String("Directory where library source code is located"))
2393         << qMakePair(additionalDocumentationOption() + QLatin1String("=<file>"),
2394                      QLatin1String("List of additional XML files to be converted to .rst files\n"
2395                                    "(for example, tutorials)."));
2396 }
2397 
handleOption(const QString & key,const QString & value)2398 bool QtDocGenerator::handleOption(const QString &key, const QString &value)
2399 {
2400     if (key == QLatin1String("library-source-dir")) {
2401         m_libSourceDir = value;
2402         return true;
2403     }
2404     if (key == QLatin1String("documentation-data-dir")) {
2405         m_docDataDir = value;
2406         return true;
2407     }
2408     if (key == QLatin1String("documentation-code-snippets-dir")) {
2409         m_codeSnippetDirs = value.split(QLatin1Char(PATH_SEP));
2410         return true;
2411     }
2412     if (key == QLatin1String("documentation-extra-sections-dir")) {
2413         m_extraSectionDir = value;
2414         return true;
2415     }
2416     if (key == QLatin1String("doc-parser")) {
2417         qCDebug(lcShibokenDoc).noquote().nospace() << "doc-parser: " << value;
2418         if (value == QLatin1String("doxygen"))
2419             m_docParser = new DoxygenParser;
2420         return true;
2421     }
2422     if (key == additionalDocumentationOption()) {
2423         m_additionalDocumentationList = value;
2424         return true;
2425     }
2426     return false;
2427 }
2428