@helloao/tools 0.2.0 → 0.4.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.
Files changed (81) hide show
  1. package/dist/cjs/generation/api.cjs +2213 -216
  2. package/dist/cjs/generation/api.cjs.map +3 -3
  3. package/dist/cjs/generation/audio.cjs +5 -5
  4. package/dist/cjs/generation/audio.cjs.map +1 -1
  5. package/dist/cjs/generation/book-order.cjs.map +1 -1
  6. package/dist/cjs/generation/common-types.cjs +782 -14
  7. package/dist/cjs/generation/common-types.cjs.map +2 -2
  8. package/dist/cjs/generation/dataset.cjs +13 -9
  9. package/dist/cjs/generation/dataset.cjs.map +2 -2
  10. package/dist/cjs/generation/index.cjs +8 -5
  11. package/dist/cjs/generation/index.cjs.map +2 -2
  12. package/dist/cjs/generation/simple.cjs +294 -0
  13. package/dist/cjs/generation/simple.cjs.map +7 -0
  14. package/dist/cjs/generation/theographic.cjs +446 -0
  15. package/dist/cjs/generation/theographic.cjs.map +7 -0
  16. package/dist/cjs/index.cjs +4 -4
  17. package/dist/cjs/index.cjs.map +1 -1
  18. package/dist/cjs/log.cjs.map +1 -1
  19. package/dist/cjs/parser/codex-parser.cjs +3 -3
  20. package/dist/cjs/parser/codex-parser.cjs.map +3 -3
  21. package/dist/cjs/parser/commentary-csv-parser.cjs +14 -3
  22. package/dist/cjs/parser/commentary-csv-parser.cjs.map +3 -3
  23. package/dist/cjs/parser/index.cjs +1 -1
  24. package/dist/cjs/parser/iterators.cjs.map +1 -1
  25. package/dist/cjs/parser/lockman-parser.cjs +1 -1
  26. package/dist/cjs/parser/lockman-parser.cjs.map +1 -1
  27. package/dist/cjs/parser/tyndale-xml-parser.cjs +1 -1
  28. package/dist/cjs/parser/tyndale-xml-parser.cjs.map +1 -1
  29. package/dist/cjs/parser/types.cjs.map +1 -1
  30. package/dist/cjs/parser/usfm-parser.cjs +2 -2
  31. package/dist/cjs/parser/usfm-parser.cjs.map +1 -1
  32. package/dist/cjs/parser/usx-parser.cjs +122 -26
  33. package/dist/cjs/parser/usx-parser.cjs.map +2 -2
  34. package/dist/cjs/parser/words.cjs +187 -0
  35. package/dist/cjs/parser/words.cjs.map +7 -0
  36. package/dist/cjs/utils.cjs +1 -1
  37. package/dist/esm/generation/api.js +2155 -214
  38. package/dist/esm/generation/api.js.map +3 -3
  39. package/dist/esm/generation/audio.js +1 -1
  40. package/dist/esm/generation/audio.js.map +1 -1
  41. package/dist/esm/generation/book-order.js.map +1 -1
  42. package/dist/esm/generation/common-types.js +738 -14
  43. package/dist/esm/generation/common-types.js.map +2 -2
  44. package/dist/esm/generation/dataset.js +6 -2
  45. package/dist/esm/generation/dataset.js.map +2 -2
  46. package/dist/esm/generation/index.js +2 -1
  47. package/dist/esm/generation/index.js.map +2 -2
  48. package/dist/esm/generation/simple.js +260 -0
  49. package/dist/esm/generation/simple.js.map +7 -0
  50. package/dist/esm/generation/theographic.js +412 -0
  51. package/dist/esm/generation/theographic.js.map +7 -0
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/log.js.map +1 -1
  54. package/dist/esm/parser/codex-parser.js +3 -3
  55. package/dist/esm/parser/codex-parser.js.map +3 -3
  56. package/dist/esm/parser/commentary-csv-parser.js +3 -2
  57. package/dist/esm/parser/commentary-csv-parser.js.map +2 -2
  58. package/dist/esm/parser/iterators.js.map +1 -1
  59. package/dist/esm/parser/lockman-parser.js +1 -1
  60. package/dist/esm/parser/lockman-parser.js.map +1 -1
  61. package/dist/esm/parser/tyndale-xml-parser.js +1 -1
  62. package/dist/esm/parser/tyndale-xml-parser.js.map +1 -1
  63. package/dist/esm/parser/usfm-parser.js +1 -1
  64. package/dist/esm/parser/usfm-parser.js.map +1 -1
  65. package/dist/esm/parser/usx-parser.js +130 -26
  66. package/dist/esm/parser/usx-parser.js.map +2 -2
  67. package/dist/esm/parser/words.js +149 -0
  68. package/dist/esm/parser/words.js.map +7 -0
  69. package/dist/types/generation/api.d.ts +9006 -1101
  70. package/dist/types/generation/common-types.d.ts +1318 -106
  71. package/dist/types/generation/dataset.d.ts +17 -1
  72. package/dist/types/generation/index.d.ts +2 -1
  73. package/dist/types/generation/simple.d.ts +110 -0
  74. package/dist/types/generation/theographic.d.ts +64 -0
  75. package/dist/types/parser/commentary-csv-parser.d.ts +1 -1
  76. package/dist/types/parser/lockman-parser.d.ts +1 -1
  77. package/dist/types/parser/tyndale-xml-parser.d.ts +1 -1
  78. package/dist/types/parser/types.d.ts +63 -0
  79. package/dist/types/parser/usx-parser.d.ts +6 -5
  80. package/dist/types/parser/words.d.ts +104 -0
  81. package/package.json +3 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../parser/commentary-csv-parser.ts"],
