28#include <QXmlStreamReader>
39#define QL1S(x) QLatin1String(x)
47 if (str.length() <=
maxlen)
54class KuitEntityResolver :
public QXmlStreamEntityResolver
65 QString value = entityMap.value(name);
84 Title, Subtitle, Para, List, Item, Note, Warning, Link,
85 Filename, Application, Command, Resource, Icode, Bcode, Shortcut,
86 Interface, Emphasis, Placeholder, Email, Numid, Envar, Message, Nl,
94 Ctx, Url, Address, Section, Label, Strong,
102 Action, Title, Option, Label, Item, Info
109 Button, Inmenu, Intoolbar,
110 Window, Menu, Tab, Group, Column, Row,
111 Slider, Spinbox, Listbox, Textbox, Chooser,
113 Inlistbox, Intable, Inrange, Intext,
114 Tooltip, Whatsthis,
Status, Progress, Tipoftheday, Credit, Shell
120 None, Plain, Rich, Term
124 typedef Tag::Var TagVar;
125 typedef Att::Var AttVar;
126 typedef Rol::Var RolVar;
127 typedef Cue::Var CueVar;
128 typedef Fmt::Var FmtVar;
134class KuitSemanticsStaticData
158 KuitEntityResolver xmlEntityResolver;
160 KuitSemanticsStaticData ();
163KuitSemanticsStaticData::KuitSemanticsStaticData ()
168 #define SETUP_TAG(tag, name, atts, subs) do { \
169 knownTags.insert(QString::fromLatin1(name), Kuit::Tag::tag); \
170 tagNames.insert(Kuit::Tag::tag, QString::fromLatin1(name)); \
172 using namespace Kuit::Att; \
173 tagAtts[Kuit::Tag::tag] << atts; \
176 using namespace Kuit::Tag; \
177 tagSubs[Kuit::Tag::tag] << subs << NumIntg << NumReal; \
183 Filename << Link << Application << Command << Resource << Icode << \
184 Shortcut << Interface << Emphasis << Placeholder << Email << \
187 SETUP_TAG(TopLong,
"kuit", Ctx, Title << Subtitle << Para);
193 INLINES << Note << Warning << Message << List);
199 SETUP_TAG(Filename,
"filename", None, Envar << Placeholder);
201 SETUP_TAG(Application,
"application", None, None);
202 SETUP_TAG(Command,
"command", Section, None);
203 SETUP_TAG(Resource,
"resource", None, None);
204 SETUP_TAG(Icode,
"icode", None, Envar << Placeholder);
206 SETUP_TAG(Shortcut,
"shortcut", None, None);
207 SETUP_TAG(Interface,
"interface", None, None);
208 SETUP_TAG(Emphasis,
"emphasis", Strong, None);
209 SETUP_TAG(Placeholder,
"placeholder", None, None);
210 SETUP_TAG(Email,
"email", Address, None);
212 SETUP_TAG(Message,
"message", None, None);
221 #define SETUP_ATT(att, name) do { \
222 knownAtts.insert(QString::fromLatin1(name), Kuit::Att::att); \
236 #define SETUP_FMT(fmt, name) do { \
237 knownFmts.insert(QString::fromLatin1(name), Kuit::Fmt::fmt); \
245 #define SETUP_ROL(rol, name, fmt, cues) do { \
246 knownRols.insert(QString::fromLatin1(name), Kuit::Rol::rol); \
247 defFmts[Kuit::Rol::rol][Kuit::Cue::None] = Kuit::Fmt::fmt; \
249 using namespace Kuit::Cue; \
250 rolCues[Kuit::Rol::rol] << cues; \
254 Button << Inmenu << Intoolbar);
256 Window << Menu << Tab << Group << Column << Row);
258 Slider << Spinbox << Listbox << Textbox << Chooser);
262 Inmenu << Inlistbox << Intable << Inrange << Intext);
264 Tooltip << Whatsthis << Kuit::Cue::Status << Progress
265 << Tipoftheday << Credit << Shell);
268 #undef SETUP_ROLCUEFMT
269 #define SETUP_ROLCUEFMT(rol, cue, fmt) do { \
270 defFmts[Kuit::Rol::rol][Kuit::Cue::cue] = Kuit::Fmt::fmt; \
279 #define SETUP_CUE(cue, name) do { \
280 knownCues.insert(QString::fromLatin1(name), Kuit::Cue::cue); \
311 qtHtmlTagNames <<
QL1S(
"a") <<
QL1S(
"address") <<
QL1S(
"b") <<
QL1S(
"big") <<
QL1S(
"blockquote")
324 #define SETUP_TAG_NL(tag, nlead) do { \
325 leadingNewlines.insert(Kuit::Tag::tag, nlead); \
346 xmlEntities[QString::fromLatin1(
"nbsp")] =
QString(QChar(0xa0));
347 xmlEntityResolver.setEntities(xmlEntities);
356class KuitSemanticsPrivate
365 QString metaTr (
const char *ctxt,
const char *
id)
const;
368 void setFormattingPatterns ();
371 void setTextTransformData ();
377 static Kuit::FmtVar formatFromContextMarker (
const QString &
ctxmark,
380 static Kuit::FmtVar formatFromTags (
const QString &text);
388 Kuit::FmtVar
fmtImp)
const;
404 typedef enum { Proper, Ignored, Dropout } Handling;
421 QString visualPattern (Kuit::TagVar tag,
int akey, Kuit::FmtVar
fmt)
const;
428 static void countWrappingNewlines (
const QString &
ptext,
453KuitSemanticsPrivate::KuitSemanticsPrivate (
const QString &
lang)
464 m_metaCat =
new KCatalog(QString::fromLatin1(
"kdelibs4"),
lang);
467 setFormattingPatterns();
470 setTextTransformData();
477QString KuitSemanticsPrivate::metaTr (
const char *ctxt,
const char *
id)
const
479 if (m_metaCat ==
NULL) {
480 return QString::fromLatin1(
id);
482 return m_metaCat->translate(ctxt,
id);
485void KuitSemanticsPrivate::setFormattingPatterns ()
487 using namespace Kuit;
491 #define SET_PATTERN(tag, atts, fmt, ctxt_ptrn) do { \
494 int akey = attSetKey(aset); \
495 QString pattern = metaTr(ctxt_ptrn); \
496 m_patterns[tag][akey][fmt] = pattern; \
498 if (fmt == Fmt::Plain && !m_patterns[tag][akey].contains(Fmt::Term)) { \
499 m_patterns[tag][akey][Fmt::Term] = pattern; \
505 #define I18N_NOOP2(ctxt, msg) ctxt, msg
510 #define XXXX_NOOP2(ctxt, msg) ctxt, msg
585 "%1 is the note label, %2 is the text",
590 "%1 is the note label, %2 is the text",
602 "<b>Warning</b>: %1"));
605 "%1 is the warning label, %2 is the text",
610 "%1 is the warning label, %2 is the text",
622 "<a href=\"%1\">%1</a>"));
625 "%1 is the URL, %2 is the descriptive text",
630 "%1 is the URL, %2 is the descriptive text",
632 "<a href=\"%1\">%2</a>"));
645 SET_PATTERN(Tag::Application, Att::None, Fmt::Plain,
649 SET_PATTERN(Tag::Application, Att::None, Fmt::Rich,
663 SET_PATTERN(Tag::Command, Att::Section, Fmt::Plain,
665 "%1 is the command name, %2 is its man section",
670 "%1 is the command name, %2 is its man section",
733 SET_PATTERN(Tag::Emphasis, Att::Strong, Fmt::Plain,
743 SET_PATTERN(Tag::Placeholder, Att::None, Fmt::Plain,
747 SET_PATTERN(Tag::Placeholder, Att::None, Fmt::Rich,
750 "<<i>%1</i>>"));
760 "<<a href=\"mailto:%1\">%1</a>>"));
763 "%1 is name, %2 is address",
768 "%1 is name, %2 is address",
770 "<a href=\"mailto:%2\">%1</a>"));
803void KuitSemanticsPrivate::setTextTransformData ()
807 #define I18N_NOOP2(ctxt, msg) metaTr(ctxt, msg)
811 m_comboKeyDelim[Kuit::Fmt::Plain] =
I18N_NOOP2(
"shortcut-key-delimiter/plain",
"+");
812 m_comboKeyDelim[Kuit::Fmt::Term] = m_comboKeyDelim[Kuit::Fmt::Plain];
815 m_comboKeyDelim[Kuit::Fmt::Rich] =
I18N_NOOP2(
"shortcut-key-delimiter/rich",
"+");
819 m_guiPathDelim[Kuit::Fmt::Plain] =
I18N_NOOP2(
"gui-path-delimiter/plain",
"→");
820 m_guiPathDelim[Kuit::Fmt::Term] = m_guiPathDelim[Kuit::Fmt::Plain];
823 m_guiPathDelim[Kuit::Fmt::Rich] =
I18N_NOOP2(
"gui-path-delimiter/rich",
"→");
828 #define SET_KEYNAME(rawname) do { \
830 QString normname = QString::fromLatin1(rawname).trimmed().toLower(); \
831 m_keyNames[normname] = metaTr("keyboard-key-name", rawname); \
836 #define I18N_NOOP2(ctxt, msg) msg
886 Kuit::FmtVar
fmtExplicit = formatFromContextMarker(ctxt, text);
907 if (
ftext.isEmpty()) {
920 foreach (
const Kuit::AttVar &
att,
alist) {
927Kuit::FmtVar KuitSemanticsPrivate::formatFromContextMarker (
972 if (
s->knownRols.contains(
rolname)) {
976 rol = Kuit::Rol::None;
978 kDebug(173) << QString::fromLatin1(
"Unknown semantic role '@%1' in "
979 "context marker for message {%2}.")
986 if (
s->knownCues.contains(
cuename)) {
990 cue = Kuit::Cue::None;
992 kDebug(173) << QString::fromLatin1(
"Unknown interface subcue ':%1' in "
993 "context marker for message {%2}.")
1000 if (
s->knownFmts.contains(
fmtname)) {
1007 if (
s->defFmts.contains(
rol)) {
1008 if (
s->defFmts[
rol].contains(
cue)) {
1012 fmt =
s->defFmts[
rol][Kuit::Cue::None];
1016 fmt = Kuit::Fmt::None;
1020 kDebug(173) << QString::fromLatin1(
"Unknown visual format '/%1' in "
1021 "context marker for message {%2}.")
1029Kuit::FmtVar KuitSemanticsPrivate::formatFromTags (
const QString &text)
1035 int p =
tagRx.indexIn(text);
1038 if (
s->qtHtmlTagNames.contains(
tagname)) {
1039 return Kuit::Fmt::Rich;
1041 p =
tagRx.indexIn(text, p +
tagRx.matchedLength());
1043 return Kuit::Fmt::Plain;
1075 if (
s->knownTags.contains(
tagname)) {
1076 Kuit::TagVar tag =
s->knownTags[
tagname];
1077 if ( tag == Kuit::Tag::TopLong
1078 || tag == Kuit::Tag::TopShort) {
1082 else if ( tag == Kuit::Tag::Para
1083 || tag == Kuit::Tag::Title
1084 || tag == Kuit::Tag::Subtitle) {
1085 toptag = Kuit::Tag::TopLong;
1088 toptag = Kuit::Tag::TopShort;
1092 toptag = Kuit::Tag::TopShort;
1096 toptag = Kuit::Tag::TopShort;
1110#define ENTITY_SUBRX "[a-z]+|#[0-9]+|#x[0-9a-fA-F]+"
1124 text.append(
original.mid(0, p + 1));
1141 xml.setEntityResolver(&
s->xmlEntityResolver);
1144 while (!
xml.atEnd()) {
1147 if (
xml.isStartElement()) {
1151 Kuit::TagVar
etag = Kuit::Tag::None;
1152 for (
int i =
openEls.size() - 1; i >= 0; --i) {
1153 if (
openEls[i].handling == OpenEl::Proper) {
1164 if (
s->qtHtmlTagNames.contains(
oel.name)) {
1170 if (
openEls.isEmpty() &&
oel.avals.contains(Kuit::Att::Ctx)) {
1172 fmtExp = formatFromContextMarker(
oel.avals[Kuit::Att::Ctx], text);
1180 if (
oel.tag == Kuit::Tag::Numid) {
1184 else if (
xml.isEndElement()) {
1191 return finalizeVisualText(
oel.formattedText,
fmtExp,
1200 if (
oel.tag == Kuit::Tag::Numid) {
1204 else if (
xml.isCharacters()) {
1210 foreach (
const QChar &
c, text) {
1211 if (
s->xmlEntitiesInverse.contains(
c)) {
1222 if (
xml.hasError()) {
1223 kDebug(173) << QString::fromLatin1(
"Markup error in message {%1}: %2. Last tag parsed: %3")
1232KuitSemanticsPrivate::OpenEl
1244 oel.name =
xml.name().toString().toLower();
1255 if (
s->knownTags.contains(
oel.name)) {
1256 oel.tag =
s->knownTags[
oel.name];
1260 if (
etag == Kuit::Tag::None ||
s->tagSubs[
etag].contains(
oel.tag)) {
1261 oel.handling = OpenEl::Proper;
1264 oel.handling = OpenEl::Dropout;
1265 kDebug(173) << QString::fromLatin1(
"Tag '%1' cannot be subtag of '%2' "
1267 .arg(
s->tagNames[
oel.tag],
s->tagNames[
etag],
1273 for (
int i = 0; i <
attnams.size(); ++i) {
1274 if (
s->knownAtts.contains(
attnams[i])) {
1276 if (
s->tagAtts[
oel.tag].contains(
att)) {
1281 kDebug(173) << QString::fromLatin1(
"Attribute '%1' cannot be used in "
1282 "tag '%2' in message {%3}.")
1288 kDebug(173) << QString::fromLatin1(
"Unknown semantic tag attribute '%1' "
1297 oel.handling = OpenEl::Dropout;
1300 oel.handling = OpenEl::Ignored;
1301 if (!
s->qtHtmlTagNames.contains(
oel.name)) {
1302 kDebug(173) << QString::fromLatin1(
"Tag '%1' is neither semantic nor HTML in "
1311QString KuitSemanticsPrivate::visualPattern (Kuit::TagVar tag,
int akey,
1312 Kuit::FmtVar
fmt)
const
1315 QString pattern = QString::fromLatin1(
"%1");
1318 if ( m_patterns.contains(tag)
1319 && m_patterns[tag].contains(akey)
1320 && m_patterns[tag][akey].contains(
fmt))
1322 pattern = m_patterns[tag][akey][
fmt];
1335 if (
oel.handling == OpenEl::Proper) {
1343 using namespace Kuit;
1347 if (
oel.tag == Tag::Link &&
oel.avals.contains(Att::Url)) {
1350 else if (
oel.tag == Tag::Command &&
oel.avals.contains(Att::Section)) {
1353 else if (
oel.tag == Tag::Email &&
oel.avals.contains(Att::Address)) {
1356 else if (
oel.tag == Tag::Note &&
oel.avals.contains(Att::Label)) {
1359 else if (
oel.tag == Tag::Warning &&
oel.avals.contains(Att::Label)) {
1368 if (!
ptext.isEmpty() &&
s->leadingNewlines.contains(
oel.tag)) {
1385 else if (
oel.handling == OpenEl::Ignored) {
1397 return oel.formattedText;
1401void KuitSemanticsPrivate::countWrappingNewlines (
const QString &text,
1404 int len = text.length();
1421 Kuit::FmtVar
fmt)
const
1424 if ( (tag == Kuit::Tag::NumIntg || tag == Kuit::Tag::NumReal) \
1430 return QString::fromLatin1(
"%1").arg(
KGlobal::locale()->formatNumber(text,
false),
1433 else if (tag == Kuit::Tag::Filename) {
1434 return QDir::toNativeSeparators(text);
1436 else if (tag == Kuit::Tag::Shortcut) {
1439 else if (tag == Kuit::Tag::Interface) {
1447QString KuitSemanticsPrivate::finalizeVisualText (
const QString &
final,
1464 int p =
entRx.indexIn(text);
1468 plain.append(text.mid(0, p));
1469 text.remove(0, p +
ent.length() + 2);
1474 c = QChar(
ent.mid(2).toInt(&ok, 16));
1476 c = QChar(
ent.mid(1).toInt(&ok, 10));
1484 else if (
s->xmlEntities.contains(
ent)) {
1489 p =
entRx.indexIn(text);
1497 text = QString::fromLatin1(
"<html>") + text +
QLatin1String(
"</html>");
1504 Kuit::FmtVar
fmt)
const
1530 if (
s->knownTags.contains(
tagname)) {
1538 pos +=
wrapRx.matchedLength();
1558 if (
s->knownTags.contains(
tagname)) {
1564 pos +=
nowrRx.matchedLength();
1575: d(
new KuitSemanticsPrivate(
lang))
1586 return d->format(text, ctxt);
1598 return (
p2 >
p1 &&
s->xmlEntities.contains(text.mid(
p1,
p2 -
p1)));
1602 int tlen = text.length();
1622 return s->qtHtmlTagNames.contains(text.mid(
p1,
p2 -
p1));
1623 }
else if (!
c.isLetter()) {
1635 int tlen = text.length();
1638 for (
int i = 0; i <
tlen; ++i) {
This class abstracts a gettext message catalog.
~KuitSemantics()
Destructor.
static bool mightBeRichText(const QString &text)
Poor man's version of Qt::mightBeRichText() (cannot link to QtGui).
KuitSemantics(const QString &lang)
Constructor.
static QString escape(const QString &text)
Convert &, ", ', <, > characters into XML entities &, <, >, ', ", respectively.
QString format(const QString &text, const QString &ctxt) const
Transforms the semantic markup in the given text into visual formatting.
#define K_GLOBAL_STATIC(TYPE, NAME)
This macro makes it easy to use non-POD types as global statics.
#define I18N_NOOP2(comment, x)
If the string is too ambiguous to be translated well to a non-english language, use this instead of I...
static QString shorten(const QString &str)
#define SETUP_ATT(att, name)
#define SET_PATTERN(tag, atts, fmt, ctxt_ptrn)
#define SETUP_TAG(tag, name, atts, subs)
#define SETUP_CUE(cue, name)
#define SETUP_ROLCUEFMT(rol, cue, fmt)
#define SETUP_TAG_NL(tag, nlead)
#define SET_KEYNAME(rawname)
#define SETUP_ROL(rol, name, fmt, cues)
#define SETUP_FMT(fmt, name)
#define XXXX_NOOP2(ctxt, msg)
KLocale * locale()
Returns the global locale object.