BibleTime
ctextrendering.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 "ctextrendering.h"
14 
15 #include <memory>
16 #include <QtAlgorithms>
17 #include "../../util/btassert.h"
18 #include "../config/btconfig.h"
19 #include "../drivers/cswordmoduleinfo.h"
20 #include "../keys/cswordkey.h"
21 #include "../keys/cswordversekey.h"
22 #include "../managers/cdisplaytemplatemgr.h"
23 #include "../managers/cswordbackend.h"
24 
25 // Sword includes:
26 #include <swkey.h>
27 
28 
29 using namespace Rendering;
30 
32  const CSwordModuleInfo *module,
33  const Settings &settings)
34  : m_settings(settings)
35  , m_key(key)
36 { m_moduleList.append(module); }
37 
39  const Settings &settings)
40  : m_settings(settings)
41  , m_alternativeContent(content)
42 {}
43 
45  const BtConstModuleList &mods,
46  const Settings &settings)
47  : m_settings(settings)
48  , m_moduleList(mods)
49  , m_key(key)
50 {}
51 
53  const QString &stopKey,
54  const CSwordModuleInfo *module,
55  const Settings &settings)
56  : m_settings(settings)
57  , m_key(startKey)
58 {
59  BT_ASSERT(module);
60  m_moduleList.append(module);
61 
62  //use the start and stop key to ceate our child items
63 
64  if (module->type() == CSwordModuleInfo::Bible) {
65  CSwordVerseKey start(module);
66  start.setKey(startKey);
67 
68  CSwordVerseKey stop(module);
69  stop.setKey(stopKey);
70 
71  if (!m_key.isEmpty() && !stopKey.isEmpty()) { //we have a range of keys
72  bool ok = true;
73 
74  while (ok && ((start < stop) || (start == stop)) ) { //range
75  m_childList.emplace_back(start.key(),
76  module,
78  false,
79  settings.keyRenderingFace});
80  ok = start.next();
81  }
82  }
83  else if (m_key.isEmpty()) {
84  m_childList.emplace_back(startKey,
85  module,
87  false,
89  }
90  }
91  else if ((module->type() == CSwordModuleInfo::Lexicon) || (module->type() == CSwordModuleInfo::Commentary) ) {
92  m_childList.emplace_back(startKey,
93  module,
95  false,
97  }
98  else if (module->type() == CSwordModuleInfo::GenericBook) {
99  m_childList.emplace_back(startKey,
100  module,
102  false,
104  }
105 
106  //make it into "<simple|range> (modulename)"
107 
108  if (startKey == stopKey) {
109  m_alternativeContent = startKey;
110  }
111  else {
112  sword::VerseKey vk(startKey.toUtf8().constData(), stopKey.toUtf8().constData());
113 
114  if (vk.getLowerBound().getBook() != vk.getUpperBound().getBook()) {
115  m_alternativeContent = QString::fromUtf8(vk.getRangeText());
116  }
117  else if (vk.getLowerBound().getChapter() != vk.getUpperBound().getChapter()) {
118  m_alternativeContent = QStringLiteral("%1 - %2:%3")
119  .arg(QString::fromUtf8(vk.getLowerBound().getText()))
120  .arg(vk.getUpperBound().getChapter())
121  .arg(vk.getUpperBound().getVerse());
122  }
123  else { //only verses differ (same book, same chapter)
124  m_alternativeContent = QStringLiteral("%1 - %2")
125  .arg(QString::fromUtf8(vk.getLowerBound().getText()))
126  .arg(vk.getUpperBound().getVerse());
127  }
128  }
130  QStringLiteral("<div class=\"rangeheading\" dir=\"ltr\">%1 (%2)"
131  "</div>")
132  .arg(m_alternativeContent, module->name());
133 }
134 
136  : CTextRendering(addText,
137  btConfig().getDisplayOptions(),
138  btConfig().getFilterOptions())
139 {}
140 
142  bool const addText,
143  DisplayOptions const & displayOptions,
144  FilterOptions const & filterOptions)
145  : m_displayOptions(displayOptions)
146  , m_filterOptions(filterOptions)
147  , m_addText(addText)
148 {}
149 
151 
153  //collect all modules which are available and used by child items
154  BtConstModuleList modules;
155 
156  for (auto const & item : tree)
157  for (auto const * const mod : item.modules())
158  if (!modules.contains(mod))
159  modules.append(mod);
160  return modules;
161 }
162 
163 QString CTextRendering::renderKeyTree(KeyTree const & tree) const {
164  //CSwordBackend::instance()()->setDisplayOptions( m_displayOptions );
166 
167  const BtConstModuleList modules = collectModules(tree);
168  QString t;
169 
170  //optimization for entries with the same key
171 
172  if (modules.count() == 1) { //this optimizes the rendering, only one key created for all items
173  std::unique_ptr<CSwordKey> key(modules.first()->createKey());
174  for (auto const & item : tree) {
175  key->setKey(item.key());
176  t.append(renderEntry(item, key.get()));
177  }
178  }
179  else {
180  for (auto const & item : tree)
181  t.append(renderEntry(item));
182  }
183 
184  return finishText(t, tree);
185 }
186 
188  CSwordVerseKey const & lowerBound,
189  CSwordVerseKey const & upperBound,
190  const BtConstModuleList &modules,
191  const QString &highlightKey,
192  const KeyTreeItem::Settings &keySettings)
193 {
194 
195  if (lowerBound == upperBound) // same key, render single key:
196  return renderSingleKey(lowerBound.key(), modules, keySettings);
197 
198  // Render range:
199  BT_ASSERT(lowerBound < upperBound);
200  KeyTree tree;
201  KeyTreeItem::Settings settings = keySettings;
202 
203  auto curKey = lowerBound;
204  do {
205  //make sure the key given by highlightKey gets marked as current key
206  settings.highlight = (!highlightKey.isEmpty() ? (curKey.key() == highlightKey) : false);
207 
208  /**
209  \todo We need to take care of linked verses if we render one or
210  (esp) more modules. If the verses 2,3,4,5 are linked to 1,
211  it should be displayed as one entry with the caption 1-5.
212  */
213 
214  if (curKey.chapter() == 0) { // range was 0:0-1:x, render 0:0 first and jump to 1:0
215  curKey.setVerse(0);
216  tree.emplace_back(curKey.key(), modules, settings);
217  curKey.setChapter(1);
218  curKey.setVerse(0);
219  }
220  tree.emplace_back(curKey.key(), modules, settings);
221  if (!curKey.next()) {
222  /// \todo Notify the user about this failure.
223  break;
224  }
225  } while (curKey < upperBound);
226  return renderKeyTree(tree);
227 }
228 
230  const QString &key,
231  const BtConstModuleList &modules,
232  const KeyTreeItem::Settings &settings)
233 {
234  KeyTree tree;
235  tree.emplace_back(key, modules, settings);
236  return renderKeyTree(tree);
237 }
238 
239 QString CTextRendering::renderEntry(KeyTreeItem const & i, CSwordKey * k) const
240 {
241  if (i.hasAlternativeContent()) {
242  auto ret = i.settings().highlight
243  ? QStringLiteral("<div class=\"currententry\">")
244  : QStringLiteral("<div class=\"entry\">");
245  ret.append(i.getAlternativeContent());
246 
247  if (!i.childList().empty()) {
248  KeyTree const & tree = i.childList();
249 
250  BtConstModuleList const modules(collectModules(tree));
251 
252  if (modules.count() == 1)
253  // insert the direction into the surrounding div:
254  ret.insert(5,
255  QStringLiteral("dir=\"%1\" ")
256  .arg(modules.first()->textDirectionAsHtml()));
257 
258  for (auto const & item : tree)
259  ret.append(renderEntry(item));
260  }
261 
262  ret.append(QStringLiteral("</div>"));
263  return ret; // WARNING: Return already here!
264  }
265 
266 
267  BtConstModuleList const & modules(i.modules());
268  if (modules.isEmpty())
269  return {}; // no module present for rendering
270 
271  std::unique_ptr<CSwordKey> scoped_key(
272  !k ? modules.first()->createKey() : nullptr);
273  CSwordKey * const key = k ? k : scoped_key.get();
274  BT_ASSERT(key);
275 
276  CSwordVerseKey * const myVK = dynamic_cast<CSwordVerseKey *>(key);
277  if (myVK)
278  myVK->setIntros(true);
279 
280  bool const oneModule = modules.size() == 1;
281  auto renderedText(oneModule
282  ? QStringLiteral("\n")
283  : QStringLiteral("\n\t\t<tr>\n"));
284  // Only insert the table stuff if we are displaying parallel.
285 
286  for (auto const & modulePtr : modules) {
287  BT_ASSERT(modulePtr);
288  if (myVK) {
289  key->setModule(*modules.begin());
290  key->setKey(i.key());
291 
292  // this would change key position due to v11n translation
293  key->setModule(modulePtr);
294  } else {
295  key->setModule(modulePtr);
296  key->setKey(i.key());
297  }
298 
299  // indicate that key was changed
300  i.setMappedKey(key->key() != i.key() ? key : nullptr);
301 
302  auto & swModule = modulePtr->swordModule();
303  auto const langAttr =
304  QStringLiteral(" xml:lang=\"%1\" lang=\"%1\"").arg(
305  modulePtr->language()->abbrev());
306 
307  QString key_renderedText;
308  if (key->isValid() && i.key() == key->key()) {
309  key_renderedText = key->renderedText();
310 
311  // if key was expanded
312  if (CSwordVerseKey const * const vk =
313  dynamic_cast<CSwordVerseKey *>(key))
314  {
315  if (vk->isBoundSet()) {
316  CSwordVerseKey pk(*vk);
317  auto const lowerBoundIndex = vk->lowerBound().index();
318  auto const upperBoundIndex = vk->upperBound().index();
319  for (auto i = lowerBoundIndex; i < upperBoundIndex; ++i) {
320  key_renderedText += ' ';
321  pk.setIndex(i + 1);
322  key_renderedText += pk.renderedText();
323  }
324  }
325  }
326  } else {
327  key_renderedText =
328  QStringLiteral("<span class=\"inactive\">&#8212;</span>");
329  }
330 
331  QString entry;
332  if (m_filterOptions.headings && key->isValid() && i.key() == key->key()) {
333 
334  // only process EntryAttributes, do not render, this might destroy the EntryAttributes again
335  swModule.renderText(nullptr, -1, 0);
336 
337  for (auto const & vp
338  : swModule.getEntryAttributes()["Heading"]["Preverse"])
339  {
340  auto preverseHeading(QString::fromUtf8(vp.second.c_str()));
341  if (preverseHeading.isEmpty())
342  continue;
343 
344  static QString const greaterOrS(
345  QStringLiteral(">\x20\x09\x0d\x0a"));
346  for (auto i = preverseHeading.indexOf('<');
347  i >= 0;
348  i = preverseHeading.indexOf('<', i))
349  {
350  auto ref = QStringView(preverseHeading).mid(i + 1);
351  if (ref.startsWith(QStringLiteral("title"))) {
352  ref = ref.mid(5); // strlen("title")
353  if (!ref.isEmpty() && greaterOrS.contains(ref[0])) {
354  auto const charsUntilTagEnd = ref.indexOf('>');
355  if (charsUntilTagEnd < 0)
356  break;
357  // Remove entire <title> tag:
358  preverseHeading.remove(i, charsUntilTagEnd + 7);
359  } else {
360  i += 7; // strlen("<title?")
361  }
362  } else if (ref.startsWith(QStringLiteral("/title"))) {
363  ref = ref.mid(6); // strlen("/title")
364  if (!ref.isEmpty() && greaterOrS.contains(ref[0])) {
365  auto const charsUntilTagEnd = ref.indexOf('>');
366  if (charsUntilTagEnd < 0)
367  break;
368  // Remove entire </title> tag:
369  preverseHeading.remove(i, charsUntilTagEnd + 8);
370  } else {
371  i += 8; // strlen("</title?")
372  }
373  } else if (ref.startsWith(QStringLiteral("div"))) {
374  ref = ref.mid(3); // strlen("div")
375  if (!ref.isEmpty() && greaterOrS.contains(ref[0])) {
376  auto const charsUntilTagEnd = ref.indexOf('>');
377  if (charsUntilTagEnd < 0)
378  break;
379  if (charsUntilTagEnd > 0) {
380  if (ref[charsUntilTagEnd - 1] == '/') {
381  // Remove entire invalid empty <div/> tag:
382  preverseHeading.remove(
383  i,
384  charsUntilTagEnd + 5);
385  } else {
386  i += charsUntilTagEnd + 5;
387  }
388  } else {
389  i += 5; // strlen("<div>")
390  }
391  } else {
392  i += 5; // strlen("<div?")
393  }
394  } else {
395  i += 3; // strlen("<?>")
396  }
397  }
398 
399  /// \todo Take care of the heading type!
400  if (!preverseHeading.isEmpty())
401  entry = QStringLiteral(
402  "<div%1 class=\"sectiontitle\">%2</div>")
403  .arg(langAttr, preverseHeading);
404  }
405  }
406 
407  entry.append(m_displayOptions.lineBreaks
408  ? QStringLiteral("<div class=\"")
409  : QStringLiteral("<div class=\"inline "));
410  if (oneModule && i.settings().highlight)
411  entry.append(QStringLiteral("current"));
412  entry.append(QStringLiteral("entry\""));
413  auto const textDirectionAttribute =
414  QStringLiteral(" dir=\"%1\">").arg(
415  modulePtr->textDirectionAsHtml());
416  entry.append(langAttr).append(textDirectionAttribute);
417 
418  //keys should normally be left-to-right, but this doesn't apply in all cases
419  if(key->isValid() && i.key() == key->key())
420  entry.append(
421  QStringLiteral(
422  "<span class=\"entryname\" dir=\"ltr\">%1</span>")
423  .arg(entryLink(i, *modulePtr)));
424 
425  if (m_addText)
426  entry.append(key_renderedText);
427 
428  for (auto const & item : i.childList())
429  entry.append(renderEntry(item));
430 
431  entry.append(QStringLiteral("</div>"));
432 
433  if (oneModule) {
434  renderedText.append(QStringLiteral("\t\t%1\n").arg(entry));
435  } else {
436  renderedText.append(
437  QStringLiteral("\t\t<td class=\"%1entry\"%2%3\n\t\t\t%4"
438  "\n\t\t</td>\n")
439  .arg(i.settings().highlight
440  ? QStringLiteral("current")
441  : QString(),
442  langAttr,
443  textDirectionAttribute,
444  entry));
445  }
446  }
447 
448  if (!oneModule)
449  renderedText.append(QStringLiteral("\t\t</tr>\n"));
450 
451  // qDebug("CTextRendering: %s", renderedText.latin1());
452  return renderedText;
453 }
454 
455 QString CTextRendering::finishText(QString const & text, KeyTree const & tree)
456  const
457 {
459  settings.modules = collectModules(tree);
460  if (settings.modules.count() == 1) {
461  CSwordModuleInfo const * const firstModule = settings.modules.first();
462  settings.langAbbrev = firstModule->language()->abbrev();
463  settings.textDirection = firstModule->textDirection();
464  } else {
465  settings.langAbbrev = QStringLiteral("unknown");
466  }
467 
470  text,
471  settings);
472 }
473 
474 /*!
475  \fn CTextRendering::entryLink( KeyTreeItem& item )
476  */
478  CSwordModuleInfo const &) const
479 { return item.key(); }
#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
QString fillTemplate(const QString &name, const QString &content, const Settings &settings) const
Fills the template.
static QString activeTemplateName()
static CDisplayTemplateMgr * instance()
static CSwordBackend & instance() noexcept
Definition: cswordbackend.h:98
void setFilterOptions(const FilterOptions &options)
QString renderedText(const CSwordKey::TextRenderType mode=CSwordKey::Normal)
Definition: cswordkey.cpp:58
bool isValid() const
Definition: cswordkey.h:95
virtual void setModule(const CSwordModuleInfo *newModule)
Definition: cswordkey.h:74
virtual bool setKey(const QString &key)=0
virtual QString key() const =0
std::shared_ptr< Language const > language() const
CSwordModuleInfo::TextDirection textDirection() const
ModuleType type() const
QString const & name() const
CSwordKey implementation for Sword's VerseKey.
void setIndex(long v)
void setIntros(bool v)
bool next(const JumpType type=JumpType::UseVerse)
QString key() const final override
bool setKey(const QString &key) final override
void setMappedKey(CSwordKey const *key) const
KeyTree & childList() const noexcept
QString const & getAlternativeContent() const
Settings const & settings() const
BtConstModuleList const & modules() const
KeyTreeItem(KeyTreeItem &&)=delete
Text rendering based on trees.
virtual QString renderEntry(KeyTreeItem const &item, CSwordKey *key=nullptr) const
virtual QString finishText(QString const &text, KeyTree const &tree) const
QString renderSingleKey(const QString &key, const BtConstModuleList &modules, const KeyTreeItem::Settings &settings=KeyTreeItem::Settings())
virtual QString entryLink(KeyTreeItem const &item, CSwordModuleInfo const &module) const
QString renderKeyRange(CSwordVerseKey const &lowerBound, CSwordVerseKey const &upperBound, const BtConstModuleList &modules, const QString &hightlightKey=QString(), const KeyTreeItem::Settings &settings=KeyTreeItem::Settings())
std::list< KeyTreeItem > KeyTree
QString renderKeyTree(KeyTree const &tree) const
DisplayOptions m_displayOptions
static BtConstModuleList collectModules(KeyTree const &tree)
CSwordModuleInfo::TextDirection textDirection
int lineBreaks
Definition: btglobal.h:47
int headings
Definition: btglobal.h:28