KI18n

kuitmarkup.cpp
1 /* This file is part of the KDE libraries
2  SPDX-FileCopyrightText: 2007, 2013 Chusslove Illich <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include <QDir>
8 #include <QRegularExpression>
9 #include <QSet>
10 #include <QStack>
11 #include <QXmlStreamReader>
12 
13 #include <klazylocalizedstring.h>
14 #include <klocalizedstring.h>
15 #include <kuitmarkup.h>
16 #include <kuitmarkup_p.h>
17 
18 #include "ki18n_logging_kuit.h"
19 
20 #define QL1S(x) QLatin1String(x)
21 #define QSL(x) QStringLiteral(x)
22 #define QL1C(x) QLatin1Char(x)
23 
24 QString Kuit::escape(const QString &text)
25 {
26  int tlen = text.length();
27  QString ntext;
28  ntext.reserve(tlen);
29  for (int i = 0; i < tlen; ++i) {
30  QChar c = text[i];
31  if (c == QL1C('&')) {
32  ntext += QStringLiteral("&amp;");
33  } else if (c == QL1C('<')) {
34  ntext += QStringLiteral("&lt;");
35  } else if (c == QL1C('>')) {
36  ntext += QStringLiteral("&gt;");
37  } else if (c == QL1C('\'')) {
38  ntext += QStringLiteral("&apos;");
39  } else if (c == QL1C('"')) {
40  ntext += QStringLiteral("&quot;");
41  } else {
42  ntext += c;
43  }
44  }
45 
46  return ntext;
47 }
48 
49 // Truncates the string, for output of long messages.
50 // (But don't truncate too much otherwise it's impossible to determine
51 // which message is faulty if many messages have the same beginning).
52 static QString shorten(const QString &str)
53 {
54  const int maxlen = 80;
55  if (str.length() <= maxlen) {
56  return str;
57  } else {
58  return QStringView(str).left(maxlen) + QSL("...");
59  }
60 }
61 
62 static void parseUiMarker(const QString &context_, QString &roleName, QString &cueName, QString &formatName)
63 {
64  // UI marker is in the form @role:cue/format,
65  // and must start just after any leading whitespace in the context string.
66  // Note that names remain untouched if the marker is not found.
67  // Normalize the whole string, all lowercase.
68  QString context = context_.trimmed().toLower();
69  if (context.startsWith(QL1C('@'))) { // found UI marker
70  static const QRegularExpression wsRx(QStringLiteral("\\s"));
71  context = context.mid(1, wsRx.match(context).capturedStart(0) - 1);
72 
73  // Possible format.
74  int pfmt = context.indexOf(QL1C('/'));
75  if (pfmt >= 0) {
76  formatName = context.mid(pfmt + 1);
77  context.truncate(pfmt);
78  }
79 
80  // Possible subcue.
81  int pcue = context.indexOf(QL1C(':'));
82  if (pcue >= 0) {
83  cueName = context.mid(pcue + 1);
84  context.truncate(pcue);
85  }
86 
87  // Role.
88  roleName = context;
89  }
90 }
91 
92 // Custom entity resolver for QXmlStreamReader.
93 class KuitEntityResolver : public QXmlStreamEntityResolver
94 {
95 public:
96  void setEntities(const QHash<QString, QString> &entities)
97  {
98  entityMap = entities;
99  }
100 
101  QString resolveUndeclaredEntity(const QString &name) override
102  {
103  QString value = entityMap.value(name);
104  // This will return empty string if the entity name is not known,
105  // which will make QXmlStreamReader signal unknown entity error.
106  return value;
107  }
108 
109 private:
110  QHash<QString, QString> entityMap;
111 };
112 
113 namespace Kuit
114 {
115 enum Role { // UI marker roles
116  UndefinedRole,
117  ActionRole,
118  TitleRole,
119  OptionRole,
120  LabelRole,
121  ItemRole,
122  InfoRole,
123 };
124 
125 enum Cue { // UI marker subcues
126  UndefinedCue,
127  ButtonCue,
128  InmenuCue,
129  IntoolbarCue,
130  WindowCue,
131  MenuCue,
132  TabCue,
133  GroupCue,
134  ColumnCue,
135  RowCue,
136  SliderCue,
137  SpinboxCue,
138  ListboxCue,
139  TextboxCue,
140  ChooserCue,
141  CheckCue,
142  RadioCue,
143  InlistboxCue,
144  IntableCue,
145  InrangeCue,
146  IntextCue,
147  ValuesuffixCue,
148  TooltipCue,
149  WhatsthisCue,
150  PlaceholderCue,
151  StatusCue,
152  ProgressCue,
153  TipofthedayCue, // deprecated in favor of UsagetipCue
154  UsagetipCue,
155  CreditCue,
156  ShellCue,
157 };
158 }
159 
160 class KuitStaticData
161 {
162 public:
163  QHash<QString, QString> xmlEntities;
164  QHash<QString, QString> xmlEntitiesInverse;
165  KuitEntityResolver xmlEntityResolver;
166 
167  QHash<QString, Kuit::Role> rolesByName;
168  QHash<QString, Kuit::Cue> cuesByName;
171  QHash<Kuit::Role, QSet<Kuit::Cue>> knownRoleCues;
172 
176 
177  QHash<QByteArray, KuitSetup *> domainSetups;
178 
179  KuitStaticData();
180  ~KuitStaticData();
181 
182  KuitStaticData(const KuitStaticData &) = delete;
183  KuitStaticData &operator=(const KuitStaticData &) = delete;
184 
185  void setXmlEntityData();
186 
187  void setUiMarkerData();
188 
189  void setKeyName(const KLazyLocalizedString &keyName);
190  void setTextTransformData();
191  QString toKeyCombo(const QStringList &languages, const QString &shstr, Kuit::VisualFormat format);
192  QString toInterfacePath(const QStringList &languages, const QString &inpstr, Kuit::VisualFormat format);
193 };
194 
195 KuitStaticData::KuitStaticData()
196 {
197  setXmlEntityData();
198  setUiMarkerData();
199  setTextTransformData();
200 }
201 
202 KuitStaticData::~KuitStaticData()
203 {
204  qDeleteAll(domainSetups);
205 }
206 
207 void KuitStaticData::setXmlEntityData()
208 {
209  QString LT = QStringLiteral("lt");
210  QString GT = QStringLiteral("gt");
211  QString AMP = QStringLiteral("amp");
212  QString APOS = QStringLiteral("apos");
213  QString QUOT = QStringLiteral("quot");
214 
215  // Default XML entities, direct and inverse mapping.
216  xmlEntities[LT] = QString(QL1C('<'));
217  xmlEntities[GT] = QString(QL1C('>'));
218  xmlEntities[AMP] = QString(QL1C('&'));
219  xmlEntities[APOS] = QString(QL1C('\''));
220  xmlEntities[QUOT] = QString(QL1C('"'));
221  xmlEntitiesInverse[QString(QL1C('<'))] = LT;
222  xmlEntitiesInverse[QString(QL1C('>'))] = GT;
223  xmlEntitiesInverse[QString(QL1C('&'))] = AMP;
224  xmlEntitiesInverse[QString(QL1C('\''))] = APOS;
225  xmlEntitiesInverse[QString(QL1C('"'))] = QUOT;
226 
227  // Custom XML entities.
228  xmlEntities[QStringLiteral("nbsp")] = QString(QChar(0xa0));
229 
230  xmlEntityResolver.setEntities(xmlEntities);
231 }
232 // clang-format off
233 void KuitStaticData::setUiMarkerData()
234 {
235  using namespace Kuit;
236 
237  // Role names and their available subcues.
238 #undef SET_ROLE
239 #define SET_ROLE(role, name, cues) do { \
240  rolesByName[name] = role; \
241  knownRoleCues[role] << cues; \
242  } while (0)
243  SET_ROLE(ActionRole, QStringLiteral("action"),
244  ButtonCue << InmenuCue << IntoolbarCue);
245  SET_ROLE(TitleRole, QStringLiteral("title"),
246  WindowCue << MenuCue << TabCue << GroupCue
247  << ColumnCue << RowCue);
248  SET_ROLE(LabelRole, QStringLiteral("label"),
249  SliderCue << SpinboxCue << ListboxCue << TextboxCue
250  << ChooserCue);
251  SET_ROLE(OptionRole, QStringLiteral("option"),
252  CheckCue << RadioCue);
253  SET_ROLE(ItemRole, QStringLiteral("item"),
254  InmenuCue << InlistboxCue << IntableCue << InrangeCue
255  << IntextCue << ValuesuffixCue);
256  SET_ROLE(InfoRole, QStringLiteral("info"),
257  TooltipCue << WhatsthisCue << PlaceholderCue << StatusCue << ProgressCue
258  << TipofthedayCue << UsagetipCue << CreditCue << ShellCue);
259 
260  // Cue names.
261 #undef SET_CUE
262 #define SET_CUE(cue, name) do { \
263  cuesByName[name] = cue; \
264  } while (0)
265  SET_CUE(ButtonCue, QStringLiteral("button"));
266  SET_CUE(InmenuCue, QStringLiteral("inmenu"));
267  SET_CUE(IntoolbarCue, QStringLiteral("intoolbar"));
268  SET_CUE(WindowCue, QStringLiteral("window"));
269  SET_CUE(MenuCue, QStringLiteral("menu"));
270  SET_CUE(TabCue, QStringLiteral("tab"));
271  SET_CUE(GroupCue, QStringLiteral("group"));
272  SET_CUE(ColumnCue, QStringLiteral("column"));
273  SET_CUE(RowCue, QStringLiteral("row"));
274  SET_CUE(SliderCue, QStringLiteral("slider"));
275  SET_CUE(SpinboxCue, QStringLiteral("spinbox"));
276  SET_CUE(ListboxCue, QStringLiteral("listbox"));
277  SET_CUE(TextboxCue, QStringLiteral("textbox"));
278  SET_CUE(ChooserCue, QStringLiteral("chooser"));
279  SET_CUE(CheckCue, QStringLiteral("check"));
280  SET_CUE(RadioCue, QStringLiteral("radio"));
281  SET_CUE(InlistboxCue, QStringLiteral("inlistbox"));
282  SET_CUE(IntableCue, QStringLiteral("intable"));
283  SET_CUE(InrangeCue, QStringLiteral("inrange"));
284  SET_CUE(IntextCue, QStringLiteral("intext"));
285  SET_CUE(ValuesuffixCue, QStringLiteral("valuesuffix"));
286  SET_CUE(TooltipCue, QStringLiteral("tooltip"));
287  SET_CUE(WhatsthisCue, QStringLiteral("whatsthis"));
288  SET_CUE(PlaceholderCue, QStringLiteral("placeholder"));
289  SET_CUE(StatusCue, QStringLiteral("status"));
290  SET_CUE(ProgressCue, QStringLiteral("progress"));
291  SET_CUE(TipofthedayCue, QStringLiteral("tipoftheday"));
292  SET_CUE(UsagetipCue, QStringLiteral("usagetip"));
293  SET_CUE(CreditCue, QStringLiteral("credit"));
294  SET_CUE(ShellCue, QStringLiteral("shell"));
295 
296  // Format names.
297 #undef SET_FORMAT
298 #define SET_FORMAT(format, name) do { \
299  formatsByName[name] = format; \
300  namesByFormat[format] = name; \
301  } while (0)
302  SET_FORMAT(UndefinedFormat, QStringLiteral("undefined"));
303  SET_FORMAT(PlainText, QStringLiteral("plain"));
304  SET_FORMAT(RichText, QStringLiteral("rich"));
305  SET_FORMAT(TermText, QStringLiteral("term"));
306 }
307 
308 void KuitStaticData::setKeyName(const KLazyLocalizedString &keyName)
309 {
310  QString normname = QString::fromUtf8(keyName.untranslatedText()).trimmed().toLower();
311  keyNames[normname] = keyName;
312 }
313 
314 void KuitStaticData::setTextTransformData()
315 {
316  // i18n: Decide which string is used to delimit keys in a keyboard
317  // shortcut (e.g. + in Ctrl+Alt+Tab) in plain text.
318  comboKeyDelim[Kuit::PlainText] = ki18nc("shortcut-key-delimiter/plain", "+");
319  comboKeyDelim[Kuit::TermText] = comboKeyDelim[Kuit::PlainText];
320  // i18n: Decide which string is used to delimit keys in a keyboard
321  // shortcut (e.g. + in Ctrl+Alt+Tab) in rich text.
322  comboKeyDelim[Kuit::RichText] = ki18nc("shortcut-key-delimiter/rich", "+");
323 
324  // i18n: Decide which string is used to delimit elements in a GUI path
325  // (e.g. -> in "Go to Settings->Advanced->Core tab.") in plain text.
326  guiPathDelim[Kuit::PlainText] = ki18nc("gui-path-delimiter/plain", "→");
327  guiPathDelim[Kuit::TermText] = guiPathDelim[Kuit::PlainText];
328  // i18n: Decide which string is used to delimit elements in a GUI path
329  // (e.g. -> in "Go to Settings->Advanced->Core tab.") in rich text.
330  guiPathDelim[Kuit::RichText] = ki18nc("gui-path-delimiter/rich", "→");
331  // NOTE: The '→' glyph seems to be available in all widespread fonts.
332 
333  // Collect keyboard key names.
334  setKeyName(kli18nc("keyboard-key-name", "Alt"));
335  setKeyName(kli18nc("keyboard-key-name", "AltGr"));
336  setKeyName(kli18nc("keyboard-key-name", "Backspace"));
337  setKeyName(kli18nc("keyboard-key-name", "CapsLock"));
338  setKeyName(kli18nc("keyboard-key-name", "Control"));
339  setKeyName(kli18nc("keyboard-key-name", "Ctrl"));
340  setKeyName(kli18nc("keyboard-key-name", "Del"));
341  setKeyName(kli18nc("keyboard-key-name", "Delete"));
342  setKeyName(kli18nc("keyboard-key-name", "Down"));
343  setKeyName(kli18nc("keyboard-key-name", "End"));
344  setKeyName(kli18nc("keyboard-key-name", "Enter"));
345  setKeyName(kli18nc("keyboard-key-name", "Esc"));
346  setKeyName(kli18nc("keyboard-key-name", "Escape"));
347  setKeyName(kli18nc("keyboard-key-name", "Home"));
348  setKeyName(kli18nc("keyboard-key-name", "Hyper"));
349  setKeyName(kli18nc("keyboard-key-name", "Ins"));
350  setKeyName(kli18nc("keyboard-key-name", "Insert"));
351  setKeyName(kli18nc("keyboard-key-name", "Left"));
352  setKeyName(kli18nc("keyboard-key-name", "Menu"));
353  setKeyName(kli18nc("keyboard-key-name", "Meta"));
354  setKeyName(kli18nc("keyboard-key-name", "NumLock"));
355  setKeyName(kli18nc("keyboard-key-name", "PageDown"));
356  setKeyName(kli18nc("keyboard-key-name", "PageUp"));
357  setKeyName(kli18nc("keyboard-key-name", "PgDown"));
358  setKeyName(kli18nc("keyboard-key-name", "PgUp"));
359  setKeyName(kli18nc("keyboard-key-name", "PauseBreak"));
360  setKeyName(kli18nc("keyboard-key-name", "PrintScreen"));
361  setKeyName(kli18nc("keyboard-key-name", "PrtScr"));
362  setKeyName(kli18nc("keyboard-key-name", "Return"));
363  setKeyName(kli18nc("keyboard-key-name", "Right"));
364  setKeyName(kli18nc("keyboard-key-name", "ScrollLock"));
365  setKeyName(kli18nc("keyboard-key-name", "Shift"));
366  setKeyName(kli18nc("keyboard-key-name", "Space"));
367  setKeyName(kli18nc("keyboard-key-name", "Super"));
368  setKeyName(kli18nc("keyboard-key-name", "SysReq"));
369  setKeyName(kli18nc("keyboard-key-name", "Tab"));
370  setKeyName(kli18nc("keyboard-key-name", "Up"));
371  setKeyName(kli18nc("keyboard-key-name", "Win"));
372  setKeyName(kli18nc("keyboard-key-name", "F1"));
373  setKeyName(kli18nc("keyboard-key-name", "F2"));
374  setKeyName(kli18nc("keyboard-key-name", "F3"));
375  setKeyName(kli18nc("keyboard-key-name", "F4"));
376  setKeyName(kli18nc("keyboard-key-name", "F5"));
377  setKeyName(kli18nc("keyboard-key-name", "F6"));
378  setKeyName(kli18nc("keyboard-key-name", "F7"));
379  setKeyName(kli18nc("keyboard-key-name", "F8"));
380  setKeyName(kli18nc("keyboard-key-name", "F9"));
381  setKeyName(kli18nc("keyboard-key-name", "F10"));
382  setKeyName(kli18nc("keyboard-key-name", "F11"));
383  setKeyName(kli18nc("keyboard-key-name", "F12"));
384  // TODO: Add rest of the key names?
385 }
386 // clang-format on
387 
388 QString KuitStaticData::toKeyCombo(const QStringList &languages, const QString &shstr, Kuit::VisualFormat format)
389 {
390  // Take '+' or '-' as input shortcut delimiter,
391  // whichever is first encountered.
392  static const QRegularExpression delimRx(QStringLiteral("[+-]"));
393 
394  const QRegularExpressionMatch match = delimRx.match(shstr);
395  QStringList keys;
396  if (match.hasMatch()) { // delimiter found, multi-key shortcut
397  const QString oldDelim = match.captured(0);
398  keys = shstr.split(oldDelim, Qt::SkipEmptyParts);
399  } else { // single-key shortcut, no delimiter found
400  keys.append(shstr);
401  }
402 
403  for (QString &key : keys) {
404  // Normalize key
405  key = key.trimmed();
406  auto nameIt = keyNames.constFind(key.toLower());
407  if (nameIt != keyNames.constEnd()) {
408  key = nameIt->toString(languages);
409  }
410  }
411  const QString delim = comboKeyDelim.value(format).toString(languages);
412  return keys.join(delim);
413 }
414 
415 QString KuitStaticData::toInterfacePath(const QStringList &languages, const QString &inpstr, Kuit::VisualFormat format)
416 {
417  // Take '/', '|' or "->" as input path delimiter,
418  // whichever is first encountered.
419  static const QRegularExpression delimRx(QStringLiteral("\\||->"));
420  const QRegularExpressionMatch match = delimRx.match(inpstr);
421  if (match.hasMatch()) { // multi-element path
422  const QString oldDelim = match.captured(0);
423  QStringList guiels = inpstr.split(oldDelim, Qt::SkipEmptyParts);
424  const QString delim = guiPathDelim.value(format).toString(languages);
425  return guiels.join(delim);
426  }
427 
428  // single-element path, no delimiter found
429  return inpstr;
430 }
431 
432 Q_GLOBAL_STATIC(KuitStaticData, staticData)
433 
434 static QString attributeSetKey(const QStringList &attribNames_)
435 {
436  QStringList attribNames = attribNames_;
437  std::sort(attribNames.begin(), attribNames.end());
438  QString key = QL1C('[') + attribNames.join(QL1C(' ')) + QL1C(']');
439  return key;
440 }
441 
442 class KuitTag
443 {
444 public:
445  QString name;
447  QSet<QString> knownAttribs;
451  int leadingNewlines;
452 
453  KuitTag(const QString &_name, Kuit::TagClass _type)
454  : name(_name)
455  , type(_type)
456  {
457  }
458  KuitTag() = default;
459 
460  QString format(const QStringList &languages,
461  const QHash<QString, QString> &attributes,
462  const QString &text,
463  const QStringList &tagPath,
464  Kuit::VisualFormat format) const;
465 };
466 
467 QString KuitTag::format(const QStringList &languages,
468  const QHash<QString, QString> &attributes,
469  const QString &text,
470  const QStringList &tagPath,
471  Kuit::VisualFormat format) const
472 {
473  KuitStaticData *s = staticData();
474  QString formattedText = text;
475  QString attribKey = attributeSetKey(attributes.keys());
476  const QHash<Kuit::VisualFormat, KLocalizedString> pattern = patterns.value(attribKey);
477  auto patternIt = pattern.constFind(format);
478  if (patternIt != pattern.constEnd()) {
479  QString modText;
480  Kuit::TagFormatter formatter = formatters.value(attribKey).value(format);
481  if (formatter != nullptr) {
482  modText = formatter(languages, name, attributes, text, tagPath, format);
483  } else {
484  modText = text;
485  }
486  KLocalizedString aggText = *patternIt;
487  // line below is first-aid fix.for e.g. <emphasis strong='true'>.
488  // TODO: proper handling of boolean attributes still needed
489  aggText = aggText.relaxSubs();
490  if (!aggText.isEmpty()) {
491  aggText = aggText.subs(modText);
492  const QStringList attributeOrder = attributeOrders.value(attribKey).value(format);
493  for (const QString &attribName : attributeOrder) {
494  aggText = aggText.subs(attributes.value(attribName));
495  }
496  formattedText = aggText.ignoreMarkup().toString(languages);
497  } else {
498  formattedText = modText;
499  }
500  } else if (patterns.contains(attribKey)) {
501  qCWarning(KI18N_KUIT)
502  << QStringLiteral("Undefined visual format for tag <%1> and attribute combination %2: %3.").arg(name, attribKey, s->namesByFormat.value(format));
503  } else {
504  qCWarning(KI18N_KUIT) << QStringLiteral("Undefined attribute combination for tag <%1>: %2.").arg(name, attribKey);
505  }
506  return formattedText;
507 }
508 
510 {
511  KuitStaticData *s = staticData();
512  KuitSetup *setup = s->domainSetups.value(domain);
513  if (!setup) {
514  setup = new KuitSetup(domain);
515  s->domainSetups.insert(domain, setup);
516  }
517  return *setup;
518 }
519 
520 KuitSetup &Kuit::setupForDomain(const char *domain)
521 {
522  return setupForDomain(QByteArray(domain));
523 }
524 
525 class KuitSetupPrivate
526 {
527 public:
528  void setTagPattern(const QString &tagName,
529  const QStringList &attribNames,
530  Kuit::VisualFormat format,
531  const KLocalizedString &pattern,
532  Kuit::TagFormatter formatter,
533  int leadingNewlines);
534 
535  void setTagClass(const QString &tagName, Kuit::TagClass aClass);
536 
537  void setFormatForMarker(const QString &marker, Kuit::VisualFormat format);
538 
539  void setDefaultMarkup();
540  void setDefaultFormats();
541 
542  QByteArray domain;
543  QHash<QString, KuitTag> knownTags;
545 };
546 
547 void KuitSetupPrivate::setTagPattern(const QString &tagName,
548  const QStringList &attribNames_,
549  Kuit::VisualFormat format,
550  const KLocalizedString &pattern,
551  Kuit::TagFormatter formatter,
552  int leadingNewlines_)
553 {
554  auto tagIt = knownTags.find(tagName);
555  if (tagIt == knownTags.end()) {
556  tagIt = knownTags.insert(tagName, KuitTag(tagName, Kuit::PhraseTag));
557  }
558 
559  KuitTag &tag = *tagIt;
560 
561  QStringList attribNames = attribNames_;
562  attribNames.removeAll(QString());
563  for (const QString &attribName : std::as_const(attribNames)) {
564  tag.knownAttribs.insert(attribName);
565  }
566  QString attribKey = attributeSetKey(attribNames);
567  tag.attributeOrders[attribKey][format] = attribNames;
568  tag.patterns[attribKey][format] = pattern;
569  tag.formatters[attribKey][format] = formatter;
570  tag.leadingNewlines = leadingNewlines_;
571 }
572 
573 void KuitSetupPrivate::setTagClass(const QString &tagName, Kuit::TagClass aClass)
574 {
575  auto tagIt = knownTags.find(tagName);
576  if (tagIt == knownTags.end()) {
577  knownTags.insert(tagName, KuitTag(tagName, aClass));
578  } else {
579  tagIt->type = aClass;
580  }
581 }
582 
583 void KuitSetupPrivate::setFormatForMarker(const QString &marker, Kuit::VisualFormat format)
584 {
585  KuitStaticData *s = staticData();
586 
587  QString roleName;
588  QString cueName;
589  QString formatName;
590  parseUiMarker(marker, roleName, cueName, formatName);
591 
592  Kuit::Role role;
593  auto roleIt = s->rolesByName.constFind(roleName);
594  if (roleIt != s->rolesByName.constEnd()) {
595  role = *roleIt;
596  } else if (!roleName.isEmpty()) {
597  qCWarning(KI18N_KUIT) << QStringLiteral("Unknown role '@%1' in UI marker {%2}, visual format not set.").arg(roleName, marker);
598  return;
599  } else {
600  qCWarning(KI18N_KUIT) << QStringLiteral("Empty role in UI marker {%1}, visual format not set.").arg(marker);
601  return;
602  }
603 
604  Kuit::Cue cue;
605  auto cueIt = s->cuesByName.constFind(cueName);
606  if (cueIt != s->cuesByName.constEnd()) {
607  cue = *cueIt;
608  if (!s->knownRoleCues.value(role).contains(cue)) {
609  qCWarning(KI18N_KUIT)
610  << QStringLiteral("Subcue ':%1' does not belong to role '@%2' in UI marker {%3}, visual format not set.").arg(cueName, roleName, marker);
611  return;
612  }
613  } else if (!cueName.isEmpty()) {
614  qCWarning(KI18N_KUIT) << QStringLiteral("Unknown subcue ':%1' in UI marker {%2}, visual format not set.").arg(cueName, marker);
615  return;
616  } else {
617  cue = Kuit::UndefinedCue;
618  }
619 
620  formatsByRoleCue[role][cue] = format;
621 }
622 
623 #define TAG_FORMATTER_ARGS \
624  const QStringList &languages, const QString &tagName, const QHash<QString, QString> &attributes, const QString &text, const QStringList &tagPath, \
625  Kuit::VisualFormat format
626 
627 static QString tagFormatterFilename(TAG_FORMATTER_ARGS)
628 {
629  Q_UNUSED(languages);
630  Q_UNUSED(tagName);
631  Q_UNUSED(attributes);
632  Q_UNUSED(tagPath);
633 #ifdef Q_OS_WIN
634  // with rich text the path can include <foo>...</foo> which will be replaced by <foo>...<\foo> on Windows!
635  // the same problem also happens for tags such as <br/> -> <br>
636  if (format == Kuit::RichText) {
637  // replace all occurrences of "</" or "/>" to make sure toNativeSeparators() doesn't destroy XML markup
638  const auto KUIT_CLOSE_XML_REPLACEMENT = QStringLiteral("__kuit_close_xml_tag__");
639  const auto KUIT_NOTEXT_XML_REPLACEMENT = QStringLiteral("__kuit_notext_xml_tag__");
640 
641  QString result = text;
642  result.replace(QStringLiteral("</"), KUIT_CLOSE_XML_REPLACEMENT);
643  result.replace(QStringLiteral("/>"), KUIT_NOTEXT_XML_REPLACEMENT);
644  result = QDir::toNativeSeparators(result);
645  result.replace(KUIT_CLOSE_XML_REPLACEMENT, QStringLiteral("</"));
646  result.replace(KUIT_NOTEXT_XML_REPLACEMENT, QStringLiteral("/>"));
647  return result;
648  }
649 #else
650  Q_UNUSED(format);
651 #endif
652  return QDir::toNativeSeparators(text);
653 }
654 
655 static QString tagFormatterShortcut(TAG_FORMATTER_ARGS)
656 {
657  Q_UNUSED(tagName);
658  Q_UNUSED(attributes);
659  Q_UNUSED(tagPath);
660  KuitStaticData *s = staticData();
661  return s->toKeyCombo(languages, text, format);
662 }
663 
664 static QString tagFormatterInterface(TAG_FORMATTER_ARGS)
665 {
666  Q_UNUSED(tagName);
667  Q_UNUSED(attributes);
668  Q_UNUSED(tagPath);
669  KuitStaticData *s = staticData();
670  return s->toInterfacePath(languages, text, format);
671 }
672 
673 void KuitSetupPrivate::setDefaultMarkup()
674 {
675  using namespace Kuit;
676 
677  const QString INTERNAL_TOP_TAG_NAME = QStringLiteral("__kuit_internal_top__");
678  const QString TITLE = QStringLiteral("title");
679  const QString EMPHASIS = QStringLiteral("emphasis");
680  const QString COMMAND = QStringLiteral("command");
681  const QString WARNING = QStringLiteral("warning");
682  const QString LINK = QStringLiteral("link");
683  const QString NOTE = QStringLiteral("note");
684 
685  // clang-format off
686  // Macro to hide message from extraction.
687 #define HI18NC ki18nc
688 
689  // Macro to expedite setting the patterns.
690 #undef SET_PATTERN
691 #define SET_PATTERN(tagName, attribNames_, format, pattern, formatter, leadNl) \
692  do { \
693  QStringList attribNames; \
694  attribNames << attribNames_; \
695  setTagPattern(tagName, attribNames, format, pattern, formatter, leadNl); \
696  /* Make TermText pattern same as PlainText if not explicitly given. */ \
697  KuitTag &tag = knownTags[tagName]; \
698  QString attribKey = attributeSetKey(attribNames); \
699  if (format == PlainText && !tag.patterns[attribKey].contains(TermText)) { \
700  setTagPattern(tagName, attribNames, TermText, pattern, formatter, leadNl); \
701  } \
702  } while (0)
703 
704  // NOTE: The following "i18n:" comments are oddly placed in order that
705  // xgettext extracts them properly.
706 
707  // -------> Internal top tag
708  setTagClass(INTERNAL_TOP_TAG_NAME, StructTag);
709  SET_PATTERN(INTERNAL_TOP_TAG_NAME, QString(), PlainText,
710  HI18NC("tag-format-pattern <> plain",
711  // i18n: KUIT pattern, see the comment to the first of these entries above.
712  "%1"),
713  nullptr, 0);
714  SET_PATTERN(INTERNAL_TOP_TAG_NAME, QString(), RichText,
715  HI18NC("tag-format-pattern <> rich",
716  // i18n: KUIT pattern, see the comment to the first of these entries above.
717  "%1"),
718  nullptr, 0);
719 
720  // -------> Title
721  setTagClass(TITLE, StructTag);
722  SET_PATTERN(TITLE, QString(), PlainText,
723  ki18nc("tag-format-pattern <title> plain",
724  // i18n: The messages with context "tag-format-pattern <tag ...> format"
725  // are KUIT patterns for formatting the text found inside KUIT tags.
726  // The format is either "plain" or "rich", and tells if the pattern
727  // is used for plain text or rich text (which can use HTML tags).
728  // You may be in general satisfied with the patterns as they are in the
729  // original. Some things you may consider changing:
730  // - the proper quotes, those used in msgid are English-standard
731  // - the <i> and <b> tags, does your language script work well with them?
732  "== %1 =="),
733  nullptr, 2);
734  SET_PATTERN(TITLE, QString(), RichText,
735  ki18nc("tag-format-pattern <title> rich",
736  // i18n: KUIT pattern, see the comment to the first of these entries above.
737  "<h2>%1</h2>"),
738  nullptr, 2);
739 
740  // -------> Subtitle
741  setTagClass(QSL("subtitle"), StructTag);
742  SET_PATTERN(QSL("subtitle"), QString(), PlainText,
743  ki18nc("tag-format-pattern <subtitle> plain",
744  // i18n: KUIT pattern, see the comment to the first of these entries above.
745  "~ %1 ~"),
746  nullptr, 2);
747  SET_PATTERN(QSL("subtitle"), QString(), RichText,
748  ki18nc("tag-format-pattern <subtitle> rich",
749  // i18n: KUIT pattern, see the comment to the first of these entries above.
750  "<h3>%1</h3>"),
751  nullptr, 2);
752 
753  // -------> Para
754  setTagClass(QSL("para"), StructTag);
755  SET_PATTERN(QSL("para"), QString(), PlainText,
756  ki18nc("tag-format-pattern <para> plain",
757  // i18n: KUIT pattern, see the comment to the first of these entries above.
758  "%1"),
759  nullptr, 2);
760  SET_PATTERN(QSL("para"), QString(), RichText,
761  ki18nc("tag-format-pattern <para> rich",
762  // i18n: KUIT pattern, see the comment to the first of these entries above.
763  "<p>%1</p>"),
764  nullptr, 2);
765 
766  // -------> List
767  setTagClass(QSL("list"), StructTag);
768  SET_PATTERN(QSL("list"), QString(), PlainText,
769  ki18nc("tag-format-pattern <list> plain",
770  // i18n: KUIT pattern, see the comment to the first of these entries above.
771  "%1"),
772  nullptr, 1);
773  SET_PATTERN(QSL("list"), QString(), RichText,
774  ki18nc("tag-format-pattern <list> rich",
775  // i18n: KUIT pattern, see the comment to the first of these entries above.
776  "<ul>%1</ul>"),
777  nullptr, 1);
778 
779  // -------> Item
780  setTagClass(QSL("item"), StructTag);
781  SET_PATTERN(QSL("item"), QString(), PlainText,
782  ki18nc("tag-format-pattern <item> plain",
783  // i18n: KUIT pattern, see the comment to the first of these entries above.
784  " * %1"),
785  nullptr, 1);
786  SET_PATTERN(QSL("item"), QString(), RichText,
787  ki18nc("tag-format-pattern <item> rich",
788  // i18n: KUIT pattern, see the comment to the first of these entries above.
789  "<li>%1</li>"),
790  nullptr, 1);
791 
792  // -------> Note
793  SET_PATTERN(NOTE, QString(), PlainText,
794  ki18nc("tag-format-pattern <note> plain",
795  // i18n: KUIT pattern, see the comment to the first of these entries above.
796  "Note: %1"),
797  nullptr, 0);
798  SET_PATTERN(NOTE, QString(), RichText,
799  ki18nc("tag-format-pattern <note> rich",
800  // i18n: KUIT pattern, see the comment to the first of these entries above.
801  "<i>Note</i>: %1"),
802  nullptr, 0);
803  SET_PATTERN(NOTE, QSL("label"), PlainText,
804  ki18nc("tag-format-pattern <note label=> plain\n"
805  "%1 is the text, %2 is the note label",
806  // i18n: KUIT pattern, see the comment to the first of these entries above.
807  "%2: %1"),
808  nullptr, 0);
809  SET_PATTERN(NOTE, QSL("label"), RichText,
810  ki18nc("tag-format-pattern <note label=> rich\n"
811  "%1 is the text, %2 is the note label",
812  // i18n: KUIT pattern, see the comment to the first of these entries above.
813  "<i>%2</i>: %1"),
814  nullptr, 0);
815 
816  // -------> Warning
817  SET_PATTERN(WARNING, QString(), PlainText,
818  ki18nc("tag-format-pattern <warning> plain",
819  // i18n: KUIT pattern, see the comment to the first of these entries above.
820  "WARNING: %1"),
821  nullptr, 0);
822  SET_PATTERN(WARNING, QString(), RichText,
823  ki18nc("tag-format-pattern <warning> rich",
824  // i18n: KUIT pattern, see the comment to the first of these entries above.
825  "<b>Warning</b>: %1"),
826  nullptr, 0);
827  SET_PATTERN(WARNING, QSL("label"), PlainText,
828  ki18nc("tag-format-pattern <warning label=> plain\n"
829  "%1 is the text, %2 is the warning label",
830  // i18n: KUIT pattern, see the comment to the first of these entries above.
831  "%2: %1"),
832  nullptr, 0);
833  SET_PATTERN(WARNING, QSL("label"), RichText,
834  ki18nc("tag-format-pattern <warning label=> rich\n"
835  "%1 is the text, %2 is the warning label",
836  // i18n: KUIT pattern, see the comment to the first of these entries above.
837  "<b>%2</b>: %1"),
838  nullptr, 0);
839 
840  // -------> Link
841  SET_PATTERN(LINK, QString(), PlainText,
842  ki18nc("tag-format-pattern <link> plain",
843  // i18n: KUIT pattern, see the comment to the first of these entries above.
844  "%1"),
845  nullptr, 0);
846  SET_PATTERN(LINK, QString(), RichText,
847  ki18nc("tag-format-pattern <link> rich",
848  // i18n: KUIT pattern, see the comment to the first of these entries above.
849  "<a href=\"%1\">%1</a>"),
850  nullptr, 0);
851  SET_PATTERN(LINK, QSL("url"), PlainText,
852  ki18nc("tag-format-pattern <link url=> plain\n"
853  "%1 is the descriptive text, %2 is the URL",
854  // i18n: KUIT pattern, see the comment to the first of these entries above.
855  "%1 (%2)"),
856  nullptr, 0);
857  SET_PATTERN(LINK, QSL("url"), RichText,
858  ki18nc("tag-format-pattern <link url=> rich\n"
859  "%1 is the descriptive text, %2 is the URL",
860  // i18n: KUIT pattern, see the comment to the first of these entries above.
861  "<a href=\"%2\">%1</a>"),
862  nullptr, 0);
863 
864  // -------> Filename
865  SET_PATTERN(QSL("filename"), QString(), PlainText,
866  ki18nc("tag-format-pattern <filename> plain",
867  // i18n: KUIT pattern, see the comment to the first of these entries above.
868  "‘%1’"),
869  tagFormatterFilename, 0);
870  SET_PATTERN(QSL("filename"), QString(), RichText,
871  ki18nc("tag-format-pattern <filename> rich",
872  // i18n: KUIT pattern, see the comment to the first of these entries above.
873  "‘<tt>%1</tt>’"),
874  tagFormatterFilename, 0);
875 
876  // -------> Application
877  SET_PATTERN(QSL("application"), QString(), PlainText,
878  ki18nc("tag-format-pattern <application> plain",
879  // i18n: KUIT pattern, see the comment to the first of these entries above.
880  "%1"),
881  nullptr, 0);
882  SET_PATTERN(QSL("application"), QString(), RichText,
883  ki18nc("tag-format-pattern <application> rich",
884  // i18n: KUIT pattern, see the comment to the first of these entries above.
885  "%1"),
886  nullptr, 0);
887 
888  // -------> Command
889  SET_PATTERN(COMMAND, QString(), PlainText,
890  ki18nc("tag-format-pattern <command> plain",
891  // i18n: KUIT pattern, see the comment to the first of these entries above.
892  "%1"),
893  nullptr, 0);
894  SET_PATTERN(COMMAND, QString(), RichText,
895  ki18nc("tag-format-pattern <command> rich",
896  // i18n: KUIT pattern, see the comment to the first of these entries above.
897  "<tt>%1</tt>"),
898  nullptr, 0);
899  SET_PATTERN(COMMAND, QSL("section"), PlainText,
900  ki18nc("tag-format-pattern <command section=> plain\n"
901  "%1 is the command name, %2 is its man section",
902  // i18n: KUIT pattern, see the comment to the first of these entries above.
903  "%1(%2)"),
904  nullptr, 0);
905  SET_PATTERN(COMMAND, QSL("section"), RichText,
906  ki18nc("tag-format-pattern <command section=> rich\n"
907  "%1 is the command name, %2 is its man section",
908  // i18n: KUIT pattern, see the comment to the first of these entries above.
909  "<tt>%1(%2)</tt>"),
910  nullptr, 0);
911 
912  // -------> Resource
913  SET_PATTERN(QSL("resource"), QString(), PlainText,
914  ki18nc("tag-format-pattern <resource> plain",
915  // i18n: KUIT pattern, see the comment to the first of these entries above.
916  "“%1”"),
917  nullptr, 0);
918  SET_PATTERN(QSL("resource"), QString(), RichText,
919  ki18nc("tag-format-pattern <resource> rich",
920  // i18n: KUIT pattern, see the comment to the first of these entries above.
921  "“%1”"),
922  nullptr, 0);
923 
924  // -------> Icode
925  SET_PATTERN(QSL("icode"), QString(), PlainText,
926  ki18nc("tag-format-pattern <icode> plain",
927  // i18n: KUIT pattern, see the comment to the first of these entries above.
928  "“%1”"),
929  nullptr, 0);
930  SET_PATTERN(QSL("icode"), QString(), RichText,
931  ki18nc("tag-format-pattern <icode> rich",
932  // i18n: KUIT pattern, see the comment to the first of these entries above.
933  "<tt>%1</tt>"),
934  nullptr, 0);
935 
936  // -------> Bcode
937  SET_PATTERN(QSL("bcode"), QString(), PlainText,
938  ki18nc("tag-format-pattern <bcode> plain",
939  // i18n: KUIT pattern, see the comment to the first of these entries above.
940  "\n%1\n"),
941  nullptr, 2);
942  SET_PATTERN(QSL("bcode"), QString(), RichText,
943  ki18nc("tag-format-pattern <bcode> rich",
944  // i18n: KUIT pattern, see the comment to the first of these entries above.
945  "<pre>%1</pre>"),
946  nullptr, 2);
947 
948  // -------> Shortcut
949  SET_PATTERN(QSL("shortcut"), QString(), PlainText,
950  ki18nc("tag-format-pattern <shortcut> plain",
951  // i18n: KUIT pattern, see the comment to the first of these entries above.
952  "%1"),
953  tagFormatterShortcut, 0);
954  SET_PATTERN(QSL("shortcut"), QString(), RichText,
955  ki18nc("tag-format-pattern <shortcut> rich",
956  // i18n: KUIT pattern, see the comment to the first of these entries above.
957  "<b>%1</b>"),
958  tagFormatterShortcut, 0);
959 
960  // -------> Interface
961  SET_PATTERN(QSL("interface"), QString(), PlainText,
962  ki18nc("tag-format-pattern <interface> plain",
963  // i18n: KUIT pattern, see the comment to the first of these entries above.
964  "|%1|"),
965  tagFormatterInterface, 0);
966  SET_PATTERN(QSL("interface"), QString(), RichText,
967  ki18nc("tag-format-pattern <interface> rich",
968  // i18n: KUIT pattern, see the comment to the first of these entries above.
969  "<i>%1</i>"),
970  tagFormatterInterface, 0);
971 
972  // -------> Emphasis
973  SET_PATTERN(EMPHASIS, QString(), PlainText,
974  ki18nc("tag-format-pattern <emphasis> plain",
975  // i18n: KUIT pattern, see the comment to the first of these entries above.
976  "*%1*"),
977  nullptr, 0);
978  SET_PATTERN(EMPHASIS, QString(), RichText,
979  ki18nc("tag-format-pattern <emphasis> rich",
980  // i18n: KUIT pattern, see the comment to the first of these entries above.
981  "<i>%1</i>"),
982  nullptr, 0);
983  SET_PATTERN(EMPHASIS, QSL("strong"), PlainText,
984  ki18nc("tag-format-pattern <emphasis-strong> plain",
985  // i18n: KUIT pattern, see the comment to the first of these entries above.
986  "**%1**"),
987  nullptr, 0);
988  SET_PATTERN(EMPHASIS, QSL("strong"), RichText,
989  ki18nc("tag-format-pattern <emphasis-strong> rich",
990  // i18n: KUIT pattern, see the comment to the first of these entries above.
991  "<b>%1</b>"),
992  nullptr, 0);
993 
994  // -------> Placeholder
995  SET_PATTERN(QSL("placeholder"), QString(), PlainText,
996  ki18nc("tag-format-pattern <placeholder> plain",
997  // i18n: KUIT pattern, see the comment to the first of these entries above.
998  "&lt;%1&gt;"),
999  nullptr, 0);
1000  SET_PATTERN(QSL("placeholder"), QString(), RichText,
1001  ki18nc("tag-format-pattern <placeholder> rich",
1002  // i18n: KUIT pattern, see the comment to the first of these entries above.
1003  "&lt;<i>%1</i>&gt;"),
1004  nullptr, 0);
1005 
1006  // -------> Email
1007  SET_PATTERN(QSL("email"), QString(), PlainText,
1008  ki18nc("tag-format-pattern <email> plain",
1009  // i18n: KUIT pattern, see the comment to the first of these entries above.
1010  "&lt;%1&gt;"),
1011  nullptr, 0);
1012  SET_PATTERN(QSL("email"), QString(), RichText,
1013  ki18nc("tag-format-pattern <email> rich",
1014  // i18n: KUIT pattern, see the comment to the first of these entries above.
1015  "&lt;<a href=\"mailto:%1\">%1</a>&gt;"),
1016  nullptr, 0);
1017  SET_PATTERN(QSL("email"), QSL("address"), PlainText,
1018  ki18nc("tag-format-pattern <email address=> plain\n"
1019  "%1 is name, %2 is address",
1020  // i18n: KUIT pattern, see the comment to the first of these entries above.
1021  "%1 &lt;%2&gt;"),
1022  nullptr, 0);
1023  SET_PATTERN(QSL("email"), QSL("address"), RichText,
1024  ki18nc("tag-format-pattern <email address=> rich\n"
1025  "%1 is name, %2 is address",
1026  // i18n: KUIT pattern, see the comment to the first of these entries above.
1027  "<a href=\"mailto:%2\">%1</a>"),
1028  nullptr, 0);
1029 
1030  // -------> Envar
1031  SET_PATTERN(QSL("envar"), QString(), PlainText,
1032  ki18nc("tag-format-pattern <envar> plain",
1033  // i18n: KUIT pattern, see the comment to the first of these entries above.
1034  "$%1"),
1035  nullptr, 0);
1036  SET_PATTERN(QSL("envar"), QString(), RichText,
1037  ki18nc("tag-format-pattern <envar> rich",
1038  // i18n: KUIT pattern, see the comment to the first of these entries above.
1039  "<tt>$%1</tt>"),
1040  nullptr, 0);
1041 
1042  // -------> Message
1043  SET_PATTERN(QSL("message"), QString(), PlainText,
1044  ki18nc("tag-format-pattern <message> plain",
1045  // i18n: KUIT pattern, see the comment to the first of these entries above.
1046  "/%1/"),
1047  nullptr, 0);
1048  SET_PATTERN(QSL("message"), QString(), RichText,
1049  ki18nc("tag-format-pattern <message> rich",
1050  // i18n: KUIT pattern, see the comment to the first of these entries above.
1051  "<i>%1</i>"),
1052  nullptr, 0);
1053 
1054  // -------> Nl
1055  SET_PATTERN(QSL("nl"), QString(), PlainText,
1056  ki18nc("tag-format-pattern <nl> plain",
1057  // i18n: KUIT pattern, see the comment to the first of these entries above.
1058  "%1\n"),
1059  nullptr, 0);
1060  SET_PATTERN(QSL("nl"), QString(), RichText,
1061  ki18nc("tag-format-pattern <nl> rich",
1062  // i18n: KUIT pattern, see the comment to the first of these entries above.
1063  "%1<br/>"),
1064  nullptr, 0);
1065  // clang-format on
1066 }
1067 
1068 void KuitSetupPrivate::setDefaultFormats()
1069 {
1070  using namespace Kuit;
1071 
1072  // Setup formats by role.
1073  formatsByRoleCue[ActionRole][UndefinedCue] = PlainText;
1074  formatsByRoleCue[TitleRole][UndefinedCue] = PlainText;
1075  formatsByRoleCue[LabelRole][UndefinedCue] = PlainText;
1076  formatsByRoleCue[OptionRole][UndefinedCue] = PlainText;
1077  formatsByRoleCue[ItemRole][UndefinedCue] = PlainText;
1078  formatsByRoleCue[InfoRole][UndefinedCue] = RichText;
1079 
1080  // Setup override formats by subcue.
1081  formatsByRoleCue[InfoRole][StatusCue] = PlainText;
1082  formatsByRoleCue[InfoRole][ProgressCue] = PlainText;
1083  formatsByRoleCue[InfoRole][CreditCue] = PlainText;
1084  formatsByRoleCue[InfoRole][ShellCue] = TermText;
1085 }
1086 
1087 KuitSetup::KuitSetup(const QByteArray &domain)
1088  : d(new KuitSetupPrivate)
1089 {
1090  d->domain = domain;
1091  d->setDefaultMarkup();
1092  d->setDefaultFormats();
1093 }
1094 
1095 KuitSetup::~KuitSetup() = default;
1096 
1097 void KuitSetup::setTagPattern(const QString &tagName,
1098  const QStringList &attribNames,
1099  Kuit::VisualFormat format,
1100  const KLocalizedString &pattern,
1101  Kuit::TagFormatter formatter,
1102  int leadingNewlines)
1103 {
1104  d->setTagPattern(tagName, attribNames, format, pattern, formatter, leadingNewlines);
1105 }
1106 
1107 void KuitSetup::setTagClass(const QString &tagName, Kuit::TagClass aClass)
1108 {
1109  d->setTagClass(tagName, aClass);
1110 }
1111 
1113 {
1114  d->setFormatForMarker(marker, format);
1115 }
1116 
1117 class KuitFormatterPrivate
1118 {
1119 public:
1120  KuitFormatterPrivate(const QString &language);
1121 
1122  QString format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const;
1123 
1124  // Get metatranslation (formatting patterns, etc.)
1125  QString metaTr(const char *context, const char *text) const;
1126 
1127  // Set visual formatting patterns for text within tags.
1128  void setFormattingPatterns();
1129 
1130  // Set data used in transformation of text within tags.
1131  void setTextTransformData();
1132 
1133  // Determine visual format by parsing the UI marker in the context.
1134  static Kuit::VisualFormat formatFromUiMarker(const QString &context, const KuitSetup &setup);
1135 
1136  // Determine if text has block structure (multiple paragraphs, etc).
1137  static bool determineIsStructured(const QString &text, const KuitSetup &setup);
1138 
1139  // Format KUIT text into visual text.
1140  QString toVisualText(const QString &text, Kuit::VisualFormat format, const KuitSetup &setup) const;
1141 
1142  // Final touches to the formatted text.
1143  QString finalizeVisualText(const QString &ftext, Kuit::VisualFormat format) const;
1144 
1145  // In case of markup errors, try to make result not look too bad.
1146  QString salvageMarkup(const QString &text, Kuit::VisualFormat format, const KuitSetup &setup) const;
1147 
1148  // Data for XML parsing state.
1149  class OpenEl
1150  {
1151  public:
1152  enum Handling { Proper, Ignored, Dropout };
1153 
1154  QString name;
1155  QHash<QString, QString> attributes;
1156  QString attribStr;
1157  Handling handling;
1158  QString formattedText;
1159  QStringList tagPath;
1160  };
1161 
1162  // Gather data about current element for the parse state.
1163  KuitFormatterPrivate::OpenEl parseOpenEl(const QXmlStreamReader &xml, const OpenEl &enclosingOel, const QString &text, const KuitSetup &setup) const;
1164 
1165  // Format text of the element.
1166  QString formatSubText(const QString &ptext, const OpenEl &oel, Kuit::VisualFormat format, const KuitSetup &setup) const;
1167 
1168  // Count number of newlines at start and at end of text.
1169  static void countWrappingNewlines(const QString &ptext, int &numle, int &numtr);
1170 
1171 private:
1172  QString language;
1173  QStringList languageAsList;
1174 
1175  QHash<Kuit::VisualFormat, QString> comboKeyDelim;
1177 
1178  QHash<QString, QString> keyNames;
1179 };
1180 
1181 KuitFormatterPrivate::KuitFormatterPrivate(const QString &language_)
1182  : language(language_)
1183 {
1184 }
1185 
1186 QString KuitFormatterPrivate::format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const
1187 {
1188  const KuitSetup &setup = Kuit::setupForDomain(domain);
1189 
1190  // If format is undefined, determine it based on UI marker inside context.
1191  Kuit::VisualFormat resolvedFormat = format;
1192  if (resolvedFormat == Kuit::UndefinedFormat) {
1193  resolvedFormat = formatFromUiMarker(context, setup);
1194  }
1195 
1196  // Quick check: are there any tags at all?
1197  QString ftext;
1198  if (text.indexOf(QL1C('<')) < 0) {
1199  ftext = finalizeVisualText(text, resolvedFormat);
1200  } else {
1201  // Format the text.
1202  ftext = toVisualText(text, resolvedFormat, setup);
1203  if (ftext.isEmpty()) { // error while processing markup
1204  ftext = salvageMarkup(text, resolvedFormat, setup);
1205  }
1206  }
1207  return ftext;
1208 }
1209 
1210 Kuit::VisualFormat KuitFormatterPrivate::formatFromUiMarker(const QString &context, const KuitSetup &setup)
1211 {
1212  KuitStaticData *s = staticData();
1213 
1214  QString roleName;
1215  QString cueName;
1216  QString formatName;
1217  parseUiMarker(context, roleName, cueName, formatName);
1218 
1219  // Set role from name.
1220  Kuit::Role role = s->rolesByName.value(roleName, Kuit::UndefinedRole);
1221  if (role == Kuit::UndefinedRole) { // unknown role
1222  if (!roleName.isEmpty()) {
1223  qCWarning(KI18N_KUIT) << QStringLiteral("Unknown role '@%1' in UI marker in context {%2}.").arg(roleName, shorten(context));
1224  }
1225  }
1226 
1227  // Set subcue from name.
1228  Kuit::Cue cue;
1229  if (role != Kuit::UndefinedRole) {
1230  cue = s->cuesByName.value(cueName, Kuit::UndefinedCue);
1231  if (cue != Kuit::UndefinedCue) { // known subcue
1232  if (!s->knownRoleCues.value(role).contains(cue)) {
1233  cue = Kuit::UndefinedCue;
1234  qCWarning(KI18N_KUIT)
1235  << QStringLiteral("Subcue ':%1' does not belong to role '@%2' in UI marker in context {%3}.").arg(cueName, roleName, shorten(context));
1236  }
1237  } else { // unknown or not given subcue
1238  if (!cueName.isEmpty()) {
1239  qCWarning(KI18N_KUIT) << QStringLiteral("Unknown subcue ':%1' in UI marker in context {%2}.").arg(cueName, shorten(context));
1240  }
1241  }
1242  } else {
1243  // Bad role, silently ignore the cue.
1244  cue = Kuit::UndefinedCue;
1245  }
1246 
1247  // Set format from name, or by derivation from context/subcue.
1248  Kuit::VisualFormat format = s->formatsByName.value(formatName, Kuit::UndefinedFormat);
1249  if (format == Kuit::UndefinedFormat) { // unknown or not given format
1250  // Check first if there is a format defined for role/subcue
1251  // combination, then for role only, otherwise default to undefined.
1252  auto formatsByCueIt = setup.d->formatsByRoleCue.constFind(role);
1253  if (formatsByCueIt != setup.d->formatsByRoleCue.constEnd()) {
1254  const auto &formatsByCue = *formatsByCueIt;
1255  auto formatIt = formatsByCue.constFind(cue);
1256  if (formatIt != formatsByCue.constEnd()) {
1257  format = *formatIt;
1258  } else {
1259  format = formatsByCue.value(Kuit::UndefinedCue);
1260  }
1261  }
1262  if (!formatName.isEmpty()) {
1263  qCWarning(KI18N_KUIT) << QStringLiteral("Unknown format '/%1' in UI marker for message {%2}.").arg(formatName, shorten(context));
1264  }
1265  }
1266  if (format == Kuit::UndefinedFormat) {
1267  format = Kuit::PlainText;
1268  }
1269 
1270  return format;
1271 }
1272 
1273 bool KuitFormatterPrivate::determineIsStructured(const QString &text, const KuitSetup &setup)
1274 {
1275  // If the text opens with a structuring tag, then it is structured,
1276  // otherwise not. Leading whitespace is ignored for this purpose.
1277  static const QRegularExpression opensWithTagRx(QStringLiteral("^\\s*<\\s*(\\w+)[^>]*>"));
1278  bool isStructured = false;
1279  const QRegularExpressionMatch match = opensWithTagRx.match(text);
1280  if (match.hasMatch()) {
1281  const QString tagName = match.captured(1).toLower();
1282  auto tagIt = setup.d->knownTags.constFind(tagName);
1283  if (tagIt != setup.d->knownTags.constEnd()) {
1284  const KuitTag &tag = *tagIt;
1285  isStructured = (tag.type == Kuit::StructTag);
1286  }
1287  }
1288  return isStructured;
1289 }
1290 
1291 static const char s_entitySubRx[] = "[a-z]+|#[0-9]+|#x[0-9a-fA-F]+";
1292 
1293 QString KuitFormatterPrivate::toVisualText(const QString &text_, Kuit::VisualFormat format, const KuitSetup &setup) const
1294 {
1295  KuitStaticData *s = staticData();
1296 
1297  // Replace &-shortcut marker with "&amp;", not to confuse the parser;
1298  // but do not touch & which forms an XML entity as it is.
1299  QString original = text_;
1300  // Regex is (see s_entitySubRx var): ^([a-z]+|#[0-9]+|#x[0-9a-fA-F]+);
1301  static const QRegularExpression restRx(QLatin1String("^(") + QLatin1String(s_entitySubRx) + QLatin1String(");"));
1302 
1303  QString text;
1304  int p = original.indexOf(QL1C('&'));
1305  while (p >= 0) {
1306  text.append(QStringView(original).mid(0, p + 1));
1307  original.remove(0, p + 1);
1308  if (original.indexOf(restRx) != 0) { // not an entity
1309  text.append(QSL("amp;"));
1310  }
1311  p = original.indexOf(QL1C('&'));
1312  }
1313  text.append(original);
1314 
1315  // FIXME: Do this and then check proper use of structuring and phrase tags.
1316 #if 0
1317  // Determine whether this is block-structured text.
1318  bool isStructured = determineIsStructured(text, setup);
1319 #endif
1320 
1321  const QString INTERNAL_TOP_TAG_NAME = QStringLiteral("__kuit_internal_top__");
1322  // Add top tag, not to confuse the parser.
1323  text = QStringLiteral("<%2>%1</%2>").arg(text, INTERNAL_TOP_TAG_NAME);
1324 
1325  QStack<OpenEl> openEls;
1326  QXmlStreamReader xml(text);
1327  xml.setEntityResolver(&s->xmlEntityResolver);
1328  QStringView lastElementName;
1329 
1330  while (!xml.atEnd()) {
1331  xml.readNext();
1332 
1333  if (xml.isStartElement()) {
1334  lastElementName = xml.name();
1335 
1336  OpenEl oel;
1337 
1338  if (openEls.isEmpty()) {
1339  // Must be the root element.
1340  oel.name = INTERNAL_TOP_TAG_NAME;
1341  oel.handling = OpenEl::Proper;
1342  } else {
1343  // Find first proper enclosing element.
1344  OpenEl enclosingOel;
1345  for (int i = openEls.size() - 1; i >= 0; --i) {
1346  if (openEls[i].handling == OpenEl::Proper) {
1347  enclosingOel = openEls[i];
1348  break;
1349  }
1350  }
1351  // Collect data about this element.
1352  oel = parseOpenEl(xml, enclosingOel, text, setup);
1353  }
1354 
1355  // Record the new element on the parse stack.
1356  openEls.push(oel);
1357  } else if (xml.isEndElement()) {
1358  // Get closed element data.
1359  OpenEl oel = openEls.pop();
1360 
1361  // If this was closing of the top element, we're done.
1362  if (openEls.isEmpty()) {
1363  // Return with final touches applied.
1364  return finalizeVisualText(oel.formattedText, format);
1365  }
1366 
1367  // Append formatted text segment.
1368  QString ptext = openEls.top().formattedText; // preceding text
1369  openEls.top().formattedText += formatSubText(ptext, oel, format, setup);
1370  } else if (xml.isCharacters()) {
1371  // Stream reader will automatically resolve default XML entities,
1372  // which is not desired in this case, as the entities are to be
1373  // resolved in finalizeVisualText. Convert back into entities.
1374  const QString ctext = xml.text().toString();
1375  QString nctext;
1376  for (const QChar c : ctext) {
1377  auto nameIt = s->xmlEntitiesInverse.constFind(c);
1378  if (nameIt != s->xmlEntitiesInverse.constEnd()) {
1379  const QString &entName = *nameIt;
1380  nctext += QL1C('&') + entName + QL1C(';');
1381  } else {
1382  nctext += c;
1383  }
1384  }
1385  openEls.top().formattedText += nctext;
1386  }
1387  }
1388 
1389  if (xml.hasError()) {
1390  qCWarning(KI18N_KUIT) << QStringLiteral("Markup error in message {%1}: %2. Last tag parsed: %3. Complete message follows:\n%4")
1391  .arg(shorten(text), xml.errorString(), lastElementName.toString(), text);
1392  return QString();
1393  }
1394 
1395  // Cannot reach here.
1396  return text;
1397 }
1398 
1399 KuitFormatterPrivate::OpenEl
1400 KuitFormatterPrivate::parseOpenEl(const QXmlStreamReader &xml, const OpenEl &enclosingOel, const QString &text, const KuitSetup &setup) const
1401 {
1402  OpenEl oel;
1403  oel.name = xml.name().toString().toLower();
1404 
1405  // Collect attribute names and values, and format attribute string.
1406  QStringList attribNames;
1407  QStringList attribValues;
1408  const auto listAttributes = xml.attributes();
1409  attribNames.reserve(listAttributes.size());
1410  attribValues.reserve(listAttributes.size());
1411  for (const QXmlStreamAttribute &xatt : listAttributes) {
1412  attribNames += xatt.name().toString().toLower();
1413  attribValues += xatt.value().toString();
1414  QChar qc = attribValues.last().indexOf(QL1C('\'')) < 0 ? QL1C('\'') : QL1C('"');
1415  oel.attribStr += QL1C(' ') + attribNames.last() + QL1C('=') + qc + attribValues.last() + qc;
1416  }
1417 
1418  auto tagIt = setup.d->knownTags.constFind(oel.name);
1419  if (tagIt != setup.d->knownTags.constEnd()) { // known KUIT element
1420  const KuitTag &tag = *tagIt;
1421  const KuitTag &etag = setup.d->knownTags.value(enclosingOel.name);
1422 
1423  // If this element can be contained within enclosing element,
1424  // mark it proper, otherwise mark it for removal.
1425  if (tag.name.isEmpty() || tag.type == Kuit::PhraseTag || etag.type == Kuit::StructTag) {
1426  oel.handling = OpenEl::Proper;
1427  } else {
1428  oel.handling = OpenEl::Dropout;
1429  qCWarning(KI18N_KUIT)
1430  << QStringLiteral("Structuring tag ('%1') cannot be subtag of phrase tag ('%2') in message {%3}.").arg(tag.name, etag.name, shorten(text));
1431  }
1432 
1433  // Resolve attributes and compute attribute set key.
1434  QSet<QString> attset;
1435  for (int i = 0; i < attribNames.size(); ++i) {
1436  QString att = attribNames[i];
1437  if (tag.knownAttribs.contains(att)) {
1438  attset << att;
1439  oel.attributes[att] = attribValues[i];
1440  } else {
1441  qCWarning(KI18N_KUIT) << QStringLiteral("Attribute '%1' not defined for tag '%2' in message {%3}.").arg(att, tag.name, shorten(text));
1442  }
1443  }
1444 
1445  // Continue tag path.
1446  oel.tagPath = enclosingOel.tagPath;
1447  oel.tagPath.prepend(enclosingOel.name);
1448 
1449  } else { // unknown element, leave it in verbatim
1450  oel.handling = OpenEl::Ignored;
1451  qCWarning(KI18N_KUIT) << QStringLiteral("Tag '%1' is not defined in message {%2}.").arg(oel.name, shorten(text));
1452  }
1453 
1454  return oel;
1455 }
1456 
1457 QString KuitFormatterPrivate::formatSubText(const QString &ptext, const OpenEl &oel, Kuit::VisualFormat format, const KuitSetup &setup) const
1458 {
1459  if (oel.handling == OpenEl::Proper) {
1460  const KuitTag &tag = setup.d->knownTags.value(oel.name);
1461  QString ftext = tag.format(languageAsList, oel.attributes, oel.formattedText, oel.tagPath, format);
1462 
1463  // Handle leading newlines, if this is not start of the text
1464  // (ptext is the preceding text).
1465  if (!ptext.isEmpty() && tag.leadingNewlines > 0) {
1466  // Count number of present newlines.
1467  int pnumle;
1468  int pnumtr;
1469  int fnumle;
1470  int fnumtr;
1471  countWrappingNewlines(ptext, pnumle, pnumtr);
1472  countWrappingNewlines(ftext, fnumle, fnumtr);
1473  // Number of leading newlines already present.
1474  int numle = pnumtr + fnumle;
1475  // The required extra newlines.
1476  QString strle;
1477  if (numle < tag.leadingNewlines) {
1478  strle = QString(tag.leadingNewlines - numle, QL1C('\n'));
1479  }
1480  ftext = strle + ftext;
1481  }
1482 
1483  return ftext;
1484 
1485  } else if (oel.handling == OpenEl::Ignored) {
1486  return QL1C('<') + oel.name + oel.attribStr + QL1C('>') + oel.formattedText + QSL("</") + oel.name + QL1C('>');
1487 
1488  } else { // oel.handling == OpenEl::Dropout
1489  return oel.formattedText;
1490  }
1491 }
1492 
1493 void KuitFormatterPrivate::countWrappingNewlines(const QString &text, int &numle, int &numtr)
1494 {
1495  int len = text.length();
1496  // Number of newlines at start of text.
1497  numle = 0;
1498  while (numle < len && text[numle] == QL1C('\n')) {
1499  ++numle;
1500  }
1501  // Number of newlines at end of text.
1502  numtr = 0;
1503  while (numtr < len && text[len - numtr - 1] == QL1C('\n')) {
1504  ++numtr;
1505  }
1506 }
1507 
1508 QString KuitFormatterPrivate::finalizeVisualText(const QString &text_, Kuit::VisualFormat format) const
1509 {
1510  KuitStaticData *s = staticData();
1511 
1512  QString text = text_;
1513 
1514  // Resolve XML entities.
1515  if (format != Kuit::RichText) {
1516  // regex is (see s_entitySubRx var): (&([a-z]+|#[0-9]+|#x[0-9a-fA-F]+);)
1517  static const QRegularExpression entRx(QLatin1String("(&(") + QLatin1String(s_entitySubRx) + QLatin1String(");)"));
1519  QString plain;
1520  while ((match = entRx.match(text)).hasMatch()) {
1521  plain.append(QStringView(text).mid(0, match.capturedStart(0)));
1522  text.remove(0, match.capturedEnd(0));
1523  const QString ent = match.captured(2);
1524  if (ent.startsWith(QL1C('#'))) { // numeric character entity
1525  bool ok;
1526 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1527  QStringView entView(ent);
1528  const QChar c = ent.at(1) == QL1C('x') ? QChar(entView.mid(2).toInt(&ok, 16)) : QChar(entView.mid(1).toInt(&ok, 10));
1529 #else
1530  const QChar c = ent.at(1) == QL1C('x') ? QChar(ent.midRef(2).toInt(&ok, 16)) : QChar(ent.midRef(1).toInt(&ok, 10));
1531 #endif
1532  if (ok) {
1533  plain.append(c);
1534  } else { // unknown Unicode point, leave as is
1535  plain.append(match.capturedView(0));
1536  }
1537  } else if (s->xmlEntities.contains(ent)) { // known entity
1538  plain.append(s->xmlEntities[ent]);
1539  } else { // unknown entity, just leave as is
1540  plain.append(match.capturedView(0));
1541  }
1542  }
1543  plain.append(text);
1544  text = plain;
1545  }
1546 
1547  // Add top tag.
1548  if (format == Kuit::RichText) {
1549  text = QLatin1String("<html>") + text + QLatin1String("</html>");
1550  }
1551 
1552  return text;
1553 }
1554 
1555 QString KuitFormatterPrivate::salvageMarkup(const QString &text_, Kuit::VisualFormat format, const KuitSetup &setup) const
1556 {
1557  QString text = text_;
1558  QString ntext;
1559 
1560  // Resolve tags simple-mindedly.
1561 
1562  // - tags with content
1563  static const QRegularExpression wrapRx(QStringLiteral("(<\\s*(\\w+)\\b([^>]*)>)(.*)(<\\s*/\\s*\\2\\s*>)"), QRegularExpression::InvertedGreedinessOption);
1564  QRegularExpressionMatchIterator iter = wrapRx.globalMatch(text);
1566  int pos = 0;
1567  while (iter.hasNext()) {
1568  match = iter.next();
1569  ntext += QStringView(text).mid(pos, match.capturedStart(0) - pos);
1570  const QString tagname = match.captured(2).toLower();
1571  const QString content = salvageMarkup(match.captured(4), format, setup);
1572  auto tagIt = setup.d->knownTags.constFind(tagname);
1573  if (tagIt != setup.d->knownTags.constEnd()) {
1574  const KuitTag &tag = *tagIt;
1575  QHash<QString, QString> attributes;
1576  // TODO: Do not ignore attributes (in match.captured(3)).
1577  ntext += tag.format(languageAsList, attributes, content, QStringList(), format);
1578  } else {
1579  ntext += match.captured(1) + content + match.captured(5);
1580  }
1581  pos = match.capturedEnd(0);
1582  }
1583  // get the remaining part after the last match in "text"
1584  ntext += QStringView(text).mid(pos);
1585  text = ntext;
1586 
1587  // - tags without content
1588  static const QRegularExpression nowrRx(QStringLiteral("<\\s*(\\w+)\\b([^>]*)/\\s*>"), QRegularExpression::InvertedGreedinessOption);
1589  iter = nowrRx.globalMatch(text);
1590  pos = 0;
1591  ntext.clear();
1592  while (iter.hasNext()) {
1593  match = iter.next();
1594  ntext += QStringView(text).mid(pos, match.capturedStart(0) - pos);
1595  const QString tagname = match.captured(1).toLower();
1596  auto tagIt = setup.d->knownTags.constFind(tagname);
1597  if (tagIt != setup.d->knownTags.constEnd()) {
1598  const KuitTag &tag = *tagIt;
1599  ntext += tag.format(languageAsList, QHash<QString, QString>(), QString(), QStringList(), format);
1600  } else {
1601  ntext += match.captured(0);
1602  }
1603  pos = match.capturedEnd(0);
1604  }
1605  // get the remaining part after the last match in "text"
1606  ntext += QStringView(text).mid(pos);
1607  text = ntext;
1608 
1609  // Add top tag.
1610  if (format == Kuit::RichText) {
1611  text = QStringLiteral("<html>") + text + QStringLiteral("</html>");
1612  }
1613 
1614  return text;
1615 }
1616 
1617 KuitFormatter::KuitFormatter(const QString &language)
1618  : d(new KuitFormatterPrivate(language))
1619 {
1620 }
1621 
1622 KuitFormatter::~KuitFormatter()
1623 {
1624  delete d;
1625 }
1626 
1627 QString KuitFormatter::format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const
1628 {
1629  return d->format(domain, context, text, format);
1630 }
void append(const T &value)
const T value(const Key &key) const const
void truncate(int position)
int toInt(bool *ok, int base) const const
bool isEmpty() const const
@ RichText
Qt rich text (HTML subset).
Definition: kuitmarkup.h:43
Class for producing and handling localized messages.
QString fromUtf8(const char *str, int size)
KLocalizedString KI18N_EXPORT ki18nc(const char *context, const char *text)
Create non-finalized translated string with context.
virtual QString resolveUndeclaredEntity(const QString &name)
QString pattern(Mode mode=Reading)
int removeAll(const T &value)
Type type(const QSqlDatabase &db)
QStringRef midRef(int position, int n) const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
KLocalizedString relaxSubs() const
Relax matching between placeholders and arguments.
QList< Key > keys() const const
KI18N_EXPORT KuitSetup & setupForDomain(const char *domain)
Get hold of the KUIT setup object for a given domain.
Definition: kuitmarkup.cpp:520
QString trimmed() const const
void clear()
@ UndefinedFormat
Visual format not defined.
Definition: kuitmarkup.h:35
QStringView mid(qsizetype start) const const
QHash::iterator find(const Key &key)
QString(* TagFormatter)(const QStringList &languages, const QString &tagName, const QHash< QString, QString > &attributes, const QString &text, const QStringList &tagPath, Kuit::VisualFormat format)
Functions accepted by tag formatting functions.
Definition: kuitmarkup.h:75
void reserve(int size)
VisualFormat
Visual formats into which KUIT markup can be resolved.
Definition: kuitmarkup.h:27
Global constants and functions related to KUIT markup.
Definition: kuitmarkup.cpp:113
QString toString() const
Finalize the translation.
QStringView left(qsizetype length) const const
QRegularExpressionMatch next()
KLocalizedString ignoreMarkup() const
Do not resolve KUIT markup.
@ PhraseTag
Tags wrapping text inserted into running text.
Definition: kuitmarkup.h:57
KLocalizedString subs(int a, int fieldWidth=0, int base=10, QChar fillChar=QLatin1Char(' ')) const
Substitute an int argument into the message.
Q_GLOBAL_STATIC(Internal::StaticControl, s_instance) class ControlPrivate
QStringRef name() const const
void reserve(int alloc)
QString toString() const const
int size() const const
void setTagPattern(const QString &tagName, const QStringList &attribNames, Kuit::VisualFormat format, const KLocalizedString &pattern, Kuit::TagFormatter formatter=nullptr, int leadingNewlines=0)
Set the formatting string for a tag with attributes combination.
T & top()
void setFormatForMarker(const QString &marker, Kuit::VisualFormat format)
Set the default visual format for a given UI marker.
@ StructTag
Tags splitting text into paragraph-level blocks.
Definition: kuitmarkup.h:61
SkipEmptyParts
bool isEmpty() const const
QString toNativeSeparators(const QString &pathName)
int length() const const
QXmlStreamAttributes attributes() const const
@ TermText
Terminal escape sequences.
Definition: kuitmarkup.h:47
QString::const_iterator constEnd() const const
QString join(const QString &separator) const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString & replace(int position, int n, QChar after)
T & last()
QString & remove(int position, int n)
void setTagClass(const QString &tagName, Kuit::TagClass aClass)
Set the KUIT class of the tag.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString toLower() const const
void push(const T &t)
QString toString() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
constexpr const char * untranslatedText() const
Returns the raw untranslated text as passed to kli18n*.
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString name(StandardShortcut id)
const QChar at(int position) const const
QList::iterator begin()
int size() const const
~KuitSetup()
Destructor.
@ PlainText
Plain text.
Definition: kuitmarkup.h:39
QList::iterator end()
TagClass
Classification of KUIT tags.
Definition: kuitmarkup.h:53
QString mid(int position, int n) const const
T value(int i) const const
QString & append(QChar ch)
bool isEmpty() const
Check whether the message is empty.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 6 2023 04:07:07 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.