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-2021 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. */
51 std::optional<ReferenceManager::DecodedHyperlink>
52 ReferenceManager::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 
89  DecodedHyperlink ret;
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:503
CSwordModuleInfo * findModuleByName(const QString &name) const
Searches for a module with the given name.
static CSwordBackend & instance() noexcept
Definition: cswordbackend.h:98
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::unique_ptr< CSwordBackend > backend(sword::InstallSource const &is)
std::map< sword::SWBuf, sword::SWLocale * > const & internalSwordLocales()
Definition: btlocalemgr.cpp:48
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)