File indexing completed on 2024-04-21 04:38:10
0001 /* 0002 SPDX-FileCopyrightText: 2018 Anton Anikin <anton@anikin.xyz> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "utils.h" 0008 0009 // KDevPlatform 0010 #include <interfaces/icore.h> 0011 #include <interfaces/iproject.h> 0012 #include <interfaces/iprojectcontroller.h> 0013 // KF 0014 #include <KLocalizedString> 0015 // Qt 0016 #include <QFile> 0017 #include <QRegularExpression> 0018 #include <QVector> 0019 0020 namespace Clazy 0021 { 0022 0023 QString prettyPathName(const QUrl& path) 0024 { 0025 return KDevelop::ICore::self()->projectController()->prettyFileName(path, KDevelop::IProjectController::FormatPlain); 0026 } 0027 0028 // Very simple Markdown parser/converter. Does not provide full Markdown language support and 0029 // was tested only with Clazy documentation. 0030 class MarkdownConverter 0031 { 0032 public: 0033 MarkdownConverter() 0034 { 0035 tagStart.resize(STATE_COUNT); 0036 tagEnd.resize(STATE_COUNT); 0037 0038 tagStart[EMPTY].clear(); 0039 tagEnd [EMPTY].clear(); 0040 0041 tagStart[HEADING] = QStringLiteral("<b>"); 0042 tagEnd [HEADING] = QStringLiteral("</b>"); 0043 0044 tagStart[PARAGRAPH] = QStringLiteral("<p>"); 0045 tagEnd [PARAGRAPH] = QStringLiteral("</p>"); 0046 0047 tagStart[PREFORMATTED] = QStringLiteral("<pre>"); 0048 tagEnd [PREFORMATTED] = QStringLiteral("</pre>"); 0049 0050 tagStart[LIST] = QStringLiteral("<ul><li>"); 0051 tagEnd [LIST] = QStringLiteral("</li></ul>"); 0052 } 0053 0054 ~MarkdownConverter() = default; 0055 0056 QString toHtml(const QString& markdown) 0057 { 0058 const QRegularExpression hRE(QStringLiteral("(#+) (.+)")); 0059 QRegularExpressionMatch match; 0060 0061 state = EMPTY; 0062 html.clear(); 0063 html += QStringLiteral("<html>"); 0064 0065 const auto lines = markdown.split(QLatin1Char('\n')); 0066 for (auto line : lines) { 0067 if (line.isEmpty()) { 0068 setState(EMPTY); 0069 continue; 0070 } 0071 0072 if (line.startsWith(QLatin1Char('#'))) { 0073 auto match = hRE.match(line); 0074 if (match.hasMatch()) { 0075 setState(HEADING); 0076 html += match.captured(2); 0077 setState(EMPTY); 0078 if (match.capturedRef(1).size() == 1) { 0079 html += QStringLiteral("<hr>"); 0080 } 0081 } 0082 continue; 0083 } 0084 0085 if (line.startsWith(QLatin1String("```"))) { 0086 setState((state == PREFORMATTED) ? EMPTY : PREFORMATTED); 0087 continue; 0088 } 0089 0090 if (line.startsWith(QLatin1String(" "))) { 0091 if (state == EMPTY) { 0092 setState(PREFORMATTED); 0093 } 0094 } else if ( 0095 line.startsWith(QLatin1String("- ")) || 0096 line.startsWith(QLatin1String("* "))) { 0097 // force close and reopen list - this fixes cases when we don't have 0098 // separator line between items 0099 setState(EMPTY); 0100 setState(LIST); 0101 line.remove(0, 2); 0102 } 0103 0104 if (state == EMPTY) { 0105 setState(PARAGRAPH); 0106 } 0107 0108 processLine(line); 0109 } 0110 setState(EMPTY); 0111 0112 html += QStringLiteral("</html>"); 0113 return html.join(QLatin1Char('\n')); 0114 } 0115 0116 private: 0117 enum STATE { 0118 EMPTY, 0119 HEADING, 0120 PARAGRAPH, 0121 PREFORMATTED, 0122 LIST, 0123 0124 STATE_COUNT 0125 }; 0126 0127 void setState(int newState) 0128 { 0129 if (state == newState) { 0130 return; 0131 } 0132 0133 if (state != EMPTY) { 0134 html += tagEnd[state]; 0135 } 0136 0137 if (newState != EMPTY) { 0138 html += tagStart[newState]; 0139 } 0140 0141 state = newState; 0142 } 0143 0144 void processLine(QString& line) 0145 { 0146 static const QRegularExpression ttRE(QStringLiteral("`([^`]+)`")); 0147 static const QRegularExpression bdRE(QStringLiteral("\\*\\*([^\\*]+)\\*\\*")); 0148 static const QRegularExpression itRE(QStringLiteral("[^\\*]\\*([^\\*]+)\\*[^\\*]")); 0149 0150 static auto applyRE = [](const QRegularExpression& re, QString& line, const QString& tag) { 0151 auto i = re.globalMatch(line); 0152 while (i.hasNext()) { 0153 auto match = i.next(); 0154 line.replace(match.captured(0), QStringLiteral("<%1>%2</%1>").arg(tag, match.captured(1))); 0155 } 0156 }; 0157 0158 if (state != PREFORMATTED) { 0159 line.replace(QLatin1Char('&'), QLatin1String("&")); 0160 line.replace(QLatin1Char('<'), QLatin1String("<")); 0161 line.replace(QLatin1Char('>'), QLatin1String(">")); 0162 0163 line.replace(QLatin1Char('\"'), QLatin1String(""")); 0164 line.replace(QLatin1Char('\''), QLatin1String("'")); 0165 0166 applyRE(ttRE, line, QStringLiteral("tt")); 0167 applyRE(bdRE, line, QStringLiteral("b")); 0168 applyRE(itRE, line, QStringLiteral("i")); 0169 } 0170 0171 html += line; 0172 } 0173 0174 private: 0175 int state; 0176 QVector<QString> tagStart; 0177 QVector<QString> tagEnd; 0178 QStringList html; 0179 }; 0180 0181 QString markdown2html(const QByteArray& markdown) 0182 { 0183 MarkdownConverter converter; 0184 return converter.toHtml(QString::fromUtf8(markdown)); 0185 } 0186 0187 }