Revision control

Copy as Markdown

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:trusted-sources
#include <qdochtmlparser_p.h>
#include <QtCore/qregularexpression.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
// An emprical value to avoid too much content
static constexpr qsizetype firstIndexOfParagraphTag = 400;
// A paragraph can start with <p><i>, or <p><tt>
// We need smallest value to use QString::indexOf
static constexpr auto lengthOfStartParagraphTag = qsizetype(std::char_traits<char>::length("<p>"));
static constexpr auto lengthOfEndParagraphTag = qsizetype(std::char_traits<char>::length("</p>"));
static QString getContentsByMarks(const QString &html, QString startMark, QString endMark)
{
startMark.prepend("$$$"_L1);
endMark.prepend("<!-- @@@"_L1);
QString contents;
qsizetype start = html.indexOf(startMark);
if (start != -1) {
start = html.indexOf("-->"_L1, start);
if (start != -1) {
qsizetype end = html.indexOf(endMark, start);
if (end != -1) {
start += qsizetype(std::char_traits<char>::length("-->"));
contents = html.mid(start, end - start);
}
}
}
return contents;
}
static void stripAllHtml(QString *html)
{
Q_ASSERT(html);
html->remove(QRegularExpression("<.*?>"_L1));
}
/*! \internal
\brief Process the string obtained from start mark to end mark.
This is duplicated from QtC's Utils::HtmlExtractor, modified on top of it.
*/
static void processOutput(QString *html)
{
Q_ASSERT(html);
if (html->isEmpty())
return;
// Do not write the first paragraph in case it has extra tags below.
// <p><i>This is only used on the Maemo platform.</i></p>
// or: <p><tt>This is used on Windows only.</tt></p>
// or: <p>[Conditional]</p>
const auto skipFirstParagraphIfNeeded = [html](qsizetype &index){
const bool shouldSkipFirstParagraph = html->indexOf(QLatin1String("<p><i>")) == index ||
html->indexOf(QLatin1String("<p><tt>")) == index ||
html->indexOf(QLatin1String("<p><b>")) == index ||
html->indexOf(QLatin1String("<p>[Conditional]</p>")) == index;
if (shouldSkipFirstParagraph)
index = html->indexOf(QLatin1String("</p>"), index) + lengthOfEndParagraphTag;
};
// Try to get the entire first paragraph, but if one is not found or if its opening
// tag is not in the very beginning (using an empirical value as the limit)
// the html is cleared out to avoid too much content.
qsizetype index = html->indexOf(QLatin1String("<p>"));
if (index != -1 && index < firstIndexOfParagraphTag) {
skipFirstParagraphIfNeeded(index);
qsizetype endIndex = html->indexOf(QLatin1String("</p>"), index + lengthOfStartParagraphTag);
if (endIndex != -1) {
*html = html->mid(index, endIndex - index);
} else {
html->clear();
}
} else {
html->clear();
}
}
class ExtractQmlType : public HtmlExtractor
{
public:
QString extract(const QString &code, const QString &keyword, ExtractionMode mode) override;
};
class ExtractQmlProperty : public HtmlExtractor
{
public:
QString extract(const QString &code, const QString &keyword, ExtractionMode mode) override;
};
class ExtractQmlMethodOrSignal : public HtmlExtractor
{
public:
QString extract(const QString &code, const QString &keyword, ExtractionMode mode) override;
};
QString ExtractQmlType::extract(const QString &code, const QString &element, ExtractionMode mode)
{
QString result;
// Get brief description
if (mode == ExtractionMode::Simplified) {
result = getContentsByMarks(code, element + "-brief"_L1 , element);
// Remove More...
if (!result.isEmpty()) {
const auto tailToRemove = "More..."_L1;
const auto lastIndex = result.lastIndexOf(tailToRemove);
if (lastIndex != -1)
result.remove(lastIndex, tailToRemove.length());
}
} else {
result = getContentsByMarks(code, element + "-description"_L1, element);
// Remove header
if (!result.isEmpty()) {
const auto headerToRemove = "Detailed Description"_L1;
const auto firstIndex = result.indexOf(headerToRemove);
if (firstIndex != -1)
result.remove(firstIndex, headerToRemove.length());
}
}
stripAllHtml(&result);
return result.trimmed();
}
QString ExtractQmlProperty::extract(const QString &code, const QString &keyword, ExtractionMode mode)
{
QString result;
// Qt 5.15 way of finding properties in doc
QString startMark = QString::fromLatin1("<a name=\"%1-prop\">").arg(keyword);
qsizetype startIndex = code.indexOf(startMark);
if (startIndex == -1) {
// if not found, try Qt6
startMark = QString::fromLatin1(
"<td class=\"tblQmlPropNode\"><p>\n<span class=\"name\">%1</span>")
.arg(keyword);
startIndex = code.indexOf(startMark);
}
if (startIndex != -1) {
result = code.mid(startIndex + startMark.size());
startIndex = result.indexOf(QLatin1String("<div class=\"qmldoc\"><p>"));
} else {
result = getContentsByMarks(code, keyword + "-prop"_L1, keyword );
startIndex = result.indexOf(QLatin1String("<p>"));
}
if (startIndex == -1)
return {};
result = result.mid(startIndex);
if (mode == ExtractionMode::Simplified)
processOutput(&result);
stripAllHtml(&result);
return result.trimmed();
}
QString ExtractQmlMethodOrSignal::extract(const QString &code, const QString &keyword, ExtractionMode mode)
{
// the case with <!-- $$$childAt[overload1]$$$childAtrealreal -->
QString mark = QString::fromLatin1("$$$%1[overload1]$$$%1").arg(keyword);
qsizetype startIndex = code.indexOf(mark);
if (startIndex != -1) {
startIndex = code.indexOf("-->"_L1, startIndex + mark.length());
if (startIndex == -1)
return {};
} else {
// it could be part of the method list
mark = QString::fromLatin1("<span class=\"name\">%1</span>")
.arg(keyword);
startIndex = code.indexOf(mark);
if (startIndex != -1)
startIndex += mark.length();
else
return {};
}
startIndex = code.indexOf(QLatin1String("<div class=\"qmldoc\"><p>"), startIndex);
if (startIndex == -1)
return {};
QString endMark = QString::fromLatin1("<!-- @@@");
qsizetype endIndex = code.indexOf(endMark, startIndex);
QString contents = code.mid(startIndex, endIndex);
if (mode == ExtractionMode::Simplified)
processOutput(&contents);
stripAllHtml(&contents);
return contents.trimmed();
}
ExtractDocumentation::ExtractDocumentation(QQmlJS::Dom::DomType domType)
{
using namespace QQmlJS::Dom;
switch (domType) {
case DomType::QmlObject:
m_extractor = std::make_unique<ExtractQmlType>();
break;
case DomType::Binding:
case DomType::PropertyDefinition:
m_extractor = std::make_unique<ExtractQmlProperty>();
break;
case DomType::MethodInfo:
m_extractor = std::make_unique<ExtractQmlMethodOrSignal>();
break;
default:
break;
}
}
QString ExtractDocumentation::execute(const QString &code, const QString &keyword, HtmlExtractor::ExtractionMode mode)
{
Q_ASSERT(m_extractor);
return m_extractor->extract(code, keyword, mode);
}
QT_END_NAMESPACE