/* 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 . */ #include "bbcode.h" #include #include #include namespace BBCode { namespace log = MOBase::log; class BBCodeMap { typedef std::map> 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("(\\[\\*\\]|)", 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("%2") .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("%2") .arg(color, content)); } } else { log::warn("don't know how to deal with tag {}", tagName); } } else { if (tagName == "*") { temp.remove(QRegularExpression("(\\[/\\*\\])?(
)?$")); } 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\\]"), "\\1"); m_TagMap["i"] = std::make_pair(QRegularExpression("\\[i\\](.*)\\[/i\\]"), "\\1"); m_TagMap["u"] = std::make_pair(QRegularExpression("\\[u\\](.*)\\[/u\\]"), "\\1"); m_TagMap["s"] = std::make_pair(QRegularExpression("\\[s\\](.*)\\[/s\\]"), "\\1"); m_TagMap["sub"] = std::make_pair(QRegularExpression("\\[sub\\](.*)\\[/sub\\]"), "\\1"); m_TagMap["sup"] = std::make_pair(QRegularExpression("\\[sup\\](.*)\\[/sup\\]"), "\\1"); m_TagMap["size="] = std::make_pair(QRegularExpression("\\[size=([^\\]]*)\\](.*)\\[/size\\]"), "\\2"); m_TagMap["color="] = std::make_pair(QRegularExpression("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), ""); m_TagMap["font="] = std::make_pair(QRegularExpression("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), "\\2"); m_TagMap["center"] = std::make_pair(QRegularExpression("\\[center\\](.*)\\[/center\\]"), "
\\1
"); m_TagMap["right"] = std::make_pair(QRegularExpression("\\[right\\](.*)\\[/right\\]"), "
\\1
"); m_TagMap["quote"] = std::make_pair(QRegularExpression("\\[quote\\](.*)\\[/quote\\]"), "
\\1
"); m_TagMap["quote="] = std::make_pair(QRegularExpression("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"), "
\\2
"); m_TagMap["spoiler"] = std::make_pair(QRegularExpression("\\[spoiler\\](.*)\\[/spoiler\\]"), "
Spoiler:
Show
\\1
"); m_TagMap["code"] = std::make_pair(QRegularExpression("\\[code\\](.*)\\[/code\\]"), "\\1"); m_TagMap["heading"] = std::make_pair(QRegularExpression("\\[heading\\](.*)\\[/heading\\]"), "

\\1

"); m_TagMap["line"] = std::make_pair(QRegularExpression("\\[line\\]"), "
"); // lists m_TagMap["list"] = std::make_pair(QRegularExpression("\\[list\\](.*)\\[/list\\]"), "
    \\1
"); m_TagMap["list="] = std::make_pair( QRegularExpression("\\[list.*\\](.*)\\[/list\\]"), "
    \\1
"); m_TagMap["ul"] = std::make_pair(QRegularExpression("\\[ul\\](.*)\\[/ul\\]"), "
    \\1
"); m_TagMap["ol"] = std::make_pair(QRegularExpression("\\[ol\\](.*)\\[/ol\\]"), "
    \\1
"); m_TagMap["li"] = std::make_pair(QRegularExpression("\\[li\\](.*)\\[/li\\]"), "
  • \\1
  • "); // tables m_TagMap["table"] = std::make_pair( QRegularExpression("\\[table\\](.*)\\[/table\\]"), "\\1
    "); m_TagMap["tr"] = std::make_pair(QRegularExpression("\\[tr\\](.*)\\[/tr\\]"), "\\1"); m_TagMap["th"] = std::make_pair(QRegularExpression("\\[th\\](.*)\\[/th\\]"), "\\1"); m_TagMap["td"] = std::make_pair(QRegularExpression("\\[td\\](.*)\\[/td\\]"), "\\1"); // web content m_TagMap["url"] = std::make_pair(QRegularExpression("\\[url\\](.*)\\[/url\\]"), "\\1"); m_TagMap["url="] = std::make_pair(QRegularExpression("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), "\\2"); m_TagMap["img"] = std::make_pair( QRegularExpression( "\\[img(?:\\s*width=\\d+\\s*,?\\s*height=\\d+)?\\](.*)\\[/img\\]"), ""); m_TagMap["img="] = std::make_pair(QRegularExpression("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), "\"\\1\""); m_TagMap["email="] = std::make_pair( QRegularExpression("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), "\\2"); m_TagMap["youtube"] = std::make_pair(QRegularExpression("\\[youtube\\](.*)\\[/youtube\\]"), "https://" "www.youtube.com/watch?v=\\1"); // 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("\\[\\*\\](.*)"), "
  • \\1
  • "); m_ColorMap.insert(std::make_pair("red", "FF0000")); m_ColorMap.insert(std::make_pair("green", "00FF00")); m_ColorMap.insert(std::make_pair("blue", "0000FF")); m_ColorMap.insert(std::make_pair("black", "000000")); m_ColorMap.insert(std::make_pair("gray", "7F7F7F")); m_ColorMap.insert(std::make_pair("white", "FFFFFF")); m_ColorMap.insert(std::make_pair("yellow", "FFFF00")); m_ColorMap.insert(std::make_pair("cyan", "00FFFF")); m_ColorMap.insert(std::make_pair("magenta", "FF00FF")); m_ColorMap.insert(std::make_pair("brown", "A52A2A")); m_ColorMap.insert(std::make_pair("orange", "FFA500")); m_ColorMap.insert(std::make_pair("gold", "FFD700")); m_ColorMap.insert(std::make_pair("deepskyblue", "00BFFF")); m_ColorMap.insert(std::make_pair("salmon", "FA8072")); m_ColorMap.insert(std::make_pair("dodgerblue", "1E90FF")); m_ColorMap.insert(std::make_pair("greenyellow", "ADFF2F")); m_ColorMap.insert(std::make_pair("peru", "CD853F")); } private: QRegularExpression m_TagNameExp; TagMap m_TagMap; std::map 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", "
    "); 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