aboutsummaryrefslogtreecommitdiffstats
path: root/sources/shiboken6/ApiExtractor/classdocumentation.cpp
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2021-12-13 14:24:42 +0100
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2021-12-16 15:38:09 +0100
commitb00c9f19ff37f2fed57c780660001f5c1a25a5cb (patch)
treeed167eb1cd1ff9480c477aa166814565078e2475 /sources/shiboken6/ApiExtractor/classdocumentation.cpp
parent565ce8c4f32822034ffe9ff25d4093c7401d2e6a (diff)
shiboken6/Documentation: Replace XPath/Xquery usage for retrieving WebXML docs by a parser
shiboken used XPath expressions to extract the function descriptions from the qdoc-generated WebXML. With the deprecation of QtXmlPatterns in Qt 6, libxml/xslt was used for this. A major drawback is that libxml/xslt is unable to match entities in attributes which means functions taking parameters by const-ref could not be found. This required cumbersome workarounds involving repeated searches, which still were not sufficient to find all functions. To fix this, write a WebXML parser based on QXmlStreamReader that extracts the contents of a WebXML page into some simple data structures and use those for searching. On this occasion, also use the metafunction flags to correctly match binary operator functions. This reduces the number of functions for which no documentation can be found (from ~1100 to ~800 for the qtbase repository). From the remaining functions, ~230 are functions inherited from templates, which should at least be listed. Others are caused by qdoc not finding hidden friends. The remaining usages of libxml/xslt are the doxygen parser and the documentation modifications using XSLT. Task-number: PYSIDE-1106 Change-Id: I31c045ad42948afad884f40d5bbc8bee7555859e Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Diffstat (limited to 'sources/shiboken6/ApiExtractor/classdocumentation.cpp')
-rw-r--r--sources/shiboken6/ApiExtractor/classdocumentation.cpp414
1 files changed, 414 insertions, 0 deletions
diff --git a/sources/shiboken6/ApiExtractor/classdocumentation.cpp b/sources/shiboken6/ApiExtractor/classdocumentation.cpp
new file mode 100644
index 000000000..3acb131fd
--- /dev/null
+++ b/sources/shiboken6/ApiExtractor/classdocumentation.cpp
@@ -0,0 +1,414 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 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$
+**
+****************************************************************************/
+
+#include "classdocumentation.h"
+#include "messages.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QBuffer>
+#include <QtCore/QFile>
+#include <QtCore/QXmlStreamReader>
+#include <QtCore/QXmlStreamAttributes>
+#include <QtCore/QXmlStreamWriter>
+
+#include <algorithm>
+
+// Sort functions by name and argument count
+static bool functionDocumentationLessThan(const FunctionDocumentation &f1,
+ const FunctionDocumentation &f2)
+{
+ const int nc = f1.name.compare(f2.name);
+ if (nc != 0)
+ return nc < 0;
+ return f1.parameters.size() < f2.parameters.size();
+}
+
+static void sortDocumentation(ClassDocumentation *cd)
+{
+ std::stable_sort(cd->enums.begin(), cd->enums.end(),
+ [] (const EnumDocumentation &e1, const EnumDocumentation &e2) {
+ return e1.name < e2.name; });
+ std::stable_sort(cd->properties.begin(), cd->properties.end(),
+ [] (const PropertyDocumentation &p1, const PropertyDocumentation &p2) {
+ return p1.name < p2.name; });
+ std::stable_sort(cd->functions.begin(), cd->functions.end(),
+ functionDocumentationLessThan);
+}
+
+qsizetype ClassDocumentation::indexOfEnum(const QString &name) const
+{
+ for (qsizetype i = 0, size = enums.size(); i < size; ++i) {
+ if (enums.at(i).name == name)
+ return i;
+ }
+ return -1;
+}
+
+FunctionDocumentationList ClassDocumentation::findFunctionCandidates(const QString &name,
+ bool constant) const
+{
+ FunctionDocumentationList result;
+ std::copy_if(functions.cbegin(), functions.cend(),
+ std::back_inserter(result),
+ [name, constant](const FunctionDocumentation &fd) {
+ return fd.constant == constant && fd.name == name;
+ });
+ return result;
+}
+
+static bool matches(const FunctionDocumentation &fd, const FunctionDocumentationQuery &q)
+{
+ return fd.name == q.name && fd.constant == q.constant && fd.parameters == q.parameters;
+}
+
+qsizetype ClassDocumentation::indexOfFunction(const FunctionDocumentationList &fl,
+ const FunctionDocumentationQuery &q)
+{
+ for (qsizetype i = 0, size = fl.size(); i < size; ++i) {
+ if (matches(fl.at(i), q))
+ return i;
+ }
+ return -1;
+}
+
+qsizetype ClassDocumentation::indexOfProperty(const QString &name) const
+{
+ for (qsizetype i = 0, size = properties.size(); i < size; ++i) {
+ if (properties.at(i).name == name)
+ return i;
+ }
+ return -1;
+}
+
+enum class WebXmlTag
+{
+ Class, Description, Enum, Function, Parameter, Property, Typedef, Other
+};
+
+static WebXmlTag tag(QStringView name)
+{
+ if (name == u"class" || name == u"namespace")
+ return WebXmlTag::Class;
+ if (name == u"enum")
+ return WebXmlTag::Enum;
+ if (name == u"function")
+ return WebXmlTag::Function;
+ if (name == u"description")
+ return WebXmlTag::Description;
+ if (name == u"parameter")
+ return WebXmlTag::Parameter;
+ if (name == u"property")
+ return WebXmlTag::Property;
+ if (name == u"typedef")
+ return WebXmlTag::Typedef;
+ return WebXmlTag::Other;
+}
+
+static void parseWebXmlElement(WebXmlTag tag, const QXmlStreamAttributes &attributes,
+ ClassDocumentation *cd)
+{
+ switch (tag) {
+ case WebXmlTag::Class:
+ cd->name = attributes.value(u"name"_qs).toString();
+ break;
+ case WebXmlTag::Enum: {
+ EnumDocumentation ed;
+ ed.name = attributes.value(u"name"_qs).toString();
+ cd->enums.append(ed);
+ }
+ break;
+ case WebXmlTag::Function: {
+ FunctionDocumentation fd;
+ fd.name = attributes.value(u"name"_qs).toString();
+ fd.signature = attributes.value(u"signature"_qs).toString();
+ fd.returnType = attributes.value(u"type"_qs).toString();
+ fd.constant = attributes.value(u"const"_qs) == u"true";
+ cd->functions.append(fd);
+ }
+ break;
+ case WebXmlTag::Parameter:
+ Q_ASSERT(!cd->functions.isEmpty());
+ cd->functions.last().parameters.append(attributes.value(u"type"_qs).toString());
+ break;
+ case WebXmlTag::Property: {
+ PropertyDocumentation pd;
+ pd.name = attributes.value(u"name"_qs).toString();
+ cd->properties.append(pd);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+// Retrieve the contents of <description>
+static QString extractWebXmlDescription(QXmlStreamReader &reader)
+{
+ QBuffer buffer;
+ buffer.open(QIODeviceBase::WriteOnly);
+ QXmlStreamWriter writer(&buffer);
+
+ do {
+ switch (reader.tokenType()) {
+ case QXmlStreamReader::StartElement:
+ writer.writeStartElement(reader.name().toString());
+ writer.writeAttributes(reader.attributes());
+ break;
+ case QXmlStreamReader::Characters:
+ writer.writeCharacters(reader.text().toString());
+ break;
+ case QXmlStreamReader::EndElement:
+ writer.writeEndElement();
+ if (reader.name() == u"description") {
+ buffer.close();
+ return QString::fromUtf8(buffer.buffer()).trimmed();
+ }
+ break;
+ default:
+ break;
+ }
+ reader.readNext();
+ } while (!reader.atEnd());
+
+ return {};
+}
+
+static QString msgXmlError(const QString &fileName, const QXmlStreamReader &reader)
+{
+ QString result;
+ QTextStream(&result) << fileName << ':' << reader.lineNumber() << ':'
+ << reader.columnNumber() << ':' << reader.errorString();
+ return result;
+}
+
+ClassDocumentation parseWebXml(const QString &fileName, QString *errorMessage)
+{
+ ClassDocumentation result;
+
+ QFile file(fileName);
+ if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
+ *errorMessage = msgCannotOpenForReading(file);
+ return result;
+ }
+
+ WebXmlTag lastTag = WebXmlTag::Other;
+ QXmlStreamReader reader(&file);
+ while (!reader.atEnd()) {
+ switch (reader.readNext()) {
+ case QXmlStreamReader::StartElement: {
+ const auto currentTag = tag(reader.name());
+ parseWebXmlElement(currentTag, reader.attributes(), &result);
+ switch (currentTag) { // Store relevant tags in lastTag
+ case WebXmlTag::Class:
+ case WebXmlTag::Function:
+ case WebXmlTag::Enum:
+ case WebXmlTag::Property:
+ case WebXmlTag::Typedef:
+ lastTag = currentTag;
+ break;
+ case WebXmlTag::Description: { // Append the description to the element
+ QString *target = nullptr;
+ switch (lastTag) {
+ case WebXmlTag::Class:
+ target = &result.description;
+ break;
+ case WebXmlTag::Function:
+ target = &result.functions.last().description;
+ break;
+ case WebXmlTag::Enum:
+ target = &result.enums.last().description;
+ break;
+ case WebXmlTag::Property:
+ target = &result.properties.last().description;
+ default:
+ break;
+ }
+ if (target != nullptr && target->isEmpty())
+ *target = extractWebXmlDescription(reader);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ default:
+ break;
+ }
+ }
+
+ if (reader.error() != QXmlStreamReader::NoError) {
+ *errorMessage= msgXmlError(fileName, reader);
+ return {};
+ }
+
+ sortDocumentation(&result);
+ return result;
+}
+
+QString webXmlModuleDescription(const QString &fileName, QString *errorMessage)
+{
+ QFile file(fileName);
+ if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
+ *errorMessage = msgCannotOpenForReading(file);
+ return {};
+ }
+
+ QString result;
+ QXmlStreamReader reader(&file);
+ while (!reader.atEnd()) {
+ switch (reader.readNext()) {
+ case QXmlStreamReader::StartElement:
+ if (reader.name() == u"description")
+ result = extractWebXmlDescription(reader);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (reader.error() != QXmlStreamReader::NoError) {
+ *errorMessage= msgXmlError(fileName, reader);
+ return {};
+ }
+
+ return result;
+}
+
+// Debug helpers
+template <class T>
+static void formatList(QDebug &debug, const char *title, const QList<T> &l)
+{
+ if (const qsizetype size = l.size()) {
+ debug << title << '[' << size << "]=(";
+ for (qsizetype i = 0; i < size; ++i) {
+ if (i)
+ debug << ", ";
+ debug << l.at(i);
+ }
+ debug << ')';
+ }
+}
+
+static void formatDescription(QDebug &debug, const QString &desc)
+{
+ debug << "description=";
+ if (desc.isEmpty()) {
+ debug << "<empty>";
+ return;
+ }
+ if (debug.verbosity() < 3)
+ debug << desc.size() << " chars";
+ else
+ debug << '"' << desc << '"';
+}
+
+QDebug operator<<(QDebug debug, const EnumDocumentation &e)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "Enum(";
+ if (e.name.isEmpty()) {
+ debug << "invalid";
+ } else {
+ debug << e.name << ", ";
+ formatDescription(debug, e.description);
+ }
+ debug << ')';
+ return debug;
+}
+
+QDebug operator<<(QDebug debug, const PropertyDocumentation &p)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "Property(";
+ if (p.name.isEmpty()) {
+ debug << "invalid";
+ } else {
+ debug << p.name << ", ";
+ formatDescription(debug, p.description);
+ }
+ debug << ')';
+ return debug;
+}
+
+QDebug operator<<(QDebug debug, const FunctionDocumentation &f)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "Function(";
+ if (f.name.isEmpty()) {
+ debug << "invalid";
+ } else {
+ debug << f.name;
+ if (!f.returnType.isEmpty())
+ debug << ", returns " << f.returnType;
+ if (f.constant)
+ debug << ", const";
+ formatList(debug, ", parameters", f.parameters);
+ debug << ", signature=\"" << f.signature << "\", ";
+ formatDescription(debug, f.description);
+ }
+ debug << ')';
+ return debug;
+}
+
+QDebug operator<<(QDebug debug, const FunctionDocumentationQuery &q)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "FunctionQuery(" << q.name;
+ if (q.constant)
+ debug << ", const";
+ formatList(debug, ", parameters", q.parameters);
+ debug << ')';
+ return debug;
+}
+
+QDebug operator<<(QDebug debug, const ClassDocumentation &c)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "Class(";
+ if (c) {
+ debug << c.name << ", ";
+ formatDescription(debug, c.description);
+ formatList(debug, ", enums", c.enums);
+ formatList(debug, ", properties", c.properties);
+ formatList(debug, ", functions", c.functions);
+ } else {
+ debug << "invalid";
+ }
+ debug << ')';
+ return debug;
+}