4
- "sourcesContent": ["import {\r\n CommentaryBookNode,\r\n CommentaryChapterNode,\r\n CommentaryParseTree,\r\n} from './types';\r\nimport { getBookId, parseVerseReference } from '../utils';\r\nimport { parse } from 'papaparse';\r\n\r\nexport interface CsvLine {\r\n book: string;\r\n chapter: string;\r\n verse: string;\r\n commentaries: string;\r\n}\r\n\r\nexport class CommentaryCsvParser {\r\n parse(data: string): CommentaryParseTree {\r\n // remove header automatically\r\n const firstLine = data.indexOf('\\n');\r\n data = data.substring(firstLine + 1);\r\n\r\n const lines = parse<string[]>(data, {\r\n header: false,\r\n // transformHeader: (_, index) => {\r\n // const headers = ['book', 'chapter', 'verse', 'commentaries'];\r\n // if (index < headers.length) {\r\n // return headers[index];\r\n // } else {\r\n // return '';\r\n // }\r\n // },\r\n delimiter: ',',\r\n escapeChar: '\"',\r\n quoteChar: '\"',\r\n });\r\n return this.parseLines(\r\n lines.data.map((line: string[]) => ({\r\n book: line[0],\r\n chapter: line[1],\r\n verse: line[2],\r\n commentaries: line[3],\r\n }))\r\n );\r\n }\r\n\r\n parseLines(data: CsvLine[]): CommentaryParseTree {\r\n let tree: CommentaryParseTree = {\r\n type: 'commentary/root',\r\n books: [],\r\n };\r\n\r\n let book: CommentaryBookNode | null = null;\r\n let chapter: CommentaryChapterNode | null = null;\r\n for (let line of data) {\r\n if (hasValue(line.book)) {\r\n const id = getBookId(line.book);\r\n if (!id) {\r\n throw new Error('Invalid book: ' + line.book);\r\n }\r\n\r\n book = tree.books.find((b) => b.book === id) ?? null;\r\n if (!book) {\r\n book = {\r\n type: 'book',\r\n book: id,\r\n introduction: null,\r\n chapters: [],\r\n };\r\n tree.books.push(book);\r\n }\r\n\r\n if (!book.introduction) {\r\n book.introduction = hasValue(line.commentaries)\r\n ? line.commentaries\r\n : null;\r\n }\r\n } else if (hasValue(line.chapter)) {\r\n const number = parseInt(line.chapter);\r\n if (isNaN(number)) {\r\n throw new Error('Invalid chapter number: ' + line.chapter);\r\n }\r\n\r\n if (!book) {\r\n throw new Error('Chapter without book');\r\n }\r\n\r\n chapter = {\r\n type: 'chapter',\r\n number,\r\n introduction: hasValue(line.commentaries)\r\n ? line.commentaries\r\n : null,\r\n verses: [],\r\n };\r\n book.chapters.push(chapter);\r\n } else if (hasValue(line.verse)) {\r\n const ref = parseVerseReference(line.verse);\r\n if (ref) {\r\n if (ref.book === book?.book) {\r\n // @ts-ignore\r\n if (ref.chapter === chapter?.number) {\r\n chapter.verses.push({\r\n type: 'verse',\r\n number: ref.verse,\r\n content: [line.commentaries],\r\n });\r\n } else {\r\n chapter = {\r\n type: 'chapter',\r\n number: ref.chapter,\r\n introduction: null,\r\n verses: [\r\n {\r\n type: 'verse',\r\n number: ref.verse,\r\n content: [line.commentaries],\r\n },\r\n ],\r\n };\r\n\r\n book.chapters.push(chapter);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return tree;\r\n }\r\n}\r\n\r\nfunction hasValue(value: string | null): boolean {\r\n return (\r\n value !== null &&\r\n value !== undefined &&\r\n value !== '' &&\r\n value.trim() !== ''\r\n );\r\n}\r\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\r\n CommentaryBookNode,\r\n CommentaryChapterNode,\r\n CommentaryParseTree,\r\n} from './types.js';\r\nimport { getBookId, parseVerseReference } from '../utils.js';\r\nimport papaparse from 'papaparse';\r\n\r\nconst { parse } = papaparse;\r\n\r\nexport interface CsvLine {\r\n book: string;\r\n chapter: string;\r\n verse: string;\r\n commentaries: string;\r\n}\r\n\r\nexport class CommentaryCsvParser {\r\n parse(data: string): CommentaryParseTree {\r\n // remove header automatically\r\n const firstLine = data.indexOf('\\n');\r\n data = data.substring(firstLine + 1);\r\n\r\n const lines = parse<string[]>(data, {\r\n header: false,\r\n // transformHeader: (_, index) => {\r\n // const headers = ['book', 'chapter', 'verse', 'commentaries'];\r\n // if (index < headers.length) {\r\n // return headers[index];\r\n // } else {\r\n // return '';\r\n // }\r\n // },\r\n delimiter: ',',\r\n escapeChar: '\"',\r\n quoteChar: '\"',\r\n });\r\n return this.parseLines(\r\n lines.data.map((line: string[]) => ({\r\n book: line[0],\r\n chapter: line[1],\r\n verse: line[2],\r\n commentaries: line[3],\r\n }))\r\n );\r\n }\r\n\r\n parseLines(data: CsvLine[]): CommentaryParseTree {\r\n let tree: CommentaryParseTree = {\r\n type: 'commentary/root',\r\n books: [],\r\n };\r\n\r\n let book: CommentaryBookNode | null = null;\r\n let chapter: CommentaryChapterNode | null = null;\r\n for (let line of data) {\r\n if (hasValue(line.book)) {\r\n const id = getBookId(line.book);\r\n if (!id) {\r\n throw new Error('Invalid book: ' + line.book);\r\n }\r\n\r\n book = tree.books.find((b) => b.book === id) ?? null;\r\n if (!book) {\r\n book = {\r\n type: 'book',\r\n book: id,\r\n introduction: null,\r\n chapters: [],\r\n };\r\n tree.books.push(book);\r\n }\r\n\r\n if (!book.introduction) {\r\n book.introduction = hasValue(line.commentaries)\r\n ? line.commentaries\r\n : null;\r\n }\r\n } else if (hasValue(line.chapter)) {\r\n const number = parseInt(line.chapter);\r\n if (isNaN(number)) {\r\n throw new Error('Invalid chapter number: ' + line.chapter);\r\n }\r\n\r\n if (!book) {\r\n throw new Error('Chapter without book');\r\n }\r\n\r\n chapter = {\r\n type: 'chapter',\r\n number,\r\n introduction: hasValue(line.commentaries)\r\n ? line.commentaries\r\n : null,\r\n verses: [],\r\n };\r\n book.chapters.push(chapter);\r\n } else if (hasValue(line.verse)) {\r\n const ref = parseVerseReference(line.verse);\r\n if (ref) {\r\n if (ref.book === book?.book) {\r\n // @ts-ignore\r\n if (ref.chapter === chapter?.number) {\r\n chapter.verses.push({\r\n type: 'verse',\r\n number: ref.verse,\r\n content: [line.commentaries],\r\n });\r\n } else {\r\n chapter = {\r\n type: 'chapter',\r\n number: ref.chapter,\r\n introduction: null,\r\n verses: [\r\n {\r\n type: 'verse',\r\n number: ref.verse,\r\n content: [line.commentaries],\r\n },\r\n ],\r\n };\r\n\r\n book.chapters.push(chapter);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return tree;\r\n }\r\n}\r\n\r\nfunction hasValue(value: string | null): boolean {\r\n return (\r\n value !== null &&\r\n value !== undefined &&\r\n value !== '' &&\r\n value.trim() !== ''\r\n );\r\n}\r\n"],
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/iterators.ts"],
4
- "sourcesContent": ["import { getLogger } from '../log.js';\r\n\r\n/**\r\n * Defines an interface that represents an iterator that can be rewound.\r\n */\r\nexport interface RewindableIterator<T> extends IterableIterator<T> {\r\n /**\r\n * Rewinds the iterator by the specified number of elements.\r\n * @param number The number of elements to rewind.\r\n */\r\n rewind(number: number): void;\r\n}\r\n\r\n/**\r\n * Defines a class that implements an iterator that can be rewound.\r\n */\r\nexport class Rewindable<T> implements RewindableIterator<T> {\r\n buffer: T[] = [];\r\n\r\n private _bufferSize: number = 0;\r\n private _position: number = -1;\r\n private _iterator: IterableIterator<T>;\r\n\r\n constructor(iterator: IterableIterator<T>, bufferSize: number = 2) {\r\n this._iterator = iterator;\r\n this._bufferSize = bufferSize;\r\n\r\n if (this._iterator.return) {\r\n this.return = (value?: any) => {\r\n return this._iterator.return!(value);\r\n };\r\n }\r\n if (this._iterator.throw) {\r\n this.throw = (value?: any) => {\r\n return this._iterator.throw!(value);\r\n };\r\n }\r\n }\r\n\r\n rewind(number: number): void {\r\n this._position += number;\r\n }\r\n\r\n [Symbol.iterator](): IterableIterator<T> {\r\n return this;\r\n }\r\n\r\n next(...args: [] | [undefined]): IteratorResult<T, any> {\r\n if (this._position >= 0) {\r\n let item = this.buffer[this._position];\r\n this._position--;\r\n return {\r\n value: item,\r\n done: false,\r\n };\r\n }\r\n\r\n let result = this._iterator.next(...args);\r\n this.buffer.unshift(result.value);\r\n if (this.buffer.length > this._bufferSize) {\r\n this.buffer.splice(\r\n this._bufferSize,\r\n this.buffer.length - this._bufferSize\r\n );\r\n }\r\n return result;\r\n }\r\n\r\n return?(value?: any): IteratorResult<T, any>;\r\n throw?(e?: any): IteratorResult<T, any>;\r\n}\r\n\r\nexport function* debug(label: string, iterator: IterableIterator<any>) {\r\n const logger = getLogger();\r\n for (let value of uncompletable(iterator)) {\r\n if (value instanceof Element) {\r\n logger.log(label, value.outerHTML);\r\n } else if (value instanceof Node) {\r\n logger.log(label, value.textContent);\r\n } else {\r\n logger.log(label, value);\r\n }\r\n yield value;\r\n }\r\n}\r\n\r\n/**\r\n * Creates a new rewindable iterator from the given iterator.\r\n * @param iterator The iterator.\r\n * @param bufferSize The size of the buffer.\r\n */\r\nexport function rewindable<T>(\r\n iterator: IterableIterator<T>,\r\n bufferSize: number = 2\r\n): Rewindable<T> {\r\n return new Rewindable(iterator, bufferSize);\r\n}\r\n\r\nfunction* iterateNodes(node: Node) {\r\n for (let i = 0; i < node.childNodes.length; i++) {\r\n yield node.childNodes[i];\r\n }\r\n}\r\n\r\n/**\r\n * Gets the char element that is the parent of the given node.\r\n * @param node The node.\r\n */\r\nexport function parentChar(node: Node): Element | null {\r\n return matchingParent(\r\n node,\r\n (n) => n instanceof Element && n.nodeName === 'char'\r\n ) as Element | null;\r\n}\r\n\r\n/**\r\n * Gets the note element that is the parent of the given node.\r\n * @param node The node.\r\n */\r\nexport function parentNote(node: Node): Element | null {\r\n return matchingParent(\r\n node,\r\n (n) => n instanceof Element && n.nodeName === 'note'\r\n ) as Element | null;\r\n}\r\n\r\n/**\r\n * Gets the parent node that matches the given filter.\r\n * @param node The node to start the search from.\r\n * @param filter The filter that should be used.\r\n */\r\nexport function matchingParent(\r\n node: Node,\r\n filter: (node: Node) => boolean\r\n): Node | null {\r\n let parent = node.parentNode;\r\n while (parent) {\r\n if (filter(parent)) {\r\n return parent;\r\n }\r\n parent = parent.parentNode;\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Determines if the given node is a child of the parent node.\r\n * @param node The node to test.\r\n * @param parent The parent.\r\n * @returns\r\n */\r\nexport function isParent(node: Node, parent: Node): boolean {\r\n let parentNode = node.parentNode;\r\n while (parentNode) {\r\n if (parentNode === parent) {\r\n return true;\r\n }\r\n parentNode = parentNode.parentNode;\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * Iterates through all of the nodes in the tree in a depth-first traversal.\r\n * @param node The node to start the traversal from.\r\n */\r\nexport function iterateAll(node: Node): RewindableIterator<Node> {\r\n return rewindable(_iterateAll(node));\r\n}\r\n\r\nexport function* iterateUntil<T>(\r\n iterator: IterableIterator<T>,\r\n predicate: (value: T) => boolean\r\n): IterableIterator<T> {\r\n for (let value of iterator) {\r\n if (predicate(value)) {\r\n return;\r\n }\r\n yield value;\r\n }\r\n}\r\n\r\nfunction* _iterateAll(node: Node): IterableIterator<Node> {\r\n for (let child of node.childNodes) {\r\n yield child;\r\n yield* iterateAll(child);\r\n }\r\n}\r\n\r\n/**\r\n * Iterates only the elements in the iterator.\r\n * @param iterator The iterator that should be used.\r\n */\r\nexport function* elements(\r\n iterator: IterableIterator<Node>\r\n): IterableIterator<Element> {\r\n for (let node of uncompletable(iterator)) {\r\n if (node instanceof Element) {\r\n yield node;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Iterates only the children of the given node in the iterator.\r\n * @param iterator The iterator that should be used.\r\n * @param parent The parent node.\r\n */\r\nexport function* children(\r\n iterator: RewindableIterator<Node>,\r\n parent: Node\r\n): IterableIterator<Node> {\r\n for (let node of uncompletable(iterator)) {\r\n if (!isParent(node, parent)) {\r\n iterator.rewind(1);\r\n return;\r\n }\r\n\r\n yield node;\r\n }\r\n}\r\n\r\n/**\r\n * Wraps the given iterable in a generator that will prevent consumers from calling return() on the iterator.\r\n * @param iterable The iterable.\r\n */\r\nexport function* uncompletable<T>(\r\n iterable: IterableIterator<T>\r\n): IterableIterator<T> {\r\n while (true) {\r\n const { done, value } = iterable.next();\r\n if (done) {\r\n return;\r\n }\r\n yield value;\r\n }\r\n}\r\n\r\n/**\r\n * Converts the given iterable into an async iterable.\r\n * @param input The input iterable.\r\n */\r\nexport async function* toAsyncIterable<T>(\r\n input: Iterable<T>\r\n): AsyncIterable<T> {\r\n for (let item of input) {\r\n yield item;\r\n }\r\n}\r\n\r\n/**\r\n * Batches items from the given iterator.\r\n * @param input The input iterator.\r\n * @param batchSize The size of each batch.\r\n */\r\nexport function* batch<T>(\r\n input: Iterable<T>,\r\n batchSize: number\r\n): Iterable<T[]> {\r\n while (true) {\r\n let batch = [] as T[];\r\n for (let item of input) {\r\n batch.push(item);\r\n if (batch.length >= batchSize) {\r\n break;\r\n }\r\n }\r\n\r\n if (batch.length === 0) {\r\n return;\r\n }\r\n\r\n yield batch;\r\n }\r\n}\r\n"],
4
+ "sourcesContent": ["import { getLogger } from '../log.js';\n\n/**\n * Defines an interface that represents an iterator that can be rewound.\n */\nexport interface RewindableIterator<T> extends IterableIterator<T> {\n /**\n * Rewinds the iterator by the specified number of elements.\n * @param number The number of elements to rewind.\n */\n rewind(number: number): void;\n}\n\n/**\n * Defines a class that implements an iterator that can be rewound.\n */\nexport class Rewindable<T> implements RewindableIterator<T> {\n buffer: T[] = [];\n\n private _bufferSize: number = 0;\n private _position: number = -1;\n private _iterator: IterableIterator<T>;\n\n constructor(iterator: IterableIterator<T>, bufferSize: number = 2) {\n this._iterator = iterator;\n this._bufferSize = bufferSize;\n\n if (this._iterator.return) {\n this.return = (value?: any) => {\n return this._iterator.return!(value);\n };\n }\n if (this._iterator.throw) {\n this.throw = (value?: any) => {\n return this._iterator.throw!(value);\n };\n }\n }\n\n rewind(number: number): void {\n this._position += number;\n }\n\n [Symbol.iterator](): IterableIterator<T> {\n return this;\n }\n\n next(...args: [] | [undefined]): IteratorResult<T, any> {\n if (this._position >= 0) {\n let item = this.buffer[this._position];\n this._position--;\n return {\n value: item,\n done: false,\n };\n }\n\n let result = this._iterator.next(...args);\n this.buffer.unshift(result.value);\n if (this.buffer.length > this._bufferSize) {\n this.buffer.splice(\n this._bufferSize,\n this.buffer.length - this._bufferSize\n );\n }\n return result;\n }\n\n return?(value?: any): IteratorResult<T, any>;\n throw?(e?: any): IteratorResult<T, any>;\n}\n\nexport function* debug(label: string, iterator: IterableIterator<any>) {\n const logger = getLogger();\n for (let value of uncompletable(iterator)) {\n if (value instanceof Element) {\n logger.log(label, value.outerHTML);\n } else if (value instanceof Node) {\n logger.log(label, value.textContent);\n } else {\n logger.log(label, value);\n }\n yield value;\n }\n}\n\n/**\n * Creates a new rewindable iterator from the given iterator.\n * @param iterator The iterator.\n * @param bufferSize The size of the buffer.\n */\nexport function rewindable<T>(\n iterator: IterableIterator<T>,\n bufferSize: number = 2\n): Rewindable<T> {\n return new Rewindable(iterator, bufferSize);\n}\n\nfunction* iterateNodes(node: Node) {\n for (let i = 0; i < node.childNodes.length; i++) {\n yield node.childNodes[i];\n }\n}\n\n/**\n * Gets the char element that is the parent of the given node.\n * @param node The node.\n */\nexport function parentChar(node: Node): Element | null {\n return matchingParent(\n node,\n (n) => n instanceof Element && n.nodeName === 'char'\n ) as Element | null;\n}\n\n/**\n * Gets the note element that is the parent of the given node.\n * @param node The node.\n */\nexport function parentNote(node: Node): Element | null {\n return matchingParent(\n node,\n (n) => n instanceof Element && n.nodeName === 'note'\n ) as Element | null;\n}\n\n/**\n * Gets the parent node that matches the given filter.\n * @param node The node to start the search from.\n * @param filter The filter that should be used.\n */\nexport function matchingParent(\n node: Node,\n filter: (node: Node) => boolean\n): Node | null {\n let parent = node.parentNode;\n while (parent) {\n if (filter(parent)) {\n return parent;\n }\n parent = parent.parentNode;\n }\n return null;\n}\n\n/**\n * Determines if the given node is a child of the parent node.\n * @param node The node to test.\n * @param parent The parent.\n * @returns\n */\nexport function isParent(node: Node, parent: Node): boolean {\n let parentNode = node.parentNode;\n while (parentNode) {\n if (parentNode === parent) {\n return true;\n }\n parentNode = parentNode.parentNode;\n }\n return false;\n}\n\n/**\n * Iterates through all of the nodes in the tree in a depth-first traversal.\n * @param node The node to start the traversal from.\n */\nexport function iterateAll(node: Node): RewindableIterator<Node> {\n return rewindable(_iterateAll(node));\n}\n\nexport function* iterateUntil<T>(\n iterator: IterableIterator<T>,\n predicate: (value: T) => boolean\n): IterableIterator<T> {\n for (let value of iterator) {\n if (predicate(value)) {\n return;\n }\n yield value;\n }\n}\n\nfunction* _iterateAll(node: Node): IterableIterator<Node> {\n for (let child of node.childNodes) {\n yield child;\n yield* iterateAll(child);\n }\n}\n\n/**\n * Iterates only the elements in the iterator.\n * @param iterator The iterator that should be used.\n */\nexport function* elements(\n iterator: IterableIterator<Node>\n): IterableIterator<Element> {\n for (let node of uncompletable(iterator)) {\n if (node instanceof Element) {\n yield node;\n }\n }\n}\n\n/**\n * Iterates only the children of the given node in the iterator.\n * @param iterator The iterator that should be used.\n * @param parent The parent node.\n */\nexport function* children(\n iterator: RewindableIterator<Node>,\n parent: Node\n): IterableIterator<Node> {\n for (let node of uncompletable(iterator)) {\n if (!isParent(node, parent)) {\n iterator.rewind(1);\n return;\n }\n\n yield node;\n }\n}\n\n/**\n * Wraps the given iterable in a generator that will prevent consumers from calling return() on the iterator.\n * @param iterable The iterable.\n */\nexport function* uncompletable<T>(\n iterable: IterableIterator<T>\n): IterableIterator<T> {\n while (true) {\n const { done, value } = iterable.next();\n if (done) {\n return;\n }\n yield value;\n }\n}\n\n/**\n * Converts the given iterable into an async iterable.\n * @param input The input iterable.\n */\nexport async function* toAsyncIterable<T>(\n input: Iterable<T>\n): AsyncIterable<T> {\n for (let item of input) {\n yield item;\n }\n}\n\n/**\n * Batches items from the given iterator.\n * @param input The input iterator.\n * @param batchSize The size of each batch.\n */\nexport function* batch<T>(\n input: Iterable<T>,\n batchSize: number\n): Iterable<T[]> {\n while (true) {\n let batch = [] as T[];\n for (let item of input) {\n batch.push(item);\n if (batch.length >= batchSize) {\n break;\n }\n }\n\n if (batch.length === 0) {\n return;\n }\n\n yield batch;\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["batch"]
7
7
  }
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- import { bookOrderMap } from "../generation/book-order";
2
+ import { bookOrderMap } from "../generation/book-order.js";
3
3
  export const LOCKMAN_PARSER_VERSION = 3;
4
4
  const bookNumberIdMap = /* @__PURE__ */ new Map();
5
5
  for (const [id, order] of bookOrderMap) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../parser/lockman-parser.ts"],
