diff --git a/autotests/ktexttohtmltest.cpp b/autotests/ktexttohtmltest.cpp
--- a/autotests/ktexttohtmltest.cpp
+++ b/autotests/ktexttohtmltest.cpp
@@ -439,6 +439,68 @@
<< KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "@@ -55,6 +55,10 @@ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/";
+ // tel: urls
+ QTest::newRow("tel url compact") << "bla bla bla bla"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla bla <tel:+491234567890> bla bla";
+ QTest::newRow("tel url fancy") << "bla bla tel:+49-321-123456 bla bla"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla bla tel:+49-321-123456 bla bla";
+
+ // phone numbers
+ QTest::newRow("tel compact international") << "call +49123456789, then hang up"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "call +49123456789, then hang up";
+ QTest::newRow("tel parenthesis/spaces international") << "phone:+33 (01) 12 34 56 78 blub"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "phone:+33 (01) 12 34 56 78 blub";
+ QTest::newRow("tel dashes international") << "bla +44-321-1-234-567"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla +44-321-1-234-567";
+ QTest::newRow("tel dashes/spaces international") << "+1 123-456-7000 blub"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "+1 123-456-7000 blub";
+ QTest::newRow("tel spaces international") << "bla +32 1 234 5678 blub"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla +32 1 234 5678 blub";
+ QTest::newRow("tel slash domestic") << "bla 030/12345678 blub"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla 030/12345678 blub";
+ QTest::newRow("tel slash/space domestic") << "Tel.: 089 / 12 34 56 78"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "Tel.: 089 / 12 34 56 78";
+ QTest::newRow("tel follow by parenthesis") << "Telefon: 0 18 05 / 12 23 46 (14 Cent/Min.*)"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "Telefon: 0 18 05 / 12 23 46 (14 Cent/Min.*)";
+ QTest::newRow("tel space single digit at end") << "0123/123 456 7"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "0123/123 456 7";
+ QTest::newRow("tel space around dash") << "bla +49 (0) 12 23 - 45 6000 blub"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla +49 (0) 12 23 - 45 6000 blub";
+
+ // negative tests for phone numbers
+ QTest::newRow("non-tel number") << "please send 1200 cakes"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "please send 1200 cakes";
+ QTest::newRow("non-tel alpha-numeric") << "bla 1-123-456-ABCD blub"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla 1-123-456-ABCD blub";
+ QTest::newRow("non-tel alpha prefix") << "ABCD0123-456-789"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "ABCD0123-456-789";
+ QTest::newRow("non-tel date") << "bla 02/03/2019 blub"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla 02/03/2019 blub";
+ QTest::newRow("non-tel too long") << "bla +012-4567890123456 blub"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla +012-4567890123456 blub";
+ QTest::newRow("non-tel unbalanced") << "bla +012-456789(01 blub"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla +012-456789(01 blub";
+ QTest::newRow("non-tel nested") << "bla +012-4(56(78)90)1 blub"
+ << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
+ << "bla +012-4(56(78)90)1 blub";
}
diff --git a/src/lib/text/ktexttohtml.cpp b/src/lib/text/ktexttohtml.cpp
--- a/src/lib/text/ktexttohtml.cpp
+++ b/src/lib/text/ktexttohtml.cpp
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -143,6 +144,60 @@
return address;
}
+QString KTextToHTMLHelper::getPhoneNumber()
+{
+ if (!mText[mPos].isDigit() && mText[mPos] != QLatin1Char('+')) {
+ return {};
+ }
+
+ static const QString allowedBeginSeparators = QStringLiteral(" \r\t\n:");
+ if (mPos > 0 && !allowedBeginSeparators.contains(mText[mPos - 1])) {
+ return {};
+ }
+
+ // this isn't 100% accurate, we filter stuff below that is too hard to capture with a regexp
+ static const QRegularExpression telPattern(QStringLiteral(R"([+0](( |( ?[/-] ?)?)\(?\d+\)?+){6,30})"));
+ const auto match = telPattern.match(mText, mPos, QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption);
+ if (match.hasMatch()) {
+ auto m = match.captured();
+ // check for maximum number of digits (15), see https://en.wikipedia.org/wiki/Telephone_numbering_plan
+ if (std::count_if(m.begin(), m.end(), [](const QChar &c) { return c.isDigit(); }) > 15) {
+ return {};
+ }
+ // only one / is allowed, otherwise we trigger on dates
+ if (std::count(m.begin(), m.end(), QLatin1Char('/')) > 1) {
+ return {};
+ }
+
+ // parenthesis need to be balanced, and must not be nested
+ int openIdx = -1;
+ for (int i = 0; i < m.size(); ++i) {
+ if ((m[i] == QLatin1Char('(') && openIdx >= 0) || (m[i] == QLatin1Char(')') && openIdx < 0)) {
+ return {};
+ }
+ if (m[i] == QLatin1Char('(')) {
+ openIdx = i;
+ } else if (m[i] == QLatin1Char(')')) {
+ openIdx = -1;
+ }
+ }
+ if (openIdx > 0) {
+ m = m.left(openIdx - 1).trimmed();
+ }
+
+ // check if there's a plausible separator at the end
+ static const QString allowedEndSeparators = QStringLiteral(" \r\t\n,.");
+ const auto l = m.size();
+ if (mText.size() > mPos + l && !allowedEndSeparators.contains(mText[mPos + l])) {
+ return {};
+ }
+
+ mPos += l - 1;
+ return m;
+ }
+ return {};
+}
+
bool KTextToHTMLHelper::atUrl() const
{
// the following characters are allowed in a dot-atom (RFC 2822):
@@ -170,7 +225,9 @@
(ch == QLatin1Char('w') && mText.midRef(mPos, 4) == QLatin1String("www.")) ||
(ch == QLatin1Char('f') && (mText.midRef(mPos, 4) == QLatin1String("ftp.") ||
mText.midRef(mPos, 7) == QLatin1String("file://"))) ||
- (ch == QLatin1Char('n') && mText.midRef(mPos, 5) == QLatin1String("news:"));
+ (ch == QLatin1Char('n') && mText.midRef(mPos, 5) == QLatin1String("news:")) ||
+ (ch == QLatin1Char('t') && mText.midRef(mPos, 4) == QLatin1String("tel:"));
+
}
bool KTextToHTMLHelper::isEmptyUrl(const QString &url) const
@@ -499,6 +556,12 @@
x += str.length() - 1;
continue;
}
+ str = helper.getPhoneNumber();
+ if (!str.isEmpty()) {
+ result += QLatin1String("") + str + QLatin1String("");
+ x += str.length() - 1;
+ continue;
+ }
}
if (flags & HighlightText) {
str = helper.highlightedText();
diff --git a/src/lib/text/ktexttohtml_p.h b/src/lib/text/ktexttohtml_p.h
--- a/src/lib/text/ktexttohtml_p.h
+++ b/src/lib/text/ktexttohtml_p.h
@@ -47,6 +47,7 @@
KTextToHTMLEmoticonsInterface *emoticonsInterface() const;
QString getEmailAddress();
+ QString getPhoneNumber();
bool atUrl() const;
bool isEmptyUrl(const QString &url) const;
QString getUrl(bool *badurl = nullptr);