@helloao/tools 0.0.6-alpha → 0.0.9

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../parser/usx-parser.ts"],
4
- "sourcesContent": ["import {\n Chapter,\n ChapterContent,\n Footnote,\n FootnoteReference,\n ParseTree,\n Verse,\n Text,\n HebrewSubtitle,\n InlineLineBreak,\n InlineHeading,\n} from './types.js';\nimport {\n iterateAll,\n children,\n RewindableIterator,\n isParent,\n} from './iterators.js';\n\nenum NodeType {\n Text = 3,\n}\n\n/**\n * The version of the parser.\n * Used to determine whether input files need to be re-parsed.\n */\nexport const PARSER_VERSION = '1';\n\n/**\n * Defines a class that is able to parse USX content.\n */\nexport class USXParser {\n private _domParser: DOMParser;\n private _noteCounter: number = 0;\n\n constructor(domParser: DOMParser) {\n this._domParser = domParser;\n }\n\n /**\n * Parses the specified USX content.\n *\n * @param usx The USX content to parse.\n * @returns The parse tree that was generated.\n */\n public parse(usx: string): ParseTree {\n const parser = this._domParser;\n const doc = parser.parseFromString(usx, 'application/xml');\n const usxElement = doc.documentElement;\n\n let root: ParseTree = {\n type: 'root',\n content: [],\n };\n\n const bookElement = usxElement.querySelector('book[code]');\n\n if (!bookElement) {\n throw new Error('The USX content does not contain a book element.');\n }\n\n const bookCode = bookElement.getAttribute('code') || '';\n\n if (!bookCode) {\n throw new Error(\n 'The book element does not contain a code attribute.'\n );\n }\n\n root.id = bookCode;\n\n const header = usxElement.querySelector('para[style=\"h\"]');\n if (header) {\n root.header = header.textContent || '';\n }\n\n const titles = usxElement.querySelectorAll(\n 'para[style=\"mt1\"], para[style=\"mt2\"], para[style=\"mt3\"]'\n );\n // const title2 = usxElement.querySelector('para[style=\"mt2\"]');\n // const title3 = usxElement.querySelector('para[style=\"mt3\"]');\n\n if (titles.length > 0) {\n root.title = [...titles]\n .map((t) => t.textContent)\n .filter((t) => t)\n .join(' ');\n }\n\n for (let content of this.iterateRootContent(usxElement)) {\n root.content.push(content);\n }\n\n return root;\n }\n\n *iterateRootContent(\n usxElement: Element\n ): Generator<ParseTree['content'][0]> {\n const iterator = iterateAll(usxElement);\n while (true) {\n const { done, value: child } = iterator.next();\n if (done) {\n break;\n }\n\n if (!(child instanceof Element)) {\n continue;\n }\n\n if (child.nodeName === 'chapter') {\n if (child.hasAttribute('eid')) {\n continue;\n }\n\n const chapter: Chapter = {\n type: 'chapter',\n number: parseInt(child.getAttribute('number') || '0', 10),\n content: [],\n footnotes: [],\n };\n\n for (let content of this.iterateChapterContent(\n chapter,\n iterator\n )) {\n chapter.content.push(content);\n }\n\n yield chapter;\n } else if (child.nodeName === 'para') {\n const style = child.getAttribute('style');\n if (\n style === 's1' ||\n style === 's2' ||\n style === 's3' ||\n style === 's4'\n ) {\n yield {\n type: 'heading',\n content: child.textContent ? [child.textContent] : [],\n };\n }\n }\n }\n }\n\n *iterateChapterContent(\n chapter: Chapter,\n nodes: RewindableIterator<Node>\n ): IterableIterator<ChapterContent> {\n while (true) {\n const { done, value: element } = nodes.next();\n if (done) {\n break;\n }\n\n if (!(element instanceof Element)) {\n continue;\n }\n\n if (element.nodeName === 'chapter') {\n break;\n } else if (element.nodeName === 'para') {\n const style = element.getAttribute('style');\n if (\n style === 's1' ||\n style === 's2' ||\n style === 's3' ||\n style === 's4'\n ) {\n yield {\n type: 'heading',\n content: element.textContent\n ? [element.textContent]\n : [],\n };\n } else if (style === 'b') {\n yield {\n type: 'line_break',\n };\n } else if (style === 'd') {\n yield* this.parseHebrewSubtitle(element, chapter, nodes);\n }\n } else if (element.nodeName === 'verse') {\n if (element.hasAttribute('eid')) {\n continue;\n }\n\n yield this.parseVerse(element, chapter, nodes);\n }\n }\n }\n\n *iterateVerseContent(\n chapter: Chapter,\n verse: Verse,\n nodes: RewindableIterator<Node>\n ): IterableIterator<string | FootnoteReference | Text | InlineLineBreak> {\n while (true) {\n const { done, value: node } = nodes.next();\n if (done) {\n break;\n }\n\n if (node.nodeName === 'verse') {\n break;\n }\n\n const parent = node.parentElement!;\n let poem: number | null = null;\n let descriptive: boolean | null = null;\n\n if (parent.nodeName === 'para') {\n const style = parent.getAttribute('style');\n if (\n style === 'q1' ||\n style === 'q2' ||\n style === 'q3' ||\n style === 'q4'\n ) {\n poem =\n style === 'q1'\n ? 1\n : style === 'q2'\n ? 2\n : style === 'q3'\n ? 3\n : 4;\n } else if (style === 'd') {\n descriptive = true;\n }\n }\n\n for (let content of this.iterateNodeTextContent(\n nodes,\n node,\n chapter,\n verse\n )) {\n if (poem !== null || descriptive !== null) {\n if (typeof content === 'string') {\n let text: Text = {\n text: content,\n };\n\n if (poem !== null) {\n text.poem = poem;\n }\n\n if (descriptive !== null) {\n text.descriptive = true;\n }\n\n yield text;\n } else {\n let text:\n | InlineLineBreak\n | InlineHeading\n | FootnoteReference\n | Text = {\n ...content,\n };\n\n if ('text' in text) {\n if (poem !== null) {\n text.poem = poem;\n }\n\n if (descriptive !== null) {\n text.descriptive = true;\n }\n }\n yield text;\n }\n } else {\n yield content;\n }\n }\n }\n }\n\n parseVerse(\n element: Element,\n chapter: Chapter,\n nodes: RewindableIterator<Node>\n ): Verse {\n const verse: Verse = {\n type: 'verse',\n number: parseInt(element.getAttribute('number') || '0', 10),\n content: [],\n };\n\n for (let content of this.iterateVerseContent(chapter, verse, nodes)) {\n addOrJoin(verse.content, content);\n }\n\n trimContent(verse.content);\n return verse;\n }\n\n *parseHebrewSubtitle(\n para: Element,\n chapter: Chapter,\n nodes: RewindableIterator<Node>\n ): IterableIterator<HebrewSubtitle | Verse> {\n const subtitle: HebrewSubtitle = {\n type: 'hebrew_subtitle',\n content: [],\n };\n\n for (let content of this.iterateHebrewSubtitleContent(\n para,\n chapter,\n nodes\n )) {\n if (typeof content === 'object' && 'number' in content) {\n yield content;\n continue;\n }\n addOrJoin(subtitle.content, content);\n }\n\n trimContent(subtitle.content);\n if (subtitle.content.length > 0) {\n yield subtitle;\n }\n }\n\n *iterateHebrewSubtitleContent(\n element: Element,\n chapter: Chapter,\n nodes: RewindableIterator<Node>\n ): IterableIterator<\n Verse | string | Text | FootnoteReference | InlineLineBreak\n > {\n while (true) {\n const { done, value: node } = nodes.next();\n if (done) {\n break;\n }\n\n if (!isParent(node, element)) {\n nodes.rewind(1);\n break;\n }\n\n if (node instanceof Element && node.nodeName === 'verse') {\n yield this.parseVerse(node, chapter, nodes);\n } else {\n yield* this.iterateNodeTextContent(nodes, node, chapter);\n }\n }\n }\n\n *iterateNodeTextContent(\n nodes: RewindableIterator<Node>,\n node: Node,\n chapter: Chapter,\n verse?: Verse\n ): IterableIterator<string | Text | FootnoteReference | InlineLineBreak> {\n if (node instanceof Element && node.nodeName === 'note') {\n yield* this.iterateNote(nodes, node, chapter, verse);\n } else if (node instanceof Element && node.nodeName === 'char') {\n yield* this.iterateChar(nodes, node);\n } else if (\n node instanceof Element &&\n node.nodeName === 'para' &&\n node.getAttribute('style') === 'b'\n ) {\n for (let _ of children(nodes, node)) {\n // iterate through all the children to prevent iterating over them multiple times\n }\n yield {\n lineBreak: true,\n };\n } else if (node.nodeType === NodeType.Text) {\n yield node.textContent || '';\n }\n }\n\n *iterateCharContent(char: Element): IterableIterator<string | Text> {\n const style = char.getAttribute('style');\n const text = trimText(char.textContent || '');\n if (style === 'wj') {\n yield {\n text,\n wordsOfJesus: true,\n };\n } else {\n yield text;\n }\n }\n\n *iterateNote(\n nodes: RewindableIterator<Node>,\n node: Element,\n chapter: Chapter,\n verse?: Verse\n ): IterableIterator<FootnoteReference> {\n const style = node.getAttribute('style');\n if (style === 'f') {\n const verseReferenceRegex = /^[0-9]{1,3}:[0-9]{1,3}/;\n\n let text = '';\n for (let child of children(nodes, node)) {\n if (child.nodeType === NodeType.Text) {\n text += child.textContent || '';\n }\n }\n\n text = text.trim();\n if (verseReferenceRegex.test(text)) {\n text = text.replace(verseReferenceRegex, '').trim();\n }\n\n const note: Footnote = {\n noteId: this._noteCounter++,\n caller: node.getAttribute('caller') || null,\n text,\n reference: {\n chapter: chapter.number,\n verse: verse?.number ?? 0,\n },\n };\n\n chapter.footnotes.push(note);\n\n yield {\n noteId: note.noteId,\n };\n } else {\n for (let _ of children(nodes, node)) {\n // iterate through all the children\n // so that we don't end up with duplicates\n }\n }\n }\n\n *iterateChar(\n nodes: RewindableIterator<Node>,\n node: Element\n ): IterableIterator<string | Text> {\n const style = node.getAttribute('style');\n let text = '';\n\n for (let char of children(nodes, node)) {\n if (char.nodeType === NodeType.Text) {\n text += char.textContent || '';\n }\n }\n\n if (style === 'wj') {\n yield {\n text,\n wordsOfJesus: true,\n };\n } else {\n yield text;\n }\n\n // if (!parentChar(node) && !parentNote(node)) {\n // yield *iterateCharContent(node);\n // }\n }\n}\n\n// Taken from https://github.com/gracious-tech/fetch/blob/1576cc4eafb32bf347a09332094cf17c2231c90c/converters/usx-to-json/src/elements.ts#L16\nconst ignoredParaStyles = new Set([\n // <para> Identification [exclude all] - Running headings & table of contents\n 'ide', // See https://github.com/schierlm/BibleMultiConverter/issues/67\n 'rem', // Remarks (valid in schema though missed in docs)\n 'h',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'toc1',\n 'toc2',\n 'toc3',\n 'toca1',\n 'toca2',\n 'toca3',\n\n /* <para> Introductions [exclude all] - Introductionary (non-biblical) content\n Which might be helpful in a printed book, but intro material in apps is usually bad UX,\n and users that really care can research a translations methodology themselves\n */\n 'imt',\n 'imt1',\n 'imt2',\n 'imt3',\n 'imt4',\n 'is',\n 'is1',\n 'is2',\n 'is3',\n 'is4',\n 'ip',\n 'ipi',\n 'im',\n 'imi',\n 'ipq',\n 'imq',\n 'ipr',\n 'iq',\n 'iq1',\n 'iq2',\n 'iq3',\n 'iq4',\n 'ib',\n 'ili',\n 'ili1',\n 'ili2',\n 'ili3',\n 'ili4',\n 'iot',\n 'io',\n 'io1',\n 'io2',\n 'io3',\n 'io4',\n 'iex',\n 'imte',\n 'ie',\n\n /* <para> Headings [exclude some] - Exclude book & chapter headings but keep section headings\n Not excluded: ms# | mr | s# | sr | d | sp | sd#\n */\n 'mt',\n 'mt1',\n 'mt2',\n 'mt3',\n 'mt4',\n 'mte',\n 'mte1',\n 'mte2',\n 'mte3',\n 'mte4',\n 'cl',\n 'cd', // Non-biblical chapter summary, more than heading\n 'r', // Parallels to be provided by external data\n]);\n\nfunction* iterateCharContent(char: Element): IterableIterator<string | Text> {\n const style = char.getAttribute('style');\n const text = trimText(char.textContent || '');\n if (style === 'wj') {\n yield {\n text,\n wordsOfJesus: true,\n };\n } else {\n yield text;\n }\n}\n\nfunction trimText(text: string): string {\n return text.replace(/\\s+/g, ' ');\n}\n\nfunction trimContent<T extends string | unknown>(content: T[]): T[] {\n for (let i = 0; i < content.length; i++) {\n const value = content[i];\n if (typeof value === 'string') {\n content[i] = trimText(value as string).trim() as T;\n if (content[i] === '') {\n content.splice(i, 1);\n i--;\n continue;\n }\n } else if (isVerseText(value)) {\n value.text = trimText(value.text).trim();\n if (value.text === '') {\n content.splice(i, 1);\n i--;\n continue;\n }\n }\n }\n return content;\n}\n\nfunction addOrJoin(array: (string | unknown)[], value: string | unknown) {\n if (array.length === 0) {\n array.push(value);\n } else {\n const last = array[array.length - 1];\n if (typeof last === 'string' && typeof value === 'string') {\n array[array.length - 1] = last + value;\n } else if (\n isVerseText(last) &&\n isVerseText(value) &&\n hasSameFormatting(last, value)\n ) {\n last.text += value.text;\n } else {\n array.push(value);\n }\n }\n}\n\nfunction isVerseText(value: unknown): value is Text {\n return typeof value === 'object' && value !== null && 'text' in value;\n}\n\nfunction hasSameFormatting(a: Text, b: Text): boolean {\n return a.poem === b.poem && a.wordsOfJesus === b.wordsOfJesus;\n}\n"],
4
+ "sourcesContent": ["import {\r\n Chapter,\r\n ChapterContent,\r\n Footnote,\r\n FootnoteReference,\r\n ParseTree,\r\n Verse,\r\n Text,\r\n HebrewSubtitle,\r\n InlineLineBreak,\r\n InlineHeading,\r\n} from './types.js';\r\nimport {\r\n iterateAll,\r\n children,\r\n RewindableIterator,\r\n isParent,\r\n} from './iterators.js';\r\n\r\nenum NodeType {\r\n Text = 3,\r\n}\r\n\r\n/**\r\n * The version of the parser.\r\n * Used to determine whether input files need to be re-parsed.\r\n */\r\nexport const PARSER_VERSION = '1';\r\n\r\n/**\r\n * Defines a class that is able to parse USX content.\r\n */\r\nexport class USXParser {\r\n private _domParser: DOMParser;\r\n private _noteCounter: number = 0;\r\n\r\n constructor(domParser: DOMParser) {\r\n this._domParser = domParser;\r\n }\r\n\r\n /**\r\n * Parses the specified USX content.\r\n *\r\n * @param usx The USX content to parse.\r\n * @returns The parse tree that was generated.\r\n */\r\n public parse(usx: string): ParseTree {\r\n const parser = this._domParser;\r\n const doc = parser.parseFromString(usx, 'application/xml');\r\n const usxElement = doc.documentElement;\r\n\r\n let root: ParseTree = {\r\n type: 'root',\r\n content: [],\r\n };\r\n\r\n const bookElement = usxElement.querySelector('book[code]');\r\n\r\n if (!bookElement) {\r\n throw new Error('The USX content does not contain a book element.');\r\n }\r\n\r\n const bookCode = bookElement.getAttribute('code') || '';\r\n\r\n if (!bookCode) {\r\n throw new Error(\r\n 'The book element does not contain a code attribute.'\r\n );\r\n }\r\n\r\n root.id = bookCode;\r\n\r\n const header = usxElement.querySelector('para[style=\"h\"]');\r\n if (header) {\r\n root.header = header.textContent || '';\r\n }\r\n\r\n const titles = usxElement.querySelectorAll(\r\n 'para[style=\"mt1\"], para[style=\"mt2\"], para[style=\"mt3\"]'\r\n );\r\n // const title2 = usxElement.querySelector('para[style=\"mt2\"]');\r\n // const title3 = usxElement.querySelector('para[style=\"mt3\"]');\r\n\r\n if (titles.length > 0) {\r\n root.title = [...titles]\r\n .map((t) => t.textContent)\r\n .filter((t) => t)\r\n .join(' ');\r\n }\r\n\r\n for (let content of this.iterateRootContent(usxElement)) {\r\n root.content.push(content);\r\n }\r\n\r\n return root;\r\n }\r\n\r\n *iterateRootContent(\r\n usxElement: Element\r\n ): Generator<ParseTree['content'][0]> {\r\n const iterator = iterateAll(usxElement);\r\n while (true) {\r\n const { done, value: child } = iterator.next();\r\n if (done) {\r\n break;\r\n }\r\n\r\n if (!(child instanceof Element)) {\r\n continue;\r\n }\r\n\r\n if (child.nodeName === 'chapter') {\r\n if (child.hasAttribute('eid')) {\r\n continue;\r\n }\r\n\r\n const chapter: Chapter = {\r\n type: 'chapter',\r\n number: parseInt(child.getAttribute('number') || '0', 10),\r\n content: [],\r\n footnotes: [],\r\n };\r\n\r\n for (let content of this.iterateChapterContent(\r\n chapter,\r\n iterator\r\n )) {\r\n chapter.content.push(content);\r\n }\r\n\r\n yield chapter;\r\n } else if (child.nodeName === 'para') {\r\n const style = child.getAttribute('style');\r\n if (\r\n style === 's1' ||\r\n style === 's2' ||\r\n style === 's3' ||\r\n style === 's4'\r\n ) {\r\n yield {\r\n type: 'heading',\r\n content: child.textContent ? [child.textContent] : [],\r\n };\r\n }\r\n }\r\n }\r\n }\r\n\r\n *iterateChapterContent(\r\n chapter: Chapter,\r\n nodes: RewindableIterator<Node>\r\n ): IterableIterator<ChapterContent> {\r\n while (true) {\r\n const { done, value: element } = nodes.next();\r\n if (done) {\r\n break;\r\n }\r\n\r\n if (!(element instanceof Element)) {\r\n continue;\r\n }\r\n\r\n if (element.nodeName === 'chapter') {\r\n break;\r\n } else if (element.nodeName === 'para') {\r\n const style = element.getAttribute('style');\r\n if (\r\n style === 's1' ||\r\n style === 's2' ||\r\n style === 's3' ||\r\n style === 's4'\r\n ) {\r\n yield {\r\n type: 'heading',\r\n content: element.textContent\r\n ? [element.textContent]\r\n : [],\r\n };\r\n } else if (style === 'b') {\r\n yield {\r\n type: 'line_break',\r\n };\r\n } else if (style === 'd') {\r\n yield* this.parseHebrewSubtitle(element, chapter, nodes);\r\n }\r\n } else if (element.nodeName === 'verse') {\r\n if (element.hasAttribute('eid')) {\r\n continue;\r\n }\r\n\r\n yield this.parseVerse(element, chapter, nodes);\r\n }\r\n }\r\n }\r\n\r\n *iterateVerseContent(\r\n chapter: Chapter,\r\n verse: Verse,\r\n nodes: RewindableIterator<Node>\r\n ): IterableIterator<string | FootnoteReference | Text | InlineLineBreak> {\r\n while (true) {\r\n const { done, value: node } = nodes.next();\r\n if (done) {\r\n break;\r\n }\r\n\r\n if (node.nodeName === 'verse') {\r\n break;\r\n }\r\n\r\n const parent = node.parentElement!;\r\n let poem: number | null = null;\r\n let descriptive: boolean | null = null;\r\n\r\n if (parent.nodeName === 'para') {\r\n const style = parent.getAttribute('style');\r\n if (\r\n style === 'q1' ||\r\n style === 'q2' ||\r\n style === 'q3' ||\r\n style === 'q4'\r\n ) {\r\n poem =\r\n style === 'q1'\r\n ? 1\r\n : style === 'q2'\r\n ? 2\r\n : style === 'q3'\r\n ? 3\r\n : 4;\r\n } else if (style === 'd') {\r\n descriptive = true;\r\n }\r\n }\r\n\r\n for (let content of this.iterateNodeTextContent(\r\n nodes,\r\n node,\r\n chapter,\r\n verse\r\n )) {\r\n if (poem !== null || descriptive !== null) {\r\n if (typeof content === 'string') {\r\n let text: Text = {\r\n text: content,\r\n };\r\n\r\n if (poem !== null) {\r\n text.poem = poem;\r\n }\r\n\r\n if (descriptive !== null) {\r\n text.descriptive = true;\r\n }\r\n\r\n yield text;\r\n } else {\r\n let text:\r\n | InlineLineBreak\r\n | InlineHeading\r\n | FootnoteReference\r\n | Text = {\r\n ...content,\r\n };\r\n\r\n if ('text' in text) {\r\n if (poem !== null) {\r\n text.poem = poem;\r\n }\r\n\r\n if (descriptive !== null) {\r\n text.descriptive = true;\r\n }\r\n }\r\n yield text;\r\n }\r\n } else {\r\n yield content;\r\n }\r\n }\r\n }\r\n }\r\n\r\n parseVerse(\r\n element: Element,\r\n chapter: Chapter,\r\n nodes: RewindableIterator<Node>\r\n ): Verse {\r\n const verse: Verse = {\r\n type: 'verse',\r\n number: parseInt(element.getAttribute('number') || '0', 10),\r\n content: [],\r\n };\r\n\r\n for (let content of this.iterateVerseContent(chapter, verse, nodes)) {\r\n addOrJoin(verse.content, content);\r\n }\r\n\r\n trimContent(verse.content);\r\n return verse;\r\n }\r\n\r\n *parseHebrewSubtitle(\r\n para: Element,\r\n chapter: Chapter,\r\n nodes: RewindableIterator<Node>\r\n ): IterableIterator<HebrewSubtitle | Verse> {\r\n const subtitle: HebrewSubtitle = {\r\n type: 'hebrew_subtitle',\r\n content: [],\r\n };\r\n\r\n for (let content of this.iterateHebrewSubtitleContent(\r\n para,\r\n chapter,\r\n nodes\r\n )) {\r\n if (typeof content === 'object' && 'number' in content) {\r\n yield content;\r\n continue;\r\n }\r\n addOrJoin(subtitle.content, content);\r\n }\r\n\r\n trimContent(subtitle.content);\r\n if (subtitle.content.length > 0) {\r\n yield subtitle;\r\n }\r\n }\r\n\r\n *iterateHebrewSubtitleContent(\r\n element: Element,\r\n chapter: Chapter,\r\n nodes: RewindableIterator<Node>\r\n ): IterableIterator<\r\n Verse | string | Text | FootnoteReference | InlineLineBreak\r\n > {\r\n while (true) {\r\n const { done, value: node } = nodes.next();\r\n if (done) {\r\n break;\r\n }\r\n\r\n if (!isParent(node, element)) {\r\n nodes.rewind(1);\r\n break;\r\n }\r\n\r\n if (node instanceof Element && node.nodeName === 'verse') {\r\n yield this.parseVerse(node, chapter, nodes);\r\n } else {\r\n yield* this.iterateNodeTextContent(nodes, node, chapter);\r\n }\r\n }\r\n }\r\n\r\n *iterateNodeTextContent(\r\n nodes: RewindableIterator<Node>,\r\n node: Node,\r\n chapter: Chapter,\r\n verse?: Verse\r\n ): IterableIterator<string | Text | FootnoteReference | InlineLineBreak> {\r\n if (node instanceof Element && node.nodeName === 'note') {\r\n yield* this.iterateNote(nodes, node, chapter, verse);\r\n } else if (node instanceof Element && node.nodeName === 'char') {\r\n yield* this.iterateChar(nodes, node);\r\n } else if (\r\n node instanceof Element &&\r\n node.nodeName === 'para' &&\r\n node.getAttribute('style') === 'b'\r\n ) {\r\n for (let _ of children(nodes, node)) {\r\n // iterate through all the children to prevent iterating over them multiple times\r\n }\r\n yield {\r\n lineBreak: true,\r\n };\r\n } else if (node.nodeType === NodeType.Text) {\r\n yield node.textContent || '';\r\n }\r\n }\r\n\r\n *iterateCharContent(char: Element): IterableIterator<string | Text> {\r\n const style = char.getAttribute('style');\r\n const text = trimText(char.textContent || '');\r\n if (style === 'wj') {\r\n yield {\r\n text,\r\n wordsOfJesus: true,\r\n };\r\n } else {\r\n yield text;\r\n }\r\n }\r\n\r\n *iterateNote(\r\n nodes: RewindableIterator<Node>,\r\n node: Element,\r\n chapter: Chapter,\r\n verse?: Verse\r\n ): IterableIterator<FootnoteReference> {\r\n const style = node.getAttribute('style');\r\n if (style === 'f') {\r\n const verseReferenceRegex = /^[0-9]{1,3}:[0-9]{1,3}/;\r\n\r\n let text = '';\r\n for (let child of children(nodes, node)) {\r\n if (child.nodeType === NodeType.Text) {\r\n text += child.textContent || '';\r\n }\r\n }\r\n\r\n text = text.trim();\r\n if (verseReferenceRegex.test(text)) {\r\n text = text.replace(verseReferenceRegex, '').trim();\r\n }\r\n\r\n const note: Footnote = {\r\n noteId: this._noteCounter++,\r\n caller: node.getAttribute('caller') || null,\r\n text,\r\n reference: {\r\n chapter: chapter.number,\r\n verse: verse?.number ?? 0,\r\n },\r\n };\r\n\r\n chapter.footnotes.push(note);\r\n\r\n yield {\r\n noteId: note.noteId,\r\n };\r\n } else {\r\n for (let _ of children(nodes, node)) {\r\n // iterate through all the children\r\n // so that we don't end up with duplicates\r\n }\r\n }\r\n }\r\n\r\n *iterateChar(\r\n nodes: RewindableIterator<Node>,\r\n node: Element\r\n ): IterableIterator<string | Text> {\r\n const style = node.getAttribute('style');\r\n let text = '';\r\n\r\n for (let char of children(nodes, node)) {\r\n if (char.nodeType === NodeType.Text) {\r\n text += char.textContent || '';\r\n }\r\n }\r\n\r\n if (style === 'wj') {\r\n yield {\r\n text,\r\n wordsOfJesus: true,\r\n };\r\n } else {\r\n yield text;\r\n }\r\n\r\n // if (!parentChar(node) && !parentNote(node)) {\r\n // yield *iterateCharContent(node);\r\n // }\r\n }\r\n}\r\n\r\n// Taken from https://github.com/gracious-tech/fetch/blob/1576cc4eafb32bf347a09332094cf17c2231c90c/converters/usx-to-json/src/elements.ts#L16\r\nconst ignoredParaStyles = new Set([\r\n // <para> Identification [exclude all] - Running headings & table of contents\r\n 'ide', // See https://github.com/schierlm/BibleMultiConverter/issues/67\r\n 'rem', // Remarks (valid in schema though missed in docs)\r\n 'h',\r\n 'h1',\r\n 'h2',\r\n 'h3',\r\n 'h4',\r\n 'toc1',\r\n 'toc2',\r\n 'toc3',\r\n 'toca1',\r\n 'toca2',\r\n 'toca3',\r\n\r\n /* <para> Introductions [exclude all] - Introductionary (non-biblical) content\r\n Which might be helpful in a printed book, but intro material in apps is usually bad UX,\r\n and users that really care can research a translations methodology themselves\r\n */\r\n 'imt',\r\n 'imt1',\r\n 'imt2',\r\n 'imt3',\r\n 'imt4',\r\n 'is',\r\n 'is1',\r\n 'is2',\r\n 'is3',\r\n 'is4',\r\n 'ip',\r\n 'ipi',\r\n 'im',\r\n 'imi',\r\n 'ipq',\r\n 'imq',\r\n 'ipr',\r\n 'iq',\r\n 'iq1',\r\n 'iq2',\r\n 'iq3',\r\n 'iq4',\r\n 'ib',\r\n 'ili',\r\n 'ili1',\r\n 'ili2',\r\n 'ili3',\r\n 'ili4',\r\n 'iot',\r\n 'io',\r\n 'io1',\r\n 'io2',\r\n 'io3',\r\n 'io4',\r\n 'iex',\r\n 'imte',\r\n 'ie',\r\n\r\n /* <para> Headings [exclude some] - Exclude book & chapter headings but keep section headings\r\n Not excluded: ms# | mr | s# | sr | d | sp | sd#\r\n */\r\n 'mt',\r\n 'mt1',\r\n 'mt2',\r\n 'mt3',\r\n 'mt4',\r\n 'mte',\r\n 'mte1',\r\n 'mte2',\r\n 'mte3',\r\n 'mte4',\r\n 'cl',\r\n 'cd', // Non-biblical chapter summary, more than heading\r\n 'r', // Parallels to be provided by external data\r\n]);\r\n\r\nfunction* iterateCharContent(char: Element): IterableIterator<string | Text> {\r\n const style = char.getAttribute('style');\r\n const text = trimText(char.textContent || '');\r\n if (style === 'wj') {\r\n yield {\r\n text,\r\n wordsOfJesus: true,\r\n };\r\n } else {\r\n yield text;\r\n }\r\n}\r\n\r\nfunction trimText(text: string): string {\r\n return text.replace(/\\s+/g, ' ');\r\n}\r\n\r\nfunction trimContent<T extends string | unknown>(content: T[]): T[] {\r\n for (let i = 0; i < content.length; i++) {\r\n const value = content[i];\r\n if (typeof value === 'string') {\r\n content[i] = trimText(value as string).trim() as T;\r\n if (content[i] === '') {\r\n content.splice(i, 1);\r\n i--;\r\n continue;\r\n }\r\n } else if (isVerseText(value)) {\r\n value.text = trimText(value.text).trim();\r\n if (value.text === '') {\r\n content.splice(i, 1);\r\n i--;\r\n continue;\r\n }\r\n }\r\n }\r\n return content;\r\n}\r\n\r\nfunction addOrJoin(array: (string | unknown)[], value: string | unknown) {\r\n if (array.length === 0) {\r\n array.push(value);\r\n } else {\r\n const last = array[array.length - 1];\r\n if (typeof last === 'string' && typeof value === 'string') {\r\n array[array.length - 1] = last + value;\r\n } else if (\r\n isVerseText(last) &&\r\n isVerseText(value) &&\r\n hasSameFormatting(last, value)\r\n ) {\r\n last.text += value.text;\r\n } else {\r\n array.push(value);\r\n }\r\n }\r\n}\r\n\r\nfunction isVerseText(value: unknown): value is Text {\r\n return typeof value === 'object' && value !== null && 'text' in value;\r\n}\r\n\r\nfunction hasSameFormatting(a: Text, b: Text): boolean {\r\n return a.poem === b.poem && a.wordsOfJesus === b.wordsOfJesus;\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": ["NodeType"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../generation/api.ts"],
4
- "sourcesContent": ["import {\n OutputFile,\n Translation,\n TranslationBook,\n TranslationBookChapter,\n TranslationBookChapterAudioLinks,\n} from './common-types.js';\nimport { DatasetOutput } from './dataset.js';\n\n/**\n * Defines the output of the API generation.\n */\nexport interface ApiOutput {\n /**\n * The list of available translations.\n * This maps to the /api/available-translations.json endpoint.\n */\n availableTranslations: ApiAvailableTranslations;\n\n /**\n * The list of books for each translation.\n * This maps to the /api/:translationId/books.json endpoint.\n */\n translationBooks: ApiTranslationBooks[];\n\n /**\n * The list of chapters for each book.\n * This maps to the following endpoints:\n * - /api/:translationId/:bookId/:chapterNumber.json\n * - /api/:translationId/:bookCommonName/:chapterNumber.json\n */\n translationBookChapters: ApiTranslationBookChapter[];\n\n /**\n * The list of audio files.\n * This maps to the following endpoints:\n * - /api/:translationId/:bookId/:chapterNumber.:reader.mp3\n */\n translationBookChapterAudio: ApiTranslationBookChapterAudio[];\n\n /**\n * The path prefix that the API should use.\n */\n pathPrefix: string;\n}\n\n/**\n * The list of available translations.\n * Maps to the /api/available-translations.json endpoint.\n */\nexport interface ApiAvailableTranslations {\n /**\n * The list of translations.\n */\n translations: ApiTranslation[];\n}\n\n/**\n * Defines a translation that is used in the API.\n */\nexport interface ApiTranslation extends Translation {\n /**\n * The API link for the list of available books for this translation.\n */\n listOfBooksApiLink: string;\n\n /**\n * The available list of formats.\n */\n availableFormats: ('json' | 'usfm')[];\n\n /**\n * The number of books that are contained in this translation.\n *\n * Complete translations should have the same number of books as the Bible (66).\n */\n numberOfBooks: number;\n\n /**\n * The total number of chapters that are contained in this translation.\n *\n * Complete translations should have the same number of chapters as the Bible (1,189).\n */\n totalNumberOfChapters: number;\n\n /**\n * The total number of verses that are contained in this translation.\n *\n * Complete translations should have the same number of verses as the Bible (around 31,102 - some translations exclude verses based on the aparent likelyhood of existing in the original source texts).\n */\n totalNumberOfVerses: number;\n\n /**\n * Gets the name of the language that the translation is in.\n * Null or undefined if the name of the language is not known.\n */\n languageName?: string;\n\n /**\n * Gets the name of the language in English.\n * Null or undefined if the language doesn't have an english name.\n */\n languageEnglishName?: string;\n}\n\n/**\n * Defines an interface that contains information about the books that are available for a translation.\n */\nexport interface ApiTranslationBooks {\n /**\n * The translation information for the books.\n */\n translation: ApiTranslation;\n\n /**\n * The list of books that are available for the translation.\n */\n books: ApiTranslationBook[];\n}\n\n/**\n * Defines a translation book that is used in the API.\n */\nexport interface ApiTranslationBook extends TranslationBook {\n /**\n * The link to the first chapter of the book.\n */\n firstChapterApiLink: string;\n\n /**\n * The link to the last chapter of the book.\n */\n lastChapterApiLink: string;\n\n /**\n * The number of chapters that the book contains.\n */\n numberOfChapters: number;\n\n /**\n * The number of verses that the book contains.\n */\n totalNumberOfVerses: number;\n}\n\n/**\n * Defines an interface that contains information about a book chapter.\n */\nexport interface ApiTranslationBookChapter extends TranslationBookChapter {\n /**\n * The translation information for the book chapter.\n */\n translation: ApiTranslation;\n\n /**\n * The book information for the book chapter.\n */\n book: ApiTranslationBook;\n\n /**\n * The link to this chapter.\n */\n thisChapterLink: string;\n\n /**\n * The link to the next chapter.\n * Null if this is the last chapter in the translation.\n */\n nextChapterApiLink: string | null;\n\n /**\n * The links to the audio versions for the next chapter.\n * Null if this is the last chapter in the translation.\n */\n nextChapterAudioLinks: TranslationBookChapterAudioLinks | null;\n\n /**\n * The link to the previous chapter.\n * Null if this is the first chapter in the translation.\n */\n previousChapterApiLink: string | null;\n\n /**\n * The links to the audio versions for the previous chapter.\n * Null if this is the first chapter in the translation.\n */\n previousChapterAudioLinks: TranslationBookChapterAudioLinks | null;\n\n /**\n * The number of verses that the chapter contains.\n */\n numberOfVerses: number;\n}\n\nexport interface ApiTranslationBookChapterAudio {\n /**\n * The chapter that the audio is for.\n */\n chapter: ApiTranslationBookChapter;\n\n /**\n * The link that the audio should be placed at.\n */\n link: string;\n\n /**\n * The original URL of the audio.\n */\n originalUrl: string;\n}\n\n/**\n * The options for generating the API.\n */\nexport interface GenerateApiOptions {\n /**\n * Whether to use the common name for the book chapter API link. If false, then book IDs are used.\n * Audio URLs will always use the book ID.\n * Defaults to false.\n */\n useCommonName?: boolean;\n\n /**\n * Whether to replace the audio URLs in the dataset with ones that are hosted locally.\n * If true, then the audio URLs in the dataset will be replaced with ones that reference files hosted by the API itself.\n * If false, then the audio URLs in the dataset will be left as is.\n * Defaults to false.\n */\n generateAudioFiles?: boolean;\n\n /**\n * Gets the english name of the given language.\n * If not provided, then the english name for the language will be unknown and omitted.\n * @param language The language to get the english name for.\n */\n getEnglishName?: (language: string) => string | null | undefined;\n\n /**\n * Gets the native name of the given language.\n * If not provided, then the native name for the language will be unknown and omitted.\n * @param language The language to get the native name for.\n */\n getNativeName?: (language: string) => string | null | undefined;\n\n /**\n * The prefix that should be added to paths that are generated.\n */\n pathPrefix?: string;\n}\n\n/**\n * Generates the API output for the given dataset.\n * @param dataset The dataset to generate the API for.\n * @param options The options for generating the API.\n */\nexport function generateApiForDataset(\n dataset: DatasetOutput,\n options: GenerateApiOptions = {}\n): ApiOutput {\n const { useCommonName, pathPrefix } = options;\n const apiPathPrefix = pathPrefix ? pathPrefix : '';\n let api: ApiOutput = {\n availableTranslations: {\n translations: [],\n },\n translationBooks: [],\n translationBookChapters: [],\n translationBookChapterAudio: [],\n pathPrefix: apiPathPrefix,\n };\n\n const getNativeName = options.getNativeName;\n const getEnglishName = options.getEnglishName;\n\n for (let { books, ...translation } of dataset.translations) {\n const apiTranslation: ApiTranslation = {\n ...translation,\n availableFormats: ['json'],\n listOfBooksApiLink: listOfBooksApiLink(\n translation.id,\n apiPathPrefix\n ),\n numberOfBooks: books.length,\n totalNumberOfChapters: 0,\n totalNumberOfVerses: 0,\n languageName: getNativeName\n ? getNativeName(translation.language) ?? undefined\n : undefined,\n languageEnglishName: getEnglishName\n ? getEnglishName(translation.language) ?? undefined\n : undefined,\n };\n\n const translationBooks: ApiTranslationBooks = {\n translation: apiTranslation,\n books: [],\n };\n\n let translationChapters: ApiTranslationBookChapter[] = [];\n\n for (let { chapters, ...book } of books) {\n const apiBook: ApiTranslationBook = {\n ...book,\n firstChapterApiLink: bookChapterApiLink(\n translation.id,\n getBookLink(book),\n 1,\n 'json',\n apiPathPrefix\n ),\n lastChapterApiLink: bookChapterApiLink(\n translation.id,\n getBookLink(book),\n chapters.length,\n 'json',\n apiPathPrefix\n ),\n numberOfChapters: chapters.length,\n totalNumberOfVerses: 0,\n };\n\n for (let { chapter, thisChapterAudioLinks } of chapters) {\n const audio: TranslationBookChapterAudioLinks = {};\n const apiBookChapter: ApiTranslationBookChapter = {\n translation: apiTranslation,\n book: apiBook,\n chapter: chapter,\n thisChapterLink: bookChapterApiLink(\n translation.id,\n getBookLink(book),\n chapter.number,\n 'json',\n apiPathPrefix\n ),\n thisChapterAudioLinks: audio,\n nextChapterApiLink: null,\n nextChapterAudioLinks: null,\n previousChapterApiLink: null,\n previousChapterAudioLinks: null,\n numberOfVerses: 0,\n };\n\n for (let reader in thisChapterAudioLinks) {\n if (options.generateAudioFiles) {\n const apiAudio: ApiTranslationBookChapterAudio = {\n chapter: apiBookChapter,\n link: bookChapterAudioApiLink(\n translation.id,\n getBookLink(book),\n chapter.number,\n reader,\n apiPathPrefix\n ),\n originalUrl: thisChapterAudioLinks[reader],\n };\n audio[reader] = apiAudio.link;\n api.translationBookChapterAudio.push(apiAudio);\n } else {\n audio[reader] = thisChapterAudioLinks[reader];\n }\n }\n\n for (let c of chapter.content) {\n if (c.type === 'verse') {\n apiBookChapter.numberOfVerses++;\n }\n }\n\n apiBook.totalNumberOfVerses += apiBookChapter.numberOfVerses;\n\n translationChapters.push(apiBookChapter);\n api.translationBookChapters.push(apiBookChapter);\n }\n\n translationBooks.books.push(apiBook);\n\n apiTranslation.totalNumberOfChapters += apiBook.numberOfChapters;\n apiTranslation.totalNumberOfVerses += apiBook.totalNumberOfVerses;\n }\n\n for (let i = 0; i < translationChapters.length; i++) {\n if (i > 0) {\n translationChapters[i].previousChapterApiLink =\n bookChapterApiLink(\n translation.id,\n getBookLink(translationChapters[i - 1].book),\n translationChapters[i - 1].chapter.number,\n 'json',\n apiPathPrefix\n );\n translationChapters[i].previousChapterAudioLinks =\n translationChapters[i - 1].thisChapterAudioLinks;\n }\n\n if (i < translationChapters.length - 1) {\n translationChapters[i].nextChapterApiLink = bookChapterApiLink(\n translation.id,\n getBookLink(translationChapters[i + 1].book),\n translationChapters[i + 1].chapter.number,\n 'json',\n apiPathPrefix\n );\n translationChapters[i].nextChapterAudioLinks =\n translationChapters[i + 1].thisChapterAudioLinks;\n }\n }\n\n api.availableTranslations.translations.push(apiTranslation);\n api.translationBooks.push(translationBooks);\n }\n\n return api;\n\n function getBookLink(book: TranslationBook): string {\n return useCommonName ? book.commonName : book.id;\n }\n}\n\n/**\n * Generates the output files for the given API.\n * @param api The API that the files should be generated for.\n */\nexport function generateFilesForApi(api: ApiOutput): OutputFile[] {\n let files: OutputFile[] = [];\n\n files.push(\n jsonFile(\n `${api.pathPrefix}/api/available_translations.json`,\n api.availableTranslations,\n true\n )\n );\n for (let translationBooks of api.translationBooks) {\n files.push(\n jsonFile(\n translationBooks.translation.listOfBooksApiLink,\n translationBooks\n )\n );\n }\n\n for (let bookChapter of api.translationBookChapters) {\n files.push(jsonFile(bookChapter.thisChapterLink, bookChapter));\n }\n\n for (let audio of api.translationBookChapterAudio) {\n files.push(downloadedFile(audio.link, audio.originalUrl));\n }\n\n return files;\n}\n\n/**\n * Generates the output files for the given datasets.\n * @param datasets The datasets to generate the output files for.\n * @param options The options for generating the API files.\n */\nexport async function* generateOutputFilesFromDatasets(\n datasets: AsyncIterable<DatasetOutput>,\n options?: GenerateApiOptions\n): AsyncGenerator<OutputFile[]> {\n for await (let dataset of datasets) {\n const api = generateApiForDataset(dataset, options);\n const files = generateFilesForApi(api);\n\n yield files;\n }\n}\n\n/**\n * Gets the API Link for the list of books endpoint for a translation.\n * @param translationId The ID of the translation.\n * @returns\n */\nexport function listOfBooksApiLink(\n translationId: string,\n prefix: string = ''\n): string {\n return `${prefix}/api/${translationId}/books.json`;\n}\n\n/**\n * Getes the API link for a book chapter.\n * @param translationId The ID of the translation.\n * @param commonName The name of the book.\n * @param chapterNumber The number of the book.\n * @param extension The extension of the file.\n */\nexport function bookChapterApiLink(\n translationId: string,\n commonName: string,\n chapterNumber: number,\n extension: string,\n prefix: string = ''\n) {\n return `${prefix}/api/${translationId}/${replaceSpacesWithUnderscores(\n commonName\n )}/${chapterNumber}.${extension}`;\n}\n\nexport function bookChapterAudioApiLink(\n translationId: string,\n bookId: string,\n chapterNumber: number,\n reader: string,\n prefix: string = ''\n) {\n return `${prefix}/api/${translationId}/${replaceSpacesWithUnderscores(\n bookId\n )}/${chapterNumber}.${reader}.mp3`;\n}\n\nexport function jsonFile(\n path: string,\n content: any,\n mergable?: boolean\n): OutputFile {\n return {\n path,\n content,\n mergable,\n };\n}\n\nexport function downloadedFile(path: string, url: string): OutputFile {\n return {\n path,\n content: () => fetch(url).then((response) => response.body),\n };\n}\n\nexport function replaceSpacesWithUnderscores(str: string): string {\n return str.replace(/[<>:\"/\\\\|?*\\s]/g, '_');\n}\n"],
4
+ "sourcesContent": ["import {\r\n OutputFile,\r\n Translation,\r\n TranslationBook,\r\n TranslationBookChapter,\r\n TranslationBookChapterAudioLinks,\r\n} from './common-types.js';\r\nimport { DatasetOutput } from './dataset.js';\r\n\r\n/**\r\n * Defines the output of the API generation.\r\n */\r\nexport interface ApiOutput {\r\n /**\r\n * The list of available translations.\r\n * This maps to the /api/available-translations.json endpoint.\r\n */\r\n availableTranslations: ApiAvailableTranslations;\r\n\r\n /**\r\n * The list of books for each translation.\r\n * This maps to the /api/:translationId/books.json endpoint.\r\n */\r\n translationBooks: ApiTranslationBooks[];\r\n\r\n /**\r\n * The list of chapters for each book.\r\n * This maps to the following endpoints:\r\n * - /api/:translationId/:bookId/:chapterNumber.json\r\n * - /api/:translationId/:bookCommonName/:chapterNumber.json\r\n */\r\n translationBookChapters: ApiTranslationBookChapter[];\r\n\r\n /**\r\n * The list of audio files.\r\n * This maps to the following endpoints:\r\n * - /api/:translationId/:bookId/:chapterNumber.:reader.mp3\r\n */\r\n translationBookChapterAudio: ApiTranslationBookChapterAudio[];\r\n\r\n /**\r\n * The path prefix that the API should use.\r\n */\r\n pathPrefix: string;\r\n}\r\n\r\n/**\r\n * The list of available translations.\r\n * Maps to the /api/available-translations.json endpoint.\r\n */\r\nexport interface ApiAvailableTranslations {\r\n /**\r\n * The list of translations.\r\n */\r\n translations: ApiTranslation[];\r\n}\r\n\r\n/**\r\n * Defines a translation that is used in the API.\r\n */\r\nexport interface ApiTranslation extends Translation {\r\n /**\r\n * The API link for the list of available books for this translation.\r\n */\r\n listOfBooksApiLink: string;\r\n\r\n /**\r\n * The available list of formats.\r\n */\r\n availableFormats: ('json' | 'usfm')[];\r\n\r\n /**\r\n * The number of books that are contained in this translation.\r\n *\r\n * Complete translations should have the same number of books as the Bible (66).\r\n */\r\n numberOfBooks: number;\r\n\r\n /**\r\n * The total number of chapters that are contained in this translation.\r\n *\r\n * Complete translations should have the same number of chapters as the Bible (1,189).\r\n */\r\n totalNumberOfChapters: number;\r\n\r\n /**\r\n * The total number of verses that are contained in this translation.\r\n *\r\n * Complete translations should have the same number of verses as the Bible (around 31,102 - some translations exclude verses based on the aparent likelyhood of existing in the original source texts).\r\n */\r\n totalNumberOfVerses: number;\r\n\r\n /**\r\n * Gets the name of the language that the translation is in.\r\n * Null or undefined if the name of the language is not known.\r\n */\r\n languageName?: string;\r\n\r\n /**\r\n * Gets the name of the language in English.\r\n * Null or undefined if the language doesn't have an english name.\r\n */\r\n languageEnglishName?: string;\r\n}\r\n\r\n/**\r\n * Defines an interface that contains information about the books that are available for a translation.\r\n */\r\nexport interface ApiTranslationBooks {\r\n /**\r\n * The translation information for the books.\r\n */\r\n translation: ApiTranslation;\r\n\r\n /**\r\n * The list of books that are available for the translation.\r\n */\r\n books: ApiTranslationBook[];\r\n}\r\n\r\n/**\r\n * Defines a translation book that is used in the API.\r\n */\r\nexport interface ApiTranslationBook extends TranslationBook {\r\n /**\r\n * The link to the first chapter of the book.\r\n */\r\n firstChapterApiLink: string;\r\n\r\n /**\r\n * The link to the last chapter of the book.\r\n */\r\n lastChapterApiLink: string;\r\n\r\n /**\r\n * The number of chapters that the book contains.\r\n */\r\n numberOfChapters: number;\r\n\r\n /**\r\n * The number of verses that the book contains.\r\n */\r\n totalNumberOfVerses: number;\r\n}\r\n\r\n/**\r\n * Defines an interface that contains information about a book chapter.\r\n */\r\nexport interface ApiTranslationBookChapter extends TranslationBookChapter {\r\n /**\r\n * The translation information for the book chapter.\r\n */\r\n translation: ApiTranslation;\r\n\r\n /**\r\n * The book information for the book chapter.\r\n */\r\n book: ApiTranslationBook;\r\n\r\n /**\r\n * The link to this chapter.\r\n */\r\n thisChapterLink: string;\r\n\r\n /**\r\n * The link to the next chapter.\r\n * Null if this is the last chapter in the translation.\r\n */\r\n nextChapterApiLink: string | null;\r\n\r\n /**\r\n * The links to the audio versions for the next chapter.\r\n * Null if this is the last chapter in the translation.\r\n */\r\n nextChapterAudioLinks: TranslationBookChapterAudioLinks | null;\r\n\r\n /**\r\n * The link to the previous chapter.\r\n * Null if this is the first chapter in the translation.\r\n */\r\n previousChapterApiLink: string | null;\r\n\r\n /**\r\n * The links to the audio versions for the previous chapter.\r\n * Null if this is the first chapter in the translation.\r\n */\r\n previousChapterAudioLinks: TranslationBookChapterAudioLinks | null;\r\n\r\n /**\r\n * The number of verses that the chapter contains.\r\n */\r\n numberOfVerses: number;\r\n}\r\n\r\nexport interface ApiTranslationBookChapterAudio {\r\n /**\r\n * The chapter that the audio is for.\r\n */\r\n chapter: ApiTranslationBookChapter;\r\n\r\n /**\r\n * The link that the audio should be placed at.\r\n */\r\n link: string;\r\n\r\n /**\r\n * The original URL of the audio.\r\n */\r\n originalUrl: string;\r\n}\r\n\r\n/**\r\n * The options for generating the API.\r\n */\r\nexport interface GenerateApiOptions {\r\n /**\r\n * Whether to use the common name for the book chapter API link. If false, then book IDs are used.\r\n * Audio URLs will always use the book ID.\r\n * Defaults to false.\r\n */\r\n useCommonName?: boolean;\r\n\r\n /**\r\n * Whether to replace the audio URLs in the dataset with ones that are hosted locally.\r\n * If true, then the audio URLs in the dataset will be replaced with ones that reference files hosted by the API itself.\r\n * If false, then the audio URLs in the dataset will be left as is.\r\n * Defaults to false.\r\n */\r\n generateAudioFiles?: boolean;\r\n\r\n /**\r\n * Gets the english name of the given language.\r\n * If not provided, then the english name for the language will be unknown and omitted.\r\n * @param language The language to get the english name for.\r\n */\r\n getEnglishName?: (language: string) => string | null | undefined;\r\n\r\n /**\r\n * Gets the native name of the given language.\r\n * If not provided, then the native name for the language will be unknown and omitted.\r\n * @param language The language to get the native name for.\r\n */\r\n getNativeName?: (language: string) => string | null | undefined;\r\n\r\n /**\r\n * The prefix that should be added to paths that are generated.\r\n */\r\n pathPrefix?: string;\r\n}\r\n\r\n/**\r\n * Generates the API output for the given dataset.\r\n * @param dataset The dataset to generate the API for.\r\n * @param options The options for generating the API.\r\n */\r\nexport function generateApiForDataset(\r\n dataset: DatasetOutput,\r\n options: GenerateApiOptions = {}\r\n): ApiOutput {\r\n const { useCommonName, pathPrefix } = options;\r\n const apiPathPrefix = pathPrefix ? pathPrefix : '';\r\n let api: ApiOutput = {\r\n availableTranslations: {\r\n translations: [],\r\n },\r\n translationBooks: [],\r\n translationBookChapters: [],\r\n translationBookChapterAudio: [],\r\n pathPrefix: apiPathPrefix,\r\n };\r\n\r\n const getNativeName = options.getNativeName;\r\n const getEnglishName = options.getEnglishName;\r\n\r\n for (let { books, ...translation } of dataset.translations) {\r\n const apiTranslation: ApiTranslation = {\r\n ...translation,\r\n availableFormats: ['json'],\r\n listOfBooksApiLink: listOfBooksApiLink(\r\n translation.id,\r\n apiPathPrefix\r\n ),\r\n numberOfBooks: books.length,\r\n totalNumberOfChapters: 0,\r\n totalNumberOfVerses: 0,\r\n languageName: getNativeName\r\n ? getNativeName(translation.language) ?? undefined\r\n : undefined,\r\n languageEnglishName: getEnglishName\r\n ? getEnglishName(translation.language) ?? undefined\r\n : undefined,\r\n };\r\n\r\n const translationBooks: ApiTranslationBooks = {\r\n translation: apiTranslation,\r\n books: [],\r\n };\r\n\r\n let translationChapters: ApiTranslationBookChapter[] = [];\r\n\r\n for (let { chapters, ...book } of books) {\r\n const apiBook: ApiTranslationBook = {\r\n ...book,\r\n firstChapterApiLink: bookChapterApiLink(\r\n translation.id,\r\n getBookLink(book),\r\n 1,\r\n 'json',\r\n apiPathPrefix\r\n ),\r\n lastChapterApiLink: bookChapterApiLink(\r\n translation.id,\r\n getBookLink(book),\r\n chapters.length,\r\n 'json',\r\n apiPathPrefix\r\n ),\r\n numberOfChapters: chapters.length,\r\n totalNumberOfVerses: 0,\r\n };\r\n\r\n for (let { chapter, thisChapterAudioLinks } of chapters) {\r\n const audio: TranslationBookChapterAudioLinks = {};\r\n const apiBookChapter: ApiTranslationBookChapter = {\r\n translation: apiTranslation,\r\n book: apiBook,\r\n chapter: chapter,\r\n thisChapterLink: bookChapterApiLink(\r\n translation.id,\r\n getBookLink(book),\r\n chapter.number,\r\n 'json',\r\n apiPathPrefix\r\n ),\r\n thisChapterAudioLinks: audio,\r\n nextChapterApiLink: null,\r\n nextChapterAudioLinks: null,\r\n previousChapterApiLink: null,\r\n previousChapterAudioLinks: null,\r\n numberOfVerses: 0,\r\n };\r\n\r\n for (let reader in thisChapterAudioLinks) {\r\n if (options.generateAudioFiles) {\r\n const apiAudio: ApiTranslationBookChapterAudio = {\r\n chapter: apiBookChapter,\r\n link: bookChapterAudioApiLink(\r\n translation.id,\r\n getBookLink(book),\r\n chapter.number,\r\n reader,\r\n apiPathPrefix\r\n ),\r\n originalUrl: thisChapterAudioLinks[reader],\r\n };\r\n audio[reader] = apiAudio.link;\r\n api.translationBookChapterAudio.push(apiAudio);\r\n } else {\r\n audio[reader] = thisChapterAudioLinks[reader];\r\n }\r\n }\r\n\r\n for (let c of chapter.content) {\r\n if (c.type === 'verse') {\r\n apiBookChapter.numberOfVerses++;\r\n }\r\n }\r\n\r\n apiBook.totalNumberOfVerses += apiBookChapter.numberOfVerses;\r\n\r\n translationChapters.push(apiBookChapter);\r\n api.translationBookChapters.push(apiBookChapter);\r\n }\r\n\r\n translationBooks.books.push(apiBook);\r\n\r\n apiTranslation.totalNumberOfChapters += apiBook.numberOfChapters;\r\n apiTranslation.totalNumberOfVerses += apiBook.totalNumberOfVerses;\r\n }\r\n\r\n for (let i = 0; i < translationChapters.length; i++) {\r\n if (i > 0) {\r\n translationChapters[i].previousChapterApiLink =\r\n bookChapterApiLink(\r\n translation.id,\r\n getBookLink(translationChapters[i - 1].book),\r\n translationChapters[i - 1].chapter.number,\r\n 'json',\r\n apiPathPrefix\r\n );\r\n translationChapters[i].previousChapterAudioLinks =\r\n translationChapters[i - 1].thisChapterAudioLinks;\r\n }\r\n\r\n if (i < translationChapters.length - 1) {\r\n translationChapters[i].nextChapterApiLink = bookChapterApiLink(\r\n translation.id,\r\n getBookLink(translationChapters[i + 1].book),\r\n translationChapters[i + 1].chapter.number,\r\n 'json',\r\n apiPathPrefix\r\n );\r\n translationChapters[i].nextChapterAudioLinks =\r\n translationChapters[i + 1].thisChapterAudioLinks;\r\n }\r\n }\r\n\r\n api.availableTranslations.translations.push(apiTranslation);\r\n api.translationBooks.push(translationBooks);\r\n }\r\n\r\n return api;\r\n\r\n function getBookLink(book: TranslationBook): string {\r\n return useCommonName ? book.commonName : book.id;\r\n }\r\n}\r\n\r\n/**\r\n * Generates the output files for the given API.\r\n * @param api The API that the files should be generated for.\r\n */\r\nexport function generateFilesForApi(api: ApiOutput): OutputFile[] {\r\n let files: OutputFile[] = [];\r\n\r\n files.push(\r\n jsonFile(\r\n `${api.pathPrefix}/api/available_translations.json`,\r\n api.availableTranslations,\r\n true\r\n )\r\n );\r\n for (let translationBooks of api.translationBooks) {\r\n files.push(\r\n jsonFile(\r\n translationBooks.translation.listOfBooksApiLink,\r\n translationBooks\r\n )\r\n );\r\n }\r\n\r\n for (let bookChapter of api.translationBookChapters) {\r\n files.push(jsonFile(bookChapter.thisChapterLink, bookChapter));\r\n }\r\n\r\n for (let audio of api.translationBookChapterAudio) {\r\n files.push(downloadedFile(audio.link, audio.originalUrl));\r\n }\r\n\r\n return files;\r\n}\r\n\r\n/**\r\n * Generates the output files for the given datasets.\r\n * @param datasets The datasets to generate the output files for.\r\n * @param options The options for generating the API files.\r\n */\r\nexport async function* generateOutputFilesFromDatasets(\r\n datasets: AsyncIterable<DatasetOutput>,\r\n options?: GenerateApiOptions\r\n): AsyncGenerator<OutputFile[]> {\r\n for await (let dataset of datasets) {\r\n const api = generateApiForDataset(dataset, options);\r\n const files = generateFilesForApi(api);\r\n\r\n yield files;\r\n }\r\n}\r\n\r\n/**\r\n * Gets the API Link for the list of books endpoint for a translation.\r\n * @param translationId The ID of the translation.\r\n * @returns\r\n */\r\nexport function listOfBooksApiLink(\r\n translationId: string,\r\n prefix: string = ''\r\n): string {\r\n return `${prefix}/api/${translationId}/books.json`;\r\n}\r\n\r\n/**\r\n * Getes the API link for a book chapter.\r\n * @param translationId The ID of the translation.\r\n * @param commonName The name of the book.\r\n * @param chapterNumber The number of the book.\r\n * @param extension The extension of the file.\r\n */\r\nexport function bookChapterApiLink(\r\n translationId: string,\r\n commonName: string,\r\n chapterNumber: number,\r\n extension: string,\r\n prefix: string = ''\r\n) {\r\n return `${prefix}/api/${translationId}/${replaceSpacesWithUnderscores(\r\n commonName\r\n )}/${chapterNumber}.${extension}`;\r\n}\r\n\r\nexport function bookChapterAudioApiLink(\r\n translationId: string,\r\n bookId: string,\r\n chapterNumber: number,\r\n reader: string,\r\n prefix: string = ''\r\n) {\r\n return `${prefix}/api/${translationId}/${replaceSpacesWithUnderscores(\r\n bookId\r\n )}/${chapterNumber}.${reader}.mp3`;\r\n}\r\n\r\nexport function jsonFile(\r\n path: string,\r\n content: any,\r\n mergable?: boolean\r\n): OutputFile {\r\n return {\r\n path,\r\n content,\r\n mergable,\r\n };\r\n}\r\n\r\nexport function downloadedFile(path: string, url: string): OutputFile {\r\n return {\r\n path,\r\n content: () => fetch(url).then((response) => response.body),\r\n };\r\n}\r\n\r\nexport function replaceSpacesWithUnderscores(str: string): string {\r\n return str.replace(/[<>:\"/\\\\|?*\\s]/g, '_');\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../generation/audio.ts"],
4
- "sourcesContent": ["import { padStart } from 'lodash';\nimport { bookOrderMap } from './book-order.js';\nimport { TranslationBookChapterAudioLinks } from './common-types.js';\n\ntype AudioTranslationUrlGenerator = (bookId: string, chapter: number) => string;\n\nconst openBibleUrlGenerator: (\n translation: string,\n reader: string,\n postfix: string | null\n) => AudioTranslationUrlGenerator = (translation, reader, postfix) => {\n return (bookId, chapter) => {\n const bookOrder = padStart(\n bookOrderMap.get(bookId)!.toString(),\n 2,\n '0'\n );\n const chapterStr = padStart(chapter.toString(), 3, '0');\n\n let link = `https://openbible.com/audio/${reader}/${translation}_${bookOrder}_${capitalize(\n bookId\n )}_${chapterStr}`;\n\n if (postfix) {\n link += `_${postfix}`;\n }\n\n link += '.mp3';\n\n return link;\n };\n};\n\n/**\n * A map of translation IDs to a map of reader IDs to the URL generator for the audio file.\n */\nexport const KNOWN_AUDIO_TRANSLATIONS: Map<\n string,\n Map<string, AudioTranslationUrlGenerator>\n> = new Map([\n [\n 'BSB',\n new Map([\n ['gilbert', openBibleUrlGenerator('BSB', 'gilbert', 'G')],\n ['hays', openBibleUrlGenerator('BSB', 'hays', 'H')],\n ['souer', openBibleUrlGenerator('BSB', 'souer', null)],\n ]),\n ],\n]);\n\n/**\n * Gets the audio URLs for the given translation, book, and chapter.\n * @param translationId The ID of the translation.\n * @param bookId The ID of the book.\n * @param chapter The number of the chapter.\n */\nexport function getAudioUrlsForChapter(\n translationId: string,\n bookId: string,\n chapter: number\n): TranslationBookChapterAudioLinks {\n const translation = KNOWN_AUDIO_TRANSLATIONS.get(translationId);\n if (!translation) {\n return {};\n }\n\n const links: TranslationBookChapterAudioLinks = {};\n for (let [reader, generator] of translation) {\n const url = generator(bookId, chapter);\n if (url) {\n links[reader] = url;\n }\n }\n return links;\n}\n\n/**\n * Capitalizes the first letter of the given string.\n * @param str The string to capitalize.\n */\nexport function capitalize(str: string): string {\n const char = str.charAt(0);\n if (/[0-9]/.test(char)) {\n return (\n str.charAt(0) +\n str.charAt(1).toUpperCase() +\n str.slice(2).toLowerCase()\n );\n }\n\n return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();\n}\n"],
4
+ "sourcesContent": ["import { padStart } from 'lodash';\r\nimport { bookOrderMap } from './book-order.js';\r\nimport { TranslationBookChapterAudioLinks } from './common-types.js';\r\n\r\ntype AudioTranslationUrlGenerator = (bookId: string, chapter: number) => string;\r\n\r\nconst openBibleUrlGenerator: (\r\n translation: string,\r\n reader: string,\r\n postfix: string | null\r\n) => AudioTranslationUrlGenerator = (translation, reader, postfix) => {\r\n return (bookId, chapter) => {\r\n const bookOrder = padStart(\r\n bookOrderMap.get(bookId)!.toString(),\r\n 2,\r\n '0'\r\n );\r\n const chapterStr = padStart(chapter.toString(), 3, '0');\r\n\r\n let link = `https://openbible.com/audio/${reader}/${translation}_${bookOrder}_${capitalize(\r\n bookId\r\n )}_${chapterStr}`;\r\n\r\n if (postfix) {\r\n link += `_${postfix}`;\r\n }\r\n\r\n link += '.mp3';\r\n\r\n return link;\r\n };\r\n};\r\n\r\n/**\r\n * A map of translation IDs to a map of reader IDs to the URL generator for the audio file.\r\n */\r\nexport const KNOWN_AUDIO_TRANSLATIONS: Map<\r\n string,\r\n Map<string, AudioTranslationUrlGenerator>\r\n> = new Map([\r\n [\r\n 'BSB',\r\n new Map([\r\n ['gilbert', openBibleUrlGenerator('BSB', 'gilbert', 'G')],\r\n ['hays', openBibleUrlGenerator('BSB', 'hays', 'H')],\r\n ['souer', openBibleUrlGenerator('BSB', 'souer', null)],\r\n ]),\r\n ],\r\n]);\r\n\r\n/**\r\n * Gets the audio URLs for the given translation, book, and chapter.\r\n * @param translationId The ID of the translation.\r\n * @param bookId The ID of the book.\r\n * @param chapter The number of the chapter.\r\n */\r\nexport function getAudioUrlsForChapter(\r\n translationId: string,\r\n bookId: string,\r\n chapter: number\r\n): TranslationBookChapterAudioLinks {\r\n const translation = KNOWN_AUDIO_TRANSLATIONS.get(translationId);\r\n if (!translation) {\r\n return {};\r\n }\r\n\r\n const links: TranslationBookChapterAudioLinks = {};\r\n for (let [reader, generator] of translation) {\r\n const url = generator(bookId, chapter);\r\n if (url) {\r\n links[reader] = url;\r\n }\r\n }\r\n return links;\r\n}\r\n\r\n/**\r\n * Capitalizes the first letter of the given string.\r\n * @param str The string to capitalize.\r\n */\r\nexport function capitalize(str: string): string {\r\n const char = str.charAt(0);\r\n if (/[0-9]/.test(char)) {\r\n return (\r\n str.charAt(0) +\r\n str.charAt(1).toUpperCase() +\r\n str.slice(2).toLowerCase()\r\n );\r\n }\r\n\r\n return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();\r\n}\r\n"],
5
5
  "mappings": ";AAAA,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAK7B,MAAM,wBAI8B,CAAC,aAAa,QAAQ,YAAY;AAClE,SAAO,CAAC,QAAQ,YAAY;AACxB,UAAM,YAAY;AAAA,MACd,aAAa,IAAI,MAAM,EAAG,SAAS;AAAA,MACnC;AAAA,MACA;AAAA,IACJ;AACA,UAAM,aAAa,SAAS,QAAQ,SAAS,GAAG,GAAG,GAAG;AAEtD,QAAI,OAAO,+BAA+B,MAAM,IAAI,WAAW,IAAI,SAAS,IAAI;AAAA,MAC5E;AAAA,IACJ,CAAC,IAAI,UAAU;AAEf,QAAI,SAAS;AACT,cAAQ,IAAI,OAAO;AAAA,IACvB;AAEA,YAAQ;AAER,WAAO;AAAA,EACX;AACJ;AAKO,aAAM,2BAGT,oBAAI,IAAI;AAAA,EACR;AAAA,IACI;AAAA,IACA,oBAAI,IAAI;AAAA,MACJ,CAAC,WAAW,sBAAsB,OAAO,WAAW,GAAG,CAAC;AAAA,MACxD,CAAC,QAAQ,sBAAsB,OAAO,QAAQ,GAAG,CAAC;AAAA,MAClD,CAAC,SAAS,sBAAsB,OAAO,SAAS,IAAI,CAAC;AAAA,IACzD,CAAC;AAAA,EACL;AACJ,CAAC;AAQM,gBAAS,uBACZ,eACA,QACA,SACgC;AAChC,QAAM,cAAc,yBAAyB,IAAI,aAAa;AAC9D,MAAI,CAAC,aAAa;AACd,WAAO,CAAC;AAAA,EACZ;AAEA,QAAM,QAA0C,CAAC;AACjD,WAAS,CAAC,QAAQ,SAAS,KAAK,aAAa;AACzC,UAAM,MAAM,UAAU,QAAQ,OAAO;AACrC,QAAI,KAAK;AACL,YAAM,MAAM,IAAI;AAAA,IACpB;AAAA,EACJ;AACA,SAAO;AACX;AAMO,gBAAS,WAAW,KAAqB;AAC5C,QAAM,OAAO,IAAI,OAAO,CAAC;AACzB,MAAI,QAAQ,KAAK,IAAI,GAAG;AACpB,WACI,IAAI,OAAO,CAAC,IACZ,IAAI,OAAO,CAAC,EAAE,YAAY,IAC1B,IAAI,MAAM,CAAC,EAAE,YAAY;AAAA,EAEjC;AAEA,SAAO,IAAI,OAAO,CAAC,EAAE,YAAY,IAAI,IAAI,MAAM,CAAC,EAAE,YAAY;AAClE;",
6
6
  "names": []
7
7
  }
@@ -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 InputFile,\n Translation,\n TranslationBook,\n TranslationBookChapter,\n} from './common-types.js';\nimport { bookIdMap, bookOrderMap } from './book-order.js';\nimport { omit, sortBy, sortedIndexBy } from 'lodash';\nimport { getAudioUrlsForChapter } from './audio.js';\nimport { CodexParser } from '../parser/codex-parser.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/**\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 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 * Generates a list of output files from the given list of input files.\n * @param file The list of files.\n */\nexport function generateDataset(\n files: InputFile[],\n parser: DOMParser = new globalThis.DOMParser()\n): DatasetOutput {\n let output: DatasetOutput = {\n translations: [],\n };\n\n let usfmParser = new UsfmParser();\n let usxParser = new USXParser(parser);\n let codexParser = new CodexParser();\n\n let parsedTranslations = new Map<string, DatasetTranslation>();\n\n const unknownLanguages = new Set<string>();\n for (let file of files) {\n try {\n let parser: UsfmParser | USXParser | CodexParser;\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 {\n console.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 const id = parsed.id;\n\n if (!id) {\n console.warn(\n '[generate] File does not have a valid book ID!',\n file.name,\n id\n );\n continue;\n }\n\n const order = bookOrderMap.get(id);\n\n if (typeof order !== 'number') {\n console.warn('[generate] Book does not have an order!', id);\n continue;\n }\n\n const bookMap = bookIdMap.get(file.metadata.translation.language);\n\n if (!bookMap) {\n if (!unknownLanguages.has(file.metadata.translation.language)) {\n console.warn(\n '[generate] File does not have a known language!',\n file.name,\n file.metadata.translation.language\n );\n unknownLanguages.add(file.metadata.translation.language);\n }\n }\n\n const bookName = bookMap?.get(id);\n\n if (!!bookMap && !bookName) {\n console.warn(\n '[generate] Book name not found for ID!',\n file.name,\n id\n );\n }\n\n let translation = parsedTranslations.get(\n file.metadata.translation.id\n );\n\n if (!translation) {\n translation = {\n ...omit(file.metadata.translation, 'direction'),\n textDirection: file.metadata.translation.direction,\n books: [],\n };\n output.translations.push(translation);\n parsedTranslations.set(\n file.metadata.translation.id,\n translation\n );\n }\n\n const name = parsed.header ?? bookName?.commonName ?? parsed.title;\n\n if (!name) {\n console.warn(\n '[generate] Book does not have a name!',\n file.name,\n id\n );\n continue;\n }\n\n const commonName =\n bookName?.commonName ?? parsed.header ?? parsed.title ?? 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 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(\n translation.books,\n book,\n (b) => b.order\n );\n translation.books.splice(index, 0, book);\n } catch (err) {\n console.error(\n `[generate] Error occurred while parsing ${file.name}`,\n err\n );\n }\n }\n\n return output;\n}\n"],
4
+ "sourcesContent": ["import { UsfmParser } from '../parser/usfm-parser.js';\r\nimport { USXParser } from '../parser/usx-parser.js';\r\nimport {\r\n InputFile,\r\n Translation,\r\n TranslationBook,\r\n TranslationBookChapter,\r\n} from './common-types.js';\r\nimport { bookIdMap, bookOrderMap } from './book-order.js';\r\nimport { omit, sortBy, sortedIndexBy } from 'lodash';\r\nimport { getAudioUrlsForChapter } from './audio.js';\r\nimport { CodexParser } from '../parser/codex-parser.js';\r\n\r\n/**\r\n * Defines an interface that contains generated dataset info.\r\n */\r\nexport interface DatasetOutput {\r\n /**\r\n * The list of translations that are available in the dataset.\r\n */\r\n translations: DatasetTranslation[];\r\n}\r\n\r\n/**\r\n * Defines a translation that is used in the dataset.\r\n */\r\nexport interface DatasetTranslation extends Translation {\r\n /**\r\n * The list of books that are available for the translation.\r\n */\r\n books: DatasetTranslationBook[];\r\n}\r\n\r\n/**\r\n * Defines a translation book that is used in the dataset.\r\n */\r\nexport interface DatasetTranslationBook extends TranslationBook {\r\n /**\r\n * The list of chapters that are available for the book.\r\n */\r\n chapters: TranslationBookChapter[];\r\n}\r\n\r\n/**\r\n * Generates a list of output files from the given list of input files.\r\n * @param file The list of files.\r\n */\r\nexport function generateDataset(\r\n files: InputFile[],\r\n parser: DOMParser = new globalThis.DOMParser()\r\n): DatasetOutput {\r\n let output: DatasetOutput = {\r\n translations: [],\r\n };\r\n\r\n let usfmParser = new UsfmParser();\r\n let usxParser = new USXParser(parser);\r\n let codexParser = new CodexParser();\r\n\r\n let parsedTranslations = new Map<string, DatasetTranslation>();\r\n\r\n const unknownLanguages = new Set<string>();\r\n for (let file of files) {\r\n try {\r\n let parser: UsfmParser | USXParser | CodexParser;\r\n if (file.fileType === 'usfm') {\r\n parser = usfmParser;\r\n } else if (file.fileType === 'usx') {\r\n parser = usxParser;\r\n } else if (file.fileType === 'json') {\r\n parser = codexParser;\r\n } else {\r\n console.warn(\r\n '[generate] File does not have a valid type!',\r\n file.name\r\n );\r\n continue;\r\n }\r\n\r\n const parsed = parser.parse(file.content);\r\n const id = parsed.id;\r\n\r\n if (!id) {\r\n console.warn(\r\n '[generate] File does not have a valid book ID!',\r\n file.name,\r\n id\r\n );\r\n continue;\r\n }\r\n\r\n const order = bookOrderMap.get(id);\r\n\r\n if (typeof order !== 'number') {\r\n console.warn('[generate] Book does not have an order!', id);\r\n continue;\r\n }\r\n\r\n const bookMap = bookIdMap.get(file.metadata.translation.language);\r\n\r\n if (!bookMap) {\r\n if (!unknownLanguages.has(file.metadata.translation.language)) {\r\n console.warn(\r\n '[generate] File does not have a known language!',\r\n file.name,\r\n file.metadata.translation.language\r\n );\r\n unknownLanguages.add(file.metadata.translation.language);\r\n }\r\n }\r\n\r\n const bookName = bookMap?.get(id);\r\n\r\n if (!!bookMap && !bookName) {\r\n console.warn(\r\n '[generate] Book name not found for ID!',\r\n file.name,\r\n id\r\n );\r\n }\r\n\r\n let translation = parsedTranslations.get(\r\n file.metadata.translation.id\r\n );\r\n\r\n if (!translation) {\r\n translation = {\r\n ...omit(file.metadata.translation, 'direction'),\r\n textDirection: file.metadata.translation.direction,\r\n books: [],\r\n };\r\n output.translations.push(translation);\r\n parsedTranslations.set(\r\n file.metadata.translation.id,\r\n translation\r\n );\r\n }\r\n\r\n const name = parsed.header ?? bookName?.commonName ?? parsed.title;\r\n\r\n if (!name) {\r\n console.warn(\r\n '[generate] Book does not have a name!',\r\n file.name,\r\n id\r\n );\r\n continue;\r\n }\r\n\r\n const commonName =\r\n bookName?.commonName ?? parsed.header ?? parsed.title ?? id;\r\n\r\n const book: DatasetTranslationBook = {\r\n id: id,\r\n name,\r\n commonName,\r\n title: parsed.title ?? null,\r\n order,\r\n chapters: [],\r\n };\r\n\r\n for (let content of parsed.content) {\r\n if (content.type === 'chapter') {\r\n book.chapters.push({\r\n chapter: {\r\n number: content.number,\r\n content: content.content,\r\n footnotes: content.footnotes,\r\n },\r\n thisChapterAudioLinks: getAudioUrlsForChapter(\r\n translation.id,\r\n id,\r\n content.number\r\n ),\r\n });\r\n }\r\n }\r\n\r\n book.chapters = sortBy(book.chapters, (c) => c.chapter.number);\r\n\r\n const index = sortedIndexBy(\r\n translation.books,\r\n book,\r\n (b) => b.order\r\n );\r\n translation.books.splice(index, 0, book);\r\n } catch (err) {\r\n console.error(\r\n `[generate] Error occurred while parsing ${file.name}`,\r\n err\r\n );\r\n }\r\n }\r\n\r\n return output;\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": ["parser"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../generation/index.ts"],
4
- "sourcesContent": ["import * as api from './api.js';\nimport * as audio from './audio.js';\nimport * as dataset from './dataset.js';\nimport * as bookOrder from './book-order.js';\nexport * from './common-types.js';\n\nexport { api, audio, dataset, bookOrder };\n"],
4
+ "sourcesContent": ["import * as api from './api.js';\r\nimport * as audio from './audio.js';\r\nimport * as dataset from './dataset.js';\r\nimport * as bookOrder from './book-order.js';\r\nexport * from './common-types.js';\r\n\r\nexport { api, audio, dataset, bookOrder };\r\n"],
5
5
  "mappings": ";AAAA,YAAY,SAAS;AACrB,YAAY,WAAW;AACvB,YAAY,aAAa;AACzB,YAAY,eAAe;AAC3B,cAAc;AAEd,SAAS,KAAK,OAAO,SAAS;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../index.ts"],
4
- "sourcesContent": ["import * as utils from './utils.js';\nimport * as generation from './generation/index.js';\nimport * as parser from './parser/index.js';\n\nexport { utils, generation, parser };\n"],
4
+ "sourcesContent": ["import * as utils from './utils.js';\r\nimport * as generation from './generation/index.js';\r\nimport * as parser from './parser/index.js';\r\n\r\nexport { utils, generation, parser };\r\n"],
5
5
  "mappings": ";AAAA,YAAY,WAAW;AACvB,YAAY,gBAAgB;AAC5B,YAAY,YAAY;AAExB,SAAS,OAAO,YAAY;",
6
6
  "names": []
7
7
  }
