twl-generator 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "twl-generator",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Generate term-to-article lists from unfoldingWord en_tw archive for Bible books. Works in both Node.js (CLI) and React.js (browser) environments.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/utils/twl-matcher.js
CHANGED
|
@@ -116,24 +116,24 @@ class PrefixTrie {
|
|
|
116
116
|
|
|
117
117
|
findMatches(text, startPos) {
|
|
118
118
|
// First try exact case matches
|
|
119
|
-
let matches = this._findMatchesInTree(this.exactCaseRoot, text, startPos, true);
|
|
119
|
+
let matches = this._findMatchesInTree(this.exactCaseRoot, text, startPos, true, text);
|
|
120
120
|
|
|
121
121
|
// If no exact case matches, try case-insensitive
|
|
122
122
|
if (matches.length === 0) {
|
|
123
|
-
matches = this._findMatchesInTree(this.lowerCaseRoot, text.toLowerCase(), startPos, false);
|
|
123
|
+
matches = this._findMatchesInTree(this.lowerCaseRoot, text.toLowerCase(), startPos, false, text);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
return matches;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
_findMatchesInTree(root,
|
|
129
|
+
_findMatchesInTree(root, searchText, startPos, isExactCase, originalText) {
|
|
130
130
|
const matches = [];
|
|
131
131
|
let node = root;
|
|
132
132
|
let currentPos = startPos;
|
|
133
133
|
|
|
134
134
|
// Try to match as long as possible
|
|
135
|
-
while (currentPos <
|
|
136
|
-
const char =
|
|
135
|
+
while (currentPos < searchText.length) {
|
|
136
|
+
const char = searchText[currentPos];
|
|
137
137
|
|
|
138
138
|
if (!node[char]) {
|
|
139
139
|
break; // No more matches possible
|
|
@@ -145,16 +145,17 @@ class PrefixTrie {
|
|
|
145
145
|
// If we found terms at this position, collect them
|
|
146
146
|
if (node._terms) {
|
|
147
147
|
const matchLength = currentPos - startPos;
|
|
148
|
-
|
|
148
|
+
// Always extract from the original text to preserve case
|
|
149
|
+
const originalMatchedText = originalText.substring(startPos, currentPos);
|
|
149
150
|
|
|
150
151
|
// Check if this is a valid word boundary match (both start and end)
|
|
151
152
|
const isStartBoundary = startPos === 0 ||
|
|
152
|
-
/[\s\p{P}]/.test(
|
|
153
|
-
!/[\w]/.test(
|
|
153
|
+
/[\s\p{P}]/.test(originalText[startPos - 1]) ||
|
|
154
|
+
!/[\w]/.test(originalText[startPos - 1]);
|
|
154
155
|
|
|
155
|
-
const isEndBoundary = currentPos >=
|
|
156
|
-
/[\s\p{P}]/.test(
|
|
157
|
-
!/[\w]/.test(
|
|
156
|
+
const isEndBoundary = currentPos >= originalText.length ||
|
|
157
|
+
/[\s\p{P}]/.test(originalText[currentPos]) ||
|
|
158
|
+
!/[\w]/.test(originalText[currentPos]);
|
|
158
159
|
|
|
159
160
|
const isWordBoundary = isStartBoundary && isEndBoundary;
|
|
160
161
|
|
|
@@ -7,18 +7,7 @@ const isNode = typeof window === 'undefined' && typeof process !== 'undefined' &
|
|
|
7
7
|
|
|
8
8
|
// Get appropriate fetch implementation
|
|
9
9
|
async function getFetch() {
|
|
10
|
-
|
|
11
|
-
// Use native fetch in Node.js 18+ or fallback to node-fetch
|
|
12
|
-
if (typeof globalThis.fetch !== 'undefined') {
|
|
13
|
-
return globalThis.fetch;
|
|
14
|
-
}
|
|
15
|
-
try {
|
|
16
|
-
const nodeFetch = await import('node-fetch');
|
|
17
|
-
return nodeFetch.default;
|
|
18
|
-
} catch (error) {
|
|
19
|
-
throw new Error('fetch not available. Please use Node.js 18+ or install node-fetch');
|
|
20
|
-
}
|
|
21
|
-
}
|
|
10
|
+
// Both Node.js 18+ and browsers have native fetch
|
|
22
11
|
return globalThis.fetch;
|
|
23
12
|
}
|
|
24
13
|
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
* import { generateTWTerms } from './utils/zipProcessor.js';
|
|
8
8
|
* const terms = await generateTWTerms();
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
import { BibleBookData } from '../common/books.js';
|
|
10
|
+
import JSZip from "jszip";
|
|
12
11
|
|
|
13
12
|
// Environment detection
|
|
14
13
|
const isNode = typeof process !== 'undefined' && process.versions?.node;
|
|
@@ -21,38 +20,6 @@ const CACHE_VERSION = '1.0';
|
|
|
21
20
|
// In-memory cache for processed terms (per session)
|
|
22
21
|
let processedTermsCache = null;
|
|
23
22
|
|
|
24
|
-
/**
|
|
25
|
-
* Get dependencies dynamically (JSZip works in both environments)
|
|
26
|
-
*/
|
|
27
|
-
async function getDeps() {
|
|
28
|
-
try {
|
|
29
|
-
const jsZipModule = await import('jszip');
|
|
30
|
-
const deps = {
|
|
31
|
-
JSZip: jsZipModule.default
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Add Node.js-specific fetch if needed (fallback for Node.js < 18)
|
|
35
|
-
if (isNode) {
|
|
36
|
-
if (typeof globalThis.fetch === 'undefined') {
|
|
37
|
-
try {
|
|
38
|
-
const nodeModule = await import('node-fetch');
|
|
39
|
-
deps.fetch = nodeModule.default;
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.warn('node-fetch not available, please use Node.js 18+ or install node-fetch');
|
|
42
|
-
deps.fetch = null;
|
|
43
|
-
}
|
|
44
|
-
} else {
|
|
45
|
-
deps.fetch = globalThis.fetch;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return deps;
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error('Failed to load dependencies:', error);
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
23
|
async function getCachedZip() {
|
|
57
24
|
if (isBrowser) {
|
|
58
25
|
// Browser: Use localStorage for ZIP cache
|
|
@@ -144,26 +111,6 @@ async function getCachedTerms() {
|
|
|
144
111
|
} catch (e) { /* ignore cleanup errors */ }
|
|
145
112
|
}
|
|
146
113
|
}
|
|
147
|
-
} else if (isNode) {
|
|
148
|
-
// Node.js file system caching
|
|
149
|
-
try {
|
|
150
|
-
const deps = await getNodeDeps();
|
|
151
|
-
if (!deps) return null;
|
|
152
|
-
|
|
153
|
-
const { fs, path, fileURLToPath } = deps;
|
|
154
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
155
|
-
const __dirname = path.dirname(__filename);
|
|
156
|
-
const CACHE_FILE = path.join(__dirname, '../../article_terms.json');
|
|
157
|
-
|
|
158
|
-
if (fs.existsSync(CACHE_FILE)) {
|
|
159
|
-
const cachedData = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
|
|
160
|
-
console.log('Using cached article terms from article_terms.json');
|
|
161
|
-
memoryCache = cachedData;
|
|
162
|
-
return cachedData;
|
|
163
|
-
}
|
|
164
|
-
} catch (error) {
|
|
165
|
-
console.log('File cache corrupted, regenerating...');
|
|
166
|
-
}
|
|
167
114
|
}
|
|
168
115
|
|
|
169
116
|
return null;
|
|
@@ -192,22 +139,6 @@ async function cacheTerms(termMap) {
|
|
|
192
139
|
console.warn('Failed to cache in browser storage:', error.message);
|
|
193
140
|
}
|
|
194
141
|
}
|
|
195
|
-
} else if (isNode) {
|
|
196
|
-
// Node.js file system caching
|
|
197
|
-
try {
|
|
198
|
-
const deps = await getNodeDeps();
|
|
199
|
-
if (!deps) return;
|
|
200
|
-
|
|
201
|
-
const { fs, path, fileURLToPath } = deps;
|
|
202
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
203
|
-
const __dirname = path.dirname(__filename);
|
|
204
|
-
const CACHE_FILE = path.join(__dirname, '../../article_terms.json');
|
|
205
|
-
|
|
206
|
-
fs.writeFileSync(CACHE_FILE, JSON.stringify(termMap, null, 2), 'utf8');
|
|
207
|
-
console.log('Article terms cached to article_terms.json');
|
|
208
|
-
} catch (error) {
|
|
209
|
-
console.warn('Failed to cache article terms to file:', error.message);
|
|
210
|
-
}
|
|
211
142
|
}
|
|
212
143
|
}
|
|
213
144
|
|
|
@@ -215,11 +146,6 @@ async function cacheTerms(termMap) {
|
|
|
215
146
|
* Process ZIP buffer and extract term mappings
|
|
216
147
|
*/
|
|
217
148
|
async function processZipBuffer(zipBuffer) {
|
|
218
|
-
// Use JSZip universally for both Node.js and Browser
|
|
219
|
-
const deps = await getDeps();
|
|
220
|
-
if (!deps) throw new Error('Failed to load dependencies');
|
|
221
|
-
const { JSZip } = deps;
|
|
222
|
-
|
|
223
149
|
const zip = new JSZip();
|
|
224
150
|
const zipData = await zip.loadAsync(zipBuffer);
|
|
225
151
|
|
|
@@ -277,17 +203,7 @@ export async function generateTWTerms() {
|
|
|
277
203
|
// Download fresh ZIP
|
|
278
204
|
console.log('Downloading TW archive...');
|
|
279
205
|
|
|
280
|
-
|
|
281
|
-
if (isBrowser) {
|
|
282
|
-
fetchFn = window.fetch;
|
|
283
|
-
} else {
|
|
284
|
-
const deps = await getDeps();
|
|
285
|
-
fetchFn = deps?.fetch;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (!fetchFn) throw new Error('Fetch not available');
|
|
289
|
-
|
|
290
|
-
const res = await fetchFn(ZIP_URL);
|
|
206
|
+
const res = await fetch(ZIP_URL);
|
|
291
207
|
if (!res.ok) throw new Error(`Failed to download ZIP: ${res.status} ${res.statusText}`);
|
|
292
208
|
|
|
293
209
|
zipBuffer = await res.arrayBuffer();
|