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 | |
7 | QT_BEGIN_NAMESPACE |
8 | |
9 | using namespace Qt::StringLiterals; |
10 | |
11 | // An emprical value to avoid too much content |
12 | static 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 |
16 | static constexpr auto lengthOfStartParagraphTag = qsizetype(std::char_traits<char>::length(s: "<p>" )); |
17 | static constexpr auto lengthOfEndParagraphTag = qsizetype(std::char_traits<char>::length(s: "</p>" )); |
18 | |
19 | static 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 | |
40 | static 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 | */ |
50 | static 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 | |
87 | class : public HtmlExtractor |
88 | { |
89 | public: |
90 | QString extract(const QString &code, const QString &keyword, ExtractionMode mode) override; |
91 | }; |
92 | |
93 | class : public HtmlExtractor |
94 | { |
95 | public: |
96 | QString extract(const QString &code, const QString &keyword, ExtractionMode mode) override; |
97 | }; |
98 | |
99 | class : public HtmlExtractor |
100 | { |
101 | public: |
102 | QString extract(const QString &code, const QString &keyword, ExtractionMode mode) override; |
103 | }; |
104 | |
105 | QString ExtractQmlType::(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 = "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 | |
133 | QString ExtractQmlProperty::(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 | |
164 | QString ExtractQmlMethodOrSignal::(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 | |
197 | 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 | |
216 | QString ExtractDocumentation::(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 | |
222 | QT_END_NAMESPACE |
223 | |