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-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 "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
59namespace {
60
61QString 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
74QString 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());
104 module = CSwordBackend::instance().findModuleByName(
105 std::move(moduleName));
106 }
107
108 if (!module)
109 module = btConfig().getDefaultSwordModuleByType(
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
164QString 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 =
183 CSwordBackend::instance().findModuleByName(modulename);
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
221CSwordModuleInfo * getStrongsModule(bool const wantHebrew) {
222 auto * const m =
224 wantHebrew
225 ? QStringLiteral("standardHebrewStrongsLexicon")
226 : QStringLiteral("standardGreekStrongsLexicon"));
227 return m ? m : getFirstAvailableStrongsModule(wantHebrew);
228}
229
230QString 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
256QString 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':
284 module = btConfig().getDefaultSwordModuleByType(
285 QStringLiteral(
286 "standardGreekMorphLexicon"));
287 skipFirstChar = true;
288 break;
289 case 'H':
290 module = btConfig().getDefaultSwordModuleByType(
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)
307 module = btConfig().getDefaultSwordModuleByType(
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(
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
346QString 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 =
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
370namespace Rendering {
371
372ListInfoData 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
400QString 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
457QString 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
CSwordModuleInfo * getDefaultSwordModuleByType(const QString &moduleType)
Returns default sword module info class for a given module type.
Definition btconfig.cpp:494
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
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)