summaryrefslogtreecommitdiff
path: root/src/bbcode.cpp
diff options
context:
space:
mode:
authorMikaƫl Capelle <capelle.mikael@gmail.com>2023-07-09 17:36:03 +0200
committerGitHub <noreply@github.com>2023-07-09 17:36:03 +0200
commitef94aee28464039672b277243a0181ae93550d6c (patch)
tree0575a68af5f154e9d6d0738edbaefb16a11d6f5c /src/bbcode.cpp
parent7d6cb8528d20e36a4cee822263865ee2f7f32481 (diff)
parent3de050e9c03e553f7ae3f780f6bd080a30ae123e (diff)
Merge pull request #1839 from Holt59/ci/initial-gh-action-for-build
Clang-Format + Github Workflow
Diffstat (limited to 'src/bbcode.cpp')
-rw-r--r--src/bbcode.cpp610
1 files changed, 318 insertions, 292 deletions
diff --git a/src/bbcode.cpp b/src/bbcode.cpp
index 36b60a66..f1a3d375 100644
--- a/src/bbcode.cpp
+++ b/src/bbcode.cpp
@@ -1,292 +1,318 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "bbcode.h"
-#include <log.h>
-#include <QRegularExpression>
-#include <map>
-
-namespace BBCode {
-
-namespace log = MOBase::log;
-
-class BBCodeMap {
-
- typedef std::map<QString, std::pair<QRegularExpression, QString> > TagMap;
-
-public:
-
- static BBCodeMap &instance() {
- static BBCodeMap s_Instance;
- return s_Instance;
- }
-
- QString convertTag(QString input, int &length)
- {
- // extract the tag name
- auto match = m_TagNameExp.match(input, 1, QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption);
- QString tagName = match.captured(0).toLower();
- TagMap::iterator tagIter = m_TagMap.find(tagName);
- if (tagIter != m_TagMap.end()) {
- // recognized tag
- if (tagName.endsWith('=')) {
- tagName.chop(1);
- }
-
- int closeTagPos = 0;
- int nextTagPos = 0;
- int nextTagSearchIndex = input.indexOf("]");
- int closeTagLength = 0;
- if (tagName == "*") {
- // ends at the next bullet point
- closeTagPos = input.indexOf(QRegularExpression("(\\[\\*\\]|</ul>)", QRegularExpression::CaseInsensitiveOption), 3);
- // leave closeTagLength at 0 because we don't want to "eat" the next bullet point
- } else if (tagName == "line") {
- // ends immediately after the tag
- closeTagPos = 6;
- // leave closeTagLength at 0 because there is no close tag to skip over
- } else {
- QRegularExpression nextTag(QString("\\[%1[=\\]]?").arg(tagName), QRegularExpression::CaseInsensitiveOption);
- QString closeTag = QString("[/%1]").arg(tagName);
- closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive);
- nextTagPos = nextTag.match(input, nextTagSearchIndex).capturedStart(0);
- while (nextTagPos != -1 && closeTagPos != -1 && nextTagPos < closeTagPos) {
- closeTagPos = input.indexOf(closeTag, closeTagPos + closeTag.size(), Qt::CaseInsensitive);
- nextTagSearchIndex = input.indexOf("]", nextTagPos);
- nextTagPos = nextTag.match(input, nextTagSearchIndex).capturedStart(0);
- }
- if (closeTagPos == -1) {
- // workaround to improve compatibility: add fake closing tag
- input.append(closeTag);
- closeTagPos = input.size() - closeTag.size();
- }
- closeTagLength = closeTag.size();
- }
-
- if (closeTagPos > -1) {
- length = closeTagPos + closeTagLength;
- QString temp = input.mid(0, length);
- tagIter->second.first.setPatternOptions(QRegularExpression::PatternOption::DotMatchesEverythingOption);
- auto match = tagIter->second.first.match(temp);
- if (match.hasMatch()) {
- if (tagIter->second.second.isEmpty()) {
- if (tagName == "color") {
- QString color = match.captured(1);
- QString content = match.captured(2);
- if (color.at(0) == '#') {
- return temp.replace(tagIter->second.first, QString("<font style=\"color: %1;\">%2</font>").arg(color, content));
- } else {
- auto colIter = m_ColorMap.find(color.toLower());
- if (colIter != m_ColorMap.end()) {
- color = colIter->second;
- }
- return temp.replace(tagIter->second.first, QString("<font style=\"color: #%1;\">%2</font>").arg(color, content));
- }
- } else {
- log::warn("don't know how to deal with tag {}", tagName);
- }
- } else {
- if (tagName == "*") {
- temp.remove(QRegularExpression("(\\[/\\*\\])?(<br/>)?$"));
- }
- return temp.replace(tagIter->second.first, tagIter->second.second);
- }
- } else {
- // expression doesn't match. either the input string is invalid
- // or the expression is
- log::warn("{} doesn't match the expression for {}", temp, tagName);
- length = 0;
- return QString();
- }
- }
- }
-
- // not a recognized tag or tag invalid
- length = 0;
- return QString();
- }
-
-private:
- BBCodeMap()
- : m_TagNameExp("[a-zA-Z*]*=?")
- {
- m_TagMap["b"] = std::make_pair(QRegularExpression("\\[b\\](.*)\\[/b\\]"),
- "<b>\\1</b>");
- m_TagMap["i"] = std::make_pair(QRegularExpression("\\[i\\](.*)\\[/i\\]"),
- "<i>\\1</i>");
- m_TagMap["u"] = std::make_pair(QRegularExpression("\\[u\\](.*)\\[/u\\]"),
- "<u>\\1</u>");
- m_TagMap["s"] = std::make_pair(QRegularExpression("\\[s\\](.*)\\[/s\\]"),
- "<s>\\1</s>");
- m_TagMap["sub"] = std::make_pair(QRegularExpression("\\[sub\\](.*)\\[/sub\\]"),
- "<sub>\\1</sub>");
- m_TagMap["sup"] = std::make_pair(QRegularExpression("\\[sup\\](.*)\\[/sup\\]"),
- "<sup>\\1</sup>");
- m_TagMap["size="] = std::make_pair(QRegularExpression("\\[size=([^\\]]*)\\](.*)\\[/size\\]"),
- "<font size=\"\\1\">\\2</font>");
- m_TagMap["color="] = std::make_pair(QRegularExpression("\\[color=([^\\]]*)\\](.*)\\[/color\\]"),
- "");
- m_TagMap["font="] = std::make_pair(QRegularExpression("\\[font=([^\\]]*)\\](.*)\\[/font\\]"),
- "<font style=\"font-family: \\1;\">\\2</font>");
- m_TagMap["center"] = std::make_pair(QRegularExpression("\\[center\\](.*)\\[/center\\]"),
- "<div align=\"center\">\\1</div>");
- m_TagMap["right"] = std::make_pair(QRegularExpression("\\[right\\](.*)\\[/right\\]"),
- "<div align=\"right\">\\1</div>");
- m_TagMap["quote"] = std::make_pair(QRegularExpression("\\[quote\\](.*)\\[/quote\\]"),
- "<figure class=\"quote\"><blockquote>\\1</blockquote></figure>");
- m_TagMap["quote="] = std::make_pair(QRegularExpression("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"),
- "<figure class=\"quote\"><blockquote>\\2</blockquote></figure>");
- m_TagMap["spoiler"] = std::make_pair(QRegularExpression("\\[spoiler\\](.*)\\[/spoiler\\]"),
- "<details><summary>Spoiler: <div class=\"bbc_spoiler_show\">Show</div></summary><div class=\"spoiler_content\">\\1</div></details>");
- m_TagMap["code"] = std::make_pair(QRegularExpression("\\[code\\](.*)\\[/code\\]"),
- "<code>\\1</code>");
- m_TagMap["heading"]= std::make_pair(QRegularExpression("\\[heading\\](.*)\\[/heading\\]"),
- "<h2><strong>\\1</strong></h2>");
- m_TagMap["line"] = std::make_pair(QRegularExpression("\\[line\\]"),
- "<hr>");
-
- // lists
- m_TagMap["list"] = std::make_pair(QRegularExpression("\\[list\\](.*)\\[/list\\]"),
- "<ul>\\1</ul>");
- m_TagMap["list="] = std::make_pair(QRegularExpression("\\[list.*\\](.*)\\[/list\\]"),
- "<ol>\\1</ol>");
- m_TagMap["ul"] = std::make_pair(QRegularExpression("\\[ul\\](.*)\\[/ul\\]"),
- "<ul>\\1</ul>");
- m_TagMap["ol"] = std::make_pair(QRegularExpression("\\[ol\\](.*)\\[/ol\\]"),
- "<ol>\\1</ol>");
- m_TagMap["li"] = std::make_pair(QRegularExpression("\\[li\\](.*)\\[/li\\]"),
- "<li>\\1</li>");
-
- // tables
- m_TagMap["table"] = std::make_pair(QRegularExpression("\\[table\\](.*)\\[/table\\]"),
- "<table>\\1</table>");
- m_TagMap["tr"] = std::make_pair(QRegularExpression("\\[tr\\](.*)\\[/tr\\]"),
- "<tr>\\1</tr>");
- m_TagMap["th"] = std::make_pair(QRegularExpression("\\[th\\](.*)\\[/th\\]"),
- "<th>\\1</th>");
- m_TagMap["td"] = std::make_pair(QRegularExpression("\\[td\\](.*)\\[/td\\]"),
- "<td>\\1</td>");
-
- // web content
- m_TagMap["url"] = std::make_pair(QRegularExpression("\\[url\\](.*)\\[/url\\]"),
- "<a href=\"\\1\">\\1</a>");
- m_TagMap["url="] = std::make_pair(QRegularExpression("\\[url=([^\\]]*)\\](.*)\\[/url\\]"),
- "<a href=\"\\1\">\\2</a>");
- m_TagMap["img"] = std::make_pair(QRegularExpression("\\[img(?:\\s*width=\\d+\\s*,?\\s*height=\\d+)?\\](.*)\\[/img\\]"),
- "<img src=\"\\1\">");
- m_TagMap["img="] = std::make_pair(QRegularExpression("\\[img=([^\\]]*)\\](.*)\\[/img\\]"),
- "<img src=\"\\2\" alt=\"\\1\">");
- m_TagMap["email="] = std::make_pair(QRegularExpression("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"),
- "<a href=\"mailto:\\1\">\\2</a>");
- m_TagMap["youtube"] = std::make_pair(QRegularExpression("\\[youtube\\](.*)\\[/youtube\\]"),
- "<a href=\"https://www.youtube.com/watch?v=\\1\">https://www.youtube.com/watch?v=\\1</a>");
-
-
- // make all patterns non-greedy and case-insensitive
- for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) {
- iter->second.first.setPatternOptions(QRegularExpression::CaseInsensitiveOption | QRegularExpression::InvertedGreedinessOption);
- }
-
- // this tag is in fact greedy
- m_TagMap["*"] = std::make_pair(QRegularExpression("\\[\\*\\](.*)"),
- "<li>\\1</li>");
-
- m_ColorMap.insert(std::make_pair<QString, QString>("red", "FF0000"));
- m_ColorMap.insert(std::make_pair<QString, QString>("green", "00FF00"));
- m_ColorMap.insert(std::make_pair<QString, QString>("blue", "0000FF"));
- m_ColorMap.insert(std::make_pair<QString, QString>("black", "000000"));
- m_ColorMap.insert(std::make_pair<QString, QString>("gray", "7F7F7F"));
- m_ColorMap.insert(std::make_pair<QString, QString>("white", "FFFFFF"));
- m_ColorMap.insert(std::make_pair<QString, QString>("yellow", "FFFF00"));
- m_ColorMap.insert(std::make_pair<QString, QString>("cyan", "00FFFF"));
- m_ColorMap.insert(std::make_pair<QString, QString>("magenta", "FF00FF"));
- m_ColorMap.insert(std::make_pair<QString, QString>("brown", "A52A2A"));
- m_ColorMap.insert(std::make_pair<QString, QString>("orange", "FFA500"));
- m_ColorMap.insert(std::make_pair<QString, QString>("gold", "FFD700"));
- m_ColorMap.insert(std::make_pair<QString, QString>("deepskyblue", "00BFFF"));
- m_ColorMap.insert(std::make_pair<QString, QString>("salmon", "FA8072"));
- m_ColorMap.insert(std::make_pair<QString, QString>("dodgerblue", "1E90FF"));
- m_ColorMap.insert(std::make_pair<QString, QString>("greenyellow", "ADFF2F"));
- m_ColorMap.insert(std::make_pair<QString, QString>("peru", "CD853F"));
- }
-
-private:
-
- QRegularExpression m_TagNameExp;
- TagMap m_TagMap;
- std::map<QString, QString> m_ColorMap;
-};
-
-
-QString convertToHTML(const QString &inputParam)
-{
- // this code goes over the input string once and replaces all bbtags
- // it encounters. This function is called recursively for every replaced
- // string to convert nested tags.
- //
- // This could be implemented simpler by applying a set of regular expressions
- // for each recognized bb-tag one after the other but that would probably be
- // very inefficient (O(n^2)).
-
- QString input = inputParam.mid(0).replace("\r\n", "<br/>");
- input.replace("\\\"", "\"").replace("\\'", "'");
- QString result;
- int lastBlock = 0;
- int pos = 0;
-
- // iterate over the input buffer
- while ((pos = input.indexOf('[', lastBlock)) != -1) {
- // append everything between the previous tag-block and the current one
- result.append(input.mid(lastBlock, pos - lastBlock));
-
- if ((pos < (input.size() - 1)) && (input.at(pos + 1) == '/')) {
- // skip invalid end tag
- int tagEnd = input.indexOf(']', pos) + 1;
- if (tagEnd == 0) {
- //no closing tag found
- //move the pos up one so that the opening bracket is ignored next iteration
- pos++;
- }
- else {
- pos = tagEnd;
- }
- } else {
- // convert the tag and content if necessary
- int length = -1;
- QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length);
- if (length != 0) {
- result.append(convertToHTML(replacement));
- // length contains the number of characters in the original tag
- pos += length;
- } else {
- // nothing replaced
- result.append('[');
- ++pos;
- }
- }
- lastBlock = pos;
- }
-
- // append the remainder (everything after the last tag)
- result.append(input.mid(lastBlock));
- return result;
-}
-
-} // namespace BBCode
-
+/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "bbcode.h"
+#include <QRegularExpression>
+#include <log.h>
+#include <map>
+
+namespace BBCode
+{
+
+namespace log = MOBase::log;
+
+class BBCodeMap
+{
+
+ typedef std::map<QString, std::pair<QRegularExpression, QString>> TagMap;
+
+public:
+ static BBCodeMap& instance()
+ {
+ static BBCodeMap s_Instance;
+ return s_Instance;
+ }
+
+ QString convertTag(QString input, int& length)
+ {
+ // extract the tag name
+ auto match = m_TagNameExp.match(input, 1, QRegularExpression::NormalMatch,
+ QRegularExpression::AnchoredMatchOption);
+ QString tagName = match.captured(0).toLower();
+ TagMap::iterator tagIter = m_TagMap.find(tagName);
+ if (tagIter != m_TagMap.end()) {
+ // recognized tag
+ if (tagName.endsWith('=')) {
+ tagName.chop(1);
+ }
+
+ int closeTagPos = 0;
+ int nextTagPos = 0;
+ int nextTagSearchIndex = input.indexOf("]");
+ int closeTagLength = 0;
+ if (tagName == "*") {
+ // ends at the next bullet point
+ closeTagPos =
+ input.indexOf(QRegularExpression("(\\[\\*\\]|</ul>)",
+ QRegularExpression::CaseInsensitiveOption),
+ 3);
+ // leave closeTagLength at 0 because we don't want to "eat" the next bullet
+ // point
+ } else if (tagName == "line") {
+ // ends immediately after the tag
+ closeTagPos = 6;
+ // leave closeTagLength at 0 because there is no close tag to skip over
+ } else {
+ QRegularExpression nextTag(QString("\\[%1[=\\]]?").arg(tagName),
+ QRegularExpression::CaseInsensitiveOption);
+ QString closeTag = QString("[/%1]").arg(tagName);
+ closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive);
+ nextTagPos = nextTag.match(input, nextTagSearchIndex).capturedStart(0);
+ while (nextTagPos != -1 && closeTagPos != -1 && nextTagPos < closeTagPos) {
+ closeTagPos = input.indexOf(closeTag, closeTagPos + closeTag.size(),
+ Qt::CaseInsensitive);
+ nextTagSearchIndex = input.indexOf("]", nextTagPos);
+ nextTagPos = nextTag.match(input, nextTagSearchIndex).capturedStart(0);
+ }
+ if (closeTagPos == -1) {
+ // workaround to improve compatibility: add fake closing tag
+ input.append(closeTag);
+ closeTagPos = input.size() - closeTag.size();
+ }
+ closeTagLength = closeTag.size();
+ }
+
+ if (closeTagPos > -1) {
+ length = closeTagPos + closeTagLength;
+ QString temp = input.mid(0, length);
+ tagIter->second.first.setPatternOptions(
+ QRegularExpression::PatternOption::DotMatchesEverythingOption);
+ auto match = tagIter->second.first.match(temp);
+ if (match.hasMatch()) {
+ if (tagIter->second.second.isEmpty()) {
+ if (tagName == "color") {
+ QString color = match.captured(1);
+ QString content = match.captured(2);
+ if (color.at(0) == '#') {
+ return temp.replace(tagIter->second.first,
+ QString("<font style=\"color: %1;\">%2</font>")
+ .arg(color, content));
+ } else {
+ auto colIter = m_ColorMap.find(color.toLower());
+ if (colIter != m_ColorMap.end()) {
+ color = colIter->second;
+ }
+ return temp.replace(tagIter->second.first,
+ QString("<font style=\"color: #%1;\">%2</font>")
+ .arg(color, content));
+ }
+ } else {
+ log::warn("don't know how to deal with tag {}", tagName);
+ }
+ } else {
+ if (tagName == "*") {
+ temp.remove(QRegularExpression("(\\[/\\*\\])?(<br/>)?$"));
+ }
+ return temp.replace(tagIter->second.first, tagIter->second.second);
+ }
+ } else {
+ // expression doesn't match. either the input string is invalid
+ // or the expression is
+ log::warn("{} doesn't match the expression for {}", temp, tagName);
+ length = 0;
+ return QString();
+ }
+ }
+ }
+
+ // not a recognized tag or tag invalid
+ length = 0;
+ return QString();
+ }
+
+private:
+ BBCodeMap() : m_TagNameExp("[a-zA-Z*]*=?")
+ {
+ m_TagMap["b"] =
+ std::make_pair(QRegularExpression("\\[b\\](.*)\\[/b\\]"), "<b>\\1</b>");
+ m_TagMap["i"] =
+ std::make_pair(QRegularExpression("\\[i\\](.*)\\[/i\\]"), "<i>\\1</i>");
+ m_TagMap["u"] =
+ std::make_pair(QRegularExpression("\\[u\\](.*)\\[/u\\]"), "<u>\\1</u>");
+ m_TagMap["s"] =
+ std::make_pair(QRegularExpression("\\[s\\](.*)\\[/s\\]"), "<s>\\1</s>");
+ m_TagMap["sub"] =
+ std::make_pair(QRegularExpression("\\[sub\\](.*)\\[/sub\\]"), "<sub>\\1</sub>");
+ m_TagMap["sup"] =
+ std::make_pair(QRegularExpression("\\[sup\\](.*)\\[/sup\\]"), "<sup>\\1</sup>");
+ m_TagMap["size="] =
+ std::make_pair(QRegularExpression("\\[size=([^\\]]*)\\](.*)\\[/size\\]"),
+ "<font size=\"\\1\">\\2</font>");
+ m_TagMap["color="] =
+ std::make_pair(QRegularExpression("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), "");
+ m_TagMap["font="] =
+ std::make_pair(QRegularExpression("\\[font=([^\\]]*)\\](.*)\\[/font\\]"),
+ "<font style=\"font-family: \\1;\">\\2</font>");
+ m_TagMap["center"] =
+ std::make_pair(QRegularExpression("\\[center\\](.*)\\[/center\\]"),
+ "<div align=\"center\">\\1</div>");
+ m_TagMap["right"] =
+ std::make_pair(QRegularExpression("\\[right\\](.*)\\[/right\\]"),
+ "<div align=\"right\">\\1</div>");
+ m_TagMap["quote"] =
+ std::make_pair(QRegularExpression("\\[quote\\](.*)\\[/quote\\]"),
+ "<figure class=\"quote\"><blockquote>\\1</blockquote></figure>");
+ m_TagMap["quote="] =
+ std::make_pair(QRegularExpression("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"),
+ "<figure class=\"quote\"><blockquote>\\2</blockquote></figure>");
+ m_TagMap["spoiler"] =
+ std::make_pair(QRegularExpression("\\[spoiler\\](.*)\\[/spoiler\\]"),
+ "<details><summary>Spoiler: <div "
+ "class=\"bbc_spoiler_show\">Show</div></summary><div "
+ "class=\"spoiler_content\">\\1</div></details>");
+ m_TagMap["code"] = std::make_pair(QRegularExpression("\\[code\\](.*)\\[/code\\]"),
+ "<code>\\1</code>");
+ m_TagMap["heading"] =
+ std::make_pair(QRegularExpression("\\[heading\\](.*)\\[/heading\\]"),
+ "<h2><strong>\\1</strong></h2>");
+ m_TagMap["line"] = std::make_pair(QRegularExpression("\\[line\\]"), "<hr>");
+
+ // lists
+ m_TagMap["list"] =
+ std::make_pair(QRegularExpression("\\[list\\](.*)\\[/list\\]"), "<ul>\\1</ul>");
+ m_TagMap["list="] = std::make_pair(
+ QRegularExpression("\\[list.*\\](.*)\\[/list\\]"), "<ol>\\1</ol>");
+ m_TagMap["ul"] =
+ std::make_pair(QRegularExpression("\\[ul\\](.*)\\[/ul\\]"), "<ul>\\1</ul>");
+ m_TagMap["ol"] =
+ std::make_pair(QRegularExpression("\\[ol\\](.*)\\[/ol\\]"), "<ol>\\1</ol>");
+ m_TagMap["li"] =
+ std::make_pair(QRegularExpression("\\[li\\](.*)\\[/li\\]"), "<li>\\1</li>");
+
+ // tables
+ m_TagMap["table"] = std::make_pair(
+ QRegularExpression("\\[table\\](.*)\\[/table\\]"), "<table>\\1</table>");
+ m_TagMap["tr"] =
+ std::make_pair(QRegularExpression("\\[tr\\](.*)\\[/tr\\]"), "<tr>\\1</tr>");
+ m_TagMap["th"] =
+ std::make_pair(QRegularExpression("\\[th\\](.*)\\[/th\\]"), "<th>\\1</th>");
+ m_TagMap["td"] =
+ std::make_pair(QRegularExpression("\\[td\\](.*)\\[/td\\]"), "<td>\\1</td>");
+
+ // web content
+ m_TagMap["url"] = std::make_pair(QRegularExpression("\\[url\\](.*)\\[/url\\]"),
+ "<a href=\"\\1\">\\1</a>");
+ m_TagMap["url="] =
+ std::make_pair(QRegularExpression("\\[url=([^\\]]*)\\](.*)\\[/url\\]"),
+ "<a href=\"\\1\">\\2</a>");
+ m_TagMap["img"] = std::make_pair(
+ QRegularExpression(
+ "\\[img(?:\\s*width=\\d+\\s*,?\\s*height=\\d+)?\\](.*)\\[/img\\]"),
+ "<img src=\"\\1\">");
+ m_TagMap["img="] =
+ std::make_pair(QRegularExpression("\\[img=([^\\]]*)\\](.*)\\[/img\\]"),
+ "<img src=\"\\2\" alt=\"\\1\">");
+ m_TagMap["email="] = std::make_pair(
+ QRegularExpression("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"),
+ "<a href=\"mailto:\\1\">\\2</a>");
+ m_TagMap["youtube"] =
+ std::make_pair(QRegularExpression("\\[youtube\\](.*)\\[/youtube\\]"),
+ "<a "
+ "href=\"https://www.youtube.com/watch?v=\\1\">https://"
+ "www.youtube.com/watch?v=\\1</a>");
+
+ // make all patterns non-greedy and case-insensitive
+ for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) {
+ iter->second.first.setPatternOptions(
+ QRegularExpression::CaseInsensitiveOption |
+ QRegularExpression::InvertedGreedinessOption);
+ }
+
+ // this tag is in fact greedy
+ m_TagMap["*"] = std::make_pair(QRegularExpression("\\[\\*\\](.*)"), "<li>\\1</li>");
+
+ m_ColorMap.insert(std::make_pair<QString, QString>("red", "FF0000"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("green", "00FF00"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("blue", "0000FF"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("black", "000000"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("gray", "7F7F7F"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("white", "FFFFFF"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("yellow", "FFFF00"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("cyan", "00FFFF"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("magenta", "FF00FF"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("brown", "A52A2A"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("orange", "FFA500"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("gold", "FFD700"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("deepskyblue", "00BFFF"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("salmon", "FA8072"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("dodgerblue", "1E90FF"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("greenyellow", "ADFF2F"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("peru", "CD853F"));
+ }
+
+private:
+ QRegularExpression m_TagNameExp;
+ TagMap m_TagMap;
+ std::map<QString, QString> m_ColorMap;
+};
+
+QString convertToHTML(const QString& inputParam)
+{
+ // this code goes over the input string once and replaces all bbtags
+ // it encounters. This function is called recursively for every replaced
+ // string to convert nested tags.
+ //
+ // This could be implemented simpler by applying a set of regular expressions
+ // for each recognized bb-tag one after the other but that would probably be
+ // very inefficient (O(n^2)).
+
+ QString input = inputParam.mid(0).replace("\r\n", "<br/>");
+ input.replace("\\\"", "\"").replace("\\'", "'");
+ QString result;
+ int lastBlock = 0;
+ int pos = 0;
+
+ // iterate over the input buffer
+ while ((pos = input.indexOf('[', lastBlock)) != -1) {
+ // append everything between the previous tag-block and the current one
+ result.append(input.mid(lastBlock, pos - lastBlock));
+
+ if ((pos < (input.size() - 1)) && (input.at(pos + 1) == '/')) {
+ // skip invalid end tag
+ int tagEnd = input.indexOf(']', pos) + 1;
+ if (tagEnd == 0) {
+ // no closing tag found
+ // move the pos up one so that the opening bracket is ignored next iteration
+ pos++;
+ } else {
+ pos = tagEnd;
+ }
+ } else {
+ // convert the tag and content if necessary
+ int length = -1;
+ QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length);
+ if (length != 0) {
+ result.append(convertToHTML(replacement));
+ // length contains the number of characters in the original tag
+ pos += length;
+ } else {
+ // nothing replaced
+ result.append('[');
+ ++pos;
+ }
+ }
+ lastBlock = pos;
+ }
+
+ // append the remainder (everything after the last tag)
+ result.append(input.mid(lastBlock));
+ return result;
+}
+
+} // namespace BBCode