BibleTime
referencemanager.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 "referencemanager.h"
14
15#include <QDebug>
16#include <utility>
17#include "../../util/btassert.h"
18#include "../config/btconfig.h"
19#include "../keys/cswordversekey.h"
20#include "../drivers/cswordmoduleinfo.h"
21#include "btlocalemgr.h"
22#include "cswordbackend.h"
23
24
25/** Returns a hyperlink used to be imbedded in the display windows. At the moment the format is sword://module/key */
27 QString const & key)
28{
29 QString type;
30 switch (module.type()) {
32 type = QStringLiteral("Bible");
33 break;
35 type = QStringLiteral("Commentary");
36 break;
38 type = QStringLiteral("Lexicon");
39 break;
41 type = QStringLiteral("Book");
42 break;
43 default:
44 return {};
45 }
46 BT_ASSERT(!module.name().isEmpty());
47 return QStringLiteral("sword://%1/%2/%3").arg(type, module.name(), key);
48}
49
50/** Decodes the given hyperlink. */
51std::optional<ReferenceManager::DecodedHyperlink>
52ReferenceManager::decodeHyperlink(QString const & hyperlink) {
53 /*
54 The following types of URLs are supported:
55 sword://bible/<module>/<key>
56 sword://commentary/<module>/<key>
57 sword://lexicon/<module>/<key>
58 sword://book/<module>/<key>
59 morph://hebrew/<key>
60 morph://greek/<key>
61 strongs://hebrew/<key>
62 strongs://greek/<key>
63 where <module> is the name of a module and <key> is any non-empty string.
64 If <module> is empty, or not present, the preferred module is returned
65 (if present).
66 */
67
68 QStringView ref(hyperlink);
69
70 static auto const preferredModule =
71 [](ReferenceManager::Type const type) -> CSwordModuleInfo * {
72 QString typeStr;
73 switch (type) {
74 #define RET_CASE(t,str) \
75 case t: typeStr = QStringLiteral("standard" str); break
76 RET_CASE(Bible, "Bible");
77 RET_CASE(Commentary, "Commentary");
78 RET_CASE(Lexicon, "Lexicon");
79 RET_CASE(StrongsHebrew, "HebrewStrongsLexicon");
80 RET_CASE(StrongsGreek, "GreekStrongsLexicon");
81 RET_CASE(MorphHebrew, "HebrewMorphLexicon");
82 RET_CASE(MorphGreek, "GreekMorphLexicon");
83 #undef RET_CASE
84 case Unknown: default: return nullptr;
85 }
86 return btConfig().getDefaultSwordModuleByType(typeStr);
87 };
88
90 static auto const removeCaseInsensitivePrefix =
91 [](QStringView & ref, QString const & prefix) {
92 if (ref.startsWith(prefix, Qt::CaseInsensitive)) {
93 ref = ref.mid(prefix.size() - 1);
94 return true;
95 }
96 return false;
97 };
98 int slashPos; // position of the last parsed slash
99 if (removeCaseInsensitivePrefix(ref, QStringLiteral("sword://"))) { //Bible, Commentary or Lexicon
100 if (removeCaseInsensitivePrefix(ref, QStringLiteral("bible/"))) {
102 } else if (removeCaseInsensitivePrefix(ref,
103 QStringLiteral("commentary/")))
104 {
106 } else if (removeCaseInsensitivePrefix(ref, QStringLiteral("lexicon/")))
107 {
109 } else if (removeCaseInsensitivePrefix(ref, QStringLiteral("book/"))) {
111 } else {
112 return {};
113 }
114
115 // string up to next slash is the modulename
116 slashPos = ref.indexOf('/');
117 if (slashPos < 0) // if key is empty
118 return {};
119 if (slashPos == 0) {
120 ret.module = preferredModule(ret.type);
121 } else { // We have a module given
123 ref.left(slashPos).toString());
124 if (!ret.module)
125 ret.module = preferredModule(ret.type);
126 }
127 } else {
128 struct { Type hebrew; Type greek; } types;
129 if (removeCaseInsensitivePrefix(ref, QStringLiteral("morph://"))) {
130 types = {MorphHebrew, MorphGreek};
131 } else if (removeCaseInsensitivePrefix(ref,
132 QStringLiteral("strongs://")))
133 {
134 types = {StrongsHebrew, StrongsGreek};
135 } else {
136 return {};
137 }
138
139 // Part up to next slash is the language:
140 slashPos = ref.indexOf('/');
141 if (slashPos <= 0) // if language or key is empty (0 or -1)
142 return {};
143 auto const language(ref.left(slashPos).toString().toLower());
144 if (language == QStringLiteral("hebrew")) {
145 ret.type = types.hebrew;
146 } else if (language == QStringLiteral("greek")) {
147 ret.type = types.greek;
148 } else {
149 return {};
150 }
151
152 ret.module = preferredModule(ret.type);
153 }
154 ref = ref.mid(slashPos + 1); // The remaining part is the key
155 if (ref.isEmpty()) // require non-empty key
156 return {};
157 ret.key = ref.toString();
158 return ret;
159}
160
161/** Parses the given verse references using the given language and the module.*/
163 QString const & ref,
164 ReferenceManager::ParseOptions const & options)
165{
166 auto & backend = CSwordBackend::instance();
167 auto const * const mod =
168 backend.findModuleByName(options.refDestinationModule);
169 if (!mod) // Parsing of non-verse based references is not supported:
170 return ref;
171
172 switch (mod->type()) {
174 default:
175 qDebug() << "CReferenceManager: Only verse based modules are supported "
176 "as ref destination module";
177 return {};
178 }
179
180 QString sourceLanguage = options.sourceLanguage;
181
182 bool const haveLocaleForSourceLanguage =
183 [&locale = std::as_const(sourceLanguage)]() {
184 if (locale == QStringLiteral("locales"))
185 return false;
186 auto const & locales = BtLocaleMgr::internalSwordLocales();
187 return locales.find(locale.toUtf8().constData())
188 != locales.end();
189 }();
190 if (!haveLocaleForSourceLanguage)
191 sourceLanguage = QStringLiteral("en_US");
192
193 auto const * const destinationLanguage =
194 haveLocaleForSourceLanguage ? "en" : "en_US";
195
196 CSwordVerseKey baseKey(nullptr);
197 baseKey.setLocale(sourceLanguage.toUtf8().constData());
198 baseKey.setKey(options.refBase); // Probably in the sourceLanguage
199 baseKey.setLocale("en_US"); // English works in all environments as base
200
201 /* HACK: We have to workaround a Sword bug, we have to set the default
202 locale to the same as the sourceLanguage! */
203 auto const oldLocaleName(backend.booknameLanguage());
204 backend.setBooknameLanguage(sourceLanguage);
205
206 sword::VerseKey dummy;
207 dummy.setLocale(sourceLanguage.toUtf8().constData());
208 BT_ASSERT(!strcmp(dummy.getLocale(), sourceLanguage.toUtf8().constData()));
209
210 QString ret;
211 for (auto const & ref : ref.split(';')) {
212 /* The listkey may contain more than one item, because a ref like
213 "Gen 1:3,5" is parsed into two single refs */
214 auto lk(dummy.parseVerseList(ref.toUtf8().constData(),
215 baseKey.key().toUtf8().constData(),
216 true));
217 BT_ASSERT(!dummy.popError());
218
219 if (!lk.getCount()) {
220 ret.append(ref); //don't change the original
221 continue;
222 }
223
224 for (int i = 0; i < lk.getCount(); ++i) {
225 if (auto * const k =
226 dynamic_cast<sword::VerseKey *>(lk.getElement(i)))
227 {
228 k->setLocale(destinationLanguage);
229 ret.append(QStringLiteral("%1; ")
230 .arg(QString::fromUtf8(k->getRangeText())));
231 } else { // A single ref
232 sword::VerseKey vk;
233 vk.setLocale(sourceLanguage.toUtf8().constData());
234 vk = lk.getElement(i)->getText();
235 vk.setLocale(destinationLanguage);
236 ret.append(QStringLiteral("%1; ")
237 .arg(QString::fromUtf8(vk.getText())));
238 }
239 }
240 }
241 backend.setBooknameLanguage(oldLocaleName);
242 return ret;
243}
#define BT_ASSERT(...)
Definition btassert.h:17
BtConfig & btConfig()
This is a shortchand for BtConfig::getInstance().
Definition btconfig.h:305
CSwordModuleInfo * getDefaultSwordModuleByType(const QString &moduleType)
Returns default sword module info class for a given module type.
Definition btconfig.cpp:494
CSwordModuleInfo * findModuleByName(const QString &name) const
Searches for a module with the given name.
static CSwordBackend & instance() noexcept
ModuleType type() const
QString const & name() const
CSwordKey implementation for Sword's VerseKey.
void setLocale(char const *const locale)
QString key() const final override
bool setKey(const QString &key) final override
std::map< sword::SWBuf, sword::SWLocale * > const & internalSwordLocales()
QString parseVerseReference(QString const &ref, ParseOptions const &options)
QString encodeHyperlink(CSwordModuleInfo const &module, QString const &key)
std::optional< DecodedHyperlink > decodeHyperlink(QString const &hyperlink)
#define RET_CASE(t, str)