Commit 6fe94005 authored by Igor Kushnir's avatar Igor Kushnir
Browse files

ManPageDocumentation: expand unsupported links on pages

file:// URL links don't work in Qt WebEngine because it forbids such
file system access. help:/ links don't work because the KDE help URL
scheme is not registered with Qt WebKit or Qt WebEngine.

Currently the HTML of each man page contains two stylesheet links with
the following URLs:

The kio_docfilter style sheet forces white background color, affects the
appearance of #header (the top banner that is hidden by
manpagedocumentation.css) and #footer (nicely separates the bottom
"Generated by kio_man version 22.04.2" text from the man page itself).

The kde-default.css affects many elements of a man page. Whether the
page looks better as a result is subjective. But at least it works as
intended and is consistent with KHelpCenter's rendering.
parent 60df20c0
Pipeline #197309 passed with stage
in 14 minutes and 34 seconds
......@@ -17,7 +17,11 @@
#include <KLocalizedString>
#include <QFile>
#include <QHash>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QStringView>
#include <QUrl>
namespace {
class StyleSheetFixer
......@@ -30,6 +34,16 @@ public:
template <typename Location>
static QString styleElementWithCode(const QByteArray& cssCode, const Location& location)
if (cssCode.isEmpty()) {
qCWarning(MANPAGE) << "empty CSS file" << location;
return QString();
return QString::fromUtf8("<style>" + cssCode + "</style>");
* Read the file contents and return it wrapped in a &lt;style&gt; HTML element.
......@@ -46,7 +60,24 @@ private:
return QString();
const auto cssCode = file.readAll();
return QString::fromUtf8("<style>" + cssCode + "</style>");
return styleElementWithCode(cssCode, fileName);
* Get the URL contents and return it wrapped in a &lt;style&gt; HTML element.
* @return The &lt;style&gt; HTML element or an empty string in case of error.
static QString getStyleSheetContents(const QUrl& url)
auto* const job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo);
if (!job->exec()) {
qCWarning(MANPAGE) << "couldn't get the contents of CSS file" << url << ':'
<< job->error() << job->errorString();
return QString();
const auto cssCode = job->data();
return styleElementWithCode(cssCode, url);
static QString readCustomStyleSheet()
......@@ -67,10 +98,6 @@ private:
void fix(QString& htmlPage)
if (m_customStyleSheet.isEmpty()) {
return; // nothing to do
const QLatin1String headEndTag("</head>");
const auto headEndTagPos = htmlPage.indexOf(headEndTag, 0, Qt::CaseInsensitive);
if (headEndTagPos == -1) {
......@@ -81,11 +108,86 @@ private:
// Apply our custom style sheet to normalize look of the page. Embed the <style> element
// into the HTML code directly rather than inject it with JavaScript to avoid reloading
// and flickering of large pages such as cmake-modules man page.
htmlPage.insert(headEndTagPos, m_customStyleSheet);
if (!m_customStyleSheet.isEmpty()) {
htmlPage.insert(headEndTagPos, m_customStyleSheet);
expandUnsupportedLinks(htmlPage, headEndTagPos);
void expandUnsupportedLinks(QString& htmlPage, int endPos)
Q_ASSERT(endPos >= 0);
static const QRegularExpression linkElement(QStringLiteral(R"(<link\s[^>]*rel="stylesheet"[^>]*>)"),
int startPos = 0;
while (true) {
const auto remainingPartOfThePage = QStringView{htmlPage}.mid(startPos, endPos - startPos);
const auto linkElementMatch = linkElement.match(remainingPartOfThePage);
if (!linkElementMatch.hasMatch()) {
break; // no more links to expand
startPos += linkElementMatch.capturedEnd();
static const QRegularExpression hrefAttribute(QStringLiteral(R"|(\shref="([^"]*)")|"),
const auto hrefAttributeMatch = hrefAttribute.match(linkElementMatch.capturedView());
if (!hrefAttributeMatch.hasMatch()) {
qCWarning(MANPAGE) << "missing href attribute in a stylesheet <link> element.";
const QUrl url{hrefAttributeMatch.captured(1)};
const auto styleSheet = expandStyleSheet(url);
if (styleSheet.isEmpty()) {
continue; // no code => skip this <link> element as expanding it won't make a difference
const auto linkElementLength = linkElementMatch.capturedLength();
const auto linkElementPos = startPos - linkElementLength;
htmlPage.replace(linkElementPos, linkElementLength, styleSheet);
const auto htmlPageSizeIncrement = styleSheet.size() - linkElementLength;
startPos += htmlPageSizeIncrement;
endPos += htmlPageSizeIncrement;
QString expandStyleSheet(const QUrl& url)
const bool isLocalFile = url.isLocalFile();
const bool isHelpUrl = !isLocalFile && url.scheme() == QLatin1String{"help"};
if (!isLocalFile && !isHelpUrl) {
qCDebug(MANPAGE) << "not expanding CSS file URL with scheme" << url.scheme();
return QString();
// Must do it this way because when an empty string is the value stored
// for url, it should be returned rather than re-read from disk.
const auto alreadyExpanded = m_expandedStyleSheets.constFind(url);
if (alreadyExpanded != m_expandedStyleSheets.cend()) {
return alreadyExpanded.value();
QString newlyExpanded;
if (isLocalFile) {
newlyExpanded = readStyleSheet(url.toLocalFile());
} else {
// Neither Qt WebKit nor Qt WebEngine knows about the help protocol and URL scheme.
// Expand the file contents at the help URL to apply the style sheet.
newlyExpanded = getStyleSheetContents(url);
m_expandedStyleSheets.insert(url, newlyExpanded);
return newlyExpanded;
/// The style sheet does not change => read it once and store in a constant.
const QString m_customStyleSheet;
/// Referenced style sheets should be few and rarely modified => read them once and store in this cache.
QHash<QUrl, QString> m_expandedStyleSheets;
} // unnamed namespace
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment