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

KDE's Doxygen guidelines are available online.