void FlatTextarea::parseLinks() { // some code is duplicated in text.cpp! LinkRanges newLinks; QString text(toPlainText()); if (text.isEmpty()) { if (!_links.isEmpty()) { _links.clear(); emit linksChanged(); } return; } initLinkSets(); int32 len = text.size(); const QChar *start = text.unicode(), *end = start + text.size(); for (int32 offset = 0, matchOffset = offset; offset < len;) { QRegularExpressionMatch m = reDomain().match(text, matchOffset); if (!m.hasMatch()) break; int32 domainOffset = m.capturedStart(); QString protocol = m.captured(1).toLower(); QString topDomain = m.captured(3).toLower(); bool isProtocolValid = protocol.isEmpty() || validProtocols().contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); bool isTopDomainValid = !protocol.isEmpty() || validTopDomains().contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar))); if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) { QString forMailName = text.mid(offset, domainOffset - offset - 1); QRegularExpressionMatch mMailName = reMailName().match(forMailName); if (mMailName.hasMatch()) { offset = matchOffset = m.capturedEnd(); continue; } } if (!isProtocolValid || !isTopDomainValid) { offset = matchOffset = m.capturedEnd(); continue; } QStack<const QChar*> parenth; const QChar *domainEnd = start + m.capturedEnd(), *p = domainEnd; for (; p < end; ++p) { QChar ch(*p); if (chIsLinkEnd(ch)) break; // link finished if (chIsAlmostLinkEnd(ch)) { const QChar *endTest = p + 1; while (endTest < end && chIsAlmostLinkEnd(*endTest)) { ++endTest; } if (endTest >= end || chIsLinkEnd(*endTest)) { break; // link finished at p } p = endTest; ch = *p; } if (ch == '(' || ch == '[' || ch == '{' || ch == '<') { parenth.push(p); } else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') { if (parenth.isEmpty()) break; const QChar *q = parenth.pop(), open(*q); if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) { p = q; break; } } } if (p > domainEnd) { // check, that domain ended if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') { matchOffset = domainEnd - start; continue; } } newLinks.push_back(qMakePair(domainOffset - 1, p - start - domainOffset + 2)); offset = matchOffset = p - start; } if (newLinks != _links) { _links = newLinks; emit linksChanged(); } }
void MessageLinksParser::parse() { const auto &textWithTags = _field->getTextWithTags(); const auto &text = textWithTags.text; const auto &tags = textWithTags.tags; const auto &markdownTags = _field->getMarkdownTags(); if (text.isEmpty()) { _list = QStringList(); return; } auto ranges = QVector<LinkRange>(); auto tag = tags.begin(); const auto tagsEnd = tags.end(); const auto processTag = [&] { Expects(tag != tagsEnd); if (Ui::InputField::IsValidMarkdownLink(tag->id) && !IsMentionLink(tag->id)) { ranges.push_back({ tag->offset, tag->length, tag->id }); } ++tag; }; const auto processTagsBefore = [&](int offset) { while (tag != tagsEnd && tag->offset + tag->length <= offset) { processTag(); } }; const auto hasTagsIntersection = [&](int till) { if (tag == tagsEnd || tag->offset >= till) { return false; } while (tag != tagsEnd && tag->offset < till) { processTag(); } return true; }; auto markdownTag = markdownTags.begin(); const auto markdownTagsEnd = markdownTags.end(); const auto markdownTagsAllow = [&](int from, int length) { while (markdownTag != markdownTagsEnd && (markdownTag->adjustedStart + markdownTag->adjustedLength <= from || !markdownTag->closed)) { ++markdownTag; continue; } if (markdownTag == markdownTagsEnd || markdownTag->adjustedStart >= from + length) { return true; } // Ignore http-links that are completely inside some tags. // This will allow sending http://test.com/__test__/test correctly. return (markdownTag->adjustedStart > from) || (markdownTag->adjustedStart + markdownTag->adjustedLength < from + length); }; const auto len = text.size(); const QChar *start = text.unicode(), *end = start + text.size(); for (auto offset = 0, matchOffset = offset; offset < len;) { auto m = TextUtilities::RegExpDomain().match(text, matchOffset); if (!m.hasMatch()) break; auto domainOffset = m.capturedStart(); auto protocol = m.captured(1).toLower(); auto topDomain = m.captured(3).toLower(); auto isProtocolValid = protocol.isEmpty() || TextUtilities::IsValidProtocol(protocol); auto isTopDomainValid = !protocol.isEmpty() || TextUtilities::IsValidTopDomain(topDomain); if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) { auto forMailName = text.mid(offset, domainOffset - offset - 1); auto mMailName = TextUtilities::RegExpMailNameAtEnd().match(forMailName); if (mMailName.hasMatch()) { offset = matchOffset = m.capturedEnd(); continue; } } if (!isProtocolValid || !isTopDomainValid) { offset = matchOffset = m.capturedEnd(); continue; } QStack<const QChar*> parenth; const QChar *domainEnd = start + m.capturedEnd(), *p = domainEnd; for (; p < end; ++p) { QChar ch(*p); if (chIsLinkEnd(ch)) break; // link finished if (chIsAlmostLinkEnd(ch)) { const QChar *endTest = p + 1; while (endTest < end && chIsAlmostLinkEnd(*endTest)) { ++endTest; } if (endTest >= end || chIsLinkEnd(*endTest)) { break; // link finished at p } p = endTest; ch = *p; } if (ch == '(' || ch == '[' || ch == '{' || ch == '<') { parenth.push(p); } else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') { if (parenth.isEmpty()) break; const QChar *q = parenth.pop(), open(*q); if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) { p = q; break; } } } if (p > domainEnd) { // check, that domain ended if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') { matchOffset = domainEnd - start; continue; } } const auto range = LinkRange { domainOffset, static_cast<int>(p - start - domainOffset), QString() }; processTagsBefore(domainOffset); if (!hasTagsIntersection(range.start + range.length)) { if (markdownTagsAllow(range.start, range.length)) { ranges.push_back(range); } } offset = matchOffset = p - start; } processTagsBefore(QFIXED_MAX); apply(text, ranges); }