@eten-tech-foundation/scripture-utilities 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const N=require("@xmldom/xmldom"),T="USJ",A="3.1",v=Object.freeze({type:T,version:A,content:[]}),M=["type","marker","content","sid","eid","number","code","altnumber","pubnumber","caller","align","category"];function m(e){return X.includes(e)}const X=["GEN","EXO","LEV","NUM","DEU","JOS","JDG","RUT","1SA","2SA","1KI","2KI","1CH","2CH","EZR","NEH","EST","JOB","PSA","PRO","ECC","SNG","ISA","JER","LAM","EZK","DAN","HOS","JOL","AMO","OBA","JON","MIC","NAM","HAB","ZEP","HAG","ZEC","MAL","MAT","MRK","LUK","JHN","ACT","ROM","1CO","2CO","GAL","EPH","PHP","COL","1TH","2TH","1TI","2TI","TIT","PHM","HEB","JAS","1PE","2PE","1JN","2JN","3JN","JUD","REV","TOB","JDT","ESG","WIS","SIR","BAR","LJE","S3Y","SUS","BEL","1MA","2MA","3MA","4MA","1ES","2ES","MAN","PS2","ODA","PSS","EZA","5EZ","6EZ","DAG","PS3","2BA","LBA","JUB","ENO","1MQ","2MQ","3MQ","REP","4BA","LAO","FRT","BAK","OTH","INT","CNC","GLO","TDX","NDX","XXA","XXB","XXC","XXD","XXE","XXF","XXG"],l="usx",O="3.1",_=`<${l} version="${O}" />`;function y(e){const n=new N.DOMParser().parseFromString(e,"text/xml");return R(n.documentElement)}function R(e){const[t]=e?J(e):[{content:[]}];return t.type=T,t.version=A,t}function J(e){const t={};let n=e.tagName,o,s,c="append";if(["row","cell"].includes(n)&&(n="table:"+n),e.attributes)for(const r of Array.from(e.attributes))t[r.name]=r.value;t.style&&(o=t.style,delete t.style),t.vid&&delete t.vid,t.status&&delete t.status;let i={type:n};o&&(i.marker=o),i={...i,...t},e.firstChild&&e.firstChild.nodeType===e.firstChild.TEXT_NODE&&e.firstChild.nodeValue&&h(e.firstChild.nodeValue)!==""&&(s=e.firstChild.nodeValue);const f=Array.from(e.childNodes);i.content=[],s&&i.content.push(s);for(const r of f){if(r.tagName===void 0)continue;const[u,S]=J(r);switch(S){case"append":i.content.push(u);break;case"merge":i.content=i.content.concat(u);break}r.nextSibling&&r.nextSibling.nodeType===r.nextSibling.TEXT_NODE&&r.nextSibling.nodeValue&&(h(r.nextSibling.nodeValue)!==""||r.nextSibling.nodeValue===" ")&&i.content.push(r.nextSibling.nodeValue)}return i.content.length===0&&i.type!==l&&delete i.content,"eid"in i&&["verse","chapter"].includes(n)&&(c="ignore"),[i,c]}function h(e){return e.replace(/(^[ \t\n\r\f\v]+)|([ \t\n\r\f\v]+$)/g,"")}let d,a;function U(e){const t=new N.DOMImplementation().createDocument("",l);return t.documentElement&&(t.documentElement.setAttribute("version",O),I(e,t)),t.toString()}function I(e,t){if(t.documentElement){for(const[n,o]of e.content.entries()){const s=n===e.content.length-1;b(o,t.documentElement,t,s)}return t.documentElement??void 0}}function b(e,t,n,o){let s,c,i;if(typeof e=="string")s=n.createTextNode(e);else if(c=e.type.replace("table:",""),s=n.createElement(c),B(s,e),e.content)for(const[r,u]of e.content.entries()){const S=r===e.content.length-1;b(u,s,n,S)}a&&(c==="verse"||t.tagName==="para"&&o)&&(i=p(n,a),a=void 0),c==="verse"&&typeof e!="string"&&e.sid!==void 0&&(a=e.sid),d&&(c==="chapter"||c==="para"&&o)&&(i=P(n,d),d=void 0),c==="chapter"&&typeof e!="string"&&e.sid!==void 0&&(d=e.sid);const f=t.nodeName===l&&(i==null?void 0:i.tagName)==="verse";i&&(!o||f)&&t.appendChild(i),t.appendChild(s),i&&o&&!f&&t.appendChild(i),o&&t.nodeName===l&&(a&&t.appendChild(p(n,a)),d&&t.appendChild(P(n,d)),a=void 0,d=void 0)}function B(e,t){t.type==="unmatched"?e.setAttribute("marker",t.marker):e.setAttribute("style",t.marker);for(const[n,o]of Object.entries(t))o&&!["type","marker","content"].includes(n)&&e.setAttribute(n,o)}function p(e,t){const n=e.createElement("verse");return n.setAttribute("eid",t),n}function P(e,t){const n=e.createElement("chapter");return n.setAttribute("eid",t),n}const E="$",g=".content[";function V(e){const t=e.split(g);if(t.shift()!==E)throw new Error(`indexesFromJsonPath: jsonPath didn't start with '${E}'`);return t.map(o=>parseInt(o,10))}function C(e){return e.reduce((t,n)=>`${t}${g}${n}]`,E)}exports.EMPTY_USJ=v;exports.EMPTY_USX=_;exports.MARKER_OBJECT_PROPS=M;exports.USJ_TYPE=T;exports.USJ_VERSION=A;exports.USX_TYPE=l;exports.USX_VERSION=O;exports.indexesFromUsjJsonPath=V;exports.isValidBookCode=m;exports.usjJsonPathFromIndexes=C;exports.usjToUsxString=U;exports.usxStringToUsj=y;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const N=require("@xmldom/xmldom"),y=new Set(["__proto__","prototype","constructor"]);function J(e){if(y.has(e))throw new Error(`The key "${e}" is not allowed to avoid prototype pollution.`)}const T="USJ",A="3.1",M=Object.freeze({type:T,version:A,content:[]}),m=["type","marker","content","sid","eid","number","code","altnumber","pubnumber","caller","align","category"];function X(e){return _.includes(e)}const _=["GEN","EXO","LEV","NUM","DEU","JOS","JDG","RUT","1SA","2SA","1KI","2KI","1CH","2CH","EZR","NEH","EST","JOB","PSA","PRO","ECC","SNG","ISA","JER","LAM","EZK","DAN","HOS","JOL","AMO","OBA","JON","MIC","NAM","HAB","ZEP","HAG","ZEC","MAL","MAT","MRK","LUK","JHN","ACT","ROM","1CO","2CO","GAL","EPH","PHP","COL","1TH","2TH","1TI","2TI","TIT","PHM","HEB","JAS","1PE","2PE","1JN","2JN","3JN","JUD","REV","TOB","JDT","ESG","WIS","SIR","BAR","LJE","S3Y","SUS","BEL","1MA","2MA","3MA","4MA","1ES","2ES","MAN","PS2","ODA","PSS","EZA","5EZ","6EZ","DAG","PS3","2BA","LBA","JUB","ENO","1MQ","2MQ","3MQ","REP","4BA","LAO","FRT","BAK","OTH","INT","CNC","GLO","TDX","NDX","XXA","XXB","XXC","XXD","XXE","XXF","XXG"],l="usx",O="3.1",R=`<${l} version="${O}" />`;function U(e){const n=new N.DOMParser().parseFromString(e,"text/xml");return I(n.documentElement)}function I(e){const[t]=e?b(e):[{content:[]}];return t.type=T,t.version=A,t}function b(e){const t={};let n=e.tagName,o,s,c="append";if(["row","cell"].includes(n)&&(n="table:"+n),e.attributes)for(const r of Array.from(e.attributes))J(r.name),t[r.name]=r.value;t.style&&(o=t.style,delete t.style),t.vid&&delete t.vid,t.status&&delete t.status;let i={type:n};o&&(i.marker=o),i={...i,...t},e.firstChild&&e.firstChild.nodeType===e.firstChild.TEXT_NODE&&e.firstChild.nodeValue&&h(e.firstChild.nodeValue)!==""&&(s=e.firstChild.nodeValue);const f=Array.from(e.childNodes);i.content=[],s&&i.content.push(s);for(const r of f){if(r.tagName===void 0)continue;const[u,S]=b(r);switch(S){case"append":i.content.push(u);break;case"merge":i.content=i.content.concat(u);break}r.nextSibling&&r.nextSibling.nodeType===r.nextSibling.TEXT_NODE&&r.nextSibling.nodeValue&&(h(r.nextSibling.nodeValue)!==""||r.nextSibling.nodeValue===" ")&&i.content.push(r.nextSibling.nodeValue)}return i.content.length===0&&i.type!==l&&delete i.content,"eid"in i&&["verse","chapter"].includes(n)&&(c="ignore"),[i,c]}function h(e){return e.replace(/(^[ \t\n\r\f\v]+)|([ \t\n\r\f\v]+$)/g,"")}let d,a;function B(e){const t=new N.DOMImplementation().createDocument("",l);return t.documentElement&&(t.documentElement.setAttribute("version",O),V(e,t)),t.toString()}function V(e,t){if(t.documentElement){for(const[n,o]of e.content.entries()){const s=n===e.content.length-1;g(o,t.documentElement,t,s)}return t.documentElement??void 0}}function g(e,t,n,o){let s,c,i;if(typeof e=="string")s=n.createTextNode(e);else if(c=e.type.replace("table:",""),s=n.createElement(c),C(s,e),e.content)for(const[r,u]of e.content.entries()){const S=r===e.content.length-1;g(u,s,n,S)}a&&(c==="verse"||t.tagName==="para"&&o)&&(i=p(n,a),a=void 0),c==="verse"&&typeof e!="string"&&e.sid!==void 0&&(a=e.sid),d&&(c==="chapter"||c==="para"&&o)&&(i=P(n,d),d=void 0),c==="chapter"&&typeof e!="string"&&e.sid!==void 0&&(d=e.sid);const f=t.nodeName===l&&(i==null?void 0:i.tagName)==="verse";i&&(!o||f)&&t.appendChild(i),t.appendChild(s),i&&o&&!f&&t.appendChild(i),o&&t.nodeName===l&&(a&&t.appendChild(p(n,a)),d&&t.appendChild(P(n,d)),a=void 0,d=void 0)}function C(e,t){t.marker&&(t.type==="unmatched"?e.setAttribute("marker",t.marker):e.setAttribute("style",t.marker));for(const[n,o]of Object.entries(t))o&&!["type","marker","content"].includes(n)&&e.setAttribute(n,o)}function p(e,t){const n=e.createElement("verse");return n.setAttribute("eid",t),n}function P(e,t){const n=e.createElement("chapter");return n.setAttribute("eid",t),n}const E="$",v=".content[";function H(e){const t=e.split(v);if(t.shift()!==E)throw new Error(`indexesFromJsonPath: jsonPath didn't start with '${E}'`);return t.map(o=>parseInt(o,10))}function j(e){return e.reduce((t,n)=>`${t}${v}${n}]`,E)}exports.EMPTY_USJ=M;exports.EMPTY_USX=R;exports.MARKER_OBJECT_PROPS=m;exports.USJ_TYPE=T;exports.USJ_VERSION=A;exports.USX_TYPE=l;exports.USX_VERSION=O;exports.VALID_BOOK_CODES=_;exports.assertSafeKey=J;exports.indexesFromUsjJsonPath=H;exports.isValidBookCode=X;exports.usjJsonPathFromIndexes=j;exports.usjToUsxString=B;exports.usxStringToUsj=U;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/converters/usj/usj.model.ts","../src/converters/usj/usx.model.ts","../src/converters/usj/usx-to-usj.ts","../src/converters/usj/usj-to-usx.ts","../src/converters/usj/jsonpath-indexes.ts"],"sourcesContent":["/**\n * Unified Scripture JSON (USJ) - The JSON variant of USFM and USX data models.\n * These types follow this schema:\n * @see https://github.com/usfm-bible/tcdocs/blob/usj/grammar/usj.js\n */\n\n/** The USJ spec type */\nexport const USJ_TYPE = \"USJ\";\n\n/** The USJ spec version */\nexport const USJ_VERSION = \"3.1\";\n\nexport const EMPTY_USJ = Object.freeze<Usj>({ type: USJ_TYPE, version: USJ_VERSION, content: [] });\n\n/** List of known properties of `MarkerObject` */\nexport const MARKER_OBJECT_PROPS: (keyof MarkerObject)[] = [\n \"type\",\n \"marker\",\n \"content\",\n \"sid\",\n \"eid\",\n \"number\",\n \"code\",\n \"altnumber\",\n \"pubnumber\",\n \"caller\",\n \"align\",\n \"category\",\n];\n\n/** Single piece of Scripture content */\nexport type MarkerContent = string | MarkerObject;\n\n/** A Scripture Marker and its contents */\nexport interface MarkerObject {\n /**\n * The kind/category of node or element this is, corresponding the USFM marker and USX node\n * @example `para`, `verse`, `char`\n */\n type: string;\n /**\n * The corresponding marker in USFM or style in USX\n * @example `p`, `v`, `nd`\n */\n marker: string;\n /** This marker's contents laid out in order */\n content?: MarkerContent[];\n /** Indicates the Book-chapter-verse value in the paragraph based structure */\n sid?: string;\n /** Milestone end ID, matches start ID (not currently included in USJ spec) */\n eid?: string;\n /** Chapter number or verse number */\n number?: string;\n /** The 3-letter book code in ID element */\n code?: BookCode;\n /** Alternate chapter number or verse number */\n altnumber?: string;\n /** Published character of chapter or verse */\n pubnumber?: string;\n /** Caller character for footnotes and cross-refs */\n caller?: string;\n /** Alignment of table cells */\n align?: string;\n /** Category of extended study bible sections */\n category?: string;\n}\n\n/** Scripture data represented in JSON format. Data compatible transformation from USX/USFM */\nexport interface Usj {\n /** The USJ spec type */\n type: typeof USJ_TYPE;\n /** The USJ spec version */\n version: typeof USJ_VERSION;\n /** The JSON representation of scripture contents from USFM/USX */\n content: MarkerContent[];\n}\n\nexport function isValidBookCode(code: string): boolean {\n return VALID_BOOK_CODES.includes(code as BookCode);\n}\n\n/** 3-letter Scripture book code */\nexport type BookCode = (typeof VALID_BOOK_CODES)[number];\n\nconst VALID_BOOK_CODES = [\n // Old Testament\n \"GEN\",\n \"EXO\",\n \"LEV\",\n \"NUM\",\n \"DEU\",\n \"JOS\",\n \"JDG\",\n \"RUT\",\n \"1SA\",\n \"2SA\",\n \"1KI\",\n \"2KI\",\n \"1CH\",\n \"2CH\",\n \"EZR\",\n \"NEH\",\n \"EST\",\n \"JOB\",\n \"PSA\",\n \"PRO\",\n \"ECC\",\n \"SNG\",\n \"ISA\",\n \"JER\",\n \"LAM\",\n \"EZK\",\n \"DAN\",\n \"HOS\",\n \"JOL\",\n \"AMO\",\n \"OBA\",\n \"JON\",\n \"MIC\",\n \"NAM\",\n \"HAB\",\n \"ZEP\",\n \"HAG\",\n \"ZEC\",\n \"MAL\",\n // New Testament\n \"MAT\",\n \"MRK\",\n \"LUK\",\n \"JHN\",\n \"ACT\",\n \"ROM\",\n \"1CO\",\n \"2CO\",\n \"GAL\",\n \"EPH\",\n \"PHP\",\n \"COL\",\n \"1TH\",\n \"2TH\",\n \"1TI\",\n \"2TI\",\n \"TIT\",\n \"PHM\",\n \"HEB\",\n \"JAS\",\n \"1PE\",\n \"2PE\",\n \"1JN\",\n \"2JN\",\n \"3JN\",\n \"JUD\",\n \"REV\",\n // Deuterocanon\n \"TOB\",\n \"JDT\",\n \"ESG\",\n \"WIS\",\n \"SIR\",\n \"BAR\",\n \"LJE\",\n \"S3Y\",\n \"SUS\",\n \"BEL\",\n \"1MA\",\n \"2MA\",\n \"3MA\",\n \"4MA\",\n \"1ES\",\n \"2ES\",\n \"MAN\",\n \"PS2\",\n \"ODA\",\n \"PSS\",\n \"EZA\",\n \"5EZ\",\n \"6EZ\",\n \"DAG\",\n \"PS3\",\n \"2BA\",\n \"LBA\",\n \"JUB\",\n \"ENO\",\n \"1MQ\",\n \"2MQ\",\n \"3MQ\",\n \"REP\",\n \"4BA\",\n \"LAO\",\n // Non scripture\n \"FRT\",\n \"BAK\",\n \"OTH\",\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n \"NDX\",\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n] as const;\n","/**\n * Unified Scripture XML (USX).\n * These types follow this schema:\n * @see https://github.com/usfm-bible/tcdocs/blob/main/grammar/usx.rng\n */\n\n/** The USX spec type */\nexport const USX_TYPE = \"usx\";\n\n/** The USX spec version */\nexport const USX_VERSION = \"3.1\";\n\nexport const EMPTY_USX = `<${USX_TYPE} version=\"${USX_VERSION}\" />`;\n","/**\n * Convert Scripture from USX to USJ.\n * Adapted to TypeScript from this file:\n * @see https://github.com/usfm-bible/usfmtc/blob/0afa385a1f282b286cc6bff7bbc953ae788aa10c/src/usfmtc/usjproc.py\n */\n\nimport { DOMParser, Element } from \"@xmldom/xmldom\";\nimport { MarkerContent, MarkerObject, USJ_TYPE, USJ_VERSION, Usj } from \"./usj.model.js\";\nimport { USX_TYPE } from \"./usx.model.js\";\n\ntype Action = \"append\" | \"merge\" | \"ignore\";\ninterface Attribs {\n [name: string]: string;\n}\n\nexport function usxStringToUsj(usxString: string): Usj {\n const parser = new DOMParser();\n const inputUsxDom = parser.parseFromString(usxString, \"text/xml\");\n return usxDomToUsj(inputUsxDom.documentElement);\n}\n\nexport function usxDomToUsj(inputUsxDom: Element | null): Usj {\n const [outputJson] = inputUsxDom\n ? convertUsxRecurse(inputUsxDom)\n : [{ content: [] as MarkerContent[] } as Usj];\n outputJson.type = USJ_TYPE;\n outputJson.version = USJ_VERSION;\n return outputJson;\n}\n\nfunction convertUsxRecurse<T extends Usj | MarkerObject = Usj>(\n inputUsxElement: Element,\n): [outputJson: T, action: Action] {\n const attribs: Attribs = {};\n let type: string = inputUsxElement.tagName;\n let marker: string | undefined;\n let text: string | undefined;\n let action: Action = \"append\";\n\n if ([\"row\", \"cell\"].includes(type)) type = \"table:\" + type;\n if (inputUsxElement.attributes) {\n for (const attrib of Array.from(inputUsxElement.attributes)) {\n attribs[attrib.name] = attrib.value;\n }\n }\n\n if (attribs.style) {\n marker = attribs.style;\n delete attribs.style;\n }\n // dropping because presence of vid in para elements is not consistent in USX\n if (attribs.vid) delete attribs.vid;\n // Not dropping `attribs.closed` for backwards compatibility.\n // dropping because it is nonstandard derived metadata that could get out of date\n if (attribs.status) delete attribs.status;\n\n let outObj: T = { type } as T;\n if (marker) (outObj as MarkerObject).marker = marker;\n outObj = { ...outObj, ...attribs };\n\n if (\n inputUsxElement.firstChild &&\n inputUsxElement.firstChild.nodeType === inputUsxElement.firstChild.TEXT_NODE &&\n inputUsxElement.firstChild.nodeValue &&\n asciiTrim(inputUsxElement.firstChild.nodeValue) !== \"\"\n ) {\n text = inputUsxElement.firstChild.nodeValue;\n }\n\n const children = Array.from(inputUsxElement.childNodes);\n outObj.content = [];\n\n if (text) {\n outObj.content.push(text);\n }\n\n for (const child of children) {\n // ChildNodes are Elements.\n if ((child as Element).tagName === undefined) {\n continue;\n }\n // ChildNodes are Elements.\n const [childDict, whatToDo] = convertUsxRecurse<MarkerObject>(child as Element);\n\n switch (whatToDo) {\n case \"append\":\n outObj.content.push(childDict);\n break;\n case \"merge\":\n outObj.content = outObj.content.concat(childDict);\n break;\n case \"ignore\":\n break;\n default:\n break;\n }\n\n // Handle tail text\n if (\n child.nextSibling &&\n child.nextSibling.nodeType === child.nextSibling.TEXT_NODE &&\n child.nextSibling.nodeValue &&\n (asciiTrim(child.nextSibling.nodeValue) !== \"\" || child.nextSibling.nodeValue === \" \")\n ) {\n outObj.content.push(child.nextSibling.nodeValue);\n }\n }\n\n // For backward compatibility, not deleting content for type: chapter, verse, optbreak, ms OR\n // marker: va, ca, b.\n if (outObj.content.length === 0 && outObj.type !== USX_TYPE) {\n delete outObj.content;\n }\n\n if (\"eid\" in outObj && [\"verse\", \"chapter\"].includes(type)) {\n action = \"ignore\";\n }\n\n return [outObj, action];\n}\n\n/**\n * Removes leading and trailing ASCII whitespace.\n *\n * Only trim ASCII whitespace characters: space, tab, line feed, carriage return, form feed,\n * vertical tab.\n * @param str - The string to remove whitespace from.\n * @returns the string with leading and trailing whitespace removed.\n */\nfunction asciiTrim(str: string): string {\n return str.replace(/(^[ \\t\\n\\r\\f\\v]+)|([ \\t\\n\\r\\f\\v]+$)/g, \"\");\n}\n","/**\n * Convert Scripture from USJ to USX.\n * Adapted to TypeScript from this file:\n * @see https://github.com/usfm-bible/usfmtc/blob/0afa385a1f282b286cc6bff7bbc953ae788aa10c/src/usfmtc/usjproc.py\n */\n\nimport { DOMImplementation, Document, Element, Text } from \"@xmldom/xmldom\";\nimport { MarkerContent, MarkerObject, Usj } from \"./usj.model.js\";\nimport { USX_TYPE, USX_VERSION } from \"./usx.model.js\";\n\nlet chapterEid: string | undefined;\nlet verseEid: string | undefined;\n\nexport function usjToUsxString(usj: Usj): string {\n const usxDoc = new DOMImplementation().createDocument(\"\", USX_TYPE);\n if (usxDoc.documentElement) {\n usxDoc.documentElement.setAttribute(\"version\", USX_VERSION);\n usjToUsxDom(usj, usxDoc);\n }\n return usxDoc.toString();\n}\n\nexport function usjToUsxDom(usj: Usj, usxDoc: Document): Element | undefined {\n if (!usxDoc.documentElement) return undefined;\n\n for (const [index, markerContent] of usj.content.entries()) {\n const isLastItem = index === usj.content.length - 1;\n convertUsjRecurse(markerContent, usxDoc.documentElement, usxDoc, isLastItem);\n }\n return usxDoc.documentElement ?? undefined;\n}\n\nfunction convertUsjRecurse(\n markerContent: MarkerContent,\n parentElement: Element,\n usxDoc: Document,\n isLastItem: boolean,\n) {\n let element: Text | Element;\n let type: string | undefined;\n let eidElement: Element | undefined;\n if (typeof markerContent === \"string\") element = usxDoc.createTextNode(markerContent);\n else {\n type = markerContent.type.replace(\"table:\", \"\");\n element = usxDoc.createElement(type);\n setAttributes(element, markerContent);\n if (markerContent.content) {\n for (const [index, item] of markerContent.content.entries()) {\n const _isLastItem = index === markerContent.content.length - 1;\n convertUsjRecurse(item, element, usxDoc, _isLastItem);\n }\n }\n }\n\n // Create chapter and verse end elements from SID attributes.\n if (verseEid && (type === \"verse\" || (parentElement.tagName === \"para\" && isLastItem))) {\n eidElement = createVerseEndElement(usxDoc, verseEid);\n verseEid = undefined;\n }\n if (type === \"verse\" && typeof markerContent !== \"string\" && markerContent.sid !== undefined)\n verseEid = markerContent.sid;\n\n if (chapterEid && (type === \"chapter\" || (type === \"para\" && isLastItem))) {\n eidElement = createChapterEndElement(usxDoc, chapterEid);\n chapterEid = undefined;\n }\n if (type === \"chapter\" && typeof markerContent !== \"string\" && markerContent.sid !== undefined)\n chapterEid = markerContent.sid;\n\n // Append to parent.\n const isVerseInImpliedPara =\n parentElement.nodeName === USX_TYPE && eidElement?.tagName === \"verse\";\n if (eidElement && (!isLastItem || isVerseInImpliedPara)) parentElement.appendChild(eidElement);\n parentElement.appendChild(element);\n if (eidElement && isLastItem && !isVerseInImpliedPara) parentElement.appendChild(eidElement);\n\n // Allow for final chapter and verse end elements at the end of an implied para.\n if (isLastItem && parentElement.nodeName === USX_TYPE) {\n if (verseEid) parentElement.appendChild(createVerseEndElement(usxDoc, verseEid));\n if (chapterEid) parentElement.appendChild(createChapterEndElement(usxDoc, chapterEid));\n verseEid = undefined;\n chapterEid = undefined;\n }\n}\n\nfunction setAttributes(element: Element, markerContent: MarkerObject) {\n if (markerContent.type === \"unmatched\") element.setAttribute(\"marker\", markerContent.marker);\n else element.setAttribute(\"style\", markerContent.marker);\n for (const [key, value] of Object.entries(markerContent)) {\n if (value && ![\"type\", \"marker\", \"content\"].includes(key)) {\n element.setAttribute(key, value as string);\n }\n }\n}\n\nfunction createVerseEndElement(usxDoc: Document, verseEid: string): Element {\n const eidElement = usxDoc.createElement(\"verse\");\n eidElement.setAttribute(\"eid\", verseEid);\n return eidElement;\n}\n\nfunction createChapterEndElement(usxDoc: Document, chapterEid: string): Element {\n const eidElement = usxDoc.createElement(\"chapter\");\n eidElement.setAttribute(\"eid\", chapterEid);\n return eidElement;\n}\n","const JSON_PATH_START = \"$\";\nconst JSON_PATH_CONTENT = \".content[\";\n\n/**\n * Converts a USJ JSONPath string into an array of indexes.\n *\n * @param jsonPath - The USJ JSONPath string to convert. It must start with `$` and contain `.content[index]` segments.\n * @returns An array of numeric indexes extracted from the JSONPath.\n * @throws Will throw an error if the JSONPath does not start with `$`.\n */\nexport function indexesFromUsjJsonPath(jsonPath: string): number[] {\n const path = jsonPath.split(JSON_PATH_CONTENT);\n if (path.shift() !== JSON_PATH_START)\n throw new Error(`indexesFromJsonPath: jsonPath didn't start with '${JSON_PATH_START}'`);\n\n const indexes = path.map((str) => parseInt(str, 10));\n return indexes;\n}\n\n/**\n * Converts an array of indexes into a USJ JSONPath string.\n *\n * @param indexes - An array of numeric indexes to convert.\n * @returns A USJ JSONPath string constructed from the indexes.\n */\nexport function usjJsonPathFromIndexes(indexes: number[]): string {\n return indexes.reduce((path, index) => `${path}${JSON_PATH_CONTENT}${index}]`, JSON_PATH_START);\n}\n"],"names":["USJ_TYPE","USJ_VERSION","EMPTY_USJ","MARKER_OBJECT_PROPS","isValidBookCode","code","VALID_BOOK_CODES","USX_TYPE","USX_VERSION","EMPTY_USX","usxStringToUsj","usxString","inputUsxDom","DOMParser","usxDomToUsj","outputJson","convertUsxRecurse","inputUsxElement","attribs","type","marker","text","action","attrib","outObj","asciiTrim","children","child","childDict","whatToDo","str","chapterEid","verseEid","usjToUsxString","usj","usxDoc","DOMImplementation","usjToUsxDom","index","markerContent","isLastItem","convertUsjRecurse","parentElement","element","eidElement","setAttributes","item","_isLastItem","createVerseEndElement","createChapterEndElement","isVerseInImpliedPara","key","value","JSON_PATH_START","JSON_PATH_CONTENT","indexesFromUsjJsonPath","jsonPath","path","usjJsonPathFromIndexes","indexes"],"mappings":"kHAOaA,EAAW,MAGXC,EAAc,MAEdC,EAAY,OAAO,OAAY,CAAE,KAAMF,EAAU,QAASC,EAAa,QAAS,EAAC,CAAG,EAGpFE,EAA8C,CACzD,OACA,SACA,UACA,MACA,MACA,SACA,OACA,YACA,YACA,SACA,QACA,UACF,EAiDO,SAASC,EAAgBC,EAAuB,CACrD,OAAOC,EAAiB,SAASD,CAAgB,CACnD,CAKA,MAAMC,EAAmB,CAEvB,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,KACF,ECtMaC,EAAW,MAGXC,EAAc,MAEdC,EAAY,IAAIF,CAAQ,aAAaC,CAAW,OCGtD,SAASE,EAAeC,EAAwB,CAErD,MAAMC,EADS,IAAIC,YAAA,EACQ,gBAAgBF,EAAW,UAAU,EAChE,OAAOG,EAAYF,EAAY,eAAe,CAChD,CAEO,SAASE,EAAYF,EAAkC,CAC5D,KAAM,CAACG,CAAU,EAAIH,EACjBI,EAAkBJ,CAAW,EAC7B,CAAC,CAAE,QAAS,CAAA,EAA8B,EAC9C,OAAAG,EAAW,KAAOf,EAClBe,EAAW,QAAUd,EACdc,CACT,CAEA,SAASC,EACPC,EACiC,CACjC,MAAMC,EAAmB,CAAA,EACzB,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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/converters/usj/converter.utils.ts","../src/converters/usj/usj.model.ts","../src/converters/usj/usx.model.ts","../src/converters/usj/usx-to-usj.ts","../src/converters/usj/usj-to-usx.ts","../src/converters/usj/jsonpath-indexes.ts"],"sourcesContent":["/* Utility functions for converters */\n\nconst UNSAFE_KEYS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\n\n/**\n * Avoid prototype pollution by disallowing unsafe keys.\n * @param key - The array key to validate.\n *\n * @public\n */\nexport function assertSafeKey(key: string): void {\n if (!UNSAFE_KEYS.has(key)) return;\n\n throw new Error(`The key \"${key}\" is not allowed to avoid prototype pollution.`);\n}\n","/**\n * Unified Scripture JSON (USJ) - The JSON variant of USFM and USX data models.\n * These types follow this schema:\n * @see https://github.com/usfm-bible/tcdocs/blob/usj/grammar/usj.js\n */\n\n/**\n * The USJ spec type\n * @public\n */\nexport const USJ_TYPE = \"USJ\";\n\n/**\n * The USJ spec version\n * @public\n */\nexport const USJ_VERSION = \"3.1\";\n\n/**\n * An empty USJ object\n * @public\n */\nexport const EMPTY_USJ = Object.freeze<Usj>({ type: USJ_TYPE, version: USJ_VERSION, content: [] });\n\n/**\n * List of known properties of `MarkerObject`\n * @public\n */\nexport const MARKER_OBJECT_PROPS: (keyof MarkerObject)[] = [\n \"type\",\n \"marker\",\n \"content\",\n \"sid\",\n \"eid\",\n \"number\",\n \"code\",\n \"altnumber\",\n \"pubnumber\",\n \"caller\",\n \"align\",\n \"category\",\n];\n\n/**\n * Single piece of Scripture content\n * @public\n */\nexport type MarkerContent = string | MarkerObject;\n\n/**\n * A Scripture Marker and its contents\n * @public\n */\nexport interface MarkerObject {\n /**\n * The kind/category of node or element this is, corresponding the USFM marker and USX node\n * @example `para`, `verse`, `char`\n */\n type: string;\n /**\n * The corresponding marker in USFM or style in USX\n * @example `p`, `v`, `nd`\n */\n marker?: string;\n /** This marker's contents laid out in order */\n content?: MarkerContent[];\n /** Indicates the Book-chapter-verse value in the paragraph based structure */\n sid?: string;\n /** Milestone end ID, matches start ID (not currently included in USJ spec) */\n eid?: string;\n /** Chapter number or verse number */\n number?: string;\n /** The 3-letter book code in ID element */\n code?: BookCode;\n /** Alternate chapter number or verse number */\n altnumber?: string;\n /** Published character of chapter or verse */\n pubnumber?: string;\n /** Caller character for footnotes and cross-refs */\n caller?: string;\n /** Alignment of table cells */\n align?: string;\n /** Category of extended study bible sections */\n category?: string;\n}\n\n/**\n * Scripture data represented in JSON format. Data compatible transformation from USX/USFM\n * @public\n */\nexport interface Usj {\n /** The USJ spec type */\n type: typeof USJ_TYPE;\n /** The USJ spec version */\n version: typeof USJ_VERSION;\n /** The JSON representation of scripture contents from USFM/USX */\n content: MarkerContent[];\n}\n\n/**\n * Check if the given code is a valid 3-letter Scripture book code.\n * @public\n */\nexport function isValidBookCode(code: string): boolean {\n return VALID_BOOK_CODES.includes(code as BookCode);\n}\n\n/**\n * 3-letter Scripture book code\n * @public\n */\nexport type BookCode = (typeof VALID_BOOK_CODES)[number];\n\n/**\n * List of valid 3-letter Scripture book codes\n * @public\n */\nexport const VALID_BOOK_CODES = [\n // Old Testament\n \"GEN\",\n \"EXO\",\n \"LEV\",\n \"NUM\",\n \"DEU\",\n \"JOS\",\n \"JDG\",\n \"RUT\",\n \"1SA\",\n \"2SA\",\n \"1KI\",\n \"2KI\",\n \"1CH\",\n \"2CH\",\n \"EZR\",\n \"NEH\",\n \"EST\",\n \"JOB\",\n \"PSA\",\n \"PRO\",\n \"ECC\",\n \"SNG\",\n \"ISA\",\n \"JER\",\n \"LAM\",\n \"EZK\",\n \"DAN\",\n \"HOS\",\n \"JOL\",\n \"AMO\",\n \"OBA\",\n \"JON\",\n \"MIC\",\n \"NAM\",\n \"HAB\",\n \"ZEP\",\n \"HAG\",\n \"ZEC\",\n \"MAL\",\n // New Testament\n \"MAT\",\n \"MRK\",\n \"LUK\",\n \"JHN\",\n \"ACT\",\n \"ROM\",\n \"1CO\",\n \"2CO\",\n \"GAL\",\n \"EPH\",\n \"PHP\",\n \"COL\",\n \"1TH\",\n \"2TH\",\n \"1TI\",\n \"2TI\",\n \"TIT\",\n \"PHM\",\n \"HEB\",\n \"JAS\",\n \"1PE\",\n \"2PE\",\n \"1JN\",\n \"2JN\",\n \"3JN\",\n \"JUD\",\n \"REV\",\n // Deuterocanon\n \"TOB\",\n \"JDT\",\n \"ESG\",\n \"WIS\",\n \"SIR\",\n \"BAR\",\n \"LJE\",\n \"S3Y\",\n \"SUS\",\n \"BEL\",\n \"1MA\",\n \"2MA\",\n \"3MA\",\n \"4MA\",\n \"1ES\",\n \"2ES\",\n \"MAN\",\n \"PS2\",\n \"ODA\",\n \"PSS\",\n \"EZA\",\n \"5EZ\",\n \"6EZ\",\n \"DAG\",\n \"PS3\",\n \"2BA\",\n \"LBA\",\n \"JUB\",\n \"ENO\",\n \"1MQ\",\n \"2MQ\",\n \"3MQ\",\n \"REP\",\n \"4BA\",\n \"LAO\",\n // Non scripture\n \"FRT\",\n \"BAK\",\n \"OTH\",\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n \"NDX\",\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n] as const;\n","/**\n * Unified Scripture XML (USX).\n * These types follow this schema:\n * @see https://github.com/usfm-bible/tcdocs/blob/main/grammar/usx.rng\n */\n\n/**\n * The USX spec type\n * @public\n */\nexport const USX_TYPE = \"usx\";\n\n/**\n * The USX spec version\n * @public\n */\nexport const USX_VERSION = \"3.1\";\n\n/**\n * An empty USX string\n * @public\n */\nexport const EMPTY_USX = `<${USX_TYPE} version=\"${USX_VERSION}\" />`;\n","/**\n * Convert Scripture from USX to USJ.\n * Adapted to TypeScript from this file:\n * @see https://github.com/usfm-bible/usfmtc/blob/0afa385a1f282b286cc6bff7bbc953ae788aa10c/src/usfmtc/usjproc.py\n */\n\nimport { DOMParser, Element } from \"@xmldom/xmldom\";\nimport { MarkerContent, MarkerObject, USJ_TYPE, USJ_VERSION, Usj } from \"./usj.model.js\";\nimport { USX_TYPE } from \"./usx.model.js\";\nimport { assertSafeKey } from \"./converter.utils.js\";\n\ntype Action = \"append\" | \"merge\" | \"ignore\";\ninterface Attribs {\n [name: string]: string;\n}\n\n/**\n * Converts a USX string to a USJ object.\n *\n * @param usxString - The USX string to convert.\n * @returns The converted USJ object.\n *\n * @public\n */\nexport function usxStringToUsj(usxString: string): Usj {\n const parser = new DOMParser();\n const inputUsxDom = parser.parseFromString(usxString, \"text/xml\");\n return usxDomToUsj(inputUsxDom.documentElement);\n}\n\nexport function usxDomToUsj(inputUsxDom: Element | null): Usj {\n const [outputJson] = inputUsxDom\n ? convertUsxRecurse(inputUsxDom)\n : [{ content: [] as MarkerContent[] } as Usj];\n outputJson.type = USJ_TYPE;\n outputJson.version = USJ_VERSION;\n return outputJson;\n}\n\nfunction convertUsxRecurse<T extends Usj | MarkerObject = Usj>(\n inputUsxElement: Element,\n): [outputJson: T, action: Action] {\n const attribs: Attribs = {};\n let type: string = inputUsxElement.tagName;\n let marker: string | undefined;\n let text: string | undefined;\n let action: Action = \"append\";\n\n if ([\"row\", \"cell\"].includes(type)) type = \"table:\" + type;\n if (inputUsxElement.attributes) {\n for (const attrib of Array.from(inputUsxElement.attributes)) {\n assertSafeKey(attrib.name);\n attribs[attrib.name] = attrib.value;\n }\n }\n\n if (attribs.style) {\n marker = attribs.style;\n delete attribs.style;\n }\n // dropping because presence of vid in para elements is not consistent in USX\n if (attribs.vid) delete attribs.vid;\n // Not dropping `attribs.closed` for backwards compatibility.\n // dropping because it is nonstandard derived metadata that could get out of date\n if (attribs.status) delete attribs.status;\n\n let outObj: T = { type } as T;\n if (marker) (outObj as MarkerObject).marker = marker;\n outObj = { ...outObj, ...attribs };\n\n if (\n inputUsxElement.firstChild &&\n inputUsxElement.firstChild.nodeType === inputUsxElement.firstChild.TEXT_NODE &&\n inputUsxElement.firstChild.nodeValue &&\n asciiTrim(inputUsxElement.firstChild.nodeValue) !== \"\"\n ) {\n text = inputUsxElement.firstChild.nodeValue;\n }\n\n const children = Array.from(inputUsxElement.childNodes);\n outObj.content = [];\n\n if (text) {\n outObj.content.push(text);\n }\n\n for (const child of children) {\n // ChildNodes are Elements.\n if ((child as Element).tagName === undefined) {\n continue;\n }\n // ChildNodes are Elements.\n const [childDict, whatToDo] = convertUsxRecurse<MarkerObject>(child as Element);\n\n switch (whatToDo) {\n case \"append\":\n outObj.content.push(childDict);\n break;\n case \"merge\":\n outObj.content = outObj.content.concat(childDict);\n break;\n case \"ignore\":\n break;\n default:\n break;\n }\n\n // Handle tail text\n if (\n child.nextSibling &&\n child.nextSibling.nodeType === child.nextSibling.TEXT_NODE &&\n child.nextSibling.nodeValue &&\n (asciiTrim(child.nextSibling.nodeValue) !== \"\" || child.nextSibling.nodeValue === \" \")\n ) {\n outObj.content.push(child.nextSibling.nodeValue);\n }\n }\n\n // For backward compatibility, not deleting content for type: chapter, verse, optbreak, ms OR\n // marker: va, ca, b.\n if (outObj.content.length === 0 && outObj.type !== USX_TYPE) {\n delete outObj.content;\n }\n\n if (\"eid\" in outObj && [\"verse\", \"chapter\"].includes(type)) {\n action = \"ignore\";\n }\n\n return [outObj, action];\n}\n\n/**\n * Removes leading and trailing ASCII whitespace.\n *\n * Only trim ASCII whitespace characters: space, tab, line feed, carriage return, form feed,\n * vertical tab.\n * @param str - The string to remove whitespace from.\n * @returns the string with leading and trailing whitespace removed.\n */\nfunction asciiTrim(str: string): string {\n return str.replace(/(^[ \\t\\n\\r\\f\\v]+)|([ \\t\\n\\r\\f\\v]+$)/g, \"\");\n}\n","/**\n * Convert Scripture from USJ to USX.\n * Adapted to TypeScript from this file:\n * @see https://github.com/usfm-bible/usfmtc/blob/0afa385a1f282b286cc6bff7bbc953ae788aa10c/src/usfmtc/usjproc.py\n */\n\nimport { DOMImplementation, Document, Element, Text } from \"@xmldom/xmldom\";\nimport { MarkerContent, MarkerObject, Usj } from \"./usj.model.js\";\nimport { USX_TYPE, USX_VERSION } from \"./usx.model.js\";\n\nlet chapterEid: string | undefined;\nlet verseEid: string | undefined;\n\n/**\n * Converts a USJ object to a USX string.\n *\n * @param usj - The USJ object to convert\n * @returns The converted USX string.\n *\n * @public\n */\nexport function usjToUsxString(usj: Usj): string {\n const usxDoc = new DOMImplementation().createDocument(\"\", USX_TYPE);\n if (usxDoc.documentElement) {\n usxDoc.documentElement.setAttribute(\"version\", USX_VERSION);\n usjToUsxDom(usj, usxDoc);\n }\n return usxDoc.toString();\n}\n\nexport function usjToUsxDom(usj: Usj, usxDoc: Document): Element | undefined {\n if (!usxDoc.documentElement) return undefined;\n\n for (const [index, markerContent] of usj.content.entries()) {\n const isLastItem = index === usj.content.length - 1;\n convertUsjRecurse(markerContent, usxDoc.documentElement, usxDoc, isLastItem);\n }\n return usxDoc.documentElement ?? undefined;\n}\n\nfunction convertUsjRecurse(\n markerContent: MarkerContent,\n parentElement: Element,\n usxDoc: Document,\n isLastItem: boolean,\n) {\n let element: Text | Element;\n let type: string | undefined;\n let eidElement: Element | undefined;\n if (typeof markerContent === \"string\") element = usxDoc.createTextNode(markerContent);\n else {\n type = markerContent.type.replace(\"table:\", \"\");\n element = usxDoc.createElement(type);\n setAttributes(element, markerContent);\n if (markerContent.content) {\n for (const [index, item] of markerContent.content.entries()) {\n const _isLastItem = index === markerContent.content.length - 1;\n convertUsjRecurse(item, element, usxDoc, _isLastItem);\n }\n }\n }\n\n // Create chapter and verse end elements from SID attributes.\n if (verseEid && (type === \"verse\" || (parentElement.tagName === \"para\" && isLastItem))) {\n eidElement = createVerseEndElement(usxDoc, verseEid);\n verseEid = undefined;\n }\n if (type === \"verse\" && typeof markerContent !== \"string\" && markerContent.sid !== undefined)\n verseEid = markerContent.sid;\n\n if (chapterEid && (type === \"chapter\" || (type === \"para\" && isLastItem))) {\n eidElement = createChapterEndElement(usxDoc, chapterEid);\n chapterEid = undefined;\n }\n if (type === \"chapter\" && typeof markerContent !== \"string\" && markerContent.sid !== undefined)\n chapterEid = markerContent.sid;\n\n // Append to parent.\n const isVerseInImpliedPara =\n parentElement.nodeName === USX_TYPE && eidElement?.tagName === \"verse\";\n if (eidElement && (!isLastItem || isVerseInImpliedPara)) parentElement.appendChild(eidElement);\n parentElement.appendChild(element);\n if (eidElement && isLastItem && !isVerseInImpliedPara) parentElement.appendChild(eidElement);\n\n // Allow for final chapter and verse end elements at the end of an implied para.\n if (isLastItem && parentElement.nodeName === USX_TYPE) {\n if (verseEid) parentElement.appendChild(createVerseEndElement(usxDoc, verseEid));\n if (chapterEid) parentElement.appendChild(createChapterEndElement(usxDoc, chapterEid));\n verseEid = undefined;\n chapterEid = undefined;\n }\n}\n\nfunction setAttributes(element: Element, markerContent: MarkerObject) {\n if (markerContent.marker) {\n if (markerContent.type === \"unmatched\") element.setAttribute(\"marker\", markerContent.marker);\n else element.setAttribute(\"style\", markerContent.marker);\n }\n for (const [key, value] of Object.entries(markerContent)) {\n if (value && ![\"type\", \"marker\", \"content\"].includes(key)) {\n element.setAttribute(key, value as string);\n }\n }\n}\n\nfunction createVerseEndElement(usxDoc: Document, verseEid: string): Element {\n const eidElement = usxDoc.createElement(\"verse\");\n eidElement.setAttribute(\"eid\", verseEid);\n return eidElement;\n}\n\nfunction createChapterEndElement(usxDoc: Document, chapterEid: string): Element {\n const eidElement = usxDoc.createElement(\"chapter\");\n eidElement.setAttribute(\"eid\", chapterEid);\n return eidElement;\n}\n","const JSON_PATH_START = \"$\";\nconst JSON_PATH_CONTENT = \".content[\";\n\n/**\n * Converts a USJ JSONPath string into an array of indexes.\n *\n * @param jsonPath - The USJ JSONPath string to convert. It must start with `$` and contain `.content[index]` segments.\n * @returns An array of numeric indexes extracted from the JSONPath.\n * @throws Will throw an error if the JSONPath does not start with `$`.\n *\n * @public\n */\nexport function indexesFromUsjJsonPath(jsonPath: string): number[] {\n const path = jsonPath.split(JSON_PATH_CONTENT);\n if (path.shift() !== JSON_PATH_START)\n throw new Error(`indexesFromJsonPath: jsonPath didn't start with '${JSON_PATH_START}'`);\n\n const indexes = path.map((str) => parseInt(str, 10));\n return indexes;\n}\n\n/**\n * Converts an array of indexes into a USJ JSONPath string.\n *\n * @param indexes - An array of numeric indexes to convert.\n * @returns A USJ JSONPath string constructed from the indexes.\n *\n * @public\n */\nexport function usjJsonPathFromIndexes(indexes: number[]): string {\n return indexes.reduce((path, index) => `${path}${JSON_PATH_CONTENT}${index}]`, JSON_PATH_START);\n}\n"],"names":["UNSAFE_KEYS","assertSafeKey","key","USJ_TYPE","USJ_VERSION","EMPTY_USJ","MARKER_OBJECT_PROPS","isValidBookCode","code","VALID_BOOK_CODES","USX_TYPE","USX_VERSION","EMPTY_USX","usxStringToUsj","usxString","inputUsxDom","DOMParser","usxDomToUsj","outputJson","convertUsxRecurse","inputUsxElement","attribs","type","marker","text","action","attrib","outObj","asciiTrim","children","child","childDict","whatToDo","str","chapterEid","verseEid","usjToUsxString","usj","usxDoc","DOMImplementation","usjToUsxDom","index","markerContent","isLastItem","convertUsjRecurse","parentElement","element","eidElement","setAttributes","item","_isLastItem","createVerseEndElement","createChapterEndElement","isVerseInImpliedPara","value","JSON_PATH_START","JSON_PATH_CONTENT","indexesFromUsjJsonPath","jsonPath","path","usjJsonPathFromIndexes","indexes"],"mappings":"kHAEMA,EAAc,IAAI,IAAI,CAAC,YAAa,YAAa,aAAa,CAAC,EAQ9D,SAASC,EAAcC,EAAmB,CAC/C,GAAKF,EAAY,IAAIE,CAAG,EAExB,MAAM,IAAI,MAAM,YAAYA,CAAG,gDAAgD,CACjF,CCJO,MAAMC,EAAW,MAMXC,EAAc,MAMdC,EAAY,OAAO,OAAY,CAAE,KAAMF,EAAU,QAASC,EAAa,QAAS,EAAC,CAAG,EAMpFE,EAA8C,CACzD,OACA,SACA,UACA,MACA,MACA,SACA,OACA,YACA,YACA,SACA,QACA,UACF,EA8DO,SAASC,EAAgBC,EAAuB,CACrD,OAAOC,EAAiB,SAASD,CAAgB,CACnD,CAYO,MAAMC,EAAmB,CAE9B,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,KACF,ECpOaC,EAAW,MAMXC,EAAc,MAMdC,EAAY,IAAIF,CAAQ,aAAaC,CAAW,OCEtD,SAASE,EAAeC,EAAwB,CAErD,MAAMC,EADS,IAAIC,YAAA,EACQ,gBAAgBF,EAAW,UAAU,EAChE,OAAOG,EAAYF,EAAY,eAAe,CAChD,CAEO,SAASE,EAAYF,EAAkC,CAC5D,KAAM,CAACG,CAAU,EAAIH,EACjBI,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"}
package/dist/index.d.ts CHANGED
@@ -1,95 +1,288 @@
1
- /** 3-letter Scripture book code */
2
- export declare type BookCode = (typeof VALID_BOOK_CODES)[number];
3
-
4
- export declare const EMPTY_USJ: Readonly<Usj>;
5
-
6
- export declare const EMPTY_USX = "<usx version=\"3.1\" />";
7
-
8
- /**
9
- * Converts a USJ JSONPath string into an array of indexes.
10
- *
11
- * @param jsonPath - The USJ JSONPath string to convert. It must start with `$` and contain `.content[index]` segments.
12
- * @returns An array of numeric indexes extracted from the JSONPath.
13
- * @throws Will throw an error if the JSONPath does not start with `$`.
14
- */
15
- export declare function indexesFromUsjJsonPath(jsonPath: string): number[];
16
-
17
- export declare function isValidBookCode(code: string): boolean;
18
-
19
- /** List of known properties of `MarkerObject` */
20
- export declare const MARKER_OBJECT_PROPS: (keyof MarkerObject)[];
21
-
22
- /** Single piece of Scripture content */
23
- export declare type MarkerContent = string | MarkerObject;
24
-
25
- /** A Scripture Marker and its contents */
26
- export declare interface MarkerObject {
27
- /**
28
- * The kind/category of node or element this is, corresponding the USFM marker and USX node
29
- * @example `para`, `verse`, `char`
30
- */
31
- type: string;
32
- /**
33
- * The corresponding marker in USFM or style in USX
34
- * @example `p`, `v`, `nd`
35
- */
36
- marker: string;
37
- /** This marker's contents laid out in order */
38
- content?: MarkerContent[];
39
- /** Indicates the Book-chapter-verse value in the paragraph based structure */
40
- sid?: string;
41
- /** Milestone end ID, matches start ID (not currently included in USJ spec) */
42
- eid?: string;
43
- /** Chapter number or verse number */
44
- number?: string;
45
- /** The 3-letter book code in ID element */
46
- code?: BookCode;
47
- /** Alternate chapter number or verse number */
48
- altnumber?: string;
49
- /** Published character of chapter or verse */
50
- pubnumber?: string;
51
- /** Caller character for footnotes and cross-refs */
52
- caller?: string;
53
- /** Alignment of table cells */
54
- align?: string;
55
- /** Category of extended study bible sections */
56
- category?: string;
57
- }
58
-
59
- /** Scripture data represented in JSON format. Data compatible transformation from USX/USFM */
60
- export declare interface Usj {
61
- /** The USJ spec type */
62
- type: typeof USJ_TYPE;
63
- /** The USJ spec version */
64
- version: typeof USJ_VERSION;
65
- /** The JSON representation of scripture contents from USFM/USX */
66
- content: MarkerContent[];
67
- }
68
-
69
- /** The USJ spec type */
70
- export declare const USJ_TYPE = "USJ";
71
-
72
- /** The USJ spec version */
73
- export declare const USJ_VERSION = "3.1";
74
-
75
- /**
76
- * Converts an array of indexes into a USJ JSONPath string.
77
- *
78
- * @param indexes - An array of numeric indexes to convert.
79
- * @returns A USJ JSONPath string constructed from the indexes.
80
- */
81
- export declare function usjJsonPathFromIndexes(indexes: number[]): string;
82
-
83
- export declare function usjToUsxString(usj: Usj): string;
84
-
85
- /** The USX spec type */
86
- export declare const USX_TYPE = "usx";
87
-
88
- /** The USX spec version */
89
- export declare const USX_VERSION = "3.1";
90
-
91
- export declare function usxStringToUsj(usxString: string): Usj;
92
-
93
- declare const VALID_BOOK_CODES: readonly ["GEN", "EXO", "LEV", "NUM", "DEU", "JOS", "JDG", "RUT", "1SA", "2SA", "1KI", "2KI", "1CH", "2CH", "EZR", "NEH", "EST", "JOB", "PSA", "PRO", "ECC", "SNG", "ISA", "JER", "LAM", "EZK", "DAN", "HOS", "JOL", "AMO", "OBA", "JON", "MIC", "NAM", "HAB", "ZEP", "HAG", "ZEC", "MAL", "MAT", "MRK", "LUK", "JHN", "ACT", "ROM", "1CO", "2CO", "GAL", "EPH", "PHP", "COL", "1TH", "2TH", "1TI", "2TI", "TIT", "PHM", "HEB", "JAS", "1PE", "2PE", "1JN", "2JN", "3JN", "JUD", "REV", "TOB", "JDT", "ESG", "WIS", "SIR", "BAR", "LJE", "S3Y", "SUS", "BEL", "1MA", "2MA", "3MA", "4MA", "1ES", "2ES", "MAN", "PS2", "ODA", "PSS", "EZA", "5EZ", "6EZ", "DAG", "PS3", "2BA", "LBA", "JUB", "ENO", "1MQ", "2MQ", "3MQ", "REP", "4BA", "LAO", "FRT", "BAK", "OTH", "INT", "CNC", "GLO", "TDX", "NDX", "XXA", "XXB", "XXC", "XXD", "XXE", "XXF", "XXG"];
94
-
95
- export { }
1
+ /**
2
+ * @packageDocumentation
3
+ * Utilities for Scripture data conversion and manipulation, including USJ/USX format conversion.
4
+ */
5
+
6
+ /**
7
+ * Avoid prototype pollution by disallowing unsafe keys.
8
+ * @param key - The array key to validate.
9
+ *
10
+ * @public
11
+ */
12
+ export declare function assertSafeKey(key: string): void;
13
+
14
+ /**
15
+ * 3-letter Scripture book code
16
+ * @public
17
+ */
18
+ export declare type BookCode = (typeof VALID_BOOK_CODES)[number];
19
+
20
+ /**
21
+ * An empty USJ object
22
+ * @public
23
+ */
24
+ export declare const EMPTY_USJ: Readonly<Usj>;
25
+
26
+ /**
27
+ * An empty USX string
28
+ * @public
29
+ */
30
+ export declare const EMPTY_USX = '<usx version="3.1" />';
31
+
32
+ /**
33
+ * Converts a USJ JSONPath string into an array of indexes.
34
+ *
35
+ * @param jsonPath - The USJ JSONPath string to convert. It must start with `$` and contain `.content[index]` segments.
36
+ * @returns An array of numeric indexes extracted from the JSONPath.
37
+ * @throws Will throw an error if the JSONPath does not start with `$`.
38
+ *
39
+ * @public
40
+ */
41
+ export declare function indexesFromUsjJsonPath(jsonPath: string): number[];
42
+
43
+ /**
44
+ * Check if the given code is a valid 3-letter Scripture book code.
45
+ * @public
46
+ */
47
+ export declare function isValidBookCode(code: string): boolean;
48
+
49
+ /**
50
+ * List of known properties of `MarkerObject`
51
+ * @public
52
+ */
53
+ export declare const MARKER_OBJECT_PROPS: (keyof MarkerObject)[];
54
+
55
+ /**
56
+ * Single piece of Scripture content
57
+ * @public
58
+ */
59
+ export declare type MarkerContent = string | MarkerObject;
60
+
61
+ /**
62
+ * A Scripture Marker and its contents
63
+ * @public
64
+ */
65
+ export declare interface MarkerObject {
66
+ /**
67
+ * The kind/category of node or element this is, corresponding the USFM marker and USX node
68
+ * @example `para`, `verse`, `char`
69
+ */
70
+ type: string;
71
+ /**
72
+ * The corresponding marker in USFM or style in USX
73
+ * @example `p`, `v`, `nd`
74
+ */
75
+ marker?: string;
76
+ /** This marker's contents laid out in order */
77
+ content?: MarkerContent[];
78
+ /** Indicates the Book-chapter-verse value in the paragraph based structure */
79
+ sid?: string;
80
+ /** Milestone end ID, matches start ID (not currently included in USJ spec) */
81
+ eid?: string;
82
+ /** Chapter number or verse number */
83
+ number?: string;
84
+ /** The 3-letter book code in ID element */
85
+ code?: BookCode;
86
+ /** Alternate chapter number or verse number */
87
+ altnumber?: string;
88
+ /** Published character of chapter or verse */
89
+ pubnumber?: string;
90
+ /** Caller character for footnotes and cross-refs */
91
+ caller?: string;
92
+ /** Alignment of table cells */
93
+ align?: string;
94
+ /** Category of extended study bible sections */
95
+ category?: string;
96
+ }
97
+
98
+ /**
99
+ * Scripture data represented in JSON format. Data compatible transformation from USX/USFM
100
+ * @public
101
+ */
102
+ export declare interface Usj {
103
+ /** The USJ spec type */
104
+ type: typeof USJ_TYPE;
105
+ /** The USJ spec version */
106
+ version: typeof USJ_VERSION;
107
+ /** The JSON representation of scripture contents from USFM/USX */
108
+ content: MarkerContent[];
109
+ }
110
+
111
+ /**
112
+ * The USJ spec type
113
+ * @public
114
+ */
115
+ export declare const USJ_TYPE = "USJ";
116
+
117
+ /**
118
+ * The USJ spec version
119
+ * @public
120
+ */
121
+ export declare const USJ_VERSION = "3.1";
122
+
123
+ /**
124
+ * Converts an array of indexes into a USJ JSONPath string.
125
+ *
126
+ * @param indexes - An array of numeric indexes to convert.
127
+ * @returns A USJ JSONPath string constructed from the indexes.
128
+ *
129
+ * @public
130
+ */
131
+ export declare function usjJsonPathFromIndexes(indexes: number[]): string;
132
+
133
+ /**
134
+ * Converts a USJ object to a USX string.
135
+ *
136
+ * @param usj - The USJ object to convert
137
+ * @returns The converted USX string.
138
+ *
139
+ * @public
140
+ */
141
+ export declare function usjToUsxString(usj: Usj): string;
142
+
143
+ /**
144
+ * The USX spec type
145
+ * @public
146
+ */
147
+ export declare const USX_TYPE = "usx";
148
+
149
+ /**
150
+ * The USX spec version
151
+ * @public
152
+ */
153
+ export declare const USX_VERSION = "3.1";
154
+
155
+ /**
156
+ * Converts a USX string to a USJ object.
157
+ *
158
+ * @param usxString - The USX string to convert.
159
+ * @returns The converted USJ object.
160
+ *
161
+ * @public
162
+ */
163
+ export declare function usxStringToUsj(usxString: string): Usj;
164
+
165
+ /**
166
+ * List of valid 3-letter Scripture book codes
167
+ * @public
168
+ */
169
+ export declare const VALID_BOOK_CODES: readonly [
170
+ "GEN",
171
+ "EXO",
172
+ "LEV",
173
+ "NUM",
174
+ "DEU",
175
+ "JOS",
176
+ "JDG",
177
+ "RUT",
178
+ "1SA",
179
+ "2SA",
180
+ "1KI",
181
+ "2KI",
182
+ "1CH",
183
+ "2CH",
184
+ "EZR",
185
+ "NEH",
186
+ "EST",
187
+ "JOB",
188
+ "PSA",
189
+ "PRO",
190
+ "ECC",
191
+ "SNG",
192
+ "ISA",
193
+ "JER",
194
+ "LAM",
195
+ "EZK",
196
+ "DAN",
197
+ "HOS",
198
+ "JOL",
199
+ "AMO",
200
+ "OBA",
201
+ "JON",
202
+ "MIC",
203
+ "NAM",
204
+ "HAB",
205
+ "ZEP",
206
+ "HAG",
207
+ "ZEC",
208
+ "MAL",
209
+ "MAT",
210
+ "MRK",
211
+ "LUK",
212
+ "JHN",
213
+ "ACT",
214
+ "ROM",
215
+ "1CO",
216
+ "2CO",
217
+ "GAL",
218
+ "EPH",
219
+ "PHP",
220
+ "COL",
221
+ "1TH",
222
+ "2TH",
223
+ "1TI",
224
+ "2TI",
225
+ "TIT",
226
+ "PHM",
227
+ "HEB",
228
+ "JAS",
229
+ "1PE",
230
+ "2PE",
231
+ "1JN",
232
+ "2JN",
233
+ "3JN",
234
+ "JUD",
235
+ "REV",
236
+ "TOB",
237
+ "JDT",
238
+ "ESG",
239
+ "WIS",
240
+ "SIR",
241
+ "BAR",
242
+ "LJE",
243
+ "S3Y",
244
+ "SUS",
245
+ "BEL",
246
+ "1MA",
247
+ "2MA",
248
+ "3MA",
249
+ "4MA",
250
+ "1ES",
251
+ "2ES",
252
+ "MAN",
253
+ "PS2",
254
+ "ODA",
255
+ "PSS",
256
+ "EZA",
257
+ "5EZ",
258
+ "6EZ",
259
+ "DAG",
260
+ "PS3",
261
+ "2BA",
262
+ "LBA",
263
+ "JUB",
264
+ "ENO",
265
+ "1MQ",
266
+ "2MQ",
267
+ "3MQ",
268
+ "REP",
269
+ "4BA",
270
+ "LAO",
271
+ "FRT",
272
+ "BAK",
273
+ "OTH",
274
+ "INT",
275
+ "CNC",
276
+ "GLO",
277
+ "TDX",
278
+ "NDX",
279
+ "XXA",
280
+ "XXB",
281
+ "XXC",
282
+ "XXD",
283
+ "XXE",
284
+ "XXF",
285
+ "XXG",
286
+ ];
287
+
288
+ export {};
package/dist/index.js CHANGED
@@ -1,5 +1,10 @@
1
1
  import { DOMParser as g, DOMImplementation as J } from "@xmldom/xmldom";
