8#include <QRegularExpression>
11#include <QXmlStreamReader>
13#include <klazylocalizedstring.h>
16#include <kuitsetup_p.h>
18#include "ki18n_logging_kuit.h"
20#define QL1S(x) QLatin1String(x)
21#define QSL(x) QStringLiteral(x)
22#define QL1C(x) QLatin1Char(x)
29 for (
int i = 0; i < tlen; ++i) {
32 ntext += QStringLiteral(
"&");
33 }
else if (c == QL1C(
'<')) {
34 ntext += QStringLiteral(
"<");
35 }
else if (c == QL1C(
'>')) {
36 ntext += QStringLiteral(
">");
37 }
else if (c == QL1C(
'\'')) {
38 ntext += QStringLiteral(
"'");
39 }
else if (c == QL1C(
'"')) {
40 ntext += QStringLiteral(
""");
54 const int maxlen = 80;
55 if (str.
length() <= maxlen) {
71 context = context.
mid(1, wsRx.match(context).capturedStart(0) - 1);
74 int pfmt = context.
indexOf(QL1C(
'/'));
76 formatName = context.
mid(pfmt + 1);
81 int pcue = context.
indexOf(QL1C(
':'));
83 cueName = context.
mid(pcue + 1);
165 KuitEntityResolver xmlEntityResolver;
182 KuitStaticData(
const KuitStaticData &) =
delete;
183 KuitStaticData &operator=(
const KuitStaticData &) =
delete;
185 void setXmlEntityData();
187 void setUiMarkerData();
190 void setTextTransformData();
195KuitStaticData::KuitStaticData()
199 setTextTransformData();
202KuitStaticData::~KuitStaticData()
204 qDeleteAll(domainSetups);
207void KuitStaticData::setXmlEntityData()
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");
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;
228 xmlEntities[QStringLiteral(
"nbsp")] =
QString(
QChar(0xa0));
230 xmlEntityResolver.setEntities(xmlEntities);
233void KuitStaticData::setUiMarkerData()
235 using namespace Kuit;
239#define SET_ROLE(role, name, cues) do { \
240 rolesByName[name] = role; \
241 knownRoleCues[role] << cues; \
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
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);
262#define SET_CUE(cue, name) do { \
263 cuesByName[name] = cue; \
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"));
298#define SET_FORMAT(format, name) do { \
299 formatsByName[name] = format; \
300 namesByFormat[format] = name; \
302 SET_FORMAT(UndefinedFormat, QStringLiteral(
"undefined"));
303 SET_FORMAT(PlainText, QStringLiteral(
"plain"));
304 SET_FORMAT(RichText, QStringLiteral(
"rich"));
305 SET_FORMAT(TermText, QStringLiteral(
"term"));
311 keyNames[normname] = keyName;
314void KuitStaticData::setTextTransformData()
318 comboKeyDelim[
Kuit::PlainText] = ki18nc(
"shortcut-key-delimiter/plain",
"+");
322 comboKeyDelim[
Kuit::RichText] = ki18nc(
"shortcut-key-delimiter/rich",
"+");
326 guiPathDelim[
Kuit::PlainText] = ki18nc(
"gui-path-delimiter/plain",
"→");
330 guiPathDelim[
Kuit::RichText] = ki18nc(
"gui-path-delimiter/rich",
"→");
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"));
396 if (
match.hasMatch()) {
406 auto nameIt = keyNames.
constFind(key.toLower());
407 if (nameIt != keyNames.
constEnd()) {
408 key = nameIt->toString(languages);
411 const QString delim = comboKeyDelim.
value(format).toString(languages);
412 return keys.join(delim);
421 if (
match.hasMatch()) {
424 const QString delim = guiPathDelim.
value(format).toString(languages);
425 return guiels.
join(delim);
432Q_GLOBAL_STATIC(KuitStaticData, staticData)
437 std::sort(attribNames.
begin(), attribNames.
end());
438 QString key = QL1C(
'[') + attribNames.
join(QL1C(
' ')) + QL1C(
']');
473 KuitStaticData *s = staticData();
475 QString attribKey = attributeSetKey(attributes.
keys());
477 auto patternIt = pattern.
constFind(format);
478 if (patternIt != pattern.
constEnd()) {
481 if (formatter !=
nullptr) {
482 modText = formatter(languages, name, attributes, text, tagPath, format);
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));
498 formattedText = modText;
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));
504 qCWarning(KI18N_KUIT) << QStringLiteral(
"Undefined attribute combination for tag <%1>: %2.").arg(name, attribKey);
506 return formattedText;
511 KuitStaticData *s = staticData();
515 s->domainSetups.
insert(domain, setup);
520class KuitSetupPrivate
523 void setTagPattern(
const QString &tagName,
528 int leadingNewlines);
534 void setDefaultMarkup();
535 void setDefaultFormats();
542void KuitSetupPrivate::setTagPattern(
const QString &tagName,
547 int leadingNewlines_)
549 auto tagIt = knownTags.
find(tagName);
550 if (tagIt == knownTags.
end()) {
554 KuitTag &tag = *tagIt;
558 for (
const QString &attribName : std::as_const(attribNames)) {
559 tag.knownAttribs.
insert(attribName);
561 QString attribKey = attributeSetKey(attribNames);
562 tag.attributeOrders[attribKey][format] = attribNames;
563 tag.patterns[attribKey][format] = pattern;
564 tag.formatters[attribKey][format] = formatter;
565 tag.leadingNewlines = leadingNewlines_;
570 auto tagIt = knownTags.
find(tagName);
571 if (tagIt == knownTags.
end()) {
572 knownTags.
insert(tagName, KuitTag(tagName, aClass));
574 tagIt->type = aClass;
580 KuitStaticData *s = staticData();
585 parseUiMarker(marker, roleName, cueName, formatName);
588 auto roleIt = s->rolesByName.
constFind(roleName);
589 if (roleIt != s->rolesByName.
constEnd()) {
591 }
else if (!roleName.
isEmpty()) {
592 qCWarning(KI18N_KUIT) << QStringLiteral(
"Unknown role '@%1' in UI marker {%2}, visual format not set.").arg(roleName, marker);
595 qCWarning(KI18N_KUIT) << QStringLiteral(
"Empty role in UI marker {%1}, visual format not set.").arg(marker);
600 auto cueIt = s->cuesByName.
constFind(cueName);
601 if (cueIt != s->cuesByName.
constEnd()) {
603 if (!s->knownRoleCues.
value(role).contains(cue)) {
604 qCWarning(KI18N_KUIT)
605 << QStringLiteral(
"Subcue ':%1' does not belong to role '@%2' in UI marker {%3}, visual format not set.").arg(cueName, roleName, marker);
608 }
else if (!cueName.
isEmpty()) {
609 qCWarning(KI18N_KUIT) << QStringLiteral(
"Unknown subcue ':%1' in UI marker {%2}, visual format not set.").arg(cueName, marker);
612 cue = Kuit::UndefinedCue;
615 formatsByRoleCue[role][cue] = format;
618#define TAG_FORMATTER_ARGS \
619 const QStringList &languages, const QString &tagName, const QHash<QString, QString> &attributes, const QString &text, const QStringList &tagPath, \
620 Kuit::VisualFormat format
622static QString tagFormatterFilename(TAG_FORMATTER_ARGS)
626 Q_UNUSED(attributes);
633 const auto KUIT_CLOSE_XML_REPLACEMENT = QStringLiteral(
"__kuit_close_xml_tag__");
634 const auto KUIT_NOTEXT_XML_REPLACEMENT = QStringLiteral(
"__kuit_notext_xml_tag__");
637 result.
replace(QStringLiteral(
"</"), KUIT_CLOSE_XML_REPLACEMENT);
638 result.
replace(QStringLiteral(
"/>"), KUIT_NOTEXT_XML_REPLACEMENT);
640 result.
replace(KUIT_CLOSE_XML_REPLACEMENT, QStringLiteral(
"</"));
641 result.
replace(KUIT_NOTEXT_XML_REPLACEMENT, QStringLiteral(
"/>"));
650static QString tagFormatterShortcut(TAG_FORMATTER_ARGS)
653 Q_UNUSED(attributes);
655 KuitStaticData *s = staticData();
656 return s->toKeyCombo(languages, text, format);
659static QString tagFormatterInterface(TAG_FORMATTER_ARGS)
662 Q_UNUSED(attributes);
664 KuitStaticData *s = staticData();
665 return s->toInterfacePath(languages, text, format);
668void KuitSetupPrivate::setDefaultMarkup()
670 using namespace Kuit;
672 const QString INTERNAL_TOP_TAG_NAME = QStringLiteral(
"__kuit_internal_top__");
673 const QString TITLE = QStringLiteral(
"title");
674 const QString EMPHASIS = QStringLiteral(
"emphasis");
675 const QString COMMAND = QStringLiteral(
"command");
676 const QString WARNING = QStringLiteral(
"warning");
677 const QString LINK = QStringLiteral(
"link");
678 const QString NOTE = QStringLiteral(
"note");
686#define SET_PATTERN(tagName, attribNames_, format, pattern, formatter, leadNl) \
688 QStringList attribNames; \
689 attribNames << attribNames_; \
690 setTagPattern(tagName, attribNames, format, pattern, formatter, leadNl); \
692 KuitTag &tag = knownTags[tagName]; \
693 QString attribKey = attributeSetKey(attribNames); \
694 if (format == PlainText && !tag.patterns[attribKey].contains(TermText)) { \
695 setTagPattern(tagName, attribNames, TermText, pattern, formatter, leadNl); \
703 setTagClass(INTERNAL_TOP_TAG_NAME, StructTag);
704 SET_PATTERN(INTERNAL_TOP_TAG_NAME,
QString(), PlainText,
705 HI18NC(
"tag-format-pattern <> plain",
709 SET_PATTERN(INTERNAL_TOP_TAG_NAME,
QString(), RichText,
710 HI18NC(
"tag-format-pattern <> rich",
716 setTagClass(TITLE, StructTag);
717 SET_PATTERN(TITLE,
QString(), PlainText,
718 ki18nc(
"tag-format-pattern <title> plain",
729 SET_PATTERN(TITLE,
QString(), RichText,
730 ki18nc(
"tag-format-pattern <title> rich",
736 setTagClass(QSL(
"subtitle"), StructTag);
737 SET_PATTERN(QSL(
"subtitle"),
QString(), PlainText,
738 ki18nc(
"tag-format-pattern <subtitle> plain",
742 SET_PATTERN(QSL(
"subtitle"),
QString(), RichText,
743 ki18nc(
"tag-format-pattern <subtitle> rich",
749 setTagClass(QSL(
"para"), StructTag);
750 SET_PATTERN(QSL(
"para"),
QString(), PlainText,
751 ki18nc(
"tag-format-pattern <para> plain",
755 SET_PATTERN(QSL(
"para"),
QString(), RichText,
756 ki18nc(
"tag-format-pattern <para> rich",
762 setTagClass(QSL(
"list"), StructTag);
763 SET_PATTERN(QSL(
"list"),
QString(), PlainText,
764 ki18nc(
"tag-format-pattern <list> plain",
768 SET_PATTERN(QSL(
"list"),
QString(), RichText,
769 ki18nc(
"tag-format-pattern <list> rich",
775 setTagClass(QSL(
"item"), StructTag);
776 SET_PATTERN(QSL(
"item"),
QString(), PlainText,
777 ki18nc(
"tag-format-pattern <item> plain",
781 SET_PATTERN(QSL(
"item"),
QString(), RichText,
782 ki18nc(
"tag-format-pattern <item> rich",
788 SET_PATTERN(NOTE,
QString(), PlainText,
789 ki18nc(
"tag-format-pattern <note> plain",
793 SET_PATTERN(NOTE,
QString(), RichText,
794 ki18nc(
"tag-format-pattern <note> rich",
798 SET_PATTERN(NOTE, QSL(
"label"), PlainText,
799 ki18nc(
"tag-format-pattern <note label=> plain\n"
800 "%1 is the text, %2 is the note label",
804 SET_PATTERN(NOTE, QSL(
"label"), RichText,
805 ki18nc(
"tag-format-pattern <note label=> rich\n"
806 "%1 is the text, %2 is the note label",
812 SET_PATTERN(WARNING,
QString(), PlainText,
813 ki18nc(
"tag-format-pattern <warning> plain",
817 SET_PATTERN(WARNING,
QString(), RichText,
818 ki18nc(
"tag-format-pattern <warning> rich",
820 "<b>Warning</b>: %1"),
822 SET_PATTERN(WARNING, QSL(
"label"), PlainText,
823 ki18nc(
"tag-format-pattern <warning label=> plain\n"
824 "%1 is the text, %2 is the warning label",
828 SET_PATTERN(WARNING, QSL(
"label"), RichText,
829 ki18nc(
"tag-format-pattern <warning label=> rich\n"
830 "%1 is the text, %2 is the warning label",
836 SET_PATTERN(LINK,
QString(), PlainText,
837 ki18nc(
"tag-format-pattern <link> plain",
841 SET_PATTERN(LINK,
QString(), RichText,
842 ki18nc(
"tag-format-pattern <link> rich",
844 "<a href=\"%1\">%1</a>"),
846 SET_PATTERN(LINK, QSL(
"url"), PlainText,
847 ki18nc(
"tag-format-pattern <link url=> plain\n"
848 "%1 is the descriptive text, %2 is the URL",
852 SET_PATTERN(LINK, QSL(
"url"), RichText,
853 ki18nc(
"tag-format-pattern <link url=> rich\n"
854 "%1 is the descriptive text, %2 is the URL",
856 "<a href=\"%2\">%1</a>"),
860 SET_PATTERN(QSL(
"filename"),
QString(), PlainText,
861 ki18nc(
"tag-format-pattern <filename> plain",
864 tagFormatterFilename, 0);
865 SET_PATTERN(QSL(
"filename"),
QString(), RichText,
866 ki18nc(
"tag-format-pattern <filename> rich",
869 tagFormatterFilename, 0);
872 SET_PATTERN(QSL(
"application"),
QString(), PlainText,
873 ki18nc(
"tag-format-pattern <application> plain",
877 SET_PATTERN(QSL(
"application"),
QString(), RichText,
878 ki18nc(
"tag-format-pattern <application> rich",
884 SET_PATTERN(COMMAND,
QString(), PlainText,
885 ki18nc(
"tag-format-pattern <command> plain",
889 SET_PATTERN(COMMAND,
QString(), RichText,
890 ki18nc(
"tag-format-pattern <command> rich",
894 SET_PATTERN(COMMAND, QSL(
"section"), PlainText,
895 ki18nc(
"tag-format-pattern <command section=> plain\n"
896 "%1 is the command name, %2 is its man section",
900 SET_PATTERN(COMMAND, QSL(
"section"), RichText,
901 ki18nc(
"tag-format-pattern <command section=> rich\n"
902 "%1 is the command name, %2 is its man section",
908 SET_PATTERN(QSL(
"resource"),
QString(), PlainText,
909 ki18nc(
"tag-format-pattern <resource> plain",
913 SET_PATTERN(QSL(
"resource"),
QString(), RichText,
914 ki18nc(
"tag-format-pattern <resource> rich",
920 SET_PATTERN(QSL(
"icode"),
QString(), PlainText,
921 ki18nc(
"tag-format-pattern <icode> plain",
925 SET_PATTERN(QSL(
"icode"),
QString(), RichText,
926 ki18nc(
"tag-format-pattern <icode> rich",
932 SET_PATTERN(QSL(
"bcode"),
QString(), PlainText,
933 ki18nc(
"tag-format-pattern <bcode> plain",
937 SET_PATTERN(QSL(
"bcode"),
QString(), RichText,
938 ki18nc(
"tag-format-pattern <bcode> rich",
944 SET_PATTERN(QSL(
"shortcut"),
QString(), PlainText,
945 ki18nc(
"tag-format-pattern <shortcut> plain",
948 tagFormatterShortcut, 0);
949 SET_PATTERN(QSL(
"shortcut"),
QString(), RichText,
950 ki18nc(
"tag-format-pattern <shortcut> rich",
953 tagFormatterShortcut, 0);
956 SET_PATTERN(QSL(
"interface"),
QString(), PlainText,
957 ki18nc(
"tag-format-pattern <interface> plain",
960 tagFormatterInterface, 0);
961 SET_PATTERN(QSL(
"interface"),
QString(), RichText,
962 ki18nc(
"tag-format-pattern <interface> rich",
965 tagFormatterInterface, 0);
968 SET_PATTERN(EMPHASIS,
QString(), PlainText,
969 ki18nc(
"tag-format-pattern <emphasis> plain",
973 SET_PATTERN(EMPHASIS,
QString(), RichText,
974 ki18nc(
"tag-format-pattern <emphasis> rich",
978 SET_PATTERN(EMPHASIS, QSL(
"strong"), PlainText,
979 ki18nc(
"tag-format-pattern <emphasis-strong> plain",
983 SET_PATTERN(EMPHASIS, QSL(
"strong"), RichText,
984 ki18nc(
"tag-format-pattern <emphasis-strong> rich",
990 SET_PATTERN(QSL(
"placeholder"),
QString(), PlainText,
991 ki18nc(
"tag-format-pattern <placeholder> plain",
995 SET_PATTERN(QSL(
"placeholder"),
QString(), RichText,
996 ki18nc(
"tag-format-pattern <placeholder> rich",
998 "<<i>%1</i>>"),
1002 SET_PATTERN(QSL(
"email"),
QString(), PlainText,
1003 ki18nc(
"tag-format-pattern <email> plain",
1007 SET_PATTERN(QSL(
"email"),
QString(), RichText,
1008 ki18nc(
"tag-format-pattern <email> rich",
1010 "<<a href=\"mailto:%1\">%1</a>>"),
1012 SET_PATTERN(QSL(
"email"), QSL(
"address"), PlainText,
1013 ki18nc(
"tag-format-pattern <email address=> plain\n"
1014 "%1 is name, %2 is address",
1018 SET_PATTERN(QSL(
"email"), QSL(
"address"), RichText,
1019 ki18nc(
"tag-format-pattern <email address=> rich\n"
1020 "%1 is name, %2 is address",
1022 "<a href=\"mailto:%2\">%1</a>"),
1026 SET_PATTERN(QSL(
"envar"),
QString(), PlainText,
1027 ki18nc(
"tag-format-pattern <envar> plain",
1031 SET_PATTERN(QSL(
"envar"),
QString(), RichText,
1032 ki18nc(
"tag-format-pattern <envar> rich",
1038 SET_PATTERN(QSL(
"message"),
QString(), PlainText,
1039 ki18nc(
"tag-format-pattern <message> plain",
1043 SET_PATTERN(QSL(
"message"),
QString(), RichText,
1044 ki18nc(
"tag-format-pattern <message> rich",
1050 SET_PATTERN(QSL(
"nl"),
QString(), PlainText,
1051 ki18nc(
"tag-format-pattern <nl> plain",
1055 SET_PATTERN(QSL(
"nl"),
QString(), RichText,
1056 ki18nc(
"tag-format-pattern <nl> rich",
1063void KuitSetupPrivate::setDefaultFormats()
1065 using namespace Kuit;
1068 formatsByRoleCue[ActionRole][UndefinedCue] =
PlainText;
1069 formatsByRoleCue[TitleRole][UndefinedCue] =
PlainText;
1070 formatsByRoleCue[LabelRole][UndefinedCue] =
PlainText;
1071 formatsByRoleCue[OptionRole][UndefinedCue] =
PlainText;
1072 formatsByRoleCue[ItemRole][UndefinedCue] =
PlainText;
1073 formatsByRoleCue[InfoRole][UndefinedCue] =
RichText;
1076 formatsByRoleCue[InfoRole][StatusCue] =
PlainText;
1077 formatsByRoleCue[InfoRole][ProgressCue] =
PlainText;
1078 formatsByRoleCue[InfoRole][CreditCue] =
PlainText;
1079 formatsByRoleCue[InfoRole][ShellCue] =
TermText;
1082KuitSetup::KuitSetup(
const QByteArray &domain)
1083 : d(new KuitSetupPrivate)
1086 d->setDefaultMarkup();
1087 d->setDefaultFormats();
1100 int leadingNewlines)
1102 d->setTagPattern(tagName, attribNames, format, pattern, formatter, leadingNewlines);
1107 d->setTagClass(tagName, aClass);
1112 d->setFormatForMarker(marker, format);
1115class KuitFormatterPrivate
1118 KuitFormatterPrivate(
const QString &language);
1123 QString metaTr(
const char *context,
const char *text)
const;
1126 void setFormattingPatterns();
1129 void setTextTransformData();
1135 static bool determineIsStructured(
const QString &text,
const KuitSetup &setup);
1150 enum Handling { Proper, Ignored, Dropout };
1167 static void countWrappingNewlines(
const QString &ptext,
int &numle,
int &numtr);
1179KuitFormatterPrivate::KuitFormatterPrivate(
const QString &language_)
1180 : language(language_)
1191 resolvedFormat = formatFromUiMarker(context, setup);
1196 if (text.
indexOf(QL1C(
'<')) < 0) {
1197 ftext = finalizeVisualText(text, resolvedFormat);
1200 ftext = toVisualText(text, resolvedFormat, setup);
1202 ftext = salvageMarkup(text, resolvedFormat, setup);
1210 KuitStaticData *s = staticData();
1215 parseUiMarker(context, roleName, cueName, formatName);
1218 Kuit::Role role = s->rolesByName.
value(roleName, Kuit::UndefinedRole);
1219 if (role == Kuit::UndefinedRole) {
1221 qCWarning(KI18N_KUIT) << QStringLiteral(
"Unknown role '@%1' in UI marker in context {%2}.").arg(roleName, shorten(context));
1227 if (role != Kuit::UndefinedRole) {
1228 cue = s->cuesByName.
value(cueName, Kuit::UndefinedCue);
1229 if (cue != Kuit::UndefinedCue) {
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));
1237 qCWarning(KI18N_KUIT) << QStringLiteral(
"Unknown subcue ':%1' in UI marker in context {%2}.").arg(cueName, shorten(context));
1242 cue = Kuit::UndefinedCue;
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()) {
1257 format = formatsByCue.value(Kuit::UndefinedCue);
1261 qCWarning(KI18N_KUIT) << QStringLiteral(
"Unknown format '/%1' in UI marker for message {%2}.").arg(formatName, shorten(context));
1271bool KuitFormatterPrivate::determineIsStructured(
const QString &text,
const KuitSetup &setup)
1275 static const QRegularExpression opensWithTagRx(QStringLiteral(
"^\\s*<\\s*(\\w+)[^>]*>"));
1276 bool isStructured =
false;
1278 if (
match.hasMatch()) {
1280 auto tagIt = setup.d->knownTags.
constFind(tagName);
1281 if (tagIt != setup.d->knownTags.
constEnd()) {
1282 const KuitTag &tag = *tagIt;
1286 return isStructured;
1289static const char s_entitySubRx[] =
"[a-z]+|#[0-9]+|#x[0-9a-fA-F]+";
1293 KuitStaticData *s = staticData();
1302 int p = original.
indexOf(QL1C(
'&'));
1305 original.
remove(0, p + 1);
1306 if (original.
indexOf(restRx) != 0) {
1307 text.
append(QSL(
"amp;"));
1309 p = original.
indexOf(QL1C(
'&'));
1316 bool isStructured = determineIsStructured(text, setup);
1319 const QString INTERNAL_TOP_TAG_NAME = QStringLiteral(
"__kuit_internal_top__");
1321 text = QStringLiteral(
"<%2>%1</%2>").
arg(text, INTERNAL_TOP_TAG_NAME);
1325 xml.setEntityResolver(&s->xmlEntityResolver);
1328 while (!xml.atEnd()) {
1331 if (xml.isStartElement()) {
1332 lastElementName = xml.name();
1338 oel.name = INTERNAL_TOP_TAG_NAME;
1339 oel.handling = OpenEl::Proper;
1342 OpenEl enclosingOel;
1343 for (
int i = openEls.
size() - 1; i >= 0; --i) {
1344 if (openEls[i].handling == OpenEl::Proper) {
1345 enclosingOel = openEls[i];
1350 oel = parseOpenEl(xml, enclosingOel, text, setup);
1355 }
else if (xml.isEndElement()) {
1357 OpenEl oel = openEls.
pop();
1362 return finalizeVisualText(oel.formattedText, format);
1367 openEls.
top().formattedText += formatSubText(ptext, oel, format, setup);
1368 }
else if (xml.isCharacters()) {
1372 const QString ctext = xml.text().toString();
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(
';');
1383 openEls.
top().formattedText += nctext;
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);
1397KuitFormatterPrivate::OpenEl
1406 const auto listAttributes = xml.
attributes();
1407 attribNames.
reserve(listAttributes.size());
1408 attribValues.
reserve(listAttributes.size());
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;
1416 auto tagIt = setup.d->knownTags.
constFind(oel.name);
1417 if (tagIt != setup.d->knownTags.
constEnd()) {
1418 const KuitTag &tag = *tagIt;
1419 const KuitTag &etag = setup.d->knownTags.
value(enclosingOel.name);
1424 oel.handling = OpenEl::Proper;
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));
1433 for (
int i = 0; i < attribNames.
size(); ++i) {
1435 if (tag.knownAttribs.
contains(att)) {
1437 oel.attributes[att] = attribValues[i];
1439 qCWarning(KI18N_KUIT) << QStringLiteral(
"Attribute '%1' not defined for tag '%2' in message {%3}.").arg(att, tag.name, shorten(text));
1444 oel.tagPath = enclosingOel.tagPath;
1445 oel.tagPath.prepend(enclosingOel.name);
1448 oel.handling = OpenEl::Ignored;
1449 qCWarning(KI18N_KUIT) << QStringLiteral(
"Tag '%1' is not defined in message {%2}.").arg(oel.name, shorten(text));
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);
1463 if (!ptext.
isEmpty() && tag.leadingNewlines > 0) {
1469 countWrappingNewlines(ptext, pnumle, pnumtr);
1470 countWrappingNewlines(ftext, fnumle, fnumtr);
1472 int numle = pnumtr + fnumle;
1475 if (numle < tag.leadingNewlines) {
1476 strle =
QString(tag.leadingNewlines - numle, QL1C(
'\n'));
1478 ftext = strle + ftext;
1483 }
else if (oel.handling == OpenEl::Ignored) {
1484 return QL1C(
'<') + oel.name + oel.attribStr + QL1C(
'>') + oel.formattedText + QSL(
"</") + oel.name + QL1C(
'>');
1487 return oel.formattedText;
1491void KuitFormatterPrivate::countWrappingNewlines(
const QString &text,
int &numle,
int &numtr)
1496 while (numle < len && text[numle] == QL1C(
'\n')) {
1501 while (numtr < len && text[len - numtr - 1] == QL1C(
'\n')) {
1508 KuitStaticData *s = staticData();
1518 while ((match = entRx.match(text)).hasMatch()) {
1525 const QChar c = ent.
at(1) == QL1C(
'x') ?
QChar(entView.mid(2).toInt(&ok, 16)) :
QChar(entView.mid(1).toInt(&
ok, 10));
1531 }
else if (s->xmlEntities.
contains(ent)) {
1532 plain.
append(s->xmlEntities[ent]);
1565 const QString content = salvageMarkup(
match.captured(4), format, setup);
1566 auto tagIt = setup.d->knownTags.
constFind(tagname);
1567 if (tagIt != setup.d->knownTags.
constEnd()) {
1568 const KuitTag &tag = *tagIt;
1571 ntext += tag.format(languageAsList, attributes, content,
QStringList(), format);
1573 ntext +=
match.captured(1) + content +
match.captured(5);
1575 pos =
match.capturedEnd(0);
1583 iter = nowrRx.globalMatch(text);
1590 auto tagIt = setup.d->knownTags.
constFind(tagname);
1591 if (tagIt != setup.d->knownTags.
constEnd()) {
1592 const KuitTag &tag = *tagIt;
1595 ntext +=
match.captured(0);
1597 pos =
match.capturedEnd(0);
1605 text = QStringLiteral(
"<html>") + text + QStringLiteral(
"</html>");
1611KuitFormatter::KuitFormatter(
const QString &language)
1612 : d(new KuitFormatterPrivate(language))
1616KuitFormatter::~KuitFormatter()
1623 return d->format(domain, context, text, format);
Lazy-initialized variant of KLocalizedString.
constexpr const char * untranslatedText() const
Returns the raw untranslated text as passed to kli18n*.
Class for producing and handling localized messages.
QString toString() const
Finalize the translation.
KLocalizedString ignoreMarkup() const
Do not resolve KUIT markup.
bool isEmpty() const
Check whether the message is empty.
KLocalizedString subs(int a, int fieldWidth=0, int base=10, QChar fillChar=QLatin1Char(' ')) const
Substitute an int argument into the message.
KLocalizedString relaxSubs() const
Relax matching between placeholders and arguments.
Class for modifying KUIT markup in a given domain.
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.
void setFormatForMarker(const QString &marker, Kuit::VisualFormat format)
Set the default visual format for a given UI marker.
void setTagClass(const QString &tagName, Kuit::TagClass aClass)
Set the KUIT class of the tag.
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
QString name(StandardAction id)
Global constants and functions related to KUIT markup.
TagClass
Classification of KUIT tags.
@ StructTag
Tags splitting text into paragraph-level blocks.
@ PhraseTag
Tags wrapping text inserted into running text.
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.
VisualFormat
Visual formats into which KUIT markup can be resolved.
@ TermText
Terminal escape sequences.
@ UndefinedFormat
Visual format not defined.
@ RichText
Qt rich text (HTML subset).
KI18N_EXPORT KuitSetup & setupForDomain(const QByteArray &domain)
Get hold of the KUIT setup object for a given domain.
QString toNativeSeparators(const QString &pathName)
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
bool contains(const Key &key) const const
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
QList< Key > keys() const const
T value(const Key &key) const const
void append(QList< T > &&value)
bool isEmpty() const const
qsizetype removeAll(const AT &t)
void reserve(qsizetype size)
qsizetype size() const const
T value(qsizetype i) const const
bool hasNext() const const
QRegularExpressionMatch next()
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
QString & append(QChar ch)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
void reserve(qsizetype size)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
QString trimmed() const const
void truncate(qsizetype position)
QString join(QChar separator) const const
QStringView left(qsizetype length) const const
QStringView mid(qsizetype start, qsizetype length) const const
QString toString() const const
QXmlStreamAttributes attributes() const const
QStringView name() const const