KCoreAddons

ktexttohtml.cpp
1 /*
2  SPDX-FileCopyrightText: 2002 Dave Corrie <[email protected]>
3  SPDX-FileCopyrightText: 2014 Daniel Vr├ítil <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "ktexttohtml.h"
9 #include "ktexttohtml_p.h"
10 #include "ktexttohtmlemoticonsinterface.h"
11 
12 #include <QCoreApplication>
13 #include <QFile>
14 #include <QPluginLoader>
15 #include <QRegularExpression>
16 #include <QStringList>
17 
18 #include <limits.h>
19 
20 static KTextToHTMLEmoticonsInterface *s_emoticonsInterface = nullptr;
21 
22 static void loadEmoticonsPlugin()
23 {
24  static bool triedLoadPlugin = false;
25  if (!triedLoadPlugin) {
26  triedLoadPlugin = true;
27 
28  // Check if QGuiApplication::platformName property exists. This is a
29  // hackish way of determining whether we are running QGuiApplication,
30  // because we cannot load the FrameworkIntegration plugin into a
31  // QCoreApplication, as it would crash immediately
32  if (qApp->metaObject()->indexOfProperty("platformName") > -1) {
33  QPluginLoader lib(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/KEmoticonsIntegrationPlugin"));
34  QObject *rootObj = lib.instance();
35  if (rootObj) {
36  s_emoticonsInterface = rootObj->property(KTEXTTOHTMLEMOTICONS_PROPERTY).value<KTextToHTMLEmoticonsInterface *>();
37  }
38  }
39  }
40  if (!s_emoticonsInterface) {
41  s_emoticonsInterface = new KTextToHTMLEmoticonsDummy();
42  }
43 }
44 
45 KTextToHTMLHelper::KTextToHTMLHelper(const QString &plainText, int pos, int maxUrlLen, int maxAddressLen)
46  : mText(plainText)
47  , mMaxUrlLen(maxUrlLen)
48  , mMaxAddressLen(maxAddressLen)
49  , mPos(pos)
50 {
51 }
52 
53 KTextToHTMLEmoticonsInterface *KTextToHTMLHelper::emoticonsInterface() const
54 {
55  if (!s_emoticonsInterface) {
56  loadEmoticonsPlugin();
57  }
58  return s_emoticonsInterface;
59 }
60 
61 QString KTextToHTMLHelper::getEmailAddress()
62 {
64 
65  if (mPos < mText.length() && mText.at(mPos) == QLatin1Char('@')) {
66  // the following characters are allowed in a dot-atom (RFC 2822):
67  // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
68  const QString allowedSpecialChars = QStringLiteral(".!#$%&'*+-/=?^_`{|}~");
69 
70  // determine the local part of the email address
71  int start = mPos - 1;
72  while (start >= 0 && mText.at(start).unicode() < 128
73  && (mText.at(start).isLetterOrNumber() //
74  || mText.at(start) == QLatin1Char('@') // allow @ to find invalid email addresses
75  || allowedSpecialChars.indexOf(mText.at(start)) != -1)) {
76  if (mText.at(start) == QLatin1Char('@')) {
77  return QString(); // local part contains '@' -> no email address
78  }
79  --start;
80  }
81  ++start;
82  // we assume that an email address starts with a letter or a digit
83  while ((start < mPos) && !mText.at(start).isLetterOrNumber()) {
84  ++start;
85  }
86  if (start == mPos) {
87  return QString(); // local part is empty -> no email address
88  }
89 
90  // determine the domain part of the email address
91  int dotPos = INT_MAX;
92  int end = mPos + 1;
93  while (end < mText.length()
94  && (mText.at(end).isLetterOrNumber() //
95  || mText.at(end) == QLatin1Char('@') // allow @ to find invalid email addresses
96  || mText.at(end) == QLatin1Char('.') //
97  || mText.at(end) == QLatin1Char('-'))) {
98  if (mText.at(end) == QLatin1Char('@')) {
99  return QString(); // domain part contains '@' -> no email address
100  }
101  if (mText.at(end) == QLatin1Char('.')) {
102  dotPos = qMin(dotPos, end); // remember index of first dot in domain
103  }
104  ++end;
105  }
106  // we assume that an email address ends with a letter or a digit
107  while ((end > mPos) && !mText.at(end - 1).isLetterOrNumber()) {
108  --end;
109  }
110  if (end == mPos) {
111  return QString(); // domain part is empty -> no email address
112  }
113  if (dotPos >= end) {
114  return QString(); // domain part doesn't contain a dot
115  }
116 
117  if (end - start > mMaxAddressLen) {
118  return QString(); // too long -> most likely no email address
119  }
120  address = mText.mid(start, end - start);
121 
122  mPos = end - 1;
123  }
124  return address;
125 }
126 
127 QString KTextToHTMLHelper::getPhoneNumber()
128 {
129  if (!mText.at(mPos).isDigit() && mText.at(mPos) != QLatin1Char('+')) {
130  return {};
131  }
132 
133  const QString allowedBeginSeparators = QStringLiteral(" \r\t\n:");
134  if (mPos > 0 && !allowedBeginSeparators.contains(mText.at(mPos - 1))) {
135  return {};
136  }
137 
138  // this isn't 100% accurate, we filter stuff below that is too hard to capture with a regexp
139  static const QRegularExpression telPattern(QStringLiteral(R"([+0](( |( ?[/-] ?)?)\(?\d+\)?+){6,30})"));
140  const auto match = telPattern.match(mText, mPos, QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption);
141  if (match.hasMatch()) {
142  QStringView matchedText = match.capturedView();
143  // check for maximum number of digits (15), see https://en.wikipedia.org/wiki/Telephone_numbering_plan
144  const int digitsCount = std::count_if(matchedText.cbegin(), matchedText.cend(), [](const QChar c) {
145  return c.isDigit();
146  });
147 
148  if (digitsCount > 15) {
149  return {};
150  }
151 
152  // only one / is allowed, otherwise we trigger on dates
153  if (matchedText.count(QLatin1Char('/')) > 1) {
154  return {};
155  }
156 
157  // parenthesis need to be balanced, and must not be nested
158  int openIdx = -1;
159  for (int i = 0, size = matchedText.size(); i < size; ++i) {
160  const QChar ch = matchedText.at(i);
161  if ((ch == QLatin1Char('(') && openIdx >= 0) || (ch == QLatin1Char(')') && openIdx < 0)) {
162  return {};
163  }
164 
165  if (ch == QLatin1Char('(')) {
166  openIdx = i;
167  } else if (ch == QLatin1Char(')')) {
168  openIdx = -1;
169  }
170  }
171 
172  if (openIdx > 0) {
173  matchedText.truncate(openIdx - 1);
174  matchedText = matchedText.trimmed();
175  }
176 
177  // check if there's a plausible separator at the end
178  const int matchedTextLength = matchedText.size();
179  const int endIdx = mPos + matchedTextLength;
180  if (endIdx < mText.size() && !QStringView(u" \r\t\n,.").contains(mText.at(endIdx))) {
181  return {};
182  }
183 
184  mPos += matchedTextLength - 1;
185  return matchedText.toString();
186  }
187  return {};
188 }
189 
190 static QString normalizePhoneNumber(const QString &str)
191 {
192  QString res;
193  res.reserve(str.size());
194  for (const auto c : str) {
195  if (c.isDigit() || c == QLatin1Char('+')) {
196  res.push_back(c);
197  }
198  }
199  return res;
200 }
201 
202 // The following characters are allowed in a dot-atom (RFC 2822):
203 // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
204 static const char s_allowedSpecialChars[] = ".!#$%&'*+-/=?^_`{|}~";
205 
206 bool KTextToHTMLHelper::atUrl() const
207 {
208  // The character directly before the URL must not be a letter, a number or
209  // any other character allowed in a dot-atom (RFC 2822).
210  if (mPos > 0) {
211  const auto chBefore = mText.at(mPos - 1);
212  if (chBefore.isLetterOrNumber() || QLatin1String(s_allowedSpecialChars).contains(chBefore)) {
213  return false;
214  }
215  }
216 
217  const auto segment = QStringView(mText).mid(mPos);
218  /* clang-format off */
219  return segment.startsWith(QLatin1String("http://"))
220  || segment.startsWith(QLatin1String("https://"))
221  || segment.startsWith(QLatin1String("vnc://"))
222  || segment.startsWith(QLatin1String("fish://"))
223  || segment.startsWith(QLatin1String("ftp://"))
224  || segment.startsWith(QLatin1String("ftps://"))
225  || segment.startsWith(QLatin1String("sftp://"))
226  || segment.startsWith(QLatin1String("smb://"))
227  || segment.startsWith(QLatin1String("irc://"))
228  || segment.startsWith(QLatin1String("ircs://"))
229  || segment.startsWith(QLatin1String("mailto:"))
230  || segment.startsWith(QLatin1String("www."))
231  || segment.startsWith(QLatin1String("ftp."))
232  || segment.startsWith(QLatin1String("file://"))
233  || segment.startsWith(QLatin1String("news:"))
234  || segment.startsWith(QLatin1String("tel:"))
235  || segment.startsWith(QLatin1String("xmpp:"));
236  /* clang-format on */
237 }
238 
239 bool KTextToHTMLHelper::isEmptyUrl(const QString &url) const
240 {
241  /* clang-format off */
242  return url.isEmpty()
243  || url == QLatin1String("http://")
244  || url == QLatin1String("https://")
245  || url == QLatin1String("fish://")
246  || url == QLatin1String("ftp://")
247  || url == QLatin1String("ftps://")
248  || url == QLatin1String("sftp://")
249  || url == QLatin1String("smb://")
250  || url == QLatin1String("vnc://")
251  || url == QLatin1String("irc://")
252  || url == QLatin1String("ircs://")
253  || url == QLatin1String("mailto")
254  || url == QLatin1String("mailto:")
255  || url == QLatin1String("www")
256  || url == QLatin1String("ftp")
257  || url == QLatin1String("news:")
258  || url == QLatin1String("news://")
259  || url == QLatin1String("tel")
260  || url == QLatin1String("tel:")
261  || url == QLatin1String("xmpp:");
262  /* clang-format on */
263 }
264 
265 QString KTextToHTMLHelper::getUrl(bool *badurl)
266 {
267  QString url;
268  if (atUrl()) {
269  // NOTE: see http://tools.ietf.org/html/rfc3986#appendix-A and especially appendix-C
270  // Appendix-C mainly says, that when extracting URLs from plain text, line breaks shall
271  // be allowed and should be ignored when the URI is extracted.
272 
273  // This implementation follows this recommendation and
274  // allows the URL to be enclosed within different kind of brackets/quotes
275  // If an URL is enclosed, whitespace characters are allowed and removed, otherwise
276  // the URL ends with the first whitespace
277  // Also, if the URL is enclosed in brackets, the URL itself is not allowed
278  // to contain the closing bracket, as this would be detected as the end of the URL
279 
280  QChar beforeUrl;
281  QChar afterUrl;
282 
283  // detect if the url has been surrounded by brackets or quotes
284  if (mPos > 0) {
285  beforeUrl = mText.at(mPos - 1);
286 
287  /*if ( beforeUrl == '(' ) {
288  afterUrl = ')';
289  } else */
290  if (beforeUrl == QLatin1Char('[')) {
291  afterUrl = QLatin1Char(']');
292  } else if (beforeUrl == QLatin1Char('<')) {
293  afterUrl = QLatin1Char('>');
294  } else if (beforeUrl == QLatin1Char('>')) { // for e.g. <link>http://.....</link>
295  afterUrl = QLatin1Char('<');
296  } else if (beforeUrl == QLatin1Char('"')) {
297  afterUrl = QLatin1Char('"');
298  }
299  }
300  url.reserve(mMaxUrlLen); // avoid allocs
301  int start = mPos;
302  bool previousCharIsSpace = false;
303  bool previousCharIsADoubleQuote = false;
304  bool previousIsAnAnchor = false;
305  /* clang-format off */
306  while (mPos < mText.length() //
307  && (mText.at(mPos).isPrint() || mText.at(mPos).isSpace())
308  && ((afterUrl.isNull() && !mText.at(mPos).isSpace())
309  || (!afterUrl.isNull() && mText.at(mPos) != afterUrl))) {
310  if (!previousCharIsSpace
311  && mText.at(mPos) == QLatin1Char('<')
312  && (mPos + 1) < mText.length()) { /* clang-format on */
313  // Fix Bug #346132: allow "http://www.foo.bar<http://foo.bar/>"
314  // < inside a URL is not allowed, however there is a test which
315  // checks that "http://some<Host>/path" should be allowed
316  // Therefore: check if what follows is another URL and if so, stop here
317  mPos++;
318  if (atUrl()) {
319  mPos--;
320  break;
321  }
322  mPos--;
323  }
324  if (!previousCharIsSpace && (mText.at(mPos) == QLatin1Char(' ')) && ((mPos + 1) < mText.length())) {
325  // Fix kmail bug: allow "http://www.foo.bar http://foo.bar/"
326  // Therefore: check if what follows is another URL and if so, stop here
327  mPos++;
328  if (atUrl()) {
329  mPos--;
330  break;
331  }
332  mPos--;
333  }
334  if (mText.at(mPos).isSpace()) {
335  previousCharIsSpace = true;
336  } else if (!previousIsAnAnchor && mText.at(mPos) == QLatin1Char('[')) {
337  break;
338  } else if (!previousIsAnAnchor && mText.at(mPos) == QLatin1Char(']')) {
339  break;
340  } else { // skip whitespace
341  if (previousCharIsSpace && mText.at(mPos) == QLatin1Char('<')) {
342  url.append(QLatin1Char(' '));
343  break;
344  }
345  previousCharIsSpace = false;
346  if (mText.at(mPos) == QLatin1Char('>') && previousCharIsADoubleQuote) {
347  // it's an invalid url
348  if (badurl) {
349  *badurl = true;
350  }
351  return QString();
352  }
353  if (mText.at(mPos) == QLatin1Char('"')) {
354  previousCharIsADoubleQuote = true;
355  } else {
356  previousCharIsADoubleQuote = false;
357  }
358  if (mText.at(mPos) == QLatin1Char('#')) {
359  previousIsAnAnchor = true;
360  }
361  url.append(mText.at(mPos));
362  if (url.length() > mMaxUrlLen) {
363  break;
364  }
365  }
366 
367  ++mPos;
368  }
369 
370  if (isEmptyUrl(url) || (url.length() > mMaxUrlLen)) {
371  mPos = start;
372  url.clear();
373  return url;
374  } else {
375  --mPos;
376  }
377  }
378 
379  // HACK: This is actually against the RFC. However, most people don't properly escape the URL in
380  // their text with "" or <>. That leads to people writing an url, followed immediately by
381  // a dot to finish the sentence. That would lead the parser to include the dot in the url,
382  // even though that is not wanted. So work around that here.
383  // Most real-life URLs hopefully don't end with dots or commas.
384  const QString wordBoundaries = QStringLiteral(".,:!?)>");
385  if (url.length() > 1) {
386  do {
387  if (wordBoundaries.contains(url.at(url.length() - 1))) {
388  url.chop(1);
389  --mPos;
390  } else {
391  break;
392  }
393  } while (url.length() > 1);
394  }
395  return url;
396 }
397 
398 QString KTextToHTMLHelper::highlightedText()
399 {
400  // formating symbols must be prepended with a whitespace
401  if ((mPos > 0) && !mText.at(mPos - 1).isSpace()) {
402  return QString();
403  }
404 
405  const QChar ch = mText.at(mPos);
406  if (ch != QLatin1Char('/') && ch != QLatin1Char('*') && ch != QLatin1Char('_') && ch != QLatin1Char('-')) {
407  return QString();
408  }
409 
410  QRegularExpression re(QStringLiteral("\\%1([^\\s|^\\%1].*[^\\s|^\\%1])\\%1").arg(ch));
411  re.setPatternOptions(QRegularExpression::InvertedGreedinessOption);
413 
414  if (match.hasMatch()) {
415  if (match.capturedStart() == mPos) {
416  int length = match.capturedLength();
417  // there must be a whitespace after the closing formating symbol
418  if (mPos + length < mText.length() && !mText.at(mPos + length).isSpace()) {
419  return QString();
420  }
421  mPos += length - 1;
422  switch (ch.toLatin1()) {
423  case '*':
424  return QLatin1String("<b>*") + match.capturedView(1) + QLatin1String("*</b>");
425  case '_':
426  return QLatin1String("<u>_") + match.capturedView(1) + QLatin1String("_</u>");
427  case '/':
428  return QLatin1String("<i>/") + match.capturedView(1) + QLatin1String("/</i>");
429  case '-':
430  return QLatin1String("<s>-") + match.capturedView(1) + QLatin1String("-</s>");
431  }
432  }
433  }
434  return QString();
435 }
436 
437 QString KTextToHTML::convertToHtml(const QString &plainText, const KTextToHTML::Options &flags, int maxUrlLen, int maxAddressLen)
438 {
439  KTextToHTMLHelper helper(plainText, 0, maxUrlLen, maxAddressLen);
440 
441  QString str;
442  QString result(static_cast<QChar *>(nullptr), helper.mText.length() * 2);
443  QChar ch;
444  int x;
445  bool startOfLine = true;
446 
447  for (helper.mPos = 0, x = 0; helper.mPos < helper.mText.length(); ++helper.mPos, ++x) {
448  ch = helper.mText.at(helper.mPos);
449  if (flags & PreserveSpaces) {
450  if (ch == QLatin1Char(' ')) {
451  if (helper.mPos + 1 < helper.mText.length()) {
452  if (helper.mText.at(helper.mPos + 1) != QLatin1Char(' ')) {
453  // A single space, make it breaking if not at the start or end of the line
454  const bool endOfLine = helper.mText.at(helper.mPos + 1) == QLatin1Char('\n');
455  if (!startOfLine && !endOfLine) {
456  result += QLatin1Char(' ');
457  } else {
458  result += QLatin1String("&nbsp;");
459  }
460  } else {
461  // Whitespace of more than one space, make it all non-breaking
462  while (helper.mPos < helper.mText.length() && helper.mText.at(helper.mPos) == QLatin1Char(' ')) {
463  result += QLatin1String("&nbsp;");
464  ++helper.mPos;
465  ++x;
466  }
467 
468  // We incremented once to often, undo that
469  --helper.mPos;
470  --x;
471  }
472  } else {
473  // Last space in the text, it is non-breaking
474  result += QLatin1String("&nbsp;");
475  }
476 
477  if (startOfLine) {
478  startOfLine = false;
479  }
480  continue;
481  } else if (ch == QLatin1Char('\t')) {
482  do {
483  result += QLatin1String("&nbsp;");
484  ++x;
485  } while ((x & 7) != 0);
486  --x;
487  startOfLine = false;
488  continue;
489  }
490  }
491  if (ch == QLatin1Char('\n')) {
492  result += QLatin1String("<br />\n"); // Keep the \n, so apps can figure out the quoting levels correctly.
493  startOfLine = true;
494  x = -1;
495  continue;
496  }
497 
498  startOfLine = false;
499  if (ch == QLatin1Char('&')) {
500  result += QLatin1String("&amp;");
501  } else if (ch == QLatin1Char('"')) {
502  result += QLatin1String("&quot;");
503  } else if (ch == QLatin1Char('<')) {
504  result += QLatin1String("&lt;");
505  } else if (ch == QLatin1Char('>')) {
506  result += QLatin1String("&gt;");
507  } else {
508  const int start = helper.mPos;
509  if (!(flags & IgnoreUrls)) {
510  bool badUrl = false;
511  str = helper.getUrl(&badUrl);
512  if (badUrl) {
513  QString resultBadUrl;
514  for (const QChar chBadUrl : std::as_const(helper.mText)) {
515  if (chBadUrl == QLatin1Char('&')) {
516  resultBadUrl += QLatin1String("&amp;");
517  } else if (chBadUrl == QLatin1Char('"')) {
518  resultBadUrl += QLatin1String("&quot;");
519  } else if (chBadUrl == QLatin1Char('<')) {
520  resultBadUrl += QLatin1String("&lt;");
521  } else if (chBadUrl == QLatin1Char('>')) {
522  resultBadUrl += QLatin1String("&gt;");
523  } else {
524  resultBadUrl += chBadUrl;
525  }
526  }
527  return resultBadUrl;
528  }
529  if (!str.isEmpty()) {
530  QString hyperlink;
531  if (str.startsWith(QLatin1String("www."))) {
532  hyperlink = QLatin1String("http://") + str;
533  } else if (str.startsWith(QLatin1String("ftp."))) {
534  hyperlink = QLatin1String("ftp://") + str;
535  } else {
536  hyperlink = str;
537  }
538  result += QLatin1String("<a href=\"") + hyperlink + QLatin1String("\">") + str.toHtmlEscaped() + QLatin1String("</a>");
539  x += helper.mPos - start;
540  continue;
541  }
542  str = helper.getEmailAddress();
543  if (!str.isEmpty()) {
544  // len is the length of the local part
545  int len = str.indexOf(QLatin1Char('@'));
546  QString localPart = str.left(len);
547 
548  // remove the local part from the result (as '&'s have been expanded to
549  // &amp; we have to take care of the 4 additional characters per '&')
550  result.truncate(result.length() - len - (localPart.count(QLatin1Char('&')) * 4));
551  x -= len;
552 
553  result += QLatin1String("<a href=\"mailto:") + str + QLatin1String("\">") + str + QLatin1String("</a>");
554  x += str.length() - 1;
555  continue;
556  }
557  if (flags & ConvertPhoneNumbers) {
558  str = helper.getPhoneNumber();
559  if (!str.isEmpty()) {
560  result += QLatin1String("<a href=\"tel:") + normalizePhoneNumber(str) + QLatin1String("\">") + str + QLatin1String("</a>");
561  x += str.length() - 1;
562  continue;
563  }
564  }
565  }
566  if (flags & HighlightText) {
567  str = helper.highlightedText();
568  if (!str.isEmpty()) {
569  result += str;
570  x += helper.mPos - start;
571  continue;
572  }
573  }
574  result += ch;
575  }
576  }
577 
578  if (flags & ReplaceSmileys) {
579  const QStringList exclude = {QStringLiteral("(c)"), QStringLiteral("(C)"), QStringLiteral("&gt;:-("), QStringLiteral("&gt;:("), QStringLiteral("(B)"),
580  QStringLiteral("(b)"), QStringLiteral("(P)"), QStringLiteral("(p)"), QStringLiteral("(O)"), QStringLiteral("(o)"),
581  QStringLiteral("(D)"), QStringLiteral("(d)"), QStringLiteral("(E)"), QStringLiteral("(e)"), QStringLiteral("(K)"),
582  QStringLiteral("(k)"), QStringLiteral("(I)"), QStringLiteral("(i)"), QStringLiteral("(L)"), QStringLiteral("(l)"),
583  QStringLiteral("(8)"), QStringLiteral("(T)"), QStringLiteral("(t)"), QStringLiteral("(G)"), QStringLiteral("(g)"),
584  QStringLiteral("(F)"), QStringLiteral("(f)"), QStringLiteral("(H)"), QStringLiteral("8)"), QStringLiteral("(N)"),
585  QStringLiteral("(n)"), QStringLiteral("(Y)"), QStringLiteral("(y)"), QStringLiteral("(U)"), QStringLiteral("(u)"),
586  QStringLiteral("(W)"), QStringLiteral("(w)"), QStringLiteral("(6)")};
587 
588  result = helper.emoticonsInterface()->parseEmoticons(result, true, exclude);
589  }
590 
591  return result;
592 }
QChar at(qsizetype n) const const
void truncate(int position)
int size() const const
QString toHtmlEscaped() const const
T value() const const
void clear()
QStringView mid(qsizetype start) const const
Q_SCRIPTABLE Q_NOREPLY void start()
void chop(int n)
@ ReplaceSmileys
Replace text emoticons smileys by emoticons images.
Definition: ktexttohtml.h:37
QStringView::const_iterator cbegin() const const
void reserve(int size)
QStringView::const_iterator cend() const const
qsizetype size() const const
@ IgnoreUrls
Don't parse and replace any URLs.
Definition: ktexttohtml.h:42
QString toString() const const
KCOREADDONS_EXPORT QString convertToHtml(const QString &plainText, const KTextToHTML::Options &options, int maxUrlLen=4096, int maxAddressLen=255)
Converts plaintext into html.
void truncate(qsizetype length)
bool isEmpty() const const
int length() const const
void push_back(QChar ch)
@ ConvertPhoneNumbers
Replace phone numbers with tel: links.
Definition: ktexttohtml.h:54
PostalAddress address(const QVariant &location)
QStringView trimmed() const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
@ PreserveSpaces
Preserve white-space formatting of the text.
Definition: ktexttohtml.h:28
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
@ HighlightText
Interpret text highlighting markup, like bold, underline and /italic/, and wrap them in corresponding...
Definition: ktexttohtml.h:48
int count() const const
QString left(int n) const const
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
This is the main function which does scored fuzzy matching.
const QChar at(int position) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
char toLatin1() const const
bool startsWith(QStringView str, Qt::CaseSensitivity cs) const const
bool isNull() const const
const QList< QKeySequence > & end()
QString & append(QChar ch)
QVariant property(const char *name) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Fri Dec 8 2023 04:03:01 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.