• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.10.0 API Reference
  • KDE Home
  • Contact Us
 

KDECore

  • kdecore
  • localization
kuitsemantics.cpp
Go to the documentation of this file.
1 /* This file is part of the KDE libraries
2  Copyright (C) 2007 Chusslove Illich <caslav.ilic@gmx.net>
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License as published by the Free Software Foundation; either
7  version 2 of the License, or (at your option) any later version.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18 */
19 
20 #include <kuitsemantics_p.h>
21 
22 #include <config.h>
23 
24 #include <QHash>
25 #include <QSet>
26 #include <QRegExp>
27 #include <QStack>
28 #include <QXmlStreamReader>
29 #include <QStringList>
30 #include <QPair>
31 #include <QDir>
32 
33 #include <kdebug.h>
34 #include <kglobal.h>
35 #include <kcatalog_p.h>
36 #include <kuitformats_p.h>
37 #include <klocale.h>
38 
39 #define QL1S(x) QLatin1String(x)
40 
41 // Truncates string, for output of long messages.
42 // (But don't truncate too much otherwise it's impossible to determine
43 // which message is faulty if many messages start with the same beginning).
44 static QString shorten (const QString &str)
45 {
46  const int maxlen = 80;
47  if (str.length() <= maxlen)
48  return str;
49  else
50  return str.left(maxlen).append(QLatin1String("..."));
51 }
52 
53 // -----------------------------------------------------------------------------
54 // All the tag, attribute, and context marker element enums.
55 namespace Kuit {
56 
57  namespace Tag { // tag names
58  typedef enum {
59  None,
60  TopLong, TopShort,
61  Title, Subtitle, Para, List, Item, Note, Warning, Link,
62  Filename, Application, Command, Resource, Icode, Bcode, Shortcut,
63  Interface, Emphasis, Placeholder, Email, Numid, Envar, Message, Nl,
64  NumIntg, NumReal // internal helpers for numbers, not part of DTD
65  } Var;
66  }
67 
68  namespace Att { // tag attribute names
69  typedef enum {
70  None,
71  Ctx, Url, Address, Section, Label, Strong,
72  Width, Fill // internal helpers for numbers, not part of DTD
73  } Var;
74  }
75 
76  namespace Rol { // semantic roles
77  typedef enum {
78  None,
79  Action, Title, Option, Label, Item, Info
80  } Var;
81  }
82 
83  namespace Cue { // interface subcues
84  typedef enum {
85  None,
86  Button, Inmenu, Intoolbar,
87  Window, Menu, Tab, Group, Column, Row,
88  Slider, Spinbox, Listbox, Textbox, Chooser,
89  Check, Radio,
90  Inlistbox, Intable, Inrange, Intext,
91  Tooltip, Whatsthis, Status, Progress, Tipoftheday, Credit, Shell
92  } Var;
93  }
94 
95  namespace Fmt { // visual formats
96  typedef enum {
97  None, Plain, Rich, Term
98  } Var;
99  }
100 
101  typedef Tag::Var TagVar;
102  typedef Att::Var AttVar;
103  typedef Rol::Var RolVar;
104  typedef Cue::Var CueVar;
105  typedef Fmt::Var FmtVar;
106 }
107 
108 // -----------------------------------------------------------------------------
109 // All the global data.
110 
111 class KuitSemanticsStaticData
112 {
113  public:
114 
115  QHash<QString, Kuit::TagVar> knownTags;
116  QHash<QString, Kuit::AttVar> knownAtts;
117  QHash<QString, Kuit::FmtVar> knownFmts;
118  QHash<QString, Kuit::RolVar> knownRols;
119  QHash<QString, Kuit::CueVar> knownCues;
120 
121  QHash<Kuit::TagVar, QSet<Kuit::TagVar> > tagSubs;
122  QHash<Kuit::TagVar, QSet<Kuit::AttVar> > tagAtts;
123  QHash<Kuit::RolVar, QSet<Kuit::CueVar> > rolCues;
124 
125  QHash<Kuit::RolVar, QHash<Kuit::CueVar, Kuit::FmtVar> > defFmts;
126 
127  QHash<Kuit::TagVar, QString> tagNames;
128 
129  QSet<QString> qtHtmlTagNames;
130 
131  QHash<Kuit::TagVar, int> leadingNewlines;
132 
133  QHash<QString, QString> xmlEntities;
134  QHash<QString, QString> xmlEntitiesInverse;
135 
136  KuitSemanticsStaticData ();
137 };
138 
139 KuitSemanticsStaticData::KuitSemanticsStaticData ()
140 {
141  // Setup known tag names, attributes, and subtags.
142  // A "lax" version of the DTD.
143  #undef SETUP_TAG
144  #define SETUP_TAG(tag, name, atts, subs) do { \
145  knownTags.insert(QString::fromLatin1(name), Kuit::Tag::tag); \
146  tagNames.insert(Kuit::Tag::tag, QString::fromLatin1(name)); \
147  { \
148  using namespace Kuit::Att; \
149  tagAtts[Kuit::Tag::tag] << atts; \
150  } \
151  { \
152  using namespace Kuit::Tag; \
153  tagSubs[Kuit::Tag::tag] << subs << NumIntg << NumReal; \
154  } \
155  } while (0)
156 
157  #undef INLINES
158  #define INLINES \
159  Filename << Link << Application << Command << Resource << Icode << \
160  Shortcut << Interface << Emphasis << Placeholder << Email << \
161  Numid << Envar << Nl
162 
163  SETUP_TAG(TopLong, "kuit", Ctx, Title << Subtitle << Para);
164  SETUP_TAG(TopShort, "kuil", Ctx, INLINES << Note << Warning << Message);
165 
166  SETUP_TAG(Title, "title", None, INLINES);
167  SETUP_TAG(Subtitle, "subtitle", None, INLINES);
168  SETUP_TAG(Para, "para", None,
169  INLINES << Note << Warning << Message << List);
170  SETUP_TAG(List, "list", None, Item);
171  SETUP_TAG(Item, "item", None, INLINES << Note << Warning << Message);
172 
173  SETUP_TAG(Note, "note", Label, INLINES);
174  SETUP_TAG(Warning, "warning", Label, INLINES);
175  SETUP_TAG(Filename, "filename", None, Envar << Placeholder);
176  SETUP_TAG(Link, "link", Url, None);
177  SETUP_TAG(Application, "application", None, None);
178  SETUP_TAG(Command, "command", Section, None);
179  SETUP_TAG(Resource, "resource", None, None);
180  SETUP_TAG(Icode, "icode", None, Envar << Placeholder);
181  SETUP_TAG(Bcode, "bcode", None, None);
182  SETUP_TAG(Shortcut, "shortcut", None, None);
183  SETUP_TAG(Interface, "interface", None, None);
184  SETUP_TAG(Emphasis, "emphasis", Strong, None);
185  SETUP_TAG(Placeholder, "placeholder", None, None);
186  SETUP_TAG(Email, "email", Address, None);
187  SETUP_TAG(Envar, "envar", None, None);
188  SETUP_TAG(Message, "message", None, None);
189  SETUP_TAG(Numid, "numid", None, None);
190  SETUP_TAG(Nl, "nl", None, None);
191  // Internal, not part of DTD.
192  SETUP_TAG(NumIntg, KUIT_NUMINTG, Width << Fill, None);
193  SETUP_TAG(NumReal, KUIT_NUMREAL, Width << Fill, None);
194 
195  // Setup known attribute names.
196  #undef SETUP_ATT
197  #define SETUP_ATT(att, name) do { \
198  knownAtts.insert(QString::fromLatin1(name), Kuit::Att::att); \
199  } while (0)
200  SETUP_ATT(Ctx, "ctx");
201  SETUP_ATT(Url, "url");
202  SETUP_ATT(Address, "address");
203  SETUP_ATT(Section, "section");
204  SETUP_ATT(Label, "label");
205  SETUP_ATT(Strong, "strong");
206  // Internal, not part of DTD.
207  SETUP_ATT(Width, "width");
208  SETUP_ATT(Fill, "fill");
209 
210  // Setup known format names.
211  #undef SETUP_FMT
212  #define SETUP_FMT(fmt, name) do { \
213  knownFmts.insert(QString::fromLatin1(name), Kuit::Fmt::fmt); \
214  } while (0)
215  SETUP_FMT(Plain, "plain");
216  SETUP_FMT(Rich, "rich");
217  SETUP_FMT(Term, "term");
218 
219  // Setup known role names, their default format and subcues.
220  #undef SETUP_ROL
221  #define SETUP_ROL(rol, name, fmt, cues) do { \
222  knownRols.insert(QString::fromLatin1(name), Kuit::Rol::rol); \
223  defFmts[Kuit::Rol::rol][Kuit::Cue::None] = Kuit::Fmt::fmt; \
224  { \
225  using namespace Kuit::Cue; \
226  rolCues[Kuit::Rol::rol] << cues; \
227  } \
228  } while (0)
229  SETUP_ROL(Action, "action", Plain,
230  Button << Inmenu << Intoolbar);
231  SETUP_ROL(Title, "title", Plain,
232  Window << Menu << Tab << Group << Column << Row);
233  SETUP_ROL(Label, "label", Plain,
234  Slider << Spinbox << Listbox << Textbox << Chooser);
235  SETUP_ROL(Option, "option", Plain,
236  Check << Radio);
237  SETUP_ROL(Item, "item", Plain,
238  Inmenu << Inlistbox << Intable << Inrange << Intext);
239  SETUP_ROL(Info, "info", Rich,
240  Tooltip << Whatsthis << Kuit::Cue::Status << Progress
241  << Tipoftheday << Credit << Shell);
242 
243  // Setup override formats by subcue.
244  #undef SETUP_ROLCUEFMT
245  #define SETUP_ROLCUEFMT(rol, cue, fmt) do { \
246  defFmts[Kuit::Rol::rol][Kuit::Cue::cue] = Kuit::Fmt::fmt; \
247  } while (0)
248  SETUP_ROLCUEFMT(Info, Status, Plain);
249  SETUP_ROLCUEFMT(Info, Progress, Plain);
250  SETUP_ROLCUEFMT(Info, Credit, Plain);
251  SETUP_ROLCUEFMT(Info, Shell, Term);
252 
253  // Setup known subcue names.
254  #undef SETUP_CUE
255  #define SETUP_CUE(cue, name) do { \
256  knownCues.insert(QString::fromLatin1(name), Kuit::Cue::cue); \
257  } while (0)
258  SETUP_CUE(Button, "button");
259  SETUP_CUE(Inmenu, "inmenu");
260  SETUP_CUE(Intoolbar, "intoolbar");
261  SETUP_CUE(Window, "window");
262  SETUP_CUE(Menu, "menu");
263  SETUP_CUE(Tab, "tab");
264  SETUP_CUE(Group, "group");
265  SETUP_CUE(Column, "column");
266  SETUP_CUE(Row, "row");
267  SETUP_CUE(Slider, "slider");
268  SETUP_CUE(Spinbox, "spinbox");
269  SETUP_CUE(Listbox, "listbox");
270  SETUP_CUE(Textbox, "textbox");
271  SETUP_CUE(Chooser, "chooser");
272  SETUP_CUE(Check, "check");
273  SETUP_CUE(Radio, "radio");
274  SETUP_CUE(Inlistbox, "inlistbox");
275  SETUP_CUE(Intable, "intable");
276  SETUP_CUE(Inrange, "inrange");
277  SETUP_CUE(Intext, "intext");
278  SETUP_CUE(Tooltip, "tooltip");
279  SETUP_CUE(Whatsthis, "whatsthis");
280  SETUP_CUE(Status, "status");
281  SETUP_CUE(Progress, "progress");
282  SETUP_CUE(Tipoftheday, "tipoftheday");
283  SETUP_CUE(Credit, "credit");
284  SETUP_CUE(Shell, "shell");
285 
286  // Collect all Qt's rich text engine HTML tags, for some checks later.
287  qtHtmlTagNames << QL1S("a") << QL1S("address") << QL1S("b") << QL1S("big") << QL1S("blockquote")
288  << QL1S("body") << QL1S("br") << QL1S("center") << QL1S("cita") << QL1S("code")
289  << QL1S("dd") << QL1S("dfn") << QL1S("div") << QL1S("dl") << QL1S("dt") << QL1S("em")
290  << QL1S("font") << QL1S("h1") << QL1S("h2") << QL1S("h3") << QL1S("h4") << QL1S("h5")
291  << QL1S("h6") << QL1S("head") << QL1S("hr") << QL1S("html") << QL1S("i") << QL1S("img")
292  << QL1S("kbd") << QL1S("meta") << QL1S("li") << QL1S("nobr") << QL1S("ol") << QL1S("p")
293  << QL1S("pre") << QL1S("qt") << QL1S("s") << QL1S("samp") << QL1S("small") << QL1S("span")
294  << QL1S("strong") << QL1S("sup") << QL1S("sub") << QL1S("table") << QL1S("tbody")
295  << QL1S("td") << QL1S("tfoot") << QL1S("th") << QL1S("thead") << QL1S("title")
296  << QL1S("tr") << QL1S("tt") << QL1S("u") << QL1S("ul") << QL1S("var");
297 
298  // Tags that format with number of leading newlines.
299  #undef SETUP_TAG_NL
300  #define SETUP_TAG_NL(tag, nlead) do { \
301  leadingNewlines.insert(Kuit::Tag::tag, nlead); \
302  } while (0)
303  SETUP_TAG_NL(Title, 2);
304  SETUP_TAG_NL(Subtitle, 2);
305  SETUP_TAG_NL(Para, 2);
306  SETUP_TAG_NL(List, 1);
307  SETUP_TAG_NL(Bcode, 1);
308  SETUP_TAG_NL(Item, 1);
309 
310  // Known XML entities, direct/inverse mapping.
311  xmlEntities[QString::fromLatin1("lt")] = QString(QLatin1Char('<'));
312  xmlEntities[QString::fromLatin1("gt")] = QString(QLatin1Char('>'));
313  xmlEntities[QString::fromLatin1("amp")] = QString(QLatin1Char('&'));
314  xmlEntities[QString::fromLatin1("apos")] = QString(QLatin1Char('\''));
315  xmlEntities[QString::fromLatin1("quot")] = QString(QLatin1Char('"'));
316  xmlEntitiesInverse[QString(QLatin1Char('<'))] = QString::fromLatin1("lt");
317  xmlEntitiesInverse[QString(QLatin1Char('>'))] = QString::fromLatin1("gt");
318  xmlEntitiesInverse[QString(QLatin1Char('&'))] = QString::fromLatin1("amp");
319  xmlEntitiesInverse[QString(QLatin1Char('\''))] = QString::fromLatin1("apos");
320  xmlEntitiesInverse[QString(QLatin1Char('"'))] = QString::fromLatin1("quot");
321 }
322 
323 K_GLOBAL_STATIC(KuitSemanticsStaticData, semanticsStaticData)
324 
325 
326 // -----------------------------------------------------------------------------
327 // The KuitSemanticsPrivate methods, they do the work.
328 
329 class KuitSemanticsPrivate
330 {
331  public:
332 
333  KuitSemanticsPrivate (const QString &lang_);
334 
335  QString format (const QString &text, const QString &ctxt) const;
336 
337  // Get metatranslation (formatting patterns, etc.)
338  QString metaTr (const char *ctxt, const char *id) const;
339 
340  // Set visual formatting patterns for text in semantic tags.
341  void setFormattingPatterns ();
342 
343  // Set data used in transformation of text within semantic tags.
344  void setTextTransformData ();
345 
346  // Compute integer hash key from the set of attributes.
347  static int attSetKey (const QSet<Kuit::AttVar> &aset = QSet<Kuit::AttVar>());
348 
349  // Determine visual format by parsing the context marker.
350  static Kuit::FmtVar formatFromContextMarker (const QString &ctxmark,
351  const QString &text);
352  // Determine visual format by parsing tags.
353  static Kuit::FmtVar formatFromTags (const QString &text);
354 
355  // Apply appropriate top tag is to the text.
356  static QString equipTopTag (const QString &text, Kuit::TagVar &toptag);
357 
358  // Formats the semantic into visual text.
359  QString semanticToVisualText (const QString &text,
360  Kuit::FmtVar fmtExp,
361  Kuit::FmtVar fmtImp) const;
362 
363  // Final touches to the formatted text.
364  QString finalizeVisualText (const QString &final,
365  Kuit::FmtVar fmt,
366  bool hadQtTag = false,
367  bool hadAnyHtmlTag = false) const;
368 
369  // In case of markup errors, try to make result not look too bad.
370  QString salvageMarkup (const QString &text, Kuit::FmtVar fmt) const;
371 
372  // Data for XML parsing state.
373  class OpenEl
374  {
375  public:
376 
377  typedef enum { Proper, Ignored, Dropout } Handling;
378 
379  Kuit::TagVar tag;
380  QString name;
381  QHash<Kuit::AttVar, QString> avals;
382  int akey;
383  QString astr;
384  Handling handling;
385  QString formattedText;
386  };
387 
388  // Gather data about current element for the parse state.
389  KuitSemanticsPrivate::OpenEl parseOpenEl (const QXmlStreamReader &xml,
390  Kuit::TagVar etag,
391  const QString &text) const;
392 
393  // Select visual pattern for given tag+attributes+format combination.
394  QString visualPattern (Kuit::TagVar tag, int akey, Kuit::FmtVar fmt) const;
395 
396  // Format text of the element.
397  QString formatSubText (const QString &ptext, const OpenEl &oel,
398  Kuit::FmtVar fmt, int numctx) const;
399 
400  // Count number of newlines at start and at end of text.
401  static void countWrappingNewlines (const QString &ptext,
402  int &numle, int &numtr);
403 
404  // Modifies text for some tags.
405  QString modifyTagText (const QString &text, Kuit::TagVar tag,
406  const QHash<Kuit::AttVar, QString> &avals,
407  int numctx, Kuit::FmtVar fmt) const;
408 
409  private:
410 
411  QString m_lang;
412 
413  QHash<Kuit::TagVar,
414  QHash<int, // attribute set key
415  QHash<Kuit::FmtVar, QString> > > m_patterns;
416 
417  QHash<Kuit::FmtVar, QString> m_comboKeyDelim;
418  QHash<Kuit::FmtVar, QString> m_guiPathDelim;
419 
420  QHash<QString, QString> m_keyNames;
421 
422  // For fetching metatranslations.
423  KCatalog *m_metaCat;
424 };
425 
426 KuitSemanticsPrivate::KuitSemanticsPrivate (const QString &lang)
427 : m_metaCat(NULL)
428 {
429  m_lang = lang;
430 
431  // NOTE: This function draws translation from raw message catalogs
432  // because full i18n system is not available at this point (this
433  // function is called within the initialization of the i18n system),
434  // Also, pattern/transformation strings are "metastrings", not
435  // fully proper i18n strings on their own.
436 
437  m_metaCat = new KCatalog(QString::fromLatin1("kdelibs4"), lang);
438 
439  // Get formatting patterns for all tag/att/fmt combinations.
440  setFormattingPatterns();
441 
442  // Get data for tag text transformations.
443  setTextTransformData();
444 
445  // Catalog not needed any more.
446  delete m_metaCat;
447  m_metaCat = NULL;
448 }
449 
450 QString KuitSemanticsPrivate::metaTr (const char *ctxt, const char *id) const
451 {
452  if (m_metaCat == NULL) {
453  return QString::fromLatin1(id);
454  }
455  return m_metaCat->translate(ctxt, id);
456 }
457 
458 void KuitSemanticsPrivate::setFormattingPatterns ()
459 {
460  using namespace Kuit;
461 
462  // Macro to expedite setting the patterns.
463  #undef SET_PATTERN
464  #define SET_PATTERN(tag, atts, fmt, ctxt_ptrn) do { \
465  QSet<AttVar> aset; \
466  aset << atts; \
467  int akey = attSetKey(aset); \
468  QString pattern = metaTr(ctxt_ptrn); \
469  m_patterns[tag][akey][fmt] = pattern; \
470  /* Make Term pattern same as Plain, unless explicitly given. */ \
471  if (fmt == Fmt::Plain && !m_patterns[tag][akey].contains(Fmt::Term)) { \
472  m_patterns[tag][akey][Fmt::Term] = pattern; \
473  } \
474  } while (0)
475 
476  // Normal I18N_NOOP2 removes context, but below we need both.
477  #undef I18N_NOOP2
478  #define I18N_NOOP2(ctxt, msg) ctxt, msg
479 
480  // Some of the formatting patterns are intentionally not exposed for
481  // localization.
482  #undef XXXX_NOOP2
483  #define XXXX_NOOP2(ctxt, msg) ctxt, msg
484 
485  // NOTE: The following "i18n:" comments are oddly placed in order that
486  // xgettext extracts them properly.
487 
488  // -------> Title
489  SET_PATTERN(Tag::Title, Att::None, Fmt::Plain,
490  I18N_NOOP2("@title/plain",
491  // i18n: The following messages, with msgctxt "@tag/modifier",
492  // are KUIT patterns for formatting the text found inside semantic tags.
493  // For review of the KUIT semantic markup, see the article on Techbase:
494  // http://techbase.kde.org/Development/Tutorials/Localization/i18n_Semantics
495  // The "/modifier" tells if the pattern is used for plain text, or rich text
496  // which can use HTML tags.
497  // You may be in general satisfied with the patterns as they are in the
498  // original. Some things you may think about changing:
499  // - the proper quotes, those used in msgid are English-standard
500  // - the <i> and <b> tags, does your language script work well with them?
501  "== %1 =="));
502  SET_PATTERN(Tag::Title, Att::None, Fmt::Rich,
503  I18N_NOOP2("@title/rich",
504  // i18n: KUIT pattern, see the comment to the first of these entries above.
505  "<h2>%1</h2>"));
506 
507  // -------> Subtitle
508  SET_PATTERN(Tag::Subtitle, Att::None, Fmt::Plain,
509  I18N_NOOP2("@subtitle/plain",
510  // i18n: KUIT pattern, see the comment to the first of these entries above.
511  "~ %1 ~"));
512  SET_PATTERN(Tag::Subtitle, Att::None, Fmt::Rich,
513  I18N_NOOP2("@subtitle/rich",
514  // i18n: KUIT pattern, see the comment to the first of these entries above.
515  "<h3>%1</h3>"));
516 
517  // -------> Para
518  SET_PATTERN(Tag::Para, Att::None, Fmt::Plain,
519  XXXX_NOOP2("@para/plain",
520  // i18n: KUIT pattern, see the comment to the first of these entries above.
521  "%1"));
522  SET_PATTERN(Tag::Para, Att::None, Fmt::Rich,
523  XXXX_NOOP2("@para/rich",
524  // i18n: KUIT pattern, see the comment to the first of these entries above.
525  "<p>%1</p>"));
526 
527  // -------> List
528  SET_PATTERN(Tag::List, Att::None, Fmt::Plain,
529  XXXX_NOOP2("@list/plain",
530  // i18n: KUIT pattern, see the comment to the first of these entries above.
531  "%1"));
532  SET_PATTERN(Tag::List, Att::None, Fmt::Rich,
533  XXXX_NOOP2("@list/rich",
534  // i18n: KUIT pattern, see the comment to the first of these entries above.
535  "<ul>%1</ul>"));
536 
537  // -------> Item
538  SET_PATTERN(Tag::Item, Att::None, Fmt::Plain,
539  I18N_NOOP2("@item/plain",
540  // i18n: KUIT pattern, see the comment to the first of these entries above.
541  " * %1"));
542  SET_PATTERN(Tag::Item, Att::None, Fmt::Rich,
543  I18N_NOOP2("@item/rich",
544  // i18n: KUIT pattern, see the comment to the first of these entries above.
545  "<li>%1</li>"));
546 
547  // -------> Note
548  SET_PATTERN(Tag::Note, Att::None, Fmt::Plain,
549  I18N_NOOP2("@note/plain",
550  // i18n: KUIT pattern, see the comment to the first of these entries above.
551  "Note: %1"));
552  SET_PATTERN(Tag::Note, Att::None, Fmt::Rich,
553  I18N_NOOP2("@note/rich",
554  // i18n: KUIT pattern, see the comment to the first of these entries above.
555  "<i>Note</i>: %1"));
556  SET_PATTERN(Tag::Note, Att::Label, Fmt::Plain,
557  I18N_NOOP2("@note-with-label/plain\n"
558  "%1 is the note label, %2 is the text",
559  // i18n: KUIT pattern, see the comment to the first of these entries above.
560  "%1: %2"));
561  SET_PATTERN(Tag::Note, Att::Label, Fmt::Rich,
562  I18N_NOOP2("@note-with-label/rich\n"
563  "%1 is the note label, %2 is the text",
564  // i18n: KUIT pattern, see the comment to the first of these entries above.
565  "<i>%1</i>: %2"));
566 
567  // -------> Warning
568  SET_PATTERN(Tag::Warning, Att::None, Fmt::Plain,
569  I18N_NOOP2("@warning/plain",
570  // i18n: KUIT pattern, see the comment to the first of these entries above.
571  "WARNING: %1"));
572  SET_PATTERN(Tag::Warning, Att::None, Fmt::Rich,
573  I18N_NOOP2("@warning/rich",
574  // i18n: KUIT pattern, see the comment to the first of these entries above.
575  "<b>Warning</b>: %1"));
576  SET_PATTERN(Tag::Warning, Att::Label, Fmt::Plain,
577  I18N_NOOP2("@warning-with-label/plain\n"
578  "%1 is the warning label, %2 is the text",
579  // i18n: KUIT pattern, see the comment to the first of these entries above.
580  "%1: %2"));
581  SET_PATTERN(Tag::Warning, Att::Label, Fmt::Rich,
582  I18N_NOOP2("@warning-with-label/rich\n"
583  "%1 is the warning label, %2 is the text",
584  // i18n: KUIT pattern, see the comment to the first of these entries above.
585  "<b>%1</b>: %2"));
586 
587  // -------> Link
588  SET_PATTERN(Tag::Link, Att::None, Fmt::Plain,
589  XXXX_NOOP2("@link/plain",
590  // i18n: KUIT pattern, see the comment to the first of these entries above.
591  "%1"));
592  SET_PATTERN(Tag::Link, Att::None, Fmt::Rich,
593  XXXX_NOOP2("@link/rich",
594  // i18n: KUIT pattern, see the comment to the first of these entries above.
595  "<a href=\"%1\">%1</a>"));
596  SET_PATTERN(Tag::Link, Att::Url, Fmt::Plain,
597  I18N_NOOP2("@link-with-description/plain\n"
598  "%1 is the URL, %2 is the descriptive text",
599  // i18n: KUIT pattern, see the comment to the first of these entries above.
600  "%2 (%1)"));
601  SET_PATTERN(Tag::Link, Att::Url, Fmt::Rich,
602  I18N_NOOP2("@link-with-description/rich\n"
603  "%1 is the URL, %2 is the descriptive text",
604  // i18n: KUIT pattern, see the comment to the first of these entries above.
605  "<a href=\"%1\">%2</a>"));
606 
607  // -------> Filename
608  SET_PATTERN(Tag::Filename, Att::None, Fmt::Plain,
609  I18N_NOOP2("@filename/plain",
610  // i18n: KUIT pattern, see the comment to the first of these entries above.
611  "‘%1’"));
612  SET_PATTERN(Tag::Filename, Att::None, Fmt::Rich,
613  I18N_NOOP2("@filename/rich",
614  // i18n: KUIT pattern, see the comment to the first of these entries above.
615  "<tt>%1</tt>"));
616 
617  // -------> Application
618  SET_PATTERN(Tag::Application, Att::None, Fmt::Plain,
619  I18N_NOOP2("@application/plain",
620  // i18n: KUIT pattern, see the comment to the first of these entries above.
621  "%1"));
622  SET_PATTERN(Tag::Application, Att::None, Fmt::Rich,
623  I18N_NOOP2("@application/rich",
624  // i18n: KUIT pattern, see the comment to the first of these entries above.
625  "%1"));
626 
627  // -------> Command
628  SET_PATTERN(Tag::Command, Att::None, Fmt::Plain,
629  I18N_NOOP2("@command/plain",
630  // i18n: KUIT pattern, see the comment to the first of these entries above.
631  "%1"));
632  SET_PATTERN(Tag::Command, Att::None, Fmt::Rich,
633  I18N_NOOP2("@command/rich",
634  // i18n: KUIT pattern, see the comment to the first of these entries above.
635  "<tt>%1</tt>"));
636  SET_PATTERN(Tag::Command, Att::Section, Fmt::Plain,
637  I18N_NOOP2("@command-with-section/plain\n"
638  "%1 is the command name, %2 is its man section",
639  // i18n: KUIT pattern, see the comment to the first of these entries above.
640  "%1(%2)"));
641  SET_PATTERN(Tag::Command, Att::Section, Fmt::Rich,
642  I18N_NOOP2("@command-with-section/rich\n"
643  "%1 is the command name, %2 is its man section",
644  // i18n: KUIT pattern, see the comment to the first of these entries above.
645  "<tt>%1(%2)</tt>"));
646 
647  // -------> Resource
648  SET_PATTERN(Tag::Resource, Att::None, Fmt::Plain,
649  I18N_NOOP2("@resource/plain",
650  // i18n: KUIT pattern, see the comment to the first of these entries above.
651  "“%1”"));
652  SET_PATTERN(Tag::Resource, Att::None, Fmt::Rich,
653  I18N_NOOP2("@resource/rich",
654  // i18n: KUIT pattern, see the comment to the first of these entries above.
655  "“%1”"));
656 
657  // -------> Icode
658  SET_PATTERN(Tag::Icode, Att::None, Fmt::Plain,
659  I18N_NOOP2("@icode/plain",
660  // i18n: KUIT pattern, see the comment to the first of these entries above.
661  "“%1”"));
662  SET_PATTERN(Tag::Icode, Att::None, Fmt::Rich,
663  I18N_NOOP2("@icode/rich",
664  // i18n: KUIT pattern, see the comment to the first of these entries above.
665  "<tt>%1</tt>"));
666 
667  // -------> Bcode
668  SET_PATTERN(Tag::Bcode, Att::None, Fmt::Plain,
669  XXXX_NOOP2("@bcode/plain",
670  // i18n: KUIT pattern, see the comment to the first of these entries above.
671  "\n%1\n"));
672  SET_PATTERN(Tag::Bcode, Att::None, Fmt::Rich,
673  XXXX_NOOP2("@bcode/rich",
674  // i18n: KUIT pattern, see the comment to the first of these entries above.
675  "<pre>%1</pre>"));
676 
677  // -------> Shortcut
678  SET_PATTERN(Tag::Shortcut, Att::None, Fmt::Plain,
679  I18N_NOOP2("@shortcut/plain",
680  // i18n: KUIT pattern, see the comment to the first of these entries above.
681  "%1"));
682  SET_PATTERN(Tag::Shortcut, Att::None, Fmt::Rich,
683  I18N_NOOP2("@shortcut/rich",
684  // i18n: KUIT pattern, see the comment to the first of these entries above.
685  "<b>%1</b>"));
686 
687  // -------> Interface
688  SET_PATTERN(Tag::Interface, Att::None, Fmt::Plain,
689  I18N_NOOP2("@interface/plain",
690  // i18n: KUIT pattern, see the comment to the first of these entries above.
691  "|%1|"));
692  SET_PATTERN(Tag::Interface, Att::None, Fmt::Rich,
693  I18N_NOOP2("@interface/rich",
694  // i18n: KUIT pattern, see the comment to the first of these entries above.
695  "<i>%1</i>"));
696 
697  // -------> Emphasis
698  SET_PATTERN(Tag::Emphasis, Att::None, Fmt::Plain,
699  I18N_NOOP2("@emphasis/plain",
700  // i18n: KUIT pattern, see the comment to the first of these entries above.
701  "*%1*"));
702  SET_PATTERN(Tag::Emphasis, Att::None, Fmt::Rich,
703  I18N_NOOP2("@emphasis/rich",
704  // i18n: KUIT pattern, see the comment to the first of these entries above.
705  "<i>%1</i>"));
706  SET_PATTERN(Tag::Emphasis, Att::Strong, Fmt::Plain,
707  I18N_NOOP2("@emphasis-strong/plain",
708  // i18n: KUIT pattern, see the comment to the first of these entries above.
709  "**%1**"));
710  SET_PATTERN(Tag::Emphasis, Att::Strong, Fmt::Rich,
711  I18N_NOOP2("@emphasis-strong/rich",
712  // i18n: KUIT pattern, see the comment to the first of these entries above.
713  "<b>%1</b>"));
714 
715  // -------> Placeholder
716  SET_PATTERN(Tag::Placeholder, Att::None, Fmt::Plain,
717  I18N_NOOP2("@placeholder/plain",
718  // i18n: KUIT pattern, see the comment to the first of these entries above.
719  "&lt;%1&gt;"));
720  SET_PATTERN(Tag::Placeholder, Att::None, Fmt::Rich,
721  I18N_NOOP2("@placeholder/rich",
722  // i18n: KUIT pattern, see the comment to the first of these entries above.
723  "&lt;<i>%1</i>&gt;"));
724 
725  // -------> Email
726  SET_PATTERN(Tag::Email, Att::None, Fmt::Plain,
727  I18N_NOOP2("@email/plain",
728  // i18n: KUIT pattern, see the comment to the first of these entries above.
729  "&lt;%1&gt;"));
730  SET_PATTERN(Tag::Email, Att::None, Fmt::Rich,
731  I18N_NOOP2("@email/rich",
732  // i18n: KUIT pattern, see the comment to the first of these entries above.
733  "&lt;<a href=\"mailto:%1\">%1</a>&gt;"));
734  SET_PATTERN(Tag::Email, Att::Address, Fmt::Plain,
735  I18N_NOOP2("@email-with-name/plain\n"
736  "%1 is name, %2 is address",
737  // i18n: KUIT pattern, see the comment to the first of these entries above.
738  "%1 &lt;%2&gt;"));
739  SET_PATTERN(Tag::Email, Att::Address, Fmt::Rich,
740  I18N_NOOP2("@email-with-name/rich\n"
741  "%1 is name, %2 is address",
742  // i18n: KUIT pattern, see the comment to the first of these entries above.
743  "<a href=\"mailto:%2\">%1</a>"));
744 
745  // -------> Envar
746  SET_PATTERN(Tag::Envar, Att::None, Fmt::Plain,
747  I18N_NOOP2("@envar/plain",
748  // i18n: KUIT pattern, see the comment to the first of these entries above.
749  "$%1"));
750  SET_PATTERN(Tag::Envar, Att::None, Fmt::Rich,
751  I18N_NOOP2("@envar/rich",
752  // i18n: KUIT pattern, see the comment to the first of these entries above.
753  "<tt>$%1</tt>"));
754 
755  // -------> Message
756  SET_PATTERN(Tag::Message, Att::None, Fmt::Plain,
757  I18N_NOOP2("@message/plain",
758  // i18n: KUIT pattern, see the comment to the first of these entries above.
759  "/%1/"));
760  SET_PATTERN(Tag::Message, Att::None, Fmt::Rich,
761  I18N_NOOP2("@message/rich",
762  // i18n: KUIT pattern, see the comment to the first of these entries above.
763  "<i>%1</i>"));
764 
765  // -------> Nl
766  SET_PATTERN(Tag::Nl, Att::None, Fmt::Plain,
767  XXXX_NOOP2("@nl/plain",
768  // i18n: KUIT pattern, see the comment to the first of these entries above.
769  "%1\n"));
770  SET_PATTERN(Tag::Nl, Att::None, Fmt::Rich,
771  XXXX_NOOP2("@nl/rich",
772  // i18n: KUIT pattern, see the comment to the first of these entries above.
773  "%1<br/>"));
774 }
775 
776 void KuitSemanticsPrivate::setTextTransformData ()
777 {
778  // Mask metaTr with I18N_NOOP2 to have stuff extracted.
779  #undef I18N_NOOP2
780  #define I18N_NOOP2(ctxt, msg) metaTr(ctxt, msg)
781 
782  // i18n: Decide which string is used to delimit keys in a keyboard
783  // shortcut (e.g. + in Ctrl+Alt+Tab) in plain text.
784  m_comboKeyDelim[Kuit::Fmt::Plain] = I18N_NOOP2("shortcut-key-delimiter/plain", "+");
785  m_comboKeyDelim[Kuit::Fmt::Term] = m_comboKeyDelim[Kuit::Fmt::Plain];
786  // i18n: Decide which string is used to delimit keys in a keyboard
787  // shortcut (e.g. + in Ctrl+Alt+Tab) in rich text.
788  m_comboKeyDelim[Kuit::Fmt::Rich] = I18N_NOOP2("shortcut-key-delimiter/rich", "+");
789 
790  // i18n: Decide which string is used to delimit elements in a GUI path
791  // (e.g. -> in "Go to Settings->Advanced->Core tab.") in plain text.
792  m_guiPathDelim[Kuit::Fmt::Plain] = I18N_NOOP2("gui-path-delimiter/plain", "→");
793  m_guiPathDelim[Kuit::Fmt::Term] = m_guiPathDelim[Kuit::Fmt::Plain];
794  // i18n: Decide which string is used to delimit elements in a GUI path
795  // (e.g. -> in "Go to Settings->Advanced->Core tab.") in rich text.
796  m_guiPathDelim[Kuit::Fmt::Rich] = I18N_NOOP2("gui-path-delimiter/rich", "→");
797  // NOTE: The '→' glyph seems to be available in all widespread fonts.
798 
799  // Collect keyboard key names.
800  #undef SET_KEYNAME
801  #define SET_KEYNAME(rawname) do { \
802  /* Normalize key, trim and all lower-case. */ \
803  QString normname = QString::fromLatin1(rawname).trimmed().toLower(); \
804  m_keyNames[normname] = metaTr("keyboard-key-name", rawname); \
805  } while (0)
806 
807  // Now we need I18N_NOOP2 that does remove context.
808  #undef I18N_NOOP2
809  #define I18N_NOOP2(ctxt, msg) msg
810 
811  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Alt"));
812  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "AltGr"));
813  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Backspace"));
814  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "CapsLock"));
815  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Control"));
816  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Ctrl"));
817  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Del"));
818  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Delete"));
819  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Down"));
820  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "End"));
821  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Enter"));
822  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Esc"));
823  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Escape"));
824  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Home"));
825  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Hyper"));
826  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Ins"));
827  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Insert"));
828  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Left"));
829  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Menu"));
830  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Meta"));
831  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "NumLock"));
832  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PageDown"));
833  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PageUp"));
834  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PgDown"));
835  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PgUp"));
836  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PauseBreak"));
837  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PrintScreen"));
838  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "PrtScr"));
839  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Return"));
840  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Right"));
841  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "ScrollLock"));
842  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Shift"));
843  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Space"));
844  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Super"));
845  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "SysReq"));
846  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Tab"));
847  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Up"));
848  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "Win"));
849  // TODO: Add rest of the key names?
850 
851  // i18n: Pattern for the function keys.
852  SET_KEYNAME(I18N_NOOP2("keyboard-key-name", "F%1"));
853 }
854 
855 QString KuitSemanticsPrivate::format (const QString &text,
856  const QString &ctxt) const
857 {
858  // Parse context marker to determine format.
859  Kuit::FmtVar fmtExplicit = formatFromContextMarker(ctxt, text);
860 
861  // Quick check: are there any tags at all?
862  if (text.indexOf(QLatin1Char('<')) < 0) {
863  return finalizeVisualText(text, fmtExplicit);
864  }
865 
866  // If format not explicitly given, heuristically determine
867  // implicit format based on presence or lack of HTML tags.
868  Kuit::FmtVar fmtImplicit = fmtExplicit;
869  if (fmtExplicit == Kuit::Fmt::None) {
870  fmtImplicit = formatFromTags(text);
871  }
872 
873  // Decide on the top tag, either TopLong or TopShort,
874  // and wrap the text with it.
875  Kuit::TagVar toptag;
876  QString wtext = equipTopTag(text, toptag);
877 
878  // Format the text.
879  QString ftext = semanticToVisualText(wtext, fmtExplicit, fmtImplicit);
880  if (ftext.isEmpty()) { // error while processing markup
881  return salvageMarkup(text, fmtImplicit);
882  }
883 
884  return ftext;
885 }
886 
887 int KuitSemanticsPrivate::attSetKey (const QSet<Kuit::AttVar> &aset)
888 {
889  QList<Kuit::AttVar> alist = aset.toList();
890  qSort(alist);
891  int key = 0;
892  int tenp = 1;
893  foreach (const Kuit::AttVar &att, alist) {
894  key += att * tenp;
895  tenp *= 10;
896  }
897  return key;
898 }
899 
900 Kuit::FmtVar KuitSemanticsPrivate::formatFromContextMarker (
901  const QString &ctxmark_, const QString &text)
902 {
903  #ifdef NDEBUG
904  Q_UNUSED(text);
905  #endif
906 
907  KuitSemanticsStaticData *s = semanticsStaticData;
908 
909  // Semantic context marker is in the form @rolname:cuename/fmtname,
910  // and must start just after any leading whitespace in the context string.
911  QString rolname;
912  QString fmtname;
913  QString cuename;
914  QString ctxmark = ctxmark_.trimmed();
915  if (ctxmark.startsWith(QLatin1Char('@'))) { // found context marker
916  static QRegExp wsRx(QString::fromLatin1("\\s"));
917  ctxmark = ctxmark.mid(1, wsRx.indexIn(ctxmark) - 1);
918 
919  // Possible visual format.
920  int pfmt = ctxmark.indexOf(QLatin1Char('/'));
921  if (pfmt >= 0) {
922  fmtname = ctxmark.mid(pfmt + 1);
923  ctxmark = ctxmark.left(pfmt);
924  }
925 
926  // Possible interface subcue.
927  int pcue = ctxmark.indexOf(QLatin1Char(':'));
928  if (pcue >= 0) {
929  cuename = ctxmark.mid(pcue + 1);
930  ctxmark = ctxmark.left(pcue);
931  }
932 
933  // Semantic role.
934  rolname = ctxmark;
935  }
936  // Names remain empty if marker was not found, which is ok.
937 
938  // Normalize names.
939  rolname = rolname.trimmed().toLower();
940  cuename = cuename.trimmed().toLower();
941  fmtname = fmtname.trimmed().toLower();
942 
943  // Set role from name.
944  Kuit::RolVar rol;
945  if (s->knownRols.contains(rolname)) { // known role
946  rol = s->knownRols[rolname];
947  }
948  else { // unknown role
949  rol = Kuit::Rol::None;
950  if (!rolname.isEmpty()) {
951  kDebug(173) << QString::fromLatin1("Unknown semantic role '@%1' in "
952  "context marker for message {%2}.")
953  .arg(rolname, shorten(text));
954  }
955  }
956 
957  // Set subcue from name.
958  Kuit::CueVar cue;
959  if (s->knownCues.contains(cuename)) { // known subcue
960  cue = s->knownCues[cuename];
961  }
962  else { // unknown or not given subcue
963  cue = Kuit::Cue::None;
964  if (!cuename.isEmpty()) {
965  kDebug(173) << QString::fromLatin1("Unknown interface subcue ':%1' in "
966  "context marker for message {%2}.")
967  .arg(cuename, shorten(text));
968  }
969  }
970 
971  // Set format from name, or by derivation from contex/subcue.
972  Kuit::FmtVar fmt;
973  if (s->knownFmts.contains(fmtname)) { // known format
974  fmt = s->knownFmts[fmtname];
975  }
976  else { // unknown or not given format
977 
978  // Check first if there is a format defined for role/subcue
979  // combination, than for role only, then default to none.
980  if (s->defFmts.contains(rol)) {
981  if (s->defFmts[rol].contains(cue)) {
982  fmt = s->defFmts[rol][cue];
983  }
984  else {
985  fmt = s->defFmts[rol][Kuit::Cue::None];
986  }
987  }
988  else {
989  fmt = Kuit::Fmt::None;
990  }
991 
992  if (!fmtname.isEmpty()) {
993  kDebug(173) << QString::fromLatin1("Unknown visual format '/%1' in "
994  "context marker for message {%2}.")
995  .arg(fmtname, shorten(text));
996  }
997  }
998 
999  return fmt;
1000 }
1001 
1002 Kuit::FmtVar KuitSemanticsPrivate::formatFromTags (const QString &text)
1003 {
1004  KuitSemanticsStaticData *s = semanticsStaticData;
1005  static QRegExp staticTagRx(QString::fromLatin1("<\\s*(\\w+)[^>]*>"));
1006 
1007  QRegExp tagRx = staticTagRx; // for thread-safety
1008  int p = tagRx.indexIn(text);
1009  while (p >= 0) {
1010  QString tagname = tagRx.capturedTexts().at(1).toLower();
1011  if (s->qtHtmlTagNames.contains(tagname)) {
1012  return Kuit::Fmt::Rich;
1013  }
1014  p = tagRx.indexIn(text, p + tagRx.matchedLength());
1015  }
1016  return Kuit::Fmt::Plain;
1017 }
1018 
1019 QString KuitSemanticsPrivate::equipTopTag (const QString &text_,
1020  Kuit::TagVar &toptag)
1021 {
1022  KuitSemanticsStaticData *s = semanticsStaticData;
1023 
1024  // Unless the text opens either with TopLong or TopShort tags,
1025  // make a guess: if it opens with one of Title, Subtitle, Para,
1026  // consider it TopLong, otherwise TopShort.
1027  static QRegExp opensWithTagRx(QString::fromLatin1("^\\s*<\\s*(\\w+)[^>]*>"));
1028  bool explicitTopTag = false;
1029 
1030  QString text = text_;
1031  int p = opensWithTagRx.indexIn(text);
1032 
1033  // <qt> or <html> tag are to be ignored for deciding the top tag.
1034  if (p >= 0) {
1035  QString fullmatch = opensWithTagRx.capturedTexts().at(0);
1036  QString tagname = opensWithTagRx.capturedTexts().at(1).toLower();
1037  if (tagname == QLatin1String("qt") || tagname == QLatin1String("html")) {
1038  // Kill the tag and see if there is another one following,
1039  // for primary check below.
1040  text = text.mid(fullmatch.length());
1041  p = opensWithTagRx.indexIn(text);
1042  }
1043  }
1044 
1045  // Check the first non-<qt>/<html> tag.
1046  if (p >= 0) { // opens with a tag
1047  QString tagname = opensWithTagRx.capturedTexts().at(1).toLower();
1048  if (s->knownTags.contains(tagname)) { // a known tag
1049  Kuit::TagVar tag = s->knownTags[tagname];
1050  if ( tag == Kuit::Tag::TopLong
1051  || tag == Kuit::Tag::TopShort) { // explicitly given top tag
1052  toptag = tag;
1053  explicitTopTag = true;
1054  }
1055  else if ( tag == Kuit::Tag::Para
1056  || tag == Kuit::Tag::Title
1057  || tag == Kuit::Tag::Subtitle) { // one of long text tags
1058  toptag = Kuit::Tag::TopLong;
1059  }
1060  else { // not one of long text tags
1061  toptag = Kuit::Tag::TopShort;
1062  }
1063  }
1064  else { // not a KUIT tag
1065  toptag = Kuit::Tag::TopShort;
1066  }
1067  }
1068  else { // doesn't open with a tag
1069  toptag = Kuit::Tag::TopShort;
1070  }
1071 
1072  // Wrap text with top tag if not explicitly given.
1073  if (!explicitTopTag) {
1074  return QLatin1Char('<') + s->tagNames[toptag] + QLatin1Char('>')
1075  + text_ // original text, not the one possibly stripped above
1076  + QLatin1String("</") + s->tagNames[toptag] + QLatin1Char('>');
1077  }
1078  else {
1079  return text;
1080  }
1081 }
1082 
1083 #define ENTITY_SUBRX "[a-z]+|#[0-9]+|#x[0-9a-fA-F]+"
1084 
1085 QString KuitSemanticsPrivate::semanticToVisualText (const QString &text_,
1086  Kuit::FmtVar fmtExp_,
1087  Kuit::FmtVar fmtImp_) const
1088 {
1089  KuitSemanticsStaticData *s = semanticsStaticData;
1090 
1091  // Replace &-shortcut marker with "&amp;", not to confuse the parser;
1092  // but do not touch & which forms an XML entity as it is.
1093  QString original = text_;
1094  QString text;
1095  int p = original.indexOf(QLatin1Char('&'));
1096  while (p >= 0) {
1097  text.append(original.mid(0, p + 1));
1098  original.remove(0, p + 1);
1099  static QRegExp restRx(QString::fromLatin1("^("ENTITY_SUBRX");"));
1100  if (original.indexOf(restRx) != 0) { // not an entity
1101  text.append(QLatin1String("amp;"));
1102  }
1103  p = original.indexOf(QLatin1Char('&'));
1104  }
1105  text.append(original);
1106 
1107  Kuit::FmtVar fmtExp = fmtExp_;
1108  Kuit::FmtVar fmtImp = fmtImp_;
1109  int numCtx = 0;
1110  bool hadQtTag = false;
1111  bool hadAnyHtmlTag = false;
1112  QStack<OpenEl> openEls;
1113  QXmlStreamReader xml(text);
1114  QStringRef lastElementName;
1115 
1116  while (!xml.atEnd()) {
1117  xml.readNext();
1118 
1119  if (xml.isStartElement()) {
1120  lastElementName = xml.name();
1121 
1122  // Find first proper enclosing element tag.
1123  Kuit::TagVar etag = Kuit::Tag::None;
1124  for (int i = openEls.size() - 1; i >= 0; --i) {
1125  if (openEls[i].handling == OpenEl::Proper) {
1126  etag = openEls[i].tag;
1127  break;
1128  }
1129  }
1130 
1131  // Collect data about this element.
1132  OpenEl oel = parseOpenEl(xml, etag, text);
1133  if (oel.name == QLatin1String("qt") || oel.name == QLatin1String("html")) {
1134  hadQtTag = true;
1135  }
1136  if (s->qtHtmlTagNames.contains(oel.name)) {
1137  hadAnyHtmlTag = true;
1138  }
1139 
1140  // If this is top tag, check if it overrides the context marker
1141  // by its ctx attribute.
1142  if (openEls.isEmpty() && oel.avals.contains(Kuit::Att::Ctx)) {
1143  // Resolve format override.
1144  fmtExp = formatFromContextMarker(oel.avals[Kuit::Att::Ctx], text);
1145  fmtImp = fmtExp;
1146  }
1147 
1148  // Record the new element on the parse stack.
1149  openEls.push(oel);
1150 
1151  // Update numeric context.
1152  if (oel.tag == Kuit::Tag::Numid) {
1153  ++numCtx;
1154  }
1155  }
1156  else if (xml.isEndElement()) {
1157  // Get closed element data.
1158  OpenEl oel = openEls.pop();
1159 
1160  // If this was closing of the top element, we're done.
1161  if (openEls.isEmpty()) {
1162  // Return with final touches applied.
1163  return finalizeVisualText(oel.formattedText, fmtExp,
1164  hadQtTag, hadAnyHtmlTag);
1165  }
1166 
1167  // Append formatted text segment.
1168  QString pt = openEls.top().formattedText; // preceding text
1169  openEls.top().formattedText += formatSubText(pt, oel, fmtImp, numCtx);
1170 
1171  // Update numeric context.
1172  if (oel.tag == Kuit::Tag::Numid) {
1173  --numCtx;
1174  }
1175  }
1176  else if (xml.isCharacters()) {
1177  // Stream reader will automatically resolve default XML entities,
1178  // which is not desired in this case, as the final text may
1179  // be rich. Convert them back into entities.
1180  QString text = xml.text().toString();
1181  QString ntext;
1182  foreach (const QChar &c, text) {
1183  if (s->xmlEntitiesInverse.contains(c)) {
1184  const QString entname = s->xmlEntitiesInverse[c];
1185  ntext += QLatin1Char('&') + entname + QLatin1Char(';');
1186  } else {
1187  ntext += c;
1188  }
1189  }
1190  openEls.top().formattedText += ntext;
1191  }
1192  }
1193 
1194  if (xml.hasError()) {
1195  kDebug(173) << QString::fromLatin1("Markup error in message {%1}: %2. Last tag parsed: %3")
1196  .arg(shorten(text), xml.errorString(), lastElementName.toString());
1197  return QString();
1198  }
1199 
1200  // Cannot reach here.
1201  return text;
1202 }
1203 
1204 KuitSemanticsPrivate::OpenEl
1205 KuitSemanticsPrivate::parseOpenEl (const QXmlStreamReader &xml,
1206  Kuit::TagVar etag,
1207  const QString &text) const
1208 {
1209  #ifdef NDEBUG
1210  Q_UNUSED(text);
1211  #endif
1212 
1213  KuitSemanticsStaticData *s = semanticsStaticData;
1214 
1215  OpenEl oel;
1216  oel.name = xml.name().toString().toLower();
1217 
1218  // Collect attribute names and values, and format attribute string.
1219  QStringList attnams, attvals;
1220  foreach (const QXmlStreamAttribute &xatt, xml.attributes()) {
1221  attnams += xatt.name().toString().toLower();
1222  attvals += xatt.value().toString();
1223  QChar qc = attvals.last().indexOf(QLatin1Char('\'')) < 0 ? QLatin1Char('\'') : QLatin1Char('"');
1224  oel.astr += QLatin1Char(' ') + attnams.last() + QLatin1Char('=') + qc + attvals.last() + qc;
1225  }
1226 
1227  if (s->knownTags.contains(oel.name)) { // known KUIT element
1228  oel.tag = s->knownTags[oel.name];
1229 
1230  // If this element can be contained within enclosing element,
1231  // mark it proper, otherwise mark it for removal.
1232  if (etag == Kuit::Tag::None || s->tagSubs[etag].contains(oel.tag)) {
1233  oel.handling = OpenEl::Proper;
1234  }
1235  else {
1236  oel.handling = OpenEl::Dropout;
1237  kDebug(173) << QString::fromLatin1("Tag '%1' cannot be subtag of '%2' "
1238  "in message {%3}.")
1239  .arg(s->tagNames[oel.tag], s->tagNames[etag],
1240  shorten(text));
1241  }
1242 
1243  // Resolve attributes and compute attribute set key.
1244  QSet<Kuit::AttVar> attset;
1245  for (int i = 0; i < attnams.size(); ++i) {
1246  if (s->knownAtts.contains(attnams[i])) {
1247  Kuit::AttVar att = s->knownAtts[attnams[i]];
1248  if (s->tagAtts[oel.tag].contains(att)) {
1249  attset << att;
1250  oel.avals[att] = attvals[i];
1251  }
1252  else {
1253  kDebug(173) << QString::fromLatin1("Attribute '%1' cannot be used in "
1254  "tag '%2' in message {%3}.")
1255  .arg(attnams[i], oel.name,
1256  shorten(text));
1257  }
1258  }
1259  else {
1260  kDebug(173) << QString::fromLatin1("Unknown semantic tag attribute '%1' "
1261  "in message {%2}.")
1262  .arg(attnams[i], shorten(text));
1263  }
1264  }
1265  oel.akey = attSetKey(attset);
1266  }
1267  else if (oel.name == QLatin1String("qt") || oel.name == QLatin1String("html")) {
1268  // Drop qt/html tags (gets added in the end).
1269  oel.handling = OpenEl::Dropout;
1270  }
1271  else { // other element, leave it in verbatim
1272  oel.handling = OpenEl::Ignored;
1273  if (!s->qtHtmlTagNames.contains(oel.name)) {
1274  kDebug(173) << QString::fromLatin1("Tag '%1' is neither semantic nor HTML in "
1275  "message {%3}.")
1276  .arg(oel.name, shorten(text));
1277  }
1278  }
1279 
1280  return oel;
1281 }
1282 
1283 QString KuitSemanticsPrivate::visualPattern (Kuit::TagVar tag, int akey,
1284  Kuit::FmtVar fmt) const
1285 {
1286  // Default pattern: simple substitution.
1287  QString pattern = QString::fromLatin1("%1");
1288 
1289  // See if there is a pattern specifically for this element.
1290  if ( m_patterns.contains(tag)
1291  && m_patterns[tag].contains(akey)
1292  && m_patterns[tag][akey].contains(fmt))
1293  {
1294  pattern = m_patterns[tag][akey][fmt];
1295  }
1296 
1297  return pattern;
1298 }
1299 
1300 QString KuitSemanticsPrivate::formatSubText (const QString &ptext,
1301  const OpenEl &oel,
1302  Kuit::FmtVar fmt,
1303  int numctx) const
1304 {
1305  KuitSemanticsStaticData *s = semanticsStaticData;
1306 
1307  if (oel.handling == OpenEl::Proper) {
1308  // Select formatting pattern.
1309  QString pattern = visualPattern(oel.tag, oel.akey, fmt);
1310 
1311  // Some tags modify their text.
1312  QString mtext = modifyTagText(oel.formattedText, oel.tag, oel.avals,
1313  numctx, fmt);
1314 
1315  using namespace Kuit;
1316 
1317  // Format text according to pattern.
1318  QString ftext;
1319  if (oel.tag == Tag::Link && oel.avals.contains(Att::Url)) {
1320  ftext = pattern.arg(oel.avals[Att::Url], mtext);
1321  }
1322  else if (oel.tag == Tag::Command && oel.avals.contains(Att::Section)) {
1323  ftext = pattern.arg(mtext, oel.avals[Att::Section]);
1324  }
1325  else if (oel.tag == Tag::Email && oel.avals.contains(Att::Address)) {
1326  ftext = pattern.arg(mtext, oel.avals[Att::Address]);
1327  }
1328  else if (oel.tag == Tag::Note && oel.avals.contains(Att::Label)) {
1329  ftext = pattern.arg(oel.avals[Att::Label], mtext);
1330  }
1331  else if (oel.tag == Tag::Warning && oel.avals.contains(Att::Label)) {
1332  ftext = pattern.arg(oel.avals[Att::Label], mtext);
1333  }
1334  else {
1335  ftext = pattern.arg(mtext);
1336  }
1337 
1338  // Handle leading newlines, if this is not start of the text
1339  // (ptext is the preceding text).
1340  if (!ptext.isEmpty() && s->leadingNewlines.contains(oel.tag)) {
1341  // Count number of present newlines.
1342  int pnumle, pnumtr, fnumle, fnumtr;
1343  countWrappingNewlines(ptext, pnumle, pnumtr);
1344  countWrappingNewlines(ftext, fnumle, fnumtr);
1345  // Number of leading newlines already present.
1346  int numle = pnumtr + fnumle;
1347  // The required extra newlines.
1348  QString strle;
1349  if (numle < s->leadingNewlines[oel.tag]) {
1350  strle = QString(s->leadingNewlines[oel.tag] - numle, QLatin1Char('\n'));
1351  }
1352  ftext = strle + ftext;
1353  }
1354 
1355  return ftext;
1356  }
1357  else if (oel.handling == OpenEl::Ignored) {
1358  if (oel.name == QLatin1String("br") || oel.name == QLatin1String("hr")) {
1359  // Close these tags in-place (just for looks).
1360  return QLatin1Char('<') + oel.name + QLatin1String("/>");
1361  }
1362  else {
1363  return QLatin1Char('<') + oel.name + oel.astr + QLatin1Char('>')
1364  + oel.formattedText
1365  + QLatin1String("</") + oel.name + QLatin1Char('>');
1366  }
1367  }
1368  else { // oel.handling == OpenEl::Dropout
1369  return oel.formattedText;
1370  }
1371 }
1372 
1373 void KuitSemanticsPrivate::countWrappingNewlines (const QString &text,
1374  int &numle, int &numtr)
1375 {
1376  int len = text.length();
1377  // Number of newlines at start of text.
1378  numle = 0;
1379  while (numle < len && text[numle] == QLatin1Char('\n')) {
1380  ++numle;
1381  }
1382  // Number of newlines at end of text.
1383  numtr = 0;
1384  while (numtr < len && text[len - numtr - 1] == QLatin1Char('\n')) {
1385  ++numtr;
1386  }
1387 }
1388 
1389 QString KuitSemanticsPrivate::modifyTagText (const QString &text,
1390  Kuit::TagVar tag,
1391  const QHash<Kuit::AttVar, QString> &avals,
1392  int numctx,
1393  Kuit::FmtVar fmt) const
1394 {
1395  // numctx < 1 means that the number is not in numeric-id context.
1396  if ( (tag == Kuit::Tag::NumIntg || tag == Kuit::Tag::NumReal) \
1397  && numctx < 1)
1398  {
1399  int fieldWidth = avals.value(Kuit::Att::Width, QString(QLatin1Char('0'))).toInt();
1400  const QString fillStr = avals.value(Kuit::Att::Fill, QString(QLatin1Char(' ')));
1401  const QChar fillChar = !fillStr.isEmpty() ? fillStr[0] : QChar::fromLatin1(' ');
1402  return QString::fromLatin1("%1").arg(KGlobal::locale()->formatNumber(text, false),
1403  fieldWidth, fillChar);
1404  }
1405  else if (tag == Kuit::Tag::Filename) {
1406  return QDir::toNativeSeparators(text);
1407  }
1408  else if (tag == Kuit::Tag::Shortcut) {
1409  return KuitFormats::toKeyCombo(text, m_comboKeyDelim[fmt], m_keyNames);
1410  }
1411  else if (tag == Kuit::Tag::Interface) {
1412  return KuitFormats::toInterfacePath(text, m_guiPathDelim[fmt]);
1413  }
1414 
1415  // Fell through, no modification.
1416  return text;
1417 }
1418 
1419 QString KuitSemanticsPrivate::finalizeVisualText (const QString &final,
1420  Kuit::FmtVar fmt,
1421  bool hadQtTag,
1422  bool hadAnyHtmlTag) const
1423 {
1424  KuitSemanticsStaticData *s = semanticsStaticData;
1425 
1426  QString text = final;
1427 
1428  // Resolve XML entities if format explicitly not rich
1429  // and no HTML tag encountered.
1430  if (fmt != Kuit::Fmt::Rich && !hadAnyHtmlTag)
1431  {
1432  static QRegExp staticEntRx(QLatin1String("&("ENTITY_SUBRX");"));
1433  // We have to have a local copy here, otherwise this function
1434  // will not be thread safe because QRegExp is not thread safe.
1435  QRegExp entRx = staticEntRx;
1436  int p = entRx.indexIn(text);
1437  QString plain;
1438  while (p >= 0) {
1439  QString ent = entRx.capturedTexts().at(1);
1440  plain.append(text.mid(0, p));
1441  text.remove(0, p + ent.length() + 2);
1442  if (ent.startsWith(QLatin1Char('#'))) { // numeric character entity
1443  QChar c;
1444  bool ok;
1445  if (ent[1] == QLatin1Char('x')) {
1446  c = QChar(ent.mid(2).toInt(&ok, 16));
1447  } else {
1448  c = QChar(ent.mid(1).toInt(&ok, 10));
1449  }
1450  if (ok) {
1451  plain.append(c);
1452  } else { // unknown Unicode point, leave as is
1453  plain.append(QLatin1Char('&') + ent + QLatin1Char(';'));
1454  }
1455  }
1456  else if (s->xmlEntities.contains(ent)) { // known entity
1457  plain.append(s->xmlEntities[ent]);
1458  } else { // unknown entity, just leave as is
1459  plain.append(QLatin1Char('&') + ent + QLatin1Char(';'));
1460  }
1461  p = entRx.indexIn(text);
1462  }
1463  plain.append(text);
1464  text = plain;
1465  }
1466 
1467  // Add top rich tag if format explicitly rich or such tag encountered.
1468  if (fmt == Kuit::Fmt::Rich || hadQtTag) {
1469  text = QString::fromLatin1("<html>") + text + QLatin1String("</html>");
1470  }
1471 
1472  return text;
1473 }
1474 
1475 QString KuitSemanticsPrivate::salvageMarkup (const QString &text_,
1476  Kuit::FmtVar fmt) const
1477 {
1478  KuitSemanticsStaticData *s = semanticsStaticData;
1479  QString text = text_;
1480  QString ntext;
1481  int pos;
1482 
1483  // Resolve KUIT tags simple-mindedly.
1484 
1485  // - tags with content
1486  static QRegExp staticWrapRx(QLatin1String("(<\\s*(\\w+)\\b([^>]*)>)(.*)(<\\s*/\\s*\\2\\s*>)"));
1487  QRegExp wrapRx = staticWrapRx; // for thread-safety
1488  wrapRx.setMinimal(true);
1489  pos = 0;
1490  ntext.clear();
1491  while (true) {
1492  int previousPos = pos;
1493  pos = wrapRx.indexIn(text, previousPos);
1494  if (pos < 0) {
1495  ntext += text.mid(previousPos);
1496  break;
1497  }
1498  ntext += text.mid(previousPos, pos - previousPos);
1499  const QStringList capts = wrapRx.capturedTexts();
1500  QString tagname = capts[2].toLower();
1501  QString content = salvageMarkup(capts[4], fmt);
1502  if (s->knownTags.contains(tagname)) {
1503  // Select formatting pattern.
1504  // TODO: Do not ignore attributes (in capts[3]).
1505  QString pattern = visualPattern(s->knownTags[tagname], 0, fmt);
1506  ntext += pattern.arg(content);
1507  } else {
1508  ntext += capts[1] + content + capts[5];
1509  }
1510  pos += wrapRx.matchedLength();
1511  }
1512  text = ntext;
1513 
1514  // - content-less tags
1515  static QRegExp staticNowrRx(QLatin1String("<\\s*(\\w+)\\b([^>]*)/\\s*>"));
1516  QRegExp nowrRx = staticNowrRx; // for thread-safety
1517  nowrRx.setMinimal(true);
1518  pos = 0;
1519  ntext.clear();
1520  while (true) {
1521  int previousPos = pos;
1522  pos = nowrRx.indexIn(text, previousPos);
1523  if (pos < 0) {
1524  ntext += text.mid(previousPos);
1525  break;
1526  }
1527  ntext += text.mid(previousPos, pos - previousPos);
1528  const QStringList capts = nowrRx.capturedTexts();
1529  QString tagname = capts[1].toLower();
1530  if (s->knownTags.contains(tagname)) {
1531  QString pattern = visualPattern(s->knownTags[tagname], 0, fmt);
1532  ntext += pattern.arg(QString());
1533  } else {
1534  ntext += capts[0];
1535  }
1536  pos += nowrRx.matchedLength();
1537  }
1538  text = ntext;
1539 
1540  return text;
1541 }
1542 
1543 // -----------------------------------------------------------------------------
1544 // The KuitSemantics methods, only delegate to KuitSemanticsPrivate.
1545 
1546 KuitSemantics::KuitSemantics (const QString &lang)
1547 : d(new KuitSemanticsPrivate(lang))
1548 {
1549 }
1550 
1551 KuitSemantics::~KuitSemantics ()
1552 {
1553  delete d;
1554 }
1555 
1556 QString KuitSemantics::format (const QString &text, const QString &ctxt) const
1557 {
1558  return d->format(text, ctxt);
1559 }
1560 
1561 bool KuitSemantics::mightBeRichText (const QString &text)
1562 {
1563  KuitSemanticsStaticData *s = semanticsStaticData;
1564 
1565  // Check by appearance of a valid XML entity at first ampersand.
1566  int p1 = text.indexOf(QLatin1Char('&'));
1567  if (p1 >= 0) {
1568  p1 += 1;
1569  int p2 = text.indexOf(QLatin1Char(';'), p1);
1570  return (p2 > p1 && s->xmlEntities.contains(text.mid(p1, p2 - p1)));
1571  }
1572 
1573  // Check by appearance of a valid Qt rich-text tag at first less-than.
1574  int tlen = text.length();
1575  p1 = text.indexOf(QLatin1Char('<'));
1576  if (p1 >= 0) {
1577  p1 += 1;
1578  // Also allow first tag to be closing tag,
1579  // e.g. in case the text is pieced up with list.join("</foo><foo>")
1580  bool closing = false;
1581  while (p1 < tlen && (text[p1].isSpace() || text[p1] == QLatin1Char('/'))) {
1582  if (text[p1] == QLatin1Char('/')) {
1583  if (!closing) {
1584  closing = true;
1585  } else {
1586  return false;
1587  }
1588  }
1589  ++p1;
1590  }
1591  for (int p2 = p1; p2 < tlen; ++p2) {
1592  QChar c = text[p2];
1593  if (c == QLatin1Char('>') || (!closing && c == QLatin1Char('/')) || c.isSpace()) {
1594  return s->qtHtmlTagNames.contains(text.mid(p1, p2 - p1));
1595  } else if (!c.isLetter()) {
1596  return false;
1597  }
1598  }
1599  return false;
1600  }
1601 
1602  return false;
1603 }
1604 
1605 QString KuitSemantics::escape (const QString &text)
1606 {
1607  int tlen = text.length();
1608  QString ntext;
1609  ntext.reserve(tlen);
1610  for (int i = 0; i < tlen; ++i) {
1611  QChar c = text[i];
1612  if (c == QLatin1Char('&')) {
1613  ntext += QLatin1String("&amp;");
1614  } else if (c == QLatin1Char('<')) {
1615  ntext += QLatin1String("&lt;");
1616  } else if (c == QLatin1Char('>')) {
1617  ntext += QLatin1String("&gt;");
1618  } else if (c == QLatin1Char('\'')) {
1619  ntext += QLatin1String("&apos;");
1620  } else if (c == QLatin1Char('"')) {
1621  ntext += QLatin1String("&quot;");
1622  } else {
1623  ntext += c;
1624  }
1625  }
1626 
1627  return ntext;
1628 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Feb 9 2013 11:53:47 by doxygen 1.8.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KDECore

Skip menu "KDECore"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Modules
  • Related Pages

kdelibs-4.10.0 API Reference

Skip menu "kdelibs-4.10.0 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal