BibleTime
btinforendering.cpp
Go to the documentation of this file.
1 /*********
2 *
3 * In the name of the Father, and of the Son, and of the Holy Spirit.
4 *
5 * This file is part of BibleTime's source code, https://bibletime.info/
6 *
7 * Copyright 1999-2021 by the BibleTime developers.
8 * The BibleTime source code is licensed under the GNU General Public License
9 * version 2.0.
10 *
11 **********/
12 
13 #include "btinforendering.h"
14 
15 #include <map>
16 #include <memory>
17 #include <QByteArray>
18 #include <QChar>
19 #include <QObject>
20 #include <QRegularExpression>
21 #include <QRegularExpressionMatch>
22 #include <QSharedPointer>
23 #include <QStringList>
24 #include <Qt>
25 #include <utility>
26 #include "../../util/btassert.h"
27 #include "../btglobal.h"
28 #include "../config/btconfig.h"
29 #include "../drivers/btmodulelist.h"
30 #include "../drivers/cswordlexiconmoduleinfo.h"
31 #include "../drivers/cswordmoduleinfo.h"
32 #include "../keys/cswordkey.h"
33 #include "../language.h"
34 #include "../managers/cdisplaytemplatemgr.h"
35 #include "../managers/cswordbackend.h"
36 #include "crossrefrendering.h"
37 #include "ctextrendering.h"
38 
39 // Sword includes:
40 #pragma GCC diagnostic push
41 #pragma GCC diagnostic ignored "-Wextra-semi"
42 #pragma GCC diagnostic ignored "-Wsuggest-override"
43 #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
44 #ifdef __clang__
45 #pragma clang diagnostic push
46 #pragma clang diagnostic ignored "-Wsuggest-destructor-override"
47 #endif
48 #include <listkey.h>
49 #include <swbuf.h>
50 #include <swkey.h>
51 #include <swmodule.h>
52 #include <versekey.h>
53 #ifdef __clang__
54 #pragma clang diagnostic pop
55 #endif
56 #pragma GCC diagnostic pop
57 
58 
59 namespace {
60 
61 QString decodeAbbreviation(QString const & data) {
62  /// \todo Is "text" correct?
63  /* before:
64  return QString("<div class=\"abbreviation\"><h3>%1: %2</h3><p>%3</p></div>")
65  .arg(QObject::tr("Abbreviation"))
66  .arg("text")
67  .arg(data);
68  */
69  return QStringLiteral(
70  "<div class=\"abbreviation\"><h3>%1: text</h3><p>%2</p></div>")
71  .arg(QObject::tr("Abbreviation"), data);
72 }
73 
74 QString decodeCrossReference(QString const & data,
75  BtConstModuleList const & modules)
76 {
77  if (data.isEmpty())
78  return QStringLiteral("<div class=\"crossrefinfo\"><h3>%1</h3></div>")
79  .arg(QObject::tr("Cross references"));
80 
81  // qWarning("setting crossref %s", data.latin1());
82 
83  DisplayOptions dispOpts;
84  dispOpts.lineBreaks = false;
85  dispOpts.verseNumbers = true;
86 
87  FilterOptions filterOpts;
88  Rendering::CrossRefRendering renderer(dispOpts, filterOpts);
90 
91  // const bool isBible = true;
92  const CSwordModuleInfo * module(nullptr);
93 
94  // a prefixed module gives the module to look into
95  static QRegularExpression const re(QStringLiteral(R"PCRE(^[^ ]+:)PCRE"));
96  // re.setMinimal(true);
97  int pos = re.match(data).capturedEnd();
98  if (pos >= 0)
99  --pos;
100 
101  if (pos > 0) {
102  auto moduleName = data.left(pos);
103  // qWarning("found module %s", moduleName.latin1());
105  std::move(moduleName));
106  }
107 
108  if (!module)
110  QStringLiteral("standardBible"));
111 
112  if (!module && modules.size() > 0)
113  module = modules.at(0);
114 
115  // BT_ASSERT(module); // why? the existense of the module is tested later
117  false,
119  };
120 
121  if (module && (module->type() == CSwordModuleInfo::Bible)) {
122  sword::VerseKey vk;
123  sword::ListKey refs =
124  vk.parseVerseList(
125  (const char*) data.mid((pos == -1) ? 0 : pos + 1).toUtf8(),
126  "Gen 1:1",
127  true);
128 
129  for (int i = 0; i < refs.getCount(); i++) {
130  sword::SWKey * const key = refs.getElement(i);
131  BT_ASSERT(key);
132  sword::VerseKey * const vk = dynamic_cast<sword::VerseKey*>(key);
133 
134  if (vk && vk->isBoundSet()) { // render a range of keys
135  tree.emplace_back(
136  QString::fromUtf8(vk->getLowerBound().getText()),
137  QString::fromUtf8(vk->getUpperBound().getText()),
138  module,
139  settings);
140  } else {
141  tree.emplace_back(QString::fromUtf8(key->getText()),
142  QString::fromUtf8(key->getText()),
143  module,
144  settings);
145  }
146  }
147  } else if (module) {
148  tree.emplace_back(data.mid((pos == -1) ? 0 : pos + 1),
149  module,
150  settings);
151  }
152 
153  // qWarning("rendered the tree: %s", renderer.renderKeyTree(tree).latin1());
154  /* spans containing rtl text need dir=rtl on their parent tag to be aligned
155  properly */
156  return QStringLiteral("<div class=\"crossrefinfo\" lang=\"%1\"><h3>%2</h3>"
157  "<div class=\"para\" dir=\"%3\">%4</div></div>")
158  .arg(module ? module->language()->abbrev() : QStringLiteral("en"),
159  QObject::tr("Cross references"),
160  module ? module->textDirectionAsHtml() : QString())
161  .arg(renderer.renderKeyTree(tree));
162 }
163 
164 QString decodeFootnote(QString const & data) {
165  QStringList list = data.split('/');
166  BT_ASSERT(list.count() >= 3);
167  if (!list.count())
168  return QString();
169 
170  FilterOptions filterOpts;
171  filterOpts.footnotes = true;
173 
174  const QString modulename = list.first();
175  const QString swordFootnote = list.last();
176 
177  // remove the first and the last and then rejoin it to get a key
178  list.pop_back();
179  list.pop_front();
180  const QString keyname = list.join('/');
181 
182  auto * const module =
184  if (!module)
185  return QString();
186 
187  QSharedPointer<CSwordKey> key(module->createKey());
188  key->setKey(keyname);
189 
190  // force entryAttributes:
191  key->renderedText(CSwordKey::ProcessEntryAttributesOnly);
192 
193  auto & m = module->swordModule();
194  const char * const note =
195  m.getEntryAttributes()
196  ["Footnote"][swordFootnote.toLatin1().data()]["body"].c_str();
197  return QStringLiteral("<div class=\"footnoteinfo\" lang=\"%1\"><h3>%2</h3>"
198  "<p>%3</p></div>")
199  .arg(module->language()->abbrev(),
200  QObject::tr("Footnote"),
201  QString::fromUtf8(m.renderText(note).c_str()));
202 }
203 
205  for (auto * const m : CSwordBackend::instance().moduleList()) {
206  if (m->type() == CSwordLexiconModuleInfo::Lexicon) {
207  auto lexModule = qobject_cast<CSwordLexiconModuleInfo *>(m);
208  if (wantHebrew
210  && lexModule->hasStrongsKeys())
211  return m;
212  if (!wantHebrew
214  && lexModule->hasStrongsKeys())
215  return m;
216  }
217  }
218  return nullptr;
219 }
220 
221 CSwordModuleInfo * getStrongsModule(bool const wantHebrew) {
222  auto * const m =
224  wantHebrew
225  ? QStringLiteral("standardHebrewStrongsLexicon")
226  : QStringLiteral("standardGreekStrongsLexicon"));
227  return m ? m : getFirstAvailableStrongsModule(wantHebrew);
228 }
229 
230 QString decodeStrongs(QString const & data) {
231  QString ret;
232  for (auto const & strongs : data.split('|')) {
233  bool const wantHebrew = strongs.left(1) == 'H';
234  CSwordModuleInfo * module = getStrongsModule(wantHebrew);
235  QString text;
236  if (module) {
237  QSharedPointer<CSwordKey> key(module->createKey());
238  auto lexModule = qobject_cast<CSwordLexiconModuleInfo *>(module);
239  key->setKey(lexModule->normalizeStrongsKey(strongs));
240  text = key->renderedText();
241  }
242  //if the module could not be found just display an empty lemma info
243 
244  ret.append(
245  QStringLiteral("<div class=\"strongsinfo\" lang=\"%1\"><h3>%2: %3"
246  "</h3><p>%4</p></div>")
247  .arg(module ? module->language()->abbrev() : QStringLiteral("en"),
248  QObject::tr("Strongs"),
249  strongs,
250  text)
251  );
252  }
253  return ret;
254 }
255 
256 QString decodeMorph(QString const & data) {
257  QStringList morphs = data.split('|');
258  QString ret;
259 
260  for (auto const & morph : morphs) {
261  //qDebug() << "CInfoDisplay::decodeMorph, morph: " << morph;
262  CSwordModuleInfo * module = nullptr;
263  bool skipFirstChar = false;
264  QString value;
265  QString valueClass;
266 
267  int valStart = morph.indexOf(':');
268  if (valStart > -1) {
269  valueClass = morph.mid(0, valStart);
270  // qDebug() << "valueClass: " << valueClass;
271  module = CSwordBackend::instance().findModuleByName(valueClass);
272  }
273  value = morph.mid(valStart + 1); /* works for prepended module and
274  without (-1 +1 == 0). */
275 
276  // if we don't have a class assigned or desired one isn't installed...
277  if (!module) {
278  // Morphs usually don't have [GH] prepended, but some old OLB
279  // codes do. We should check if we're digit after first char
280  // to better guess this.
281  if (value.size() > 1 && value.at(1).isDigit()) {
282  switch (value.at(0).toLatin1()) {
283  case 'G':
285  QStringLiteral(
286  "standardGreekMorphLexicon"));
287  skipFirstChar = true;
288  break;
289  case 'H':
291  QStringLiteral(
292  "standardHebrewMorphLexicon"));
293  skipFirstChar = true;
294  break;
295  default:
296  skipFirstChar = false;
297  /** \todo we can't tell here if it's a greek or hebrew
298  moprh code, that's a problem we have to solve
299  */
300  /* module = getBtConfig().getDefaultSwordModuleByType(
301  "standardGreekMorphLexicon"); */
302  break;
303  }
304  }
305  //if it is still not set use the default
306  if (!module)
308  QStringLiteral("standardGreekMorphLexicon"));
309  }
310 
311  QString text;
312  // BT_ASSERT(module);
313  if (module) {
314  QSharedPointer<CSwordKey> key(module->createKey());
315 
316  // skip H or G (language sign) if we have to skip it
317  const bool isOk = key->setKey(skipFirstChar ? value.mid(1) : value);
318  // BT_ASSERT(isOk);
319  /* try to use the other morph lexicon, because this one failed with
320  the current morph code. */
321  if (!isOk) {
322  /// \todo: what if the module doesn't exist?
323  key->setModule(
324  btConfig().getDefaultSwordModuleByType(
325  QStringLiteral("standardHebrewMorphLexicon")));
326  key->setKey(skipFirstChar ? value.mid(1) : value);
327  }
328 
329  text = key->renderedText();
330  }
331 
332  // if the module wasn't found just display an empty morph info
333  ret.append(QStringLiteral("<div class=\"morphinfo\" lang=\"%1\">"
334  "<h3>%2: %3</h3><p>%4</p></div>")
335  .arg(module
336  ? module->language()->abbrev()
337  : QStringLiteral("en"),
338  QObject::tr("Morphology"),
339  value,
340  text));
341  }
342 
343  return ret;
344 }
345 
346 QString decodeSwordReference(QString const & data) {
347  static QRegularExpression const rx(
348  QStringLiteral(R"PCRE(sword://(bible|lexicon)/(.*?)/(.*?))PCRE"),
349  QRegularExpression::CaseInsensitiveOption);
350  if (auto const match = rx.match(data); match.hasMatch()) {
351  if (auto * const module =
352  CSwordBackend::instance().findModuleByName(
353  match.captured(2)))
354  {
355  std::unique_ptr<CSwordKey> key(module->createKey());
356  auto reference = match.captured(3);
357  key->setKey(reference);
358  return QStringLiteral("<div class=\"crossrefinfo\" lang=\"%1\">"
359  "<h3>%2</h3><p>%3</p></div>")
360  .arg(module->language()->abbrev(),
361  std::move(reference),
362  key->renderedText());
363  }
364  }
365  return {};
366 }
367 
368 } // anonymous namespace
369 
370 namespace Rendering {
371 
372 ListInfoData detectInfo(QString const & data) {
373  ListInfoData list;
374  auto const attrList(data.split(QStringLiteral("||")));
375 
376  for (auto const & attrPair : attrList) {
377  auto const attr(attrPair.split('='));
378  if (attr.size() == 2) {
379  auto const & attrName = attr[0];
380  auto const & attrValue = attr[1];
381  if (attrName == QStringLiteral("note")) {
382  list.append(qMakePair(Footnote, attrValue));
383  } else if (attrName == QStringLiteral("lemma")) {
384  list.append(qMakePair(Lemma, attrValue));
385  } else if (attrName == QStringLiteral("morph")) {
386  list.append(qMakePair(Morph, attrValue));
387  } else if (attrName == QStringLiteral("expansion")) {
388  list.append(qMakePair(Abbreviation, attrValue));
389  } else if (attrName == QStringLiteral("crossrefs")) {
390  list.append(qMakePair(CrossReference, attrValue));
391  } else if (attrName == QStringLiteral("href")) {
392  list.append(qMakePair(Reference, attrValue));
393  }
394  }
395  }
396  return list;
397 }
398 
399 
400 QString formatInfo(const ListInfoData & list, BtConstModuleList const & modules)
401 {
402  BT_ASSERT(!modules.contains(nullptr) && (modules.size() <= 1 && "not implemented"));
403 
404  if (list.isEmpty())
405  return QString();
406 
407  QString text;
408 
409  for (auto const & infoData : list) {
410  auto const & value = infoData.second;
411  switch (infoData.first) {
412  case Lemma:
413  text.append(decodeStrongs(value));
414  continue;
415  case Morph:
416  text.append(decodeMorph(value));
417  continue;
418  case CrossReference:
419  text.append(decodeCrossReference(value, modules));
420  continue;
421  case Footnote:
422  text.append(decodeFootnote(value));
423  continue;
424  case Abbreviation:
425  text.append(decodeAbbreviation(value));
426  continue;
427  case Text:
428  text.append(value);
429  continue;
430  case Reference:
431  if (value.contains(QStringLiteral("strongs:"))) {
432  auto v(value.right(value.size() - value.lastIndexOf('/')
433  - 1));
434  if (value.contains(QStringLiteral("GREEK"))) {
435  v.prepend('G');
436  } else if (value.contains(QStringLiteral("HEBREW"))) {
437  v.prepend('H');
438  } else {
439  BT_ASSERT(false && "not implemented");
440  }
441  text.append(decodeStrongs(v));
442  } else if (value.contains(QStringLiteral("sword:"))) {
443  text.append(decodeSwordReference(value));
444  continue;
445  } else {
446  BT_ASSERT(false); /// \todo Why is this here?
447  }
448  [[fallthrough]];
449  default:
450  continue;
451  }
452  }
453 
454  return formatInfo(text);
455 }
456 
457 QString formatInfo(QString const & info, QString const & lang) {
459  BT_ASSERT(mgr);
460 
462  settings.pageCSS_ID = QStringLiteral("infodisplay");
463 
464  return mgr->fillTemplate(
466  QStringLiteral("<div class=\"infodisplay\"%1>%2</div>")
467  .arg(lang.isEmpty()
468  ? QString()
469  : QStringLiteral(" lang=\"%1\"").arg(lang),
470  info),
471  settings);
472 }
473 
474 } // namespace Rendering {
#define BT_ASSERT(...)
Definition: btassert.h:17
BtConfig & btConfig()
This is a shortchand for BtConfig::getInstance().
Definition: btconfig.h:305
QList< CSwordModuleInfo const * > BtConstModuleList
Definition: btmodulelist.h:21
CSwordModuleInfo * getDefaultSwordModuleByType(const QString &moduleType)
Returns default sword module info class for a given module type.
Definition: btconfig.cpp:503
QString fillTemplate(const QString &name, const QString &content, const Settings &settings) const
Fills the template.
static QString activeTemplateName()
static CDisplayTemplateMgr * instance()
CSwordModuleInfo * findModuleByName(const QString &name) const
Searches for a module with the given name.
static CSwordBackend & instance() noexcept
Definition: cswordbackend.h:98
void setFilterOptions(const FilterOptions &options)
@ ProcessEntryAttributesOnly
Definition: cswordkey.h:28
virtual CSwordKey * createKey() const =0
std::shared_ptr< Language const > language() const
ModuleType type() const
char const * textDirectionAsHtml() const
std::list< KeyTreeItem > KeyTree
QString renderKeyTree(KeyTree const &tree) const
QList< InfoData > ListInfoData
QString formatInfo(const ListInfoData &list, BtConstModuleList const &modules)
ListInfoData detectInfo(QString const &data)
QString decodeMorph(QString const &data)
QString decodeStrongs(QString const &data)
CSwordModuleInfo * getFirstAvailableStrongsModule(bool wantHebrew)
QString decodeAbbreviation(QString const &data)
QString decodeFootnote(QString const &data)
QString decodeSwordReference(QString const &data)
QString decodeCrossReference(QString const &data, BtConstModuleList const &modules)
CSwordModuleInfo * getStrongsModule(bool const wantHebrew)
int lineBreaks
Definition: btglobal.h:47
int verseNumbers
Definition: btglobal.h:48
int footnotes
Definition: btglobal.h:26