40 lines
1.9 KiB
C++
40 lines
1.9 KiB
C++
#include "message_links.h"
|
|
#include <QRegularExpression>
|
|
#include <QUrl>
|
|
|
|
/* Отделить адрес от текста, сохраняя сбалансированные скобки внутри пути/query. */
|
|
QVector<MessageLink> messageLinks(const QString& text) {
|
|
static const QRegularExpression pattern(QStringLiteral(
|
|
"utun://[A-Za-z0-9+/]+={0,2}|(?i:https?://)[^\\s\\p{Z}\\p{Cc}<>\"'`«»“”‘’]+"));
|
|
QVector<MessageLink> links;
|
|
auto matches = pattern.globalMatch(text);
|
|
while (matches.hasNext()) {
|
|
const auto match = matches.next();
|
|
QString url = match.captured();
|
|
if (!url.startsWith(QStringLiteral("utun://"))) {
|
|
// Один подсчёт и один проход по хвосту: повторный count() на каждой скобке даёт O(n²).
|
|
qsizetype balance[3] = {};
|
|
for (const QChar ch : url) {
|
|
const int opening = QStringLiteral("([{").indexOf(ch);
|
|
const int closing = QStringLiteral(")]}").indexOf(ch);
|
|
if (opening >= 0) balance[opening]--;
|
|
if (closing >= 0) balance[closing]++;
|
|
}
|
|
auto end = url.size();
|
|
while (end > 0) {
|
|
const QChar last = url.at(end - 1);
|
|
const int closing = QStringLiteral(")]}").indexOf(last);
|
|
const bool unmatched = closing >= 0 && balance[closing] > 0;
|
|
if (!QStringLiteral(".,;:!?").contains(last) && !unmatched) break;
|
|
if (closing >= 0) balance[closing]--;
|
|
end--;
|
|
}
|
|
url.truncate(end);
|
|
const QUrl parsed(url, QUrl::StrictMode);
|
|
if (!parsed.isValid() || parsed.host().isEmpty()) continue;
|
|
}
|
|
links.append({(int)match.capturedStart(), (int)url.length(), url});
|
|
}
|
|
return links;
|
|
}
|