Remove Redundant Parsing of Links (#4507)
Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
+19
-15
@@ -67,7 +67,7 @@ namespace {
|
||||
|
||||
LinkParser::LinkParser(const QString &unparsedString)
|
||||
{
|
||||
this->match_ = unparsedString;
|
||||
ParsedLink result;
|
||||
|
||||
// This is not implemented with a regex to increase performance.
|
||||
// We keep removing parts of the url until there's either nothing left or we fail.
|
||||
@@ -79,11 +79,13 @@ LinkParser::LinkParser(const QString &unparsedString)
|
||||
if (l.startsWith("https://", Qt::CaseInsensitive))
|
||||
{
|
||||
hasHttp = true;
|
||||
result.protocol = l.mid(0, 8);
|
||||
l = l.mid(8);
|
||||
}
|
||||
else if (l.startsWith("http://", Qt::CaseInsensitive))
|
||||
{
|
||||
hasHttp = true;
|
||||
result.protocol = l.mid(0, 7);
|
||||
l = l.mid(7);
|
||||
}
|
||||
|
||||
@@ -92,8 +94,10 @@ LinkParser::LinkParser(const QString &unparsedString)
|
||||
|
||||
// Host `a.b.c.com`
|
||||
QStringRef host = l;
|
||||
ParsedLink::StringView rest;
|
||||
bool lastWasDot = true;
|
||||
bool inIpv6 = false;
|
||||
bool hasMatch = false;
|
||||
|
||||
for (int i = 0; i < l.size(); i++)
|
||||
{
|
||||
@@ -111,24 +115,28 @@ LinkParser::LinkParser(const QString &unparsedString)
|
||||
if (l[i] == ':' && !inIpv6)
|
||||
{
|
||||
host = l.mid(0, i);
|
||||
rest = l.mid(i);
|
||||
l = l.mid(i + 1);
|
||||
goto parsePort;
|
||||
}
|
||||
else if (l[i] == '/')
|
||||
{
|
||||
host = l.mid(0, i);
|
||||
rest = l.mid(i);
|
||||
l = l.mid(i + 1);
|
||||
goto parsePath;
|
||||
}
|
||||
else if (l[i] == '?')
|
||||
{
|
||||
host = l.mid(0, i);
|
||||
rest = l.mid(i);
|
||||
l = l.mid(i + 1);
|
||||
goto parseQuery;
|
||||
}
|
||||
else if (l[i] == '#')
|
||||
{
|
||||
host = l.mid(0, i);
|
||||
rest = l.mid(i);
|
||||
l = l.mid(i + 1);
|
||||
goto parseAnchor;
|
||||
}
|
||||
@@ -176,32 +184,28 @@ parseAnchor:
|
||||
|
||||
done:
|
||||
// check host
|
||||
this->hasMatch_ = isValidHostname(host) || isValidIpv4(host)
|
||||
hasMatch = isValidHostname(host) || isValidIpv4(host)
|
||||
#ifdef C_MATCH_IPV6_LINK
|
||||
|
||||
|| (hasHttp && isValidIpv6(host))
|
||||
|| (hasHttp && isValidIpv6(host))
|
||||
#endif
|
||||
;
|
||||
|
||||
if (this->hasMatch_)
|
||||
if (hasMatch)
|
||||
{
|
||||
this->match_ = unparsedString;
|
||||
result.host = host;
|
||||
result.rest = rest;
|
||||
result.source = unparsedString;
|
||||
this->result_ = std::move(result);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
hasMatch_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bool LinkParser::hasMatch() const
|
||||
const std::optional<ParsedLink> &LinkParser::result() const
|
||||
{
|
||||
return this->hasMatch_;
|
||||
}
|
||||
|
||||
QString LinkParser::getCaptured() const
|
||||
{
|
||||
return this->match_;
|
||||
return this->result_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -2,19 +2,31 @@
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct ParsedLink {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
using StringView = QStringView;
|
||||
#else
|
||||
using StringView = QStringRef;
|
||||
#endif
|
||||
StringView protocol;
|
||||
StringView host;
|
||||
StringView rest;
|
||||
QString source;
|
||||
};
|
||||
|
||||
class LinkParser
|
||||
{
|
||||
public:
|
||||
explicit LinkParser(const QString &unparsedString);
|
||||
|
||||
bool hasMatch() const;
|
||||
QString getCaptured() const;
|
||||
const std::optional<ParsedLink> &result() const;
|
||||
|
||||
private:
|
||||
bool hasMatch_{false};
|
||||
QString match_;
|
||||
std::optional<ParsedLink> result_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Env.hpp"
|
||||
#include "common/LinkParser.hpp"
|
||||
#include "common/NetworkResult.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "common/SignalVector.hpp"
|
||||
@@ -148,15 +149,15 @@ bool appendWhisperMessageWordsLocally(const QStringList &words)
|
||||
void operator()(const QString &string,
|
||||
MessageBuilder &b) const
|
||||
{
|
||||
auto linkString = b.matchLink(string);
|
||||
if (linkString.isEmpty())
|
||||
LinkParser parser(string);
|
||||
if (parser.result())
|
||||
{
|
||||
b.emplace<TextElement>(string,
|
||||
MessageElementFlag::Text);
|
||||
b.addLink(*parser.result());
|
||||
}
|
||||
else
|
||||
{
|
||||
b.addLink(string, linkString);
|
||||
b.emplace<TextElement>(string,
|
||||
MessageElementFlag::Text);
|
||||
}
|
||||
}
|
||||
} visitor;
|
||||
|
||||
@@ -240,10 +240,10 @@ MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text,
|
||||
text.split(QRegularExpression("\\s"), Qt::SkipEmptyParts);
|
||||
for (const auto &word : textFragments)
|
||||
{
|
||||
const auto linkString = this->matchLink(word);
|
||||
if (!linkString.isEmpty())
|
||||
LinkParser parser(word);
|
||||
if (parser.result())
|
||||
{
|
||||
this->addLink(word, linkString);
|
||||
this->addLink(*parser.result());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -707,52 +707,25 @@ std::unique_ptr<MessageElement> MessageBuilder::releaseBack()
|
||||
return ptr;
|
||||
}
|
||||
|
||||
QString MessageBuilder::matchLink(const QString &string)
|
||||
void MessageBuilder::addLink(const ParsedLink &parsedLink)
|
||||
{
|
||||
LinkParser linkParser(string);
|
||||
|
||||
static QRegularExpression httpRegex(
|
||||
"\\bhttps?://", QRegularExpression::CaseInsensitiveOption);
|
||||
static QRegularExpression ftpRegex(
|
||||
"\\bftps?://", QRegularExpression::CaseInsensitiveOption);
|
||||
static QRegularExpression spotifyRegex(
|
||||
"\\bspotify:", QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
if (!linkParser.hasMatch())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString captured = linkParser.getCaptured();
|
||||
|
||||
if (!captured.contains(httpRegex) && !captured.contains(ftpRegex) &&
|
||||
!captured.contains(spotifyRegex))
|
||||
{
|
||||
captured.insert(0, "http://");
|
||||
}
|
||||
|
||||
return captured;
|
||||
}
|
||||
|
||||
void MessageBuilder::addLink(const QString &origLink,
|
||||
const QString &matchedLink)
|
||||
{
|
||||
static QRegularExpression domainRegex(
|
||||
R"(^(?:(?:ftp|http)s?:\/\/)?([^\/]+)(?:\/.*)?$)",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
QString lowercaseLinkString;
|
||||
auto match = domainRegex.match(origLink);
|
||||
if (match.isValid())
|
||||
QString origLink = parsedLink.source;
|
||||
QString matchedLink;
|
||||
|
||||
if (parsedLink.protocol.isNull())
|
||||
{
|
||||
lowercaseLinkString = origLink.mid(0, match.capturedStart(1)) +
|
||||
match.captured(1).toLower() +
|
||||
origLink.mid(match.capturedEnd(1));
|
||||
matchedLink = QStringLiteral("http://") + parsedLink.source;
|
||||
}
|
||||
else
|
||||
{
|
||||
lowercaseLinkString = origLink;
|
||||
lowercaseLinkString += parsedLink.protocol;
|
||||
matchedLink = parsedLink.source;
|
||||
}
|
||||
|
||||
lowercaseLinkString += parsedLink.host.toString().toLower();
|
||||
lowercaseLinkString += parsedLink.rest;
|
||||
|
||||
auto linkElement = Link(Link::Url, matchedLink);
|
||||
|
||||
auto textColor = MessageColor(MessageColor::Link);
|
||||
@@ -816,12 +789,10 @@ void MessageBuilder::addIrcMessageText(const QString &text)
|
||||
auto string = QString(word);
|
||||
|
||||
// Actually just text
|
||||
auto linkString = this->matchLink(string);
|
||||
auto link = Link();
|
||||
|
||||
if (!linkString.isEmpty())
|
||||
LinkParser parser(string);
|
||||
if (parser.result())
|
||||
{
|
||||
this->addLink(string, linkString);
|
||||
this->addLink(*parser.result());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -909,28 +880,24 @@ void MessageBuilder::addTextOrEmoji(const QString &string_)
|
||||
auto string = QString(string_);
|
||||
|
||||
// Actually just text
|
||||
auto linkString = this->matchLink(string);
|
||||
auto link = Link();
|
||||
LinkParser linkParser(string);
|
||||
if (linkParser.result())
|
||||
{
|
||||
this->addLink(*linkParser.result());
|
||||
return;
|
||||
}
|
||||
|
||||
auto &&textColor = this->textColor_;
|
||||
if (linkString.isEmpty())
|
||||
if (string.startsWith('@'))
|
||||
{
|
||||
if (string.startsWith('@'))
|
||||
{
|
||||
this->emplace<TextElement>(string, MessageElementFlag::BoldUsername,
|
||||
textColor, FontStyle::ChatMediumBold);
|
||||
this->emplace<TextElement>(
|
||||
string, MessageElementFlag::NonBoldUsername, textColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emplace<TextElement>(string, MessageElementFlag::Text,
|
||||
textColor);
|
||||
}
|
||||
this->emplace<TextElement>(string, MessageElementFlag::BoldUsername,
|
||||
textColor, FontStyle::ChatMediumBold);
|
||||
this->emplace<TextElement>(string, MessageElementFlag::NonBoldUsername,
|
||||
textColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->addLink(string, linkString);
|
||||
this->emplace<TextElement>(string, MessageElementFlag::Text, textColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ class TextElement;
|
||||
struct Emote;
|
||||
using EmotePtr = std::shared_ptr<const Emote>;
|
||||
|
||||
struct ParsedLink;
|
||||
|
||||
struct SystemMessageTag {
|
||||
};
|
||||
struct TimeoutMessageTag {
|
||||
@@ -94,8 +96,7 @@ public:
|
||||
std::weak_ptr<Message> weakOf();
|
||||
|
||||
void append(std::unique_ptr<MessageElement> element);
|
||||
QString matchLink(const QString &string);
|
||||
void addLink(const QString &origLink, const QString &matchedLink);
|
||||
void addLink(const ParsedLink &parsedLink);
|
||||
|
||||
/**
|
||||
* Adds the text, applies irc colors, adds links,
|
||||
|
||||
@@ -15,8 +15,10 @@ bool LinkPredicate::appliesToImpl(const Message &message)
|
||||
{
|
||||
for (const auto &word : message.messageText.split(' ', Qt::SkipEmptyParts))
|
||||
{
|
||||
if (LinkParser(word).hasMatch())
|
||||
if (LinkParser(word).result())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/LinkParser.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/ignores/IgnoreController.hpp"
|
||||
@@ -465,12 +466,12 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
|
||||
}
|
||||
|
||||
// Actually just text
|
||||
auto linkString = this->matchLink(string);
|
||||
LinkParser parsed(string);
|
||||
auto textColor = this->textColor_;
|
||||
|
||||
if (!linkString.isEmpty())
|
||||
if (parsed.result())
|
||||
{
|
||||
this->addLink(string, linkString);
|
||||
this->addLink(*parsed.result());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user