8 #include <QRegularExpression>
11 #include <QXmlStreamReader>
13 #include <klazylocalizedstring.h>
15 #include <kuitmarkup.h>
16 #include <kuitmarkup_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);
103 QString value = entityMap.value(name);
165 KuitEntityResolver xmlEntityResolver;
182 KuitStaticData(
const KuitStaticData &) =
delete;
183 KuitStaticData &operator=(
const KuitStaticData &) =
delete;
185 void setXmlEntityData();
187 void setUiMarkerData();
190 void setTextTransformData();
195 KuitStaticData::KuitStaticData()
199 setTextTransformData();
202 KuitStaticData::~KuitStaticData()
204 qDeleteAll(domainSetups);
207 void 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);
233 void 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; \
303 SET_FORMAT(
PlainText, QStringLiteral(
"plain"));
304 SET_FORMAT(
RichText, QStringLiteral(
"rich"));
305 SET_FORMAT(
TermText, QStringLiteral(
"term"));
311 keyNames[normname] = keyName;
314 void KuitStaticData::setTextTransformData()
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);
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);
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();
512 KuitSetup *setup = s->domainSetups.value(domain);
515 s->domainSetups.insert(domain, setup);
525 class KuitSetupPrivate
528 void setTagPattern(
const QString &tagName,
533 int leadingNewlines);
539 void setDefaultMarkup();
540 void setDefaultFormats();
547 void KuitSetupPrivate::setTagPattern(
const QString &tagName,
552 int leadingNewlines_)
554 auto tagIt = knownTags.
find(tagName);
555 if (tagIt == knownTags.end()) {
559 KuitTag &tag = *tagIt;
563 for (
const QString &attribName : std::as_const(attribNames)) {
564 tag.knownAttribs.insert(attribName);
566 QString attribKey = attributeSetKey(attribNames);
567 tag.attributeOrders[attribKey][format] = attribNames;
568 tag.patterns[attribKey][format] =
pattern;
569 tag.formatters[attribKey][format] = formatter;
570 tag.leadingNewlines = leadingNewlines_;
575 auto tagIt = knownTags.find(tagName);
576 if (tagIt == knownTags.end()) {
577 knownTags.insert(tagName, KuitTag(tagName, aClass));
579 tagIt->type = aClass;
585 KuitStaticData *s = staticData();
590 parseUiMarker(marker, roleName, cueName, formatName);
593 auto roleIt = s->rolesByName.constFind(roleName);
594 if (roleIt != s->rolesByName.constEnd()) {
596 }
else if (!roleName.
isEmpty()) {
597 qCWarning(KI18N_KUIT) << QStringLiteral(
"Unknown role '@%1' in UI marker {%2}, visual format not set.").arg(roleName, marker);
600 qCWarning(KI18N_KUIT) << QStringLiteral(
"Empty role in UI marker {%1}, visual format not set.").arg(marker);
605 auto cueIt = s->cuesByName.constFind(cueName);
606 if (cueIt != s->cuesByName.constEnd()) {
608 if (!s->knownRoleCues.value(role).contains(cue)) {
609 qCWarning(KI18N_KUIT)
610 << QStringLiteral(
"Subcue ':%1' does not belong to role '@%2' in UI marker {%3}, visual format not set.").arg(cueName, roleName, marker);
613 }
else if (!cueName.
isEmpty()) {
614 qCWarning(KI18N_KUIT) << QStringLiteral(
"Unknown subcue ':%1' in UI marker {%2}, visual format not set.").arg(cueName, marker);
617 cue = Kuit::UndefinedCue;
620 formatsByRoleCue[role][cue] = format;
623 #define TAG_FORMATTER_ARGS \
624 const QStringList &languages, const QString &tagName, const QHash<QString, QString> &attributes, const QString &text, const QStringList &tagPath, \
625 Kuit::VisualFormat format
627 static QString tagFormatterFilename(TAG_FORMATTER_ARGS)
631 Q_UNUSED(attributes);
638 const auto KUIT_CLOSE_XML_REPLACEMENT = QStringLiteral(
"__kuit_close_xml_tag__");
639 const auto KUIT_NOTEXT_XML_REPLACEMENT = QStringLiteral(
"__kuit_notext_xml_tag__");
642 result.
replace(QStringLiteral(
"</"), KUIT_CLOSE_XML_REPLACEMENT);
643 result.
replace(QStringLiteral(
"/>"), KUIT_NOTEXT_XML_REPLACEMENT);
645 result.
replace(KUIT_CLOSE_XML_REPLACEMENT, QStringLiteral(
"</"));
646 result.
replace(KUIT_NOTEXT_XML_REPLACEMENT, QStringLiteral(
"/>"));
655 static QString tagFormatterShortcut(TAG_FORMATTER_ARGS)
658 Q_UNUSED(attributes);
660 KuitStaticData *s = staticData();
661 return s->toKeyCombo(languages, text, format);
664 static QString tagFormatterInterface(TAG_FORMATTER_ARGS)
667 Q_UNUSED(attributes);
669 KuitStaticData *s = staticData();
670 return s->toInterfacePath(languages, text, format);
673 void KuitSetupPrivate::setDefaultMarkup()
675 using namespace Kuit;
677 const QString INTERNAL_TOP_TAG_NAME = QStringLiteral(
"__kuit_internal_top__");
678 const QString TITLE = QStringLiteral(
"title");
679 const QString EMPHASIS = QStringLiteral(
"emphasis");
680 const QString COMMAND = QStringLiteral(
"command");
681 const QString WARNING = QStringLiteral(
"warning");
682 const QString LINK = QStringLiteral(
"link");
683 const QString NOTE = QStringLiteral(
"note");
687 #define HI18NC ki18nc
691 #define SET_PATTERN(tagName, attribNames_, format, pattern, formatter, leadNl) \
693 QStringList attribNames; \
694 attribNames << attribNames_; \
695 setTagPattern(tagName, attribNames, format, pattern, formatter, leadNl); \
697 KuitTag &tag = knownTags[tagName]; \
698 QString attribKey = attributeSetKey(attribNames); \
699 if (format == PlainText && !tag.patterns[attribKey].contains(TermText)) { \
700 setTagPattern(tagName, attribNames, TermText, pattern, formatter, leadNl); \
708 setTagClass(INTERNAL_TOP_TAG_NAME,
StructTag);
710 HI18NC(
"tag-format-pattern <> plain",
715 HI18NC(
"tag-format-pattern <> rich",
723 ki18nc(
"tag-format-pattern <title> plain",
735 ki18nc(
"tag-format-pattern <title> rich",
743 ki18nc(
"tag-format-pattern <subtitle> plain",
748 ki18nc(
"tag-format-pattern <subtitle> rich",
756 ki18nc(
"tag-format-pattern <para> plain",
761 ki18nc(
"tag-format-pattern <para> rich",
769 ki18nc(
"tag-format-pattern <list> plain",
774 ki18nc(
"tag-format-pattern <list> rich",
782 ki18nc(
"tag-format-pattern <item> plain",
787 ki18nc(
"tag-format-pattern <item> rich",
794 ki18nc(
"tag-format-pattern <note> plain",
799 ki18nc(
"tag-format-pattern <note> rich",
803 SET_PATTERN(NOTE, QSL(
"label"),
PlainText,
804 ki18nc(
"tag-format-pattern <note label=> plain\n"
805 "%1 is the text, %2 is the note label",
809 SET_PATTERN(NOTE, QSL(
"label"),
RichText,
810 ki18nc(
"tag-format-pattern <note label=> rich\n"
811 "%1 is the text, %2 is the note label",
818 ki18nc(
"tag-format-pattern <warning> plain",
823 ki18nc(
"tag-format-pattern <warning> rich",
825 "<b>Warning</b>: %1"),
827 SET_PATTERN(WARNING, QSL(
"label"),
PlainText,
828 ki18nc(
"tag-format-pattern <warning label=> plain\n"
829 "%1 is the text, %2 is the warning label",
833 SET_PATTERN(WARNING, QSL(
"label"),
RichText,
834 ki18nc(
"tag-format-pattern <warning label=> rich\n"
835 "%1 is the text, %2 is the warning label",
842 ki18nc(
"tag-format-pattern <link> plain",
847 ki18nc(
"tag-format-pattern <link> rich",
849 "<a href=\"%1\">%1</a>"),
852 ki18nc(
"tag-format-pattern <link url=> plain\n"
853 "%1 is the descriptive text, %2 is the URL",
857 SET_PATTERN(LINK, QSL(
"url"),
RichText,
858 ki18nc(
"tag-format-pattern <link url=> rich\n"
859 "%1 is the descriptive text, %2 is the URL",
861 "<a href=\"%2\">%1</a>"),
866 ki18nc(
"tag-format-pattern <filename> plain",
869 tagFormatterFilename, 0);
871 ki18nc(
"tag-format-pattern <filename> rich",
874 tagFormatterFilename, 0);
878 ki18nc(
"tag-format-pattern <application> plain",
883 ki18nc(
"tag-format-pattern <application> rich",
890 ki18nc(
"tag-format-pattern <command> plain",
895 ki18nc(
"tag-format-pattern <command> rich",
899 SET_PATTERN(COMMAND, QSL(
"section"),
PlainText,
900 ki18nc(
"tag-format-pattern <command section=> plain\n"
901 "%1 is the command name, %2 is its man section",
905 SET_PATTERN(COMMAND, QSL(
"section"),
RichText,
906 ki18nc(
"tag-format-pattern <command section=> rich\n"
907 "%1 is the command name, %2 is its man section",
914 ki18nc(
"tag-format-pattern <resource> plain",
919 ki18nc(
"tag-format-pattern <resource> rich",
926 ki18nc(
"tag-format-pattern <icode> plain",
931 ki18nc(
"tag-format-pattern <icode> rich",
938 ki18nc(
"tag-format-pattern <bcode> plain",
943 ki18nc(
"tag-format-pattern <bcode> rich",
950 ki18nc(
"tag-format-pattern <shortcut> plain",
953 tagFormatterShortcut, 0);
955 ki18nc(
"tag-format-pattern <shortcut> rich",
958 tagFormatterShortcut, 0);
962 ki18nc(
"tag-format-pattern <interface> plain",
965 tagFormatterInterface, 0);
967 ki18nc(
"tag-format-pattern <interface> rich",
970 tagFormatterInterface, 0);
974 ki18nc(
"tag-format-pattern <emphasis> plain",
979 ki18nc(
"tag-format-pattern <emphasis> rich",
983 SET_PATTERN(EMPHASIS, QSL(
"strong"),
PlainText,
984 ki18nc(
"tag-format-pattern <emphasis-strong> plain",
988 SET_PATTERN(EMPHASIS, QSL(
"strong"),
RichText,
989 ki18nc(
"tag-format-pattern <emphasis-strong> rich",
996 ki18nc(
"tag-format-pattern <placeholder> plain",
1001 ki18nc(
"tag-format-pattern <placeholder> rich",
1003 "<<i>%1</i>>"),
1008 ki18nc(
"tag-format-pattern <email> plain",
1013 ki18nc(
"tag-format-pattern <email> rich",
1015 "<<a href=\"mailto:%1\">%1</a>>"),
1017 SET_PATTERN(QSL(
"email"), QSL(
"address"),
PlainText,
1018 ki18nc(
"tag-format-pattern <email address=> plain\n"
1019 "%1 is name, %2 is address",
1023 SET_PATTERN(QSL(
"email"), QSL(
"address"),
RichText,
1024 ki18nc(
"tag-format-pattern <email address=> rich\n"
1025 "%1 is name, %2 is address",
1027 "<a href=\"mailto:%2\">%1</a>"),
1032 ki18nc(
"tag-format-pattern <envar> plain",
1037 ki18nc(
"tag-format-pattern <envar> rich",
1044 ki18nc(
"tag-format-pattern <message> plain",
1049 ki18nc(
"tag-format-pattern <message> rich",
1056 ki18nc(
"tag-format-pattern <nl> plain",
1061 ki18nc(
"tag-format-pattern <nl> rich",
1068 void KuitSetupPrivate::setDefaultFormats()
1070 using namespace Kuit;
1073 formatsByRoleCue[ActionRole][UndefinedCue] =
PlainText;
1074 formatsByRoleCue[TitleRole][UndefinedCue] =
PlainText;
1075 formatsByRoleCue[LabelRole][UndefinedCue] =
PlainText;
1076 formatsByRoleCue[OptionRole][UndefinedCue] =
PlainText;
1077 formatsByRoleCue[ItemRole][UndefinedCue] =
PlainText;
1078 formatsByRoleCue[InfoRole][UndefinedCue] =
RichText;
1081 formatsByRoleCue[InfoRole][StatusCue] =
PlainText;
1082 formatsByRoleCue[InfoRole][ProgressCue] =
PlainText;
1083 formatsByRoleCue[InfoRole][CreditCue] =
PlainText;
1084 formatsByRoleCue[InfoRole][ShellCue] =
TermText;
1087 KuitSetup::KuitSetup(
const QByteArray &domain)
1088 : d(new KuitSetupPrivate)
1091 d->setDefaultMarkup();
1092 d->setDefaultFormats();
1102 int leadingNewlines)
1104 d->setTagPattern(tagName, attribNames, format, pattern, formatter, leadingNewlines);
1109 d->setTagClass(tagName, aClass);
1114 d->setFormatForMarker(marker, format);
1117 class KuitFormatterPrivate
1120 KuitFormatterPrivate(
const QString &language);
1125 QString metaTr(
const char *context,
const char *text)
const;
1128 void setFormattingPatterns();
1131 void setTextTransformData();
1137 static bool determineIsStructured(
const QString &text,
const KuitSetup &setup);
1152 enum Handling { Proper, Ignored, Dropout };
1169 static void countWrappingNewlines(
const QString &ptext,
int &numle,
int &numtr);
1181 KuitFormatterPrivate::KuitFormatterPrivate(
const QString &language_)
1182 : language(language_)
1193 resolvedFormat = formatFromUiMarker(context, setup);
1198 if (text.
indexOf(QL1C(
'<')) < 0) {
1199 ftext = finalizeVisualText(text, resolvedFormat);
1202 ftext = toVisualText(text, resolvedFormat, setup);
1204 ftext = salvageMarkup(text, resolvedFormat, setup);
1212 KuitStaticData *s = staticData();
1217 parseUiMarker(context, roleName, cueName, formatName);
1220 Kuit::Role role = s->rolesByName.value(roleName, Kuit::UndefinedRole);
1221 if (role == Kuit::UndefinedRole) {
1223 qCWarning(KI18N_KUIT) << QStringLiteral(
"Unknown role '@%1' in UI marker in context {%2}.").arg(roleName, shorten(context));
1229 if (role != Kuit::UndefinedRole) {
1230 cue = s->cuesByName.value(cueName, Kuit::UndefinedCue);
1231 if (cue != Kuit::UndefinedCue) {
1232 if (!s->knownRoleCues.value(role).contains(cue)) {
1233 cue = Kuit::UndefinedCue;
1234 qCWarning(KI18N_KUIT)
1235 << QStringLiteral(
"Subcue ':%1' does not belong to role '@%2' in UI marker in context {%3}.").arg(cueName, roleName, shorten(context));
1239 qCWarning(KI18N_KUIT) << QStringLiteral(
"Unknown subcue ':%1' in UI marker in context {%2}.").arg(cueName, shorten(context));
1244 cue = Kuit::UndefinedCue;
1252 auto formatsByCueIt = setup.d->formatsByRoleCue.constFind(role);
1253 if (formatsByCueIt != setup.d->formatsByRoleCue.constEnd()) {
1254 const auto &formatsByCue = *formatsByCueIt;
1255 auto formatIt = formatsByCue.constFind(cue);
1256 if (formatIt != formatsByCue.constEnd()) {
1259 format = formatsByCue.value(Kuit::UndefinedCue);
1263 qCWarning(KI18N_KUIT) << QStringLiteral(
"Unknown format '/%1' in UI marker for message {%2}.").arg(formatName, shorten(context));
1273 bool KuitFormatterPrivate::determineIsStructured(
const QString &text,
const KuitSetup &setup)
1277 static const QRegularExpression opensWithTagRx(QStringLiteral(
"^\\s*<\\s*(\\w+)[^>]*>"));
1278 bool isStructured =
false;
1280 if (
match.hasMatch()) {
1282 auto tagIt = setup.d->knownTags.constFind(tagName);
1283 if (tagIt != setup.d->knownTags.constEnd()) {
1284 const KuitTag &tag = *tagIt;
1288 return isStructured;
1291 static const char s_entitySubRx[] =
"[a-z]+|#[0-9]+|#x[0-9a-fA-F]+";
1295 KuitStaticData *s = staticData();
1304 int p = original.
indexOf(QL1C(
'&'));
1307 original.
remove(0, p + 1);
1308 if (original.
indexOf(restRx) != 0) {
1309 text.
append(QSL(
"amp;"));
1311 p = original.
indexOf(QL1C(
'&'));
1318 bool isStructured = determineIsStructured(text, setup);
1321 const QString INTERNAL_TOP_TAG_NAME = QStringLiteral(
"__kuit_internal_top__");
1323 text = QStringLiteral(
"<%2>%1</%2>").
arg(text, INTERNAL_TOP_TAG_NAME);
1327 xml.setEntityResolver(&s->xmlEntityResolver);
1330 while (!xml.atEnd()) {
1333 if (xml.isStartElement()) {
1334 lastElementName = xml.name();
1340 oel.name = INTERNAL_TOP_TAG_NAME;
1341 oel.handling = OpenEl::Proper;
1344 OpenEl enclosingOel;
1345 for (
int i = openEls.
size() - 1; i >= 0; --i) {
1346 if (openEls[i].handling == OpenEl::Proper) {
1347 enclosingOel = openEls[i];
1352 oel = parseOpenEl(xml, enclosingOel, text, setup);
1357 }
else if (xml.isEndElement()) {
1359 OpenEl oel = openEls.
pop();
1364 return finalizeVisualText(oel.formattedText, format);
1369 openEls.
top().formattedText += formatSubText(ptext, oel, format, setup);
1370 }
else if (xml.isCharacters()) {
1374 const QString ctext = xml.text().toString();
1376 for (
const QChar c : ctext) {
1377 auto nameIt = s->xmlEntitiesInverse.constFind(c);
1378 if (nameIt != s->xmlEntitiesInverse.constEnd()) {
1379 const QString &entName = *nameIt;
1380 nctext += QL1C(
'&') + entName + QL1C(
';');
1385 openEls.
top().formattedText += nctext;
1389 if (xml.hasError()) {
1390 qCWarning(KI18N_KUIT) << QStringLiteral(
"Markup error in message {%1}: %2. Last tag parsed: %3. Complete message follows:\n%4")
1391 .
arg(shorten(text), xml.errorString(), lastElementName.
toString(), text);
1399 KuitFormatterPrivate::OpenEl
1408 const auto listAttributes = xml.
attributes();
1409 attribNames.
reserve(listAttributes.size());
1410 attribValues.
reserve(listAttributes.size());
1412 attribNames += xatt.name().toString().toLower();
1413 attribValues += xatt.
value().toString();
1414 QChar qc = attribValues.
last().indexOf(QL1C(
'\'')) < 0 ? QL1C(
'\'') : QL1C(
'"');
1415 oel.attribStr += QL1C(
' ') + attribNames.
last() + QL1C(
'=') + qc + attribValues.
last() + qc;
1418 auto tagIt = setup.d->knownTags.constFind(oel.name);
1419 if (tagIt != setup.d->knownTags.constEnd()) {
1420 const KuitTag &tag = *tagIt;
1421 const KuitTag &etag = setup.d->knownTags.value(enclosingOel.name);
1426 oel.handling = OpenEl::Proper;
1428 oel.handling = OpenEl::Dropout;
1429 qCWarning(KI18N_KUIT)
1430 << QStringLiteral(
"Structuring tag ('%1') cannot be subtag of phrase tag ('%2') in message {%3}.").arg(tag.name, etag.name, shorten(text));
1435 for (
int i = 0; i < attribNames.
size(); ++i) {
1437 if (tag.knownAttribs.contains(att)) {
1439 oel.attributes[att] = attribValues[i];
1441 qCWarning(KI18N_KUIT) << QStringLiteral(
"Attribute '%1' not defined for tag '%2' in message {%3}.").arg(att, tag.name, shorten(text));
1446 oel.tagPath = enclosingOel.tagPath;
1447 oel.tagPath.prepend(enclosingOel.name);
1450 oel.handling = OpenEl::Ignored;
1451 qCWarning(KI18N_KUIT) << QStringLiteral(
"Tag '%1' is not defined in message {%2}.").arg(oel.name, shorten(text));
1459 if (oel.handling == OpenEl::Proper) {
1460 const KuitTag &tag = setup.d->knownTags.value(oel.name);
1461 QString ftext = tag.format(languageAsList, oel.attributes, oel.formattedText, oel.tagPath, format);
1465 if (!ptext.
isEmpty() && tag.leadingNewlines > 0) {
1471 countWrappingNewlines(ptext, pnumle, pnumtr);
1472 countWrappingNewlines(ftext, fnumle, fnumtr);
1474 int numle = pnumtr + fnumle;
1477 if (numle < tag.leadingNewlines) {
1478 strle =
QString(tag.leadingNewlines - numle, QL1C(
'\n'));
1480 ftext = strle + ftext;
1485 }
else if (oel.handling == OpenEl::Ignored) {
1486 return QL1C(
'<') + oel.name + oel.attribStr + QL1C(
'>') + oel.formattedText + QSL(
"</") + oel.name + QL1C(
'>');
1489 return oel.formattedText;
1493 void KuitFormatterPrivate::countWrappingNewlines(
const QString &text,
int &numle,
int &numtr)
1498 while (numle < len && text[numle] == QL1C(
'\n')) {
1503 while (numtr < len && text[len - numtr - 1] == QL1C(
'\n')) {
1510 KuitStaticData *s = staticData();
1520 while ((match = entRx.match(text)).hasMatch()) {
1526 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1528 const QChar c = ent.
at(1) == QL1C(
'x') ?
QChar(entView.mid(2).toInt(&ok, 16)) :
QChar(entView.mid(1).toInt(&
ok, 10));
1537 }
else if (s->xmlEntities.contains(ent)) {
1538 plain.
append(s->xmlEntities[ent]);
1571 const QString content = salvageMarkup(
match.captured(4), format, setup);
1572 auto tagIt = setup.d->knownTags.constFind(tagname);
1573 if (tagIt != setup.d->knownTags.constEnd()) {
1574 const KuitTag &tag = *tagIt;
1577 ntext += tag.format(languageAsList, attributes, content,
QStringList(), format);
1579 ntext +=
match.captured(1) + content +
match.captured(5);
1581 pos =
match.capturedEnd(0);
1589 iter = nowrRx.globalMatch(text);
1596 auto tagIt = setup.d->knownTags.constFind(tagname);
1597 if (tagIt != setup.d->knownTags.constEnd()) {
1598 const KuitTag &tag = *tagIt;
1601 ntext +=
match.captured(0);
1603 pos =
match.capturedEnd(0);
1611 text = QStringLiteral(
"<html>") + text + QStringLiteral(
"</html>");
1617 KuitFormatter::KuitFormatter(
const QString &language)
1618 : d(new KuitFormatterPrivate(language))
1622 KuitFormatter::~KuitFormatter()
1629 return d->format(domain, context, text, format);