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