2
- const p = "USJ", O = "3.1", _ = Object.freeze({ type: p, version: O, content: [] }), B = [
2
+ const y = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
3
+ function m(e) {
4
+ if (y.has(e))
5
+ throw new Error(`The key "${e}" is not allowed to avoid prototype pollution.`);
6
+ }
7
+ const T = "USJ", O = "3.1", H = Object.freeze({ type: T, version: O, content: [] }), I = [
3
8
  "type",
4
9
  "marker",
5
10
  "content",
@@ -13,7 +18,7 @@ const p = "USJ", O = "3.1", _ = Object.freeze({ type: p, version: O, content: []
13
18
  "align",
14
19
  "category"
15
20
  ];
16
- function H(e) {
21
+ function V(e) {
17
22
  return M.includes(e);
18
23
  }
19
24
  const M = [
@@ -137,21 +142,21 @@ const M = [
137
142
  "XXE",
138
143
  "XXF",
139
144
  "XXG"
140
- ], l = "usx", N = "3.1", I = `<${l} version="${N}" />`;
141
- function C(e) {
145
+ ], l = "usx", N = "3.1", C = `<${l} version="${N}" />`;
146
+ function D(e) {
142
147
  const n = new g().parseFromString(e, "text/xml");
143
- return m(n.documentElement);
148
+ return X(n.documentElement);
144
149
  }
145
- function m(e) {
150
+ function X(e) {
146
151
  const [t] = e ? b(e) : [{ content: [] }];
147
- return t.type = p, t.version = O, t;
152
+ return t.type = T, t.version = O, t;
148
153
  }
149
154
  function b(e) {
150
155
  const t = {};
151
156
  let n = e.tagName, o, s, c = "append";
152
157
  if (["row", "cell"].includes(n) && (n = "table:" + n), e.attributes)
153
158
  for (const r of Array.from(e.attributes))
154
- t[r.name] = r.value;
159
+ m(r.name), t[r.name] = r.value;
155
160
  t.style && (o = t.style, delete t.style), t.vid && delete t.vid, t.status && delete t.status;
156
161
  let i = { type: n };
157
162
  o && (i.marker = o), i = { ...i, ...t }, e.firstChild && e.firstChild.nodeType === e.firstChild.TEXT_NODE && e.firstChild.nodeValue && E(e.firstChild.nodeValue) !== "" && (s = e.firstChild.nodeValue);
@@ -177,11 +182,11 @@ function E(e) {
177
182
  return e.replace(/(^[ \t\n\r\f\v]+)|([ \t\n\r\f\v]+$)/g, "");
178
183
  }
179
184
  let d, a;
180
- function V(e) {
185
+ function U(e) {
181
186
  const t = new J().createDocument("", l);
182
- return t.documentElement && (t.documentElement.setAttribute("version", N), y(e, t)), t.toString();
187
+ return t.documentElement && (t.documentElement.setAttribute("version", N), _(e, t)), t.toString();
183
188
  }
184
- function y(e, t) {
189
+ function _(e, t) {
185
190
  if (t.documentElement) {
186
191
  for (const [n, o] of e.content.entries()) {
187
192
  const s = n === e.content.length - 1;
@@ -193,21 +198,21 @@ function y(e, t) {
193
198
  function v(e, t, n, o) {
194
199
  let s, c, i;
195
200
  if (typeof e == "string") s = n.createTextNode(e);
196
- else if (c = e.type.replace("table:", ""), s = n.createElement(c), X(s, e), e.content)
201
+ else if (c = e.type.replace("table:", ""), s = n.createElement(c), R(s, e), e.content)
197
202
  for (const [r, u] of e.content.entries()) {
198
203
  const S = r === e.content.length - 1;
199
204
  v(u, s, n, S);
200
205
  }
201
- a && (c === "verse" || t.tagName === "para" && o) && (i = T(n, a), a = void 0), c === "verse" && typeof e != "string" && e.sid !== void 0 && (a = e.sid), d && (c === "chapter" || c === "para" && o) && (i = h(n, d), d = void 0), c === "chapter" && typeof e != "string" && e.sid !== void 0 && (d = e.sid);
206
+ a && (c === "verse" || t.tagName === "para" && o) && (i = p(n, a), a = void 0), c === "verse" && typeof e != "string" && e.sid !== void 0 && (a = e.sid), d && (c === "chapter" || c === "para" && o) && (i = h(n, d), d = void 0), c === "chapter" && typeof e != "string" && e.sid !== void 0 && (d = e.sid);
202
207
  const f = t.nodeName === l && (i == null ? void 0 : i.tagName) === "verse";
203
- i && (!o || f) && t.appendChild(i), t.appendChild(s), i && o && !f && t.appendChild(i), o && t.nodeName === l && (a && t.appendChild(T(n, a)), d && t.appendChild(h(n, d)), a = void 0, d = void 0);
208
+ i && (!o || f) && t.appendChild(i), t.appendChild(s), i && o && !f && t.appendChild(i), o && t.nodeName === l && (a && t.appendChild(p(n, a)), d && t.appendChild(h(n, d)), a = void 0, d = void 0);
204
209
  }
205
- function X(e, t) {
206
- t.type === "unmatched" ? e.setAttribute("marker", t.marker) : e.setAttribute("style", t.marker);
210
+ function R(e, t) {
211
+ t.marker && (t.type === "unmatched" ? e.setAttribute("marker", t.marker) : e.setAttribute("style", t.marker));
207
212
  for (const [n, o] of Object.entries(t))
208
213
  o && !["type", "marker", "content"].includes(n) && e.setAttribute(n, o);
209
214
  }
210
- function T(e, t) {
215
+ function p(e, t) {
211
216
  const n = e.createElement("verse");
212
217
  return n.setAttribute("eid", t), n;
213
218
  }
@@ -216,27 +221,29 @@ function h(e, t) {
216
221
  return n.setAttribute("eid", t), n;
217
222
  }
218
223
  const A = "$", P = ".content[";
219
- function D(e) {
224
+ function w(e) {
220
225
  const t = e.split(P);
221
226
  if (t.shift() !== A)
222
227
  throw new Error(`indexesFromJsonPath: jsonPath didn't start with '${A}'`);
223
228
  return t.map((o) => parseInt(o, 10));
224
229
  }
225
- function U(e) {
230
+ function L(e) {
226
231
  return e.reduce((t, n) => `${t}${P}${n}]`, A);
227
232
  }
228
233
  export {
229
- _ as EMPTY_USJ,
230
- I as EMPTY_USX,
231
- B as MARKER_OBJECT_PROPS,
232
- p as USJ_TYPE,
234
+ H as EMPTY_USJ,
235
+ C as EMPTY_USX,
236
+ I as MARKER_OBJECT_PROPS,
237
+ T as USJ_TYPE,
233
238
  O as USJ_VERSION,
234
239
  l as USX_TYPE,
235
240
  N as USX_VERSION,
236
- D as indexesFromUsjJsonPath,
237
- H as isValidBookCode,
238
- U as usjJsonPathFromIndexes,
239
- V as usjToUsxString,
240
- C as usxStringToUsj
241
+ M as VALID_BOOK_CODES,
242
+ m as assertSafeKey,
243
+ w as indexesFromUsjJsonPath,
244
+ V as isValidBookCode,
245
+ L as usjJsonPathFromIndexes,
246
+ U as usjToUsxString,
247
+ D as usxStringToUsj
241
248
  };
242
249
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/converters/usj/usj.model.ts","../src/converters/usj/usx.model.ts","../src/converters/usj/usx-to-usj.ts","../src/converters/usj/usj-to-usx.ts","../src/converters/usj/jsonpath-indexes.ts"],"sourcesContent":["/**\n * Unified Scripture JSON (USJ) - The JSON variant of USFM and USX data models.\n * These types follow this schema:\n * @see https://github.com/usfm-bible/tcdocs/blob/usj/grammar/usj.js\n */\n\n/** The USJ spec type */\nexport const USJ_TYPE = \"USJ\";\n\n/** The USJ spec version */\nexport const USJ_VERSION = \"3.1\";\n\nexport const EMPTY_USJ = Object.freeze<Usj>({ type: USJ_TYPE, version: USJ_VERSION, content: [] });\n\n/** List of known properties of `MarkerObject` */\nexport const MARKER_OBJECT_PROPS: (keyof MarkerObject)[] = [\n \"type\",\n \"marker\",\n \"content\",\n \"sid\",\n \"eid\",\n \"number\",\n \"code\",\n \"altnumber\",\n \"pubnumber\",\n \"caller\",\n \"align\",\n \"category\",\n];\n\n/** Single piece of Scripture content */\nexport type MarkerContent = string | MarkerObject;\n\n/** A Scripture Marker and its contents */\nexport interface MarkerObject {\n /**\n * The kind/category of node or element this is, corresponding the USFM marker and USX node\n * @example `para`, `verse`, `char`\n */\n type: string;\n /**\n * The corresponding marker in USFM or style in USX\n * @example `p`, `v`, `nd`\n */\n marker: string;\n /** This marker's contents laid out in order */\n content?: MarkerContent[];\n /** Indicates the Book-chapter-verse value in the paragraph based structure */\n sid?: string;\n /** Milestone end ID, matches start ID (not currently included in USJ spec) */\n eid?: string;\n /** Chapter number or verse number */\n number?: string;\n /** The 3-letter book code in ID element */\n code?: BookCode;\n /** Alternate chapter number or verse number */\n altnumber?: string;\n /** Published character of chapter or verse */\n pubnumber?: string;\n /** Caller character for footnotes and cross-refs */\n caller?: string;\n /** Alignment of table cells */\n align?: string;\n /** Category of extended study bible sections */\n category?: string;\n}\n\n/** Scripture data represented in JSON format. Data compatible transformation from USX/USFM */\nexport interface Usj {\n /** The USJ spec type */\n type: typeof USJ_TYPE;\n /** The USJ spec version */\n version: typeof USJ_VERSION;\n /** The JSON representation of scripture contents from USFM/USX */\n content: MarkerContent[];\n}\n\nexport function isValidBookCode(code: string): boolean {\n return VALID_BOOK_CODES.includes(code as BookCode);\n}\n\n/** 3-letter Scripture book code */\nexport type BookCode = (typeof VALID_BOOK_CODES)[number];\n\nconst VALID_BOOK_CODES = [\n // Old Testament\n \"GEN\",\n \"EXO\",\n \"LEV\",\n \"NUM\",\n \"DEU\",\n \"JOS\",\n \"JDG\",\n \"RUT\",\n \"1SA\",\n \"2SA\",\n \"1KI\",\n \"2KI\",\n \"1CH\",\n \"2CH\",\n \"EZR\",\n \"NEH\",\n \"EST\",\n \"JOB\",\n \"PSA\",\n \"PRO\",\n \"ECC\",\n \"SNG\",\n \"ISA\",\n \"JER\",\n \"LAM\",\n \"EZK\",\n \"DAN\",\n \"HOS\",\n \"JOL\",\n \"AMO\",\n \"OBA\",\n \"JON\",\n \"MIC\",\n \"NAM\",\n \"HAB\",\n \"ZEP\",\n \"HAG\",\n \"ZEC\",\n \"MAL\",\n // New Testament\n \"MAT\",\n \"MRK\",\n \"LUK\",\n \"JHN\",\n \"ACT\",\n \"ROM\",\n \"1CO\",\n \"2CO\",\n \"GAL\",\n \"EPH\",\n \"PHP\",\n \"COL\",\n \"1TH\",\n \"2TH\",\n \"1TI\",\n \"2TI\",\n \"TIT\",\n \"PHM\",\n \"HEB\",\n \"JAS\",\n \"1PE\",\n \"2PE\",\n \"1JN\",\n \"2JN\",\n \"3JN\",\n \"JUD\",\n \"REV\",\n // Deuterocanon\n \"TOB\",\n \"JDT\",\n \"ESG\",\n \"WIS\",\n \"SIR\",\n \"BAR\",\n \"LJE\",\n \"S3Y\",\n \"SUS\",\n \"BEL\",\n \"1MA\",\n \"2MA\",\n \"3MA\",\n \"4MA\",\n \"1ES\",\n \"2ES\",\n \"MAN\",\n \"PS2\",\n \"ODA\",\n \"PSS\",\n \"EZA\",\n \"5EZ\",\n \"6EZ\",\n \"DAG\",\n \"PS3\",\n \"2BA\",\n \"LBA\",\n \"JUB\",\n \"ENO\",\n \"1MQ\",\n \"2MQ\",\n \"3MQ\",\n \"REP\",\n \"4BA\",\n \"LAO\",\n // Non scripture\n \"FRT\",\n \"BAK\",\n \"OTH\",\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n \"NDX\",\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n] as const;\n","/**\n * Unified Scripture XML (USX).\n * These types follow this schema:\n * @see https://github.com/usfm-bible/tcdocs/blob/main/grammar/usx.rng\n */\n\n/** The USX spec type */\nexport const USX_TYPE = \"usx\";\n\n/** The USX spec version */\nexport const USX_VERSION = \"3.1\";\n\nexport const EMPTY_USX = `<${USX_TYPE} version=\"${USX_VERSION}\" />`;\n","/**\n * Convert Scripture from USX to USJ.\n * Adapted to TypeScript from this file:\n * @see https://github.com/usfm-bible/usfmtc/blob/0afa385a1f282b286cc6bff7bbc953ae788aa10c/src/usfmtc/usjproc.py\n */\n\nimport { DOMParser, Element } from \"@xmldom/xmldom\";\nimport { MarkerContent, MarkerObject, USJ_TYPE, USJ_VERSION, Usj } from \"./usj.model.js\";\nimport { USX_TYPE } from \"./usx.model.js\";\n\ntype Action = \"append\" | \"merge\" | \"ignore\";\ninterface Attribs {\n [name: string]: string;\n}\n\nexport function usxStringToUsj(usxString: string): Usj {\n const parser = new DOMParser();\n const inputUsxDom = parser.parseFromString(usxString, \"text/xml\");\n return usxDomToUsj(inputUsxDom.documentElement);\n}\n\nexport function usxDomToUsj(inputUsxDom: Element | null): Usj {\n const [outputJson] = inputUsxDom\n ? convertUsxRecurse(inputUsxDom)\n : [{ content: [] as MarkerContent[] } as Usj];\n outputJson.type = USJ_TYPE;\n outputJson.version = USJ_VERSION;\n return outputJson;\n}\n\nfunction convertUsxRecurse<T extends Usj | MarkerObject = Usj>(\n inputUsxElement: Element,\n): [outputJson: T, action: Action] {\n const attribs: Attribs = {};\n let type: string = inputUsxElement.tagName;\n let marker: string | undefined;\n let text: string | undefined;\n let action: Action = \"append\";\n\n if ([\"row\", \"cell\"].includes(type)) type = \"table:\" + type;\n if (inputUsxElement.attributes) {\n for (const attrib of Array.from(inputUsxElement.attributes)) {\n attribs[attrib.name] = attrib.value;\n }\n }\n\n if (attribs.style) {\n marker = attribs.style;\n delete attribs.style;\n }\n // dropping because presence of vid in para elements is not consistent in USX\n if (attribs.vid) delete attribs.vid;\n // Not dropping `attribs.closed` for backwards compatibility.\n // dropping because it is nonstandard derived metadata that could get out of date\n if (attribs.status) delete attribs.status;\n\n let outObj: T = { type } as T;\n if (marker) (outObj as MarkerObject).marker = marker;\n outObj = { ...outObj, ...attribs };\n\n if (\n inputUsxElement.firstChild &&\n inputUsxElement.firstChild.nodeType === inputUsxElement.firstChild.TEXT_NODE &&\n inputUsxElement.firstChild.nodeValue &&\n asciiTrim(inputUsxElement.firstChild.nodeValue) !== \"\"\n ) {\n text = inputUsxElement.firstChild.nodeValue;\n }\n\n const children = Array.from(inputUsxElement.childNodes);\n outObj.content = [];\n\n if (text) {\n outObj.content.push(text);\n }\n\n for (const child of children) {\n // ChildNodes are Elements.\n if ((child as Element).tagName === undefined) {\n continue;\n }\n // ChildNodes are Elements.\n const [childDict, whatToDo] = convertUsxRecurse<MarkerObject>(child as Element);\n\n switch (whatToDo) {\n case \"append\":\n outObj.content.push(childDict);\n break;\n case \"merge\":\n outObj.content = outObj.content.concat(childDict);\n break;\n case \"ignore\":\n break;\n default:\n break;\n }\n\n // Handle tail text\n if (\n child.nextSibling &&\n child.nextSibling.nodeType === child.nextSibling.TEXT_NODE &&\n child.nextSibling.nodeValue &&\n (asciiTrim(child.nextSibling.nodeValue) !== \"\" || child.nextSibling.nodeValue === \" \")\n ) {\n outObj.content.push(child.nextSibling.nodeValue);\n }\n }\n\n // For backward compatibility, not deleting content for type: chapter, verse, optbreak, ms OR\n // marker: va, ca, b.\n if (outObj.content.length === 0 && outObj.type !== USX_TYPE) {\n delete outObj.content;\n }\n\n if (\"eid\" in outObj && [\"verse\", \"chapter\"].includes(type)) {\n action = \"ignore\";\n }\n\n return [outObj, action];\n}\n\n/**\n * Removes leading and trailing ASCII whitespace.\n *\n * Only trim ASCII whitespace characters: space, tab, line feed, carriage return, form feed,\n * vertical tab.\n * @param str - The string to remove whitespace from.\n * @returns the string with leading and trailing whitespace removed.\n */\nfunction asciiTrim(str: string): string {\n return str.replace(/(^[ \\t\\n\\r\\f\\v]+)|([ \\t\\n\\r\\f\\v]+$)/g, \"\");\n}\n","/**\n * Convert Scripture from USJ to USX.\n * Adapted to TypeScript from this file:\n * @see https://github.com/usfm-bible/usfmtc/blob/0afa385a1f282b286cc6bff7bbc953ae788aa10c/src/usfmtc/usjproc.py\n */\n\nimport { DOMImplementation, Document, Element, Text } from \"@xmldom/xmldom\";\nimport { MarkerContent, MarkerObject, Usj } from \"./usj.model.js\";\nimport { USX_TYPE, USX_VERSION } from \"./usx.model.js\";\n\nlet chapterEid: string | undefined;\nlet verseEid: string | undefined;\n\nexport function usjToUsxString(usj: Usj): string {\n const usxDoc = new DOMImplementation().createDocument(\"\", USX_TYPE);\n if (usxDoc.documentElement) {\n usxDoc.documentElement.setAttribute(\"version\", USX_VERSION);\n usjToUsxDom(usj, usxDoc);\n }\n return usxDoc.toString();\n}\n\nexport function usjToUsxDom(usj: Usj, usxDoc: Document): Element | undefined {\n if (!usxDoc.documentElement) return undefined;\n\n for (const [index, markerContent] of usj.content.entries()) {\n const isLastItem = index === usj.content.length - 1;\n convertUsjRecurse(markerContent, usxDoc.documentElement, usxDoc, isLastItem);\n }\n return usxDoc.documentElement ?? undefined;\n}\n\nfunction convertUsjRecurse(\n markerContent: MarkerContent,\n parentElement: Element,\n usxDoc: Document,\n isLastItem: boolean,\n) {\n let element: Text | Element;\n let type: string | undefined;\n let eidElement: Element | undefined;\n if (typeof markerContent === \"string\") element = usxDoc.createTextNode(markerContent);\n else {\n type = markerContent.type.replace(\"table:\", \"\");\n element = usxDoc.createElement(type);\n setAttributes(element, markerContent);\n if (markerContent.content) {\n for (const [index, item] of markerContent.content.entries()) {\n const _isLastItem = index === markerContent.content.length - 1;\n convertUsjRecurse(item, element, usxDoc, _isLastItem);\n }\n }\n }\n\n // Create chapter and verse end elements from SID attributes.\n if (verseEid && (type === \"verse\" || (parentElement.tagName === \"para\" && isLastItem))) {\n eidElement = createVerseEndElement(usxDoc, verseEid);\n verseEid = undefined;\n }\n if (type === \"verse\" && typeof markerContent !== \"string\" && markerContent.sid !== undefined)\n verseEid = markerContent.sid;\n\n if (chapterEid && (type === \"chapter\" || (type === \"para\" && isLastItem))) {\n eidElement = createChapterEndElement(usxDoc, chapterEid);\n chapterEid = undefined;\n }\n if (type === \"chapter\" && typeof markerContent !== \"string\" && markerContent.sid !== undefined)\n chapterEid = markerContent.sid;\n\n // Append to parent.\n const isVerseInImpliedPara =\n parentElement.nodeName === USX_TYPE && eidElement?.tagName === \"verse\";\n if (eidElement && (!isLastItem || isVerseInImpliedPara)) parentElement.appendChild(eidElement);\n parentElement.appendChild(element);\n if (eidElement && isLastItem && !isVerseInImpliedPara) parentElement.appendChild(eidElement);\n\n // Allow for final chapter and verse end elements at the end of an implied para.\n if (isLastItem && parentElement.nodeName === USX_TYPE) {\n if (verseEid) parentElement.appendChild(createVerseEndElement(usxDoc, verseEid));\n if (chapterEid) parentElement.appendChild(createChapterEndElement(usxDoc, chapterEid));\n verseEid = undefined;\n chapterEid = undefined;\n }\n}\n\nfunction setAttributes(element: Element, markerContent: MarkerObject) {\n if (markerContent.type === \"unmatched\") element.setAttribute(\"marker\", markerContent.marker);\n else element.setAttribute(\"style\", markerContent.marker);\n for (const [key, value] of Object.entries(markerContent)) {\n if (value && ![\"type\", \"marker\", \"content\"].includes(key)) {\n element.setAttribute(key, value as string);\n }\n }\n}\n\nfunction createVerseEndElement(usxDoc: Document, verseEid: string): Element {\n const eidElement = usxDoc.createElement(\"verse\");\n eidElement.setAttribute(\"eid\", verseEid);\n return eidElement;\n}\n\nfunction createChapterEndElement(usxDoc: Document, chapterEid: string): Element {\n const eidElement = usxDoc.createElement(\"chapter\");\n eidElement.setAttribute(\"eid\", chapterEid);\n return eidElement;\n}\n","const JSON_PATH_START = \"$\";\nconst JSON_PATH_CONTENT = \".content[\";\n\n/**\n * Converts a USJ JSONPath string into an array of indexes.\n *\n * @param jsonPath - The USJ JSONPath string to convert. It must start with `$` and contain `.content[index]` segments.\n * @returns An array of numeric indexes extracted from the JSONPath.\n * @throws Will throw an error if the JSONPath does not start with `$`.\n */\nexport function indexesFromUsjJsonPath(jsonPath: string): number[] {\n const path = jsonPath.split(JSON_PATH_CONTENT);\n if (path.shift() !== JSON_PATH_START)\n throw new Error(`indexesFromJsonPath: jsonPath didn't start with '${JSON_PATH_START}'`);\n\n const indexes = path.map((str) => parseInt(str, 10));\n return indexes;\n}\n\n/**\n * Converts an array of indexes into a USJ JSONPath string.\n *\n * @param indexes - An array of numeric indexes to convert.\n * @returns A USJ JSONPath string constructed from the indexes.\n */\nexport function usjJsonPathFromIndexes(indexes: number[]): string {\n return indexes.reduce((path, index) => `${path}${JSON_PATH_CONTENT}${index}]`, JSON_PATH_START);\n}\n"],"names":["USJ_TYPE","USJ_VERSION","EMPTY_USJ","MARKER_OBJECT_PROPS","isValidBookCode","code","VALID_BOOK_CODES","USX_TYPE","USX_VERSION","EMPTY_USX","usxStringToUsj","usxString","inputUsxDom","DOMParser","usxDomToUsj","outputJson","convertUsxRecurse","inputUsxElement","attribs","type","marker","text","action","attrib","outObj","asciiTrim","children","child","childDict","whatToDo","str","chapterEid","verseEid","usjToUsxString","usj","usxDoc","DOMImplementation","usjToUsxDom","index","markerContent","isLastItem","convertUsjRecurse","parentElement","element","eidElement","setAttributes","item","_isLastItem","createVerseEndElement","createChapterEndElement","isVerseInImpliedPara","key","value","JSON_PATH_START","JSON_PATH_CONTENT","indexesFromUsjJsonPath","jsonPath","path","usjJsonPathFromIndexes","indexes"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/converters/usj/converter.utils.ts","../src/converters/usj/usj.model.ts","../src/converters/usj/usx.model.ts","../src/converters/usj/usx-to-usj.ts","../src/converters/usj/usj-to-usx.ts","../src/converters/usj/jsonpath-indexes.ts"],"sourcesContent":["/* Utility functions for converters */\n\nconst UNSAFE_KEYS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\n\n/**\n * Avoid prototype pollution by disallowing unsafe keys.\n * @param key - The array key to validate.\n *\n * @public\n */\nexport function assertSafeKey(key: string): void {\n if (!UNSAFE_KEYS.has(key)) return;\n\n throw new Error(`The key \"${key}\" is not allowed to avoid prototype pollution.`);\n}\n","/**\n * Unified Scripture JSON (USJ) - The JSON variant of USFM and USX data models.\n * These types follow this schema:\n * @see https://github.com/usfm-bible/tcdocs/blob/usj/grammar/usj.js\n */\n\n/**\n * The USJ spec type\n * @public\n */\nexport const USJ_TYPE = \"USJ\";\n\n/**\n * The USJ spec version\n * @public\n */\nexport const USJ_VERSION = \"3.1\";\n\n/**\n * An empty USJ object\n * @public\n */\nexport const EMPTY_USJ = Object.freeze<Usj>({ type: USJ_TYPE, version: USJ_VERSION, content: [] });\n\n/**\n * List of known properties of `MarkerObject`\n * @public\n */\nexport const MARKER_OBJECT_PROPS: (keyof MarkerObject)[] = [\n \"type\",\n \"marker\",\n \"content\",\n \"sid\",\n \"eid\",\n \"number\",\n \"code\",\n \"altnumber\",\n \"pubnumber\",\n \"caller\",\n \"align\",\n \"category\",\n];\n\n/**\n * Single piece of Scripture content\n * @public\n */\nexport type MarkerContent = string | MarkerObject;\n\n/**\n * A Scripture Marker and its contents\n * @public\n */\nexport interface MarkerObject {\n /**\n * The kind/category of node or element this is, corresponding the USFM marker and USX node\n * @example `para`, `verse`, `char`\n */\n type: string;\n /**\n * The corresponding marker in USFM or style in USX\n * @example `p`, `v`, `nd`\n */\n marker?: string;\n /** This marker's contents laid out in order */\n content?: MarkerContent[];\n /** Indicates the Book-chapter-verse value in the paragraph based structure */\n sid?: string;\n /** Milestone end ID, matches start ID (not currently included in USJ spec) */\n eid?: string;\n /** Chapter number or verse number */\n number?: string;\n /** The 3-letter book code in ID element */\n code?: BookCode;\n /** Alternate chapter number or verse number */\n altnumber?: string;\n /** Published character of chapter or verse */\n pubnumber?: string;\n /** Caller character for footnotes and cross-refs */\n caller?: string;\n /** Alignment of table cells */\n align?: string;\n /** Category of extended study bible sections */\n category?: string;\n}\n\n/**\n * Scripture data represented in JSON format. Data compatible transformation from USX/USFM\n * @public\n */\nexport interface Usj {\n /** The USJ spec type */\n type: typeof USJ_TYPE;\n /** The USJ spec version */\n version: typeof USJ_VERSION;\n /** The JSON representation of scripture contents from USFM/USX */\n content: MarkerContent[];\n}\n\n/**\n * Check if the given code is a valid 3-letter Scripture book code.\n * @public\n */\nexport function isValidBookCode(code: string): boolean {\n return VALID_BOOK_CODES.includes(code as BookCode);\n}\n\n/**\n * 3-letter Scripture book code\n * @public\n */\nexport type BookCode = (typeof VALID_BOOK_CODES)[number];\n\n/**\n * List of valid 3-letter Scripture book codes\n * @public\n */\nexport const VALID_BOOK_CODES = [\n // Old Testament\n \"GEN\",\n \"EXO\",\n \"LEV\",\n \"NUM\",\n \"DEU\",\n \"JOS\",\n \"JDG\",\n \"RUT\",\n \"1SA\",\n \"2SA\",\n \"1KI\",\n \"2KI\",\n \"1CH\",\n \"2CH\",\n \"EZR\",\n \"NEH\",\n \"EST\",\n \"JOB\",\n \"PSA\",\n \"PRO\",\n \"ECC\",\n \"SNG\",\n \"ISA\",\n \"JER\",\n \"LAM\",\n \"EZK\",\n \"DAN\",\n \"HOS\",\n \"JOL\",\n \"AMO\",\n \"OBA\",\n \"JON\",\n \"MIC\",\n \"NAM\",\n \"HAB\",\n \"ZEP\",\n \"HAG\",\n \"ZEC\",\n \"MAL\",\n // New Testament\n \"MAT\",\n \"MRK\",\n \"LUK\",\n \"JHN\",\n \"ACT\",\n \"ROM\",\n \"1CO\",\n \"2CO\",\n \"GAL\",\n \"EPH\",\n \"PHP\",\n \"COL\",\n \"1TH\",\n \"2TH\",\n \"1TI\",\n \"2TI\",\n \"TIT\",\n \"PHM\",\n \"HEB\",\n \"JAS\",\n \"1PE\",\n \"2PE\",\n \"1JN\",\n \"2JN\",\n \"3JN\",\n \"JUD\",\n \"REV\",\n // Deuterocanon\n \"TOB\",\n \"JDT\",\n \"ESG\",\n \"WIS\",\n \"SIR\",\n \"BAR\",\n \"LJE\",\n \"S3Y\",\n \"SUS\",\n \"BEL\",\n \"1MA\",\n \"2MA\",\n \"3MA\",\n \"4MA\",\n \"1ES\",\n \"2ES\",\n \"MAN\",\n \"PS2\",\n \"ODA\",\n \"PSS\",\n \"EZA\",\n \"5EZ\",\n \"6EZ\",\n \"DAG\",\n \"PS3\",\n \"2BA\",\n \"LBA\",\n \"JUB\",\n \"ENO\",\n \"1MQ\",\n \"2MQ\",\n \"3MQ\",\n \"REP\",\n \"4BA\",\n \"LAO\",\n // Non scripture\n \"FRT\",\n \"BAK\",\n \"OTH\",\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n \"NDX\",\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n] as const;\n","/**\n * Unified Scripture XML (USX).\n * These types follow this schema:\n * @see https://github.com/usfm-bible/tcdocs/blob/main/grammar/usx.rng\n */\n\n/**\n * The USX spec type\n * @public\n */\nexport const USX_TYPE = \"usx\";\n\n/**\n * The USX spec version\n * @public\n */\nexport const USX_VERSION = \"3.1\";\n\n/**\n * An empty USX string\n * @public\n */\nexport const EMPTY_USX = `<${USX_TYPE} version=\"${USX_VERSION}\" />`;\n","/**\n * Convert Scripture from USX to USJ.\n * Adapted to TypeScript from this file:\n * @see https://github.com/usfm-bible/usfmtc/blob/0afa385a1f282b286cc6bff7bbc953ae788aa10c/src/usfmtc/usjproc.py\n */\n\nimport { DOMParser, Element } from \"@xmldom/xmldom\";\nimport { MarkerContent, MarkerObject, USJ_TYPE, USJ_VERSION, Usj } from \"./usj.model.js\";\nimport { USX_TYPE } from \"./usx.model.js\";\nimport { assertSafeKey } from \"./converter.utils.js\";\n\ntype Action = \"append\" | \"merge\" | \"ignore\";\ninterface Attribs {\n [name: string]: string;\n}\n\n/**\n * Converts a USX string to a USJ object.\n *\n * @param usxString - The USX string to convert.\n * @returns The converted USJ object.\n *\n * @public\n */\nexport function usxStringToUsj(usxString: string): Usj {\n const parser = new DOMParser();\n const inputUsxDom = parser.parseFromString(usxString, \"text/xml\");\n return usxDomToUsj(inputUsxDom.documentElement);\n}\n\nexport function usxDomToUsj(inputUsxDom: Element | null): Usj {\n const [outputJson] = inputUsxDom\n ? convertUsxRecurse(inputUsxDom)\n : [{ content: [] as MarkerContent[] } as Usj];\n outputJson.type = USJ_TYPE;\n outputJson.version = USJ_VERSION;\n return outputJson;\n}\n\nfunction convertUsxRecurse<T extends Usj | MarkerObject = Usj>(\n inputUsxElement: Element,\n): [outputJson: T, action: Action] {\n const attribs: Attribs = {};\n let type: string = inputUsxElement.tagName;\n let marker: string | undefined;\n let text: string | undefined;\n let action: Action = \"append\";\n\n if ([\"row\", \"cell\"].includes(type)) type = \"table:\" + type;\n if (inputUsxElement.attributes) {\n for (const attrib of Array.from(inputUsxElement.attributes)) {\n assertSafeKey(attrib.name);\n attribs[attrib.name] = attrib.value;\n }\n }\n\n if (attribs.style) {\n marker = attribs.style;\n delete attribs.style;\n }\n // dropping because presence of vid in para elements is not consistent in USX\n if (attribs.vid) delete attribs.vid;\n // Not dropping `attribs.closed` for backwards compatibility.\n // dropping because it is nonstandard derived metadata that could get out of date\n if (attribs.status) delete attribs.status;\n\n let outObj: T = { type } as T;\n if (marker) (outObj as MarkerObject).marker = marker;\n outObj = { ...outObj, ...attribs };\n\n if (\n inputUsxElement.firstChild &&\n inputUsxElement.firstChild.nodeType === inputUsxElement.firstChild.TEXT_NODE &&\n inputUsxElement.firstChild.nodeValue &&\n asciiTrim(inputUsxElement.firstChild.nodeValue) !== \"\"\n ) {\n text = inputUsxElement.firstChild.nodeValue;\n }\n\n const children = Array.from(inputUsxElement.childNodes);\n outObj.content = [];\n\n if (text) {\n outObj.content.push(text);\n }\n\n for (const child of children) {\n // ChildNodes are Elements.\n if ((child as Element).tagName === undefined) {\n continue;\n }\n // ChildNodes are Elements.\n const [childDict, whatToDo] = convertUsxRecurse<MarkerObject>(child as Element);\n\n switch (whatToDo) {\n case \"append\":\n outObj.content.push(childDict);\n break;\n case \"merge\":\n outObj.content = outObj.content.concat(childDict);\n break;\n case \"ignore\":\n break;\n default:\n break;\n }\n\n // Handle tail text\n if (\n child.nextSibling &&\n child.nextSibling.nodeType === child.nextSibling.TEXT_NODE &&\n child.nextSibling.nodeValue &&\n (asciiTrim(child.nextSibling.nodeValue) !== \"\" || child.nextSibling.nodeValue === \" \")\n ) {\n outObj.content.push(child.nextSibling.nodeValue);\n }\n }\n\n // For backward compatibility, not deleting content for type: chapter, verse, optbreak, ms OR\n // marker: va, ca, b.\n if (outObj.content.length === 0 && outObj.type !== USX_TYPE) {\n delete outObj.content;\n }\n\n if (\"eid\" in outObj && [\"verse\", \"chapter\"].includes(type)) {\n action = \"ignore\";\n }\n\n return [outObj, action];\n}\n\n/**\n * Removes leading and trailing ASCII whitespace.\n *\n * Only trim ASCII whitespace characters: space, tab, line feed, carriage return, form feed,\n * vertical tab.\n * @param str - The string to remove whitespace from.\n * @returns the string with leading and trailing whitespace removed.\n */\nfunction asciiTrim(str: string): string {\n return str.replace(/(^[ \\t\\n\\r\\f\\v]+)|([ \\t\\n\\r\\f\\v]+$)/g, \"\");\n}\n","/**\n * Convert Scripture from USJ to USX.\n * Adapted to TypeScript from this file:\n * @see https://github.com/usfm-bible/usfmtc/blob/0afa385a1f282b286cc6bff7bbc953ae788aa10c/src/usfmtc/usjproc.py\n */\n\nimport { DOMImplementation, Document, Element, Text } from \"@xmldom/xmldom\";\nimport { MarkerContent, MarkerObject, Usj } from \"./usj.model.js\";\nimport { USX_TYPE, USX_VERSION } from \"./usx.model.js\";\n\nlet chapterEid: string | undefined;\nlet verseEid: string | undefined;\n\n/**\n * Converts a USJ object to a USX string.\n *\n * @param usj - The USJ object to convert\n * @returns The converted USX string.\n *\n * @public\n */\nexport function usjToUsxString(usj: Usj): string {\n const usxDoc = new DOMImplementation().createDocument(\"\", USX_TYPE);\n if (usxDoc.documentElement) {\n usxDoc.documentElement.setAttribute(\"version\", USX_VERSION);\n usjToUsxDom(usj, usxDoc);\n }\n return usxDoc.toString();\n}\n\nexport function usjToUsxDom(usj: Usj, usxDoc: Document): Element | undefined {\n if (!usxDoc.documentElement) return undefined;\n\n for (const [index, markerContent] of usj.content.entries()) {\n const isLastItem = index === usj.content.length - 1;\n convertUsjRecurse(markerContent, usxDoc.documentElement, usxDoc, isLastItem);\n }\n return usxDoc.documentElement ?? undefined;\n}\n\nfunction convertUsjRecurse(\n markerContent: MarkerContent,\n parentElement: Element,\n usxDoc: Document,\n isLastItem: boolean,\n) {\n let element: Text | Element;\n let type: string | undefined;\n let eidElement: Element | undefined;\n if (typeof markerContent === \"string\") element = usxDoc.createTextNode(markerContent);\n else {\n type = markerContent.type.replace(\"table:\", \"\");\n element = usxDoc.createElement(type);\n setAttributes(element, markerContent);\n if (markerContent.content) {\n for (const [index, item] of markerContent.content.entries()) {\n const _isLastItem = index === markerContent.content.length - 1;\n convertUsjRecurse(item, element, usxDoc, _isLastItem);\n }\n }\n }\n\n // Create chapter and verse end elements from SID attributes.\n if (verseEid && (type === \"verse\" || (parentElement.tagName === \"para\" && isLastItem))) {\n eidElement = createVerseEndElement(usxDoc, verseEid);\n verseEid = undefined;\n }\n if (type === \"verse\" && typeof markerContent !== \"string\" && markerContent.sid !== undefined)\n verseEid = markerContent.sid;\n\n if (chapterEid && (type === \"chapter\" || (type === \"para\" && isLastItem))) {\n eidElement = createChapterEndElement(usxDoc, chapterEid);\n chapterEid = undefined;\n }\n if (type === \"chapter\" && typeof markerContent !== \"string\" && markerContent.sid !== undefined)\n chapterEid = markerContent.sid;\n\n // Append to parent.\n const isVerseInImpliedPara =\n parentElement.nodeName === USX_TYPE && eidElement?.tagName === \"verse\";\n if (eidElement && (!isLastItem || isVerseInImpliedPara)) parentElement.appendChild(eidElement);\n parentElement.appendChild(element);\n if (eidElement && isLastItem && !isVerseInImpliedPara) parentElement.appendChild(eidElement);\n\n // Allow for final chapter and verse end elements at the end of an implied para.\n if (isLastItem && parentElement.nodeName === USX_TYPE) {\n if (verseEid) parentElement.appendChild(createVerseEndElement(usxDoc, verseEid));\n if (chapterEid) parentElement.appendChild(createChapterEndElement(usxDoc, chapterEid));\n verseEid = undefined;\n chapterEid = undefined;\n }\n}\n\nfunction setAttributes(element: Element, markerContent: MarkerObject) {\n if (markerContent.marker) {\n if (markerContent.type === \"unmatched\") element.setAttribute(\"marker\", markerContent.marker);\n else element.setAttribute(\"style\", markerContent.marker);\n }\n for (const [key, value] of Object.entries(markerContent)) {\n if (value && ![\"type\", \"marker\", \"content\"].includes(key)) {\n element.setAttribute(key, value as string);\n }\n }\n}\n\nfunction createVerseEndElement(usxDoc: Document, verseEid: string): Element {\n const eidElement = usxDoc.createElement(\"verse\");\n eidElement.setAttribute(\"eid\", verseEid);\n return eidElement;\n}\n\nfunction createChapterEndElement(usxDoc: Document, chapterEid: string): Element {\n const eidElement = usxDoc.createElement(\"chapter\");\n eidElement.setAttribute(\"eid\", chapterEid);\n return eidElement;\n}\n","const JSON_PATH_START = \"$\";\nconst JSON_PATH_CONTENT = \".content[\";\n\n/**\n * Converts a USJ JSONPath string into an array of indexes.\n *\n * @param jsonPath - The USJ JSONPath string to convert. It must start with `$` and contain `.content[index]` segments.\n * @returns An array of numeric indexes extracted from the JSONPath.\n * @throws Will throw an error if the JSONPath does not start with `$`.\n *\n * @public\n */\nexport function indexesFromUsjJsonPath(jsonPath: string): number[] {\n const path = jsonPath.split(JSON_PATH_CONTENT);\n if (path.shift() !== JSON_PATH_START)\n throw new Error(`indexesFromJsonPath: jsonPath didn't start with '${JSON_PATH_START}'`);\n\n const indexes = path.map((str) => parseInt(str, 10));\n return indexes;\n}\n\n/**\n * Converts an array of indexes into a USJ JSONPath string.\n *\n * @param indexes - An array of numeric indexes to convert.\n * @returns A USJ JSONPath string constructed from the indexes.\n *\n * @public\n */\nexport function usjJsonPathFromIndexes(indexes: number[]): string {\n return indexes.reduce((path, index) => `${path}${JSON_PATH_CONTENT}${index}]`, JSON_PATH_START);\n}\n"],"names":["UNSAFE_KEYS","assertSafeKey","key","USJ_TYPE","USJ_VERSION","EMPTY_USJ","MARKER_OBJECT_PROPS","isValidBookCode","code","VALID_BOOK_CODES","USX_TYPE","USX_VERSION","EMPTY_USX","usxStringToUsj","usxString","inputUsxDom","DOMParser","usxDomToUsj","outputJson","convertUsxRecurse","inputUsxElement","attribs","type","marker","text","action","attrib","outObj","asciiTrim","children","child","childDict","whatToDo","str","chapterEid","verseEid","usjToUsxString","usj","usxDoc","DOMImplementation","usjToUsxDom","index","markerContent","isLastItem","convertUsjRecurse","parentElement","element","eidElement","setAttributes","item","_isLastItem","createVerseEndElement","createChapterEndElement","isVerseInImpliedPara","value","JSON_PATH_START","JSON_PATH_CONTENT","indexesFromUsjJsonPath","jsonPath","path","usjJsonPathFromIndexes","indexes"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.52.13"
9
+ }
10
+ ]
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eten-tech-foundation/scripture-utilities",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Utilities for working with Scripture data.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/eten-tech-foundation/scripture-editors/tree/main/packages/utilities#readme",
@@ -0,0 +1,15 @@
1
+ /* Utility functions for converters */
2
+
3
+ const UNSAFE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
4
+
5
+ /**
6
+ * Avoid prototype pollution by disallowing unsafe keys.
7
+ * @param key - The array key to validate.
8
+ *
9
+ * @public
10
+ */
11
+ export function assertSafeKey(key: string): void {
12
+ if (!UNSAFE_KEYS.has(key)) return;
13
+
14
+ throw new Error(`The key "${key}" is not allowed to avoid prototype pollution.`);
15
+ }
@@ -7,6 +7,8 @@ const JSON_PATH_CONTENT = ".content[";
7
7
  * @param jsonPath - The USJ JSONPath string to convert. It must start with `$` and contain `.content[index]` segments.
8
8
  * @returns An array of numeric indexes extracted from the JSONPath.
9
9
  * @throws Will throw an error if the JSONPath does not start with `$`.
10
+ *
11
+ * @public
10
12
  */
11
13
  export function indexesFromUsjJsonPath(jsonPath: string): number[] {
12
14
  const path = jsonPath.split(JSON_PATH_CONTENT);
@@ -22,6 +24,8 @@ export function indexesFromUsjJsonPath(jsonPath: string): number[] {
22
24
  *
23
25
  * @param indexes - An array of numeric indexes to convert.
24
26
  * @returns A USJ JSONPath string constructed from the indexes.
27
+ *
28
+ * @public
25
29
  */
26
30
  export function usjJsonPathFromIndexes(indexes: number[]): string {
27
31
  return indexes.reduce((path, index) => `${path}${JSON_PATH_CONTENT}${index}]`, JSON_PATH_START);
@@ -11,6 +11,14 @@ import { USX_TYPE, USX_VERSION } from "./usx.model.js";
11
11
  let chapterEid: string | undefined;
12
12
  let verseEid: string | undefined;
13
13
 
14
+ /**
15
+ * Converts a USJ object to a USX string.
16
+ *
17
+ * @param usj - The USJ object to convert
18
+ * @returns The converted USX string.
19
+ *
20
+ * @public
21
+ */
14
22
  export function usjToUsxString(usj: Usj): string {
15
23
  const usxDoc = new DOMImplementation().createDocument("", USX_TYPE);
16
24
  if (usxDoc.documentElement) {
@@ -84,8 +92,10 @@ function convertUsjRecurse(
84
92
  }
85
93
 
86
94
  function setAttributes(element: Element, markerContent: MarkerObject) {
87
- if (markerContent.type === "unmatched") element.setAttribute("marker", markerContent.marker);
88
- else element.setAttribute("style", markerContent.marker);
95
+ if (markerContent.marker) {
96
+ if (markerContent.type === "unmatched") element.setAttribute("marker", markerContent.marker);
97
+ else element.setAttribute("style", markerContent.marker);
98
+ }
89
99
  for (const [key, value] of Object.entries(markerContent)) {
90
100
  if (value && !["type", "marker", "content"].includes(key)) {
91
101
  element.setAttribute(key, value as string);
@@ -4,15 +4,28 @@
4
4
  * @see https://github.com/usfm-bible/tcdocs/blob/usj/grammar/usj.js
5
5
  */
6
6
 
7
- /** The USJ spec type */
7
+ /**
8
+ * The USJ spec type
9
+ * @public
10
+ */
8
11
  export const USJ_TYPE = "USJ";
9
12
 
10
- /** The USJ spec version */
13
+ /**
14
+ * The USJ spec version
15
+ * @public
16
+ */
11
17
  export const USJ_VERSION = "3.1";
12
18
 
19
+ /**
20
+ * An empty USJ object
21
+ * @public
22
+ */
13
23
  export const EMPTY_USJ = Object.freeze<Usj>({ type: USJ_TYPE, version: USJ_VERSION, content: [] });
14
24
 
15
- /** List of known properties of `MarkerObject` */
25
+ /**
26
+ * List of known properties of `MarkerObject`
27
+ * @public
28
+ */
16
29
  export const MARKER_OBJECT_PROPS: (keyof MarkerObject)[] = [
17
30
  "type",
18
31
  "marker",
@@ -28,10 +41,16 @@ export const MARKER_OBJECT_PROPS: (keyof MarkerObject)[] = [
28
41
  "category",
29
42
  ];
30
43
 
31
- /** Single piece of Scripture content */
44
+ /**
45
+ * Single piece of Scripture content
46
+ * @public
47
+ */
32
48
  export type MarkerContent = string | MarkerObject;
33
49
 
34
- /** A Scripture Marker and its contents */
50
+ /**
51
+ * A Scripture Marker and its contents
52
+ * @public
53
+ */
35
54
  export interface MarkerObject {
36
55
  /**
37
56
  * The kind/category of node or element this is, corresponding the USFM marker and USX node
@@ -42,7 +61,7 @@ export interface MarkerObject {
42
61
  * The corresponding marker in USFM or style in USX
43
62
  * @example `p`, `v`, `nd`
44
63
  */
45
- marker: string;
64
+ marker?: string;
46
65
  /** This marker's contents laid out in order */
47
66
  content?: MarkerContent[];
48
67
  /** Indicates the Book-chapter-verse value in the paragraph based structure */
@@ -65,7 +84,10 @@ export interface MarkerObject {
65
84
  category?: string;
66
85
  }
67
86
 
68
- /** Scripture data represented in JSON format. Data compatible transformation from USX/USFM */
87
+ /**
88
+ * Scripture data represented in JSON format. Data compatible transformation from USX/USFM
89
+ * @public
90
+ */
69
91
  export interface Usj {
70
92
  /** The USJ spec type */
71
93
  type: typeof USJ_TYPE;
@@ -75,14 +97,25 @@ export interface Usj {
75
97
  content: MarkerContent[];
76
98
  }
77
99
 
100
+ /**
101
+ * Check if the given code is a valid 3-letter Scripture book code.
102
+ * @public
103
+ */
78
104
  export function isValidBookCode(code: string): boolean {
79
105
  return VALID_BOOK_CODES.includes(code as BookCode);
80
106
  }
81
107
 
82
- /** 3-letter Scripture book code */
108
+ /**
109
+ * 3-letter Scripture book code
110
+ * @public
111
+ */
83
112
  export type BookCode = (typeof VALID_BOOK_CODES)[number];
84
113
 
85
- const VALID_BOOK_CODES = [
114
+ /**
115
+ * List of valid 3-letter Scripture book codes
116
+ * @public
117
+ */
118
+ export const VALID_BOOK_CODES = [
86
119
  // Old Testament
87
120
  "GEN",
88
121
  "EXO",
@@ -7,12 +7,21 @@
7
7
  import { DOMParser, Element } from "@xmldom/xmldom";
8
8
  import { MarkerContent, MarkerObject, USJ_TYPE, USJ_VERSION, Usj } from "./usj.model.js";
9
9
  import { USX_TYPE } from "./usx.model.js";
10
+ import { assertSafeKey } from "./converter.utils.js";
10
11
 
11
12
  type Action = "append" | "merge" | "ignore";
12
13
  interface Attribs {
13
14
  [name: string]: string;
14
15
  }
15
16
 
17
+ /**
18
+ * Converts a USX string to a USJ object.
19
+ *
20
+ * @param usxString - The USX string to convert.
21
+ * @returns The converted USJ object.
22
+ *
23
+ * @public
24
+ */
16
25
  export function usxStringToUsj(usxString: string): Usj {
17
26
  const parser = new DOMParser();
18
27
  const inputUsxDom = parser.parseFromString(usxString, "text/xml");
@@ -40,6 +49,7 @@ function convertUsxRecurse<T extends Usj | MarkerObject = Usj>(
40
49
  if (["row", "cell"].includes(type)) type = "table:" + type;
41
50
  if (inputUsxElement.attributes) {
42
51
  for (const attrib of Array.from(inputUsxElement.attributes)) {
52
+ assertSafeKey(attrib.name);
43
53
  attribs[attrib.name] = attrib.value;
44
54
  }
45
55
  }
@@ -4,10 +4,20 @@
4
4
  * @see https://github.com/usfm-bible/tcdocs/blob/main/grammar/usx.rng
5
5
  */
6
6
 
7
- /** The USX spec type */
7
+ /**
8
+ * The USX spec type
9
+ * @public
10
+ */
8
11
  export const USX_TYPE = "usx";
9
12
 
10
- /** The USX spec version */
13
+ /**
14
+ * The USX spec version
15
+ * @public
16
+ */
11
17
  export const USX_VERSION = "3.1";
12
18
 
19
+ /**
20
+ * An empty USX string
21
+ * @public
22
+ */
13
23
  export const EMPTY_USX = `<${USX_TYPE} version="${USX_VERSION}" />`;
package/src/index.ts CHANGED
@@ -1,10 +1,18 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Utilities for Scripture data conversion and manipulation, including USJ/USX format conversion.
4
+ */
5
+
1
6
  export type { Usj, BookCode, MarkerContent, MarkerObject } from "./converters/usj/usj.model.js";
7
+
8
+ export { assertSafeKey } from "./converters/usj/converter.utils.js";
2
9
  export {
3
10
  EMPTY_USJ,
4
11
  MARKER_OBJECT_PROPS,
5
12
  USJ_TYPE,
6
13
  USJ_VERSION,
7
14
  isValidBookCode,
15
+ VALID_BOOK_CODES,
8
16
  } from "./converters/usj/usj.model.js";
9
17
  export { EMPTY_USX, USX_TYPE, USX_VERSION } from "./converters/usj/usx.model.js";
10
18
  export { usxStringToUsj } from "./converters/usj/usx-to-usj.js";