@helloao/tools 0.0.19 → 0.1.0
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/dist/cjs/generation/dataset.cjs +1 -1
- package/dist/cjs/generation/dataset.cjs.map +2 -2
- package/dist/cjs/parser/codex-parser.cjs +86 -10
- package/dist/cjs/parser/codex-parser.cjs.map +2 -2
- package/dist/esm/generation/dataset.js +1 -1
- package/dist/esm/generation/dataset.js.map +2 -2
- package/dist/esm/parser/codex-parser.js +86 -10
- package/dist/esm/parser/codex-parser.js.map +2 -2
- package/dist/types/parser/codex-parser.d.ts +28 -0
- package/package.json +1 -1
|
@@ -38,7 +38,7 @@ function generateDataset(files, parser = new globalThis.DOMParser(), bookMap) {
|
|
|
38
38
|
};
|
|
39
39
|
let usfmParser = new import_usfm_parser.UsfmParser();
|
|
40
40
|
let usxParser = new import_usx_parser.USXParser(parser);
|
|
41
|
-
let codexParser = new import_codex_parser.CodexParser();
|
|
41
|
+
let codexParser = new import_codex_parser.CodexParser(parser);
|
|
42
42
|
let csvCommentaryParser = new import_commentary_csv_parser.CommentaryCsvParser();
|
|
43
43
|
let tyndaleXmlParser = new import_tyndale_xml_parser.TyndaleXmlParser(parser);
|
|
44
44
|
let parsedTranslations = /* @__PURE__ */ new Map();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../generation/dataset.ts"],
|
|
4
|
-
"sourcesContent": ["import { UsfmParser } from '../parser/usfm-parser.js';\nimport { USXParser } from '../parser/usx-parser.js';\nimport {\n Commentary,\n CommentaryBook,\n CommentaryBookChapter,\n CommentaryChapterData,\n CommentaryProfile,\n InputCommentaryFile,\n InputFile,\n InputFileBase,\n InputTranslationFile,\n MetadataBase,\n Translation,\n TranslationBook,\n TranslationBookChapter,\n Dataset as CommonDataset,\n DatasetBook,\n DatasetBookChapter,\n} from './common-types.js';\nimport {\n bookIdMap as defaultBookIdMap,\n bookOrderMap,\n apocryphaBooks,\n} from './book-order.js';\nimport { omit, sortBy, sortedIndexBy } from 'lodash';\nimport { getAudioUrlsForChapter } from './audio.js';\nimport { CodexParser } from '../parser/codex-parser.js';\nimport { CommentaryCsvParser } from '../parser/commentary-csv-parser.js';\nimport {\n CommentaryParseTree,\n ParseMessage,\n ParseTree,\n} from '../parser/types.js';\nimport { TyndaleXmlParser } from '../parser/tyndale-xml-parser.js';\nimport { getLogger } from '../log.js';\n\n/**\n * Defines an interface that contains generated dataset info.\n */\nexport interface DatasetOutput {\n /**\n * The list of translations that are available in the dataset.\n */\n translations: DatasetTranslation[];\n\n /**\n * The list of commentaries that are available in the dataset.\n */\n commentaries: DatasetCommentary[];\n\n /**\n * The list of datasets that are available in the dataset.\n */\n datasets?: DatasetDataset[];\n\n parseMessages?: {\n [key: string]: ParseMessage[];\n };\n}\n\n/**\n * Defines a translation that is used in the dataset.\n */\nexport interface DatasetTranslation extends Translation {\n /**\n * The list of books that are available for the translation.\n */\n books: DatasetTranslationBook[];\n}\n\n/**\n * Defines a commentary that is used in the dataset.\n */\nexport interface DatasetCommentary extends Commentary {\n /**\n * The list of books that are available for the commentary.\n */\n books: DatasetCommentaryBook[];\n\n /**\n * The list of profiles that are available for the commentary.\n */\n profiles: DatasetCommentaryProfile[];\n}\n\n/**\n * Defines a translation book that is used in the dataset.\n */\nexport interface DatasetTranslationBook extends TranslationBook {\n /**\n * The list of chapters that are available for the book.\n */\n chapters: TranslationBookChapter[];\n}\n\n/**\n * Defines a commentary book that is used in the dataset.\n */\nexport interface DatasetCommentaryBook extends CommentaryBook {\n /**\n * The list of chapters that are available for the book.\n */\n chapters: CommentaryBookChapter[];\n}\n\nexport interface DatasetCommentaryProfile extends CommentaryProfile {\n /**\n * The contents of the profile.\n */\n content: string[];\n}\n\nexport interface DatasetDataset extends CommonDataset {\n books: DatasetDatasetBook[];\n}\n\nexport interface DatasetDatasetBook extends DatasetBook {\n chapters: DatasetBookChapter[];\n}\n\n/**\n * Generates a list of output files from the given list of input files.\n * @param file The list of files.\n * @param parser The parser to use for parsing the files.\n * @param bookMap The map of book IDs to names. If undefined, then a default map will be used.\n */\nexport function generateDataset(\n files: InputFile[],\n parser: DOMParser = new globalThis.DOMParser(),\n bookMap?: Map<string, { commonName: string }> | undefined\n): DatasetOutput {\n const logger = getLogger();\n let output: DatasetOutput = {\n translations: [],\n commentaries: [],\n };\n\n let usfmParser = new UsfmParser();\n let usxParser = new USXParser(parser);\n let codexParser = new CodexParser();\n let csvCommentaryParser = new CommentaryCsvParser();\n let tyndaleXmlParser = new TyndaleXmlParser(parser);\n\n let parsedTranslations = new Map<string, DatasetTranslation>();\n let parsedCommentaries = new Map<string, DatasetCommentary>();\n\n const unknownLanguages = new Set<string>();\n for (let file of files) {\n try {\n let parser:\n | UsfmParser\n | USXParser\n | CodexParser\n | CommentaryCsvParser\n | TyndaleXmlParser;\n if (file.fileType === 'usfm') {\n parser = usfmParser;\n } else if (file.fileType === 'usx') {\n parser = usxParser;\n } else if (file.fileType === 'json') {\n parser = codexParser;\n } else if (file.fileType === 'commentary/csv') {\n parser = csvCommentaryParser;\n } else if (file.fileType === 'commentary/tyndale-xml') {\n parser = tyndaleXmlParser;\n } else {\n logger.warn(\n '[generate] File does not have a valid type!',\n file.name\n );\n continue;\n }\n\n const parsed = parser.parse(file.content);\n\n if (\n 'parseMessages' in parsed &&\n parsed.parseMessages &&\n file.name\n ) {\n const messages = (output.parseMessages =\n output.parseMessages || {});\n messages[file.name] = parsed.parseMessages;\n }\n\n if (parsed.type === 'root') {\n addTranslationTree(file as InputTranslationFile, parsed);\n } else {\n addCommentaryTree(file as InputCommentaryFile, parsed);\n }\n } catch (err) {\n logger.error(\n `[generate] Error occurred while parsing ${file.name}`,\n err\n );\n }\n }\n\n function addTranslationTree(file: InputTranslationFile, parsed: ParseTree) {\n const id = parsed.id;\n\n if (!id) {\n logger.warn(\n '[generate] File does not have a valid book ID!',\n file.name,\n id\n );\n return;\n }\n\n const order = bookOrderMap.get(id);\n\n if (typeof order !== 'number') {\n logger.warn('[generate] Book does not have an order!', id);\n return;\n }\n\n const isApocryphal = apocryphaBooks.has(id);\n\n const bookName = getBookNames(file, file.metadata, id);\n\n let translation = parsedTranslations.get(file.metadata.id);\n\n if (!translation) {\n translation = {\n ...omit(file.metadata, 'direction'),\n textDirection: file.metadata.direction,\n books: [],\n };\n output.translations.push(translation);\n parsedTranslations.set(file.metadata.id, translation);\n }\n\n const name =\n parsed.header ??\n (bookName?.exactMatch ? bookName?.bookName?.commonName : null) ??\n parsed.title ??\n bookName?.bookName?.commonName;\n\n if (!name) {\n logger.warn('[generate] Book does not have a name!', file.name, id);\n return;\n }\n\n const commonName =\n (bookName?.exactMatch ? bookName?.bookName?.commonName : null) ??\n parsed.header ??\n parsed.title ??\n bookName?.bookName?.commonName ??\n id;\n\n const book: DatasetTranslationBook = {\n id: id,\n name,\n commonName,\n title: parsed.title ?? null,\n order,\n chapters: [],\n };\n\n if (isApocryphal) {\n book.isApocryphal = true;\n }\n\n for (let content of parsed.content) {\n if (content.type === 'chapter') {\n book.chapters.push({\n chapter: {\n number: content.number,\n content: content.content,\n footnotes: content.footnotes,\n },\n thisChapterAudioLinks: getAudioUrlsForChapter(\n translation.id,\n id,\n content.number\n ),\n });\n }\n }\n\n book.chapters = sortBy(book.chapters, (c) => c.chapter.number);\n\n const index = sortedIndexBy(translation.books, book, (b) => b.order);\n translation.books.splice(index, 0, book);\n }\n\n function addCommentaryTree(\n file: InputCommentaryFile,\n parsed: CommentaryParseTree\n ) {\n let commentary = parsedCommentaries.get(file.metadata.id);\n\n if (!commentary) {\n commentary = {\n ...omit(file.metadata, 'direction'),\n textDirection: file.metadata.direction,\n books: [],\n profiles: [],\n };\n output.commentaries.push(commentary);\n parsedCommentaries.set(file.metadata.id, commentary);\n }\n\n for (let parsedBook of parsed.books) {\n let book = commentary.books.find((b) => b.id === parsedBook.book);\n if (book && book.introduction && parsedBook.introduction) {\n logger.warn(\n '[generate] Book already exists in commentary!',\n file.name,\n parsedBook.book\n );\n continue;\n }\n\n if (!book) {\n const name = getBookNames(file, file.metadata, parsedBook.book);\n book = {\n id: parsedBook.book,\n order: bookOrderMap.get(parsedBook.book) ?? -1,\n commonName: name?.bookName?.commonName ?? parsedBook.book,\n name: name?.bookName?.commonName ?? parsedBook.book,\n chapters: [],\n };\n }\n\n if (parsedBook.introduction) {\n book.introduction = parsedBook.introduction;\n }\n\n if (parsedBook.introductionSummary && !book.introductionSummary) {\n book.introductionSummary = parsedBook.introductionSummary;\n }\n\n for (let chapter of parsedBook.chapters) {\n let data: CommentaryChapterData = {\n number: chapter.number,\n content: chapter.verses,\n };\n\n if (chapter.introduction) {\n data.introduction = chapter.introduction;\n }\n\n book.chapters.push({\n chapter: data,\n });\n }\n\n book.chapters = sortBy(book.chapters, (c) => c.chapter.number);\n commentary.books.push(book);\n }\n\n if (parsed.profiles) {\n for (let profile of parsed.profiles) {\n const existing = commentary.profiles.find(\n (p) => p.id === profile.id\n );\n if (existing) {\n logger.warn(\n '[generate] Profile already exists in commentary!',\n file.name,\n profile.id\n );\n continue;\n }\n\n const datasetProfile: DatasetCommentaryProfile = {\n id: profile.id,\n subject: profile.subject,\n reference: profile.reference,\n content: profile.content,\n };\n\n commentary.profiles.push(datasetProfile);\n }\n }\n\n commentary.books = sortBy(commentary.books, (b) => b.order);\n commentary.profiles = sortBy(commentary.profiles, (p) => p.id);\n }\n\n function getBookNames(\n file: InputFileBase,\n metadata: MetadataBase,\n id: string\n ) {\n let exactMatch = true;\n let map = bookMap;\n if (!map) {\n map = defaultBookIdMap.get(metadata.language);\n\n if (!map) {\n if (!unknownLanguages.has(metadata.language)) {\n logger.warn(\n '[generate] File does not have a known language!',\n file.name,\n metadata.language\n );\n unknownLanguages.add(metadata.language);\n }\n\n logger.warn(\n '[generate] Using English book map for unknown language! This might result in outputting english book names.'\n );\n exactMatch = false;\n map = defaultBookIdMap.get('en');\n }\n }\n\n const bookName = map?.get(id);\n\n if (!!map && !bookName) {\n logger.warn(\n '[generate] Book name not found for ID!',\n file.name,\n id\n );\n return null;\n }\n\n return {\n exactMatch,\n bookName,\n };\n }\n\n return output;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA2B;AAC3B,wBAA0B;AAmB1B,wBAIO;AACP,oBAA4C;AAC5C,mBAAuC;AACvC,0BAA4B;AAC5B,mCAAoC;AAMpC,gCAAiC;AACjC,iBAA0B;AA4FnB,SAAS,gBACZ,OACA,SAAoB,IAAI,WAAW,UAAU,GAC7C,SACa;AACb,QAAM,aAAS,sBAAU;AACzB,MAAI,SAAwB;AAAA,IACxB,cAAc,CAAC;AAAA,IACf,cAAc,CAAC;AAAA,EACnB;AAEA,MAAI,aAAa,IAAI,8BAAW;AAChC,MAAI,YAAY,IAAI,4BAAU,MAAM;AACpC,MAAI,cAAc,IAAI,gCAAY;
|
|
4
|
+
"sourcesContent": ["import { UsfmParser } from '../parser/usfm-parser.js';\nimport { USXParser } from '../parser/usx-parser.js';\nimport {\n Commentary,\n CommentaryBook,\n CommentaryBookChapter,\n CommentaryChapterData,\n CommentaryProfile,\n InputCommentaryFile,\n InputFile,\n InputFileBase,\n InputTranslationFile,\n MetadataBase,\n Translation,\n TranslationBook,\n TranslationBookChapter,\n Dataset as CommonDataset,\n DatasetBook,\n DatasetBookChapter,\n} from './common-types.js';\nimport {\n bookIdMap as defaultBookIdMap,\n bookOrderMap,\n apocryphaBooks,\n} from './book-order.js';\nimport { omit, sortBy, sortedIndexBy } from 'lodash';\nimport { getAudioUrlsForChapter } from './audio.js';\nimport { CodexParser } from '../parser/codex-parser.js';\nimport { CommentaryCsvParser } from '../parser/commentary-csv-parser.js';\nimport {\n CommentaryParseTree,\n ParseMessage,\n ParseTree,\n} from '../parser/types.js';\nimport { TyndaleXmlParser } from '../parser/tyndale-xml-parser.js';\nimport { getLogger } from '../log.js';\n\n/**\n * Defines an interface that contains generated dataset info.\n */\nexport interface DatasetOutput {\n /**\n * The list of translations that are available in the dataset.\n */\n translations: DatasetTranslation[];\n\n /**\n * The list of commentaries that are available in the dataset.\n */\n commentaries: DatasetCommentary[];\n\n /**\n * The list of datasets that are available in the dataset.\n */\n datasets?: DatasetDataset[];\n\n parseMessages?: {\n [key: string]: ParseMessage[];\n };\n}\n\n/**\n * Defines a translation that is used in the dataset.\n */\nexport interface DatasetTranslation extends Translation {\n /**\n * The list of books that are available for the translation.\n */\n books: DatasetTranslationBook[];\n}\n\n/**\n * Defines a commentary that is used in the dataset.\n */\nexport interface DatasetCommentary extends Commentary {\n /**\n * The list of books that are available for the commentary.\n */\n books: DatasetCommentaryBook[];\n\n /**\n * The list of profiles that are available for the commentary.\n */\n profiles: DatasetCommentaryProfile[];\n}\n\n/**\n * Defines a translation book that is used in the dataset.\n */\nexport interface DatasetTranslationBook extends TranslationBook {\n /**\n * The list of chapters that are available for the book.\n */\n chapters: TranslationBookChapter[];\n}\n\n/**\n * Defines a commentary book that is used in the dataset.\n */\nexport interface DatasetCommentaryBook extends CommentaryBook {\n /**\n * The list of chapters that are available for the book.\n */\n chapters: CommentaryBookChapter[];\n}\n\nexport interface DatasetCommentaryProfile extends CommentaryProfile {\n /**\n * The contents of the profile.\n */\n content: string[];\n}\n\nexport interface DatasetDataset extends CommonDataset {\n books: DatasetDatasetBook[];\n}\n\nexport interface DatasetDatasetBook extends DatasetBook {\n chapters: DatasetBookChapter[];\n}\n\n/**\n * Generates a list of output files from the given list of input files.\n * @param file The list of files.\n * @param parser The parser to use for parsing the files.\n * @param bookMap The map of book IDs to names. If undefined, then a default map will be used.\n */\nexport function generateDataset(\n files: InputFile[],\n parser: DOMParser = new globalThis.DOMParser(),\n bookMap?: Map<string, { commonName: string }> | undefined\n): DatasetOutput {\n const logger = getLogger();\n let output: DatasetOutput = {\n translations: [],\n commentaries: [],\n };\n\n let usfmParser = new UsfmParser();\n let usxParser = new USXParser(parser);\n let codexParser = new CodexParser(parser);\n let csvCommentaryParser = new CommentaryCsvParser();\n let tyndaleXmlParser = new TyndaleXmlParser(parser);\n\n let parsedTranslations = new Map<string, DatasetTranslation>();\n let parsedCommentaries = new Map<string, DatasetCommentary>();\n\n const unknownLanguages = new Set<string>();\n for (let file of files) {\n try {\n let parser:\n | UsfmParser\n | USXParser\n | CodexParser\n | CommentaryCsvParser\n | TyndaleXmlParser;\n if (file.fileType === 'usfm') {\n parser = usfmParser;\n } else if (file.fileType === 'usx') {\n parser = usxParser;\n } else if (file.fileType === 'json') {\n parser = codexParser;\n } else if (file.fileType === 'commentary/csv') {\n parser = csvCommentaryParser;\n } else if (file.fileType === 'commentary/tyndale-xml') {\n parser = tyndaleXmlParser;\n } else {\n logger.warn(\n '[generate] File does not have a valid type!',\n file.name\n );\n continue;\n }\n\n const parsed = parser.parse(file.content);\n\n if (\n 'parseMessages' in parsed &&\n parsed.parseMessages &&\n file.name\n ) {\n const messages = (output.parseMessages =\n output.parseMessages || {});\n messages[file.name] = parsed.parseMessages;\n }\n\n if (parsed.type === 'root') {\n addTranslationTree(file as InputTranslationFile, parsed);\n } else {\n addCommentaryTree(file as InputCommentaryFile, parsed);\n }\n } catch (err) {\n logger.error(\n `[generate] Error occurred while parsing ${file.name}`,\n err\n );\n }\n }\n\n function addTranslationTree(file: InputTranslationFile, parsed: ParseTree) {\n const id = parsed.id;\n\n if (!id) {\n logger.warn(\n '[generate] File does not have a valid book ID!',\n file.name,\n id\n );\n return;\n }\n\n const order = bookOrderMap.get(id);\n\n if (typeof order !== 'number') {\n logger.warn('[generate] Book does not have an order!', id);\n return;\n }\n\n const isApocryphal = apocryphaBooks.has(id);\n\n const bookName = getBookNames(file, file.metadata, id);\n\n let translation = parsedTranslations.get(file.metadata.id);\n\n if (!translation) {\n translation = {\n ...omit(file.metadata, 'direction'),\n textDirection: file.metadata.direction,\n books: [],\n };\n output.translations.push(translation);\n parsedTranslations.set(file.metadata.id, translation);\n }\n\n const name =\n parsed.header ??\n (bookName?.exactMatch ? bookName?.bookName?.commonName : null) ??\n parsed.title ??\n bookName?.bookName?.commonName;\n\n if (!name) {\n logger.warn('[generate] Book does not have a name!', file.name, id);\n return;\n }\n\n const commonName =\n (bookName?.exactMatch ? bookName?.bookName?.commonName : null) ??\n parsed.header ??\n parsed.title ??\n bookName?.bookName?.commonName ??\n id;\n\n const book: DatasetTranslationBook = {\n id: id,\n name,\n commonName,\n title: parsed.title ?? null,\n order,\n chapters: [],\n };\n\n if (isApocryphal) {\n book.isApocryphal = true;\n }\n\n for (let content of parsed.content) {\n if (content.type === 'chapter') {\n book.chapters.push({\n chapter: {\n number: content.number,\n content: content.content,\n footnotes: content.footnotes,\n },\n thisChapterAudioLinks: getAudioUrlsForChapter(\n translation.id,\n id,\n content.number\n ),\n });\n }\n }\n\n book.chapters = sortBy(book.chapters, (c) => c.chapter.number);\n\n const index = sortedIndexBy(translation.books, book, (b) => b.order);\n translation.books.splice(index, 0, book);\n }\n\n function addCommentaryTree(\n file: InputCommentaryFile,\n parsed: CommentaryParseTree\n ) {\n let commentary = parsedCommentaries.get(file.metadata.id);\n\n if (!commentary) {\n commentary = {\n ...omit(file.metadata, 'direction'),\n textDirection: file.metadata.direction,\n books: [],\n profiles: [],\n };\n output.commentaries.push(commentary);\n parsedCommentaries.set(file.metadata.id, commentary);\n }\n\n for (let parsedBook of parsed.books) {\n let book = commentary.books.find((b) => b.id === parsedBook.book);\n if (book && book.introduction && parsedBook.introduction) {\n logger.warn(\n '[generate] Book already exists in commentary!',\n file.name,\n parsedBook.book\n );\n continue;\n }\n\n if (!book) {\n const name = getBookNames(file, file.metadata, parsedBook.book);\n book = {\n id: parsedBook.book,\n order: bookOrderMap.get(parsedBook.book) ?? -1,\n commonName: name?.bookName?.commonName ?? parsedBook.book,\n name: name?.bookName?.commonName ?? parsedBook.book,\n chapters: [],\n };\n }\n\n if (parsedBook.introduction) {\n book.introduction = parsedBook.introduction;\n }\n\n if (parsedBook.introductionSummary && !book.introductionSummary) {\n book.introductionSummary = parsedBook.introductionSummary;\n }\n\n for (let chapter of parsedBook.chapters) {\n let data: CommentaryChapterData = {\n number: chapter.number,\n content: chapter.verses,\n };\n\n if (chapter.introduction) {\n data.introduction = chapter.introduction;\n }\n\n book.chapters.push({\n chapter: data,\n });\n }\n\n book.chapters = sortBy(book.chapters, (c) => c.chapter.number);\n commentary.books.push(book);\n }\n\n if (parsed.profiles) {\n for (let profile of parsed.profiles) {\n const existing = commentary.profiles.find(\n (p) => p.id === profile.id\n );\n if (existing) {\n logger.warn(\n '[generate] Profile already exists in commentary!',\n file.name,\n profile.id\n );\n continue;\n }\n\n const datasetProfile: DatasetCommentaryProfile = {\n id: profile.id,\n subject: profile.subject,\n reference: profile.reference,\n content: profile.content,\n };\n\n commentary.profiles.push(datasetProfile);\n }\n }\n\n commentary.books = sortBy(commentary.books, (b) => b.order);\n commentary.profiles = sortBy(commentary.profiles, (p) => p.id);\n }\n\n function getBookNames(\n file: InputFileBase,\n metadata: MetadataBase,\n id: string\n ) {\n let exactMatch = true;\n let map = bookMap;\n if (!map) {\n map = defaultBookIdMap.get(metadata.language);\n\n if (!map) {\n if (!unknownLanguages.has(metadata.language)) {\n logger.warn(\n '[generate] File does not have a known language!',\n file.name,\n metadata.language\n );\n unknownLanguages.add(metadata.language);\n }\n\n logger.warn(\n '[generate] Using English book map for unknown language! This might result in outputting english book names.'\n );\n exactMatch = false;\n map = defaultBookIdMap.get('en');\n }\n }\n\n const bookName = map?.get(id);\n\n if (!!map && !bookName) {\n logger.warn(\n '[generate] Book name not found for ID!',\n file.name,\n id\n );\n return null;\n }\n\n return {\n exactMatch,\n bookName,\n };\n }\n\n return output;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["parser", "defaultBookIdMap"]
|
|
7
7
|
}
|
|
@@ -39,7 +39,10 @@ const verseSchema = import_zod.z.object({
|
|
|
39
39
|
id: import_zod.z.string(),
|
|
40
40
|
bookCode: import_zod.z.string().optional(),
|
|
41
41
|
chapter: import_zod.z.number().optional(),
|
|
42
|
-
verse: import_zod.z.number().optional()
|
|
42
|
+
verse: import_zod.z.number().optional(),
|
|
43
|
+
data: import_zod.z.object({
|
|
44
|
+
globalReferences: import_zod.z.array(import_zod.z.string()).optional()
|
|
45
|
+
}).optional()
|
|
43
46
|
});
|
|
44
47
|
const paratextSchema = import_zod.z.object({
|
|
45
48
|
type: import_zod.z.literal("paratext"),
|
|
@@ -69,6 +72,10 @@ class CodexParser {
|
|
|
69
72
|
// */
|
|
70
73
|
// preserveMarkdown: boolean = true;
|
|
71
74
|
_noteCounter = 0;
|
|
75
|
+
_parser;
|
|
76
|
+
constructor(parser) {
|
|
77
|
+
this._parser = parser;
|
|
78
|
+
}
|
|
72
79
|
/**
|
|
73
80
|
* Parses the specified codex content.
|
|
74
81
|
*
|
|
@@ -84,7 +91,7 @@ class CodexParser {
|
|
|
84
91
|
};
|
|
85
92
|
let chapters = /* @__PURE__ */ new Map();
|
|
86
93
|
let lastChapter = null;
|
|
87
|
-
function
|
|
94
|
+
function ensureChapter(chapterNumber) {
|
|
88
95
|
let chapter = chapters.get(chapterNumber);
|
|
89
96
|
if (!chapter) {
|
|
90
97
|
chapter = {
|
|
@@ -96,8 +103,23 @@ class CodexParser {
|
|
|
96
103
|
lastChapter = chapter;
|
|
97
104
|
chapters.set(chapterNumber, chapter);
|
|
98
105
|
}
|
|
106
|
+
return chapter;
|
|
107
|
+
}
|
|
108
|
+
function getDocumentContent(doc) {
|
|
109
|
+
let content = "";
|
|
110
|
+
for (let element of doc.childNodes) {
|
|
111
|
+
content += element.textContent;
|
|
112
|
+
}
|
|
113
|
+
return content;
|
|
114
|
+
}
|
|
115
|
+
function addChapterContent(chapterNumber, content) {
|
|
116
|
+
const chapter = ensureChapter(chapterNumber);
|
|
99
117
|
chapter.content.push(...content);
|
|
100
118
|
}
|
|
119
|
+
function addChapterFootnote(chapterNumber, footnote) {
|
|
120
|
+
const chapter = ensureChapter(chapterNumber);
|
|
121
|
+
chapter.footnotes.push(footnote);
|
|
122
|
+
}
|
|
101
123
|
function addReference(ref2, content) {
|
|
102
124
|
addChapterContent(ref2.chapter, [
|
|
103
125
|
{
|
|
@@ -119,8 +141,12 @@ class CodexParser {
|
|
|
119
141
|
function addLineBreaks(lines2) {
|
|
120
142
|
return lines2.reduce(
|
|
121
143
|
(prev, current) => {
|
|
122
|
-
if (
|
|
123
|
-
|
|
144
|
+
if (typeof current === "string" && current.trim() === "") {
|
|
145
|
+
return prev;
|
|
146
|
+
}
|
|
147
|
+
if (prev.length === 0) {
|
|
148
|
+
return [current];
|
|
149
|
+
} else {
|
|
124
150
|
return [
|
|
125
151
|
...prev,
|
|
126
152
|
{
|
|
@@ -128,6 +154,7 @@ class CodexParser {
|
|
|
128
154
|
},
|
|
129
155
|
current
|
|
130
156
|
];
|
|
157
|
+
}
|
|
131
158
|
},
|
|
132
159
|
[]
|
|
133
160
|
);
|
|
@@ -172,6 +199,15 @@ class CodexParser {
|
|
|
172
199
|
"Could not find verse reference in metadata."
|
|
173
200
|
);
|
|
174
201
|
}
|
|
202
|
+
} else if (metadata.data?.globalReferences?.length) {
|
|
203
|
+
reference = (0, import_utils.parseVerseReference)(
|
|
204
|
+
metadata.data.globalReferences[0]
|
|
205
|
+
);
|
|
206
|
+
if (!reference) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
"Could not find verse reference in globalReferences metadata."
|
|
209
|
+
);
|
|
210
|
+
}
|
|
175
211
|
} else {
|
|
176
212
|
throw new Error(
|
|
177
213
|
"Could not find verse reference in metadata."
|
|
@@ -184,13 +220,49 @@ class CodexParser {
|
|
|
184
220
|
}
|
|
185
221
|
previousReference = reference;
|
|
186
222
|
if (!root.id) root.id = reference.book;
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
223
|
+
const doc = this._parser.parseFromString(
|
|
224
|
+
cell.value,
|
|
225
|
+
"text/html"
|
|
226
|
+
);
|
|
227
|
+
for (let element of doc.children) {
|
|
228
|
+
const allFootnotes = element.querySelectorAll(
|
|
229
|
+
"sup.footnote-marker[data-footnote]"
|
|
230
|
+
);
|
|
231
|
+
for (const footnote of allFootnotes) {
|
|
232
|
+
const footnoteData = footnote.getAttribute("data-footnote");
|
|
233
|
+
if (footnoteData) {
|
|
234
|
+
const footnoteDoc = this._parser.parseFromString(
|
|
235
|
+
footnoteData,
|
|
236
|
+
"text/html"
|
|
237
|
+
);
|
|
238
|
+
const footnoteText = getDocumentContent(footnoteDoc);
|
|
239
|
+
if (footnoteText) {
|
|
240
|
+
addChapterFootnote(reference.chapter, {
|
|
241
|
+
noteId: this._noteCounter++,
|
|
242
|
+
text: footnoteText,
|
|
243
|
+
caller: null
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
footnote.remove();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const content = getDocumentContent(doc);
|
|
251
|
+
if (content) {
|
|
252
|
+
const newLines = content.split("\n");
|
|
253
|
+
lines.push(...newLines);
|
|
254
|
+
}
|
|
190
255
|
} else if (metadata.type === "paratext") {
|
|
191
256
|
const reference = (0, import_utils.parseVerseReference)(`${metadata.id}:1`);
|
|
192
257
|
if (reference) {
|
|
193
|
-
const
|
|
258
|
+
const doc = this._parser.parseFromString(
|
|
259
|
+
cell.value,
|
|
260
|
+
"text/html"
|
|
261
|
+
);
|
|
262
|
+
const content = getDocumentContent(doc);
|
|
263
|
+
if (!content) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
194
266
|
const lines2 = content.split("\n").reduce((prev, current) => {
|
|
195
267
|
if (prev.length === 0)
|
|
196
268
|
return [toHeading(current)];
|
|
@@ -207,8 +279,12 @@ class CodexParser {
|
|
|
207
279
|
(line) => addChapterContent(reference.chapter, [line])
|
|
208
280
|
);
|
|
209
281
|
} else {
|
|
210
|
-
const
|
|
211
|
-
|
|
282
|
+
const doc = this._parser.parseFromString(
|
|
283
|
+
cell.value,
|
|
284
|
+
"text/html"
|
|
285
|
+
);
|
|
286
|
+
const content = getDocumentContent(doc);
|
|
287
|
+
if (content && !cell.metadata) {
|
|
212
288
|
if (lastChapter) {
|
|
213
289
|
lastChapter.footnotes.push({
|
|
214
290
|
noteId: this._noteCounter++,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../parser/codex-parser.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod';\nimport {\n Chapter,\n ChapterContent,\n ParseTree,\n Verse,\n VerseContent,\n} from './types.js';\nimport { getBookId, parseVerseReference, VerseRef } from '../utils.js';\nimport { ref } from 'process';\n\nexport const chapterHeadingSchema = z.object({\n type: z.literal('chapter-heading'),\n data: z.object({\n chapter: z.coerce.number().int(),\n }),\n});\n\nexport const verseSchema = z.object({\n type: z.literal('text'),\n id: z.string(),\n bookCode: z.string().optional(),\n chapter: z.number().optional(),\n verse: z.number().optional(),\n});\n\nexport const paratextSchema = z.object({\n type: z.literal('paratext'),\n id: z.string(),\n});\n\nexport const metadataSchema = z.discriminatedUnion('type', [\n chapterHeadingSchema,\n verseSchema,\n paratextSchema,\n]);\n\nexport const codexSchema = z.object({\n cells: z.array(\n z.object({\n kind: z.number(),\n languageId: z.string(),\n value: z.string(),\n metadata: z\n .object({\n type: z.string(),\n })\n .passthrough()\n .nullable()\n .optional(),\n })\n ),\n});\n\n/**\n * Defines a parser for codex files.\n */\nexport class CodexParser {\n // TODO:\n // /**\n // * Whether to preserve markdown in the parsed content.\n // */\n // preserveMarkdown: boolean = true;\n\n private _noteCounter: number = 0;\n\n /**\n * Parses the specified codex content.\n *\n * @param codex The codex content to parse.\n * @returns The parse tree that was generated.\n */\n public parse(codex: string): ParseTree {\n const json = JSON.parse(codex);\n const data = codexSchema.parse(json);\n\n let root: ParseTree = {\n type: 'root',\n content: [],\n };\n\n let chapters: Map<number, Chapter> = new Map();\n let lastChapter: Chapter | null = null;\n\n function addChapterContent(\n chapterNumber: number,\n content: ChapterContent[]\n ) {\n let chapter = chapters.get(chapterNumber);\n if (!chapter) {\n chapter = {\n type: 'chapter',\n number: chapterNumber,\n content: [],\n footnotes: [],\n };\n lastChapter = chapter;\n chapters.set(chapterNumber, chapter);\n }\n\n chapter.content.push(...content);\n }\n\n function addReference(ref: VerseRef, content: Verse['content']) {\n addChapterContent(ref.chapter, [\n {\n type: 'verse',\n number: ref.verse,\n content: content,\n },\n ]);\n }\n\n function stripHTML(value: string) {\n return value.replace(/<[^>]*>/g, '');\n }\n\n function toHeading(content: string) {\n return {\n type: 'heading',\n content: [content],\n } satisfies ChapterContent;\n }\n\n function addLineBreaks(lines: Verse['content']) {\n return lines.reduce(\n (prev, current) => {\n if (prev.length === 0) return [current];\n else\n return [\n ...prev,\n {\n lineBreak: true,\n },\n current,\n ] satisfies Verse['content'];\n },\n [] as Verse['content']\n );\n }\n\n let previousReference: VerseRef | null = null;\n let lines: Verse['content'] = [];\n\n for (let cell of data.cells) {\n if (cell.languageId === 'html' && cell.value) {\n if (!cell.metadata) continue; // ignore html cells without metadata\n\n const metadataResult = metadataSchema.safeParse(cell.metadata);\n\n if (!metadataResult.success) continue; // ignore cells with invalid/unknown metadata\n const metadata = metadataResult.data;\n if (metadata.type === 'text') {\n let reference = parseVerseReference(metadata.id);\n if (!reference) {\n if (metadata.bookCode && metadata.chapter) {\n const bookId = getBookId(metadata.bookCode);\n if (!bookId) {\n throw new Error(\n 'Could not find book ID for code: ' +\n metadata.bookCode\n );\n }\n\n if (metadata.verse) {\n reference = {\n book: bookId,\n chapter: metadata.chapter,\n verse: metadata.verse,\n };\n } else if (previousReference?.verse) {\n if (\n previousReference.chapter !==\n metadata.chapter ||\n previousReference.book !== bookId\n ) {\n throw new Error(\n 'Cannot infer verse number for non-consecutive verse reference.'\n );\n }\n\n // Sometimes codex doesn't include verse numbers for every cell, so we infer it from the previous reference\n reference = {\n book: bookId,\n chapter: metadata.chapter,\n verse: previousReference.verse,\n };\n } else {\n throw new Error(\n 'Could not find verse reference in metadata.'\n );\n }\n } else {\n throw new Error(\n 'Could not find verse reference in metadata.'\n );\n }\n }\n\n if (\n previousReference &&\n (reference.book !== previousReference.book ||\n reference.chapter !== previousReference.chapter ||\n reference.verse !== previousReference.verse)\n ) {\n addReference(previousReference, addLineBreaks(lines));\n lines = [];\n }\n\n previousReference = reference;\n\n if (!root.id) root.id = reference.book; // set book id if not already set\n\n const content = stripHTML(cell.value);\n\n // add line breaks in heading if there are multiple lines\n const newLines = content.split('\\n');\n\n lines.push(...newLines);\n } else if (metadata.type === 'paratext') {\n const reference = parseVerseReference(`${metadata.id}:1`); // chapter headings do not have a verse reference always default to 1\n if (reference) {\n const content = stripHTML(cell.value);\n\n // add line breaks in heading if there are multiple lines\n const lines = content\n .split('\\n')\n .reduce<ChapterContent[]>((prev, current) => {\n if (prev.length === 0)\n return [toHeading(current)];\n else\n return [\n ...prev,\n {\n type: 'line_break',\n },\n toHeading(current),\n ] satisfies ChapterContent[];\n }, []);\n\n lines.forEach((line) =>\n addChapterContent(reference.chapter, [line])\n );\n } else {\n // parse paratext that isn't a chapter reference as footnote\n const content = stripHTML(cell.value);\n if (!cell.metadata) {\n if (lastChapter) {\n (lastChapter as Chapter).footnotes.push({\n noteId: this._noteCounter++,\n text: content,\n caller: null,\n });\n }\n }\n }\n }\n }\n }\n\n if (previousReference) {\n addReference(previousReference, addLineBreaks(lines));\n }\n\n for (let chapter of chapters.values()) {\n root.content.push(chapter);\n }\n\n return root;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAkB;AAQlB,mBAAyD;AAGlD,MAAM,uBAAuB,aAAE,OAAO;AAAA,EACzC,MAAM,aAAE,QAAQ,iBAAiB;AAAA,EACjC,MAAM,aAAE,OAAO;AAAA,IACX,SAAS,aAAE,OAAO,OAAO,EAAE,IAAI;AAAA,EACnC,CAAC;AACL,CAAC;AAEM,MAAM,cAAc,aAAE,OAAO;AAAA,EAChC,MAAM,aAAE,QAAQ,MAAM;AAAA,EACtB,IAAI,aAAE,OAAO;AAAA,EACb,UAAU,aAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,SAAS,aAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,OAAO,aAAE,OAAO,EAAE,SAAS;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod';\nimport {\n Chapter,\n ChapterContent,\n ParseTree,\n Verse,\n VerseContent,\n} from './types.js';\nimport { getBookId, parseVerseReference, VerseRef } from '../utils.js';\nimport { ref } from 'process';\n\nexport const chapterHeadingSchema = z.object({\n type: z.literal('chapter-heading'),\n data: z.object({\n chapter: z.coerce.number().int(),\n }),\n});\n\nexport const verseSchema = z.object({\n type: z.literal('text'),\n id: z.string(),\n bookCode: z.string().optional(),\n chapter: z.number().optional(),\n verse: z.number().optional(),\n data: z\n .object({\n globalReferences: z.array(z.string()).optional(),\n })\n .optional(),\n});\n\nexport const paratextSchema = z.object({\n type: z.literal('paratext'),\n id: z.string(),\n});\n\nexport const metadataSchema = z.discriminatedUnion('type', [\n chapterHeadingSchema,\n verseSchema,\n paratextSchema,\n]);\n\nexport const codexSchema = z.object({\n cells: z.array(\n z.object({\n kind: z.number(),\n languageId: z.string(),\n value: z.string(),\n metadata: z\n .object({\n type: z.string(),\n })\n .passthrough()\n .nullable()\n .optional(),\n })\n ),\n});\n\n/**\n * Defines a parser for codex files.\n */\nexport class CodexParser {\n // TODO:\n // /**\n // * Whether to preserve markdown in the parsed content.\n // */\n // preserveMarkdown: boolean = true;\n\n private _noteCounter: number = 0;\n\n private _parser: DOMParser;\n\n constructor(parser: DOMParser) {\n this._parser = parser;\n }\n\n /**\n * Parses the specified codex content.\n *\n * @param codex The codex content to parse.\n * @returns The parse tree that was generated.\n */\n public parse(codex: string): ParseTree {\n const json = JSON.parse(codex);\n const data = codexSchema.parse(json);\n\n let root: ParseTree = {\n type: 'root',\n content: [],\n };\n\n let chapters: Map<number, Chapter> = new Map();\n let lastChapter: Chapter | null = null;\n\n function ensureChapter(chapterNumber: number) {\n let chapter = chapters.get(chapterNumber);\n if (!chapter) {\n chapter = {\n type: 'chapter',\n number: chapterNumber,\n content: [],\n footnotes: [],\n };\n lastChapter = chapter;\n chapters.set(chapterNumber, chapter);\n }\n\n return chapter;\n }\n\n function getDocumentContent(doc: Document): string {\n let content = '';\n for (let element of doc.childNodes) {\n content += element.textContent;\n }\n return content;\n }\n\n function addChapterContent(\n chapterNumber: number,\n content: ChapterContent[]\n ) {\n const chapter = ensureChapter(chapterNumber);\n chapter.content.push(...content);\n }\n\n function addChapterFootnote(\n chapterNumber: number,\n footnote: Chapter['footnotes'][0]\n ) {\n const chapter = ensureChapter(chapterNumber);\n chapter.footnotes.push(footnote);\n }\n\n function addReference(ref: VerseRef, content: Verse['content']) {\n addChapterContent(ref.chapter, [\n {\n type: 'verse',\n number: ref.verse,\n content: content,\n },\n ]);\n }\n\n function stripHTML(value: string) {\n return value.replace(/<[^>]*>/g, '');\n }\n\n function toHeading(content: string) {\n return {\n type: 'heading',\n content: [content],\n } satisfies ChapterContent;\n }\n\n function addLineBreaks(lines: Verse['content']) {\n return lines.reduce(\n (prev, current) => {\n if (typeof current === 'string' && current.trim() === '') {\n return prev; // skip empty lines\n }\n if (prev.length === 0) {\n return [current];\n } else {\n return [\n ...prev,\n {\n lineBreak: true,\n },\n current,\n ] satisfies Verse['content'];\n }\n },\n [] as Verse['content']\n );\n }\n\n let previousReference: VerseRef | null = null;\n let lines: Verse['content'] = [];\n\n for (let cell of data.cells) {\n if (cell.languageId === 'html' && cell.value) {\n if (!cell.metadata) continue; // ignore html cells without metadata\n\n const metadataResult = metadataSchema.safeParse(cell.metadata);\n\n if (!metadataResult.success) continue; // ignore cells with invalid/unknown metadata\n const metadata = metadataResult.data;\n if (metadata.type === 'text') {\n let reference = parseVerseReference(metadata.id);\n if (!reference) {\n if (metadata.bookCode && metadata.chapter) {\n const bookId = getBookId(metadata.bookCode);\n if (!bookId) {\n throw new Error(\n 'Could not find book ID for code: ' +\n metadata.bookCode\n );\n }\n\n if (metadata.verse) {\n reference = {\n book: bookId,\n chapter: metadata.chapter,\n verse: metadata.verse,\n };\n } else if (previousReference?.verse) {\n if (\n previousReference.chapter !==\n metadata.chapter ||\n previousReference.book !== bookId\n ) {\n throw new Error(\n 'Cannot infer verse number for non-consecutive verse reference.'\n );\n }\n\n // Sometimes codex doesn't include verse numbers for every cell, so we infer it from the previous reference\n reference = {\n book: bookId,\n chapter: metadata.chapter,\n verse: previousReference.verse,\n };\n } else {\n throw new Error(\n 'Could not find verse reference in metadata.'\n );\n }\n } else if (metadata.data?.globalReferences?.length) {\n reference = parseVerseReference(\n metadata.data.globalReferences[0]\n );\n if (!reference) {\n throw new Error(\n 'Could not find verse reference in globalReferences metadata.'\n );\n }\n } else {\n throw new Error(\n 'Could not find verse reference in metadata.'\n );\n }\n }\n\n if (\n previousReference &&\n (reference.book !== previousReference.book ||\n reference.chapter !== previousReference.chapter ||\n reference.verse !== previousReference.verse)\n ) {\n addReference(previousReference, addLineBreaks(lines));\n lines = [];\n }\n\n previousReference = reference;\n\n if (!root.id) root.id = reference.book; // set book id if not already set\n\n const doc = this._parser.parseFromString(\n cell.value,\n 'text/html'\n );\n\n for (let element of doc.children) {\n const allFootnotes = element.querySelectorAll(\n 'sup.footnote-marker[data-footnote]'\n );\n for (const footnote of allFootnotes) {\n const footnoteData =\n footnote.getAttribute('data-footnote');\n if (footnoteData) {\n const footnoteDoc =\n this._parser.parseFromString(\n footnoteData,\n 'text/html'\n );\n const footnoteText =\n getDocumentContent(footnoteDoc);\n if (footnoteText) {\n addChapterFootnote(reference.chapter, {\n noteId: this._noteCounter++,\n text: footnoteText,\n caller: null,\n });\n }\n }\n footnote.remove();\n }\n }\n\n const content = getDocumentContent(doc);\n\n if (content) {\n // add line breaks in heading if there are multiple lines\n const newLines = content.split('\\n');\n\n lines.push(...newLines);\n }\n } else if (metadata.type === 'paratext') {\n const reference = parseVerseReference(`${metadata.id}:1`); // chapter headings do not have a verse reference always default to 1\n if (reference) {\n const doc = this._parser.parseFromString(\n cell.value,\n 'text/html'\n );\n const content = getDocumentContent(doc);\n\n if (!content) {\n continue;\n }\n\n // add line breaks in heading if there are multiple lines\n const lines = content\n .split('\\n')\n .reduce<ChapterContent[]>((prev, current) => {\n if (prev.length === 0)\n return [toHeading(current)];\n else\n return [\n ...prev,\n {\n type: 'line_break',\n },\n toHeading(current),\n ] satisfies ChapterContent[];\n }, []);\n\n lines.forEach((line) =>\n addChapterContent(reference.chapter, [line])\n );\n } else {\n const doc = this._parser.parseFromString(\n cell.value,\n 'text/html'\n );\n const content = getDocumentContent(doc);\n // parse paratext that isn't a chapter reference as footnote\n // const content = stripHTML(cell.value);\n if (content && !cell.metadata) {\n if (lastChapter) {\n (lastChapter as Chapter).footnotes.push({\n noteId: this._noteCounter++,\n text: content,\n caller: null,\n });\n }\n }\n }\n }\n }\n }\n\n if (previousReference) {\n addReference(previousReference, addLineBreaks(lines));\n }\n\n for (let chapter of chapters.values()) {\n root.content.push(chapter);\n }\n\n return root;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["ref", "lines"]
|
|
7
7
|
}
|
|
@@ -20,7 +20,7 @@ export function generateDataset(files, parser = new globalThis.DOMParser(), book
|
|
|
20
20
|
};
|
|
21
21
|
let usfmParser = new UsfmParser();
|
|
22
22
|
let usxParser = new USXParser(parser);
|
|
23
|
-
let codexParser = new CodexParser();
|
|
23
|
+
let codexParser = new CodexParser(parser);
|
|
24
24
|
let csvCommentaryParser = new CommentaryCsvParser();
|
|
25
25
|
let tyndaleXmlParser = new TyndaleXmlParser(parser);
|
|
26
26
|
let parsedTranslations = /* @__PURE__ */ new Map();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../generation/dataset.ts"],
|
|
4
|
-
"sourcesContent": ["import { UsfmParser } from '../parser/usfm-parser.js';\nimport { USXParser } from '../parser/usx-parser.js';\nimport {\n Commentary,\n CommentaryBook,\n CommentaryBookChapter,\n CommentaryChapterData,\n CommentaryProfile,\n InputCommentaryFile,\n InputFile,\n InputFileBase,\n InputTranslationFile,\n MetadataBase,\n Translation,\n TranslationBook,\n TranslationBookChapter,\n Dataset as CommonDataset,\n DatasetBook,\n DatasetBookChapter,\n} from './common-types.js';\nimport {\n bookIdMap as defaultBookIdMap,\n bookOrderMap,\n apocryphaBooks,\n} from './book-order.js';\nimport { omit, sortBy, sortedIndexBy } from 'lodash';\nimport { getAudioUrlsForChapter } from './audio.js';\nimport { CodexParser } from '../parser/codex-parser.js';\nimport { CommentaryCsvParser } from '../parser/commentary-csv-parser.js';\nimport {\n CommentaryParseTree,\n ParseMessage,\n ParseTree,\n} from '../parser/types.js';\nimport { TyndaleXmlParser } from '../parser/tyndale-xml-parser.js';\nimport { getLogger } from '../log.js';\n\n/**\n * Defines an interface that contains generated dataset info.\n */\nexport interface DatasetOutput {\n /**\n * The list of translations that are available in the dataset.\n */\n translations: DatasetTranslation[];\n\n /**\n * The list of commentaries that are available in the dataset.\n */\n commentaries: DatasetCommentary[];\n\n /**\n * The list of datasets that are available in the dataset.\n */\n datasets?: DatasetDataset[];\n\n parseMessages?: {\n [key: string]: ParseMessage[];\n };\n}\n\n/**\n * Defines a translation that is used in the dataset.\n */\nexport interface DatasetTranslation extends Translation {\n /**\n * The list of books that are available for the translation.\n */\n books: DatasetTranslationBook[];\n}\n\n/**\n * Defines a commentary that is used in the dataset.\n */\nexport interface DatasetCommentary extends Commentary {\n /**\n * The list of books that are available for the commentary.\n */\n books: DatasetCommentaryBook[];\n\n /**\n * The list of profiles that are available for the commentary.\n */\n profiles: DatasetCommentaryProfile[];\n}\n\n/**\n * Defines a translation book that is used in the dataset.\n */\nexport interface DatasetTranslationBook extends TranslationBook {\n /**\n * The list of chapters that are available for the book.\n */\n chapters: TranslationBookChapter[];\n}\n\n/**\n * Defines a commentary book that is used in the dataset.\n */\nexport interface DatasetCommentaryBook extends CommentaryBook {\n /**\n * The list of chapters that are available for the book.\n */\n chapters: CommentaryBookChapter[];\n}\n\nexport interface DatasetCommentaryProfile extends CommentaryProfile {\n /**\n * The contents of the profile.\n */\n content: string[];\n}\n\nexport interface DatasetDataset extends CommonDataset {\n books: DatasetDatasetBook[];\n}\n\nexport interface DatasetDatasetBook extends DatasetBook {\n chapters: DatasetBookChapter[];\n}\n\n/**\n * Generates a list of output files from the given list of input files.\n * @param file The list of files.\n * @param parser The parser to use for parsing the files.\n * @param bookMap The map of book IDs to names. If undefined, then a default map will be used.\n */\nexport function generateDataset(\n files: InputFile[],\n parser: DOMParser = new globalThis.DOMParser(),\n bookMap?: Map<string, { commonName: string }> | undefined\n): DatasetOutput {\n const logger = getLogger();\n let output: DatasetOutput = {\n translations: [],\n commentaries: [],\n };\n\n let usfmParser = new UsfmParser();\n let usxParser = new USXParser(parser);\n let codexParser = new CodexParser();\n let csvCommentaryParser = new CommentaryCsvParser();\n let tyndaleXmlParser = new TyndaleXmlParser(parser);\n\n let parsedTranslations = new Map<string, DatasetTranslation>();\n let parsedCommentaries = new Map<string, DatasetCommentary>();\n\n const unknownLanguages = new Set<string>();\n for (let file of files) {\n try {\n let parser:\n | UsfmParser\n | USXParser\n | CodexParser\n | CommentaryCsvParser\n | TyndaleXmlParser;\n if (file.fileType === 'usfm') {\n parser = usfmParser;\n } else if (file.fileType === 'usx') {\n parser = usxParser;\n } else if (file.fileType === 'json') {\n parser = codexParser;\n } else if (file.fileType === 'commentary/csv') {\n parser = csvCommentaryParser;\n } else if (file.fileType === 'commentary/tyndale-xml') {\n parser = tyndaleXmlParser;\n } else {\n logger.warn(\n '[generate] File does not have a valid type!',\n file.name\n );\n continue;\n }\n\n const parsed = parser.parse(file.content);\n\n if (\n 'parseMessages' in parsed &&\n parsed.parseMessages &&\n file.name\n ) {\n const messages = (output.parseMessages =\n output.parseMessages || {});\n messages[file.name] = parsed.parseMessages;\n }\n\n if (parsed.type === 'root') {\n addTranslationTree(file as InputTranslationFile, parsed);\n } else {\n addCommentaryTree(file as InputCommentaryFile, parsed);\n }\n } catch (err) {\n logger.error(\n `[generate] Error occurred while parsing ${file.name}`,\n err\n );\n }\n }\n\n function addTranslationTree(file: InputTranslationFile, parsed: ParseTree) {\n const id = parsed.id;\n\n if (!id) {\n logger.warn(\n '[generate] File does not have a valid book ID!',\n file.name,\n id\n );\n return;\n }\n\n const order = bookOrderMap.get(id);\n\n if (typeof order !== 'number') {\n logger.warn('[generate] Book does not have an order!', id);\n return;\n }\n\n const isApocryphal = apocryphaBooks.has(id);\n\n const bookName = getBookNames(file, file.metadata, id);\n\n let translation = parsedTranslations.get(file.metadata.id);\n\n if (!translation) {\n translation = {\n ...omit(file.metadata, 'direction'),\n textDirection: file.metadata.direction,\n books: [],\n };\n output.translations.push(translation);\n parsedTranslations.set(file.metadata.id, translation);\n }\n\n const name =\n parsed.header ??\n (bookName?.exactMatch ? bookName?.bookName?.commonName : null) ??\n parsed.title ??\n bookName?.bookName?.commonName;\n\n if (!name) {\n logger.warn('[generate] Book does not have a name!', file.name, id);\n return;\n }\n\n const commonName =\n (bookName?.exactMatch ? bookName?.bookName?.commonName : null) ??\n parsed.header ??\n parsed.title ??\n bookName?.bookName?.commonName ??\n id;\n\n const book: DatasetTranslationBook = {\n id: id,\n name,\n commonName,\n title: parsed.title ?? null,\n order,\n chapters: [],\n };\n\n if (isApocryphal) {\n book.isApocryphal = true;\n }\n\n for (let content of parsed.content) {\n if (content.type === 'chapter') {\n book.chapters.push({\n chapter: {\n number: content.number,\n content: content.content,\n footnotes: content.footnotes,\n },\n thisChapterAudioLinks: getAudioUrlsForChapter(\n translation.id,\n id,\n content.number\n ),\n });\n }\n }\n\n book.chapters = sortBy(book.chapters, (c) => c.chapter.number);\n\n const index = sortedIndexBy(translation.books, book, (b) => b.order);\n translation.books.splice(index, 0, book);\n }\n\n function addCommentaryTree(\n file: InputCommentaryFile,\n parsed: CommentaryParseTree\n ) {\n let commentary = parsedCommentaries.get(file.metadata.id);\n\n if (!commentary) {\n commentary = {\n ...omit(file.metadata, 'direction'),\n textDirection: file.metadata.direction,\n books: [],\n profiles: [],\n };\n output.commentaries.push(commentary);\n parsedCommentaries.set(file.metadata.id, commentary);\n }\n\n for (let parsedBook of parsed.books) {\n let book = commentary.books.find((b) => b.id === parsedBook.book);\n if (book && book.introduction && parsedBook.introduction) {\n logger.warn(\n '[generate] Book already exists in commentary!',\n file.name,\n parsedBook.book\n );\n continue;\n }\n\n if (!book) {\n const name = getBookNames(file, file.metadata, parsedBook.book);\n book = {\n id: parsedBook.book,\n order: bookOrderMap.get(parsedBook.book) ?? -1,\n commonName: name?.bookName?.commonName ?? parsedBook.book,\n name: name?.bookName?.commonName ?? parsedBook.book,\n chapters: [],\n };\n }\n\n if (parsedBook.introduction) {\n book.introduction = parsedBook.introduction;\n }\n\n if (parsedBook.introductionSummary && !book.introductionSummary) {\n book.introductionSummary = parsedBook.introductionSummary;\n }\n\n for (let chapter of parsedBook.chapters) {\n let data: CommentaryChapterData = {\n number: chapter.number,\n content: chapter.verses,\n };\n\n if (chapter.introduction) {\n data.introduction = chapter.introduction;\n }\n\n book.chapters.push({\n chapter: data,\n });\n }\n\n book.chapters = sortBy(book.chapters, (c) => c.chapter.number);\n commentary.books.push(book);\n }\n\n if (parsed.profiles) {\n for (let profile of parsed.profiles) {\n const existing = commentary.profiles.find(\n (p) => p.id === profile.id\n );\n if (existing) {\n logger.warn(\n '[generate] Profile already exists in commentary!',\n file.name,\n profile.id\n );\n continue;\n }\n\n const datasetProfile: DatasetCommentaryProfile = {\n id: profile.id,\n subject: profile.subject,\n reference: profile.reference,\n content: profile.content,\n };\n\n commentary.profiles.push(datasetProfile);\n }\n }\n\n commentary.books = sortBy(commentary.books, (b) => b.order);\n commentary.profiles = sortBy(commentary.profiles, (p) => p.id);\n }\n\n function getBookNames(\n file: InputFileBase,\n metadata: MetadataBase,\n id: string\n ) {\n let exactMatch = true;\n let map = bookMap;\n if (!map) {\n map = defaultBookIdMap.get(metadata.language);\n\n if (!map) {\n if (!unknownLanguages.has(metadata.language)) {\n logger.warn(\n '[generate] File does not have a known language!',\n file.name,\n metadata.language\n );\n unknownLanguages.add(metadata.language);\n }\n\n logger.warn(\n '[generate] Using English book map for unknown language! This might result in outputting english book names.'\n );\n exactMatch = false;\n map = defaultBookIdMap.get('en');\n }\n }\n\n const bookName = map?.get(id);\n\n if (!!map && !bookName) {\n logger.warn(\n '[generate] Book name not found for ID!',\n file.name,\n id\n );\n return null;\n }\n\n return {\n exactMatch,\n bookName,\n };\n }\n\n return output;\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAmB1B;AAAA,EACI,aAAa;AAAA,EACb;AAAA,EACA;AAAA,OACG;AACP,SAAS,MAAM,QAAQ,qBAAqB;AAC5C,SAAS,8BAA8B;AACvC,SAAS,mBAAmB;AAC5B,SAAS,2BAA2B;AAMpC,SAAS,wBAAwB;AACjC,SAAS,iBAAiB;AA4FnB,gBAAS,gBACZ,OACA,SAAoB,IAAI,WAAW,UAAU,GAC7C,SACa;AACb,QAAM,SAAS,UAAU;AACzB,MAAI,SAAwB;AAAA,IACxB,cAAc,CAAC;AAAA,IACf,cAAc,CAAC;AAAA,EACnB;AAEA,MAAI,aAAa,IAAI,WAAW;AAChC,MAAI,YAAY,IAAI,UAAU,MAAM;AACpC,MAAI,cAAc,IAAI,YAAY;
|
|
4
|
+
"sourcesContent": ["import { UsfmParser } from '../parser/usfm-parser.js';\nimport { USXParser } from '../parser/usx-parser.js';\nimport {\n Commentary,\n CommentaryBook,\n CommentaryBookChapter,\n CommentaryChapterData,\n CommentaryProfile,\n InputCommentaryFile,\n InputFile,\n InputFileBase,\n InputTranslationFile,\n MetadataBase,\n Translation,\n TranslationBook,\n TranslationBookChapter,\n Dataset as CommonDataset,\n DatasetBook,\n DatasetBookChapter,\n} from './common-types.js';\nimport {\n bookIdMap as defaultBookIdMap,\n bookOrderMap,\n apocryphaBooks,\n} from './book-order.js';\nimport { omit, sortBy, sortedIndexBy } from 'lodash';\nimport { getAudioUrlsForChapter } from './audio.js';\nimport { CodexParser } from '../parser/codex-parser.js';\nimport { CommentaryCsvParser } from '../parser/commentary-csv-parser.js';\nimport {\n CommentaryParseTree,\n ParseMessage,\n ParseTree,\n} from '../parser/types.js';\nimport { TyndaleXmlParser } from '../parser/tyndale-xml-parser.js';\nimport { getLogger } from '../log.js';\n\n/**\n * Defines an interface that contains generated dataset info.\n */\nexport interface DatasetOutput {\n /**\n * The list of translations that are available in the dataset.\n */\n translations: DatasetTranslation[];\n\n /**\n * The list of commentaries that are available in the dataset.\n */\n commentaries: DatasetCommentary[];\n\n /**\n * The list of datasets that are available in the dataset.\n */\n datasets?: DatasetDataset[];\n\n parseMessages?: {\n [key: string]: ParseMessage[];\n };\n}\n\n/**\n * Defines a translation that is used in the dataset.\n */\nexport interface DatasetTranslation extends Translation {\n /**\n * The list of books that are available for the translation.\n */\n books: DatasetTranslationBook[];\n}\n\n/**\n * Defines a commentary that is used in the dataset.\n */\nexport interface DatasetCommentary extends Commentary {\n /**\n * The list of books that are available for the commentary.\n */\n books: DatasetCommentaryBook[];\n\n /**\n * The list of profiles that are available for the commentary.\n */\n profiles: DatasetCommentaryProfile[];\n}\n\n/**\n * Defines a translation book that is used in the dataset.\n */\nexport interface DatasetTranslationBook extends TranslationBook {\n /**\n * The list of chapters that are available for the book.\n */\n chapters: TranslationBookChapter[];\n}\n\n/**\n * Defines a commentary book that is used in the dataset.\n */\nexport interface DatasetCommentaryBook extends CommentaryBook {\n /**\n * The list of chapters that are available for the book.\n */\n chapters: CommentaryBookChapter[];\n}\n\nexport interface DatasetCommentaryProfile extends CommentaryProfile {\n /**\n * The contents of the profile.\n */\n content: string[];\n}\n\nexport interface DatasetDataset extends CommonDataset {\n books: DatasetDatasetBook[];\n}\n\nexport interface DatasetDatasetBook extends DatasetBook {\n chapters: DatasetBookChapter[];\n}\n\n/**\n * Generates a list of output files from the given list of input files.\n * @param file The list of files.\n * @param parser The parser to use for parsing the files.\n * @param bookMap The map of book IDs to names. If undefined, then a default map will be used.\n */\nexport function generateDataset(\n files: InputFile[],\n parser: DOMParser = new globalThis.DOMParser(),\n bookMap?: Map<string, { commonName: string }> | undefined\n): DatasetOutput {\n const logger = getLogger();\n let output: DatasetOutput = {\n translations: [],\n commentaries: [],\n };\n\n let usfmParser = new UsfmParser();\n let usxParser = new USXParser(parser);\n let codexParser = new CodexParser(parser);\n let csvCommentaryParser = new CommentaryCsvParser();\n let tyndaleXmlParser = new TyndaleXmlParser(parser);\n\n let parsedTranslations = new Map<string, DatasetTranslation>();\n let parsedCommentaries = new Map<string, DatasetCommentary>();\n\n const unknownLanguages = new Set<string>();\n for (let file of files) {\n try {\n let parser:\n | UsfmParser\n | USXParser\n | CodexParser\n | CommentaryCsvParser\n | TyndaleXmlParser;\n if (file.fileType === 'usfm') {\n parser = usfmParser;\n } else if (file.fileType === 'usx') {\n parser = usxParser;\n } else if (file.fileType === 'json') {\n parser = codexParser;\n } else if (file.fileType === 'commentary/csv') {\n parser = csvCommentaryParser;\n } else if (file.fileType === 'commentary/tyndale-xml') {\n parser = tyndaleXmlParser;\n } else {\n logger.warn(\n '[generate] File does not have a valid type!',\n file.name\n );\n continue;\n }\n\n const parsed = parser.parse(file.content);\n\n if (\n 'parseMessages' in parsed &&\n parsed.parseMessages &&\n file.name\n ) {\n const messages = (output.parseMessages =\n output.parseMessages || {});\n messages[file.name] = parsed.parseMessages;\n }\n\n if (parsed.type === 'root') {\n addTranslationTree(file as InputTranslationFile, parsed);\n } else {\n addCommentaryTree(file as InputCommentaryFile, parsed);\n }\n } catch (err) {\n logger.error(\n `[generate] Error occurred while parsing ${file.name}`,\n err\n );\n }\n }\n\n function addTranslationTree(file: InputTranslationFile, parsed: ParseTree) {\n const id = parsed.id;\n\n if (!id) {\n logger.warn(\n '[generate] File does not have a valid book ID!',\n file.name,\n id\n );\n return;\n }\n\n const order = bookOrderMap.get(id);\n\n if (typeof order !== 'number') {\n logger.warn('[generate] Book does not have an order!', id);\n return;\n }\n\n const isApocryphal = apocryphaBooks.has(id);\n\n const bookName = getBookNames(file, file.metadata, id);\n\n let translation = parsedTranslations.get(file.metadata.id);\n\n if (!translation) {\n translation = {\n ...omit(file.metadata, 'direction'),\n textDirection: file.metadata.direction,\n books: [],\n };\n output.translations.push(translation);\n parsedTranslations.set(file.metadata.id, translation);\n }\n\n const name =\n parsed.header ??\n (bookName?.exactMatch ? bookName?.bookName?.commonName : null) ??\n parsed.title ??\n bookName?.bookName?.commonName;\n\n if (!name) {\n logger.warn('[generate] Book does not have a name!', file.name, id);\n return;\n }\n\n const commonName =\n (bookName?.exactMatch ? bookName?.bookName?.commonName : null) ??\n parsed.header ??\n parsed.title ??\n bookName?.bookName?.commonName ??\n id;\n\n const book: DatasetTranslationBook = {\n id: id,\n name,\n commonName,\n title: parsed.title ?? null,\n order,\n chapters: [],\n };\n\n if (isApocryphal) {\n book.isApocryphal = true;\n }\n\n for (let content of parsed.content) {\n if (content.type === 'chapter') {\n book.chapters.push({\n chapter: {\n number: content.number,\n content: content.content,\n footnotes: content.footnotes,\n },\n thisChapterAudioLinks: getAudioUrlsForChapter(\n translation.id,\n id,\n content.number\n ),\n });\n }\n }\n\n book.chapters = sortBy(book.chapters, (c) => c.chapter.number);\n\n const index = sortedIndexBy(translation.books, book, (b) => b.order);\n translation.books.splice(index, 0, book);\n }\n\n function addCommentaryTree(\n file: InputCommentaryFile,\n parsed: CommentaryParseTree\n ) {\n let commentary = parsedCommentaries.get(file.metadata.id);\n\n if (!commentary) {\n commentary = {\n ...omit(file.metadata, 'direction'),\n textDirection: file.metadata.direction,\n books: [],\n profiles: [],\n };\n output.commentaries.push(commentary);\n parsedCommentaries.set(file.metadata.id, commentary);\n }\n\n for (let parsedBook of parsed.books) {\n let book = commentary.books.find((b) => b.id === parsedBook.book);\n if (book && book.introduction && parsedBook.introduction) {\n logger.warn(\n '[generate] Book already exists in commentary!',\n file.name,\n parsedBook.book\n );\n continue;\n }\n\n if (!book) {\n const name = getBookNames(file, file.metadata, parsedBook.book);\n book = {\n id: parsedBook.book,\n order: bookOrderMap.get(parsedBook.book) ?? -1,\n commonName: name?.bookName?.commonName ?? parsedBook.book,\n name: name?.bookName?.commonName ?? parsedBook.book,\n chapters: [],\n };\n }\n\n if (parsedBook.introduction) {\n book.introduction = parsedBook.introduction;\n }\n\n if (parsedBook.introductionSummary && !book.introductionSummary) {\n book.introductionSummary = parsedBook.introductionSummary;\n }\n\n for (let chapter of parsedBook.chapters) {\n let data: CommentaryChapterData = {\n number: chapter.number,\n content: chapter.verses,\n };\n\n if (chapter.introduction) {\n data.introduction = chapter.introduction;\n }\n\n book.chapters.push({\n chapter: data,\n });\n }\n\n book.chapters = sortBy(book.chapters, (c) => c.chapter.number);\n commentary.books.push(book);\n }\n\n if (parsed.profiles) {\n for (let profile of parsed.profiles) {\n const existing = commentary.profiles.find(\n (p) => p.id === profile.id\n );\n if (existing) {\n logger.warn(\n '[generate] Profile already exists in commentary!',\n file.name,\n profile.id\n );\n continue;\n }\n\n const datasetProfile: DatasetCommentaryProfile = {\n id: profile.id,\n subject: profile.subject,\n reference: profile.reference,\n content: profile.content,\n };\n\n commentary.profiles.push(datasetProfile);\n }\n }\n\n commentary.books = sortBy(commentary.books, (b) => b.order);\n commentary.profiles = sortBy(commentary.profiles, (p) => p.id);\n }\n\n function getBookNames(\n file: InputFileBase,\n metadata: MetadataBase,\n id: string\n ) {\n let exactMatch = true;\n let map = bookMap;\n if (!map) {\n map = defaultBookIdMap.get(metadata.language);\n\n if (!map) {\n if (!unknownLanguages.has(metadata.language)) {\n logger.warn(\n '[generate] File does not have a known language!',\n file.name,\n metadata.language\n );\n unknownLanguages.add(metadata.language);\n }\n\n logger.warn(\n '[generate] Using English book map for unknown language! This might result in outputting english book names.'\n );\n exactMatch = false;\n map = defaultBookIdMap.get('en');\n }\n }\n\n const bookName = map?.get(id);\n\n if (!!map && !bookName) {\n logger.warn(\n '[generate] Book name not found for ID!',\n file.name,\n id\n );\n return null;\n }\n\n return {\n exactMatch,\n bookName,\n };\n }\n\n return output;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["parser"]
|
|
7
7
|
}
|
|
@@ -12,7 +12,10 @@ export const verseSchema = z.object({
|
|
|
12
12
|
id: z.string(),
|
|
13
13
|
bookCode: z.string().optional(),
|
|
14
14
|
chapter: z.number().optional(),
|
|
15
|
-
verse: z.number().optional()
|
|
15
|
+
verse: z.number().optional(),
|
|
16
|
+
data: z.object({
|
|
17
|
+
globalReferences: z.array(z.string()).optional()
|
|
18
|
+
}).optional()
|
|
16
19
|
});
|
|
17
20
|
export const paratextSchema = z.object({
|
|
18
21
|
type: z.literal("paratext"),
|
|
@@ -42,6 +45,10 @@ export class CodexParser {
|
|
|
42
45
|
// */
|
|
43
46
|
// preserveMarkdown: boolean = true;
|
|
44
47
|
_noteCounter = 0;
|
|
48
|
+
_parser;
|
|
49
|
+
constructor(parser) {
|
|
50
|
+
this._parser = parser;
|
|
51
|
+
}
|
|
45
52
|
/**
|
|
46
53
|
* Parses the specified codex content.
|
|
47
54
|
*
|
|
@@ -57,7 +64,7 @@ export class CodexParser {
|
|
|
57
64
|
};
|
|
58
65
|
let chapters = /* @__PURE__ */ new Map();
|
|
59
66
|
let lastChapter = null;
|
|
60
|
-
function
|
|
67
|
+
function ensureChapter(chapterNumber) {
|
|
61
68
|
let chapter = chapters.get(chapterNumber);
|
|
62
69
|
if (!chapter) {
|
|
63
70
|
chapter = {
|
|
@@ -69,8 +76,23 @@ export class CodexParser {
|
|
|
69
76
|
lastChapter = chapter;
|
|
70
77
|
chapters.set(chapterNumber, chapter);
|
|
71
78
|
}
|
|
79
|
+
return chapter;
|
|
80
|
+
}
|
|
81
|
+
function getDocumentContent(doc) {
|
|
82
|
+
let content = "";
|
|
83
|
+
for (let element of doc.childNodes) {
|
|
84
|
+
content += element.textContent;
|
|
85
|
+
}
|
|
86
|
+
return content;
|
|
87
|
+
}
|
|
88
|
+
function addChapterContent(chapterNumber, content) {
|
|
89
|
+
const chapter = ensureChapter(chapterNumber);
|
|
72
90
|
chapter.content.push(...content);
|
|
73
91
|
}
|
|
92
|
+
function addChapterFootnote(chapterNumber, footnote) {
|
|
93
|
+
const chapter = ensureChapter(chapterNumber);
|
|
94
|
+
chapter.footnotes.push(footnote);
|
|
95
|
+
}
|
|
74
96
|
function addReference(ref2, content) {
|
|
75
97
|
addChapterContent(ref2.chapter, [
|
|
76
98
|
{
|
|
@@ -92,8 +114,12 @@ export class CodexParser {
|
|
|
92
114
|
function addLineBreaks(lines2) {
|
|
93
115
|
return lines2.reduce(
|
|
94
116
|
(prev, current) => {
|
|
95
|
-
if (
|
|
96
|
-
|
|
117
|
+
if (typeof current === "string" && current.trim() === "") {
|
|
118
|
+
return prev;
|
|
119
|
+
}
|
|
120
|
+
if (prev.length === 0) {
|
|
121
|
+
return [current];
|
|
122
|
+
} else {
|
|
97
123
|
return [
|
|
98
124
|
...prev,
|
|
99
125
|
{
|
|
@@ -101,6 +127,7 @@ export class CodexParser {
|
|
|
101
127
|
},
|
|
102
128
|
current
|
|
103
129
|
];
|
|
130
|
+
}
|
|
104
131
|
},
|
|
105
132
|
[]
|
|
106
133
|
);
|
|
@@ -145,6 +172,15 @@ export class CodexParser {
|
|
|
145
172
|
"Could not find verse reference in metadata."
|
|
146
173
|
);
|
|
147
174
|
}
|
|
175
|
+
} else if (metadata.data?.globalReferences?.length) {
|
|
176
|
+
reference = parseVerseReference(
|
|
177
|
+
metadata.data.globalReferences[0]
|
|
178
|
+
);
|
|
179
|
+
if (!reference) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
"Could not find verse reference in globalReferences metadata."
|
|
182
|
+
);
|
|
183
|
+
}
|
|
148
184
|
} else {
|
|
149
185
|
throw new Error(
|
|
150
186
|
"Could not find verse reference in metadata."
|
|
@@ -157,13 +193,49 @@ export class CodexParser {
|
|
|
157
193
|
}
|
|
158
194
|
previousReference = reference;
|
|
159
195
|
if (!root.id) root.id = reference.book;
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
196
|
+
const doc = this._parser.parseFromString(
|
|
197
|
+
cell.value,
|
|
198
|
+
"text/html"
|
|
199
|
+
);
|
|
200
|
+
for (let element of doc.children) {
|
|
201
|
+
const allFootnotes = element.querySelectorAll(
|
|
202
|
+
"sup.footnote-marker[data-footnote]"
|
|
203
|
+
);
|
|
204
|
+
for (const footnote of allFootnotes) {
|
|
205
|
+
const footnoteData = footnote.getAttribute("data-footnote");
|
|
206
|
+
if (footnoteData) {
|
|
207
|
+
const footnoteDoc = this._parser.parseFromString(
|
|
208
|
+
footnoteData,
|
|
209
|
+
"text/html"
|
|
210
|
+
);
|
|
211
|
+
const footnoteText = getDocumentContent(footnoteDoc);
|
|
212
|
+
if (footnoteText) {
|
|
213
|
+
addChapterFootnote(reference.chapter, {
|
|
214
|
+
noteId: this._noteCounter++,
|
|
215
|
+
text: footnoteText,
|
|
216
|
+
caller: null
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
footnote.remove();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const content = getDocumentContent(doc);
|
|
224
|
+
if (content) {
|
|
225
|
+
const newLines = content.split("\n");
|
|
226
|
+
lines.push(...newLines);
|
|
227
|
+
}
|
|
163
228
|
} else if (metadata.type === "paratext") {
|
|
164
229
|
const reference = parseVerseReference(`${metadata.id}:1`);
|
|
165
230
|
if (reference) {
|
|
166
|
-
const
|
|
231
|
+
const doc = this._parser.parseFromString(
|
|
232
|
+
cell.value,
|
|
233
|
+
"text/html"
|
|
234
|
+
);
|
|
235
|
+
const content = getDocumentContent(doc);
|
|
236
|
+
if (!content) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
167
239
|
const lines2 = content.split("\n").reduce((prev, current) => {
|
|
168
240
|
if (prev.length === 0)
|
|
169
241
|
return [toHeading(current)];
|
|
@@ -180,8 +252,12 @@ export class CodexParser {
|
|
|
180
252
|
(line) => addChapterContent(reference.chapter, [line])
|
|
181
253
|
);
|
|
182
254
|
} else {
|
|
183
|
-
const
|
|
184
|
-
|
|
255
|
+
const doc = this._parser.parseFromString(
|
|
256
|
+
cell.value,
|
|
257
|
+
"text/html"
|
|
258
|
+
);
|
|
259
|
+
const content = getDocumentContent(doc);
|
|
260
|
+
if (content && !cell.metadata) {
|
|
185
261
|
if (lastChapter) {
|
|
186
262
|
lastChapter.footnotes.push({
|
|
187
263
|
noteId: this._noteCounter++,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../parser/codex-parser.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod';\nimport {\n Chapter,\n ChapterContent,\n ParseTree,\n Verse,\n VerseContent,\n} from './types.js';\nimport { getBookId, parseVerseReference, VerseRef } from '../utils.js';\nimport { ref } from 'process';\n\nexport const chapterHeadingSchema = z.object({\n type: z.literal('chapter-heading'),\n data: z.object({\n chapter: z.coerce.number().int(),\n }),\n});\n\nexport const verseSchema = z.object({\n type: z.literal('text'),\n id: z.string(),\n bookCode: z.string().optional(),\n chapter: z.number().optional(),\n verse: z.number().optional(),\n});\n\nexport const paratextSchema = z.object({\n type: z.literal('paratext'),\n id: z.string(),\n});\n\nexport const metadataSchema = z.discriminatedUnion('type', [\n chapterHeadingSchema,\n verseSchema,\n paratextSchema,\n]);\n\nexport const codexSchema = z.object({\n cells: z.array(\n z.object({\n kind: z.number(),\n languageId: z.string(),\n value: z.string(),\n metadata: z\n .object({\n type: z.string(),\n })\n .passthrough()\n .nullable()\n .optional(),\n })\n ),\n});\n\n/**\n * Defines a parser for codex files.\n */\nexport class CodexParser {\n // TODO:\n // /**\n // * Whether to preserve markdown in the parsed content.\n // */\n // preserveMarkdown: boolean = true;\n\n private _noteCounter: number = 0;\n\n /**\n * Parses the specified codex content.\n *\n * @param codex The codex content to parse.\n * @returns The parse tree that was generated.\n */\n public parse(codex: string): ParseTree {\n const json = JSON.parse(codex);\n const data = codexSchema.parse(json);\n\n let root: ParseTree = {\n type: 'root',\n content: [],\n };\n\n let chapters: Map<number, Chapter> = new Map();\n let lastChapter: Chapter | null = null;\n\n function addChapterContent(\n chapterNumber: number,\n content: ChapterContent[]\n ) {\n let chapter = chapters.get(chapterNumber);\n if (!chapter) {\n chapter = {\n type: 'chapter',\n number: chapterNumber,\n content: [],\n footnotes: [],\n };\n lastChapter = chapter;\n chapters.set(chapterNumber, chapter);\n }\n\n chapter.content.push(...content);\n }\n\n function addReference(ref: VerseRef, content: Verse['content']) {\n addChapterContent(ref.chapter, [\n {\n type: 'verse',\n number: ref.verse,\n content: content,\n },\n ]);\n }\n\n function stripHTML(value: string) {\n return value.replace(/<[^>]*>/g, '');\n }\n\n function toHeading(content: string) {\n return {\n type: 'heading',\n content: [content],\n } satisfies ChapterContent;\n }\n\n function addLineBreaks(lines: Verse['content']) {\n return lines.reduce(\n (prev, current) => {\n if (prev.length === 0) return [current];\n else\n return [\n ...prev,\n {\n lineBreak: true,\n },\n current,\n ] satisfies Verse['content'];\n },\n [] as Verse['content']\n );\n }\n\n let previousReference: VerseRef | null = null;\n let lines: Verse['content'] = [];\n\n for (let cell of data.cells) {\n if (cell.languageId === 'html' && cell.value) {\n if (!cell.metadata) continue; // ignore html cells without metadata\n\n const metadataResult = metadataSchema.safeParse(cell.metadata);\n\n if (!metadataResult.success) continue; // ignore cells with invalid/unknown metadata\n const metadata = metadataResult.data;\n if (metadata.type === 'text') {\n let reference = parseVerseReference(metadata.id);\n if (!reference) {\n if (metadata.bookCode && metadata.chapter) {\n const bookId = getBookId(metadata.bookCode);\n if (!bookId) {\n throw new Error(\n 'Could not find book ID for code: ' +\n metadata.bookCode\n );\n }\n\n if (metadata.verse) {\n reference = {\n book: bookId,\n chapter: metadata.chapter,\n verse: metadata.verse,\n };\n } else if (previousReference?.verse) {\n if (\n previousReference.chapter !==\n metadata.chapter ||\n previousReference.book !== bookId\n ) {\n throw new Error(\n 'Cannot infer verse number for non-consecutive verse reference.'\n );\n }\n\n // Sometimes codex doesn't include verse numbers for every cell, so we infer it from the previous reference\n reference = {\n book: bookId,\n chapter: metadata.chapter,\n verse: previousReference.verse,\n };\n } else {\n throw new Error(\n 'Could not find verse reference in metadata.'\n );\n }\n } else {\n throw new Error(\n 'Could not find verse reference in metadata.'\n );\n }\n }\n\n if (\n previousReference &&\n (reference.book !== previousReference.book ||\n reference.chapter !== previousReference.chapter ||\n reference.verse !== previousReference.verse)\n ) {\n addReference(previousReference, addLineBreaks(lines));\n lines = [];\n }\n\n previousReference = reference;\n\n if (!root.id) root.id = reference.book; // set book id if not already set\n\n const content = stripHTML(cell.value);\n\n // add line breaks in heading if there are multiple lines\n const newLines = content.split('\\n');\n\n lines.push(...newLines);\n } else if (metadata.type === 'paratext') {\n const reference = parseVerseReference(`${metadata.id}:1`); // chapter headings do not have a verse reference always default to 1\n if (reference) {\n const content = stripHTML(cell.value);\n\n // add line breaks in heading if there are multiple lines\n const lines = content\n .split('\\n')\n .reduce<ChapterContent[]>((prev, current) => {\n if (prev.length === 0)\n return [toHeading(current)];\n else\n return [\n ...prev,\n {\n type: 'line_break',\n },\n toHeading(current),\n ] satisfies ChapterContent[];\n }, []);\n\n lines.forEach((line) =>\n addChapterContent(reference.chapter, [line])\n );\n } else {\n // parse paratext that isn't a chapter reference as footnote\n const content = stripHTML(cell.value);\n if (!cell.metadata) {\n if (lastChapter) {\n (lastChapter as Chapter).footnotes.push({\n noteId: this._noteCounter++,\n text: content,\n caller: null,\n });\n }\n }\n }\n }\n }\n }\n\n if (previousReference) {\n addReference(previousReference, addLineBreaks(lines));\n }\n\n for (let chapter of chapters.values()) {\n root.content.push(chapter);\n }\n\n return root;\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,SAAS;AAQlB,SAAS,WAAW,2BAAqC;AAGlD,aAAM,uBAAuB,EAAE,OAAO;AAAA,EACzC,MAAM,EAAE,QAAQ,iBAAiB;AAAA,EACjC,MAAM,EAAE,OAAO;AAAA,IACX,SAAS,EAAE,OAAO,OAAO,EAAE,IAAI;AAAA,EACnC,CAAC;AACL,CAAC;AAEM,aAAM,cAAc,EAAE,OAAO;AAAA,EAChC,MAAM,EAAE,QAAQ,MAAM;AAAA,EACtB,IAAI,EAAE,OAAO;AAAA,EACb,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,OAAO,EAAE,OAAO,EAAE,SAAS;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod';\nimport {\n Chapter,\n ChapterContent,\n ParseTree,\n Verse,\n VerseContent,\n} from './types.js';\nimport { getBookId, parseVerseReference, VerseRef } from '../utils.js';\nimport { ref } from 'process';\n\nexport const chapterHeadingSchema = z.object({\n type: z.literal('chapter-heading'),\n data: z.object({\n chapter: z.coerce.number().int(),\n }),\n});\n\nexport const verseSchema = z.object({\n type: z.literal('text'),\n id: z.string(),\n bookCode: z.string().optional(),\n chapter: z.number().optional(),\n verse: z.number().optional(),\n data: z\n .object({\n globalReferences: z.array(z.string()).optional(),\n })\n .optional(),\n});\n\nexport const paratextSchema = z.object({\n type: z.literal('paratext'),\n id: z.string(),\n});\n\nexport const metadataSchema = z.discriminatedUnion('type', [\n chapterHeadingSchema,\n verseSchema,\n paratextSchema,\n]);\n\nexport const codexSchema = z.object({\n cells: z.array(\n z.object({\n kind: z.number(),\n languageId: z.string(),\n value: z.string(),\n metadata: z\n .object({\n type: z.string(),\n })\n .passthrough()\n .nullable()\n .optional(),\n })\n ),\n});\n\n/**\n * Defines a parser for codex files.\n */\nexport class CodexParser {\n // TODO:\n // /**\n // * Whether to preserve markdown in the parsed content.\n // */\n // preserveMarkdown: boolean = true;\n\n private _noteCounter: number = 0;\n\n private _parser: DOMParser;\n\n constructor(parser: DOMParser) {\n this._parser = parser;\n }\n\n /**\n * Parses the specified codex content.\n *\n * @param codex The codex content to parse.\n * @returns The parse tree that was generated.\n */\n public parse(codex: string): ParseTree {\n const json = JSON.parse(codex);\n const data = codexSchema.parse(json);\n\n let root: ParseTree = {\n type: 'root',\n content: [],\n };\n\n let chapters: Map<number, Chapter> = new Map();\n let lastChapter: Chapter | null = null;\n\n function ensureChapter(chapterNumber: number) {\n let chapter = chapters.get(chapterNumber);\n if (!chapter) {\n chapter = {\n type: 'chapter',\n number: chapterNumber,\n content: [],\n footnotes: [],\n };\n lastChapter = chapter;\n chapters.set(chapterNumber, chapter);\n }\n\n return chapter;\n }\n\n function getDocumentContent(doc: Document): string {\n let content = '';\n for (let element of doc.childNodes) {\n content += element.textContent;\n }\n return content;\n }\n\n function addChapterContent(\n chapterNumber: number,\n content: ChapterContent[]\n ) {\n const chapter = ensureChapter(chapterNumber);\n chapter.content.push(...content);\n }\n\n function addChapterFootnote(\n chapterNumber: number,\n footnote: Chapter['footnotes'][0]\n ) {\n const chapter = ensureChapter(chapterNumber);\n chapter.footnotes.push(footnote);\n }\n\n function addReference(ref: VerseRef, content: Verse['content']) {\n addChapterContent(ref.chapter, [\n {\n type: 'verse',\n number: ref.verse,\n content: content,\n },\n ]);\n }\n\n function stripHTML(value: string) {\n return value.replace(/<[^>]*>/g, '');\n }\n\n function toHeading(content: string) {\n return {\n type: 'heading',\n content: [content],\n } satisfies ChapterContent;\n }\n\n function addLineBreaks(lines: Verse['content']) {\n return lines.reduce(\n (prev, current) => {\n if (typeof current === 'string' && current.trim() === '') {\n return prev; // skip empty lines\n }\n if (prev.length === 0) {\n return [current];\n } else {\n return [\n ...prev,\n {\n lineBreak: true,\n },\n current,\n ] satisfies Verse['content'];\n }\n },\n [] as Verse['content']\n );\n }\n\n let previousReference: VerseRef | null = null;\n let lines: Verse['content'] = [];\n\n for (let cell of data.cells) {\n if (cell.languageId === 'html' && cell.value) {\n if (!cell.metadata) continue; // ignore html cells without metadata\n\n const metadataResult = metadataSchema.safeParse(cell.metadata);\n\n if (!metadataResult.success) continue; // ignore cells with invalid/unknown metadata\n const metadata = metadataResult.data;\n if (metadata.type === 'text') {\n let reference = parseVerseReference(metadata.id);\n if (!reference) {\n if (metadata.bookCode && metadata.chapter) {\n const bookId = getBookId(metadata.bookCode);\n if (!bookId) {\n throw new Error(\n 'Could not find book ID for code: ' +\n metadata.bookCode\n );\n }\n\n if (metadata.verse) {\n reference = {\n book: bookId,\n chapter: metadata.chapter,\n verse: metadata.verse,\n };\n } else if (previousReference?.verse) {\n if (\n previousReference.chapter !==\n metadata.chapter ||\n previousReference.book !== bookId\n ) {\n throw new Error(\n 'Cannot infer verse number for non-consecutive verse reference.'\n );\n }\n\n // Sometimes codex doesn't include verse numbers for every cell, so we infer it from the previous reference\n reference = {\n book: bookId,\n chapter: metadata.chapter,\n verse: previousReference.verse,\n };\n } else {\n throw new Error(\n 'Could not find verse reference in metadata.'\n );\n }\n } else if (metadata.data?.globalReferences?.length) {\n reference = parseVerseReference(\n metadata.data.globalReferences[0]\n );\n if (!reference) {\n throw new Error(\n 'Could not find verse reference in globalReferences metadata.'\n );\n }\n } else {\n throw new Error(\n 'Could not find verse reference in metadata.'\n );\n }\n }\n\n if (\n previousReference &&\n (reference.book !== previousReference.book ||\n reference.chapter !== previousReference.chapter ||\n reference.verse !== previousReference.verse)\n ) {\n addReference(previousReference, addLineBreaks(lines));\n lines = [];\n }\n\n previousReference = reference;\n\n if (!root.id) root.id = reference.book; // set book id if not already set\n\n const doc = this._parser.parseFromString(\n cell.value,\n 'text/html'\n );\n\n for (let element of doc.children) {\n const allFootnotes = element.querySelectorAll(\n 'sup.footnote-marker[data-footnote]'\n );\n for (const footnote of allFootnotes) {\n const footnoteData =\n footnote.getAttribute('data-footnote');\n if (footnoteData) {\n const footnoteDoc =\n this._parser.parseFromString(\n footnoteData,\n 'text/html'\n );\n const footnoteText =\n getDocumentContent(footnoteDoc);\n if (footnoteText) {\n addChapterFootnote(reference.chapter, {\n noteId: this._noteCounter++,\n text: footnoteText,\n caller: null,\n });\n }\n }\n footnote.remove();\n }\n }\n\n const content = getDocumentContent(doc);\n\n if (content) {\n // add line breaks in heading if there are multiple lines\n const newLines = content.split('\\n');\n\n lines.push(...newLines);\n }\n } else if (metadata.type === 'paratext') {\n const reference = parseVerseReference(`${metadata.id}:1`); // chapter headings do not have a verse reference always default to 1\n if (reference) {\n const doc = this._parser.parseFromString(\n cell.value,\n 'text/html'\n );\n const content = getDocumentContent(doc);\n\n if (!content) {\n continue;\n }\n\n // add line breaks in heading if there are multiple lines\n const lines = content\n .split('\\n')\n .reduce<ChapterContent[]>((prev, current) => {\n if (prev.length === 0)\n return [toHeading(current)];\n else\n return [\n ...prev,\n {\n type: 'line_break',\n },\n toHeading(current),\n ] satisfies ChapterContent[];\n }, []);\n\n lines.forEach((line) =>\n addChapterContent(reference.chapter, [line])\n );\n } else {\n const doc = this._parser.parseFromString(\n cell.value,\n 'text/html'\n );\n const content = getDocumentContent(doc);\n // parse paratext that isn't a chapter reference as footnote\n // const content = stripHTML(cell.value);\n if (content && !cell.metadata) {\n if (lastChapter) {\n (lastChapter as Chapter).footnotes.push({\n noteId: this._noteCounter++,\n text: content,\n caller: null,\n });\n }\n }\n }\n }\n }\n }\n\n if (previousReference) {\n addReference(previousReference, addLineBreaks(lines));\n }\n\n for (let chapter of chapters.values()) {\n root.content.push(chapter);\n }\n\n return root;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["ref", "lines"]
|
|
7
7
|
}
|
|
@@ -26,17 +26,30 @@ export declare const verseSchema: z.ZodObject<{
|
|
|
26
26
|
bookCode: z.ZodOptional<z.ZodString>;
|
|
27
27
|
chapter: z.ZodOptional<z.ZodNumber>;
|
|
28
28
|
verse: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
data: z.ZodOptional<z.ZodObject<{
|
|
30
|
+
globalReferences: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
globalReferences?: string[] | undefined;
|
|
33
|
+
}, {
|
|
34
|
+
globalReferences?: string[] | undefined;
|
|
35
|
+
}>>;
|
|
29
36
|
}, "strip", z.ZodTypeAny, {
|
|
30
37
|
id: string;
|
|
31
38
|
type: "text";
|
|
32
39
|
chapter?: number | undefined;
|
|
33
40
|
verse?: number | undefined;
|
|
41
|
+
data?: {
|
|
42
|
+
globalReferences?: string[] | undefined;
|
|
43
|
+
} | undefined;
|
|
34
44
|
bookCode?: string | undefined;
|
|
35
45
|
}, {
|
|
36
46
|
id: string;
|
|
37
47
|
type: "text";
|
|
38
48
|
chapter?: number | undefined;
|
|
39
49
|
verse?: number | undefined;
|
|
50
|
+
data?: {
|
|
51
|
+
globalReferences?: string[] | undefined;
|
|
52
|
+
} | undefined;
|
|
40
53
|
bookCode?: string | undefined;
|
|
41
54
|
}>;
|
|
42
55
|
export declare const paratextSchema: z.ZodObject<{
|
|
@@ -74,17 +87,30 @@ export declare const metadataSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObjec
|
|
|
74
87
|
bookCode: z.ZodOptional<z.ZodString>;
|
|
75
88
|
chapter: z.ZodOptional<z.ZodNumber>;
|
|
76
89
|
verse: z.ZodOptional<z.ZodNumber>;
|
|
90
|
+
data: z.ZodOptional<z.ZodObject<{
|
|
91
|
+
globalReferences: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
92
|
+
}, "strip", z.ZodTypeAny, {
|
|
93
|
+
globalReferences?: string[] | undefined;
|
|
94
|
+
}, {
|
|
95
|
+
globalReferences?: string[] | undefined;
|
|
96
|
+
}>>;
|
|
77
97
|
}, "strip", z.ZodTypeAny, {
|
|
78
98
|
id: string;
|
|
79
99
|
type: "text";
|
|
80
100
|
chapter?: number | undefined;
|
|
81
101
|
verse?: number | undefined;
|
|
102
|
+
data?: {
|
|
103
|
+
globalReferences?: string[] | undefined;
|
|
104
|
+
} | undefined;
|
|
82
105
|
bookCode?: string | undefined;
|
|
83
106
|
}, {
|
|
84
107
|
id: string;
|
|
85
108
|
type: "text";
|
|
86
109
|
chapter?: number | undefined;
|
|
87
110
|
verse?: number | undefined;
|
|
111
|
+
data?: {
|
|
112
|
+
globalReferences?: string[] | undefined;
|
|
113
|
+
} | undefined;
|
|
88
114
|
bookCode?: string | undefined;
|
|
89
115
|
}>, z.ZodObject<{
|
|
90
116
|
type: z.ZodLiteral<"paratext">;
|
|
@@ -147,6 +173,8 @@ export declare const codexSchema: z.ZodObject<{
|
|
|
147
173
|
*/
|
|
148
174
|
export declare class CodexParser {
|
|
149
175
|
private _noteCounter;
|
|
176
|
+
private _parser;
|
|
177
|
+
constructor(parser: DOMParser);
|
|
150
178
|
/**
|
|
151
179
|
* Parses the specified codex content.
|
|
152
180
|
*
|