@helloao/tools 0.0.4 → 0.0.6
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/README.md +6 -6
- package/generation/api.d.ts +46 -5
- package/generation/api.js +57 -23
- package/generation/api.spec.js +236 -79
- package/generation/dataset.d.ts +1 -1
- package/generation/dataset.js +5 -5
- package/package.json +1 -2
- package/parser/usx-parser.d.ts +2 -2
- package/parser/usx-parser.js +92 -31
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ $ npm install @helloao/tools
|
|
|
16
16
|
### Usage
|
|
17
17
|
|
|
18
18
|
#### Parse a USX File
|
|
19
|
+
|
|
19
20
|
```typescript
|
|
20
21
|
import { parser } from '@helloao/tools';
|
|
21
22
|
// Used to parse XML
|
|
@@ -36,7 +37,6 @@ const domParser = new DOMParser();
|
|
|
36
37
|
// Each input file needs some metadata about the translation that it is associated with
|
|
37
38
|
const translation: generation.ParseTreeMetadata = {
|
|
38
39
|
translation: {
|
|
39
|
-
|
|
40
40
|
// The ID of the translation
|
|
41
41
|
// this should be unique for the translation
|
|
42
42
|
id: 'my translation id',
|
|
@@ -60,7 +60,7 @@ const translation: generation.ParseTreeMetadata = {
|
|
|
60
60
|
// The direction that the text is written in.
|
|
61
61
|
// "ltr" means "left to right" and "rtl" means "right to left"
|
|
62
62
|
direction: 'ltr',
|
|
63
|
-
}
|
|
63
|
+
},
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
// The list of files that should be processed.
|
|
@@ -74,14 +74,14 @@ const files: generation.InputFile[] = [
|
|
|
74
74
|
|
|
75
75
|
// The type of the file.
|
|
76
76
|
// One of "usx", "usfm", and "json"
|
|
77
|
-
fileType: 'usx'
|
|
78
|
-
}
|
|
77
|
+
fileType: 'usx',
|
|
78
|
+
},
|
|
79
79
|
];
|
|
80
80
|
|
|
81
81
|
// Generate a dataset from the files
|
|
82
82
|
// Datasets organize all the files and their content
|
|
83
83
|
// by translation, book, chapter, and verse
|
|
84
|
-
const dataset = generation.dataset.generateDataset(files,
|
|
84
|
+
const dataset = generation.dataset.generateDataset(files, domParser);
|
|
85
85
|
|
|
86
86
|
// Generate an API representation from the files
|
|
87
87
|
// This adds links between chapters and additional metadata.
|
|
@@ -95,4 +95,4 @@ const outputFiles = generation.api.generateFilesForApi(api);
|
|
|
95
95
|
for (let file of outputFiles) {
|
|
96
96
|
console.log(file.path, file.content);
|
|
97
97
|
}
|
|
98
|
-
```
|
|
98
|
+
```
|
package/generation/api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { OutputFile, Translation, TranslationBook, TranslationBookChapter, TranslationBookChapterAudioLinks } from
|
|
2
|
-
import { DatasetOutput } from
|
|
1
|
+
import { OutputFile, Translation, TranslationBook, TranslationBookChapter, TranslationBookChapterAudioLinks } from './common-types';
|
|
2
|
+
import { DatasetOutput } from './dataset';
|
|
3
3
|
/**
|
|
4
4
|
* Defines the output of the API generation.
|
|
5
5
|
*/
|
|
@@ -27,6 +27,10 @@ export interface ApiOutput {
|
|
|
27
27
|
* - /api/:translationId/:bookId/:chapterNumber.:reader.mp3
|
|
28
28
|
*/
|
|
29
29
|
translationBookChapterAudio: ApiTranslationBookChapterAudio[];
|
|
30
|
+
/**
|
|
31
|
+
* The path prefix that the API should use.
|
|
32
|
+
*/
|
|
33
|
+
pathPrefix: string;
|
|
30
34
|
}
|
|
31
35
|
/**
|
|
32
36
|
* The list of available translations.
|
|
@@ -168,6 +172,9 @@ export interface ApiTranslationBookChapterAudio {
|
|
|
168
172
|
*/
|
|
169
173
|
originalUrl: string;
|
|
170
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* The options for generating the API.
|
|
177
|
+
*/
|
|
171
178
|
export interface GenerateApiOptions {
|
|
172
179
|
/**
|
|
173
180
|
* Whether to use the common name for the book chapter API link. If false, then book IDs are used.
|
|
@@ -182,6 +189,22 @@ export interface GenerateApiOptions {
|
|
|
182
189
|
* Defaults to false.
|
|
183
190
|
*/
|
|
184
191
|
generateAudioFiles?: boolean;
|
|
192
|
+
/**
|
|
193
|
+
* Gets the english name of the given language.
|
|
194
|
+
* If not provided, then the english name for the language will be unknown and omitted.
|
|
195
|
+
* @param language The language to get the english name for.
|
|
196
|
+
*/
|
|
197
|
+
getEnglishName?: (language: string) => string | null | undefined;
|
|
198
|
+
/**
|
|
199
|
+
* Gets the native name of the given language.
|
|
200
|
+
* If not provided, then the native name for the language will be unknown and omitted.
|
|
201
|
+
* @param language The language to get the native name for.
|
|
202
|
+
*/
|
|
203
|
+
getNativeName?: (language: string) => string | null | undefined;
|
|
204
|
+
/**
|
|
205
|
+
* The prefix that should be added to paths that are generated.
|
|
206
|
+
*/
|
|
207
|
+
pathPrefix?: string;
|
|
185
208
|
}
|
|
186
209
|
/**
|
|
187
210
|
* Generates the API output for the given dataset.
|
|
@@ -194,9 +217,27 @@ export declare function generateApiForDataset(dataset: DatasetOutput, options?:
|
|
|
194
217
|
* @param api The API that the files should be generated for.
|
|
195
218
|
*/
|
|
196
219
|
export declare function generateFilesForApi(api: ApiOutput): OutputFile[];
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Generates the output files for the given datasets.
|
|
222
|
+
* @param datasets The datasets to generate the output files for.
|
|
223
|
+
* @param options The options for generating the API files.
|
|
224
|
+
*/
|
|
225
|
+
export declare function generateOutputFilesFromDatasets(datasets: AsyncIterable<DatasetOutput>, options?: GenerateApiOptions): AsyncGenerator<OutputFile[]>;
|
|
226
|
+
/**
|
|
227
|
+
* Gets the API Link for the list of books endpoint for a translation.
|
|
228
|
+
* @param translationId The ID of the translation.
|
|
229
|
+
* @returns
|
|
230
|
+
*/
|
|
231
|
+
export declare function listOfBooksApiLink(translationId: string, prefix?: string): string;
|
|
232
|
+
/**
|
|
233
|
+
* Getes the API link for a book chapter.
|
|
234
|
+
* @param translationId The ID of the translation.
|
|
235
|
+
* @param commonName The name of the book.
|
|
236
|
+
* @param chapterNumber The number of the book.
|
|
237
|
+
* @param extension The extension of the file.
|
|
238
|
+
*/
|
|
239
|
+
export declare function bookChapterApiLink(translationId: string, commonName: string, chapterNumber: number, extension: string, prefix?: string): string;
|
|
240
|
+
export declare function bookChapterAudioApiLink(translationId: string, bookId: string, chapterNumber: number, reader: string, prefix?: string): string;
|
|
200
241
|
export declare function jsonFile(path: string, content: any, mergable?: boolean): OutputFile;
|
|
201
242
|
export declare function downloadedFile(path: string, url: string): OutputFile;
|
|
202
243
|
export declare function replaceSpacesWithUnderscores(str: string): string;
|
package/generation/api.js
CHANGED
|
@@ -2,20 +2,21 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateApiForDataset = generateApiForDataset;
|
|
4
4
|
exports.generateFilesForApi = generateFilesForApi;
|
|
5
|
+
exports.generateOutputFilesFromDatasets = generateOutputFilesFromDatasets;
|
|
5
6
|
exports.listOfBooksApiLink = listOfBooksApiLink;
|
|
6
7
|
exports.bookChapterApiLink = bookChapterApiLink;
|
|
7
8
|
exports.bookChapterAudioApiLink = bookChapterAudioApiLink;
|
|
8
9
|
exports.jsonFile = jsonFile;
|
|
9
10
|
exports.downloadedFile = downloadedFile;
|
|
10
11
|
exports.replaceSpacesWithUnderscores = replaceSpacesWithUnderscores;
|
|
11
|
-
const all_iso_language_codes_1 = require("all-iso-language-codes");
|
|
12
12
|
/**
|
|
13
13
|
* Generates the API output for the given dataset.
|
|
14
14
|
* @param dataset The dataset to generate the API for.
|
|
15
15
|
* @param options The options for generating the API.
|
|
16
16
|
*/
|
|
17
17
|
function generateApiForDataset(dataset, options = {}) {
|
|
18
|
-
const { useCommonName } = options;
|
|
18
|
+
const { useCommonName, pathPrefix } = options;
|
|
19
|
+
const apiPathPrefix = pathPrefix ? pathPrefix : '';
|
|
19
20
|
let api = {
|
|
20
21
|
availableTranslations: {
|
|
21
22
|
translations: [],
|
|
@@ -23,17 +24,24 @@ function generateApiForDataset(dataset, options = {}) {
|
|
|
23
24
|
translationBooks: [],
|
|
24
25
|
translationBookChapters: [],
|
|
25
26
|
translationBookChapterAudio: [],
|
|
27
|
+
pathPrefix: apiPathPrefix,
|
|
26
28
|
};
|
|
29
|
+
const getNativeName = options.getNativeName;
|
|
30
|
+
const getEnglishName = options.getEnglishName;
|
|
27
31
|
for (let { books, ...translation } of dataset.translations) {
|
|
28
32
|
const apiTranslation = {
|
|
29
33
|
...translation,
|
|
30
34
|
availableFormats: ['json'],
|
|
31
|
-
listOfBooksApiLink: listOfBooksApiLink(translation.id),
|
|
35
|
+
listOfBooksApiLink: listOfBooksApiLink(translation.id, apiPathPrefix),
|
|
32
36
|
numberOfBooks: books.length,
|
|
33
37
|
totalNumberOfChapters: 0,
|
|
34
38
|
totalNumberOfVerses: 0,
|
|
35
|
-
languageName:
|
|
36
|
-
|
|
39
|
+
languageName: getNativeName
|
|
40
|
+
? getNativeName(translation.language) ?? undefined
|
|
41
|
+
: undefined,
|
|
42
|
+
languageEnglishName: getEnglishName
|
|
43
|
+
? getEnglishName(translation.language) ?? undefined
|
|
44
|
+
: undefined,
|
|
37
45
|
};
|
|
38
46
|
const translationBooks = {
|
|
39
47
|
translation: apiTranslation,
|
|
@@ -43,8 +51,8 @@ function generateApiForDataset(dataset, options = {}) {
|
|
|
43
51
|
for (let { chapters, ...book } of books) {
|
|
44
52
|
const apiBook = {
|
|
45
53
|
...book,
|
|
46
|
-
firstChapterApiLink: bookChapterApiLink(translation.id, getBookLink(book), 1, 'json'),
|
|
47
|
-
lastChapterApiLink: bookChapterApiLink(translation.id, getBookLink(book), chapters.length, 'json'),
|
|
54
|
+
firstChapterApiLink: bookChapterApiLink(translation.id, getBookLink(book), 1, 'json', apiPathPrefix),
|
|
55
|
+
lastChapterApiLink: bookChapterApiLink(translation.id, getBookLink(book), chapters.length, 'json', apiPathPrefix),
|
|
48
56
|
numberOfChapters: chapters.length,
|
|
49
57
|
totalNumberOfVerses: 0,
|
|
50
58
|
};
|
|
@@ -54,7 +62,7 @@ function generateApiForDataset(dataset, options = {}) {
|
|
|
54
62
|
translation: apiTranslation,
|
|
55
63
|
book: apiBook,
|
|
56
64
|
chapter: chapter,
|
|
57
|
-
thisChapterLink: bookChapterApiLink(translation.id, getBookLink(book), chapter.number, 'json'),
|
|
65
|
+
thisChapterLink: bookChapterApiLink(translation.id, getBookLink(book), chapter.number, 'json', apiPathPrefix),
|
|
58
66
|
thisChapterAudioLinks: audio,
|
|
59
67
|
nextChapterApiLink: null,
|
|
60
68
|
nextChapterAudioLinks: null,
|
|
@@ -66,7 +74,7 @@ function generateApiForDataset(dataset, options = {}) {
|
|
|
66
74
|
if (options.generateAudioFiles) {
|
|
67
75
|
const apiAudio = {
|
|
68
76
|
chapter: apiBookChapter,
|
|
69
|
-
link: bookChapterAudioApiLink(translation.id, getBookLink(book), chapter.number, reader),
|
|
77
|
+
link: bookChapterAudioApiLink(translation.id, getBookLink(book), chapter.number, reader, apiPathPrefix),
|
|
70
78
|
originalUrl: thisChapterAudioLinks[reader],
|
|
71
79
|
};
|
|
72
80
|
audio[reader] = apiAudio.link;
|
|
@@ -91,12 +99,15 @@ function generateApiForDataset(dataset, options = {}) {
|
|
|
91
99
|
}
|
|
92
100
|
for (let i = 0; i < translationChapters.length; i++) {
|
|
93
101
|
if (i > 0) {
|
|
94
|
-
translationChapters[i].previousChapterApiLink =
|
|
95
|
-
|
|
102
|
+
translationChapters[i].previousChapterApiLink =
|
|
103
|
+
bookChapterApiLink(translation.id, getBookLink(translationChapters[i - 1].book), translationChapters[i - 1].chapter.number, 'json', apiPathPrefix);
|
|
104
|
+
translationChapters[i].previousChapterAudioLinks =
|
|
105
|
+
translationChapters[i - 1].thisChapterAudioLinks;
|
|
96
106
|
}
|
|
97
107
|
if (i < translationChapters.length - 1) {
|
|
98
|
-
translationChapters[i].nextChapterApiLink = bookChapterApiLink(translation.id, getBookLink(translationChapters[i + 1].book), translationChapters[i + 1].chapter.number, 'json');
|
|
99
|
-
translationChapters[i].nextChapterAudioLinks =
|
|
108
|
+
translationChapters[i].nextChapterApiLink = bookChapterApiLink(translation.id, getBookLink(translationChapters[i + 1].book), translationChapters[i + 1].chapter.number, 'json', apiPathPrefix);
|
|
109
|
+
translationChapters[i].nextChapterAudioLinks =
|
|
110
|
+
translationChapters[i + 1].thisChapterAudioLinks;
|
|
100
111
|
}
|
|
101
112
|
}
|
|
102
113
|
api.availableTranslations.translations.push(apiTranslation);
|
|
@@ -113,7 +124,7 @@ function generateApiForDataset(dataset, options = {}) {
|
|
|
113
124
|
*/
|
|
114
125
|
function generateFilesForApi(api) {
|
|
115
126
|
let files = [];
|
|
116
|
-
files.push(jsonFile(
|
|
127
|
+
files.push(jsonFile(`${api.pathPrefix}/api/available_translations.json`, api.availableTranslations, true));
|
|
117
128
|
for (let translationBooks of api.translationBooks) {
|
|
118
129
|
files.push(jsonFile(translationBooks.translation.listOfBooksApiLink, translationBooks));
|
|
119
130
|
}
|
|
@@ -125,27 +136,50 @@ function generateFilesForApi(api) {
|
|
|
125
136
|
}
|
|
126
137
|
return files;
|
|
127
138
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Generates the output files for the given datasets.
|
|
141
|
+
* @param datasets The datasets to generate the output files for.
|
|
142
|
+
* @param options The options for generating the API files.
|
|
143
|
+
*/
|
|
144
|
+
async function* generateOutputFilesFromDatasets(datasets, options) {
|
|
145
|
+
for await (let dataset of datasets) {
|
|
146
|
+
const api = generateApiForDataset(dataset, options);
|
|
147
|
+
const files = generateFilesForApi(api);
|
|
148
|
+
yield files;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Gets the API Link for the list of books endpoint for a translation.
|
|
153
|
+
* @param translationId The ID of the translation.
|
|
154
|
+
* @returns
|
|
155
|
+
*/
|
|
156
|
+
function listOfBooksApiLink(translationId, prefix = '') {
|
|
157
|
+
return `${prefix}/api/${translationId}/books.json`;
|
|
131
158
|
}
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Getes the API link for a book chapter.
|
|
161
|
+
* @param translationId The ID of the translation.
|
|
162
|
+
* @param commonName The name of the book.
|
|
163
|
+
* @param chapterNumber The number of the book.
|
|
164
|
+
* @param extension The extension of the file.
|
|
165
|
+
*/
|
|
166
|
+
function bookChapterApiLink(translationId, commonName, chapterNumber, extension, prefix = '') {
|
|
167
|
+
return `${prefix}/api/${translationId}/${replaceSpacesWithUnderscores(commonName)}/${chapterNumber}.${extension}`;
|
|
134
168
|
}
|
|
135
|
-
function bookChapterAudioApiLink(translationId, bookId, chapterNumber, reader) {
|
|
136
|
-
return
|
|
169
|
+
function bookChapterAudioApiLink(translationId, bookId, chapterNumber, reader, prefix = '') {
|
|
170
|
+
return `${prefix}/api/${translationId}/${replaceSpacesWithUnderscores(bookId)}/${chapterNumber}.${reader}.mp3`;
|
|
137
171
|
}
|
|
138
172
|
function jsonFile(path, content, mergable) {
|
|
139
173
|
return {
|
|
140
174
|
path,
|
|
141
175
|
content,
|
|
142
|
-
mergable
|
|
176
|
+
mergable,
|
|
143
177
|
};
|
|
144
178
|
}
|
|
145
179
|
function downloadedFile(path, url) {
|
|
146
180
|
return {
|
|
147
181
|
path,
|
|
148
|
-
content: () => fetch(url).then(response => response.body),
|
|
182
|
+
content: () => fetch(url).then((response) => response.body),
|
|
149
183
|
};
|
|
150
184
|
}
|
|
151
185
|
function replaceSpacesWithUnderscores(str) {
|
package/generation/api.spec.js
CHANGED
|
@@ -28,23 +28,23 @@ describe('generateApiForDataset()', () => {
|
|
|
28
28
|
language: 'eng',
|
|
29
29
|
direction: 'ltr',
|
|
30
30
|
licenseUrl: 'https://berean.bible/terms.htm',
|
|
31
|
-
website: 'https://berean.bible'
|
|
31
|
+
website: 'https://berean.bible',
|
|
32
32
|
};
|
|
33
33
|
let inputFiles = [
|
|
34
34
|
{
|
|
35
35
|
fileType: 'usfm',
|
|
36
36
|
metadata: {
|
|
37
|
-
translation: translation1
|
|
37
|
+
translation: translation1,
|
|
38
38
|
},
|
|
39
|
-
content: firstXLines(_01GENBSB_usfm_1.default, 13)
|
|
39
|
+
content: firstXLines(_01GENBSB_usfm_1.default, 13),
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
fileType: 'usfm',
|
|
43
43
|
metadata: {
|
|
44
|
-
translation: translation1
|
|
44
|
+
translation: translation1,
|
|
45
45
|
},
|
|
46
|
-
content: firstXLines(_02EXOBSB_usfm_1.default, 14)
|
|
47
|
-
}
|
|
46
|
+
content: firstXLines(_02EXOBSB_usfm_1.default, 14),
|
|
47
|
+
},
|
|
48
48
|
];
|
|
49
49
|
const dataset = (0, dataset_1.generateDataset)(inputFiles, new linkedom_1.DOMParser());
|
|
50
50
|
const generated = (0, api_1.generateApiForDataset)(dataset);
|
|
@@ -59,9 +59,7 @@ describe('generateApiForDataset()', () => {
|
|
|
59
59
|
textDirection: 'ltr',
|
|
60
60
|
licenseUrl: 'https://berean.bible/terms.htm',
|
|
61
61
|
website: 'https://berean.bible',
|
|
62
|
-
availableFormats: [
|
|
63
|
-
'json'
|
|
64
|
-
],
|
|
62
|
+
availableFormats: ['json'],
|
|
65
63
|
listOfBooksApiLink: '/api/bsb/books.json',
|
|
66
64
|
numberOfBooks: 2,
|
|
67
65
|
totalNumberOfChapters: 2,
|
|
@@ -71,9 +69,7 @@ describe('generateApiForDataset()', () => {
|
|
|
71
69
|
};
|
|
72
70
|
expect(tree).toEqual({
|
|
73
71
|
'/api/available_translations.json': {
|
|
74
|
-
translations: [
|
|
75
|
-
expectedTranslation
|
|
76
|
-
]
|
|
72
|
+
translations: [expectedTranslation],
|
|
77
73
|
},
|
|
78
74
|
'/api/bsb/books.json': {
|
|
79
75
|
translation: expectedTranslation,
|
|
@@ -99,8 +95,8 @@ describe('generateApiForDataset()', () => {
|
|
|
99
95
|
totalNumberOfVerses: 2,
|
|
100
96
|
firstChapterApiLink: '/api/bsb/EXO/1.json',
|
|
101
97
|
lastChapterApiLink: '/api/bsb/EXO/1.json',
|
|
102
|
-
}
|
|
103
|
-
]
|
|
98
|
+
},
|
|
99
|
+
],
|
|
104
100
|
},
|
|
105
101
|
'/api/bsb/GEN/1.json': {
|
|
106
102
|
translation: expectedTranslation,
|
|
@@ -113,7 +109,7 @@ describe('generateApiForDataset()', () => {
|
|
|
113
109
|
numberOfChapters: 1,
|
|
114
110
|
totalNumberOfVerses: 2,
|
|
115
111
|
firstChapterApiLink: '/api/bsb/GEN/1.json',
|
|
116
|
-
lastChapterApiLink: '/api/bsb/GEN/1.json'
|
|
112
|
+
lastChapterApiLink: '/api/bsb/GEN/1.json',
|
|
117
113
|
},
|
|
118
114
|
thisChapterLink: '/api/bsb/GEN/1.json',
|
|
119
115
|
thisChapterAudioLinks: {},
|
|
@@ -127,33 +123,31 @@ describe('generateApiForDataset()', () => {
|
|
|
127
123
|
content: [
|
|
128
124
|
{
|
|
129
125
|
type: 'heading',
|
|
130
|
-
content: [
|
|
131
|
-
'The Creation'
|
|
132
|
-
]
|
|
126
|
+
content: ['The Creation'],
|
|
133
127
|
},
|
|
134
128
|
{
|
|
135
|
-
type: 'line_break'
|
|
129
|
+
type: 'line_break',
|
|
136
130
|
},
|
|
137
131
|
{
|
|
138
132
|
type: 'verse',
|
|
139
133
|
number: 1,
|
|
140
134
|
content: [
|
|
141
|
-
'In the beginning God created the heavens and the earth.'
|
|
135
|
+
'In the beginning God created the heavens and the earth.',
|
|
142
136
|
],
|
|
143
137
|
},
|
|
144
138
|
{
|
|
145
|
-
type: 'line_break'
|
|
139
|
+
type: 'line_break',
|
|
146
140
|
},
|
|
147
141
|
{
|
|
148
142
|
type: 'verse',
|
|
149
143
|
number: 2,
|
|
150
144
|
content: [
|
|
151
|
-
'Now the earth was formless and void, and darkness was over the surface of the deep. And the Spirit of God was hovering over the surface of the waters.'
|
|
145
|
+
'Now the earth was formless and void, and darkness was over the surface of the deep. And the Spirit of God was hovering over the surface of the waters.',
|
|
152
146
|
],
|
|
153
147
|
},
|
|
154
148
|
],
|
|
155
|
-
footnotes: []
|
|
156
|
-
}
|
|
149
|
+
footnotes: [],
|
|
150
|
+
},
|
|
157
151
|
},
|
|
158
152
|
'/api/bsb/EXO/1.json': {
|
|
159
153
|
translation: expectedTranslation,
|
|
@@ -166,7 +160,7 @@ describe('generateApiForDataset()', () => {
|
|
|
166
160
|
numberOfChapters: 1,
|
|
167
161
|
totalNumberOfVerses: 2,
|
|
168
162
|
firstChapterApiLink: '/api/bsb/EXO/1.json',
|
|
169
|
-
lastChapterApiLink: '/api/bsb/EXO/1.json'
|
|
163
|
+
lastChapterApiLink: '/api/bsb/EXO/1.json',
|
|
170
164
|
},
|
|
171
165
|
thisChapterLink: '/api/bsb/EXO/1.json',
|
|
172
166
|
thisChapterAudioLinks: {},
|
|
@@ -180,34 +174,30 @@ describe('generateApiForDataset()', () => {
|
|
|
180
174
|
content: [
|
|
181
175
|
{
|
|
182
176
|
type: 'heading',
|
|
183
|
-
content: [
|
|
184
|
-
'The Israelites Multiply in Egypt'
|
|
185
|
-
]
|
|
177
|
+
content: ['The Israelites Multiply in Egypt'],
|
|
186
178
|
},
|
|
187
179
|
{
|
|
188
|
-
type: 'line_break'
|
|
180
|
+
type: 'line_break',
|
|
189
181
|
},
|
|
190
182
|
{
|
|
191
183
|
type: 'verse',
|
|
192
184
|
number: 1,
|
|
193
185
|
content: [
|
|
194
|
-
'These are the names of the sons of Israel who went to Egypt with Jacob, each with his family:'
|
|
186
|
+
'These are the names of the sons of Israel who went to Egypt with Jacob, each with his family:',
|
|
195
187
|
],
|
|
196
188
|
},
|
|
197
189
|
{
|
|
198
|
-
type: 'line_break'
|
|
190
|
+
type: 'line_break',
|
|
199
191
|
},
|
|
200
192
|
{
|
|
201
193
|
type: 'verse',
|
|
202
194
|
number: 2,
|
|
203
|
-
content: [
|
|
204
|
-
'Reuben, Simeon, Levi, and Judah;'
|
|
205
|
-
],
|
|
195
|
+
content: ['Reuben, Simeon, Levi, and Judah;'],
|
|
206
196
|
},
|
|
207
197
|
],
|
|
208
|
-
footnotes: []
|
|
209
|
-
}
|
|
210
|
-
}
|
|
198
|
+
footnotes: [],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
211
201
|
});
|
|
212
202
|
// expect(availableTranslations).toEqual({
|
|
213
203
|
// translations: [
|
|
@@ -215,6 +205,189 @@ describe('generateApiForDataset()', () => {
|
|
|
215
205
|
// ]
|
|
216
206
|
// });
|
|
217
207
|
});
|
|
208
|
+
it('should use the given path prefix', () => {
|
|
209
|
+
let translation1 = {
|
|
210
|
+
id: 'bsb',
|
|
211
|
+
name: 'Berean Standard Bible',
|
|
212
|
+
englishName: 'Berean Standard Bible',
|
|
213
|
+
shortName: 'BSB',
|
|
214
|
+
language: 'eng',
|
|
215
|
+
direction: 'ltr',
|
|
216
|
+
licenseUrl: 'https://berean.bible/terms.htm',
|
|
217
|
+
website: 'https://berean.bible',
|
|
218
|
+
};
|
|
219
|
+
let inputFiles = [
|
|
220
|
+
{
|
|
221
|
+
fileType: 'usfm',
|
|
222
|
+
metadata: {
|
|
223
|
+
translation: translation1,
|
|
224
|
+
},
|
|
225
|
+
content: firstXLines(_01GENBSB_usfm_1.default, 13),
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
fileType: 'usfm',
|
|
229
|
+
metadata: {
|
|
230
|
+
translation: translation1,
|
|
231
|
+
},
|
|
232
|
+
content: firstXLines(_02EXOBSB_usfm_1.default, 14),
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
const dataset = (0, dataset_1.generateDataset)(inputFiles, new linkedom_1.DOMParser());
|
|
236
|
+
const generated = (0, api_1.generateApiForDataset)(dataset, {
|
|
237
|
+
pathPrefix: '/hello',
|
|
238
|
+
});
|
|
239
|
+
const files = (0, api_1.generateFilesForApi)(generated);
|
|
240
|
+
const tree = fileTree(files);
|
|
241
|
+
const expectedTranslation = {
|
|
242
|
+
id: 'bsb',
|
|
243
|
+
name: 'Berean Standard Bible',
|
|
244
|
+
englishName: 'Berean Standard Bible',
|
|
245
|
+
shortName: 'BSB',
|
|
246
|
+
language: 'eng',
|
|
247
|
+
textDirection: 'ltr',
|
|
248
|
+
licenseUrl: 'https://berean.bible/terms.htm',
|
|
249
|
+
website: 'https://berean.bible',
|
|
250
|
+
availableFormats: ['json'],
|
|
251
|
+
listOfBooksApiLink: '/hello/api/bsb/books.json',
|
|
252
|
+
numberOfBooks: 2,
|
|
253
|
+
totalNumberOfChapters: 2,
|
|
254
|
+
totalNumberOfVerses: 4,
|
|
255
|
+
languageName: 'English',
|
|
256
|
+
languageEnglishName: 'English',
|
|
257
|
+
};
|
|
258
|
+
expect(tree).toEqual({
|
|
259
|
+
'/hello/api/available_translations.json': {
|
|
260
|
+
translations: [expectedTranslation],
|
|
261
|
+
},
|
|
262
|
+
'/hello/api/bsb/books.json': {
|
|
263
|
+
translation: expectedTranslation,
|
|
264
|
+
books: [
|
|
265
|
+
{
|
|
266
|
+
id: 'GEN',
|
|
267
|
+
name: 'Genesis',
|
|
268
|
+
commonName: 'Genesis',
|
|
269
|
+
title: 'Genesis',
|
|
270
|
+
order: 1,
|
|
271
|
+
numberOfChapters: 1,
|
|
272
|
+
totalNumberOfVerses: 2,
|
|
273
|
+
firstChapterApiLink: '/hello/api/bsb/GEN/1.json',
|
|
274
|
+
lastChapterApiLink: '/hello/api/bsb/GEN/1.json',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: 'EXO',
|
|
278
|
+
name: 'Exodus',
|
|
279
|
+
commonName: 'Exodus',
|
|
280
|
+
title: 'Exodus',
|
|
281
|
+
order: 2,
|
|
282
|
+
numberOfChapters: 1,
|
|
283
|
+
totalNumberOfVerses: 2,
|
|
284
|
+
firstChapterApiLink: '/hello/api/bsb/EXO/1.json',
|
|
285
|
+
lastChapterApiLink: '/hello/api/bsb/EXO/1.json',
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
},
|
|
289
|
+
'/hello/api/bsb/GEN/1.json': {
|
|
290
|
+
translation: expectedTranslation,
|
|
291
|
+
book: {
|
|
292
|
+
id: 'GEN',
|
|
293
|
+
name: 'Genesis',
|
|
294
|
+
commonName: 'Genesis',
|
|
295
|
+
title: 'Genesis',
|
|
296
|
+
order: 1,
|
|
297
|
+
numberOfChapters: 1,
|
|
298
|
+
totalNumberOfVerses: 2,
|
|
299
|
+
firstChapterApiLink: '/hello/api/bsb/GEN/1.json',
|
|
300
|
+
lastChapterApiLink: '/hello/api/bsb/GEN/1.json',
|
|
301
|
+
},
|
|
302
|
+
thisChapterLink: '/hello/api/bsb/GEN/1.json',
|
|
303
|
+
thisChapterAudioLinks: {},
|
|
304
|
+
nextChapterApiLink: '/hello/api/bsb/EXO/1.json',
|
|
305
|
+
nextChapterAudioLinks: {},
|
|
306
|
+
previousChapterApiLink: null,
|
|
307
|
+
previousChapterAudioLinks: null,
|
|
308
|
+
numberOfVerses: 2,
|
|
309
|
+
chapter: {
|
|
310
|
+
number: 1,
|
|
311
|
+
content: [
|
|
312
|
+
{
|
|
313
|
+
type: 'heading',
|
|
314
|
+
content: ['The Creation'],
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
type: 'line_break',
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
type: 'verse',
|
|
321
|
+
number: 1,
|
|
322
|
+
content: [
|
|
323
|
+
'In the beginning God created the heavens and the earth.',
|
|
324
|
+
],
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
type: 'line_break',
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
type: 'verse',
|
|
331
|
+
number: 2,
|
|
332
|
+
content: [
|
|
333
|
+
'Now the earth was formless and void, and darkness was over the surface of the deep. And the Spirit of God was hovering over the surface of the waters.',
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
footnotes: [],
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
'/hello/api/bsb/EXO/1.json': {
|
|
341
|
+
translation: expectedTranslation,
|
|
342
|
+
book: {
|
|
343
|
+
id: 'EXO',
|
|
344
|
+
name: 'Exodus',
|
|
345
|
+
commonName: 'Exodus',
|
|
346
|
+
title: 'Exodus',
|
|
347
|
+
order: 2,
|
|
348
|
+
numberOfChapters: 1,
|
|
349
|
+
totalNumberOfVerses: 2,
|
|
350
|
+
firstChapterApiLink: '/hello/api/bsb/EXO/1.json',
|
|
351
|
+
lastChapterApiLink: '/hello/api/bsb/EXO/1.json',
|
|
352
|
+
},
|
|
353
|
+
thisChapterLink: '/hello/api/bsb/EXO/1.json',
|
|
354
|
+
thisChapterAudioLinks: {},
|
|
355
|
+
nextChapterApiLink: null,
|
|
356
|
+
nextChapterAudioLinks: null,
|
|
357
|
+
previousChapterApiLink: '/hello/api/bsb/GEN/1.json',
|
|
358
|
+
previousChapterAudioLinks: {},
|
|
359
|
+
numberOfVerses: 2,
|
|
360
|
+
chapter: {
|
|
361
|
+
number: 1,
|
|
362
|
+
content: [
|
|
363
|
+
{
|
|
364
|
+
type: 'heading',
|
|
365
|
+
content: ['The Israelites Multiply in Egypt'],
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
type: 'line_break',
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
type: 'verse',
|
|
372
|
+
number: 1,
|
|
373
|
+
content: [
|
|
374
|
+
'These are the names of the sons of Israel who went to Egypt with Jacob, each with his family:',
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
type: 'line_break',
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
type: 'verse',
|
|
382
|
+
number: 2,
|
|
383
|
+
content: ['Reuben, Simeon, Levi, and Judah;'],
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
footnotes: [],
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
});
|
|
218
391
|
it('should use underscores for spaces in the book name', () => {
|
|
219
392
|
let translation1 = {
|
|
220
393
|
id: 'bsb',
|
|
@@ -224,15 +397,15 @@ describe('generateApiForDataset()', () => {
|
|
|
224
397
|
language: 'eng',
|
|
225
398
|
direction: 'ltr',
|
|
226
399
|
licenseUrl: 'https://berean.bible/terms.htm',
|
|
227
|
-
website: 'https://berean.bible'
|
|
400
|
+
website: 'https://berean.bible',
|
|
228
401
|
};
|
|
229
402
|
let inputFiles = [
|
|
230
403
|
{
|
|
231
404
|
fileType: 'usfm',
|
|
232
405
|
metadata: {
|
|
233
|
-
translation: translation1
|
|
406
|
+
translation: translation1,
|
|
234
407
|
},
|
|
235
|
-
content: firstXLines(_131CHBSB_usfm_1.default, 11)
|
|
408
|
+
content: firstXLines(_131CHBSB_usfm_1.default, 11),
|
|
236
409
|
},
|
|
237
410
|
];
|
|
238
411
|
const dataset = (0, dataset_1.generateDataset)(inputFiles, new linkedom_1.DOMParser());
|
|
@@ -250,9 +423,7 @@ describe('generateApiForDataset()', () => {
|
|
|
250
423
|
textDirection: 'ltr',
|
|
251
424
|
licenseUrl: 'https://berean.bible/terms.htm',
|
|
252
425
|
website: 'https://berean.bible',
|
|
253
|
-
availableFormats: [
|
|
254
|
-
'json'
|
|
255
|
-
],
|
|
426
|
+
availableFormats: ['json'],
|
|
256
427
|
listOfBooksApiLink: '/api/bsb/books.json',
|
|
257
428
|
numberOfBooks: 1,
|
|
258
429
|
totalNumberOfChapters: 1,
|
|
@@ -262,9 +433,7 @@ describe('generateApiForDataset()', () => {
|
|
|
262
433
|
};
|
|
263
434
|
expect(tree).toEqual({
|
|
264
435
|
'/api/available_translations.json': {
|
|
265
|
-
translations: [
|
|
266
|
-
expectedTranslation
|
|
267
|
-
]
|
|
436
|
+
translations: [expectedTranslation],
|
|
268
437
|
},
|
|
269
438
|
'/api/bsb/books.json': {
|
|
270
439
|
translation: expectedTranslation,
|
|
@@ -280,7 +449,7 @@ describe('generateApiForDataset()', () => {
|
|
|
280
449
|
firstChapterApiLink: '/api/bsb/1_Chronicles/1.json',
|
|
281
450
|
lastChapterApiLink: '/api/bsb/1_Chronicles/1.json',
|
|
282
451
|
},
|
|
283
|
-
]
|
|
452
|
+
],
|
|
284
453
|
},
|
|
285
454
|
'/api/bsb/1_Chronicles/1.json': {
|
|
286
455
|
translation: expectedTranslation,
|
|
@@ -293,9 +462,9 @@ describe('generateApiForDataset()', () => {
|
|
|
293
462
|
totalNumberOfVerses: 1,
|
|
294
463
|
order: 13,
|
|
295
464
|
firstChapterApiLink: '/api/bsb/1_Chronicles/1.json',
|
|
296
|
-
lastChapterApiLink: '/api/bsb/1_Chronicles/1.json'
|
|
465
|
+
lastChapterApiLink: '/api/bsb/1_Chronicles/1.json',
|
|
297
466
|
},
|
|
298
|
-
thisChapterLink:
|
|
467
|
+
thisChapterLink: '/api/bsb/1_Chronicles/1.json',
|
|
299
468
|
thisChapterAudioLinks: {},
|
|
300
469
|
nextChapterApiLink: null,
|
|
301
470
|
nextChapterAudioLinks: null,
|
|
@@ -307,23 +476,19 @@ describe('generateApiForDataset()', () => {
|
|
|
307
476
|
content: [
|
|
308
477
|
{
|
|
309
478
|
type: 'heading',
|
|
310
|
-
content: [
|
|
311
|
-
'From Adam to Abraham'
|
|
312
|
-
]
|
|
479
|
+
content: ['From Adam to Abraham'],
|
|
313
480
|
},
|
|
314
481
|
{
|
|
315
|
-
type: 'line_break'
|
|
482
|
+
type: 'line_break',
|
|
316
483
|
},
|
|
317
484
|
{
|
|
318
485
|
type: 'verse',
|
|
319
486
|
number: 1,
|
|
320
|
-
content: [
|
|
321
|
-
'Adam, Seth, Enosh,'
|
|
322
|
-
],
|
|
487
|
+
content: ['Adam, Seth, Enosh,'],
|
|
323
488
|
},
|
|
324
489
|
],
|
|
325
|
-
footnotes: []
|
|
326
|
-
}
|
|
490
|
+
footnotes: [],
|
|
491
|
+
},
|
|
327
492
|
},
|
|
328
493
|
});
|
|
329
494
|
// expect(availableTranslations).toEqual({
|
|
@@ -341,15 +506,15 @@ describe('generateApiForDataset()', () => {
|
|
|
341
506
|
language: 'spa',
|
|
342
507
|
direction: 'ltr',
|
|
343
508
|
licenseUrl: 'https://berean.bible/terms.htm',
|
|
344
|
-
website: 'https://berean.bible'
|
|
509
|
+
website: 'https://berean.bible',
|
|
345
510
|
};
|
|
346
511
|
let inputFiles = [
|
|
347
512
|
{
|
|
348
513
|
fileType: 'usfm',
|
|
349
514
|
metadata: {
|
|
350
|
-
translation: translation1
|
|
515
|
+
translation: translation1,
|
|
351
516
|
},
|
|
352
|
-
content: firstXLines(_131CHBSB_usfm_1.default, 11)
|
|
517
|
+
content: firstXLines(_131CHBSB_usfm_1.default, 11),
|
|
353
518
|
},
|
|
354
519
|
];
|
|
355
520
|
const dataset = (0, dataset_1.generateDataset)(inputFiles, new linkedom_1.DOMParser());
|
|
@@ -367,9 +532,7 @@ describe('generateApiForDataset()', () => {
|
|
|
367
532
|
textDirection: 'ltr',
|
|
368
533
|
licenseUrl: 'https://berean.bible/terms.htm',
|
|
369
534
|
website: 'https://berean.bible',
|
|
370
|
-
availableFormats: [
|
|
371
|
-
'json'
|
|
372
|
-
],
|
|
535
|
+
availableFormats: ['json'],
|
|
373
536
|
listOfBooksApiLink: '/api/bsb/books.json',
|
|
374
537
|
numberOfBooks: 1,
|
|
375
538
|
totalNumberOfChapters: 1,
|
|
@@ -379,9 +542,7 @@ describe('generateApiForDataset()', () => {
|
|
|
379
542
|
};
|
|
380
543
|
expect(tree).toEqual({
|
|
381
544
|
'/api/available_translations.json': {
|
|
382
|
-
translations: [
|
|
383
|
-
expectedTranslation
|
|
384
|
-
]
|
|
545
|
+
translations: [expectedTranslation],
|
|
385
546
|
},
|
|
386
547
|
'/api/bsb/books.json': {
|
|
387
548
|
translation: expectedTranslation,
|
|
@@ -397,7 +558,7 @@ describe('generateApiForDataset()', () => {
|
|
|
397
558
|
firstChapterApiLink: '/api/bsb/1_Chronicles/1.json',
|
|
398
559
|
lastChapterApiLink: '/api/bsb/1_Chronicles/1.json',
|
|
399
560
|
},
|
|
400
|
-
]
|
|
561
|
+
],
|
|
401
562
|
},
|
|
402
563
|
'/api/bsb/1_Chronicles/1.json': {
|
|
403
564
|
translation: expectedTranslation,
|
|
@@ -410,9 +571,9 @@ describe('generateApiForDataset()', () => {
|
|
|
410
571
|
totalNumberOfVerses: 1,
|
|
411
572
|
order: 13,
|
|
412
573
|
firstChapterApiLink: '/api/bsb/1_Chronicles/1.json',
|
|
413
|
-
lastChapterApiLink: '/api/bsb/1_Chronicles/1.json'
|
|
574
|
+
lastChapterApiLink: '/api/bsb/1_Chronicles/1.json',
|
|
414
575
|
},
|
|
415
|
-
thisChapterLink:
|
|
576
|
+
thisChapterLink: '/api/bsb/1_Chronicles/1.json',
|
|
416
577
|
thisChapterAudioLinks: {},
|
|
417
578
|
nextChapterApiLink: null,
|
|
418
579
|
nextChapterAudioLinks: null,
|
|
@@ -424,23 +585,19 @@ describe('generateApiForDataset()', () => {
|
|
|
424
585
|
content: [
|
|
425
586
|
{
|
|
426
587
|
type: 'heading',
|
|
427
|
-
content: [
|
|
428
|
-
'From Adam to Abraham'
|
|
429
|
-
]
|
|
588
|
+
content: ['From Adam to Abraham'],
|
|
430
589
|
},
|
|
431
590
|
{
|
|
432
|
-
type: 'line_break'
|
|
591
|
+
type: 'line_break',
|
|
433
592
|
},
|
|
434
593
|
{
|
|
435
594
|
type: 'verse',
|
|
436
595
|
number: 1,
|
|
437
|
-
content: [
|
|
438
|
-
'Adam, Seth, Enosh,'
|
|
439
|
-
],
|
|
596
|
+
content: ['Adam, Seth, Enosh,'],
|
|
440
597
|
},
|
|
441
598
|
],
|
|
442
|
-
footnotes: []
|
|
443
|
-
}
|
|
599
|
+
footnotes: [],
|
|
600
|
+
},
|
|
444
601
|
},
|
|
445
602
|
});
|
|
446
603
|
// expect(availableTranslations).toEqual({
|
package/generation/dataset.d.ts
CHANGED
package/generation/dataset.js
CHANGED
|
@@ -63,7 +63,7 @@ function generateDataset(files, parser = new globalThis.DOMParser()) {
|
|
|
63
63
|
translation = {
|
|
64
64
|
...(0, lodash_1.omit)(file.metadata.translation, 'direction'),
|
|
65
65
|
textDirection: file.metadata.translation.direction,
|
|
66
|
-
books: []
|
|
66
|
+
books: [],
|
|
67
67
|
};
|
|
68
68
|
output.translations.push(translation);
|
|
69
69
|
parsedTranslations.set(file.metadata.translation.id, translation);
|
|
@@ -88,14 +88,14 @@ function generateDataset(files, parser = new globalThis.DOMParser()) {
|
|
|
88
88
|
chapter: {
|
|
89
89
|
number: content.number,
|
|
90
90
|
content: content.content,
|
|
91
|
-
footnotes: content.footnotes
|
|
91
|
+
footnotes: content.footnotes,
|
|
92
92
|
},
|
|
93
|
-
thisChapterAudioLinks: (0, audio_1.getAudioUrlsForChapter)(translation.id, id, content.number)
|
|
93
|
+
thisChapterAudioLinks: (0, audio_1.getAudioUrlsForChapter)(translation.id, id, content.number),
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
book.chapters = (0, lodash_1.sortBy)(book.chapters, c => c.chapter.number);
|
|
98
|
-
const index = (0, lodash_1.sortedIndexBy)(translation.books, book, b => b.order);
|
|
97
|
+
book.chapters = (0, lodash_1.sortBy)(book.chapters, (c) => c.chapter.number);
|
|
98
|
+
const index = (0, lodash_1.sortedIndexBy)(translation.books, book, (b) => b.order);
|
|
99
99
|
translation.books.splice(index, 0, book);
|
|
100
100
|
}
|
|
101
101
|
catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helloao/tools",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "A set of tools for managing HelloAO's Free Bible API",
|
|
5
5
|
"module": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"lodash": "4.17.21",
|
|
11
|
-
"all-iso-language-codes": "1.0.17",
|
|
12
11
|
"zod": "^3.23.8"
|
|
13
12
|
},
|
|
14
13
|
"devDependencies": {
|
package/parser/usx-parser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Chapter, ChapterContent, FootnoteReference, ParseTree, Verse, Text, HebrewSubtitle, InlineLineBreak } from
|
|
2
|
-
import { RewindableIterator } from
|
|
1
|
+
import { Chapter, ChapterContent, FootnoteReference, ParseTree, Verse, Text, HebrewSubtitle, InlineLineBreak } from './types';
|
|
2
|
+
import { RewindableIterator } from './iterators';
|
|
3
3
|
/**
|
|
4
4
|
* The version of the parser.
|
|
5
5
|
* Used to determine whether input files need to be re-parsed.
|
package/parser/usx-parser.js
CHANGED
|
@@ -32,7 +32,7 @@ class USXParser {
|
|
|
32
32
|
const usxElement = doc.documentElement;
|
|
33
33
|
let root = {
|
|
34
34
|
type: 'root',
|
|
35
|
-
content: []
|
|
35
|
+
content: [],
|
|
36
36
|
};
|
|
37
37
|
const bookElement = usxElement.querySelector('book[code]');
|
|
38
38
|
if (!bookElement) {
|
|
@@ -51,7 +51,10 @@ class USXParser {
|
|
|
51
51
|
// const title2 = usxElement.querySelector('para[style="mt2"]');
|
|
52
52
|
// const title3 = usxElement.querySelector('para[style="mt3"]');
|
|
53
53
|
if (titles.length > 0) {
|
|
54
|
-
root.title = [...titles]
|
|
54
|
+
root.title = [...titles]
|
|
55
|
+
.map((t) => t.textContent)
|
|
56
|
+
.filter((t) => t)
|
|
57
|
+
.join(' ');
|
|
55
58
|
}
|
|
56
59
|
for (let content of this.iterateRootContent(usxElement)) {
|
|
57
60
|
root.content.push(content);
|
|
@@ -85,10 +88,13 @@ class USXParser {
|
|
|
85
88
|
}
|
|
86
89
|
else if (child.nodeName === 'para') {
|
|
87
90
|
const style = child.getAttribute('style');
|
|
88
|
-
if (style === 's1' ||
|
|
91
|
+
if (style === 's1' ||
|
|
92
|
+
style === 's2' ||
|
|
93
|
+
style === 's3' ||
|
|
94
|
+
style === 's4') {
|
|
89
95
|
yield {
|
|
90
96
|
type: 'heading',
|
|
91
|
-
content: child.textContent ? [child.textContent] : []
|
|
97
|
+
content: child.textContent ? [child.textContent] : [],
|
|
92
98
|
};
|
|
93
99
|
}
|
|
94
100
|
}
|
|
@@ -108,10 +114,15 @@ class USXParser {
|
|
|
108
114
|
}
|
|
109
115
|
else if (element.nodeName === 'para') {
|
|
110
116
|
const style = element.getAttribute('style');
|
|
111
|
-
if (style === 's1' ||
|
|
117
|
+
if (style === 's1' ||
|
|
118
|
+
style === 's2' ||
|
|
119
|
+
style === 's3' ||
|
|
120
|
+
style === 's4') {
|
|
112
121
|
yield {
|
|
113
122
|
type: 'heading',
|
|
114
|
-
content: element.textContent
|
|
123
|
+
content: element.textContent
|
|
124
|
+
? [element.textContent]
|
|
125
|
+
: [],
|
|
115
126
|
};
|
|
116
127
|
}
|
|
117
128
|
else if (style === 'b') {
|
|
@@ -145,8 +156,18 @@ class USXParser {
|
|
|
145
156
|
let descriptive = null;
|
|
146
157
|
if (parent.nodeName === 'para') {
|
|
147
158
|
const style = parent.getAttribute('style');
|
|
148
|
-
if (style === 'q1' ||
|
|
149
|
-
|
|
159
|
+
if (style === 'q1' ||
|
|
160
|
+
style === 'q2' ||
|
|
161
|
+
style === 'q3' ||
|
|
162
|
+
style === 'q4') {
|
|
163
|
+
poem =
|
|
164
|
+
style === 'q1'
|
|
165
|
+
? 1
|
|
166
|
+
: style === 'q2'
|
|
167
|
+
? 2
|
|
168
|
+
: style === 'q3'
|
|
169
|
+
? 3
|
|
170
|
+
: 4;
|
|
150
171
|
}
|
|
151
172
|
else if (style === 'd') {
|
|
152
173
|
descriptive = true;
|
|
@@ -156,7 +177,7 @@ class USXParser {
|
|
|
156
177
|
if (poem !== null || descriptive !== null) {
|
|
157
178
|
if (typeof content === 'string') {
|
|
158
179
|
let text = {
|
|
159
|
-
text: content
|
|
180
|
+
text: content,
|
|
160
181
|
};
|
|
161
182
|
if (poem !== null) {
|
|
162
183
|
text.poem = poem;
|
|
@@ -168,7 +189,7 @@ class USXParser {
|
|
|
168
189
|
}
|
|
169
190
|
else {
|
|
170
191
|
let text = {
|
|
171
|
-
...content
|
|
192
|
+
...content,
|
|
172
193
|
};
|
|
173
194
|
if ('text' in text) {
|
|
174
195
|
if (poem !== null) {
|
|
@@ -191,7 +212,7 @@ class USXParser {
|
|
|
191
212
|
const verse = {
|
|
192
213
|
type: 'verse',
|
|
193
214
|
number: parseInt(element.getAttribute('number') || '0', 10),
|
|
194
|
-
content: []
|
|
215
|
+
content: [],
|
|
195
216
|
};
|
|
196
217
|
for (let content of this.iterateVerseContent(chapter, verse, nodes)) {
|
|
197
218
|
addOrJoin(verse.content, content);
|
|
@@ -202,7 +223,7 @@ class USXParser {
|
|
|
202
223
|
*parseHebrewSubtitle(para, chapter, nodes) {
|
|
203
224
|
const subtitle = {
|
|
204
225
|
type: 'hebrew_subtitle',
|
|
205
|
-
content: []
|
|
226
|
+
content: [],
|
|
206
227
|
};
|
|
207
228
|
for (let content of this.iterateHebrewSubtitleContent(para, chapter, nodes)) {
|
|
208
229
|
if (typeof content === 'object' && 'number' in content) {
|
|
@@ -241,12 +262,14 @@ class USXParser {
|
|
|
241
262
|
else if (node instanceof Element && node.nodeName === 'char') {
|
|
242
263
|
yield* this.iterateChar(nodes, node);
|
|
243
264
|
}
|
|
244
|
-
else if (node instanceof Element &&
|
|
265
|
+
else if (node instanceof Element &&
|
|
266
|
+
node.nodeName === 'para' &&
|
|
267
|
+
node.getAttribute('style') === 'b') {
|
|
245
268
|
for (let _ of (0, iterators_1.children)(nodes, node)) {
|
|
246
269
|
// iterate through all the children to prevent iterating over them multiple times
|
|
247
270
|
}
|
|
248
271
|
yield {
|
|
249
|
-
lineBreak: true
|
|
272
|
+
lineBreak: true,
|
|
250
273
|
};
|
|
251
274
|
}
|
|
252
275
|
else if (node.nodeType === NodeType.Text) {
|
|
@@ -259,7 +282,7 @@ class USXParser {
|
|
|
259
282
|
if (style === 'wj') {
|
|
260
283
|
yield {
|
|
261
284
|
text,
|
|
262
|
-
wordsOfJesus: true
|
|
285
|
+
wordsOfJesus: true,
|
|
263
286
|
};
|
|
264
287
|
}
|
|
265
288
|
else {
|
|
@@ -286,12 +309,12 @@ class USXParser {
|
|
|
286
309
|
text,
|
|
287
310
|
reference: {
|
|
288
311
|
chapter: chapter.number,
|
|
289
|
-
verse: verse?.number ?? 0
|
|
290
|
-
}
|
|
312
|
+
verse: verse?.number ?? 0,
|
|
313
|
+
},
|
|
291
314
|
};
|
|
292
315
|
chapter.footnotes.push(note);
|
|
293
316
|
yield {
|
|
294
|
-
noteId: note.noteId
|
|
317
|
+
noteId: note.noteId,
|
|
295
318
|
};
|
|
296
319
|
}
|
|
297
320
|
else {
|
|
@@ -312,7 +335,7 @@ class USXParser {
|
|
|
312
335
|
if (style === 'wj') {
|
|
313
336
|
yield {
|
|
314
337
|
text,
|
|
315
|
-
wordsOfJesus: true
|
|
338
|
+
wordsOfJesus: true,
|
|
316
339
|
};
|
|
317
340
|
}
|
|
318
341
|
else {
|
|
@@ -329,15 +352,31 @@ const ignoredParaStyles = new Set([
|
|
|
329
352
|
// <para> Identification [exclude all] - Running headings & table of contents
|
|
330
353
|
'ide', // See https://github.com/schierlm/BibleMultiConverter/issues/67
|
|
331
354
|
'rem', // Remarks (valid in schema though missed in docs)
|
|
332
|
-
'h',
|
|
333
|
-
'
|
|
334
|
-
'
|
|
355
|
+
'h',
|
|
356
|
+
'h1',
|
|
357
|
+
'h2',
|
|
358
|
+
'h3',
|
|
359
|
+
'h4',
|
|
360
|
+
'toc1',
|
|
361
|
+
'toc2',
|
|
362
|
+
'toc3',
|
|
363
|
+
'toca1',
|
|
364
|
+
'toca2',
|
|
365
|
+
'toca3',
|
|
335
366
|
/* <para> Introductions [exclude all] - Introductionary (non-biblical) content
|
|
336
367
|
Which might be helpful in a printed book, but intro material in apps is usually bad UX,
|
|
337
368
|
and users that really care can research a translations methodology themselves
|
|
338
369
|
*/
|
|
339
|
-
'imt',
|
|
340
|
-
'
|
|
370
|
+
'imt',
|
|
371
|
+
'imt1',
|
|
372
|
+
'imt2',
|
|
373
|
+
'imt3',
|
|
374
|
+
'imt4',
|
|
375
|
+
'is',
|
|
376
|
+
'is1',
|
|
377
|
+
'is2',
|
|
378
|
+
'is3',
|
|
379
|
+
'is4',
|
|
341
380
|
'ip',
|
|
342
381
|
'ipi',
|
|
343
382
|
'im',
|
|
@@ -345,19 +384,39 @@ const ignoredParaStyles = new Set([
|
|
|
345
384
|
'ipq',
|
|
346
385
|
'imq',
|
|
347
386
|
'ipr',
|
|
348
|
-
'iq',
|
|
387
|
+
'iq',
|
|
388
|
+
'iq1',
|
|
389
|
+
'iq2',
|
|
390
|
+
'iq3',
|
|
391
|
+
'iq4',
|
|
349
392
|
'ib',
|
|
350
|
-
'ili',
|
|
393
|
+
'ili',
|
|
394
|
+
'ili1',
|
|
395
|
+
'ili2',
|
|
396
|
+
'ili3',
|
|
397
|
+
'ili4',
|
|
351
398
|
'iot',
|
|
352
|
-
'io',
|
|
399
|
+
'io',
|
|
400
|
+
'io1',
|
|
401
|
+
'io2',
|
|
402
|
+
'io3',
|
|
403
|
+
'io4',
|
|
353
404
|
'iex',
|
|
354
405
|
'imte',
|
|
355
406
|
'ie',
|
|
356
407
|
/* <para> Headings [exclude some] - Exclude book & chapter headings but keep section headings
|
|
357
408
|
Not excluded: ms# | mr | s# | sr | d | sp | sd#
|
|
358
409
|
*/
|
|
359
|
-
'mt',
|
|
360
|
-
'
|
|
410
|
+
'mt',
|
|
411
|
+
'mt1',
|
|
412
|
+
'mt2',
|
|
413
|
+
'mt3',
|
|
414
|
+
'mt4',
|
|
415
|
+
'mte',
|
|
416
|
+
'mte1',
|
|
417
|
+
'mte2',
|
|
418
|
+
'mte3',
|
|
419
|
+
'mte4',
|
|
361
420
|
'cl',
|
|
362
421
|
'cd', // Non-biblical chapter summary, more than heading
|
|
363
422
|
'r', // Parallels to be provided by external data
|
|
@@ -368,7 +427,7 @@ function* iterateCharContent(char) {
|
|
|
368
427
|
if (style === 'wj') {
|
|
369
428
|
yield {
|
|
370
429
|
text,
|
|
371
|
-
wordsOfJesus: true
|
|
430
|
+
wordsOfJesus: true,
|
|
372
431
|
};
|
|
373
432
|
}
|
|
374
433
|
else {
|
|
@@ -409,7 +468,9 @@ function addOrJoin(array, value) {
|
|
|
409
468
|
if (typeof last === 'string' && typeof value === 'string') {
|
|
410
469
|
array[array.length - 1] = last + value;
|
|
411
470
|
}
|
|
412
|
-
else if (isVerseText(last) &&
|
|
471
|
+
else if (isVerseText(last) &&
|
|
472
|
+
isVerseText(value) &&
|
|
473
|
+
hasSameFormatting(last, value)) {
|
|
413
474
|
last.text += value.text;
|
|
414
475
|
}
|
|
415
476
|
else {
|