aboutsummaryrefslogtreecommitdiffstats
path: root/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp')
-rw-r--r--sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp959
1 files changed, 616 insertions, 343 deletions
diff --git a/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp b/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp
index 20681f426..55c1d2090 100644
--- a/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp
+++ b/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp
@@ -1,45 +1,23 @@
-/****************************************************************************
-**
-** Copyright (C) 2020 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qt for Python.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qtxmltosphinx.h"
+#include "exception.h"
#include "qtxmltosphinxinterface.h"
+#include <codesniphelpers.h>
#include "rstformat.h"
+#include "qtcompat.h"
+
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
+#include <QtCore/QHash>
#include <QtCore/QLoggingCategory>
#include <QtCore/QRegularExpression>
#include <QtCore/QXmlStreamReader>
-static inline QString nameAttribute() { return QStringLiteral("name"); }
-static inline QString titleAttribute() { return QStringLiteral("title"); }
-static inline QString fullTitleAttribute() { return QStringLiteral("fulltitle"); }
+using namespace Qt::StringLiterals;
QString msgTagWarning(const QXmlStreamReader &reader, const QString &context,
const QString &tag, const QString &message)
@@ -57,78 +35,130 @@ QString msgTagWarning(const QXmlStreamReader &reader, const QString &context,
return result;
}
-QString msgFallbackWarning(const QXmlStreamReader &reader, const QString &context,
- const QString &tag, const QString &location,
- const QString &identifier, const QString &fallback)
+QString msgFallbackWarning(const QString &location, const QString &identifier,
+ const QString &fallback)
{
- QString message = QLatin1String("Falling back to \"")
- + QDir::toNativeSeparators(fallback) + QLatin1String("\" for \"")
- + location + QLatin1Char('"');
+ QString message = u"Falling back to \""_s
+ + QDir::toNativeSeparators(fallback) + u"\" for \""_s
+ + location + u'"';
if (!identifier.isEmpty())
- message += QLatin1String(" [") + identifier + QLatin1Char(']');
- return msgTagWarning(reader, context, tag, message);
+ message += u" ["_s + identifier + u']';
+ return message;
}
-struct QtXmlToSphinx::LinkContext
+QString msgSnippetsResolveError(const QString &path, const QStringList &locations)
{
- enum Type
- {
- Method = 0x1, Function = 0x2,
- FunctionMask = Method | Function,
- Class = 0x4, Attribute = 0x8, Module = 0x10,
- Reference = 0x20, External= 0x40
+ QString result;
+ QTextStream(&result) << "Could not resolve \"" << path << R"(" in ")"
+ << locations.join(uR"(", ")"_s);
+ return result;
+}
+
+static bool isHttpLink(const QString &ref)
+{
+ return ref.startsWith(u"http://") || ref.startsWith(u"https://");
+}
+
+static QString trimRight(QString s)
+{
+ while (!s.isEmpty() && s.crbegin()->isSpace())
+ s.chop(1);
+ return s;
+}
+
+static QString trimLeadingNewlines(QString s)
+{
+ while (!s.isEmpty() && s.at(0) == u'\n')
+ s.remove(0, 1);
+ return s;
+}
+
+QDebug operator<<(QDebug d, const QtXmlToSphinxLink &l)
+{
+ static const QHash<QtXmlToSphinxLink::Type, const char *> typeName = {
+ {QtXmlToSphinxLink::Method, "Method"},
+ {QtXmlToSphinxLink::Function, "Function"},
+ {QtXmlToSphinxLink::Class, "Class"},
+ {QtXmlToSphinxLink::Attribute, "Attribute"},
+ {QtXmlToSphinxLink::Module, "Module"},
+ {QtXmlToSphinxLink::Reference, "Reference"},
+ {QtXmlToSphinxLink::External, "External"},
};
- enum Flags { InsideBold = 0x1, InsideItalic = 0x2 };
+ QDebugStateSaver saver(d);
+ d.noquote();
+ d.nospace();
+ d << "QtXmlToSphinxLinkContext(" << typeName.value(l.type, "") << ", ref=\""
+ << l.linkRef << '"';
+ if (!l.linkText.isEmpty())
+ d << ", text=\"" << l.linkText << '"';
+ d << ')';
+ return d;
+}
- explicit LinkContext(const QString &ref) : linkRef(ref) {}
+QDebug operator<<(QDebug debug, const QtXmlToSphinx::TableCell &c)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "Cell(\"" << c.data << '"';
+ if (c.colSpan != 0)
+ debug << ", colSpan=" << c.colSpan;
+ if (c.rowSpan != 0)
+ debug << ", rowSpan=" << c.rowSpan;
+ debug << ')';
+ return debug;
+}
- QString linkRef;
- QString linkText;
- Type type = Reference;
- int flags = 0;
-};
+QDebug operator<<(QDebug debug, const QtXmlToSphinx::Table &t)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ t.formatDebug(debug);
+ return debug;
+}
-static const char *linkKeyWord(QtXmlToSphinx::LinkContext::Type type)
+static const char *linkKeyWord(QtXmlToSphinxLink::Type type)
{
switch (type) {
- case QtXmlToSphinx::LinkContext::Method:
+ case QtXmlToSphinxLink::Method:
return ":meth:";
- case QtXmlToSphinx::LinkContext::Function:
+ case QtXmlToSphinxLink::Function:
return ":func:";
- case QtXmlToSphinx::LinkContext::Class:
+ case QtXmlToSphinxLink::Class:
return ":class:";
- case QtXmlToSphinx::LinkContext::Attribute:
+ case QtXmlToSphinxLink::Attribute:
return ":attr:";
- case QtXmlToSphinx::LinkContext::Module:
+ case QtXmlToSphinxLink::Module:
return ":mod:";
- case QtXmlToSphinx::LinkContext::Reference:
+ case QtXmlToSphinxLink::Reference:
return ":ref:";
- case QtXmlToSphinx::LinkContext::External:
+ case QtXmlToSphinxLink::External:
break;
- case QtXmlToSphinx::LinkContext::FunctionMask:
+ case QtXmlToSphinxLink::FunctionMask:
break;
}
return "";
}
-TextStream &operator<<(TextStream &str, const QtXmlToSphinx::LinkContext &linkContext)
+TextStream &operator<<(TextStream &str, const QtXmlToSphinxLink &linkContext)
{
// Temporarily turn off bold/italic since links do not work within
- if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideBold)
+ if (linkContext.flags & QtXmlToSphinxLink::InsideBold)
str << "**";
- else if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideItalic)
+ else if (linkContext.flags & QtXmlToSphinxLink::InsideItalic)
str << '*';
str << ' ' << linkKeyWord(linkContext.type) << '`';
- const bool isExternal = linkContext.type == QtXmlToSphinx::LinkContext::External;
+ const bool isExternal = linkContext.type == QtXmlToSphinxLink::External;
if (!linkContext.linkText.isEmpty()) {
writeEscapedRstText(str, linkContext.linkText);
- if (isExternal && !linkContext.linkText.endsWith(QLatin1Char(' ')))
+ if (isExternal && !linkContext.linkText.endsWith(u' '))
str << ' ';
str << '<';
}
// Convert page titles to RST labels
- str << (linkContext.type == QtXmlToSphinx::LinkContext::Reference
+ str << (linkContext.type == QtXmlToSphinxLink::Reference
? toRstLabel(linkContext.linkRef) : linkContext.linkRef);
if (!linkContext.linkText.isEmpty())
str << '>';
@@ -136,9 +166,9 @@ TextStream &operator<<(TextStream &str, const QtXmlToSphinx::LinkContext &linkCo
if (isExternal)
str << '_';
str << ' ';
- if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideBold)
+ if (linkContext.flags & QtXmlToSphinxLink::InsideBold)
str << "**";
- else if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideItalic)
+ else if (linkContext.flags & QtXmlToSphinxLink::InsideItalic)
str << '*';
return str;
}
@@ -237,9 +267,8 @@ QtXmlToSphinx::QtXmlToSphinx(const QtXmlToSphinxDocGeneratorInterface *docGenera
const QtXmlToSphinxParameters &parameters,
const QString& doc, const QString& context)
: m_output(static_cast<QString *>(nullptr)),
- m_tableHasHeader(false), m_context(context),
- m_generator(docGenerator), m_parameters(parameters),
- m_insideBold(false), m_insideItalic(false)
+ m_context(context),
+ m_generator(docGenerator), m_parameters(parameters)
{
m_result = transform(doc);
}
@@ -276,7 +305,7 @@ void QtXmlToSphinx::callHandler(WebXmlTag t, QXmlStreamReader &r)
handleTableTag(r);
break;
case WebXmlTag::header:
- handleRowTag(r);
+ handleHeaderTag(r);
break;
case WebXmlTag::row:
handleRowTag(r);
@@ -388,35 +417,49 @@ void QtXmlToSphinx::callHandler(WebXmlTag t, QXmlStreamReader &r)
void QtXmlToSphinx::formatCurrentTable()
{
- if (m_currentTable.isEmpty())
+ Q_ASSERT(!m_tables.isEmpty());
+ auto &table = m_tables.back();
+ if (table.isEmpty())
return;
- m_currentTable.setHeaderEnabled(m_tableHasHeader);
- m_currentTable.normalize();
+ table.normalize();
m_output << '\n';
- m_currentTable.format(m_output);
+ table.format(m_output);
}
void QtXmlToSphinx::pushOutputBuffer()
{
- auto *buffer = new QString();
- m_buffers << buffer;
- m_output.setString(buffer);
+ m_buffers.append(std::make_shared<QString>());
+ m_output.setString(m_buffers.top().get());
}
QString QtXmlToSphinx::popOutputBuffer()
{
Q_ASSERT(!m_buffers.isEmpty());
- QString* str = m_buffers.pop();
- QString strcpy(*str);
- delete str;
- m_output.setString(m_buffers.isEmpty() ? 0 : m_buffers.top());
- return strcpy;
+ QString result(*m_buffers.top());
+ m_buffers.pop();
+ m_output.setString(m_buffers.isEmpty() ? nullptr : m_buffers.top().get());
+ return result;
+}
+
+constexpr auto autoTranslatedPlaceholder = "AUTO_GENERATED\n"_L1;
+constexpr auto autoTranslatedNote =
+R"(.. warning::
+ This section contains snippets that were automatically
+ translated from C++ to Python and may contain errors.
+
+)"_L1;
+
+void QtXmlToSphinx::setAutoTranslatedNote(QString *str) const
+{
+ if (m_containsAutoTranslations)
+ str->replace(autoTranslatedPlaceholder, autoTranslatedNote);
+ else
+ str->remove(autoTranslatedPlaceholder);
}
QString QtXmlToSphinx::transform(const QString& doc)
{
Q_ASSERT(m_buffers.isEmpty());
- Indentation indentation(m_output);
if (doc.trimmed().isEmpty())
return doc;
@@ -424,6 +467,9 @@ QString QtXmlToSphinx::transform(const QString& doc)
QXmlStreamReader reader(doc);
+ m_output << autoTranslatedPlaceholder;
+ Indentation indentation(m_output);
+
while (!reader.atEnd()) {
QXmlStreamReader::TokenType token = reader.readNext();
if (reader.hasError()) {
@@ -432,7 +478,7 @@ QString QtXmlToSphinx::transform(const QString& doc)
<< reader.errorString() << " at " << reader.lineNumber()
<< ':' << reader.columnNumber() << '\n' << doc;
m_output << message;
- warn(message);
+ throw Exception(message);
break;
}
@@ -455,7 +501,7 @@ QString QtXmlToSphinx::transform(const QString& doc)
if (!m_inlineImages.isEmpty()) {
// Write out inline image definitions stored in handleInlineImageTag().
m_output << '\n' << disableIndent;
- for (const InlineImage &img : qAsConst(m_inlineImages))
+ for (const InlineImage &img : std::as_const(m_inlineImages))
m_output << ".. |" << img.tag << "| image:: " << img.href << '\n';
m_output << '\n' << enableIndent;
m_inlineImages.clear();
@@ -464,13 +510,14 @@ QString QtXmlToSphinx::transform(const QString& doc)
m_output.flush();
QString retval = popOutputBuffer();
Q_ASSERT(m_buffers.isEmpty());
+ setAutoTranslatedNote(&retval);
return retval;
}
static QString resolveFile(const QStringList &locations, const QString &path)
{
for (QString location : locations) {
- location.append(QLatin1Char('/'));
+ location.append(u'/');
location.append(path);
if (QFileInfo::exists(location))
return location;
@@ -478,25 +525,170 @@ static QString resolveFile(const QStringList &locations, const QString &path)
return QString();
}
-QString QtXmlToSphinx::readFromLocations(const QStringList &locations, const QString &path,
- const QString &identifier, QString *errorMessage)
+enum class SnippetType
+{
+ Other, // .qdoc, .qml,...
+ CppSource, CppHeader // Potentially converted to Python
+};
+
+SnippetType snippetType(const QString &path)
+{
+ if (path.endsWith(u".cpp"))
+ return SnippetType::CppSource;
+ if (path.endsWith(u".h"))
+ return SnippetType::CppHeader;
+ return SnippetType::Other;
+}
+
+// Return the name of a .cpp/.h snippet converted to Python by snippets-translate
+static QString pySnippetName(const QString &path, SnippetType type)
{
+ switch (type) {
+ case SnippetType::CppSource:
+ return path.left(path.size() - 3) + u"py"_s;
+ break;
+ case SnippetType::CppHeader:
+ return path + u".py"_s;
+ break;
+ default:
+ break;
+ }
+ return {};
+}
+
+QtXmlToSphinx::Snippet QtXmlToSphinx::readSnippetFromLocations(const QString &path,
+ const QString &identifier,
+ const QString &fallbackPath,
+ QString *errorMessage)
+{
+ // For anything else but C++ header/sources (no conversion to Python),
+ // use existing fallback paths first.
+ const auto type = snippetType(path);
+ if (type == SnippetType::Other && !fallbackPath.isEmpty()) {
+ const QString code = readFromLocation(fallbackPath, identifier, errorMessage);
+ return {code, code.isNull() ? Snippet::Error : Snippet::Fallback};
+ }
+
+ // For C++ header/sources, try snippets converted to Python first.
QString resolvedPath;
- if (path.endsWith(QLatin1String(".cpp"))) {
- const QString pySnippet = path.left(path.size() - 3) + QLatin1String("py");
- resolvedPath = resolveFile(locations, pySnippet);
+ const auto &locations = m_parameters.codeSnippetDirs;
+
+ if (type != SnippetType::Other) {
+ if (!fallbackPath.isEmpty() && !m_parameters.codeSnippetRewriteOld.isEmpty()) {
+ // Try looking up Python converted snippets by rewriting snippets paths
+ QString rewrittenPath = pySnippetName(fallbackPath, type);
+ if (!rewrittenPath.isEmpty()) {
+ rewrittenPath.replace(m_parameters.codeSnippetRewriteOld,
+ m_parameters.codeSnippetRewriteNew);
+ const QString code = readFromLocation(rewrittenPath, identifier, errorMessage);
+ m_containsAutoTranslations = true;
+ return {code, code.isNull() ? Snippet::Error : Snippet::Converted};
+ }
+ }
+
+ resolvedPath = resolveFile(locations, pySnippetName(path, type));
+ if (!resolvedPath.isEmpty()) {
+ const QString code = readFromLocation(resolvedPath, identifier, errorMessage);
+ return {code, code.isNull() ? Snippet::Error : Snippet::Converted};
+ }
+ }
+
+ resolvedPath = resolveFile(locations, path);
+ if (!resolvedPath.isEmpty()) {
+ const QString code = readFromLocation(resolvedPath, identifier, errorMessage);
+ return {code, code.isNull() ? Snippet::Error : Snippet::Resolved};
+ }
+
+ if (!fallbackPath.isEmpty()) {
+ *errorMessage = msgFallbackWarning(path, identifier, fallbackPath);
+ const QString code = readFromLocation(fallbackPath, identifier, errorMessage);
+ return {code, code.isNull() ? Snippet::Error : Snippet::Fallback};
}
- if (resolvedPath.isEmpty())
- resolvedPath = resolveFile(locations, path);
- if (resolvedPath.isEmpty()) {
- QTextStream(errorMessage) << "Could not resolve \"" << path << "\" in \""
- << locations.join(QLatin1String("\", \""));
- return QString(); // null
+
+ *errorMessage = msgSnippetsResolveError(path, locations);
+ return {{}, Snippet::Error};
+}
+
+// Helpers for extracting qdoc snippets "#/// [id]"
+static QString fileNameOfDevice(const QIODevice *inputFile)
+{
+ const auto *file = qobject_cast<const QFile *>(inputFile);
+ return file ? QDir::toNativeSeparators(file->fileName()) : u"<stdin>"_s;
+}
+
+static QString msgSnippetNotFound(const QIODevice &inputFile,
+ const QString &identifier)
+{
+ return u"Code snippet file found ("_s + fileNameOfDevice(&inputFile)
+ + u"), but snippet ["_s + identifier + u"] not found."_s;
+}
+
+static QString msgEmptySnippet(const QIODevice &inputFile, int lineNo,
+ const QString &identifier)
+{
+ return u"Empty code snippet ["_s + identifier + u"] at "_s
+ + fileNameOfDevice(&inputFile) + u':' + QString::number(lineNo);
+}
+
+// Pattern to match qdoc snippet IDs with "#/// [id]" comments and helper to find ID
+static const QRegularExpression &snippetIdPattern()
+{
+ static const QRegularExpression result(uR"RX((//|#) *! *\[([^]]+)\])RX"_s);
+ Q_ASSERT(result.isValid());
+ return result;
+}
+
+static bool matchesSnippetId(QRegularExpressionMatchIterator it,
+ const QString &identifier)
+{
+ while (it.hasNext()) {
+ if (it.next().captured(2) == identifier)
+ return true;
}
- qCDebug(m_generator->loggingCategory()).noquote().nospace()
- << "snippet file " << path
- << " [" << identifier << ']' << " resolved to " << resolvedPath;
- return readFromLocation(resolvedPath, identifier, errorMessage);
+ return false;
+}
+
+QString QtXmlToSphinx::readSnippet(QIODevice &inputFile, const QString &identifier,
+ QString *errorMessage)
+{
+ const QByteArray identifierBA = identifier.toUtf8();
+ // Lambda that matches the snippet id
+ const auto snippetIdPred = [&identifierBA, &identifier](const QByteArray &lineBA)
+ {
+ const bool isComment = lineBA.contains('/') || lineBA.contains('#');
+ if (!isComment || !lineBA.contains(identifierBA))
+ return false;
+ const QString line = QString::fromUtf8(lineBA);
+ return matchesSnippetId(snippetIdPattern().globalMatch(line), identifier);
+ };
+
+ // Find beginning, skip over
+ int lineNo = 1;
+ for (; !inputFile.atEnd() && !snippetIdPred(inputFile.readLine());
+ ++lineNo) {
+ }
+
+ if (inputFile.atEnd()) {
+ *errorMessage = msgSnippetNotFound(inputFile, identifier);
+ return {};
+ }
+
+ QString code;
+ for (; !inputFile.atEnd(); ++lineNo) {
+ const QString line = QString::fromUtf8(inputFile.readLine());
+ auto it = snippetIdPattern().globalMatch(line);
+ if (it.hasNext()) { // Skip snippet id lines
+ if (matchesSnippetId(it, identifier))
+ break;
+ } else {
+ code += line;
+ }
+ }
+
+ if (code.isEmpty())
+ *errorMessage = msgEmptySnippet(inputFile, lineNo, identifier);
+
+ return code;
}
QString QtXmlToSphinx::readFromLocation(const QString &location, const QString &identifier,
@@ -508,44 +700,18 @@ QString QtXmlToSphinx::readFromLocation(const QString &location, const QString &
QTextStream(errorMessage) << "Could not read code snippet file: "
<< QDir::toNativeSeparators(inputFile.fileName())
<< ": " << inputFile.errorString();
- return QString(); // null
+ return {}; // null
}
- QString code = QLatin1String(""); // non-null
+ QString code = u""_s; // non-null
if (identifier.isEmpty()) {
while (!inputFile.atEnd())
code += QString::fromUtf8(inputFile.readLine());
- return code;
- }
-
- const QRegularExpression searchString(QLatin1String("//!\\s*\\[")
- + identifier + QLatin1String("\\]"));
- Q_ASSERT(searchString.isValid());
- static const QRegularExpression codeSnippetCode(QLatin1String("//!\\s*\\[[\\w\\d\\s]+\\]"));
- Q_ASSERT(codeSnippetCode.isValid());
-
- bool getCode = false;
-
- while (!inputFile.atEnd()) {
- QString line = QString::fromUtf8(inputFile.readLine());
- if (getCode && !line.contains(searchString)) {
- line.remove(codeSnippetCode);
- code += line;
- } else if (line.contains(searchString)) {
- if (getCode)
- break;
- getCode = true;
- }
+ return CodeSnipHelpers::fixSpaces(code);
}
- if (!getCode) {
- QTextStream(errorMessage) << "Code snippet file found ("
- << QDir::toNativeSeparators(location) << "), but snippet ["
- << identifier << "] not found.";
- return QString(); // null
- }
-
- return code;
+ code = readSnippet(inputFile, identifier, errorMessage);
+ return code.isEmpty() ? QString{} : CodeSnipHelpers::fixSpaces(code); // maintain isNull()
}
void QtXmlToSphinx::handleHeadingTag(QXmlStreamReader& reader)
@@ -555,7 +721,7 @@ void QtXmlToSphinx::handleHeadingTag(QXmlStreamReader& reader)
static char types[] = { '-', '^' };
QXmlStreamReader::TokenType token = reader.tokenType();
if (token == QXmlStreamReader::StartElement) {
- uint typeIdx = reader.attributes().value(QLatin1String("level")).toUInt();
+ uint typeIdx = reader.attributes().value(u"level"_s).toUInt();
if (typeIdx >= sizeof(types))
type = types[sizeof(types)-1];
else
@@ -572,80 +738,133 @@ void QtXmlToSphinx::handleHeadingTag(QXmlStreamReader& reader)
void QtXmlToSphinx::handleParaTag(QXmlStreamReader& reader)
{
- QXmlStreamReader::TokenType token = reader.tokenType();
- if (token == QXmlStreamReader::StartElement) {
- pushOutputBuffer();
- } else if (token == QXmlStreamReader::EndElement) {
- QString result = popOutputBuffer().simplified();
- if (result.startsWith(QLatin1String("**Warning:**")))
- result.replace(0, 12, QLatin1String(".. warning:: "));
- else if (result.startsWith(QLatin1String("**Note:**")))
- result.replace(0, 9, QLatin1String(".. note:: "));
+ switch (reader.tokenType()) {
+ case QXmlStreamReader::StartElement:
+ handleParaTagStart();
+ break;
+ case QXmlStreamReader::EndElement:
+ handleParaTagEnd();
+ break;
+ case QXmlStreamReader::Characters:
+ handleParaTagText(reader);
+ break;
+ default:
+ break;
+ }
+}
- m_output << result << "\n\n";
- } else if (token == QXmlStreamReader::Characters) {
- const auto text = reader.text();
- const QChar end = m_output.lastChar();
- if (!text.isEmpty() && m_output.indentation() == 0 && !end.isNull()) {
- QChar start = text[0];
- if ((end == QLatin1Char('*') || end == QLatin1Char('`')) && start != QLatin1Char(' ') && !start.isPunct())
- m_output << '\\';
- }
- m_output << escape(text);
+void QtXmlToSphinx::handleParaTagStart()
+{
+ pushOutputBuffer();
+}
+
+void QtXmlToSphinx::handleParaTagText(QXmlStreamReader& reader)
+{
+ const auto text = reader.text();
+ const QChar end = m_output.lastChar();
+ if (!text.isEmpty() && m_output.indentation() == 0 && !end.isNull()) {
+ QChar start = text[0];
+ if ((end == u'*' || end == u'`') && start != u' ' && !start.isPunct())
+ m_output << '\\';
}
+ m_output << escape(text);
+}
+
+void QtXmlToSphinx::handleParaTagEnd()
+{
+ QString result = popOutputBuffer().simplified();
+ if (result.startsWith(u"**Warning:**"))
+ result.replace(0, 12, ".. warning:: "_L1);
+ else if (result.startsWith(u"**Note:**"))
+ result.replace(0, 9, ".. note:: "_L1);
+ m_output << result << "\n\n";
}
void QtXmlToSphinx::handleItalicTag(QXmlStreamReader& reader)
{
- QXmlStreamReader::TokenType token = reader.tokenType();
- if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement) {
- m_insideItalic = !m_insideItalic;
- m_output << '*';
- } else if (token == QXmlStreamReader::Characters) {
+ switch (reader.tokenType()) {
+ case QXmlStreamReader::StartElement:
+ if (m_formattingDepth++ == 0) {
+ m_insideItalic = true;
+ m_output << rstItalic;
+ }
+ break;
+ case QXmlStreamReader::EndElement:
+ if (--m_formattingDepth == 0) {
+ m_insideItalic = false;
+ m_output << rstItalicOff;
+ }
+ break;
+ case QXmlStreamReader::Characters:
m_output << escape(reader.text().trimmed());
+ break;
+ default:
+ break;
}
}
void QtXmlToSphinx::handleBoldTag(QXmlStreamReader& reader)
{
- QXmlStreamReader::TokenType token = reader.tokenType();
- if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement) {
- m_insideBold = !m_insideBold;
- m_output << "**";
- } else if (token == QXmlStreamReader::Characters) {
+ switch (reader.tokenType()) {
+ case QXmlStreamReader::StartElement:
+ if (m_formattingDepth++ == 0) {
+ m_insideBold = true;
+ m_output << rstBold;
+ }
+ break;
+ case QXmlStreamReader::EndElement:
+ if (--m_formattingDepth == 0) {
+ m_insideBold = false;
+ m_output << rstBoldOff;
+ }
+ break;
+ case QXmlStreamReader::Characters:
m_output << escape(reader.text().trimmed());
+ break;
+ default:
+ break;
}
}
void QtXmlToSphinx::handleArgumentTag(QXmlStreamReader& reader)
{
- QXmlStreamReader::TokenType token = reader.tokenType();
- if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement)
- m_output << "``";
- else if (token == QXmlStreamReader::Characters)
+ switch (reader.tokenType()) {
+ case QXmlStreamReader::StartElement:
+ if (m_formattingDepth++ == 0)
+ m_output << rstCode;
+ break;
+ case QXmlStreamReader::EndElement:
+ if (--m_formattingDepth == 0)
+ m_output << rstCodeOff;
+ break;
+ case QXmlStreamReader::Characters:
m_output << reader.text().trimmed();
+ break;
+ default:
+ break;
+ }
}
-static inline QString functionLinkType() { return QStringLiteral("function"); }
-static inline QString classLinkType() { return QStringLiteral("class"); }
+constexpr auto functionLinkType = "function"_L1;
+constexpr auto classLinkType = "class"_L1;
static inline QString fixLinkType(QStringView type)
{
// TODO: create a flag PROPERTY-AS-FUNCTION to ask if the properties
// are recognized as such or not in the binding
- if (type == QLatin1String("property"))
- return functionLinkType();
- if (type == QLatin1String("typedef"))
- return classLinkType();
+ if (type == u"property")
+ return functionLinkType;
+ if (type == u"typedef")
+ return classLinkType;
return type.toString();
}
static inline QString linkSourceAttribute(const QString &type)
{
- if (type == functionLinkType() || type == classLinkType())
- return QLatin1String("raw");
- return type == QLatin1String("enum") || type == QLatin1String("page")
- ? type : QLatin1String("href");
+ if (type == functionLinkType || type == classLinkType)
+ return u"raw"_s;
+ return type == u"enum" || type == u"page"
+ ? type : u"href"_s;
}
// "See also" links may appear as nested links:
@@ -667,8 +886,8 @@ void QtXmlToSphinx::handleSeeAlsoTag(QXmlStreamReader& reader)
if (!textR.isEmpty()) {
const QString text = textR.toString();
if (m_seeAlsoContext.isNull()) {
- const QString type = text.endsWith(QLatin1String("()"))
- ? functionLinkType() : classLinkType();
+ const QString type = text.endsWith(u"()")
+ ? functionLinkType : classLinkType;
m_seeAlsoContext.reset(handleLinkStart(type, text));
}
handleLinkText(m_seeAlsoContext.data(), text);
@@ -687,12 +906,12 @@ void QtXmlToSphinx::handleSeeAlsoTag(QXmlStreamReader& reader)
}
}
-static inline QString fallbackPathAttribute() { return QStringLiteral("path"); }
+constexpr auto fallbackPathAttribute = "path"_L1;
template <class Indent> // const char*/class Indentor
void formatSnippet(TextStream &str, Indent indent, const QString &snippet)
{
- const auto lines = QStringView{snippet}.split(QLatin1Char('\n'));
+ const auto lines = QStringView{snippet}.split(u'\n');
for (const auto &line : lines) {
if (!line.trimmed().isEmpty())
str << indent << line;
@@ -718,68 +937,61 @@ void QtXmlToSphinx::handleSnippetTag(QXmlStreamReader& reader)
{
QXmlStreamReader::TokenType token = reader.tokenType();
if (token == QXmlStreamReader::StartElement) {
- const bool consecutiveSnippet = m_lastTagName == QLatin1String("snippet")
- || m_lastTagName == QLatin1String("dots") || m_lastTagName == QLatin1String("codeline");
+ const bool consecutiveSnippet = m_lastTagName == u"snippet"
+ || m_lastTagName == u"dots" || m_lastTagName == u"codeline";
if (consecutiveSnippet) {
m_output.flush();
- m_output.string()->chop(2);
+ m_output.string()->chop(1); // Strip newline from previous snippet
}
- QString location = reader.attributes().value(QLatin1String("location")).toString();
- QString identifier = reader.attributes().value(QLatin1String("identifier")).toString();
+ QString location = reader.attributes().value(u"location"_s).toString();
+ QString identifier = reader.attributes().value(u"identifier"_s).toString();
+ QString fallbackPath;
+ if (reader.attributes().hasAttribute(fallbackPathAttribute))
+ fallbackPath = reader.attributes().value(fallbackPathAttribute).toString();
QString errorMessage;
- const QString pythonCode =
- readFromLocations(m_parameters.codeSnippetDirs, location, identifier, &errorMessage);
+
+ const Snippet snippet = readSnippetFromLocations(location, identifier,
+ fallbackPath, &errorMessage);
if (!errorMessage.isEmpty())
warn(msgTagWarning(reader, m_context, m_lastTagName, errorMessage));
- // Fall back to C++ snippet when "path" attribute is present.
- // Also read fallback snippet when comparison is desired.
- QString fallbackCode;
- if ((pythonCode.isEmpty() || m_parameters.snippetComparison)
- && reader.attributes().hasAttribute(fallbackPathAttribute())) {
- const QString fallback = reader.attributes().value(fallbackPathAttribute()).toString();
- if (QFileInfo::exists(fallback)) {
- if (pythonCode.isEmpty())
- warn(msgFallbackWarning(reader, m_context, m_lastTagName, location, identifier, fallback));
- fallbackCode = readFromLocation(fallback, identifier, &errorMessage);
- if (!errorMessage.isEmpty())
- warn(msgTagWarning(reader, m_context, m_lastTagName, errorMessage));
- }
- }
- if (!pythonCode.isEmpty() && !fallbackCode.isEmpty() && m_parameters.snippetComparison)
- debug(msgSnippetComparison(location, identifier, pythonCode, fallbackCode));
+ if (m_parameters.snippetComparison && snippet.result == Snippet::Converted
+ && !fallbackPath.isEmpty()) {
+ const QString fallbackCode = readFromLocation(fallbackPath, identifier, &errorMessage);
+ debug(msgSnippetComparison(location, identifier, snippet.code, fallbackCode));
+ }
if (!consecutiveSnippet)
m_output << "::\n\n";
Indentation indentation(m_output);
- const QString code = pythonCode.isEmpty() ? fallbackCode : pythonCode;
- if (code.isEmpty())
+ if (snippet.result == Snippet::Error)
m_output << "<Code snippet \"" << location << ':' << identifier << "\" not found>\n";
else
- m_output << code << ensureEndl;
+ m_output << snippet.code << ensureEndl;
m_output << '\n';
}
}
+
void QtXmlToSphinx::handleDotsTag(QXmlStreamReader& reader)
{
QXmlStreamReader::TokenType token = reader.tokenType();
if (token == QXmlStreamReader::StartElement) {
- const bool consecutiveSnippet = m_lastTagName == QLatin1String("snippet")
- || m_lastTagName == QLatin1String("dots") || m_lastTagName == QLatin1String("codeline");
+ const bool consecutiveSnippet = m_lastTagName == u"snippet"
+ || m_lastTagName == u"dots" || m_lastTagName == u"codeline";
if (consecutiveSnippet) {
m_output.flush();
m_output.string()->chop(2);
} else {
m_output << "::\n\n";
}
- Indentation indentation(m_output);
pushOutputBuffer();
- int indent = reader.attributes().value(QLatin1String("indent")).toInt();
+ int indent = reader.attributes().value(u"indent"_s).toInt()
+ + m_output.indentation() * m_output.tabWidth();
for (int i = 0; i < indent; ++i)
m_output << ' ';
} else if (token == QXmlStreamReader::Characters) {
- m_output << reader.text().toString();
+ m_output << reader.text().toString().trimmed();
} else if (token == QXmlStreamReader::EndElement) {
m_output << disableIndent << popOutputBuffer() << "\n\n\n" << enableIndent;
}
@@ -789,12 +1001,15 @@ void QtXmlToSphinx::handleTableTag(QXmlStreamReader& reader)
{
QXmlStreamReader::TokenType token = reader.tokenType();
if (token == QXmlStreamReader::StartElement) {
- m_currentTable.clear();
- m_tableHasHeader = false;
+ if (parentTag() == WebXmlTag::para)
+ handleParaTagEnd(); // End <para> to prevent the table from being rst-escaped
+ m_tables.push({});
} else if (token == QXmlStreamReader::EndElement) {
// write the table on m_output
formatCurrentTable();
- m_currentTable.clear();
+ m_tables.pop();
+ if (parentTag() == WebXmlTag::para)
+ handleParaTagStart();
}
}
@@ -804,11 +1019,11 @@ void QtXmlToSphinx::handleTermTag(QXmlStreamReader& reader)
if (token == QXmlStreamReader::StartElement) {
pushOutputBuffer();
} else if (token == QXmlStreamReader::Characters) {
- m_output << reader.text().toString().replace(QLatin1String("::"), QLatin1String("."));
+ m_output << reader.text().toString().replace(u"::"_s, u"."_s);
} else if (token == QXmlStreamReader::EndElement) {
TableCell cell;
cell.data = popOutputBuffer().trimmed();
- m_currentTable.appendRow(TableRow(1, cell));
+ m_tables.back().appendRow(TableRow(1, cell));
}
}
@@ -817,70 +1032,83 @@ void QtXmlToSphinx::handleItemTag(QXmlStreamReader& reader)
{
QXmlStreamReader::TokenType token = reader.tokenType();
if (token == QXmlStreamReader::StartElement) {
- if (m_currentTable.isEmpty())
- m_currentTable.appendRow({});
- TableRow& row = m_currentTable.last();
+ auto &table = m_tables.back();
+ if (table.isEmpty())
+ table.appendRow({});
+ TableRow& row = table.last();
TableCell cell;
- cell.colSpan = reader.attributes().value(QLatin1String("colspan")).toShort();
- cell.rowSpan = reader.attributes().value(QLatin1String("rowspan")).toShort();
+ cell.colSpan = reader.attributes().value(u"colspan"_s).toShort();
+ cell.rowSpan = reader.attributes().value(u"rowspan"_s).toShort();
row << cell;
pushOutputBuffer();
} else if (token == QXmlStreamReader::EndElement) {
- QString data = popOutputBuffer().trimmed();
- if (!m_currentTable.isEmpty()) {
- TableRow& row = m_currentTable.last();
+ QString data = trimLeadingNewlines(trimRight(popOutputBuffer()));
+ auto &table = m_tables.back();
+ if (!table.isEmpty()) {
+ TableRow& row = table.last();
if (!row.isEmpty())
row.last().data = data;
}
}
}
-void QtXmlToSphinx::handleRowTag(QXmlStreamReader& reader)
+void QtXmlToSphinx::handleHeaderTag(QXmlStreamReader &reader)
{
- QXmlStreamReader::TokenType token = reader.tokenType();
- if (token == QXmlStreamReader::StartElement) {
- m_tableHasHeader = reader.name() == QLatin1String("header");
- m_currentTable.appendRow({});
+ // <header> in WebXML is either a table header or a description of a
+ // C++ header with "name"/"href" attributes.
+ if (reader.tokenType() == QXmlStreamReader::StartElement
+ && !reader.attributes().hasAttribute(u"name"_s)) {
+ auto &table = m_tables.back();
+ table.setHeaderEnabled(true);
+ table.appendRow({});
}
}
+void QtXmlToSphinx::handleRowTag(QXmlStreamReader& reader)
+{
+ if (reader.tokenType() == QXmlStreamReader::StartElement)
+ m_tables.back().appendRow({});
+}
+
enum ListType { BulletList, OrderedList, EnumeratedList };
static inline ListType webXmlListType(QStringView t)
{
- if (t == QLatin1String("enum"))
+ if (t == u"enum")
return EnumeratedList;
- if (t == QLatin1String("ordered"))
+ if (t == u"ordered")
return OrderedList;
return BulletList;
}
void QtXmlToSphinx::handleListTag(QXmlStreamReader& reader)
{
- // BUG We do not support a list inside a table cell
static ListType listType = BulletList;
QXmlStreamReader::TokenType token = reader.tokenType();
if (token == QXmlStreamReader::StartElement) {
- listType = webXmlListType(reader.attributes().value(QLatin1String("type")));
+ m_tables.push({});
+ auto &table = m_tables.back();
+ listType = webXmlListType(reader.attributes().value(u"type"_s));
if (listType == EnumeratedList) {
- m_currentTable.appendRow(TableRow{TableCell(QLatin1String("Constant")),
- TableCell(QLatin1String("Description"))});
- m_tableHasHeader = true;
+ table.appendRow(TableRow{TableCell(u"Constant"_s),
+ TableCell(u"Description"_s)});
+ table.setHeaderEnabled(true);
}
m_output.indent();
} else if (token == QXmlStreamReader::EndElement) {
m_output.outdent();
- if (!m_currentTable.isEmpty()) {
+ const auto &table = m_tables.back();
+ if (!table.isEmpty()) {
switch (listType) {
case BulletList:
case OrderedList: {
m_output << '\n';
const char *separator = listType == BulletList ? "* " : "#. ";
const char *indentLine = listType == BulletList ? " " : " ";
- for (const TableCell &cell : m_currentTable.constFirst()) {
- const auto itemLines = QStringView{cell.data}.split(QLatin1Char('\n'));
+ for (const TableCell &cell : table.constFirst()) {
+ const auto itemLines = QStringView{cell.data}.split(u'\n');
m_output << separator << itemLines.constFirst() << '\n';
- for (int i = 1, max = itemLines.count(); i < max; ++i)
+ for (qsizetype i = 1, max = itemLines.size(); i < max; ++i)
m_output << indentLine << itemLines[i] << '\n';
}
m_output << '\n';
@@ -891,7 +1119,7 @@ void QtXmlToSphinx::handleListTag(QXmlStreamReader& reader)
break;
}
}
- m_currentTable.clear();
+ m_tables.pop();
}
}
@@ -901,7 +1129,7 @@ void QtXmlToSphinx::handleLinkTag(QXmlStreamReader& reader)
case QXmlStreamReader::StartElement: {
// <link> embedded in <see-also> means the characters of <see-also> are no link.
m_seeAlsoContext.reset();
- const QString type = fixLinkType(reader.attributes().value(QLatin1String("type")));
+ const QString type = fixLinkType(reader.attributes().value(u"type"_s));
const QString ref = reader.attributes().value(linkSourceAttribute(type)).toString();
m_linkContext.reset(handleLinkStart(type, ref));
}
@@ -920,90 +1148,94 @@ void QtXmlToSphinx::handleLinkTag(QXmlStreamReader& reader)
}
}
-QtXmlToSphinx::LinkContext *QtXmlToSphinx::handleLinkStart(const QString &type, QString ref) const
+QtXmlToSphinxLink *QtXmlToSphinx::handleLinkStart(const QString &type, QString ref) const
{
- ref.replace(QLatin1String("::"), QLatin1String("."));
- ref.remove(QLatin1String("()"));
- auto *result = new LinkContext(ref);
+ ref.replace(u"::"_s, u"."_s);
+ ref.remove(u"()"_s);
+ auto *result = new QtXmlToSphinxLink(ref);
if (m_insideBold)
- result->flags |= LinkContext::InsideBold;
+ result->flags |= QtXmlToSphinxLink::InsideBold;
else if (m_insideItalic)
- result->flags |= LinkContext::InsideItalic;
+ result->flags |= QtXmlToSphinxLink::InsideItalic;
- if (type == functionLinkType() && !m_context.isEmpty()) {
- result->type = LinkContext::Method;
- const auto rawlinklist = QStringView{result->linkRef}.split(QLatin1Char('.'));
+ if (type == u"external" || isHttpLink(ref)) {
+ result->type = QtXmlToSphinxLink::External;
+ } else if (type == functionLinkType && !m_context.isEmpty()) {
+ result->type = QtXmlToSphinxLink::Method;
+ const auto rawlinklist = QStringView{result->linkRef}.split(u'.');
if (rawlinklist.size() == 1 || rawlinklist.constFirst() == m_context) {
const auto lastRawLink = rawlinklist.constLast().toString();
QString context = m_generator->resolveContextForMethod(m_context, lastRawLink);
if (!result->linkRef.startsWith(context))
- result->linkRef.prepend(context + QLatin1Char('.'));
+ result->linkRef.prepend(context + u'.');
} else {
result->linkRef = m_generator->expandFunction(result->linkRef);
}
- } else if (type == functionLinkType() && m_context.isEmpty()) {
- result->type = LinkContext::Function;
- } else if (type == classLinkType()) {
- result->type = LinkContext::Class;
+ } else if (type == functionLinkType && m_context.isEmpty()) {
+ result->type = QtXmlToSphinxLink::Function;
+ } else if (type == classLinkType) {
+ result->type = QtXmlToSphinxLink::Class;
result->linkRef = m_generator->expandClass(m_context, result->linkRef);
- } else if (type == QLatin1String("enum")) {
- result->type = LinkContext::Attribute;
- } else if (type == QLatin1String("page")) {
+ } else if (type == u"enum") {
+ result->type = QtXmlToSphinxLink::Attribute;
+ } else if (type == u"page") {
// Module, external web page or reference
if (result->linkRef == m_parameters.moduleName)
- result->type = LinkContext::Module;
- else if (result->linkRef.startsWith(QLatin1String("http")))
- result->type = LinkContext::External;
+ result->type = QtXmlToSphinxLink::Module;
else
- result->type = LinkContext::Reference;
- } else if (type == QLatin1String("external")) {
- result->type = LinkContext::External;
+ result->type = QtXmlToSphinxLink::Reference;
} else {
- result->type = LinkContext::Reference;
+ result->type = QtXmlToSphinxLink::Reference;
}
return result;
}
// <link raw="Model/View Classes" href="model-view-programming.html#model-view-classes"
// type="page" page="Model/View Programming">Model/View Classes</link>
-// <link type="page" page="http://doc.qt.io/qt-5/class.html">QML types</link>
+// <link type="page" page="https://doc.qt.io/qt-5/class.html">QML types</link>
// <link raw="Qt Quick" href="qtquick-index.html" type="page" page="Qt Quick">Qt Quick</link>
// <link raw="QObject" href="qobject.html" type="class">QObject</link>
// <link raw="Qt::Window" href="qt.html#WindowType-enum" type="enum" enum="Qt::WindowType">Qt::Window</link>
// <link raw="QNetworkSession::reject()" href="qnetworksession.html#reject" type="function">QNetworkSession::reject()</link>
-static QString fixLinkText(const QtXmlToSphinx::LinkContext *linkContext,
+static QString fixLinkText(const QtXmlToSphinxLink *linkContext,
QString linktext)
{
- if (linkContext->type == QtXmlToSphinx::LinkContext::External
- || linkContext->type == QtXmlToSphinx::LinkContext::Reference) {
+ if (linkContext->type == QtXmlToSphinxLink::External
+ || linkContext->type == QtXmlToSphinxLink::Reference) {
return linktext;
}
// For the language reference documentation, strip the module name.
// Clear the link text if that matches the function/class/enumeration name.
- const int lastSep = linktext.lastIndexOf(QLatin1String("::"));
+ const int lastSep = linktext.lastIndexOf(u"::");
if (lastSep != -1)
linktext.remove(0, lastSep + 2);
else
QtXmlToSphinx::stripPythonQualifiers(&linktext);
if (linkContext->linkRef == linktext)
- return QString();
- if ((linkContext->type & QtXmlToSphinx::LinkContext::FunctionMask) != 0
- && (linkContext->linkRef + QLatin1String("()")) == linktext) {
- return QString();
+ return {};
+ if ((linkContext->type & QtXmlToSphinxLink::FunctionMask) != 0
+ && (linkContext->linkRef + u"()"_s) == linktext) {
+ return {};
}
return linktext;
}
-void QtXmlToSphinx::handleLinkText(LinkContext *linkContext, const QString &linktext)
+void QtXmlToSphinx::handleLinkText(QtXmlToSphinxLink *linkContext, const QString &linktext)
{
linkContext->linkText = fixLinkText(linkContext, linktext);
}
-void QtXmlToSphinx::handleLinkEnd(LinkContext *linkContext)
+void QtXmlToSphinx::handleLinkEnd(QtXmlToSphinxLink *linkContext)
+{
+ m_output << m_generator->resolveLink(*linkContext);
+}
+
+WebXmlTag QtXmlToSphinx::parentTag() const
{
- m_output << *linkContext;
+ const auto index = m_tagStack.size() - 2;
+ return index >= 0 ? m_tagStack.at(index) : WebXmlTag::Unknown;
}
// Copy images that are placed in a subdirectory "images" under the webxml files
@@ -1012,8 +1244,8 @@ static bool copyImage(const QString &href, const QString &docDataDir,
const QString &context, const QString &outputDir,
const QLoggingCategory &lc, QString *errorMessage)
{
- const QChar slash = QLatin1Char('/');
- const int lastSlash = href.lastIndexOf(slash);
+ const QChar slash = u'/';
+ const auto lastSlash = href.lastIndexOf(slash);
const QString imagePath = lastSlash != -1 ? href.left(lastSlash) : QString();
const QString imageFileName = lastSlash != -1 ? href.right(href.size() - lastSlash - 1) : href;
QFileInfo imageSource(docDataDir + slash + href);
@@ -1026,10 +1258,10 @@ static bool copyImage(const QString &href, const QString &docDataDir,
// FIXME: Not perfect yet, should have knowledge about namespaces (DataVis3D) or
// nested classes "Pyside2.QtGui.QTouchEvent.QTouchPoint".
QString relativeTargetDir = context;
- const int lastDot = relativeTargetDir.lastIndexOf(QLatin1Char('.'));
+ const auto lastDot = relativeTargetDir.lastIndexOf(u'.');
if (lastDot != -1)
relativeTargetDir.truncate(lastDot);
- relativeTargetDir.replace(QLatin1Char('.'), slash);
+ relativeTargetDir.replace(u'.', slash);
if (!imagePath.isEmpty())
relativeTargetDir += slash + imagePath;
@@ -1069,7 +1301,7 @@ bool QtXmlToSphinx::copyImage(const QString &href) const
m_generator->loggingCategory(),
&errorMessage);
if (!result)
- warn(errorMessage);
+ throw Exception(errorMessage);
return result;
}
@@ -1077,7 +1309,7 @@ void QtXmlToSphinx::handleImageTag(QXmlStreamReader& reader)
{
if (reader.tokenType() != QXmlStreamReader::StartElement)
return;
- const QString href = reader.attributes().value(QLatin1String("href")).toString();
+ const QString href = reader.attributes().value(u"href"_s).toString();
if (copyImage(href))
m_output << ".. image:: " << href << "\n\n";
}
@@ -1086,17 +1318,17 @@ void QtXmlToSphinx::handleInlineImageTag(QXmlStreamReader& reader)
{
if (reader.tokenType() != QXmlStreamReader::StartElement)
return;
- const QString href = reader.attributes().value(QLatin1String("href")).toString();
+ const QString href = reader.attributes().value(u"href"_s).toString();
if (!copyImage(href))
return;
// Handle inline images by substitution references. Insert a unique tag
// enclosed by '|' and define it further down. Determine tag from the base
//file name with number.
QString tag = href;
- int pos = tag.lastIndexOf(QLatin1Char('/'));
+ auto pos = tag.lastIndexOf(u'/');
if (pos != -1)
tag.remove(0, pos + 1);
- pos = tag.indexOf(QLatin1Char('.'));
+ pos = tag.indexOf(u'.');
if (pos != -1)
tag.truncate(pos);
tag += QString::number(m_inlineImages.size() + 1);
@@ -1108,7 +1340,7 @@ void QtXmlToSphinx::handleRawTag(QXmlStreamReader& reader)
{
QXmlStreamReader::TokenType token = reader.tokenType();
if (token == QXmlStreamReader::StartElement) {
- QString format = reader.attributes().value(QLatin1String("format")).toString();
+ QString format = reader.attributes().value(u"format"_s).toString();
m_output << ".. raw:: " << format.toLower() << "\n\n";
} else if (token == QXmlStreamReader::Characters) {
Indentation indent(m_output);
@@ -1161,11 +1393,11 @@ void QtXmlToSphinx::handlePageTag(QXmlStreamReader &reader)
m_output << disableIndent;
- const auto title = reader.attributes().value(titleAttribute());
+ const auto title = reader.attributes().value("title");
if (!title.isEmpty())
m_output << rstLabel(title.toString());
- const auto fullTitle = reader.attributes().value(fullTitleAttribute());
+ const auto fullTitle = reader.attributes().value("fulltitle");
const int size = fullTitle.isEmpty()
? writeEscapedRstText(m_output, title)
: writeEscapedRstText(m_output, fullTitle);
@@ -1178,7 +1410,7 @@ void QtXmlToSphinx::handleTargetTag(QXmlStreamReader &reader)
{
if (reader.tokenType() != QXmlStreamReader::StartElement)
return;
- const auto name = reader.attributes().value(nameAttribute());
+ const auto name = reader.attributes().value("name");
if (!name.isEmpty())
m_output << rstLabel(name.toString());
}
@@ -1198,14 +1430,14 @@ void QtXmlToSphinx::handleAnchorTag(QXmlStreamReader& reader)
QXmlStreamReader::TokenType token = reader.tokenType();
if (token == QXmlStreamReader::StartElement) {
QString anchor;
- if (reader.attributes().hasAttribute(QLatin1String("id")))
- anchor = reader.attributes().value(QLatin1String("id")).toString();
- else if (reader.attributes().hasAttribute(QLatin1String("name")))
- anchor = reader.attributes().value(QLatin1String("name")).toString();
+ if (reader.attributes().hasAttribute(u"id"_s))
+ anchor = reader.attributes().value(u"id"_s).toString();
+ else if (reader.attributes().hasAttribute(u"name"_s))
+ anchor = reader.attributes().value(u"name"_s).toString();
if (!anchor.isEmpty() && m_opened_anchor != anchor) {
m_opened_anchor = anchor;
if (!m_context.isEmpty())
- anchor.prepend(m_context + QLatin1Char('_'));
+ anchor.prepend(m_context + u'_');
m_output << rstLabel(anchor);
}
} else if (token == QXmlStreamReader::EndElement) {
@@ -1224,7 +1456,7 @@ void QtXmlToSphinx::handleQuoteFileTag(QXmlStreamReader& reader)
QXmlStreamReader::TokenType token = reader.tokenType();
if (token == QXmlStreamReader::Characters) {
QString location = reader.text().toString();
- location.prepend(m_parameters.libSourceDir + QLatin1Char('/'));
+ location.prepend(m_parameters.libSourceDir + u'/');
QString errorMessage;
QString code = readFromLocation(location, QString(), &errorMessage);
if (!errorMessage.isEmpty())
@@ -1239,51 +1471,72 @@ void QtXmlToSphinx::handleQuoteFileTag(QXmlStreamReader& reader)
}
}
+bool QtXmlToSphinx::Table::hasEmptyLeadingRow() const
+{
+ return !m_rows.isEmpty() && m_rows.constFirst().isEmpty();
+}
+
+bool QtXmlToSphinx::Table::hasEmptyTrailingRow() const
+{
+ return !m_rows.isEmpty() && m_rows.constLast().isEmpty();
+}
+
void QtXmlToSphinx::Table::normalize()
{
- if (m_normalized || isEmpty())
+ if (m_normalized)
+ return;
+
+ // Empty leading/trailing rows have been observed with nested tables
+ if (hasEmptyLeadingRow() || hasEmptyLeadingRow()) {
+ qWarning() << "QtXmlToSphinx: Table with leading/trailing empty columns found: " << *this;
+ while (hasEmptyTrailingRow())
+ m_rows.pop_back();
+ while (hasEmptyLeadingRow())
+ m_rows.pop_front();
+ }
+
+ if (isEmpty())
return;
//QDoc3 generates tables with wrong number of columns. We have to
//check and if necessary, merge the last columns.
- int maxCols = -1;
- for (const auto &row : qAsConst(m_rows)) {
- if (row.count() > maxCols)
- maxCols = row.count();
+ qsizetype maxCols = -1;
+ for (const auto &row : std::as_const(m_rows)) {
+ if (row.size() > maxCols)
+ maxCols = row.size();
}
if (maxCols <= 0)
return;
// add col spans
- for (int row = 0; row < m_rows.count(); ++row) {
- for (int col = 0; col < m_rows.at(row).count(); ++col) {
+ for (qsizetype row = 0; row < m_rows.size(); ++row) {
+ for (qsizetype col = 0; col < m_rows.at(row).size(); ++col) {
QtXmlToSphinx::TableCell& cell = m_rows[row][col];
bool mergeCols = (col >= maxCols);
if (cell.colSpan > 0) {
QtXmlToSphinx::TableCell newCell;
newCell.colSpan = -1;
- for (int i = 0, max = cell.colSpan-1; i < max; ++i) {
+ for (int i = 0, max = cell.colSpan-1; i < max; ++i)
m_rows[row].insert(col + 1, newCell);
- }
cell.colSpan = 0;
col++;
} else if (mergeCols) {
- m_rows[row][maxCols - 1].data += QLatin1Char(' ') + cell.data;
+ m_rows[row][maxCols - 1].data += u' ' + cell.data;
}
}
}
// row spans
- const int numCols = m_rows.constFirst().count();
- for (int col = 0; col < numCols; ++col) {
- for (int row = 0; row < m_rows.count(); ++row) {
- if (col < m_rows[row].count()) {
+ const qsizetype numCols = m_rows.constFirst().size();
+ for (qsizetype col = 0; col < numCols; ++col) {
+ for (qsizetype row = 0; row < m_rows.size(); ++row) {
+ if (col < m_rows[row].size()) {
QtXmlToSphinx::TableCell& cell = m_rows[row][col];
if (cell.rowSpan > 0) {
QtXmlToSphinx::TableCell newCell;
newCell.rowSpan = -1;
- int targetRow = row + 1;
- const int targetEndRow =
- std::min(targetRow + cell.rowSpan - 1, int(m_rows.count()));
+ qsizetype targetRow = row + 1;
+ const qsizetype targetEndRow =
+ std::min(targetRow + cell.rowSpan - 1, m_rows.size());
cell.rowSpan = 0;
for ( ; targetRow < targetEndRow; ++targetRow)
m_rows[targetRow].insert(col, newCell);
@@ -1303,16 +1556,17 @@ void QtXmlToSphinx::Table::format(TextStream& s) const
Q_ASSERT(isNormalized());
// calc width and height of each column and row
- const int headerColumnCount = m_rows.constFirst().count();
- QList<int> colWidths(headerColumnCount, 0);
- QList<int> rowHeights(m_rows.count(), 0);
- for (int i = 0, maxI = m_rows.count(); i < maxI; ++i) {
+ const qsizetype headerColumnCount = m_rows.constFirst().size();
+ QList<qsizetype> colWidths(headerColumnCount, 0);
+ QList<qsizetype> rowHeights(m_rows.size(), 0);
+ for (qsizetype i = 0, maxI = m_rows.size(); i < maxI; ++i) {
const QtXmlToSphinx::TableRow& row = m_rows.at(i);
- for (int j = 0, maxJ = std::min(row.count(), colWidths.size()); j < maxJ; ++j) {
- const auto rowLines = QStringView{row[j].data}.split(QLatin1Char('\n')); // cache this would be a good idea
+ for (qsizetype j = 0, maxJ = std::min(row.size(), colWidths.size()); j < maxJ; ++j) {
+ // cache this would be a good idea
+ const auto rowLines = QStringView{row[j].data}.split(u'\n');
for (const auto &str : rowLines)
- colWidths[j] = std::max(colWidths[j], int(str.size()));
- rowHeights[i] = std::max(rowHeights[i], int(rowLines.size()));
+ colWidths[j] = std::max(colWidths[j], str.size());
+ rowHeights[i] = std::max(rowHeights[i], rowLines.size());
}
}
@@ -1320,44 +1574,41 @@ void QtXmlToSphinx::Table::format(TextStream& s) const
return; // empty table (table with empty cells)
// create a horizontal line to be used later.
- QString horizontalLine = QLatin1String("+");
- for (int i = 0, max = colWidths.count(); i < max; ++i) {
- horizontalLine += QString(colWidths.at(i), QLatin1Char('-'));
- horizontalLine += QLatin1Char('+');
- }
+ QString horizontalLine = u"+"_s;
+ for (auto colWidth : colWidths)
+ horizontalLine += QString(colWidth, u'-') + u'+';
// write table rows
- for (int i = 0, maxI = m_rows.count(); i < maxI; ++i) { // for each row
+ for (qsizetype i = 0, maxI = m_rows.size(); i < maxI; ++i) { // for each row
const QtXmlToSphinx::TableRow& row = m_rows.at(i);
// print line
s << '+';
- for (int col = 0; col < headerColumnCount; ++col) {
- char c;
- if (col >= row.length() || row[col].rowSpan == -1)
+ for (qsizetype col = 0; col < headerColumnCount; ++col) {
+ char c = '-';
+ if (col >= row.size() || row[col].rowSpan == -1)
c = ' ';
else if (i == 1 && hasHeader())
c = '=';
- else
- c = '-';
s << Pad(c, colWidths.at(col)) << '+';
}
s << '\n';
// Print the table cells
- for (int rowLine = 0; rowLine < rowHeights[i]; ++rowLine) { // for each line in a row
- int j = 0;
- for (int maxJ = std::min(int(row.count()), headerColumnCount); j < maxJ; ++j) { // for each column
+ for (qsizetype rowLine = 0; rowLine < rowHeights.at(i); ++rowLine) { // for each line in a row
+ qsizetype j = 0;
+ for (qsizetype maxJ = std::min(row.size(), headerColumnCount); j < maxJ; ++j) { // for each column
const QtXmlToSphinx::TableCell& cell = row[j];
- const auto rowLines = QStringView{cell.data}.split(QLatin1Char('\n')); // FIXME: Cache this!!!
+ // FIXME: Cache this!!!
+ const auto rowLines = QStringView{cell.data}.split(u'\n');
if (!j || !cell.colSpan)
s << '|';
else
s << ' ';
- const int width = colWidths.at(j);
- if (rowLine < rowLines.count())
+ const auto width = int(colWidths.at(j));
+ if (rowLine < rowLines.size())
s << AlignedField(rowLines.at(rowLine), width);
else
s << Pad(' ', width);
@@ -1370,9 +1621,31 @@ void QtXmlToSphinx::Table::format(TextStream& s) const
s << horizontalLine << "\n\n";
}
+void QtXmlToSphinx::Table::formatDebug(QDebug &debug) const
+{
+ const auto rowCount = m_rows.size();
+ debug << "Table(" <<rowCount << " rows";
+ if (m_hasHeader)
+ debug << ", [header]";
+ if (m_normalized)
+ debug << ", [normalized]";
+ for (qsizetype r = 0; r < rowCount; ++r) {
+ const auto &row = m_rows.at(r);
+ const auto &colCount = row.size();
+ debug << ", row " << r << " [" << colCount << "]={";
+ for (qsizetype c = 0; c < colCount; ++c) {
+ if (c > 0)
+ debug << ", ";
+ debug << row.at(c);
+ }
+ debug << '}';
+ }
+ debug << ')';
+}
+
void QtXmlToSphinx::stripPythonQualifiers(QString *s)
{
- const int lastSep = s->lastIndexOf(QLatin1Char('.'));
+ const int lastSep = s->lastIndexOf(u'.');
if (lastSep != -1)
s->remove(0, lastSep + 1);
}