File indexing completed on 2024-12-22 04:48:10

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2023 Louis Schul <schul9louis@gmail.com>
0004 */
0005 
0006 // CREDIT TO ORIGINAL IDEA: https://marked.js.org/
0007 
0008 #include "renderer.h"
0009 #include "logic/plugins/syntaxHighlight/highlightHelper.h"
0010 
0011 #include <QRegularExpression>
0012 #include <QUrl>
0013 
0014 QString Renderer::code(QString &code, const QString &lang, const bool highlight)
0015 {
0016     if (highlight) {
0017         code = HighlightHelper::getHighlightedString(code, lang);
0018     }
0019 
0020     return QStringLiteral("<pre><code>") + (highlight ? code : escape(code, true)) + QStringLiteral("</code></pre>\n");
0021 }
0022 
0023 QString Renderer::blockquote(const QString &quote)
0024 {
0025     return QStringLiteral("<blockquote>\n") + quote + QStringLiteral("</blockquote>\n");
0026 }
0027 
0028 QString Renderer::html(const QString &html)
0029 {
0030     return html;
0031 }
0032 
0033 QString Renderer::heading(const QString &text, const QString &lvl, const QString &raw, const bool scrollTo)
0034 {
0035     static const QRegularExpression rawReg = QRegularExpression(QStringLiteral("[^\\w]+"));
0036     const QString id = scrollTo ? QStringLiteral("noteMapperScrollTo") : raw.toLower().replace(rawReg, QStringLiteral("-"));
0037     return QStringLiteral("<h") + lvl + QStringLiteral(" id=\"") + id + QStringLiteral("\">") + text + QStringLiteral("</h") + lvl + QStringLiteral(">\n");
0038 }
0039 
0040 QString Renderer::hr()
0041 {
0042     return QStringLiteral("<hr>\n");
0043 }
0044 
0045 QString Renderer::list(const QString &body, bool ordered, const QString &start)
0046 {
0047     const QString type = ordered ? QStringLiteral("ol") : QStringLiteral("ul");
0048     QString startat;
0049     if (ordered && start != QString::number(1)) {
0050         startat = (QStringLiteral(" start=\"") + start + QStringLiteral("\""));
0051     } else {
0052         startat = QLatin1String();
0053     }
0054 
0055     return QStringLiteral("<") + type + startat + QStringLiteral(">\n") + body + QStringLiteral("</") + type + QStringLiteral(">\n");
0056 }
0057 
0058 QString Renderer::listItem(const QString &text, const bool hasCheck)
0059 {
0060     const QString out = hasCheck ? QStringLiteral("<li class=\"hasCheck\"> <label class=\"form-control\">\n") + text + QStringLiteral("</label></li>\n")
0061                                  : QStringLiteral("<li>") + text + QStringLiteral("</li>\n");
0062     return out;
0063 }
0064 
0065 QString Renderer::checkbox(bool checked)
0066 {
0067     const QString checkedString = checked ? QStringLiteral("checked=\"\" ") : QLatin1String();
0068 
0069     return QStringLiteral("<input ") + checkedString + QStringLiteral("disabled=\"\" type=\"checkbox\">");
0070 }
0071 
0072 QString Renderer::paragraph(const QString &text)
0073 {
0074     return QStringLiteral("<p>") + text + QStringLiteral("</p>\n");
0075 }
0076 
0077 QString Renderer::table(const QString &header, QString &body)
0078 {
0079     if (!body.isEmpty())
0080         body = QStringLiteral("<tbody>") + body + QStringLiteral("</tbody>");
0081 
0082     return QStringLiteral("<table>\n") + QStringLiteral("<thead>\n") + header + QStringLiteral("</thead>\n") + body + QStringLiteral("</table>\n");
0083 }
0084 
0085 QString Renderer::tableRow(const QString &content)
0086 {
0087     return QStringLiteral("<tr>\n") + content + QStringLiteral("</tr>\n");
0088 }
0089 
0090 QString Renderer::tableCell(const QString &content, const QVariantMap &flags)
0091 {
0092     const QString type = flags[QStringLiteral("header")].toBool() ? QStringLiteral("th") : QStringLiteral("td");
0093     const QString align = flags[QStringLiteral("align")].toString();
0094     QString tag;
0095     if (align.isEmpty()) {
0096         tag = QStringLiteral("<") + type + QStringLiteral(">");
0097     } else {
0098         tag = QStringLiteral("<") + type + QStringLiteral(" style=\"text-align:") + align + QStringLiteral(";\">");
0099     }
0100     return tag + content + QStringLiteral("</") + type + QStringLiteral(">\n");
0101 }
0102 
0103 QString Renderer::strong(const QString &text)
0104 {
0105     return QStringLiteral("<strong>") + text + QStringLiteral("</strong>");
0106 }
0107 
0108 QString Renderer::em(const QString &text)
0109 {
0110     return QStringLiteral("<em>") + text + QStringLiteral("</em>");
0111 }
0112 
0113 QString Renderer::codeSpan(const QString &text)
0114 {
0115     return QStringLiteral("<code>") + text + QStringLiteral("</code>");
0116 }
0117 
0118 QString Renderer::br()
0119 {
0120     return QStringLiteral("<br>");
0121 }
0122 
0123 QString Renderer::del(const QString &text)
0124 {
0125     return QStringLiteral("<del>") + text + QStringLiteral("</del>");
0126 }
0127 
0128 QString Renderer::mark(const QString &text)
0129 {
0130     return QStringLiteral("<mark>") + text + QStringLiteral("</mark>");
0131 }
0132 
0133 QString Renderer::wikilink(const QString &href, const QString &title, const QString &text)
0134 {
0135     const QString leading = QStringLiteral("<a href=\"") + href + QStringLiteral("\"");
0136     const QString ending = QStringLiteral(">") + text + QStringLiteral("</a>");
0137     QString middle = QLatin1String();
0138     if (!title.isEmpty()) {
0139         QStringLiteral(" title=\"") + title + QStringLiteral("\"");
0140     }
0141     return leading + middle + ending;
0142 }
0143 
0144 QString Renderer::link(QString &href, const QString &title, const QString &text)
0145 {
0146     const QByteArray uri = QUrl::fromUserInput(href).toEncoded();
0147     if (uri.isEmpty())
0148         return text;
0149 
0150     static const QRegularExpression uriPercentReg = QRegularExpression(QStringLiteral("%25"));
0151     href = QString::fromUtf8(uri).replace(uriPercentReg, QStringLiteral("%"));
0152 
0153     const QString leading = QStringLiteral("<a href=\"") + escape(href, false) + QStringLiteral("\"");
0154     const QString ending = QStringLiteral(">") + text + QStringLiteral("</a>");
0155     QString middle = QLatin1String();
0156     if (!title.isEmpty()) {
0157         QStringLiteral(" title=\"") + title + QStringLiteral("\"");
0158     }
0159 
0160     return leading + middle + ending;
0161 }
0162 
0163 QString Renderer::image(const QString &href, const QString &title, const QString &text)
0164 {
0165     const QString leading = QStringLiteral("<img src=\"") + href + QStringLiteral("\" alt=\"") + text + QStringLiteral("\"");
0166     QString middle = QLatin1String();
0167     if (!title.isEmpty()) {
0168         QStringLiteral(" title=\"") + title + QStringLiteral("\"");
0169     }
0170     static const QString ending = QStringLiteral(">");
0171 
0172     return leading + middle + ending;
0173 }
0174 
0175 QString Renderer::text(const QString &text)
0176 {
0177     return text;
0178 }
0179 
0180 QString Renderer::escape(QString &html, bool encode)
0181 {
0182     const QRegularExpression encodedReplacement = !encode ? QRegularExpression(QStringLiteral("&(?!#?\\w+;)")) : QRegularExpression(QStringLiteral("&"));
0183 
0184     static const QRegularExpression leftBracketReg = QRegularExpression(QStringLiteral("<"));
0185     static const QRegularExpression rightBracketReg = QRegularExpression(QStringLiteral(">"));
0186     static const QRegularExpression quoteReg = QRegularExpression(QStringLiteral("\""));
0187     static const QRegularExpression apostropheReg = QRegularExpression(QStringLiteral("'"));
0188     return html.replace(encodedReplacement, QStringLiteral("&amp;"))
0189         .replace(leftBracketReg, QStringLiteral("&lt;"))
0190         .replace(rightBracketReg, QStringLiteral("&gt;"))
0191         .replace(quoteReg, QStringLiteral("&quot;"))
0192         .replace(apostropheReg, QStringLiteral("&#39;"));
0193 }
0194 
0195 QString Renderer::unescape(const QString &html)
0196 {
0197     // explicitly match decimal, hex, and named HTML entities
0198     QString result = html;
0199     static const QRegularExpression regex(QStringLiteral("&(#(?:\\d+)|(?:#x[0-9A-Fa-f]+)|(?:\\w+));?"));
0200     QRegularExpressionMatchIterator i = regex.globalMatch(result);
0201 
0202     QRegularExpressionMatch match;
0203     while (i.hasNext()) {
0204         match = i.next();
0205         const QString entity = match.captured(1).toLower();
0206 
0207         if (entity == QStringLiteral("colon")) {
0208             result.replace(match.capturedStart(), match.capturedLength(), QStringLiteral(":"));
0209             continue;
0210         }
0211         if (entity.startsWith(QStringLiteral("#"))) {
0212             bool ok;
0213             // check for hexadecimal or numerical value
0214             const int charCode = (entity.at(1) == QChar::fromLatin1('x')) ? entity.mid(2).toInt(&ok, 16) : entity.mid(1).toInt(&ok);
0215 
0216             result.replace(match.capturedStart(), match.capturedLength(), QChar(charCode));
0217             continue;
0218         }
0219         result.replace(match.capturedStart(), match.capturedLength(), QLatin1String());
0220     }
0221 
0222     return result;
0223 }