From 87f219a78f3fdd117b5b69bbb3a0ce5f75663f65 Mon Sep 17 00:00:00 2001 From: noavarice Date: Sun, 19 Feb 2017 17:48:44 +0300 Subject: [PATCH] refactor: message text formatting works better now - tag intersection detected - variables and constants' names became shorter --- src/chatlog/chatmessage.cpp | 16 ++- src/chatlog/textformatter.cpp | 160 ++++++++++++++++++---------- src/chatlog/textformatter.h | 27 +++-- test/chatlog/textformatter_test.cpp | 52 ++++++--- test/core/toxid_test.cpp | 19 ++++ test/core/toxpk_test.cpp | 19 ++++ 6 files changed, 207 insertions(+), 86 deletions(-) diff --git a/src/chatlog/chatmessage.cpp b/src/chatlog/chatmessage.cpp index d8f86a2eb..fc37130cb 100644 --- a/src/chatlog/chatmessage.cpp +++ b/src/chatlog/chatmessage.cpp @@ -57,10 +57,11 @@ ChatMessage::Ptr ChatMessage::createChatMessage(const QString &sender, const QSt text = detectQuotes(detectAnchors(text), type); //text styling - auto styleType = Settings::getInstance().getStylePreference(); - if (styleType != Settings::StyleType::NONE) { + Settings::StyleType styleType = Settings::getInstance().getStylePreference(); + if (styleType != Settings::StyleType::NONE) + { TextFormatter tf = TextFormatter(text); - text = tf.applyStyling(styleType == Settings::StyleType::WITHOUT_CHARS); + text = tf.applyStyling(styleType == Settings::StyleType::WITH_CHARS); } @@ -254,17 +255,22 @@ QString ChatMessage::detectQuotes(const QString& str, MessageType type) // don't quote first line in action message. This makes co-existence of // quotes and action messages possible, since only first line can cause // problems in case where there is quote in it used. - if (QRegExp("^(>|>).*").exactMatch(messageLines[i])) { + if (QRegExp("^(>|>).*").exactMatch(messageLines[i])) + { if (i > 0 || type != ACTION) quotedText += "" + messageLines[i] + ""; else quotedText += messageLines[i]; - } else { + } + else + { quotedText += messageLines[i]; } if (i < messageLines.size() - 1) + { quotedText += '\n'; + } } return quotedText; diff --git a/src/chatlog/textformatter.cpp b/src/chatlog/textformatter.cpp index 080206cd0..201d9e405 100644 --- a/src/chatlog/textformatter.cpp +++ b/src/chatlog/textformatter.cpp @@ -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 . +*/ + #include "textformatter.h" #include @@ -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]))"); +static const QString COMMON_PATTERN = QStringLiteral("(?<=^|[^%1<])" + "[%1]{%3}" + "(?![%1 \\n])" + ".+?" + "(? fontStylePatterns { +static const QVector fontStylePatterns +{ QStringLiteral("%1"), QStringLiteral("%1"), QStringLiteral("%1"), @@ -32,93 +59,110 @@ static const QVector fontStylePatterns { }; // Unfortunately, can't use simple QMap because ordered applying of styles is required -static const QVector> 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> 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 pair : textPatternStyle) { - QRegularExpression exp = pair.first; - QRegularExpressionMatchIterator matchesIterator = exp.globalMatch(out); + for (QPair 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); } diff --git a/src/chatlog/textformatter.h b/src/chatlog/textformatter.h index e05cd12fa..bed818027 100644 --- a/src/chatlog/textformatter.h +++ b/src/chatlog/textformatter.h @@ -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 . +*/ + #ifndef TEXTFORMATTER_H #define TEXTFORMATTER_H @@ -9,16 +28,12 @@ private: QString sourceString; - int patternEscapeSignsCount(const QString& str); - - int getCapturedLength(const QRegularExpressionMatch& match, const int exprNumber); - - QString applyHtmlFontStyling(bool dontShowFormattingSymbols); + QString applyHtmlFontStyling(bool showFormattingSymbols); public: explicit TextFormatter(const QString& str); - QString applyStyling(bool dontShowFormattingSymbols); + QString applyStyling(bool showFormattingSymbols); }; #endif // TEXTFORMATTER_H diff --git a/test/chatlog/textformatter_test.cpp b/test/chatlog/textformatter_test.cpp index 6bf934139..5918acebd 100644 --- a/test/chatlog/textformatter_test.cpp +++ b/test/chatlog/textformatter_test.cpp @@ -1,7 +1,25 @@ +/* + 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 . +*/ + #include "src/chatlog/textformatter.h" #include "test/common.h" -#include #include #include #include @@ -74,81 +92,81 @@ static const StringToString multilineCode }; /** - * @brief commonTest Testing cases which are common for all types of formatting except multiline code + * @brief Testing cases which are common for all types of formatting except multiline code * @param noSymbols True if it's not allowed to show formatting symbols * @param map Grouped cases * @param signs Combination of formatting symbols */ -void commonTest(bool noSymbols, const StringToString map, const QString signs) +static void commonTest(bool showSymbols, const StringToString map, const QString signs) { for (QString key : map.keys()) { QString source = key.arg(signs); TextFormatter tf = TextFormatter(source); - QString result = map[key].arg(noSymbols ? "" : signs, signsToTags[signs]); - ck_assert(tf.applyStyling(noSymbols) == result); + QString result = map[key].arg(showSymbols ? signs : "", signsToTags[signs]); + ck_assert(tf.applyStyling(showSymbols) == result); } } /** - * @brief commonExceptionsTest Testing exception cases + * @brief Testing exception cases * @param signs Combination of formatting symbols */ -void commonExceptionsTest(const QString signs) +static void commonExceptionsTest(const QString signs) { for (QString source : commonExceptions) { TextFormatter tf = TextFormatter(source.arg(signs)); - ck_assert(tf.applyStyling(true) == source.arg(signs)); + ck_assert(tf.applyStyling(false) == source.arg(signs)); } } /** - * @brief specialTest Testing some uncommon, special cases + * @brief Testing some uncommon, special cases * @param map Grouped cases */ -void specialTest(const StringToString map) +static void specialTest(const StringToString map) { for (QString key : map.keys()) { TextFormatter tf = TextFormatter(key); - ck_assert(tf.applyStyling(true) == map[key]); + ck_assert(tf.applyStyling(false) == map[key]); } } START_TEST(singleSignNoSymbolsTest) { - commonTest(true, commonWorkCases, "*"); + commonTest(false, commonWorkCases, "*"); } END_TEST START_TEST(slashNoSymbolsTest) { - commonTest(true, commonWorkCases, "/"); + commonTest(false, commonWorkCases, "/"); } END_TEST START_TEST(doubleSignNoSymbolsTest) { - commonTest(true, commonWorkCases, "**"); + commonTest(false, commonWorkCases, "**"); } END_TEST START_TEST(singleSignWithSymbolsTest) { - commonTest(false, commonWorkCases, "*"); + commonTest(true, commonWorkCases, "*"); } END_TEST START_TEST(slashWithSymbolsTest) { - commonTest(false, commonWorkCases, "/"); + commonTest(true, commonWorkCases, "/"); } END_TEST START_TEST(doubleSignWithSymbolsTest) { - commonTest(false, commonWorkCases, "**"); + commonTest(true, commonWorkCases, "**"); } END_TEST diff --git a/test/core/toxid_test.cpp b/test/core/toxid_test.cpp index 24574084f..110b05a02 100644 --- a/test/core/toxid_test.cpp +++ b/test/core/toxid_test.cpp @@ -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 . +*/ + #include "src/core/toxid.h" #include "test/common.h" diff --git a/test/core/toxpk_test.cpp b/test/core/toxpk_test.cpp index 1f9747dc4..99c90f03a 100644 --- a/test/core/toxpk_test.cpp +++ b/test/core/toxpk_test.cpp @@ -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 . +*/ + #include "src/core/toxid.h" #include "test/common.h"