|
|
|
@ -1,3 +1,22 @@
@@ -1,3 +1,22 @@
|
|
|
|
|
/*
|
|
|
|
|
Copyright © 2017 by The qTox Project Contributors |
|
|
|
|
|
|
|
|
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
|
|
|
|
|
|
|
|
|
qTox is libre 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. |
|
|
|
|
|
|
|
|
|
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
#include "textformatter.h" |
|
|
|
|
|
|
|
|
|
#include <QMap> |
|
|
|
@ -13,17 +32,25 @@ enum TextStyle {
@@ -13,17 +32,25 @@ enum TextStyle {
|
|
|
|
|
CODE |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
static const QString SINGLE_SIGN_FORMATTING_TEXT_FONT_PATTERN = QStringLiteral("(?:(^|[^\\%1]))(\\%1[^\\s\\%1])([^\\%1\\n]+)([^\\s\\%1]\\%1)(?:($|[^\\%1]))"); |
|
|
|
|
|
|
|
|
|
//Pattern for escaping slashes from inserted HTML tags
|
|
|
|
|
static const QString SINGLE_SLASH_FORMATTING_TEXT_FONT_PATTERN = QStringLiteral("(?:(^|[^/<]))(\\/[^\\s/])([^\\n/]+)([^<\\s/]\\/)(?:($|[^/]))"); |
|
|
|
|
|
|
|
|
|
static const QString DOUBLE_SIGN_FORMATTING_TEXT_FONT_PATTERN = QStringLiteral("(?:(^|[^\\%1]))([\\%1]{2}[^\\s\\%1])([^\\n]+)([^\\s\\%1][\\%1]{2})(?:($|[^\\%1]))"); |
|
|
|
|
|
|
|
|
|
static const QString MULTILINE_CODE_FORMATTING_TEXT_FONT_PATTERN = QStringLiteral("(?:(^|[^`]))([`]{3})((\\n|.)+)([`]{3})(?:($|[^`]))"); |
|
|
|
|
static const QString COMMON_PATTERN = QStringLiteral("(?<=^|[^%1<])" |
|
|
|
|
"[%1]{%3}" |
|
|
|
|
"(?![%1 \\n])" |
|
|
|
|
".+?" |
|
|
|
|
"(?<![%1< \\n])" |
|
|
|
|
"[%1]{%3}" |
|
|
|
|
"(?=$|[^%1])"); |
|
|
|
|
|
|
|
|
|
static const QString MULTILINE_CODE = QStringLiteral("(?<=^|[^`])" |
|
|
|
|
"```" |
|
|
|
|
"(?!`)" |
|
|
|
|
"(.|\\n)+" |
|
|
|
|
"(?<!`)" |
|
|
|
|
"```" |
|
|
|
|
"(?=$|[^`])"); |
|
|
|
|
|
|
|
|
|
// Items in vector associated with TextStyle values respectively. Do NOT change this order
|
|
|
|
|
static const QVector<QString> fontStylePatterns { |
|
|
|
|
static const QVector<QString> fontStylePatterns |
|
|
|
|
{ |
|
|
|
|
QStringLiteral("<b>%1</b>"), |
|
|
|
|
QStringLiteral("<i>%1</i>"), |
|
|
|
|
QStringLiteral("<u>%1</u>"), |
|
|
|
@ -32,93 +59,110 @@ static const QVector<QString> fontStylePatterns {
@@ -32,93 +59,110 @@ static const QVector<QString> fontStylePatterns {
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Unfortunately, can't use simple QMap because ordered applying of styles is required
|
|
|
|
|
static const QVector<QPair<QRegularExpression, QString>> textPatternStyle { |
|
|
|
|
{ QRegularExpression(SINGLE_SLASH_FORMATTING_TEXT_FONT_PATTERN), fontStylePatterns[ITALIC] }, |
|
|
|
|
{ QRegularExpression(SINGLE_SIGN_FORMATTING_TEXT_FONT_PATTERN.arg('*')), fontStylePatterns[BOLD] }, |
|
|
|
|
{ QRegularExpression(SINGLE_SIGN_FORMATTING_TEXT_FONT_PATTERN.arg('_')), fontStylePatterns[UNDERLINE] }, |
|
|
|
|
{ QRegularExpression(SINGLE_SIGN_FORMATTING_TEXT_FONT_PATTERN.arg('~')), fontStylePatterns[STRIKE] }, |
|
|
|
|
{ QRegularExpression(SINGLE_SIGN_FORMATTING_TEXT_FONT_PATTERN.arg('`')), fontStylePatterns[CODE] }, |
|
|
|
|
{ QRegularExpression(DOUBLE_SIGN_FORMATTING_TEXT_FONT_PATTERN.arg('*')), fontStylePatterns[BOLD] }, |
|
|
|
|
{ QRegularExpression(DOUBLE_SIGN_FORMATTING_TEXT_FONT_PATTERN.arg('/')), fontStylePatterns[ITALIC] }, |
|
|
|
|
{ QRegularExpression(DOUBLE_SIGN_FORMATTING_TEXT_FONT_PATTERN.arg('_')), fontStylePatterns[UNDERLINE] }, |
|
|
|
|
{ QRegularExpression(DOUBLE_SIGN_FORMATTING_TEXT_FONT_PATTERN.arg('~')), fontStylePatterns[STRIKE] }, |
|
|
|
|
{ QRegularExpression(MULTILINE_CODE_FORMATTING_TEXT_FONT_PATTERN), fontStylePatterns[CODE] } |
|
|
|
|
static const QVector<QPair<QRegularExpression, QString>> textPatternStyle |
|
|
|
|
{ |
|
|
|
|
{ QRegularExpression(COMMON_PATTERN.arg("*", "1")), fontStylePatterns[BOLD] }, |
|
|
|
|
{ QRegularExpression(COMMON_PATTERN.arg("/", "1")), fontStylePatterns[ITALIC] }, |
|
|
|
|
{ QRegularExpression(COMMON_PATTERN.arg("_", "1")), fontStylePatterns[UNDERLINE] }, |
|
|
|
|
{ QRegularExpression(COMMON_PATTERN.arg("~", "1")), fontStylePatterns[STRIKE] }, |
|
|
|
|
{ QRegularExpression(COMMON_PATTERN.arg("`", "1")), fontStylePatterns[CODE] }, |
|
|
|
|
{ QRegularExpression(COMMON_PATTERN.arg("*", "2")), fontStylePatterns[BOLD] }, |
|
|
|
|
{ QRegularExpression(COMMON_PATTERN.arg("/", "2")), fontStylePatterns[ITALIC] }, |
|
|
|
|
{ QRegularExpression(COMMON_PATTERN.arg("_", "2")), fontStylePatterns[UNDERLINE] }, |
|
|
|
|
{ QRegularExpression(COMMON_PATTERN.arg("~", "2")), fontStylePatterns[STRIKE] }, |
|
|
|
|
{ QRegularExpression(MULTILINE_CODE), fontStylePatterns[CODE] } |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
TextFormatter::TextFormatter(const QString &str) |
|
|
|
|
: sourceString(str) {} |
|
|
|
|
: sourceString(str) |
|
|
|
|
{ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief TextFormatter::patternEscapeSignsCount Counts equal symbols at the beginning of the string |
|
|
|
|
* @brief Counts equal symbols at the beginning of the string |
|
|
|
|
* @param str Source string |
|
|
|
|
* @return Amount of equal symbols at the beginning of the string |
|
|
|
|
*/ |
|
|
|
|
int TextFormatter::patternEscapeSignsCount(const QString &str) { |
|
|
|
|
static int patternSignsCount(const QString& str) |
|
|
|
|
{ |
|
|
|
|
QChar escapeSign = str.at(0); |
|
|
|
|
int result = 0; |
|
|
|
|
for (const QChar c : str) { |
|
|
|
|
if (c == escapeSign) |
|
|
|
|
++result; |
|
|
|
|
else |
|
|
|
|
break; |
|
|
|
|
int length = str.length(); |
|
|
|
|
while (result < length && str[result] == escapeSign) |
|
|
|
|
{ |
|
|
|
|
++result; |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief TextFormatter::getCapturedLength Get length of string captured by subexpression with appropriate checks |
|
|
|
|
* @param match Global match of QRegularExpression |
|
|
|
|
* @param exprNumber Number of subexpression |
|
|
|
|
* @return Length of captured string. If nothing was captured, returns 0 |
|
|
|
|
* @brief Checks HTML tags intersection while applying styles to the message text |
|
|
|
|
* @param str Checking string |
|
|
|
|
* @return True, if tag intersection detected |
|
|
|
|
*/ |
|
|
|
|
int TextFormatter::getCapturedLength(const QRegularExpressionMatch &match, const int exprNumber) { |
|
|
|
|
QString captured = match.captured(exprNumber); |
|
|
|
|
return captured.isNull() || captured.isEmpty() ? 0 : captured.length(); |
|
|
|
|
static bool isTagIntersection(const QString& str) |
|
|
|
|
{ |
|
|
|
|
const QRegularExpression TAG_PATTERN("(?<=<)/?[a-zA-Z0-9]+(?=>)"); |
|
|
|
|
|
|
|
|
|
int openingTagCount = 0; |
|
|
|
|
int closingTagCount = 0; |
|
|
|
|
|
|
|
|
|
QRegularExpressionMatchIterator iter = TAG_PATTERN.globalMatch(str); |
|
|
|
|
while (iter.hasNext()) |
|
|
|
|
{ |
|
|
|
|
iter.next().captured()[0] == '/' |
|
|
|
|
? ++closingTagCount |
|
|
|
|
: ++openingTagCount; |
|
|
|
|
} |
|
|
|
|
return openingTagCount != closingTagCount; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief TextFormatter::applyHtmlFontStyling Applies styles to the font of text that was passed to the constructor |
|
|
|
|
* @param dontShowFormattingSymbols True, if it does not suppose to include formatting symbols into resulting string |
|
|
|
|
* @brief Applies styles to the font of text that was passed to the constructor |
|
|
|
|
* @param showFormattingSymbols True, if it is supposed to include formatting symbols into resulting string |
|
|
|
|
* @return Source text with styled font |
|
|
|
|
*/ |
|
|
|
|
QString TextFormatter::applyHtmlFontStyling(bool dontShowFormattingSymbols){ |
|
|
|
|
QString TextFormatter::applyHtmlFontStyling(bool showFormattingSymbols) |
|
|
|
|
{ |
|
|
|
|
QString out = sourceString; |
|
|
|
|
int choppingSignsCountMultiplier = dontShowFormattingSymbols ? 0 : 1; |
|
|
|
|
|
|
|
|
|
for (QPair<QRegularExpression, QString> pair : textPatternStyle) { |
|
|
|
|
QRegularExpression exp = pair.first; |
|
|
|
|
QRegularExpressionMatchIterator matchesIterator = exp.globalMatch(out); |
|
|
|
|
for (QPair<QRegularExpression, QString> pair : textPatternStyle) |
|
|
|
|
{ |
|
|
|
|
QRegularExpressionMatchIterator matchesIterator = pair.first.globalMatch(out); |
|
|
|
|
int insertedTagSymbolsCount = 0; |
|
|
|
|
|
|
|
|
|
while (matchesIterator.hasNext()) { |
|
|
|
|
while (matchesIterator.hasNext()) |
|
|
|
|
{ |
|
|
|
|
QRegularExpressionMatch match = matchesIterator.next(); |
|
|
|
|
if (isTagIntersection(match.captured())) |
|
|
|
|
{ |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Regular expressions may capture one redundant symbol from both sides because of extra check, so we don't need to handle them
|
|
|
|
|
int firstCheckResultLength = getCapturedLength(match, 1); |
|
|
|
|
int matchStart = match.capturedStart() + firstCheckResultLength + insertedTagSymbolsCount; |
|
|
|
|
int matchLength = match.capturedLength() - firstCheckResultLength - getCapturedLength(match, exp.captureCount()); |
|
|
|
|
int capturedStart = match.capturedStart() + insertedTagSymbolsCount; |
|
|
|
|
int capturedLength = match.capturedLength(); |
|
|
|
|
|
|
|
|
|
int choppingSignsCount = patternEscapeSignsCount(out.mid(matchStart, matchLength)); |
|
|
|
|
int textStart = matchStart + choppingSignsCount; |
|
|
|
|
int textLength = matchLength - choppingSignsCount * 2; |
|
|
|
|
QString stylingText = out.mid(capturedStart, capturedLength); |
|
|
|
|
int choppingSignsCount = showFormattingSymbols ? 0 : patternSignsCount(stylingText); |
|
|
|
|
int textStart = capturedStart + choppingSignsCount; |
|
|
|
|
int textLength = capturedLength - 2 * choppingSignsCount; |
|
|
|
|
|
|
|
|
|
QString styledText = pair.second.arg(out.mid(textStart, textLength)); |
|
|
|
|
|
|
|
|
|
textStart = matchStart + choppingSignsCount * choppingSignsCountMultiplier; |
|
|
|
|
textLength = matchLength - choppingSignsCount * choppingSignsCountMultiplier * 2; |
|
|
|
|
|
|
|
|
|
out.replace(textStart, textLength, styledText); |
|
|
|
|
insertedTagSymbolsCount += pair.second.length() - 2 - choppingSignsCount * (1 - choppingSignsCountMultiplier) * 2; |
|
|
|
|
out.replace(capturedStart, capturedLength, styledText); |
|
|
|
|
// Subtracting length of "%1"
|
|
|
|
|
insertedTagSymbolsCount += pair.second.length() - 2 - 2 * choppingSignsCount; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return out; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief TextFormatter::applyStyling Applies all styling for the text |
|
|
|
|
* @param dontShowFormattingSymbols True, if it does not suppose to include formatting symbols into resulting string |
|
|
|
|
* @brief Applies all styling for the text |
|
|
|
|
* @param showFormattingSymbols True, if it is supposed to include formatting symbols into resulting string |
|
|
|
|
* @return Styled string |
|
|
|
|
*/ |
|
|
|
|
QString TextFormatter::applyStyling(bool dontShowFormattingSymbols) { |
|
|
|
|
return applyHtmlFontStyling(dontShowFormattingSymbols); |
|
|
|
|
QString TextFormatter::applyStyling(bool showFormattingSymbols) |
|
|
|
|
{ |
|
|
|
|
return applyHtmlFontStyling(showFormattingSymbols); |
|
|
|
|
} |
|
|
|
|