1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <qdochtmlparser_p.h>
5#include <QtCore/qregularexpression.h>
6
7QT_BEGIN_NAMESPACE
8
9using namespace Qt::StringLiterals;
10
11// An emprical value to avoid too much content
12static constexpr qsizetype firstIndexOfParagraphTag = 400;
13
14// A paragraph can start with <p><i>, or <p><tt>
15// We need smallest value to use QString::indexOf
16static constexpr auto lengthOfStartParagraphTag = qsizetype(std::char_traits<char>::length(s: "<p>"));
17static constexpr auto lengthOfEndParagraphTag = qsizetype(std::char_traits<char>::length(s: "</p>"));
18
19static QString getContentsByMarks(const QString &html, QString startMark, QString endMark)
20{
21 startMark.prepend(s: "$$$"_L1);
22 endMark.prepend(s: "<!-- @@@"_L1);
23
24 QString contents;
25 qsizetype start = html.indexOf(s: startMark);
26 if (start != -1) {
27 start = html.indexOf(s: "-->"_L1, from: start);
28 if (start != -1) {
29 qsizetype end = html.indexOf(s: endMark, from: start);
30 if (end != -1) {
31 start += qsizetype(std::char_traits<char>::length(s: "-->"));
32 contents = html.mid(position: start, n: end - start);
33 }
34 }
35 }
36 return contents;
37}
38
39
40static void stripAllHtml(QString *html)
41{
42 Q_ASSERT(html);
43 html->remove(re: QRegularExpression("<.*?>"_L1));
44}
45
46/*! \internal
47 \brief Process the string obtained from start mark to end mark.
48 This is duplicated from QtC's Utils::HtmlExtractor, modified on top of it.
49*/
50static void processOutput(QString *html)
51{
52 Q_ASSERT(html);
53 if (html->isEmpty())
54 return;
55
56 // Do not write the first paragraph in case it has extra tags below.
57 // <p><i>This is only used on the Maemo platform.</i></p>
58 // or: <p><tt>This is used on Windows only.</tt></p>
59 // or: <p>[Conditional]</p>
60 const auto skipFirstParagraphIfNeeded = [html](qsizetype &index){
61 const bool shouldSkipFirstParagraph = html->indexOf(s: QLatin1String("<p><i>")) == index ||
62 html->indexOf(s: QLatin1String("<p><tt>")) == index ||
63 html->indexOf(s: QLatin1String("<p><b>")) == index ||
64 html->indexOf(s: QLatin1String("<p>[Conditional]</p>")) == index;
65
66 if (shouldSkipFirstParagraph)
67 index = html->indexOf(s: QLatin1String("</p>"), from: index) + lengthOfEndParagraphTag;
68 };
69
70 // Try to get the entire first paragraph, but if one is not found or if its opening
71 // tag is not in the very beginning (using an empirical value as the limit)
72 // the html is cleared out to avoid too much content.
73 qsizetype index = html->indexOf(s: QLatin1String("<p>"));
74 if (index != -1 && index < firstIndexOfParagraphTag) {
75 skipFirstParagraphIfNeeded(index);
76 qsizetype endIndex = html->indexOf(s: QLatin1String("</p>"), from: index + lengthOfStartParagraphTag);
77 if (endIndex != -1) {
78 *html = html->mid(position: index, n: endIndex - index);
79 } else {
80 html->clear();
81 }
82 } else {
83 html->clear();
84 }
85}
86
87class ExtractQmlType : public HtmlExtractor
88{
89public:
90 QString extract(const QString &code, const QString &keyword, ExtractionMode mode) override;
91};
92
93class ExtractQmlProperty : public HtmlExtractor
94{
95public:
96 QString extract(const QString &code, const QString &keyword, ExtractionMode mode) override;
97};
98
99class ExtractQmlMethodOrSignal : public HtmlExtractor
100{
101public:
102 QString extract(const QString &code, const QString &keyword, ExtractionMode mode) override;
103};
104
105QString ExtractQmlType::extract(const QString &code, const QString &element, ExtractionMode mode)
106{
107 QString result;
108 // Get brief description
109 if (mode == ExtractionMode::Simplified) {
110 result = getContentsByMarks(html: code, startMark: element + "-brief"_L1 , endMark: element);
111 // Remove More...
112 if (!result.isEmpty()) {
113 const auto tailToRemove = "More..."_L1;
114 const auto lastIndex = result.lastIndexOf(s: tailToRemove);
115 if (lastIndex != -1)
116 result.remove(i: lastIndex, len: tailToRemove.length());
117 }
118 } else {
119 result = getContentsByMarks(html: code, startMark: element + "-description"_L1, endMark: element);
120 // Remove header
121 if (!result.isEmpty()) {
122 const auto headerToRemove = "Detailed Description"_L1;
123 const auto firstIndex = result.indexOf(s: headerToRemove);
124 if (firstIndex != -1)
125 result.remove(i: firstIndex, len: headerToRemove.length());
126 }
127 }
128
129 stripAllHtml(html: &result);
130 return result.trimmed();
131}
132
133QString ExtractQmlProperty::extract(const QString &code, const QString &keyword, ExtractionMode mode)
134{
135 QString result;
136 // Qt 5.15 way of finding properties in doc
137 QString startMark = QString::fromLatin1(ba: "<a name=\"%1-prop\">").arg(a: keyword);
138 qsizetype startIndex = code.indexOf(s: startMark);
139 if (startIndex == -1) {
140 // if not found, try Qt6
141 startMark = QString::fromLatin1(
142 ba: "<td class=\"tblQmlPropNode\"><p>\n<span class=\"name\">%1</span>")
143 .arg(a: keyword);
144 startIndex = code.indexOf(s: startMark);
145 }
146
147 if (startIndex != -1) {
148 result = code.mid(position: startIndex + startMark.size());
149 startIndex = result.indexOf(s: QLatin1String("<div class=\"qmldoc\"><p>"));
150 } else {
151 result = getContentsByMarks(html: code, startMark: keyword + "-prop"_L1, endMark: keyword );
152 startIndex = result.indexOf(s: QLatin1String("<p>"));
153 }
154
155 if (startIndex == -1)
156 return {};
157 result = result.mid(position: startIndex);
158 if (mode == ExtractionMode::Simplified)
159 processOutput(html: &result);
160 stripAllHtml(html: &result);
161 return result.trimmed();
162}
163
164QString ExtractQmlMethodOrSignal::extract(const QString &code, const QString &keyword, ExtractionMode mode)
165{
166 // the case with <!-- $$$childAt[overload1]$$$childAtrealreal -->
167 QString mark = QString::fromLatin1(ba: "$$$%1[overload1]$$$%1").arg(a: keyword);
168 qsizetype startIndex = code.indexOf(s: mark);
169 if (startIndex != -1) {
170 startIndex = code.indexOf(s: "-->"_L1, from: startIndex + mark.length());
171 if (startIndex == -1)
172 return {};
173 } else {
174 // it could be part of the method list
175 mark = QString::fromLatin1(ba: "<span class=\"name\">%1</span>")
176 .arg(a: keyword);
177 startIndex = code.indexOf(s: mark);
178 if (startIndex != -1)
179 startIndex += mark.length();
180 else
181 return {};
182 }
183
184 startIndex = code.indexOf(s: QLatin1String("<div class=\"qmldoc\"><p>"), from: startIndex);
185 if (startIndex == -1)
186 return {};
187
188 QString endMark = QString::fromLatin1(ba: "<!-- @@@");
189 qsizetype endIndex = code.indexOf(s: endMark, from: startIndex);
190 QString contents = code.mid(position: startIndex, n: endIndex);
191 if (mode == ExtractionMode::Simplified)
192 processOutput(html: &contents);
193 stripAllHtml(html: &contents);
194 return contents.trimmed();
195}
196
197ExtractDocumentation::ExtractDocumentation(QQmlJS::Dom::DomType domType)
198{
199 using namespace QQmlJS::Dom;
200 switch (domType) {
201 case DomType::QmlObject:
202 m_extractor = std::make_unique<ExtractQmlType>();
203 break;
204 case DomType::Binding:
205 case DomType::PropertyDefinition:
206 m_extractor = std::make_unique<ExtractQmlProperty>();
207 break;
208 case DomType::MethodInfo:
209 m_extractor = std::make_unique<ExtractQmlMethodOrSignal>();
210 break;
211 default:
212 break;
213 }
214}
215
216QString ExtractDocumentation::execute(const QString &code, const QString &keyword, HtmlExtractor::ExtractionMode mode)
217{
218 Q_ASSERT(m_extractor);
219 return m_extractor->extract(code, keyword, mode);
220}
221
222QT_END_NAMESPACE
223

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/qmlls/qdochtmlparser.cpp