@@ -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 { parseVerseReference, VerseRef } from '../utils.js';\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 metadataSchema = z.discriminatedUnion('type', [\n chapterHeadingSchema,\n]);\n\nexport const codexSchema = z.object({\n cells: z.array(\n z.object({\n // kind: z.number().int(),\n language: 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 USX content.\n *\n * @param usx The USX 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 for (let cell of data.cells) {\n if (cell.language === 'scripture') {\n const lines = cell.value.split('\\n');\n const references = lines.map(\n (l) => parseVerseReference(l) ?? l\n );\n let currentRef: VerseRef | null = null;\n let content: Verse['content'] = [];\n for (let i = 0; i < references.length; i++) {\n const ref = references[i];\n const nextRef = references[i + 1];\n\n if (typeof ref === 'string') {\n // continuation on existing verse\n // or line break between verses\n\n if (ref === '') {\n // line break\n\n if (typeof nextRef === 'object') {\n // between verses\n\n if (\n currentRef &&\n currentRef.chapter === nextRef.chapter\n ) {\n // in same chapter\n addChapterContent(currentRef.chapter, [\n {\n type: 'line_break',\n },\n ]);\n } else {\n // do nothing since line breaks between chapters are ignored\n }\n } else {\n // inside verse\n content.push({\n lineBreak: true,\n });\n }\n } else if (ref) {\n content.push(ref);\n }\n } else {\n if (!root.id) {\n root.id = ref.book;\n }\n\n // new verse\n if (currentRef) {\n // finish previous verse\n currentRef = null;\n content = [];\n }\n\n currentRef = ref;\n\n if (ref.content) {\n content.push(ref.content);\n }\n\n addReference(ref, content);\n }\n }\n } else if (cell.language === 'markdown') {\n if (cell.metadata) {\n if (cell.metadata.type === 'chapter-heading') {\n const metadata = metadataSchema.parse(cell.metadata);\n\n const chapter = metadata.data.chapter;\n addChapterContent(chapter, [\n {\n type: 'heading',\n content: [cell.value],\n },\n ]);\n }\n } else {\n if (lastChapter) {\n (lastChapter as Chapter).footnotes.push({\n noteId: this._noteCounter++,\n text: cell.value,\n caller: null,\n });\n }\n }\n }\n }\n\n for (let chapter of chapters.values()) {\n root.content.push(chapter);\n }\n\n return root;\n }\n}\n"],
4
+ "sourcesContent": ["import { z } from 'zod';\r\nimport {\r\n Chapter,\r\n ChapterContent,\r\n ParseTree,\r\n Verse,\r\n VerseContent,\r\n} from './types.js';\r\nimport { parseVerseReference, VerseRef } from '../utils.js';\r\n\r\nexport const chapterHeadingSchema = z.object({\r\n type: z.literal('chapter-heading'),\r\n data: z.object({\r\n chapter: z.coerce.number().int(),\r\n }),\r\n});\r\n\r\nexport const metadataSchema = z.discriminatedUnion('type', [\r\n chapterHeadingSchema,\r\n]);\r\n\r\nexport const codexSchema = z.object({\r\n cells: z.array(\r\n z.object({\r\n // kind: z.number().int(),\r\n language: z.string(),\r\n value: z.string(),\r\n metadata: z\r\n .object({\r\n type: z.string(),\r\n })\r\n .passthrough()\r\n .nullable()\r\n .optional(),\r\n })\r\n ),\r\n});\r\n\r\n/**\r\n * Defines a parser for codex files.\r\n */\r\nexport class CodexParser {\r\n // TODO:\r\n // /**\r\n // * Whether to preserve markdown in the parsed content.\r\n // */\r\n // preserveMarkdown: boolean = true;\r\n\r\n private _noteCounter: number = 0;\r\n\r\n /**\r\n * Parses the specified USX content.\r\n *\r\n * @param usx The USX content to parse.\r\n * @returns The parse tree that was generated.\r\n */\r\n public parse(codex: string): ParseTree {\r\n const json = JSON.parse(codex);\r\n const data = codexSchema.parse(json);\r\n\r\n let root: ParseTree = {\r\n type: 'root',\r\n content: [],\r\n };\r\n\r\n let chapters: Map<number, Chapter> = new Map();\r\n let lastChapter: Chapter | null = null;\r\n\r\n function addChapterContent(\r\n chapterNumber: number,\r\n content: ChapterContent[]\r\n ) {\r\n let chapter = chapters.get(chapterNumber);\r\n if (!chapter) {\r\n chapter = {\r\n type: 'chapter',\r\n number: chapterNumber,\r\n content: [],\r\n footnotes: [],\r\n };\r\n lastChapter = chapter;\r\n chapters.set(chapterNumber, chapter);\r\n }\r\n\r\n chapter.content.push(...content);\r\n }\r\n\r\n function addReference(ref: VerseRef, content: Verse['content']) {\r\n addChapterContent(ref.chapter, [\r\n {\r\n type: 'verse',\r\n number: ref.verse,\r\n content: content,\r\n },\r\n ]);\r\n }\r\n\r\n for (let cell of data.cells) {\r\n if (cell.language === 'scripture') {\r\n const lines = cell.value.split('\\n');\r\n const references = lines.map(\r\n (l) => parseVerseReference(l) ?? l\r\n );\r\n let currentRef: VerseRef | null = null;\r\n let content: Verse['content'] = [];\r\n for (let i = 0; i < references.length; i++) {\r\n const ref = references[i];\r\n const nextRef = references[i + 1];\r\n\r\n if (typeof ref === 'string') {\r\n // continuation on existing verse\r\n // or line break between verses\r\n\r\n if (ref === '') {\r\n // line break\r\n\r\n if (typeof nextRef === 'object') {\r\n // between verses\r\n\r\n if (\r\n currentRef &&\r\n currentRef.chapter === nextRef.chapter\r\n ) {\r\n // in same chapter\r\n addChapterContent(currentRef.chapter, [\r\n {\r\n type: 'line_break',\r\n },\r\n ]);\r\n } else {\r\n // do nothing since line breaks between chapters are ignored\r\n }\r\n } else {\r\n // inside verse\r\n content.push({\r\n lineBreak: true,\r\n });\r\n }\r\n } else if (ref) {\r\n content.push(ref);\r\n }\r\n } else {\r\n if (!root.id) {\r\n root.id = ref.book;\r\n }\r\n\r\n // new verse\r\n if (currentRef) {\r\n // finish previous verse\r\n currentRef = null;\r\n content = [];\r\n }\r\n\r\n currentRef = ref;\r\n\r\n if (ref.content) {\r\n content.push(ref.content);\r\n }\r\n\r\n addReference(ref, content);\r\n }\r\n }\r\n } else if (cell.language === 'markdown') {\r\n if (cell.metadata) {\r\n if (cell.metadata.type === 'chapter-heading') {\r\n const metadata = metadataSchema.parse(cell.metadata);\r\n\r\n const chapter = metadata.data.chapter;\r\n addChapterContent(chapter, [\r\n {\r\n type: 'heading',\r\n content: [cell.value],\r\n },\r\n ]);\r\n }\r\n } else {\r\n if (lastChapter) {\r\n (lastChapter as Chapter).footnotes.push({\r\n noteId: this._noteCounter++,\r\n text: cell.value,\r\n caller: null,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n for (let chapter of chapters.values()) {\r\n root.content.push(chapter);\r\n }\r\n\r\n return root;\r\n }\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../parser/index.ts"],
4
- "sourcesContent": ["export * from './types.js';\nexport * from './usfm-parser.js';\nexport * from './usx-parser.js';\nexport * from './codex-parser.js';\nimport * as iterators from './iterators.js';\n\nexport { iterators };\n"],
4
+ "sourcesContent": ["export * from './types.js';\r\nexport * from './usfm-parser.js';\r\nexport * from './usx-parser.js';\r\nexport * from './codex-parser.js';\r\nimport * as iterators from './iterators.js';\r\n\r\nexport { iterators };\r\n"],
5
5
  "mappings": ";AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,YAAY,eAAe;AAE3B,SAAS;",
6
6
  "names": []
7
7
  }