4
- "sourcesContent": ["import {\r\n ParseTree,\r\n Chapter,\r\n Verse,\r\n Heading,\r\n Footnote,\r\n Text,\r\n FootnoteReference,\r\n HebrewSubtitle,\r\n} from './types';\r\nimport { bookOrderMap } from '../generation/book-order';\r\n\r\nexport const LOCKMAN_PARSER_VERSION = 3;\r\n\r\nconst bookNumberIdMap = new Map<number, string>();\r\nfor (const [id, order] of bookOrderMap) {\r\n if (!bookNumberIdMap.has(order)) {\r\n bookNumberIdMap.set(order, id);\r\n }\r\n}\r\n\r\nexport class LockmanParser {\r\n parse(text: string): ParseTree[] {\r\n const roots: ParseTree[] = [];\r\n let currentRoot: ParseTree | null = null;\r\n let currentChapter: Chapter | null = null;\r\n let currentVerse: Verse | null = null;\r\n\r\n // Splits text into tokens based on structural tags\r\n // Identify major blocks: BN, CN, SH, and Verse Starters (V, C, PM, A, PO)\r\n // Note: SH and CN content is strictly inside the tag.\r\n // Verse content follows the tag.\r\n // We use a regex that captures the tag AND its \"attributes\" like {{..}}Num<T> for verses.\r\n\r\n // Regex for structural separators\r\n // Captures:\r\n // 1. Title: <BN>...</BN>\r\n // 2. Chapter Heading: <CN>...</CN> or <SN>...</SN>\r\n // 3. Section Heading: <SH>...</SH> or <SS>...</SS>\r\n // 4. Verse Start: (<V>|<C>|<CC>|<CP>|<PM>|<A>|<PO>|<P>|<PN>){{...}}Number<T>\r\n\r\n const structureRegex =\r\n /(<BN>.*?<\\/BN>)|((?:<CN>.*?<\\/CN>)|(?:<SN>.*?<\\/SN>))|((?:<SH>.*?<\\/SH>)|(?:<SS>.*?<\\/SS>))|((?:<V>|<C>|<CC>|<CP>|<PM>|<A>|<PO>|<P>|<PN>)\\{\\{.*?\\}\\}\\d+(?:<T>|\\^)(?:\\{.*?\\})?)/g;\r\n\r\n let lastIndex = 0;\r\n let match;\r\n let isPoem = false;\r\n\r\n while ((match = structureRegex.exec(text)) !== null) {\r\n const fullMatch = match[0];\r\n const startIndex = match.index;\r\n\r\n // Content belonging to the PREVIOUS structural element\r\n if (startIndex > lastIndex) {\r\n const content = text.substring(lastIndex, startIndex);\r\n if (currentVerse && currentChapter) {\r\n this.parseVerseContent(\r\n content,\r\n currentVerse,\r\n currentChapter,\r\n isPoem\r\n );\r\n }\r\n }\r\n\r\n lastIndex = startIndex + fullMatch.length;\r\n\r\n if (match[1]) {\r\n // <BN>Title</BN>\r\n // Start of a NEW Book\r\n const title = match[1].replace(/<\\/?BN>/g, '').trim();\r\n\r\n currentRoot = {\r\n type: 'root',\r\n title: title,\r\n content: [],\r\n };\r\n roots.push(currentRoot);\r\n\r\n // Reset context for new book\r\n currentChapter = null;\r\n currentVerse = null;\r\n } else if (match[2]) {\r\n // <CN>CHAPTER 1</CN> or <SN>PSALM 1</SN>\r\n // Start new Chapter\r\n // Ensure we have a root (handle case where no <BN> provided, implicitly create one? Or just attach to last root)\r\n if (!currentRoot) {\r\n // Fallback if no <BN> was found first\r\n currentRoot = {\r\n type: 'root',\r\n content: [],\r\n };\r\n roots.push(currentRoot);\r\n }\r\n\r\n // Remove tags: <CN>, </CN>, <SN>, </SN>\r\n const inner = match[2].replace(/<\\/?(CN|SN)>/g, '').trim();\r\n const numMatch = inner.match(/\\d+/);\r\n const num = numMatch ? parseInt(numMatch[0], 10) : 0;\r\n\r\n currentChapter = {\r\n type: 'chapter',\r\n number: num,\r\n content: [],\r\n footnotes: [],\r\n };\r\n currentRoot.content.push(currentChapter);\r\n currentVerse = null;\r\n } else if (match[3]) {\r\n // <SH>Heading</SH> or <SS>Subtitle</SS>\r\n if (!currentRoot) {\r\n currentRoot = { type: 'root', content: [] };\r\n roots.push(currentRoot);\r\n }\r\n\r\n const raw = match[3];\r\n if (raw.startsWith('<SS>')) {\r\n // Hebrew subtitle in Psalms\r\n const inner = raw.replace(/<\\/?SS>/g, '').trim();\r\n const subtitle: HebrewSubtitle = {\r\n type: 'hebrew_subtitle',\r\n content: [],\r\n };\r\n\r\n if (currentChapter) {\r\n this.parseHebrewSubtitleContent(\r\n inner,\r\n subtitle,\r\n currentChapter\r\n );\r\n currentChapter.content.push(subtitle);\r\n }\r\n } else {\r\n // Section heading\r\n const inner = raw.replace(/<\\/?SH>/g, '').trim();\r\n const normalized = inner.replace(/<\\\\>|<\\/?>|[{}]/g, '');\r\n const heading: Heading = {\r\n type: 'heading',\r\n content: [normalized],\r\n };\r\n if (currentChapter) {\r\n currentChapter.content.push(heading);\r\n } else {\r\n // Start of book heading? Not typical in this structure but possible\r\n currentRoot.content.push(heading);\r\n }\r\n }\r\n } else if (match[4]) {\r\n // Verse Start: <Tag>{{Ref}}Number<T>\r\n // Extract Verse Number\r\n const tagContent = match[4];\r\n if (currentRoot && !currentRoot.id) {\r\n const bookId = this.getBookIdFromVerseTag(tagContent);\r\n if (bookId) {\r\n currentRoot.id = bookId;\r\n }\r\n }\r\n // Regex to extract digits before <T>\r\n const vNumMatch = tagContent.match(/(\\d+)(?:<T>|\\^)/);\r\n const vNum = vNumMatch ? parseInt(vNumMatch[1], 10) : 0;\r\n\r\n if (currentChapter) {\r\n // Check for Paragraph Marker\r\n if (tagContent.startsWith('<PM>')) {\r\n currentChapter.content.push({ type: 'line_break' });\r\n }\r\n\r\n // Update global isPoem flag\r\n isPoem = /<(?:P|PO|PN|CC|CP)>/.test(tagContent);\r\n\r\n currentVerse = {\r\n type: 'verse',\r\n number: vNum,\r\n content: [],\r\n };\r\n currentChapter.content.push(currentVerse);\r\n }\r\n }\r\n }\r\n\r\n // Process remaining text after the last match\r\n if (lastIndex < text.length) {\r\n const content = text.substring(lastIndex);\r\n if (currentVerse && currentChapter) {\r\n this.parseVerseContent(\r\n content,\r\n currentVerse,\r\n currentChapter,\r\n isPoem\r\n );\r\n }\r\n }\r\n\r\n return roots;\r\n }\r\n\r\n private getBookIdFromVerseTag(tagContent: string): string | null {\r\n const match = tagContent.match(/\\{\\{(\\d+)::/);\r\n if (!match) {\r\n return null;\r\n }\r\n\r\n const bookNumber = parseInt(match[1], 10);\r\n if (isNaN(bookNumber)) {\r\n return null;\r\n }\r\n\r\n return bookNumberIdMap.get(bookNumber) ?? null;\r\n }\r\n\r\n private parseVerseContent(\r\n text: string,\r\n verse: Verse,\r\n chapter: Chapter,\r\n isPoem: boolean\r\n ) {\r\n // Remove line breaks that are just formatting in the source file\r\n // But keep spaces.\r\n // The source has newlines. We should arguably treat them as spaces.\r\n let cleanText = text.replace(/\\s+/g, ' ').trim();\r\n cleanText = cleanText.replace(/[\\+\\-][\u201C\u2018\"]/g, '');\r\n cleanText = cleanText.replace(/\\+\\[/g, '[').replace(/\\+\\]/g, ']');\r\n cleanText = cleanText.replace(/@\\[.*?@\\]/g, '');\r\n\r\n // Regex to separate text from special inline blocks:\r\n // 1. Footnotes: <$F ... $E>\r\n // 2. Cross References: <$R ... $RE> (To be removed)\r\n // 3. Formatting Tags: <RA>, <N1>, etc. (To be removed?)\r\n // 4. Italics in curly braces: {text}\r\n\r\n // Note: Curly braces are nested inside footnotes sometimes, but stripped there.\r\n // At the verse level, they denote italics.\r\n\r\n const segmentRegex =\r\n /(<\\$F.*?\\$E>)|(<\\$R.*?\\$RE>)|(<[^>]*>)|(\\{.*?\\})/g;\r\n\r\n let lastIdx = 0;\r\n let m;\r\n let wordsOfJesus = false;\r\n\r\n while ((m = segmentRegex.exec(cleanText)) !== null) {\r\n const snippet = cleanText.substring(lastIdx, m.index);\r\n if (snippet) {\r\n // Add plain text\r\n this.addText(verse, snippet, isPoem, wordsOfJesus);\r\n }\r\n\r\n if (m[1]) {\r\n // Footnote <$F ... $E>\r\n this.processFootnote(m[1], verse, chapter);\r\n } else if (m[2]) {\r\n // Cross Ref -> Ignore\r\n } else if (m[3]) {\r\n if (m[3] === '<PO>') {\r\n verse.content.push({ lineBreak: true });\r\n lastIdx = segmentRegex.lastIndex;\r\n continue;\r\n }\r\n // Handle words of Jesus markers\r\n if (m[3] === '<RS>') {\r\n wordsOfJesus = true;\r\n } else if (m[3] === '</RS>') {\r\n wordsOfJesus = false;\r\n }\r\n // Other tag -> Ignore (e.g. <RA>, <N1>, <FA>)\r\n // These are often just markers or anchors.\r\n } else if (m[4]) {\r\n // Italics {text}\r\n const content = m[4].substring(1, m[4].length - 1);\r\n // We push a Text object with italics: true\r\n // Check if we need to decode inside? Usually just text.\r\n if (content) {\r\n let text: Text = {\r\n text: content,\r\n italics: true,\r\n };\r\n\r\n if (isPoem) {\r\n text.poem = 1;\r\n }\r\n\r\n if (wordsOfJesus) {\r\n text.wordsOfJesus = true;\r\n }\r\n\r\n verse.content.push(text);\r\n }\r\n }\r\n\r\n lastIdx = segmentRegex.lastIndex;\r\n }\r\n\r\n const remaining = cleanText.substring(lastIdx);\r\n if (remaining) {\r\n this.addText(verse, remaining, isPoem, wordsOfJesus);\r\n }\r\n }\r\n\r\n private addText(\r\n verse: Verse,\r\n text: string,\r\n isPoem: boolean,\r\n wordsOfJesus: boolean\r\n ) {\r\n if (!text) return;\r\n\r\n if (isPoem || wordsOfJesus) {\r\n // When poem=1 or wordsOfJesus is true, use Text objects to carry attributes.\r\n const lastItem = verse.content[verse.content.length - 1];\r\n\r\n if (\r\n lastItem &&\r\n typeof lastItem !== 'string' &&\r\n !('noteId' in lastItem) &&\r\n (lastItem as Text).poem === (isPoem ? 1 : undefined) &&\r\n (lastItem as Text).wordsOfJesus ===\r\n (wordsOfJesus ? true : undefined) &&\r\n !(lastItem as Text).italics\r\n ) {\r\n (lastItem as Text).text += text;\r\n } else {\r\n const formatted: Text = {\r\n text: text,\r\n };\r\n if (isPoem) {\r\n formatted.poem = 1;\r\n }\r\n if (wordsOfJesus) {\r\n formatted.wordsOfJesus = true;\r\n }\r\n verse.content.push(formatted);\r\n }\r\n } else {\r\n // Merge with previous string if possible\r\n const lastItem = verse.content[verse.content.length - 1];\r\n if (typeof lastItem === 'string') {\r\n verse.content[verse.content.length - 1] = lastItem + text;\r\n } else {\r\n verse.content.push(text);\r\n }\r\n }\r\n }\r\n\r\n private parseHebrewSubtitleContent(\r\n text: string,\r\n subtitle: HebrewSubtitle,\r\n chapter: Chapter\r\n ) {\r\n let cleanText = text.replace(/\\s+/g, ' ').trim();\r\n cleanText = cleanText.replace(/[{}]/g, '');\r\n cleanText = cleanText.replace(/@\\[.*?@\\]/g, '');\r\n\r\n const segmentRegex =\r\n /(<\\$F.*?\\$E>)|(<\\$R.*?\\$RE>)|(<[^>]+>)|(\\{.*?\\})/g;\r\n\r\n let lastIdx = 0;\r\n let m;\r\n\r\n while ((m = segmentRegex.exec(cleanText)) !== null) {\r\n const snippet = cleanText.substring(lastIdx, m.index);\r\n if (snippet) {\r\n this.addSubtitleText(subtitle, snippet);\r\n }\r\n\r\n if (m[1]) {\r\n const ref = this.processFootnoteForSubtitle(m[1], chapter);\r\n if (ref) {\r\n subtitle.content.push(ref);\r\n }\r\n } else if (m[2]) {\r\n // Cross Ref -> Ignore\r\n } else if (m[3]) {\r\n // Other tag -> Ignore\r\n } else if (m[4]) {\r\n const content = m[4].substring(1, m[4].length - 1);\r\n if (content) {\r\n this.addSubtitleText(subtitle, content);\r\n }\r\n }\r\n\r\n lastIdx = segmentRegex.lastIndex;\r\n }\r\n\r\n const remaining = cleanText.substring(lastIdx);\r\n if (remaining) {\r\n this.addSubtitleText(subtitle, remaining);\r\n }\r\n }\r\n\r\n private addSubtitleText(subtitle: HebrewSubtitle, text: string) {\r\n if (!text) return;\r\n\r\n const lastItem = subtitle.content[subtitle.content.length - 1];\r\n if (typeof lastItem === 'string') {\r\n subtitle.content[subtitle.content.length - 1] = lastItem + text;\r\n } else {\r\n subtitle.content.push(text);\r\n }\r\n }\r\n\r\n private processFootnote(block: string, verse: Verse, chapter: Chapter) {\r\n // block is <$F...$E>\r\n // Inner content has tags like <FN>, <FNC>, <N1>...\r\n // We want the readable text.\r\n // Remove known technical tags.\r\n\r\n const text = this.extractFootnoteText(block);\r\n\r\n if (!text) return;\r\n\r\n const noteId = chapter.footnotes.length + 1;\r\n\r\n const fn: Footnote = {\r\n noteId: noteId,\r\n text: text,\r\n caller: '+',\r\n reference: {\r\n chapter: chapter.number,\r\n verse: verse.number,\r\n },\r\n };\r\n chapter.footnotes.push(fn);\r\n\r\n verse.content.push({\r\n noteId: noteId,\r\n } as FootnoteReference);\r\n }\r\n\r\n private processFootnoteForSubtitle(\r\n block: string,\r\n chapter: Chapter\r\n ): FootnoteReference | null {\r\n const text = this.extractFootnoteText(block);\r\n\r\n if (!text) return null;\r\n\r\n const noteId = chapter.footnotes.length + 1;\r\n\r\n const fn: Footnote = {\r\n noteId: noteId,\r\n text: text,\r\n caller: '+',\r\n reference: {\r\n chapter: chapter.number,\r\n verse: 0,\r\n },\r\n };\r\n chapter.footnotes.push(fn);\r\n\r\n return {\r\n noteId: noteId,\r\n } as FootnoteReference;\r\n }\r\n\r\n private extractFootnoteText(block: string): string {\r\n // Remove outer $F...$E\r\n let inner = block.replace(/^<\\$F/, '').replace(/\\$E>$/, '');\r\n\r\n // Remove internal technical tags\r\n // Remove <FN>...</FN> completely\r\n inner = inner.replace(/<FN>.*?<\\/FN>/g, '');\r\n\r\n // Remove other tags <N1>, <FA> but keep content\r\n // Also remove italics curly braces (keep content inside)\r\n return inner\r\n .replace(/<[^>]+>/g, '')\r\n .replace(/[\\{\\}]/g, '')\r\n .trim();\r\n }\r\n}\r\n"],
4
+ "sourcesContent": ["import {\r\n ParseTree,\r\n Chapter,\r\n Verse,\r\n Heading,\r\n Footnote,\r\n Text,\r\n FootnoteReference,\r\n HebrewSubtitle,\r\n} from './types.js';\r\nimport { bookOrderMap } from '../generation/book-order.js';\r\n\r\nexport const LOCKMAN_PARSER_VERSION = 3;\r\n\r\nconst bookNumberIdMap = new Map<number, string>();\r\nfor (const [id, order] of bookOrderMap) {\r\n if (!bookNumberIdMap.has(order)) {\r\n bookNumberIdMap.set(order, id);\r\n }\r\n}\r\n\r\nexport class LockmanParser {\r\n parse(text: string): ParseTree[] {\r\n const roots: ParseTree[] = [];\r\n let currentRoot: ParseTree | null = null;\r\n let currentChapter: Chapter | null = null;\r\n let currentVerse: Verse | null = null;\r\n\r\n // Splits text into tokens based on structural tags\r\n // Identify major blocks: BN, CN, SH, and Verse Starters (V, C, PM, A, PO)\r\n // Note: SH and CN content is strictly inside the tag.\r\n // Verse content follows the tag.\r\n // We use a regex that captures the tag AND its \"attributes\" like {{..}}Num<T> for verses.\r\n\r\n // Regex for structural separators\r\n // Captures:\r\n // 1. Title: <BN>...</BN>\r\n // 2. Chapter Heading: <CN>...</CN> or <SN>...</SN>\r\n // 3. Section Heading: <SH>...</SH> or <SS>...</SS>\r\n // 4. Verse Start: (<V>|<C>|<CC>|<CP>|<PM>|<A>|<PO>|<P>|<PN>){{...}}Number<T>\r\n\r\n const structureRegex =\r\n /(<BN>.*?<\\/BN>)|((?:<CN>.*?<\\/CN>)|(?:<SN>.*?<\\/SN>))|((?:<SH>.*?<\\/SH>)|(?:<SS>.*?<\\/SS>))|((?:<V>|<C>|<CC>|<CP>|<PM>|<A>|<PO>|<P>|<PN>)\\{\\{.*?\\}\\}\\d+(?:<T>|\\^)(?:\\{.*?\\})?)/g;\r\n\r\n let lastIndex = 0;\r\n let match;\r\n let isPoem = false;\r\n\r\n while ((match = structureRegex.exec(text)) !== null) {\r\n const fullMatch = match[0];\r\n const startIndex = match.index;\r\n\r\n // Content belonging to the PREVIOUS structural element\r\n if (startIndex > lastIndex) {\r\n const content = text.substring(lastIndex, startIndex);\r\n if (currentVerse && currentChapter) {\r\n this.parseVerseContent(\r\n content,\r\n currentVerse,\r\n currentChapter,\r\n isPoem\r\n );\r\n }\r\n }\r\n\r\n lastIndex = startIndex + fullMatch.length;\r\n\r\n if (match[1]) {\r\n // <BN>Title</BN>\r\n // Start of a NEW Book\r\n const title = match[1].replace(/<\\/?BN>/g, '').trim();\r\n\r\n currentRoot = {\r\n type: 'root',\r\n title: title,\r\n content: [],\r\n };\r\n roots.push(currentRoot);\r\n\r\n // Reset context for new book\r\n currentChapter = null;\r\n currentVerse = null;\r\n } else if (match[2]) {\r\n // <CN>CHAPTER 1</CN> or <SN>PSALM 1</SN>\r\n // Start new Chapter\r\n // Ensure we have a root (handle case where no <BN> provided, implicitly create one? Or just attach to last root)\r\n if (!currentRoot) {\r\n // Fallback if no <BN> was found first\r\n currentRoot = {\r\n type: 'root',\r\n content: [],\r\n };\r\n roots.push(currentRoot);\r\n }\r\n\r\n // Remove tags: <CN>, </CN>, <SN>, </SN>\r\n const inner = match[2].replace(/<\\/?(CN|SN)>/g, '').trim();\r\n const numMatch = inner.match(/\\d+/);\r\n const num = numMatch ? parseInt(numMatch[0], 10) : 0;\r\n\r\n currentChapter = {\r\n type: 'chapter',\r\n number: num,\r\n content: [],\r\n footnotes: [],\r\n };\r\n currentRoot.content.push(currentChapter);\r\n currentVerse = null;\r\n } else if (match[3]) {\r\n // <SH>Heading</SH> or <SS>Subtitle</SS>\r\n if (!currentRoot) {\r\n currentRoot = { type: 'root', content: [] };\r\n roots.push(currentRoot);\r\n }\r\n\r\n const raw = match[3];\r\n if (raw.startsWith('<SS>')) {\r\n // Hebrew subtitle in Psalms\r\n const inner = raw.replace(/<\\/?SS>/g, '').trim();\r\n const subtitle: HebrewSubtitle = {\r\n type: 'hebrew_subtitle',\r\n content: [],\r\n };\r\n\r\n if (currentChapter) {\r\n this.parseHebrewSubtitleContent(\r\n inner,\r\n subtitle,\r\n currentChapter\r\n );\r\n currentChapter.content.push(subtitle);\r\n }\r\n } else {\r\n // Section heading\r\n const inner = raw.replace(/<\\/?SH>/g, '').trim();\r\n const normalized = inner.replace(/<\\\\>|<\\/?>|[{}]/g, '');\r\n const heading: Heading = {\r\n type: 'heading',\r\n content: [normalized],\r\n };\r\n if (currentChapter) {\r\n currentChapter.content.push(heading);\r\n } else {\r\n // Start of book heading? Not typical in this structure but possible\r\n currentRoot.content.push(heading);\r\n }\r\n }\r\n } else if (match[4]) {\r\n // Verse Start: <Tag>{{Ref}}Number<T>\r\n // Extract Verse Number\r\n const tagContent = match[4];\r\n if (currentRoot && !currentRoot.id) {\r\n const bookId = this.getBookIdFromVerseTag(tagContent);\r\n if (bookId) {\r\n currentRoot.id = bookId;\r\n }\r\n }\r\n // Regex to extract digits before <T>\r\n const vNumMatch = tagContent.match(/(\\d+)(?:<T>|\\^)/);\r\n const vNum = vNumMatch ? parseInt(vNumMatch[1], 10) : 0;\r\n\r\n if (currentChapter) {\r\n // Check for Paragraph Marker\r\n if (tagContent.startsWith('<PM>')) {\r\n currentChapter.content.push({ type: 'line_break' });\r\n }\r\n\r\n // Update global isPoem flag\r\n isPoem = /<(?:P|PO|PN|CC|CP)>/.test(tagContent);\r\n\r\n currentVerse = {\r\n type: 'verse',\r\n number: vNum,\r\n content: [],\r\n };\r\n currentChapter.content.push(currentVerse);\r\n }\r\n }\r\n }\r\n\r\n // Process remaining text after the last match\r\n if (lastIndex < text.length) {\r\n const content = text.substring(lastIndex);\r\n if (currentVerse && currentChapter) {\r\n this.parseVerseContent(\r\n content,\r\n currentVerse,\r\n currentChapter,\r\n isPoem\r\n );\r\n }\r\n }\r\n\r\n return roots;\r\n }\r\n\r\n private getBookIdFromVerseTag(tagContent: string): string | null {\r\n const match = tagContent.match(/\\{\\{(\\d+)::/);\r\n if (!match) {\r\n return null;\r\n }\r\n\r\n const bookNumber = parseInt(match[1], 10);\r\n if (isNaN(bookNumber)) {\r\n return null;\r\n }\r\n\r\n return bookNumberIdMap.get(bookNumber) ?? null;\r\n }\r\n\r\n private parseVerseContent(\r\n text: string,\r\n verse: Verse,\r\n chapter: Chapter,\r\n isPoem: boolean\r\n ) {\r\n // Remove line breaks that are just formatting in the source file\r\n // But keep spaces.\r\n // The source has newlines. We should arguably treat them as spaces.\r\n let cleanText = text.replace(/\\s+/g, ' ').trim();\r\n cleanText = cleanText.replace(/[\\+\\-][\u201C\u2018\"]/g, '');\r\n cleanText = cleanText.replace(/\\+\\[/g, '[').replace(/\\+\\]/g, ']');\r\n cleanText = cleanText.replace(/@\\[.*?@\\]/g, '');\r\n\r\n // Regex to separate text from special inline blocks:\r\n // 1. Footnotes: <$F ... $E>\r\n // 2. Cross References: <$R ... $RE> (To be removed)\r\n // 3. Formatting Tags: <RA>, <N1>, etc. (To be removed?)\r\n // 4. Italics in curly braces: {text}\r\n\r\n // Note: Curly braces are nested inside footnotes sometimes, but stripped there.\r\n // At the verse level, they denote italics.\r\n\r\n const segmentRegex =\r\n /(<\\$F.*?\\$E>)|(<\\$R.*?\\$RE>)|(<[^>]*>)|(\\{.*?\\})/g;\r\n\r\n let lastIdx = 0;\r\n let m;\r\n let wordsOfJesus = false;\r\n\r\n while ((m = segmentRegex.exec(cleanText)) !== null) {\r\n const snippet = cleanText.substring(lastIdx, m.index);\r\n if (snippet) {\r\n // Add plain text\r\n this.addText(verse, snippet, isPoem, wordsOfJesus);\r\n }\r\n\r\n if (m[1]) {\r\n // Footnote <$F ... $E>\r\n this.processFootnote(m[1], verse, chapter);\r\n } else if (m[2]) {\r\n // Cross Ref -> Ignore\r\n } else if (m[3]) {\r\n if (m[3] === '<PO>') {\r\n verse.content.push({ lineBreak: true });\r\n lastIdx = segmentRegex.lastIndex;\r\n continue;\r\n }\r\n // Handle words of Jesus markers\r\n if (m[3] === '<RS>') {\r\n wordsOfJesus = true;\r\n } else if (m[3] === '</RS>') {\r\n wordsOfJesus = false;\r\n }\r\n // Other tag -> Ignore (e.g. <RA>, <N1>, <FA>)\r\n // These are often just markers or anchors.\r\n } else if (m[4]) {\r\n // Italics {text}\r\n const content = m[4].substring(1, m[4].length - 1);\r\n // We push a Text object with italics: true\r\n // Check if we need to decode inside? Usually just text.\r\n if (content) {\r\n let text: Text = {\r\n text: content,\r\n italics: true,\r\n };\r\n\r\n if (isPoem) {\r\n text.poem = 1;\r\n }\r\n\r\n if (wordsOfJesus) {\r\n text.wordsOfJesus = true;\r\n }\r\n\r\n verse.content.push(text);\r\n }\r\n }\r\n\r\n lastIdx = segmentRegex.lastIndex;\r\n }\r\n\r\n const remaining = cleanText.substring(lastIdx);\r\n if (remaining) {\r\n this.addText(verse, remaining, isPoem, wordsOfJesus);\r\n }\r\n }\r\n\r\n private addText(\r\n verse: Verse,\r\n text: string,\r\n isPoem: boolean,\r\n wordsOfJesus: boolean\r\n ) {\r\n if (!text) return;\r\n\r\n if (isPoem || wordsOfJesus) {\r\n // When poem=1 or wordsOfJesus is true, use Text objects to carry attributes.\r\n const lastItem = verse.content[verse.content.length - 1];\r\n\r\n if (\r\n lastItem &&\r\n typeof lastItem !== 'string' &&\r\n !('noteId' in lastItem) &&\r\n (lastItem as Text).poem === (isPoem ? 1 : undefined) &&\r\n (lastItem as Text).wordsOfJesus ===\r\n (wordsOfJesus ? true : undefined) &&\r\n !(lastItem as Text).italics\r\n ) {\r\n (lastItem as Text).text += text;\r\n } else {\r\n const formatted: Text = {\r\n text: text,\r\n };\r\n if (isPoem) {\r\n formatted.poem = 1;\r\n }\r\n if (wordsOfJesus) {\r\n formatted.wordsOfJesus = true;\r\n }\r\n verse.content.push(formatted);\r\n }\r\n } else {\r\n // Merge with previous string if possible\r\n const lastItem = verse.content[verse.content.length - 1];\r\n if (typeof lastItem === 'string') {\r\n verse.content[verse.content.length - 1] = lastItem + text;\r\n } else {\r\n verse.content.push(text);\r\n }\r\n }\r\n }\r\n\r\n private parseHebrewSubtitleContent(\r\n text: string,\r\n subtitle: HebrewSubtitle,\r\n chapter: Chapter\r\n ) {\r\n let cleanText = text.replace(/\\s+/g, ' ').trim();\r\n cleanText = cleanText.replace(/[{}]/g, '');\r\n cleanText = cleanText.replace(/@\\[.*?@\\]/g, '');\r\n\r\n const segmentRegex =\r\n /(<\\$F.*?\\$E>)|(<\\$R.*?\\$RE>)|(<[^>]+>)|(\\{.*?\\})/g;\r\n\r\n let lastIdx = 0;\r\n let m;\r\n\r\n while ((m = segmentRegex.exec(cleanText)) !== null) {\r\n const snippet = cleanText.substring(lastIdx, m.index);\r\n if (snippet) {\r\n this.addSubtitleText(subtitle, snippet);\r\n }\r\n\r\n if (m[1]) {\r\n const ref = this.processFootnoteForSubtitle(m[1], chapter);\r\n if (ref) {\r\n subtitle.content.push(ref);\r\n }\r\n } else if (m[2]) {\r\n // Cross Ref -> Ignore\r\n } else if (m[3]) {\r\n // Other tag -> Ignore\r\n } else if (m[4]) {\r\n const content = m[4].substring(1, m[4].length - 1);\r\n if (content) {\r\n this.addSubtitleText(subtitle, content);\r\n }\r\n }\r\n\r\n lastIdx = segmentRegex.lastIndex;\r\n }\r\n\r\n const remaining = cleanText.substring(lastIdx);\r\n if (remaining) {\r\n this.addSubtitleText(subtitle, remaining);\r\n }\r\n }\r\n\r\n private addSubtitleText(subtitle: HebrewSubtitle, text: string) {\r\n if (!text) return;\r\n\r\n const lastItem = subtitle.content[subtitle.content.length - 1];\r\n if (typeof lastItem === 'string') {\r\n subtitle.content[subtitle.content.length - 1] = lastItem + text;\r\n } else {\r\n subtitle.content.push(text);\r\n }\r\n }\r\n\r\n private processFootnote(block: string, verse: Verse, chapter: Chapter) {\r\n // block is <$F...$E>\r\n // Inner content has tags like <FN>, <FNC>, <N1>...\r\n // We want the readable text.\r\n // Remove known technical tags.\r\n\r\n const text = this.extractFootnoteText(block);\r\n\r\n if (!text) return;\r\n\r\n const noteId = chapter.footnotes.length + 1;\r\n\r\n const fn: Footnote = {\r\n noteId: noteId,\r\n text: text,\r\n caller: '+',\r\n reference: {\r\n chapter: chapter.number,\r\n verse: verse.number,\r\n },\r\n };\r\n chapter.footnotes.push(fn);\r\n\r\n verse.content.push({\r\n noteId: noteId,\r\n } as FootnoteReference);\r\n }\r\n\r\n private processFootnoteForSubtitle(\r\n block: string,\r\n chapter: Chapter\r\n ): FootnoteReference | null {\r\n const text = this.extractFootnoteText(block);\r\n\r\n if (!text) return null;\r\n\r\n const noteId = chapter.footnotes.length + 1;\r\n\r\n const fn: Footnote = {\r\n noteId: noteId,\r\n text: text,\r\n caller: '+',\r\n reference: {\r\n chapter: chapter.number,\r\n verse: 0,\r\n },\r\n };\r\n chapter.footnotes.push(fn);\r\n\r\n return {\r\n noteId: noteId,\r\n } as FootnoteReference;\r\n }\r\n\r\n private extractFootnoteText(block: string): string {\r\n // Remove outer $F...$E\r\n let inner = block.replace(/^<\\$F/, '').replace(/\\$E>$/, '');\r\n\r\n // Remove internal technical tags\r\n // Remove <FN>...</FN> completely\r\n inner = inner.replace(/<FN>.*?<\\/FN>/g, '');\r\n\r\n // Remove other tags <N1>, <FA> but keep content\r\n // Also remove italics curly braces (keep content inside)\r\n return inner\r\n .replace(/<[^>]+>/g, '')\r\n .replace(/[\\{\\}]/g, '')\r\n .trim();\r\n }\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": ["text"]
7
7
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  import { getLogger } from "../log.js";
3
- import { parseVerseReference } from "../utils";
3
+ import { parseVerseReference } from "../utils.js";
4
4
  var NodeType = /* @__PURE__ */ ((NodeType2) => {
5
5
  NodeType2[NodeType2["Element"] = 1] = "Element";
6
6
  NodeType2[NodeType2["Attribute"] = 2] = "Attribute";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../parser/tyndale-xml-parser.ts"],
4
- "sourcesContent": ["import { getLogger } from '../log.js';\r\nimport { parseVerseReference } from '../utils';\r\nimport {\r\n CommentaryBookNode,\r\n CommentaryChapterNode,\r\n CommentaryParseTree,\r\n CommentaryVerseNode,\r\n} from './types';\r\n\r\nenum NodeType {\r\n Element = 1,\r\n Attribute = 2,\r\n Text = 3,\r\n}\r\n\r\nexport class TyndaleXmlParser {\r\n private _domParser: DOMParser;\r\n\r\n constructor(domParser: DOMParser) {\r\n this._domParser = domParser;\r\n }\r\n\r\n parse(xml: string): CommentaryParseTree {\r\n const logger = getLogger();\r\n const parser = this._domParser;\r\n const doc = parser.parseFromString(xml, 'application/xml');\r\n const rootElement = doc.documentElement;\r\n\r\n let tree: CommentaryParseTree = {\r\n type: 'commentary/root',\r\n books: [],\r\n };\r\n\r\n let books: Map<string, CommentaryBookNode> = new Map();\r\n\r\n function getBook(id: string): CommentaryBookNode {\r\n let bookNode = books.get(id);\r\n\r\n if (!bookNode) {\r\n bookNode = {\r\n type: 'book',\r\n book: id,\r\n introduction: null,\r\n chapters: [],\r\n };\r\n\r\n books.set(id, bookNode);\r\n tree.books.push(bookNode);\r\n }\r\n return bookNode;\r\n }\r\n\r\n function getChapter(\r\n book: CommentaryBookNode,\r\n number: number\r\n ): CommentaryChapterNode {\r\n let chapterNode = book.chapters.find(\r\n (chapter) => chapter.number === number\r\n );\r\n\r\n if (!chapterNode) {\r\n chapterNode = {\r\n type: 'chapter',\r\n number: number,\r\n introduction: null,\r\n verses: [],\r\n };\r\n\r\n book.chapters.push(chapterNode);\r\n }\r\n\r\n return chapterNode;\r\n }\r\n\r\n function getVerse(\r\n chapter: CommentaryChapterNode,\r\n number: number\r\n ): CommentaryVerseNode {\r\n let verseNode = chapter.verses.find(\r\n (verse) => verse.number === number\r\n );\r\n\r\n if (!verseNode) {\r\n verseNode = {\r\n type: 'verse',\r\n number: number,\r\n content: [],\r\n };\r\n\r\n chapter.verses.push(verseNode);\r\n }\r\n\r\n return verseNode;\r\n }\r\n\r\n function formatContent(node: Node, trim: boolean = true): string {\r\n let text: string;\r\n if (node.nodeName === 'p') {\r\n text = (node.textContent || '') + '\\n';\r\n } else if (node.nodeName === '#text') {\r\n text = node.textContent || '';\r\n } else if (node.nodeName === 'br') {\r\n text = '\\n';\r\n } else if (node.nodeType === NodeType.Element) {\r\n text = '';\r\n for (let child of node.childNodes) {\r\n text += formatContent(child, false);\r\n }\r\n } else {\r\n text = node.textContent || '';\r\n }\r\n\r\n if (trim) {\r\n const lines = text.split('\\n');\r\n for (let i = 0; i < lines.length; i++) {\r\n // Trim extra whitespace and replace multiple spaces with a single space\r\n lines[i] = lines[i].replace(/\\s+/g, ' ').trim();\r\n }\r\n text = lines.join('\\n').trim();\r\n }\r\n\r\n return text;\r\n }\r\n\r\n const items = rootElement.querySelectorAll('item');\r\n\r\n for (let item of items) {\r\n const typename = item.getAttribute('typename');\r\n\r\n if (typename === 'StudyNote') {\r\n const refs = item.querySelector('refs')?.textContent;\r\n const body = item.querySelector('body');\r\n\r\n if (!refs || !body) {\r\n logger.warn(\r\n 'Skipping study note item without refs or body:',\r\n item\r\n );\r\n continue;\r\n }\r\n\r\n const ref = parseVerseReference(refs);\r\n\r\n if (!ref) {\r\n logger.warn('Failed to parse verse reference:', refs);\r\n continue;\r\n }\r\n\r\n const bookNode = getBook(ref.book);\r\n const chapterNode = getChapter(bookNode, ref.chapter);\r\n const verseNode = getVerse(chapterNode, ref.verse);\r\n verseNode.content.push(formatContent(body));\r\n } else if (typename === 'BookIntro') {\r\n const refs = item.querySelector('refs')?.textContent;\r\n const body = item.querySelector('body');\r\n\r\n if (!refs || !body) {\r\n logger.warn(\r\n 'Skipping book item without refs or body:',\r\n item\r\n );\r\n continue;\r\n }\r\n\r\n const ref = parseVerseReference(refs);\r\n\r\n if (!ref) {\r\n logger.warn('Failed to parse verse reference:', refs);\r\n continue;\r\n }\r\n\r\n const bookNode = getBook(ref.book);\r\n\r\n bookNode.introduction = formatContent(body);\r\n } else if (typename === 'BookIntroSummary') {\r\n const refs = item.querySelector('refs')?.textContent;\r\n const body = item.querySelector('body');\r\n\r\n if (!refs || !body) {\r\n logger.warn(\r\n 'Skipping book item without refs or body:',\r\n item\r\n );\r\n continue;\r\n }\r\n\r\n const ref = parseVerseReference(refs);\r\n\r\n if (!ref) {\r\n logger.warn('Failed to parse verse reference:', refs);\r\n continue;\r\n }\r\n\r\n const bookNode = getBook(ref.book);\r\n\r\n bookNode.introductionSummary = formatContent(body);\r\n } else if (typename === 'Profile') {\r\n const refs = item.querySelector('refs')?.textContent;\r\n const body = item.querySelector('body');\r\n const title = item.querySelector('title')?.textContent;\r\n const name = item.getAttribute('name');\r\n\r\n if (!refs || !body || !title || !name) {\r\n logger.warn(\r\n 'Skipping profile item without refs, body, or title:',\r\n item\r\n );\r\n continue;\r\n }\r\n\r\n const ref = parseVerseReference(refs);\r\n\r\n if (!ref) {\r\n logger.warn('Failed to parse verse reference:', refs);\r\n continue;\r\n }\r\n\r\n if (!tree.profiles) {\r\n tree.profiles = [];\r\n }\r\n\r\n // convert camelCase to kebab-case\r\n const id = toKebabCase(name);\r\n tree.profiles.push({\r\n id,\r\n subject: title,\r\n content: [formatContent(body)],\r\n reference: ref,\r\n });\r\n }\r\n }\r\n\r\n return tree;\r\n }\r\n}\r\n\r\nexport function toKebabCase(camelCase: string): string {\r\n return camelCase.replace(/([a-z])\\s*([A-Z])/g, '$1-$2').toLowerCase();\r\n}\r\n"],
4
+ "sourcesContent": ["import { getLogger } from '../log.js';\r\nimport { parseVerseReference } from '../utils.js';\r\nimport {\r\n CommentaryBookNode,\r\n CommentaryChapterNode,\r\n CommentaryParseTree,\r\n CommentaryVerseNode,\r\n} from './types.js';\r\n\r\nenum NodeType {\r\n Element = 1,\r\n Attribute = 2,\r\n Text = 3,\r\n}\r\n\r\nexport class TyndaleXmlParser {\r\n private _domParser: DOMParser;\r\n\r\n constructor(domParser: DOMParser) {\r\n this._domParser = domParser;\r\n }\r\n\r\n parse(xml: string): CommentaryParseTree {\r\n const logger = getLogger();\r\n const parser = this._domParser;\r\n const doc = parser.parseFromString(xml, 'application/xml');\r\n const rootElement = doc.documentElement;\r\n\r\n let tree: CommentaryParseTree = {\r\n type: 'commentary/root',\r\n books: [],\r\n };\r\n\r\n let books: Map<string, CommentaryBookNode> = new Map();\r\n\r\n function getBook(id: string): CommentaryBookNode {\r\n let bookNode = books.get(id);\r\n\r\n if (!bookNode) {\r\n bookNode = {\r\n type: 'book',\r\n book: id,\r\n introduction: null,\r\n chapters: [],\r\n };\r\n\r\n books.set(id, bookNode);\r\n tree.books.push(bookNode);\r\n }\r\n return bookNode;\r\n }\r\n\r\n function getChapter(\r\n book: CommentaryBookNode,\r\n number: number\r\n ): CommentaryChapterNode {\r\n let chapterNode = book.chapters.find(\r\n (chapter) => chapter.number === number\r\n );\r\n\r\n if (!chapterNode) {\r\n chapterNode = {\r\n type: 'chapter',\r\n number: number,\r\n introduction: null,\r\n verses: [],\r\n };\r\n\r\n book.chapters.push(chapterNode);\r\n }\r\n\r\n return chapterNode;\r\n }\r\n\r\n function getVerse(\r\n chapter: CommentaryChapterNode,\r\n number: number\r\n ): CommentaryVerseNode {\r\n let verseNode = chapter.verses.find(\r\n (verse) => verse.number === number\r\n );\r\n\r\n if (!verseNode) {\r\n verseNode = {\r\n type: 'verse',\r\n number: number,\r\n content: [],\r\n };\r\n\r\n chapter.verses.push(verseNode);\r\n }\r\n\r\n return verseNode;\r\n }\r\n\r\n function formatContent(node: Node, trim: boolean = true): string {\r\n let text: string;\r\n if (node.nodeName === 'p') {\r\n text = (node.textContent || '') + '\\n';\r\n } else if (node.nodeName === '#text') {\r\n text = node.textContent || '';\r\n } else if (node.nodeName === 'br') {\r\n text = '\\n';\r\n } else if (node.nodeType === NodeType.Element) {\r\n text = '';\r\n for (let child of node.childNodes) {\r\n text += formatContent(child, false);\r\n }\r\n } else {\r\n text = node.textContent || '';\r\n }\r\n\r\n if (trim) {\r\n const lines = text.split('\\n');\r\n for (let i = 0; i < lines.length; i++) {\r\n // Trim extra whitespace and replace multiple spaces with a single space\r\n lines[i] = lines[i].replace(/\\s+/g, ' ').trim();\r\n }\r\n text = lines.join('\\n').trim();\r\n }\r\n\r\n return text;\r\n }\r\n\r\n const items = rootElement.querySelectorAll('item');\r\n\r\n for (let item of items) {\r\n const typename = item.getAttribute('typename');\r\n\r\n if (typename === 'StudyNote') {\r\n const refs = item.querySelector('refs')?.textContent;\r\n const body = item.querySelector('body');\r\n\r\n if (!refs || !body) {\r\n logger.warn(\r\n 'Skipping study note item without refs or body:',\r\n item\r\n );\r\n continue;\r\n }\r\n\r\n const ref = parseVerseReference(refs);\r\n\r\n if (!ref) {\r\n logger.warn('Failed to parse verse reference:', refs);\r\n continue;\r\n }\r\n\r\n const bookNode = getBook(ref.book);\r\n const chapterNode = getChapter(bookNode, ref.chapter);\r\n const verseNode = getVerse(chapterNode, ref.verse);\r\n verseNode.content.push(formatContent(body));\r\n } else if (typename === 'BookIntro') {\r\n const refs = item.querySelector('refs')?.textContent;\r\n const body = item.querySelector('body');\r\n\r\n if (!refs || !body) {\r\n logger.warn(\r\n 'Skipping book item without refs or body:',\r\n item\r\n );\r\n continue;\r\n }\r\n\r\n const ref = parseVerseReference(refs);\r\n\r\n if (!ref) {\r\n logger.warn('Failed to parse verse reference:', refs);\r\n continue;\r\n }\r\n\r\n const bookNode = getBook(ref.book);\r\n\r\n bookNode.introduction = formatContent(body);\r\n } else if (typename === 'BookIntroSummary') {\r\n const refs = item.querySelector('refs')?.textContent;\r\n const body = item.querySelector('body');\r\n\r\n if (!refs || !body) {\r\n logger.warn(\r\n 'Skipping book item without refs or body:',\r\n item\r\n );\r\n continue;\r\n }\r\n\r\n const ref = parseVerseReference(refs);\r\n\r\n if (!ref) {\r\n logger.warn('Failed to parse verse reference:', refs);\r\n continue;\r\n }\r\n\r\n const bookNode = getBook(ref.book);\r\n\r\n bookNode.introductionSummary = formatContent(body);\r\n } else if (typename === 'Profile') {\r\n const refs = item.querySelector('refs')?.textContent;\r\n const body = item.querySelector('body');\r\n const title = item.querySelector('title')?.textContent;\r\n const name = item.getAttribute('name');\r\n\r\n if (!refs || !body || !title || !name) {\r\n logger.warn(\r\n 'Skipping profile item without refs, body, or title:',\r\n item\r\n );\r\n continue;\r\n }\r\n\r\n const ref = parseVerseReference(refs);\r\n\r\n if (!ref) {\r\n logger.warn('Failed to parse verse reference:', refs);\r\n continue;\r\n }\r\n\r\n if (!tree.profiles) {\r\n tree.profiles = [];\r\n }\r\n\r\n // convert camelCase to kebab-case\r\n const id = toKebabCase(name);\r\n tree.profiles.push({\r\n id,\r\n subject: title,\r\n content: [formatContent(body)],\r\n reference: ref,\r\n });\r\n }\r\n }\r\n\r\n return tree;\r\n }\r\n}\r\n\r\nexport function toKebabCase(camelCase: string): string {\r\n return camelCase.replace(/([a-z])\\s*([A-Z])/g, '$1-$2').toLowerCase();\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": ["NodeType"]
7
7
  }
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- import { findLast } from "lodash";
2
+ import { findLast } from "es-toolkit/compat";
3
3
  import { getLogger } from "../log.js";
4
4
  export class UsfmTokenizer {
5
5
  _input = "";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../parser/usfm-parser.ts"],
4
- "sourcesContent": ["import { findLast } from 'lodash';\r\nimport {\r\n ParseTree,\r\n Chapter,\r\n Verse,\r\n HebrewSubtitle,\r\n FootnoteReference,\r\n Footnote,\r\n Text,\r\n} from './types.js';\r\nimport { getLogger } from '../log.js';\r\n\r\n/**\r\n * Defines a class that can tokenize a stream of characters into tokens.\r\n */\r\nexport class UsfmTokenizer {\r\n private _input: string = '';\r\n private _index: number = 0;\r\n private _start: number = 0;\r\n\r\n private get _tokenLength() {\r\n return this._index - this._start;\r\n }\r\n\r\n /**\r\n * Converts the given input into a list of tokens.\r\n * @param input The input that should be tokenized.\r\n */\r\n tokenize(input: string): SimpleToken[] {\r\n this._input = input;\r\n this._index = 0;\r\n\r\n return this._parseTokens();\r\n }\r\n\r\n private _parseTokens() {\r\n let tokens: SimpleToken[] = [];\r\n let token = this._parseToken();\r\n while (token) {\r\n tokens.push(token);\r\n token = this._parseToken();\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n private _parseToken(): SimpleToken | null {\r\n let state:\r\n | 'none'\r\n | 'marker_start'\r\n | 'marker_number'\r\n | 'whitespace'\r\n | 'word' = 'none';\r\n let kind: T | null = null;\r\n this._start = this._index;\r\n\r\n while (this._index < this._input.length) {\r\n const codePointNumber = this._input.codePointAt(this._index);\r\n\r\n if (typeof codePointNumber === 'undefined') {\r\n throw new Error('Unable to get code point!');\r\n }\r\n\r\n const codePoint = String.fromCodePoint(codePointNumber);\r\n\r\n if (state === 'none') {\r\n if (codePoint === '\\\\') {\r\n state = 'marker_start';\r\n } else if (isWhitespace(codePoint)) {\r\n state = 'whitespace';\r\n } else {\r\n state = 'word';\r\n }\r\n } else if (state === 'marker_start') {\r\n if (isDigit(codePoint)) {\r\n if (this._tokenLength === 0) {\r\n throw new Error(\r\n 'Invalid Marker: Markers must not contain only digits.'\r\n );\r\n }\r\n state = 'marker_number';\r\n } else if (codePoint === '*') {\r\n this._index += codePoint.length;\r\n kind = 'marker';\r\n break;\r\n } else if (isWhitespace(codePoint)) {\r\n kind = 'marker';\r\n break;\r\n }\r\n } else if (state === 'marker_number') {\r\n if (codePoint === '*') {\r\n this._index += codePoint.length;\r\n kind = 'marker';\r\n break;\r\n } else if (!isDigit(codePoint)) {\r\n kind = 'marker';\r\n break;\r\n }\r\n } else if (state === 'whitespace') {\r\n if (!isWhitespace(codePoint)) {\r\n kind = 'whitespace';\r\n break;\r\n }\r\n } else if (state === 'word') {\r\n if (isWhitespace(codePoint) || codePoint === '\\\\') {\r\n kind = 'word';\r\n break;\r\n }\r\n }\r\n\r\n this._index += codePoint.length;\r\n }\r\n\r\n if (!kind) {\r\n if (this._index >= this._input.length) {\r\n if (state == 'marker_start' || state === 'marker_number') {\r\n kind = 'marker';\r\n } else if (state === 'word') {\r\n kind = 'word';\r\n } else if (state === 'whitespace') {\r\n kind = 'whitespace';\r\n }\r\n }\r\n }\r\n\r\n if (kind) {\r\n return t(loc(this._start, this._index), kind);\r\n }\r\n\r\n return null;\r\n }\r\n}\r\n\r\nexport interface UsfmParseOptions {\r\n paragraphs: Set<string>;\r\n}\r\n\r\n/**\r\n * Defines a USFM Parser.\r\n */\r\nexport class UsfmParser {\r\n private _poem: number | null = null;\r\n private _wordsOfJesus: boolean = false;\r\n\r\n tokenize(input: string): Token[] {\r\n const simpleTokens = new UsfmTokenizer().tokenize(input);\r\n let tokens: Token[] = [];\r\n\r\n for (let t of simpleTokens) {\r\n if (t.kind === 'marker') {\r\n let source = input.substring(t.loc.start, t.loc.end);\r\n const isEnd = source.endsWith('*');\r\n\r\n if (isEnd) {\r\n source = source.substring(0, source.length - 1);\r\n }\r\n\r\n let numberIndex = -1;\r\n for (let i = 0; i < source.length; i++) {\r\n if (isDigit(source[i])) {\r\n numberIndex = i;\r\n break;\r\n }\r\n }\r\n\r\n let number: number | null = null;\r\n if (numberIndex === 1) {\r\n throw new Error(\r\n 'Markers must not be made only of numbers!'\r\n );\r\n }\r\n if (numberIndex > 0) {\r\n number = parseInt(source.substring(numberIndex));\r\n source = source.substring(0, numberIndex);\r\n }\r\n\r\n if (source.length === 1) {\r\n if (isEnd) {\r\n // Ending marker does not have a command.\r\n // We should look for a matching start marker.\r\n const startMarker = findLast(\r\n tokens,\r\n (t) => t.kind === 'marker' && t.type === 'start'\r\n ) as MarkerToken;\r\n if (startMarker) {\r\n source = startMarker.command;\r\n }\r\n }\r\n\r\n if (source.length === 1) {\r\n throw new Error(\r\n `Markers must have a command! Token: ${t.loc.start}-${t.loc.end}`\r\n );\r\n }\r\n }\r\n\r\n tokens.push(\r\n marker(t.loc, source, number, isEnd ? 'end' : 'start')\r\n );\r\n } else if (t.kind === 'whitespace') {\r\n let source = input.substring(t.loc.start, t.loc.end);\r\n tokens.push(whitespace(t.loc, source));\r\n } else if (t.kind === 'word') {\r\n let source = input.substring(t.loc.start, t.loc.end);\r\n tokens.push(word(t.loc, source));\r\n }\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n parse(input: string): ParseTree {\r\n let root: ParseTree = {\r\n type: 'root',\r\n content: [],\r\n };\r\n\r\n const tokens = this.tokenize(input);\r\n\r\n let expectingId = 0;\r\n let expectingName = 0;\r\n let expectingTitle = 0;\r\n let expectingSectionHeading = 0;\r\n let expectingFootnote = 0;\r\n let expectingFootnoteReference = 0;\r\n let expectingFootnoteText = 0;\r\n let expectingReferenceText = 0;\r\n let expectingWordAttribute = 0;\r\n let expectingNestedWordAttribute = 0;\r\n let expectingWordsOfJesus = 0;\r\n let expectingIntroParagraph = 0;\r\n let expectingCrossReference = 0;\r\n let expectingUnknownCommand = 0;\r\n\r\n let canParseFootnotes = true;\r\n let chapter: Chapter | null = null;\r\n let lastVerse: Verse | null = null;\r\n let verse: Verse | null = null;\r\n let subtitle: HebrewSubtitle | null = null;\r\n let words: string[] = [];\r\n let verseContent: (Text | FootnoteReference | string)[] = [];\r\n let sectionContent: string = '';\r\n let currentFootnoteId = 0;\r\n let footnote: Footnote | null = null;\r\n\r\n this._poem = null;\r\n\r\n const addWordsToVerseOrSubtitle = () => {\r\n if (words.length > 0) {\r\n const text = this._text(words.join('').trimEnd());\r\n if (verse) {\r\n verse.content.push(text);\r\n } else if (subtitle) {\r\n subtitle.content.push(text);\r\n } else {\r\n verseContent.push(text);\r\n }\r\n words = [];\r\n }\r\n };\r\n\r\n const addVerseContentToChapter = (token: Token | null) => {\r\n if (!chapter) {\r\n return;\r\n }\r\n if (verseContent.length > 0) {\r\n if (chapter.content.some((c) => c.type === 'verse')) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot infer first verse after other verses have been added to the chapter!'\r\n );\r\n }\r\n // Implicit first verse\r\n verse = {\r\n type: 'verse',\r\n number: 1,\r\n content: verseContent,\r\n };\r\n chapter.content.push(verse);\r\n verseContent = [];\r\n }\r\n };\r\n\r\n const cleanupVerse = () => {\r\n if (!verse || !chapter) {\r\n return;\r\n }\r\n let chapterContent: Chapter['content'] = [];\r\n for (let i = verse.content.length - 1; i >= 0; i--) {\r\n let content = verse.content[i];\r\n if (typeof content === 'object' && 'heading' in content) {\r\n // move headings that occur at the end of a verse to the chapter\r\n chapterContent.unshift({\r\n type: 'heading',\r\n content: [content.heading],\r\n });\r\n verse.content.splice(i, 1);\r\n } else if (\r\n typeof content === 'object' &&\r\n 'lineBreak' in content &&\r\n content.lineBreak\r\n ) {\r\n // move line breaks that occur at the end of a verse to the chapter\r\n chapterContent.unshift({\r\n type: 'line_break',\r\n });\r\n verse.content.splice(i, 1);\r\n } else {\r\n break;\r\n }\r\n }\r\n\r\n for (let content of chapterContent) {\r\n chapter.content.push(content);\r\n }\r\n };\r\n\r\n const completeVerseOrSubtitle = (token: Token | null) => {\r\n if (verse && isNaN(verse.number)) {\r\n // Verse is invalid for some reason.\r\n const index = chapter!.content.indexOf(verse);\r\n if (index >= 0) {\r\n chapter!.content.splice(index, 1);\r\n }\r\n verse = null;\r\n }\r\n if (verse || subtitle) {\r\n addWordsToVerseOrSubtitle();\r\n }\r\n\r\n addVerseContentToChapter(token);\r\n cleanupVerse();\r\n };\r\n\r\n const completeSection = () => {\r\n if (expectingSectionHeading > 0) {\r\n if (verse) {\r\n addWordsToVerseOrSubtitle();\r\n verse.content.push({\r\n heading: sectionContent,\r\n });\r\n } else if (chapter) {\r\n chapter.content.push({\r\n type: 'heading',\r\n content: [sectionContent],\r\n });\r\n } else {\r\n root.content.push({\r\n type: 'heading',\r\n content: [sectionContent],\r\n });\r\n }\r\n sectionContent = '';\r\n expectingSectionHeading = 0;\r\n }\r\n };\r\n\r\n const addWordsToFootnote = () => {\r\n if (footnote && words.length > 0) {\r\n footnote.text += words.join(' ');\r\n words = [];\r\n }\r\n };\r\n\r\n for (let token of tokens) {\r\n if (token.kind === 'marker') {\r\n if (token.command === '\\\\c') {\r\n addWordsToVerseOrSubtitle();\r\n cleanupVerse();\r\n\r\n chapter = {\r\n type: 'chapter',\r\n number: NaN,\r\n content: [],\r\n footnotes: [],\r\n };\r\n verse = null;\r\n verseContent = [];\r\n\r\n root.content.push(chapter);\r\n } else if (token.command === '\\\\v') {\r\n if (!chapter) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot parse a verse without chapter information!'\r\n );\r\n } else {\r\n completeSection();\r\n completeVerseOrSubtitle(token);\r\n\r\n lastVerse = verse;\r\n verse = {\r\n type: 'verse',\r\n number: NaN,\r\n content: [],\r\n };\r\n\r\n chapter.content.push(verse);\r\n }\r\n } else if (token.command === '\\\\d') {\r\n if (!chapter) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot parse a hebrew subtitle without chapter information!'\r\n );\r\n } else {\r\n completeVerseOrSubtitle(token);\r\n\r\n subtitle = {\r\n type: 'hebrew_subtitle',\r\n content: [],\r\n };\r\n\r\n chapter.content.push(subtitle);\r\n }\r\n } else if (token.command === '\\\\b' || token.command === '\\\\p') {\r\n if (!chapter) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot parse a line break without chapter information!'\r\n );\r\n } else {\r\n if (verse) {\r\n addWordsToVerseOrSubtitle();\r\n verse.content.push({\r\n lineBreak: true,\r\n });\r\n } else {\r\n completeVerseOrSubtitle(token);\r\n chapter.content.push({\r\n type: 'line_break',\r\n });\r\n }\r\n }\r\n } else if (token.command === '\\\\q') {\r\n addWordsToVerseOrSubtitle();\r\n this._poem = token.number;\r\n } else if (token.command === '\\\\p') {\r\n addWordsToVerseOrSubtitle();\r\n this._poem = null;\r\n } else if (token.command === '\\\\id') {\r\n expectingId = 1;\r\n } else if (token.command === '\\\\h') {\r\n expectingName = 1;\r\n root.header = undefined;\r\n } else if (\r\n token.command === '\\\\mt' ||\r\n token.command === '\\\\+mt'\r\n ) {\r\n expectingTitle = 1;\r\n } else if (token.command === '\\\\s') {\r\n expectingSectionHeading = 1;\r\n } else if (token.command === '\\\\r') {\r\n expectingReferenceText = 1;\r\n } else if (token.command === '\\\\f' && canParseFootnotes) {\r\n if (token.type === 'start') {\r\n if (!chapter) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot start a footnote outside of a chapter!',\r\n true\r\n );\r\n } else {\r\n addWordsToVerseOrSubtitle();\r\n footnote = {\r\n noteId: currentFootnoteId,\r\n text: '',\r\n caller: null,\r\n };\r\n const ref: FootnoteReference = {\r\n noteId: footnote.noteId,\r\n };\r\n expectingFootnote = 1;\r\n\r\n chapter.footnotes.push(footnote);\r\n\r\n if (verse) {\r\n verse.content.push(ref);\r\n } else if (subtitle) {\r\n subtitle.content.push(ref);\r\n } else {\r\n verseContent.push(ref);\r\n }\r\n\r\n currentFootnoteId += 1;\r\n }\r\n } else {\r\n addWordsToFootnote();\r\n expectingFootnote = 0;\r\n expectingFootnoteText = 0;\r\n expectingFootnoteReference = 0;\r\n footnote = null;\r\n }\r\n } else if (token.command === '\\\\fr' && canParseFootnotes) {\r\n if (!footnote) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot start a footnote reference outside of a footnote!',\r\n true\r\n );\r\n } else {\r\n expectingFootnoteReference = 1;\r\n }\r\n } else if (token.command === '\\\\ft' && canParseFootnotes) {\r\n if (!footnote) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot start footnote text outside of a footnote!',\r\n true\r\n );\r\n } else {\r\n expectingFootnoteText = 1;\r\n }\r\n } else if (token.command === '\\\\w') {\r\n if (token.type === 'start') {\r\n expectingWordAttribute = 1;\r\n } else {\r\n expectingWordAttribute = 0;\r\n }\r\n } else if (token.command === '\\\\+w') {\r\n if (token.type === 'start') {\r\n expectingNestedWordAttribute = 1;\r\n } else {\r\n expectingNestedWordAttribute = 0;\r\n }\r\n } else if (\r\n token.command === '\\\\wj' ||\r\n token.command === '\\\\+wj'\r\n ) {\r\n if (token.type === 'start') {\r\n addWordsToVerseOrSubtitle();\r\n this._wordsOfJesus = true;\r\n } else {\r\n addWordsToVerseOrSubtitle();\r\n this._wordsOfJesus = false;\r\n }\r\n } else if (token.command === '\\\\ip') {\r\n expectingIntroParagraph = 1;\r\n canParseFootnotes = false;\r\n } else if (token.command === '\\\\x') {\r\n if (token.type === 'start') {\r\n expectingCrossReference = 1;\r\n } else {\r\n expectingCrossReference = 0;\r\n }\r\n } else if (token.command.indexOf('-') >= 0) {\r\n if (token.type === 'start') {\r\n expectingUnknownCommand = 1;\r\n }\r\n } else if (token.type === 'end') {\r\n expectingUnknownCommand = 0;\r\n }\r\n } else if (token.kind === 'word') {\r\n if (expectingId > 0) {\r\n if (expectingId === 1) {\r\n root.id = token.word;\r\n expectingId = 2;\r\n }\r\n } else if (expectingName > 0) {\r\n if (root.header) {\r\n root.header += ' ' + token.word;\r\n } else {\r\n root.header = token.word;\r\n }\r\n } else if (expectingTitle > 0) {\r\n if (root.title) {\r\n root.title += ' ' + token.word;\r\n } else {\r\n root.title = token.word;\r\n }\r\n } else if (expectingSectionHeading > 0) {\r\n if (sectionContent) {\r\n sectionContent += ' ' + token.word;\r\n } else {\r\n sectionContent = token.word;\r\n }\r\n } else if (expectingFootnoteReference > 0) {\r\n if ((expectingFootnoteReference = 1)) {\r\n const [chapter, verse] = token.word.split(/[\\.\\:]/);\r\n\r\n if (footnote) {\r\n footnote.reference = {\r\n chapter: parseInt(chapter),\r\n verse: parseInt(verse),\r\n };\r\n }\r\n\r\n expectingFootnoteReference = 0;\r\n }\r\n } else if (expectingFootnoteText > 0) {\r\n words.push(token.word);\r\n } else if (expectingFootnote > 0) {\r\n if (expectingFootnote === 1) {\r\n if (token.word) {\r\n if (footnote) {\r\n if (token.word === '+' || token.word !== '-') {\r\n footnote.caller = token.word;\r\n } else {\r\n footnote.caller = null;\r\n }\r\n }\r\n // this._throwError(input, token, 'Footnotes must use the \"+\" caller.');\r\n }\r\n expectingFootnote = 2;\r\n } else {\r\n words.push(token.word);\r\n }\r\n } else if (expectingReferenceText > 0) {\r\n // Skip processing words for references\r\n // because references aren't included in the JSON format\r\n // (for now)\r\n } else if (expectingCrossReference > 0) {\r\n // Skip processing words for cross references\r\n } else if (chapter && isNaN(chapter.number)) {\r\n chapter.number = parseInt(token.word);\r\n if (isNaN(chapter.number)) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'The first word token after a chapter marker must be parsable to an integer!'\r\n );\r\n }\r\n } else if (verse && isNaN(verse.number)) {\r\n verse.number = parseInt(token.word);\r\n if (isNaN(verse.number)) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'The first word token after a verse marker must be parsable to an integer!'\r\n );\r\n }\r\n } else if (expectingWordAttribute > 0) {\r\n if (expectingWordAttribute === 1) {\r\n const firstVerticalBarIndex = token.word.indexOf('|');\r\n\r\n if (firstVerticalBarIndex >= 0) {\r\n const name = token.word.slice(\r\n 0,\r\n firstVerticalBarIndex\r\n );\r\n // const rest = token.word.slice(firstVerticalBarIndex + '|'.length);\r\n words.push(name);\r\n expectingWordAttribute = 2;\r\n } else {\r\n words.push(token.word);\r\n }\r\n }\r\n } else if (expectingNestedWordAttribute > 0) {\r\n if (expectingNestedWordAttribute === 1) {\r\n const firstVerticalBarIndex = token.word.indexOf('|');\r\n\r\n if (firstVerticalBarIndex >= 0) {\r\n const name = token.word.slice(\r\n 0,\r\n firstVerticalBarIndex\r\n );\r\n // const rest = token.word.slice(firstVerticalBarIndex + '|'.length);\r\n words.push(name);\r\n expectingNestedWordAttribute = 2;\r\n } else {\r\n words.push(token.word);\r\n }\r\n }\r\n } else if (expectingUnknownCommand > 0) {\r\n } else if (expectingIntroParagraph > 0) {\r\n // Skip processing words for intro paragraphs\r\n } else {\r\n words.push(token.word);\r\n }\r\n } else if (token.kind === 'whitespace') {\r\n if (expectingId > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingId = 0;\r\n }\r\n } else if (expectingName > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingName = 0;\r\n }\r\n } else if (expectingTitle > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingTitle = 0;\r\n }\r\n } else if (expectingSectionHeading > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n completeSection();\r\n }\r\n } else if (expectingReferenceText > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingReferenceText = 0;\r\n }\r\n } else if (expectingIntroParagraph > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingIntroParagraph = 0;\r\n canParseFootnotes = true;\r\n }\r\n } else if (\r\n expectingId > 0 ||\r\n expectingName > 0 ||\r\n expectingTitle > 0 ||\r\n expectingSectionHeading > 0 ||\r\n expectingFootnote > 0 ||\r\n expectingFootnoteReference > 0 ||\r\n expectingFootnoteText > 0 ||\r\n expectingReferenceText > 0 ||\r\n expectingCrossReference > 0 ||\r\n expectingWordAttribute > 0 ||\r\n expectingNestedWordAttribute > 0\r\n ) {\r\n // Skip\r\n } else if (expectingUnknownCommand > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingUnknownCommand = 0;\r\n }\r\n } else if (words.length > 0) {\r\n let lastWord = words[words.length - 1];\r\n if (lastWord !== ' ') {\r\n words.push(' ');\r\n }\r\n }\r\n }\r\n }\r\n\r\n completeVerseOrSubtitle(null);\r\n\r\n return root;\r\n }\r\n\r\n renderMarkdown(tree: ParseTree): string {\r\n let md = '';\r\n\r\n if (tree.header) {\r\n md += `# ${tree.header}\\n`;\r\n }\r\n\r\n for (let c of tree.content) {\r\n if (c.type === 'heading') {\r\n md += `## ${c.content.join(' ')}\\n`;\r\n } else if (c.type === 'chapter') {\r\n md += `### ${c.number}\\n`;\r\n\r\n for (let content of c.content) {\r\n if (content.type === 'heading') {\r\n md += `#### ${content.content.join(' ')}\\n`;\r\n } else if (content.type === 'line_break') {\r\n md += '\\n\\n';\r\n } else if (content.type === 'verse') {\r\n md += `<em>${content.number}</em>`;\r\n for (let v of content.content) {\r\n if (typeof v === 'string') {\r\n md += v + ' ';\r\n } else if ('text' in v) {\r\n md += v.text + ' ';\r\n }\r\n }\r\n md += '\\n';\r\n }\r\n }\r\n }\r\n }\r\n\r\n return md;\r\n }\r\n\r\n private _hasAttribute() {\r\n return this._poem !== null || this._wordsOfJesus;\r\n }\r\n\r\n private _text(text: string): Text | string {\r\n if (!this._hasAttribute()) {\r\n return text;\r\n }\r\n const t: Text = {\r\n text,\r\n };\r\n\r\n if (this._poem !== null) {\r\n t.poem = this._poem;\r\n }\r\n\r\n if (this._wordsOfJesus) {\r\n t.wordsOfJesus = true;\r\n }\r\n\r\n return t;\r\n }\r\n\r\n private _throwError(\r\n source: string,\r\n token: Token | null,\r\n message: string,\r\n warn: boolean = false\r\n ): void {\r\n const logger = getLogger();\r\n if (token) {\r\n let line = 1;\r\n let column = 1;\r\n\r\n let start = token.loc.start;\r\n\r\n for (let i = 0; i < start; i++) {\r\n let char = source[i];\r\n if (char === '\\n') {\r\n line += 1;\r\n column = 1;\r\n } else {\r\n column += 1;\r\n }\r\n }\r\n\r\n let tokenDebug = '';\r\n if (token.kind === 'word') {\r\n tokenDebug = ', word';\r\n } else if (token.kind === 'marker') {\r\n tokenDebug = ', ' + token.command;\r\n } else {\r\n tokenDebug = '';\r\n }\r\n\r\n if (warn) {\r\n logger.warn(`(${line}, ${column}${tokenDebug}) ${message}`);\r\n } else {\r\n throw new Error(`(${line}, ${column}${tokenDebug}) ${message}`);\r\n }\r\n } else {\r\n if (warn) {\r\n logger.warn(message);\r\n } else {\r\n throw new Error(message);\r\n }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Determines if the given character is a digit.\r\n * @param char The character.\r\n */\r\nexport function isDigit(char: string): boolean {\r\n return char.length === 1 && char >= '0' && char <= '9';\r\n}\r\n\r\n/**\r\n * Determines if the given character is considered whitespace.\r\n * @param char The character.\r\n */\r\nexport function isWhitespace(char: string): boolean {\r\n return char === ' ' || char === '\\t' || char === '\\n' || char === '\\r';\r\n}\r\n\r\nexport function t(loc: SourceLocation, kind: T): SimpleToken {\r\n return {\r\n loc,\r\n kind,\r\n };\r\n}\r\n\r\n/**\r\n * Creates a new source location.\r\n * @param start The start of the location.\r\n * @param end The end of the location.\r\n */\r\nexport function loc(start: number, end: number): SourceLocation {\r\n return {\r\n start,\r\n end,\r\n };\r\n}\r\n\r\n/**\r\n * Creates a new marker token.\r\n * @param loc The location for the token.\r\n * @param command The command that the token contains.\r\n * @param number The number that the marker contains.\r\n * @param type The type of the marker.\r\n */\r\nexport function marker(\r\n loc: SourceLocation,\r\n command: string,\r\n number: number | null = null,\r\n type: MarkerToken['type'] = 'start'\r\n): MarkerToken {\r\n return {\r\n kind: 'marker',\r\n loc,\r\n command,\r\n number,\r\n type,\r\n };\r\n}\r\n\r\n/**\r\n * Creates a new word token.\r\n * @param loc The location for the token.\r\n * @param word The word contained by the token.\r\n */\r\nexport function word(loc: SourceLocation, word: string): WordToken {\r\n return {\r\n kind: 'word',\r\n loc,\r\n word,\r\n };\r\n}\r\n\r\nexport function whitespace(\r\n loc: SourceLocation,\r\n whitespace: string\r\n): WhitespaceToken {\r\n return {\r\n kind: 'whitespace',\r\n loc,\r\n whitespace,\r\n };\r\n}\r\n\r\nexport type T = Token['kind'];\r\n\r\n/**\r\n * Defines a simple token.\r\n */\r\nexport interface SimpleToken {\r\n kind: T;\r\n loc: SourceLocation;\r\n}\r\n\r\nexport type Token = MarkerToken | WordToken | WhitespaceToken;\r\n\r\n/**\r\n * Defines an interface for a USFM marker node.\r\n * That is, the syntax for a marker.\r\n */\r\nexport interface MarkerToken {\r\n kind: 'marker';\r\n\r\n /**\r\n * The command that the marker represents.\r\n * This is generally the name of the marker (like \"p\" or \"v\" for paragraph and verse markers)\r\n */\r\n command: string;\r\n\r\n /**\r\n * The number that the marker contains.\r\n * Null if no number was specified.\r\n */\r\n number: number | null;\r\n\r\n /**\r\n * Whether the marker represents a start or an end.\r\n */\r\n type: 'start' | 'end';\r\n\r\n /**\r\n * The location of the node.\r\n */\r\n loc: SourceLocation;\r\n}\r\n\r\n/**\r\n * Defines an interface for a Whitespace node.\r\n */\r\nexport interface WhitespaceToken {\r\n kind: 'whitespace';\r\n\r\n /**\r\n * The whitespace contained by the node.\r\n */\r\n whitespace: string;\r\n\r\n /**\r\n * The location of the node.\r\n */\r\n loc: SourceLocation;\r\n}\r\n\r\n/**\r\n * Defines an interface for a word token.\r\n */\r\nexport interface WordToken {\r\n kind: 'word';\r\n\r\n /**\r\n * The word contained by the token.\r\n */\r\n word: string;\r\n\r\n /**\r\n * The location of the word.\r\n */\r\n loc: SourceLocation;\r\n}\r\n\r\n/**\r\n * The source location of the node.\r\n */\r\nexport interface SourceLocation {\r\n start: number;\r\n end: number;\r\n}\r\n"],
4
+ "sourcesContent": ["import { findLast } from 'es-toolkit/compat';\r\nimport {\r\n ParseTree,\r\n Chapter,\r\n Verse,\r\n HebrewSubtitle,\r\n FootnoteReference,\r\n Footnote,\r\n Text,\r\n} from './types.js';\r\nimport { getLogger } from '../log.js';\r\n\r\n/**\r\n * Defines a class that can tokenize a stream of characters into tokens.\r\n */\r\nexport class UsfmTokenizer {\r\n private _input: string = '';\r\n private _index: number = 0;\r\n private _start: number = 0;\r\n\r\n private get _tokenLength() {\r\n return this._index - this._start;\r\n }\r\n\r\n /**\r\n * Converts the given input into a list of tokens.\r\n * @param input The input that should be tokenized.\r\n */\r\n tokenize(input: string): SimpleToken[] {\r\n this._input = input;\r\n this._index = 0;\r\n\r\n return this._parseTokens();\r\n }\r\n\r\n private _parseTokens() {\r\n let tokens: SimpleToken[] = [];\r\n let token = this._parseToken();\r\n while (token) {\r\n tokens.push(token);\r\n token = this._parseToken();\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n private _parseToken(): SimpleToken | null {\r\n let state:\r\n | 'none'\r\n | 'marker_start'\r\n | 'marker_number'\r\n | 'whitespace'\r\n | 'word' = 'none';\r\n let kind: T | null = null;\r\n this._start = this._index;\r\n\r\n while (this._index < this._input.length) {\r\n const codePointNumber = this._input.codePointAt(this._index);\r\n\r\n if (typeof codePointNumber === 'undefined') {\r\n throw new Error('Unable to get code point!');\r\n }\r\n\r\n const codePoint = String.fromCodePoint(codePointNumber);\r\n\r\n if (state === 'none') {\r\n if (codePoint === '\\\\') {\r\n state = 'marker_start';\r\n } else if (isWhitespace(codePoint)) {\r\n state = 'whitespace';\r\n } else {\r\n state = 'word';\r\n }\r\n } else if (state === 'marker_start') {\r\n if (isDigit(codePoint)) {\r\n if (this._tokenLength === 0) {\r\n throw new Error(\r\n 'Invalid Marker: Markers must not contain only digits.'\r\n );\r\n }\r\n state = 'marker_number';\r\n } else if (codePoint === '*') {\r\n this._index += codePoint.length;\r\n kind = 'marker';\r\n break;\r\n } else if (isWhitespace(codePoint)) {\r\n kind = 'marker';\r\n break;\r\n }\r\n } else if (state === 'marker_number') {\r\n if (codePoint === '*') {\r\n this._index += codePoint.length;\r\n kind = 'marker';\r\n break;\r\n } else if (!isDigit(codePoint)) {\r\n kind = 'marker';\r\n break;\r\n }\r\n } else if (state === 'whitespace') {\r\n if (!isWhitespace(codePoint)) {\r\n kind = 'whitespace';\r\n break;\r\n }\r\n } else if (state === 'word') {\r\n if (isWhitespace(codePoint) || codePoint === '\\\\') {\r\n kind = 'word';\r\n break;\r\n }\r\n }\r\n\r\n this._index += codePoint.length;\r\n }\r\n\r\n if (!kind) {\r\n if (this._index >= this._input.length) {\r\n if (state == 'marker_start' || state === 'marker_number') {\r\n kind = 'marker';\r\n } else if (state === 'word') {\r\n kind = 'word';\r\n } else if (state === 'whitespace') {\r\n kind = 'whitespace';\r\n }\r\n }\r\n }\r\n\r\n if (kind) {\r\n return t(loc(this._start, this._index), kind);\r\n }\r\n\r\n return null;\r\n }\r\n}\r\n\r\nexport interface UsfmParseOptions {\r\n paragraphs: Set<string>;\r\n}\r\n\r\n/**\r\n * Defines a USFM Parser.\r\n */\r\nexport class UsfmParser {\r\n private _poem: number | null = null;\r\n private _wordsOfJesus: boolean = false;\r\n\r\n tokenize(input: string): Token[] {\r\n const simpleTokens = new UsfmTokenizer().tokenize(input);\r\n let tokens: Token[] = [];\r\n\r\n for (let t of simpleTokens) {\r\n if (t.kind === 'marker') {\r\n let source = input.substring(t.loc.start, t.loc.end);\r\n const isEnd = source.endsWith('*');\r\n\r\n if (isEnd) {\r\n source = source.substring(0, source.length - 1);\r\n }\r\n\r\n let numberIndex = -1;\r\n for (let i = 0; i < source.length; i++) {\r\n if (isDigit(source[i])) {\r\n numberIndex = i;\r\n break;\r\n }\r\n }\r\n\r\n let number: number | null = null;\r\n if (numberIndex === 1) {\r\n throw new Error(\r\n 'Markers must not be made only of numbers!'\r\n );\r\n }\r\n if (numberIndex > 0) {\r\n number = parseInt(source.substring(numberIndex));\r\n source = source.substring(0, numberIndex);\r\n }\r\n\r\n if (source.length === 1) {\r\n if (isEnd) {\r\n // Ending marker does not have a command.\r\n // We should look for a matching start marker.\r\n const startMarker = findLast(\r\n tokens,\r\n (t) => t.kind === 'marker' && t.type === 'start'\r\n ) as MarkerToken;\r\n if (startMarker) {\r\n source = startMarker.command;\r\n }\r\n }\r\n\r\n if (source.length === 1) {\r\n throw new Error(\r\n `Markers must have a command! Token: ${t.loc.start}-${t.loc.end}`\r\n );\r\n }\r\n }\r\n\r\n tokens.push(\r\n marker(t.loc, source, number, isEnd ? 'end' : 'start')\r\n );\r\n } else if (t.kind === 'whitespace') {\r\n let source = input.substring(t.loc.start, t.loc.end);\r\n tokens.push(whitespace(t.loc, source));\r\n } else if (t.kind === 'word') {\r\n let source = input.substring(t.loc.start, t.loc.end);\r\n tokens.push(word(t.loc, source));\r\n }\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n parse(input: string): ParseTree {\r\n let root: ParseTree = {\r\n type: 'root',\r\n content: [],\r\n };\r\n\r\n const tokens = this.tokenize(input);\r\n\r\n let expectingId = 0;\r\n let expectingName = 0;\r\n let expectingTitle = 0;\r\n let expectingSectionHeading = 0;\r\n let expectingFootnote = 0;\r\n let expectingFootnoteReference = 0;\r\n let expectingFootnoteText = 0;\r\n let expectingReferenceText = 0;\r\n let expectingWordAttribute = 0;\r\n let expectingNestedWordAttribute = 0;\r\n let expectingWordsOfJesus = 0;\r\n let expectingIntroParagraph = 0;\r\n let expectingCrossReference = 0;\r\n let expectingUnknownCommand = 0;\r\n\r\n let canParseFootnotes = true;\r\n let chapter: Chapter | null = null;\r\n let lastVerse: Verse | null = null;\r\n let verse: Verse | null = null;\r\n let subtitle: HebrewSubtitle | null = null;\r\n let words: string[] = [];\r\n let verseContent: (Text | FootnoteReference | string)[] = [];\r\n let sectionContent: string = '';\r\n let currentFootnoteId = 0;\r\n let footnote: Footnote | null = null;\r\n\r\n this._poem = null;\r\n\r\n const addWordsToVerseOrSubtitle = () => {\r\n if (words.length > 0) {\r\n const text = this._text(words.join('').trimEnd());\r\n if (verse) {\r\n verse.content.push(text);\r\n } else if (subtitle) {\r\n subtitle.content.push(text);\r\n } else {\r\n verseContent.push(text);\r\n }\r\n words = [];\r\n }\r\n };\r\n\r\n const addVerseContentToChapter = (token: Token | null) => {\r\n if (!chapter) {\r\n return;\r\n }\r\n if (verseContent.length > 0) {\r\n if (chapter.content.some((c) => c.type === 'verse')) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot infer first verse after other verses have been added to the chapter!'\r\n );\r\n }\r\n // Implicit first verse\r\n verse = {\r\n type: 'verse',\r\n number: 1,\r\n content: verseContent,\r\n };\r\n chapter.content.push(verse);\r\n verseContent = [];\r\n }\r\n };\r\n\r\n const cleanupVerse = () => {\r\n if (!verse || !chapter) {\r\n return;\r\n }\r\n let chapterContent: Chapter['content'] = [];\r\n for (let i = verse.content.length - 1; i >= 0; i--) {\r\n let content = verse.content[i];\r\n if (typeof content === 'object' && 'heading' in content) {\r\n // move headings that occur at the end of a verse to the chapter\r\n chapterContent.unshift({\r\n type: 'heading',\r\n content: [content.heading],\r\n });\r\n verse.content.splice(i, 1);\r\n } else if (\r\n typeof content === 'object' &&\r\n 'lineBreak' in content &&\r\n content.lineBreak\r\n ) {\r\n // move line breaks that occur at the end of a verse to the chapter\r\n chapterContent.unshift({\r\n type: 'line_break',\r\n });\r\n verse.content.splice(i, 1);\r\n } else {\r\n break;\r\n }\r\n }\r\n\r\n for (let content of chapterContent) {\r\n chapter.content.push(content);\r\n }\r\n };\r\n\r\n const completeVerseOrSubtitle = (token: Token | null) => {\r\n if (verse && isNaN(verse.number)) {\r\n // Verse is invalid for some reason.\r\n const index = chapter!.content.indexOf(verse);\r\n if (index >= 0) {\r\n chapter!.content.splice(index, 1);\r\n }\r\n verse = null;\r\n }\r\n if (verse || subtitle) {\r\n addWordsToVerseOrSubtitle();\r\n }\r\n\r\n addVerseContentToChapter(token);\r\n cleanupVerse();\r\n };\r\n\r\n const completeSection = () => {\r\n if (expectingSectionHeading > 0) {\r\n if (verse) {\r\n addWordsToVerseOrSubtitle();\r\n verse.content.push({\r\n heading: sectionContent,\r\n });\r\n } else if (chapter) {\r\n chapter.content.push({\r\n type: 'heading',\r\n content: [sectionContent],\r\n });\r\n } else {\r\n root.content.push({\r\n type: 'heading',\r\n content: [sectionContent],\r\n });\r\n }\r\n sectionContent = '';\r\n expectingSectionHeading = 0;\r\n }\r\n };\r\n\r\n const addWordsToFootnote = () => {\r\n if (footnote && words.length > 0) {\r\n footnote.text += words.join(' ');\r\n words = [];\r\n }\r\n };\r\n\r\n for (let token of tokens) {\r\n if (token.kind === 'marker') {\r\n if (token.command === '\\\\c') {\r\n addWordsToVerseOrSubtitle();\r\n cleanupVerse();\r\n\r\n chapter = {\r\n type: 'chapter',\r\n number: NaN,\r\n content: [],\r\n footnotes: [],\r\n };\r\n verse = null;\r\n verseContent = [];\r\n\r\n root.content.push(chapter);\r\n } else if (token.command === '\\\\v') {\r\n if (!chapter) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot parse a verse without chapter information!'\r\n );\r\n } else {\r\n completeSection();\r\n completeVerseOrSubtitle(token);\r\n\r\n lastVerse = verse;\r\n verse = {\r\n type: 'verse',\r\n number: NaN,\r\n content: [],\r\n };\r\n\r\n chapter.content.push(verse);\r\n }\r\n } else if (token.command === '\\\\d') {\r\n if (!chapter) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot parse a hebrew subtitle without chapter information!'\r\n );\r\n } else {\r\n completeVerseOrSubtitle(token);\r\n\r\n subtitle = {\r\n type: 'hebrew_subtitle',\r\n content: [],\r\n };\r\n\r\n chapter.content.push(subtitle);\r\n }\r\n } else if (token.command === '\\\\b' || token.command === '\\\\p') {\r\n if (!chapter) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot parse a line break without chapter information!'\r\n );\r\n } else {\r\n if (verse) {\r\n addWordsToVerseOrSubtitle();\r\n verse.content.push({\r\n lineBreak: true,\r\n });\r\n } else {\r\n completeVerseOrSubtitle(token);\r\n chapter.content.push({\r\n type: 'line_break',\r\n });\r\n }\r\n }\r\n } else if (token.command === '\\\\q') {\r\n addWordsToVerseOrSubtitle();\r\n this._poem = token.number;\r\n } else if (token.command === '\\\\p') {\r\n addWordsToVerseOrSubtitle();\r\n this._poem = null;\r\n } else if (token.command === '\\\\id') {\r\n expectingId = 1;\r\n } else if (token.command === '\\\\h') {\r\n expectingName = 1;\r\n root.header = undefined;\r\n } else if (\r\n token.command === '\\\\mt' ||\r\n token.command === '\\\\+mt'\r\n ) {\r\n expectingTitle = 1;\r\n } else if (token.command === '\\\\s') {\r\n expectingSectionHeading = 1;\r\n } else if (token.command === '\\\\r') {\r\n expectingReferenceText = 1;\r\n } else if (token.command === '\\\\f' && canParseFootnotes) {\r\n if (token.type === 'start') {\r\n if (!chapter) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot start a footnote outside of a chapter!',\r\n true\r\n );\r\n } else {\r\n addWordsToVerseOrSubtitle();\r\n footnote = {\r\n noteId: currentFootnoteId,\r\n text: '',\r\n caller: null,\r\n };\r\n const ref: FootnoteReference = {\r\n noteId: footnote.noteId,\r\n };\r\n expectingFootnote = 1;\r\n\r\n chapter.footnotes.push(footnote);\r\n\r\n if (verse) {\r\n verse.content.push(ref);\r\n } else if (subtitle) {\r\n subtitle.content.push(ref);\r\n } else {\r\n verseContent.push(ref);\r\n }\r\n\r\n currentFootnoteId += 1;\r\n }\r\n } else {\r\n addWordsToFootnote();\r\n expectingFootnote = 0;\r\n expectingFootnoteText = 0;\r\n expectingFootnoteReference = 0;\r\n footnote = null;\r\n }\r\n } else if (token.command === '\\\\fr' && canParseFootnotes) {\r\n if (!footnote) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot start a footnote reference outside of a footnote!',\r\n true\r\n );\r\n } else {\r\n expectingFootnoteReference = 1;\r\n }\r\n } else if (token.command === '\\\\ft' && canParseFootnotes) {\r\n if (!footnote) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'Cannot start footnote text outside of a footnote!',\r\n true\r\n );\r\n } else {\r\n expectingFootnoteText = 1;\r\n }\r\n } else if (token.command === '\\\\w') {\r\n if (token.type === 'start') {\r\n expectingWordAttribute = 1;\r\n } else {\r\n expectingWordAttribute = 0;\r\n }\r\n } else if (token.command === '\\\\+w') {\r\n if (token.type === 'start') {\r\n expectingNestedWordAttribute = 1;\r\n } else {\r\n expectingNestedWordAttribute = 0;\r\n }\r\n } else if (\r\n token.command === '\\\\wj' ||\r\n token.command === '\\\\+wj'\r\n ) {\r\n if (token.type === 'start') {\r\n addWordsToVerseOrSubtitle();\r\n this._wordsOfJesus = true;\r\n } else {\r\n addWordsToVerseOrSubtitle();\r\n this._wordsOfJesus = false;\r\n }\r\n } else if (token.command === '\\\\ip') {\r\n expectingIntroParagraph = 1;\r\n canParseFootnotes = false;\r\n } else if (token.command === '\\\\x') {\r\n if (token.type === 'start') {\r\n expectingCrossReference = 1;\r\n } else {\r\n expectingCrossReference = 0;\r\n }\r\n } else if (token.command.indexOf('-') >= 0) {\r\n if (token.type === 'start') {\r\n expectingUnknownCommand = 1;\r\n }\r\n } else if (token.type === 'end') {\r\n expectingUnknownCommand = 0;\r\n }\r\n } else if (token.kind === 'word') {\r\n if (expectingId > 0) {\r\n if (expectingId === 1) {\r\n root.id = token.word;\r\n expectingId = 2;\r\n }\r\n } else if (expectingName > 0) {\r\n if (root.header) {\r\n root.header += ' ' + token.word;\r\n } else {\r\n root.header = token.word;\r\n }\r\n } else if (expectingTitle > 0) {\r\n if (root.title) {\r\n root.title += ' ' + token.word;\r\n } else {\r\n root.title = token.word;\r\n }\r\n } else if (expectingSectionHeading > 0) {\r\n if (sectionContent) {\r\n sectionContent += ' ' + token.word;\r\n } else {\r\n sectionContent = token.word;\r\n }\r\n } else if (expectingFootnoteReference > 0) {\r\n if ((expectingFootnoteReference = 1)) {\r\n const [chapter, verse] = token.word.split(/[\\.\\:]/);\r\n\r\n if (footnote) {\r\n footnote.reference = {\r\n chapter: parseInt(chapter),\r\n verse: parseInt(verse),\r\n };\r\n }\r\n\r\n expectingFootnoteReference = 0;\r\n }\r\n } else if (expectingFootnoteText > 0) {\r\n words.push(token.word);\r\n } else if (expectingFootnote > 0) {\r\n if (expectingFootnote === 1) {\r\n if (token.word) {\r\n if (footnote) {\r\n if (token.word === '+' || token.word !== '-') {\r\n footnote.caller = token.word;\r\n } else {\r\n footnote.caller = null;\r\n }\r\n }\r\n // this._throwError(input, token, 'Footnotes must use the \"+\" caller.');\r\n }\r\n expectingFootnote = 2;\r\n } else {\r\n words.push(token.word);\r\n }\r\n } else if (expectingReferenceText > 0) {\r\n // Skip processing words for references\r\n // because references aren't included in the JSON format\r\n // (for now)\r\n } else if (expectingCrossReference > 0) {\r\n // Skip processing words for cross references\r\n } else if (chapter && isNaN(chapter.number)) {\r\n chapter.number = parseInt(token.word);\r\n if (isNaN(chapter.number)) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'The first word token after a chapter marker must be parsable to an integer!'\r\n );\r\n }\r\n } else if (verse && isNaN(verse.number)) {\r\n verse.number = parseInt(token.word);\r\n if (isNaN(verse.number)) {\r\n this._throwError(\r\n input,\r\n token,\r\n 'The first word token after a verse marker must be parsable to an integer!'\r\n );\r\n }\r\n } else if (expectingWordAttribute > 0) {\r\n if (expectingWordAttribute === 1) {\r\n const firstVerticalBarIndex = token.word.indexOf('|');\r\n\r\n if (firstVerticalBarIndex >= 0) {\r\n const name = token.word.slice(\r\n 0,\r\n firstVerticalBarIndex\r\n );\r\n // const rest = token.word.slice(firstVerticalBarIndex + '|'.length);\r\n words.push(name);\r\n expectingWordAttribute = 2;\r\n } else {\r\n words.push(token.word);\r\n }\r\n }\r\n } else if (expectingNestedWordAttribute > 0) {\r\n if (expectingNestedWordAttribute === 1) {\r\n const firstVerticalBarIndex = token.word.indexOf('|');\r\n\r\n if (firstVerticalBarIndex >= 0) {\r\n const name = token.word.slice(\r\n 0,\r\n firstVerticalBarIndex\r\n );\r\n // const rest = token.word.slice(firstVerticalBarIndex + '|'.length);\r\n words.push(name);\r\n expectingNestedWordAttribute = 2;\r\n } else {\r\n words.push(token.word);\r\n }\r\n }\r\n } else if (expectingUnknownCommand > 0) {\r\n } else if (expectingIntroParagraph > 0) {\r\n // Skip processing words for intro paragraphs\r\n } else {\r\n words.push(token.word);\r\n }\r\n } else if (token.kind === 'whitespace') {\r\n if (expectingId > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingId = 0;\r\n }\r\n } else if (expectingName > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingName = 0;\r\n }\r\n } else if (expectingTitle > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingTitle = 0;\r\n }\r\n } else if (expectingSectionHeading > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n completeSection();\r\n }\r\n } else if (expectingReferenceText > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingReferenceText = 0;\r\n }\r\n } else if (expectingIntroParagraph > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingIntroParagraph = 0;\r\n canParseFootnotes = true;\r\n }\r\n } else if (\r\n expectingId > 0 ||\r\n expectingName > 0 ||\r\n expectingTitle > 0 ||\r\n expectingSectionHeading > 0 ||\r\n expectingFootnote > 0 ||\r\n expectingFootnoteReference > 0 ||\r\n expectingFootnoteText > 0 ||\r\n expectingReferenceText > 0 ||\r\n expectingCrossReference > 0 ||\r\n expectingWordAttribute > 0 ||\r\n expectingNestedWordAttribute > 0\r\n ) {\r\n // Skip\r\n } else if (expectingUnknownCommand > 0) {\r\n if (token.whitespace.includes('\\n')) {\r\n expectingUnknownCommand = 0;\r\n }\r\n } else if (words.length > 0) {\r\n let lastWord = words[words.length - 1];\r\n if (lastWord !== ' ') {\r\n words.push(' ');\r\n }\r\n }\r\n }\r\n }\r\n\r\n completeVerseOrSubtitle(null);\r\n\r\n return root;\r\n }\r\n\r\n renderMarkdown(tree: ParseTree): string {\r\n let md = '';\r\n\r\n if (tree.header) {\r\n md += `# ${tree.header}\\n`;\r\n }\r\n\r\n for (let c of tree.content) {\r\n if (c.type === 'heading') {\r\n md += `## ${c.content.join(' ')}\\n`;\r\n } else if (c.type === 'chapter') {\r\n md += `### ${c.number}\\n`;\r\n\r\n for (let content of c.content) {\r\n if (content.type === 'heading') {\r\n md += `#### ${content.content.join(' ')}\\n`;\r\n } else if (content.type === 'line_break') {\r\n md += '\\n\\n';\r\n } else if (content.type === 'verse') {\r\n md += `<em>${content.number}</em>`;\r\n for (let v of content.content) {\r\n if (typeof v === 'string') {\r\n md += v + ' ';\r\n } else if ('text' in v) {\r\n md += v.text + ' ';\r\n }\r\n }\r\n md += '\\n';\r\n }\r\n }\r\n }\r\n }\r\n\r\n return md;\r\n }\r\n\r\n private _hasAttribute() {\r\n return this._poem !== null || this._wordsOfJesus;\r\n }\r\n\r\n private _text(text: string): Text | string {\r\n if (!this._hasAttribute()) {\r\n return text;\r\n }\r\n const t: Text = {\r\n text,\r\n };\r\n\r\n if (this._poem !== null) {\r\n t.poem = this._poem;\r\n }\r\n\r\n if (this._wordsOfJesus) {\r\n t.wordsOfJesus = true;\r\n }\r\n\r\n return t;\r\n }\r\n\r\n private _throwError(\r\n source: string,\r\n token: Token | null,\r\n message: string,\r\n warn: boolean = false\r\n ): void {\r\n const logger = getLogger();\r\n if (token) {\r\n let line = 1;\r\n let column = 1;\r\n\r\n let start = token.loc.start;\r\n\r\n for (let i = 0; i < start; i++) {\r\n let char = source[i];\r\n if (char === '\\n') {\r\n line += 1;\r\n column = 1;\r\n } else {\r\n column += 1;\r\n }\r\n }\r\n\r\n let tokenDebug = '';\r\n if (token.kind === 'word') {\r\n tokenDebug = ', word';\r\n } else if (token.kind === 'marker') {\r\n tokenDebug = ', ' + token.command;\r\n } else {\r\n tokenDebug = '';\r\n }\r\n\r\n if (warn) {\r\n logger.warn(`(${line}, ${column}${tokenDebug}) ${message}`);\r\n } else {\r\n throw new Error(`(${line}, ${column}${tokenDebug}) ${message}`);\r\n }\r\n } else {\r\n if (warn) {\r\n logger.warn(message);\r\n } else {\r\n throw new Error(message);\r\n }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Determines if the given character is a digit.\r\n * @param char The character.\r\n */\r\nexport function isDigit(char: string): boolean {\r\n return char.length === 1 && char >= '0' && char <= '9';\r\n}\r\n\r\n/**\r\n * Determines if the given character is considered whitespace.\r\n * @param char The character.\r\n */\r\nexport function isWhitespace(char: string): boolean {\r\n return char === ' ' || char === '\\t' || char === '\\n' || char === '\\r';\r\n}\r\n\r\nexport function t(loc: SourceLocation, kind: T): SimpleToken {\r\n return {\r\n loc,\r\n kind,\r\n };\r\n}\r\n\r\n/**\r\n * Creates a new source location.\r\n * @param start The start of the location.\r\n * @param end The end of the location.\r\n */\r\nexport function loc(start: number, end: number): SourceLocation {\r\n return {\r\n start,\r\n end,\r\n };\r\n}\r\n\r\n/**\r\n * Creates a new marker token.\r\n * @param loc The location for the token.\r\n * @param command The command that the token contains.\r\n * @param number The number that the marker contains.\r\n * @param type The type of the marker.\r\n */\r\nexport function marker(\r\n loc: SourceLocation,\r\n command: string,\r\n number: number | null = null,\r\n type: MarkerToken['type'] = 'start'\r\n): MarkerToken {\r\n return {\r\n kind: 'marker',\r\n loc,\r\n command,\r\n number,\r\n type,\r\n };\r\n}\r\n\r\n/**\r\n * Creates a new word token.\r\n * @param loc The location for the token.\r\n * @param word The word contained by the token.\r\n */\r\nexport function word(loc: SourceLocation, word: string): WordToken {\r\n return {\r\n kind: 'word',\r\n loc,\r\n word,\r\n };\r\n}\r\n\r\nexport function whitespace(\r\n loc: SourceLocation,\r\n whitespace: string\r\n): WhitespaceToken {\r\n return {\r\n kind: 'whitespace',\r\n loc,\r\n whitespace,\r\n };\r\n}\r\n\r\nexport type T = Token['kind'];\r\n\r\n/**\r\n * Defines a simple token.\r\n */\r\nexport interface SimpleToken {\r\n kind: T;\r\n loc: SourceLocation;\r\n}\r\n\r\nexport type Token = MarkerToken | WordToken | WhitespaceToken;\r\n\r\n/**\r\n * Defines an interface for a USFM marker node.\r\n * That is, the syntax for a marker.\r\n */\r\nexport interface MarkerToken {\r\n kind: 'marker';\r\n\r\n /**\r\n * The command that the marker represents.\r\n * This is generally the name of the marker (like \"p\" or \"v\" for paragraph and verse markers)\r\n */\r\n command: string;\r\n\r\n /**\r\n * The number that the marker contains.\r\n * Null if no number was specified.\r\n */\r\n number: number | null;\r\n\r\n /**\r\n * Whether the marker represents a start or an end.\r\n */\r\n type: 'start' | 'end';\r\n\r\n /**\r\n * The location of the node.\r\n */\r\n loc: SourceLocation;\r\n}\r\n\r\n/**\r\n * Defines an interface for a Whitespace node.\r\n */\r\nexport interface WhitespaceToken {\r\n kind: 'whitespace';\r\n\r\n /**\r\n * The whitespace contained by the node.\r\n */\r\n whitespace: string;\r\n\r\n /**\r\n * The location of the node.\r\n */\r\n loc: SourceLocation;\r\n}\r\n\r\n/**\r\n * Defines an interface for a word token.\r\n */\r\nexport interface WordToken {\r\n kind: 'word';\r\n\r\n /**\r\n * The word contained by the token.\r\n */\r\n word: string;\r\n\r\n /**\r\n * The location of the word.\r\n */\r\n loc: SourceLocation;\r\n}\r\n\r\n/**\r\n * The source location of the node.\r\n */\r\nexport interface SourceLocation {\r\n start: number;\r\n end: number;\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": ["t", "chapter", "verse", "loc", "word", "whitespace"]
7
7
  }