BibleTime
cswordlexiconmoduleinfo.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
14
15#include <QChar>
16#include <QDataStream>
17#include <QDebug>
18#include <QDir>
19#include <QFile>
20#include <QIODevice>
21#include <QRegularExpression>
22#include <QRegularExpressionMatch>
23#include <utility>
24#include "../../util/btassert.h"
25#include "../../util/cp1252.h"
26#include "../../util/directory.h"
27#include "../keys/cswordldkey.h"
28
29// Sword includes:
30#ifdef __GNUC__
31#pragma GCC diagnostic push
32#pragma GCC diagnostic ignored "-Wextra-semi"
33#pragma GCC diagnostic ignored "-Wsuggest-override"
34#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
35#endif
36#ifdef __clang__
37#pragma clang diagnostic push
38#pragma clang diagnostic ignored "-Wsuggest-destructor-override"
39#endif
40#include <swkey.h>
41#include <swmodule.h>
42#ifdef __clang__
43#pragma clang diagnostic pop
44#endif
45#ifdef __GNUC__
46#pragma GCC diagnostic pop
47#endif
48
49
50namespace {
51
52// Change it once the format changed to make all systems rebuild their caches
53constexpr quint8 const cacheFormat = 4;
54
55} // Anonymous namespace
56
58 CSwordBackend & backend)
59 : CSwordModuleInfo(module, backend, Lexicon)
60{
61 /**
62 See if module keys are consistent with Strong's references
63 and determine if keys start with "G" or "H" and the number
64 of digits in the keys.
65 */
66 module.setPosition(sword::TOP);
67 module.increment();
68 QString key = QString::fromUtf8(module.getKeyText());
69 QRegularExpression rx1(QStringLiteral("^[GH][0-9]+$"));
70 if (rx1.match(key).hasMatch()) {
71 m_hasStrongsKeys = true;
73 m_strongsDigitsLength = key.length() - 1;
74 } else {
75 QRegularExpression rx2(QStringLiteral("^[0-9]+$"));
76 if (rx2.match(key).hasMatch()) {
77 m_hasStrongsKeys = true;
78 m_strongsDigitsLength = key.length();
79 }
80 }
81}
82
83const QStringList &CSwordLexiconModuleInfo::entries() const {
84 namespace DU = util::directory;
85
86 // If cache is ok, just return it:
87 if (!m_entries.empty()) {
88 return m_entries;
89 }
90
91 QFile cacheFile(QStringLiteral("%1/%2")
92 .arg(DU::getUserCacheDir().absolutePath(), name()));
93
94 QString const moduleVersion = config(CSwordModuleInfo::ModuleVersion);
95
96 auto const writeCache = [this,&cacheFile,&moduleVersion]{
97 qDebug() << "Writing cache file" << cacheFile.fileName();
98 if (cacheFile.open(QIODevice::WriteOnly)) {
99 QDataStream s(&cacheFile);
100 s.setVersion(QDataStream::Qt_5_15);
101 s << moduleVersion
102 << QString::number(cacheFormat)
103 << QString::number(QDataStream::Qt_DefaultCompiledVersion);
104 s.setVersion(QDataStream::Qt_DefaultCompiledVersion);
105 s << m_entries;
106 cacheFile.close();
107
108 if (s.status() == QDataStream::Ok) {
109 qDebug() << "Cache file written successfully!";
110 } else {
111 qDebug() << "Failed to write cache file! Attempting to remove.";
112 if (cacheFile.remove()) {
113 qDebug() << "Removed potentially corrupt cache.";
114 } else {
115 qDebug() << "Failed to remove potentially corrupt cache!";
116 }
117 }
118 } else {
119 qDebug() << "Failed to open cache file for writing!";
120 }
121 };
122
123 /*
124 * Try the module's cache
125 */
126 if (cacheFile.open(QIODevice::ReadOnly)) {
127 qDebug() << "Reading lexicon cache for module" << name() << "...";
128 QDataStream s(&cacheFile);
129 s.setVersion(QDataStream::Qt_5_15);
130 QString str;
131 s >> str;
132 qDebug() << " module version:" << str;
133 if (str == moduleVersion) {
134 s >> str;
135 qDebug() << " cache version:" << str;
136 bool isNumber = false;
137 auto const cacheVersion = str.toInt(&isNumber);
138 if (isNumber && cacheVersion <= cacheFormat) {
139 s >> str;
140 qDebug() << " QDataStream version:" << str;
141 auto const dataStreamVersion = str.toInt(&isNumber);
142 if (isNumber
143 && dataStreamVersion
144 <= QDataStream::Qt_DefaultCompiledVersion)
145 {
146 auto const resetVersion = cacheVersion >= 4;
147 qDebug() << " QDataStream version reset:" << resetVersion;
148 if (resetVersion)
149 s.setVersion(dataStreamVersion);
150 s >> m_entries;
151 if (s.status() == QDataStream::Ok) {
152 qDebug() << " entries read:" << m_entries.size();
153 cacheFile.close();
154
155 // Update cache format:
156 if (cacheVersion != cacheFormat)
157 writeCache();
158
159 return m_entries;
160 }
161 m_entries.clear();
162 }
163 }
164 }
165
166 cacheFile.close();
167 }
168
169 /*
170 * Ok, no cache or invalid.
171 */
172 qDebug() << "Read all entries of lexicon" << name();
173
174 auto & m = swordModule();
175 m.setSkipConsecutiveLinks(true);
176 m.setPosition(sword::TOP);
177 snap(); //snap to top entry
178
179 if (isUnicode()) {
180 do {
181 m_entries.append(QString::fromUtf8(m.getKeyText()));
182 m.increment();
183 } while (!m.popError());
184 } else {
185 do {
186 bool error;
187 auto converted = util::cp1252::toUnicode(m.getKeyText(), error);
188 BT_ASSERT(!error);
189 m_entries.append(std::move(converted));
190 m.increment();
191 } while (!m.popError());
192 }
193
194 m.setPosition(sword::TOP); // back to the first entry
195 m.setSkipConsecutiveLinks(false);
196
197 /// \todo Document why the following code is here:
198 if (!m_entries.empty() && m_entries.front().simplified().isEmpty())
199 m_entries.pop_front();
200
201 writeCache();
202
203 return m_entries;
204}
205
207{ return swordModule().getRawEntry(); }
208
212
213QString CSwordLexiconModuleInfo::normalizeStrongsKey(const QString &key) const {
214 if (auto const match =
215 QRegularExpression(QStringLiteral("^([GH]?)0*([0-9]+?)$"))
216 .match(key);
217 match.hasMatch())
218 {
219 auto const lang = match.capturedView(1);
220 auto const digits = match.capturedView(2);
221
222 qsizetype size = (digits.size() > m_strongsDigitsLength)
223 ? digits.size()
225 auto numPaddingZeroesRequired = size - digits.size();
227 size += lang.size();
228 QString normalized;
229 normalized.reserve(size);
231 normalized.append(lang);
232 while (numPaddingZeroesRequired--)
233 normalized.append('0');
234 normalized.append(digits);
235 return normalized;
236 }
237 return key;
238}
239
241{ return new CSwordLDKey(swordModule().getKey(), this); }
#define BT_ASSERT(...)
Definition btassert.h:17
The backend layer main class, a backend implementation of Sword.
CSwordLexiconModuleInfo(sword::SWModule &module, CSwordBackend &backend)
QString normalizeStrongsKey(const QString &key) const
CSwordKey * createKey() const final override
const QStringList & entries() const
bool snap() const final override
QString config(const CSwordModuleInfo::ConfigEntry entry) const
bool isUnicode() const noexcept
QString const & name() const
sword::SWModule & swordModule() const
QString toUnicode(QByteArray const &data, bool &error)
Definition cp1252.cpp:142