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