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-2025 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
29using 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
163QString 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
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
455QString 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
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
void setFilterOptions(const FilterOptions &options)
QString renderedText(const CSwordKey::TextRenderType mode=CSwordKey::Normal)
Definition cswordkey.cpp:58
CSwordModuleInfo::TextDirection textDirection() const
ModuleType type() const
QString const & name() const
std::shared_ptr< Language const > language() 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
KeyTree & childList() const noexcept
void setMappedKey(CSwordKey const *key) const
QString const & getAlternativeContent() 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