2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
2010-08-27 12:11:55 +02:00
|
|
|
|
|
|
|
|
#include "helpitem.h"
|
2019-01-24 11:30:58 +01:00
|
|
|
#include "helpmanager.h"
|
2010-08-27 12:11:55 +02:00
|
|
|
|
2019-01-28 13:00:03 +01:00
|
|
|
#include <utils/algorithm.h>
|
2010-08-27 12:11:55 +02:00
|
|
|
#include <utils/htmldocextractor.h>
|
|
|
|
|
|
2021-09-20 17:45:08 +02:00
|
|
|
#include <QVersionNumber>
|
|
|
|
|
|
2019-01-24 11:30:58 +01:00
|
|
|
using namespace Core;
|
2010-08-27 12:11:55 +02:00
|
|
|
|
2018-11-25 18:52:41 +01:00
|
|
|
HelpItem::HelpItem() = default;
|
2010-08-27 12:11:55 +02:00
|
|
|
|
2019-01-25 15:04:50 +01:00
|
|
|
HelpItem::HelpItem(const char *helpId)
|
2019-01-28 13:00:03 +01:00
|
|
|
: HelpItem(QStringList(QString::fromUtf8(helpId)), {}, Unknown)
|
2019-01-25 15:04:50 +01:00
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
HelpItem::HelpItem(const QString &helpId)
|
2019-01-28 13:00:03 +01:00
|
|
|
: HelpItem(QStringList(helpId), {}, Unknown)
|
2019-01-25 15:04:50 +01:00
|
|
|
{}
|
|
|
|
|
|
2019-01-28 12:46:30 +01:00
|
|
|
HelpItem::HelpItem(const QUrl &url)
|
|
|
|
|
: m_helpUrl(url)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
HelpItem::HelpItem(const QUrl &url, const QString &docMark, HelpItem::Category category)
|
|
|
|
|
: m_helpUrl(url)
|
|
|
|
|
, m_docMark(docMark)
|
|
|
|
|
, m_category(category)
|
|
|
|
|
{}
|
|
|
|
|
|
2019-01-28 13:00:03 +01:00
|
|
|
HelpItem::HelpItem(const QString &helpId, const QString &docMark, Category category)
|
|
|
|
|
: HelpItem(QStringList(helpId), docMark, category)
|
2010-08-27 12:11:55 +02:00
|
|
|
{}
|
|
|
|
|
|
2019-01-28 13:00:03 +01:00
|
|
|
HelpItem::HelpItem(const QStringList &helpIds, const QString &docMark, Category category)
|
|
|
|
|
: m_docMark(docMark)
|
|
|
|
|
, m_category(category)
|
|
|
|
|
{
|
|
|
|
|
setHelpIds(helpIds);
|
|
|
|
|
}
|
2013-05-15 12:46:08 +02:00
|
|
|
|
2019-01-28 12:46:30 +01:00
|
|
|
void HelpItem::setHelpUrl(const QUrl &url)
|
|
|
|
|
{
|
|
|
|
|
m_helpUrl = url;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QUrl &HelpItem::helpUrl() const
|
|
|
|
|
{
|
|
|
|
|
return m_helpUrl;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 13:00:03 +01:00
|
|
|
void HelpItem::setHelpIds(const QStringList &ids)
|
|
|
|
|
{
|
|
|
|
|
m_helpIds = Utils::filteredUnique(
|
|
|
|
|
Utils::filtered(ids, [](const QString &s) { return !s.isEmpty(); }));
|
|
|
|
|
}
|
2010-08-27 12:11:55 +02:00
|
|
|
|
2019-01-28 13:00:03 +01:00
|
|
|
const QStringList &HelpItem::helpIds() const
|
|
|
|
|
{
|
|
|
|
|
return m_helpIds;
|
|
|
|
|
}
|
2010-08-27 12:11:55 +02:00
|
|
|
|
|
|
|
|
void HelpItem::setDocMark(const QString &mark)
|
|
|
|
|
{ m_docMark = mark; }
|
|
|
|
|
|
|
|
|
|
const QString &HelpItem::docMark() const
|
|
|
|
|
{ return m_docMark; }
|
|
|
|
|
|
|
|
|
|
void HelpItem::setCategory(Category cat)
|
|
|
|
|
{ m_category = cat; }
|
|
|
|
|
|
|
|
|
|
HelpItem::Category HelpItem::category() const
|
|
|
|
|
{ return m_category; }
|
|
|
|
|
|
2019-02-01 12:11:57 +01:00
|
|
|
bool HelpItem::isEmpty() const
|
|
|
|
|
{
|
|
|
|
|
return m_helpUrl.isEmpty() && m_helpIds.isEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
2010-08-27 12:11:55 +02:00
|
|
|
bool HelpItem::isValid() const
|
|
|
|
|
{
|
2019-01-28 13:00:03 +01:00
|
|
|
if (m_helpUrl.isEmpty() && m_helpIds.isEmpty())
|
2013-05-15 12:46:08 +02:00
|
|
|
return false;
|
2019-02-01 15:52:43 +01:00
|
|
|
return !links().empty();
|
2010-08-27 12:11:55 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-25 15:16:47 +02:00
|
|
|
QString HelpItem::firstParagraph() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_firstParagraph)
|
|
|
|
|
m_firstParagraph = extractContent(false);
|
|
|
|
|
return *m_firstParagraph;
|
|
|
|
|
}
|
|
|
|
|
|
2010-08-27 12:11:55 +02:00
|
|
|
QString HelpItem::extractContent(bool extended) const
|
|
|
|
|
{
|
|
|
|
|
Utils::HtmlDocExtractor htmlExtractor;
|
|
|
|
|
if (extended)
|
2010-09-01 12:08:38 +02:00
|
|
|
htmlExtractor.setMode(Utils::HtmlDocExtractor::Extended);
|
2010-08-27 12:11:55 +02:00
|
|
|
else
|
2010-09-01 12:08:38 +02:00
|
|
|
htmlExtractor.setMode(Utils::HtmlDocExtractor::FirstParagraph);
|
2010-08-27 12:11:55 +02:00
|
|
|
|
|
|
|
|
QString contents;
|
2019-02-01 15:52:43 +01:00
|
|
|
for (const Link &item : links()) {
|
|
|
|
|
const QUrl url = item.second;
|
2013-08-29 19:00:34 +02:00
|
|
|
const QString html = QString::fromUtf8(Core::HelpManager::fileData(url));
|
2010-08-27 12:11:55 +02:00
|
|
|
switch (m_category) {
|
|
|
|
|
case Brief:
|
|
|
|
|
contents = htmlExtractor.getClassOrNamespaceBrief(html, m_docMark);
|
|
|
|
|
break;
|
|
|
|
|
case ClassOrNamespace:
|
|
|
|
|
contents = htmlExtractor.getClassOrNamespaceDescription(html, m_docMark);
|
|
|
|
|
break;
|
|
|
|
|
case Function:
|
|
|
|
|
contents = htmlExtractor.getFunctionDescription(html, m_docMark);
|
|
|
|
|
break;
|
|
|
|
|
case Enum:
|
|
|
|
|
contents = htmlExtractor.getEnumDescription(html, m_docMark);
|
|
|
|
|
break;
|
|
|
|
|
case Typedef:
|
|
|
|
|
contents = htmlExtractor.getTypedefDescription(html, m_docMark);
|
|
|
|
|
break;
|
|
|
|
|
case Macro:
|
|
|
|
|
contents = htmlExtractor.getMacroDescription(html, m_docMark);
|
|
|
|
|
break;
|
2010-11-17 14:48:25 +01:00
|
|
|
case QmlComponent:
|
|
|
|
|
contents = htmlExtractor.getQmlComponentDescription(html, m_docMark);
|
|
|
|
|
break;
|
|
|
|
|
case QmlProperty:
|
|
|
|
|
contents = htmlExtractor.getQmlPropertyDescription(html, m_docMark);
|
2010-08-27 12:11:55 +02:00
|
|
|
break;
|
2011-11-10 11:55:05 +01:00
|
|
|
case QMakeVariableOfFunction:
|
|
|
|
|
contents = htmlExtractor.getQMakeVariableOrFunctionDescription(html, m_docMark);
|
|
|
|
|
break;
|
2010-08-27 12:11:55 +02:00
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!contents.isEmpty())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return contents;
|
|
|
|
|
}
|
2013-05-15 12:46:08 +02:00
|
|
|
|
2021-09-20 17:45:08 +02:00
|
|
|
// The following is only correct under the specific current conditions, and it will
|
|
|
|
|
// always be quite some guessing as long as the version information does not
|
|
|
|
|
// include separators for major vs minor vs patch version.
|
|
|
|
|
static QVersionNumber qtVersionHeuristic(const QString &digits)
|
|
|
|
|
{
|
|
|
|
|
if (digits.count() > 6 || digits.count() < 3)
|
|
|
|
|
return {}; // suspicious version number
|
|
|
|
|
|
|
|
|
|
for (const QChar &digit : digits)
|
|
|
|
|
if (!digit.isDigit())
|
|
|
|
|
return {}; // we should have only digits
|
|
|
|
|
|
|
|
|
|
// When we have 3 digits, we split it like: ABC -> A.B.C
|
|
|
|
|
// When we have 4 digits, we split it like: ABCD -> A.BC.D
|
|
|
|
|
// When we have 5 digits, we split it like: ABCDE -> A.BC.DE
|
|
|
|
|
// When we have 6 digits, we split it like: ABCDEF -> AB.CD.EF
|
|
|
|
|
switch (digits.count()) {
|
|
|
|
|
case 3:
|
|
|
|
|
return QVersionNumber(digits.mid(0, 1).toInt(),
|
|
|
|
|
digits.mid(1, 1).toInt(),
|
|
|
|
|
digits.mid(2, 1).toInt());
|
|
|
|
|
case 4:
|
|
|
|
|
return QVersionNumber(digits.mid(0, 1).toInt(),
|
|
|
|
|
digits.mid(1, 2).toInt(),
|
|
|
|
|
digits.mid(3, 1).toInt());
|
|
|
|
|
case 5:
|
|
|
|
|
return QVersionNumber(digits.mid(0, 1).toInt(),
|
|
|
|
|
digits.mid(1, 2).toInt(),
|
|
|
|
|
digits.mid(3, 2).toInt());
|
|
|
|
|
case 6:
|
|
|
|
|
return QVersionNumber(digits.mid(0, 2).toInt(),
|
|
|
|
|
digits.mid(2, 2).toInt(),
|
|
|
|
|
digits.mid(4, 2).toInt());
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::pair<QUrl, QVersionNumber> extractVersion(const QUrl &url)
|
2019-02-01 15:52:43 +01:00
|
|
|
{
|
|
|
|
|
const QString host = url.host();
|
|
|
|
|
const QStringList hostParts = host.split('.');
|
|
|
|
|
if (hostParts.size() == 4 && (host.startsWith("com.trolltech.")
|
|
|
|
|
|| host.startsWith("org.qt-project."))) {
|
2021-09-20 17:45:08 +02:00
|
|
|
const QVersionNumber version = qtVersionHeuristic(hostParts.at(3));
|
|
|
|
|
if (!version.isNull()) {
|
2019-02-01 15:52:43 +01:00
|
|
|
QUrl urlWithoutVersion(url);
|
|
|
|
|
urlWithoutVersion.setHost(hostParts.mid(0, 3).join('.'));
|
|
|
|
|
return {urlWithoutVersion, version};
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-20 17:45:08 +02:00
|
|
|
return {url, {}};
|
2019-02-01 15:52:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sort primary by "url without version" and seconday by "version"
|
|
|
|
|
static bool helpUrlLessThan(const QUrl &a, const QUrl &b)
|
|
|
|
|
{
|
2021-09-20 17:45:08 +02:00
|
|
|
const std::pair<QUrl, QVersionNumber> va = extractVersion(a);
|
|
|
|
|
const std::pair<QUrl, QVersionNumber> vb = extractVersion(b);
|
2019-02-01 15:52:43 +01:00
|
|
|
const QString sa = va.first.toString();
|
|
|
|
|
const QString sb = vb.first.toString();
|
|
|
|
|
if (sa == sb)
|
|
|
|
|
return va.second > vb.second;
|
|
|
|
|
return sa < sb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool linkLessThan(const HelpItem::Link &a, const HelpItem::Link &b)
|
|
|
|
|
{
|
|
|
|
|
return helpUrlLessThan(a.second, b.second);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// links are sorted with highest "version" first (for Qt help urls)
|
|
|
|
|
const HelpItem::Links &HelpItem::links() const
|
2013-05-15 12:46:08 +02:00
|
|
|
{
|
2019-01-28 12:46:30 +01:00
|
|
|
if (!m_helpLinks) {
|
2019-01-28 13:00:03 +01:00
|
|
|
if (!m_helpUrl.isEmpty()) {
|
2019-02-01 15:52:43 +01:00
|
|
|
m_keyword = m_helpUrl.toString();
|
|
|
|
|
m_helpLinks.emplace(Links{{m_keyword, m_helpUrl}});
|
2019-01-28 13:00:03 +01:00
|
|
|
} else {
|
|
|
|
|
m_helpLinks.emplace(); // set a value even if there are no help IDs
|
2019-10-16 13:28:28 +02:00
|
|
|
QMultiMap<QString, QUrl> helpLinks;
|
2019-01-28 13:00:03 +01:00
|
|
|
for (const QString &id : m_helpIds) {
|
2019-02-01 15:52:43 +01:00
|
|
|
helpLinks = Core::HelpManager::linksForIdentifier(id);
|
|
|
|
|
if (!helpLinks.isEmpty()) {
|
|
|
|
|
m_keyword = id;
|
2019-01-28 13:00:03 +01:00
|
|
|
break;
|
2019-02-01 15:52:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (helpLinks.isEmpty()) { // perform keyword lookup as well as a fallback
|
|
|
|
|
for (const QString &id : m_helpIds) {
|
|
|
|
|
helpLinks = Core::HelpManager::linksForKeyword(id);
|
|
|
|
|
if (!helpLinks.isEmpty()) {
|
|
|
|
|
m_keyword = id;
|
|
|
|
|
m_isFuzzyMatch = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-24 13:43:54 +02:00
|
|
|
for (auto it = helpLinks.cbegin(), end = helpLinks.cend(); it != end; ++it)
|
2019-02-01 15:52:43 +01:00
|
|
|
m_helpLinks->emplace_back(it.key(), it.value());
|
2019-01-28 13:00:03 +01:00
|
|
|
}
|
2019-02-01 15:52:43 +01:00
|
|
|
Utils::sort(*m_helpLinks, linkLessThan);
|
2019-01-28 12:46:30 +01:00
|
|
|
}
|
2019-01-25 15:48:24 +01:00
|
|
|
return *m_helpLinks;
|
2013-05-15 12:46:08 +02:00
|
|
|
}
|
2019-02-04 16:13:59 +01:00
|
|
|
|
2019-02-01 15:52:43 +01:00
|
|
|
static const HelpItem::Links getBestLinks(const HelpItem::Links &links)
|
|
|
|
|
{
|
|
|
|
|
// extract the highest version (== first) link of each individual topic
|
|
|
|
|
HelpItem::Links bestLinks;
|
|
|
|
|
QUrl currentUnversionedUrl;
|
|
|
|
|
for (const HelpItem::Link &link : links) {
|
|
|
|
|
const QUrl unversionedUrl = extractVersion(link.second).first;
|
|
|
|
|
if (unversionedUrl != currentUnversionedUrl) {
|
|
|
|
|
currentUnversionedUrl = unversionedUrl;
|
|
|
|
|
bestLinks.push_back(link);
|
2019-02-04 16:13:59 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-02-01 15:52:43 +01:00
|
|
|
return bestLinks;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const HelpItem::Links getBestLink(const HelpItem::Links &links)
|
|
|
|
|
{
|
|
|
|
|
if (links.empty())
|
|
|
|
|
return {};
|
|
|
|
|
// Extract single link with highest version, from all topics.
|
|
|
|
|
// This is to ensure that if we succeeded with an ID lookup, and we have e.g. Qt5 and Qt4
|
|
|
|
|
// documentation, that we only return the Qt5 link even though the Qt5 and Qt4 URLs look
|
|
|
|
|
// different.
|
2021-09-20 17:45:08 +02:00
|
|
|
QVersionNumber highestVersion;
|
2021-10-29 15:57:36 +02:00
|
|
|
// Default to first link if version extraction failed, possibly because it is not a Qt doc link
|
|
|
|
|
HelpItem::Link bestLink = links.front();
|
2019-02-01 15:52:43 +01:00
|
|
|
for (const HelpItem::Link &link : links) {
|
2021-09-20 17:45:08 +02:00
|
|
|
const QVersionNumber version = extractVersion(link.second).second;
|
2019-02-01 15:52:43 +01:00
|
|
|
if (version > highestVersion) {
|
|
|
|
|
highestVersion = version;
|
|
|
|
|
bestLink = link;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {bestLink};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const HelpItem::Links HelpItem::bestLinks() const
|
|
|
|
|
{
|
2019-02-12 13:06:02 +01:00
|
|
|
if (isFuzzyMatch())
|
2019-02-01 15:52:43 +01:00
|
|
|
return getBestLinks(links());
|
|
|
|
|
return getBestLink(links());
|
2019-02-04 16:13:59 +01:00
|
|
|
}
|
|
|
|
|
|
2019-02-01 15:52:43 +01:00
|
|
|
const QString HelpItem::keyword() const
|
2019-02-04 16:13:59 +01:00
|
|
|
{
|
2019-02-01 15:52:43 +01:00
|
|
|
return m_keyword;
|
2019-02-04 16:13:59 +01:00
|
|
|
}
|
2019-02-12 13:06:02 +01:00
|
|
|
|
|
|
|
|
bool HelpItem::isFuzzyMatch() const
|
|
|
|
|
{
|
|
|
|
|
// make sure m_isFuzzyMatch is correct
|
|
|
|
|
links();
|
|
|
|
|
return m_isFuzzyMatch;
|
|
|
|
|
}
|