@prose-reader/cfi 1.371.0 → 1.373.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){let t=[e],n=e;for(;n.parentNode;)t.push(n.parentNode),n=n.parentNode;return t}function t(t,n){if(t===n)return t;let r=e(t),i=new Set(r),a=n;for(;a;){if(i.has(a))return a;a=a.parentNode}return null}var n=/[[\]^,();]/g;function r(e){return e.replace(n,`^$&`)}var i=/^epubcfi\((.*)\)$/,a=e=>e.nodeType===Node.ELEMENT_NODE,o=e=>typeof e==`object`&&!!e&&`nodeType`in e&&(e.nodeType===Node.ELEMENT_NODE||e.nodeType===Node.TEXT_NODE||e.nodeType===Node.CDATA_SECTION_NODE),s=e=>e.nodeType===Node.TEXT_NODE||e.nodeType===Node.CDATA_SECTION_NODE,c=e=>{let t=e.parentNode,n=0;for(let r=0;t&&r<t.childNodes.length;r++){let i=t.childNodes[r];if(!i||(a(i)&&n++,i===e))break}return n*2},l=e=>{let t=e.parentNode,n=0,r=0;for(let i=0;t&&i<t.childNodes.length;i++){let o=t.childNodes[i];if(!o||o===e)break;a(o)?(n++,r=0):s(o)&&(r+=o.data.length)}return{step:n*2+1,base:r}},u=(e,t)=>{let n=(t-1)/2,r=0,i=0;for(let t=0;t<e.childNodes.length;t++){let o=e.childNodes[t];if(!o)break;if(a(o)){if(r++,r>n)break;i=t+1}else if(r===n&&s(o))return{kind:`node`,node:o}}return r<n?void 0:{kind:`boundary`,parent:e,childIndex:i}},d=(e,t)=>{let n=e,r=0,i=e;for(;i&&!a(i);){if(s(i)){if(n=i,t<r+i.data.length)return{node:n,offset:t-r};r+=i.data.length}i=i.nextSibling}return{node:n,offset:t-(r-n.data.length)}};function f(e){if(p(e))return!1;let t=e[e.length-1];return e.length>1&&(t===void 0||t.length===0)}function p(e){return typeof e==`object`&&!!e&&`parent`in e&&`start`in e&&`end`in e}function m(e){let t=e.match(i);return t&&t[1]||e}function h(e){let t=[],n=null,r=!1,i=``,a=e=>{t.push(e),n=null,i=``},o=e=>{i+=e,r=!1},s=m(e).trim(),c=Array.from(s).concat(``);for(let e=0;e<c.length;e++){let t=c[e];if(!t){n===`/`||n===`:`?a([n,parseInt(i,10)]):n===`~`?a([`~`,parseFloat(i)]):n===`@`?a([`@`,parseFloat(i)]):n===`[`?a([`[`,i]):n===`;`||n?.startsWith(`;`)?a([n,i]):n===`!`&&a([`!`,0]);break}if(t===`^`&&!r){r=!0;continue}if(n===`!`)a([`!`,0]);else if(n===`,`)a([`,`,0]);else if(n===`/`||n===`:`){if(/^\d$/.test(t)){o(t);continue}a([n,parseInt(i,10)])}else if(n===`~`){if(/^\d$/.test(t)||t===`.`){o(t);continue}a([`~`,parseFloat(i)])}else if(n===`@`){if(t===`:`){a([`@`,parseFloat(i)]),n=`@`;continue}if(/^\d$/.test(t)||t===`.`){o(t);continue}a([`@`,parseFloat(i)])}else if(n===`[`)if(t===`;`&&!r)a([`[`,i]),n=`;`;else if(t===`]`&&!r)a([`[`,i]);else{o(t);continue}else if(n===`;`)if(t===`=`&&!r)n=`;${i}`,i=``;else if(t===`;`&&!r)a([n,i]),n=`;`;else if(t===`]`&&!r)a([n,i]);else{o(t);continue}else if(n?.startsWith(`;`))if(t===`;`&&!r)a([n,i]),n=`;`;else if(t===`]`&&!r)a([n,i]);else{o(t);continue}else n===null&&t===`;`&&(n=`;`);(t===`/`||t===`:`||t===`~`||t===`@`||t===`[`||t===`!`||t===`,`)&&(n=t)}return t}function g(e,t){return e?e.map((e,n)=>e[0]===t?n:null).filter(e=>e!==null):[]}function _(e,t){let n=[],r=0;for(let i of t)n.push(e.slice(r,i)),r=i;return n.push(e.slice(r)),n}function v(e){let t=[],n={},r=-1;for(let i=0;i<e.length;i++){let a=e[i];if(!a)continue;let[o,s]=a;o===`/`?(r++,t[r]={index:s},n[r]=[]):r>=0&&n[r]?.push(a)}for(let e=0;e<t.length;e++){let r=t[e];if(!r)continue;let i=n[e]||[];for(let e=0;e<i.length;e++){let t=i[e];if(!t)continue;let[n,a]=t;if(n===`:`)r.offset=a;else if(n===`~`)r.temporal=a;else if(n===`@`)r.spatial=(r.spatial||[]).concat(a);else if(n===`;s`)r.side=a;else if(n.startsWith(`;`)&&n!==`;s`){let e=n.substring(1);r.extensions||={},r.extensions[e]=a}else if(n===`[`){let t=typeof a==`string`&&!a.includes(` `)&&a.length<50;e===0&&t&&!r.id?r.id=a:a!==``&&(r.text=a.split(`,`).map(e=>e.trim()))}}}return t}function y(e){return _(e,g(e,`!`)).map(v)}function b(e){if(!e)throw Error(`CFI string cannot be empty`);let t=h(e);if(!t||t.length===0)throw Error(`Failed to tokenize CFI string`);let n=g(t,`,`);if(n.length===0)return y(t);let[r,i,a]=_(t,n);function o(e,t){if(!t||t.length===0)return[[]];let n=t.filter(e=>e[0]!==`,`);if(n.length===1&&n[0]&&n[0][0]===`:`){let t=e.length>0?e[e.length-1]:void 0,r=n[0];if(t&&t.length>0&&r){let e=t.map(e=>({...e})),n=e[e.length-1];return n&&(n.offset=r[1]),[e]}return[[]]}return y(t)}let s=y(r||[]);return{parent:s,start:o(s,i||[]),end:o(s,a||[])}}function x(e,t=!1){return typeof e==`string`?x(b(e),t):`parent`in e?t?e.parent.concat(e.end):e.parent.concat(e.start):e}function S(e){return e.offset===void 0?e.index===void 0?e.temporal!==void 0||e.spatial!==void 0?3:4:2:1}function C(e,t){let n=typeof e==`string`?b(e):e,r=typeof t==`string`?b(t):t;if(`parent`in n||`parent`in r)return C(x(n),x(r))||C(x(n,!0),x(r,!0));for(let e=0;e<Math.max(n.length,r.length);e++){let t=n[e]||[],i=r[e]||[],a=Math.max(t.length,i.length)-1;for(let e=0;e<=a;e++){let n=t[e],r=i[e];if(!n)return-1;if(!r||n.index>r.index)return 1;if(n.index<r.index)return-1;let o=S(n),s=S(r);if(o!==s)return o-s;let c=n.temporal!==void 0,l=r.temporal!==void 0;if(c&&!l)return 1;if(!c&&l)return-1;if(c&&l){if((n.temporal??0)>(r.temporal??0))return 1;if((n.temporal??0)<(r.temporal??0))return-1}let u=n.spatial!==void 0,d=r.spatial!==void 0;if(u&&!d)return 1;if(!u&&d)return-1;if(u&&d){let e=n.spatial?.[1]??0,t=r.spatial?.[1]??0;if(e>t)return 1;if(e<t)return-1;let i=n.spatial?.[0]??0,a=r.spatial?.[0]??0;if(i>a)return 1;if(i<a)return-1}if(e===a){if((n.offset??0)>(r.offset??0))return 1;if((n.offset??0)<(r.offset??0))return-1}}}return 0}var w=6;function T(e,t,n={}){if(!e.textContent||e.textContent.trim()===``)return null;let r=e.textContent,i=n.textAssertionLength||10;if(t!==void 0&&t<=r.length){let e=Math.floor(i/2),n=Math.max(0,t-e),a=Math.min(r.length,t+e);return r.substring(n,a)}return r.substring(0,Math.min(r.length,i))}function E(e){let[t,n]=e;return`@${Math.max(0,Math.min(100,t))}:${Math.max(0,Math.min(100,n))}`}function D(e,t,n){let r=e;return t!==void 0&&(r+=`~${t}`),n!==void 0&&(r+=E(n)),r}function O(e,t,n){let i=e;if(t&&(i+=`[${r(t)}]`),n){let e=n===`before`?`b`:`a`;t?i=`${i.substring(0,i.length-1)};s=${e}]`:i+=`[;s=${e}]`}return i}function k(e,t,n={},i){let o=``,u=e,d=null,f=t;if(e&&!a(e)){let n=e.parentNode;if(!n)throw Error(`Text node doesn't have a parent`);let{step:r,base:i}=l(e);o=`/${r}`,d=s(e)?e:null,f=t===void 0?void 0:i+t,u=n}let p=null;for(d&&n.includeTextAssertions&&(p=T(d,t,n));u?.parentNode;){let e=c(u);if(o=a(u)&&u.id?`/${e}[${r(u.id)}]${o}`:`/${e}${o}`,u.parentNode.nodeName.toLowerCase()===`html`)break;u=u.parentNode}return f!==void 0&&(o+=`:${f}`),o=D(o,i?.temporal,i?.spatial||n.spatialOffset),o=O(o,p,n.includeSideBias),o=M(o,n.extensions),o}function A(e,t,n,i={},o){let u=a(t)?0:l(t).base;if(e===t){let e=n===void 0?``:`:${u+n}`;return e=D(e,o?.temporal,o?.spatial),e}let d=[],f=t;for(;f&&f!==e;){let e=f.parentNode;if(!e)break;let t=a(f)?c(f):l(f).step;a(f)&&f.id?d.unshift(`/${t}[${r(f.id)}]`):d.unshift(`/${t}`),f=e}let p=d.join(``);if(n!==void 0&&(p+=`:${u+n}`),p=D(p,o?.temporal,o?.spatial),i.includeTextAssertions&&s(t)){let e=T(t,n,i);p=O(p,e,i.includeSideBias)}return p=M(p,i.extensions),p}var j=e=>Object.entries(e).map(([e,t])=>{let n=r(t);return`${e}=${encodeURIComponent(n)}`}).join(`;`);function M(e,t){if(!t||Object.keys(t).length===0)return e;let n=j(t);if(e.endsWith(`]`)){let t=e.lastIndexOf(`[`),r=e.substring(t+1,e.length-1);return r.includes(n)?e:`${e.substring(0,e.length-1)}${r.includes(`;`),`;`}${n}]`}return e.endsWith(`!`)?e:`${e}[;${n}]`}function N(e,n,r,i,a={},o,s){let c=t(e,r);if(!c)throw Error(`No common ancestor found`);let l=k(c,void 0,a),u=A(c,e,n,a,o),d=A(c,r,i,a,s);return a.extensions&&Object.keys(a.extensions).length>0?`${M(l,a.extensions)},${M(u,a.extensions)},${M(d,a.extensions)}`:`${l},${u},${d}`}var P=(e,t,n={},i)=>{let a=`/6/${(e+1)*2}`;return t&&(a+=`[${r(t)}]`),a=i?M(a,n.extensions):a,`${a}!`};function F(e,t={}){if(o(e))return`epubcfi(${k(e,void 0,t)})`;let n=``;if(!(`start`in e))return e.spineIndex!==void 0&&(n=P(e.spineIndex,e.spineId,t,!e.node),!e.node)||(n+=k(e.node??null,e.offset,t,e)),`epubcfi(${n})`;let{start:r,end:i}=e;return r.spineIndex!==void 0&&(n=P(r.spineIndex,r.spineId,t,!r.node)),r.node&&i.node&&(n+=N(r.node,r.offset??0,i.node,i.offset??0,t,r,i)),`epubcfi(${n})`}function I(e,t,n={}){try{return L(typeof e==`string`?b(e):e,t,n)}catch(e){if(n.throwOnError)throw e;return{node:null,isRange:!1}}}function L(e,t,n={asRange:!1}){if(p(e))return R(e,t);if(f(e))return U(null);let r=e.at(-1);if(r)return J(r,t,n);throw Error(`Invalid CFI structure`)}function R(e,t){let n=e.parent,r=e.start[0]||[],i=e.end[0]||[],a=t.documentElement;if(n.length>0)if(n.length>1){let e=n[0];if(e){let n=J(e,t);o(n.node)&&(a=n.node)}if(n.length>1&&a){let e=n[1];if(e){let n=J(e,t);o(n.node)&&(a=n.node)}}}else{let e=n[0];if(e){let n=J(e,t);o(n.node)&&(a=n.node)}}if(!a)throw Error(`Failed to resolve parent node in CFI range`);let c=s(a),l,u,d=0,f=0;if(c){l=a,u=a;let e=r[r.length-1],t=i[i.length-1];d=(Array.isArray(e?.offset)?e.offset[0]:e?.offset)??0,f=(Array.isArray(t?.offset)?t.offset[0]:t?.offset)??0}else{let e=r.length===0,t=i.length===0;if(e){if(!a)throw Error(`Failed to resolve parent node in CFI range (start)`);l=a,d=r[0]?.offset??0}else{let e=q(a,r,0,!0);if(!e)throw Error(`Failed to resolve start node in CFI range`);l=e.node;let t=r[r.length-1];d=e.childIndex??(Array.isArray(t?.offset)?t.offset[0]:t?.offset)??0}if(t){if(!a)throw Error(`Failed to resolve parent node in CFI range (end)`);u=a,f=i[0]?.offset??0}else{let e=q(a,i,0,!0);if(!e)throw Error(`Failed to resolve end node in CFI range`);u=e.node;let t=i[i.length-1];f=e.childIndex??(Array.isArray(t?.offset)?t.offset[0]:t?.offset)??0}}let p=K(l,d),m=K(u,f),h=t.createRange();return h.setStart(p.node,p.offset??0),h.setEnd(m.node,m.offset??0),{...H(r[r.length-1]),node:h,isRange:!0,offset:p.offset}}function z(e){if(e?.side)return e.side;if(e?.text&&e.text.length>0){let t=e.text[0];if(t){let e=t.match(/^([ab])$/);if(e)return e[1]}}}function B(e){return e.index%2!=0}function V(e){return(p(e)?e.end:e).at(-1)?.at(-1)?.extensions}function H(e){let t=z(e),n=e?.extensions;return{offset:e?.offset,temporal:e?.temporal,spatial:e?.spatial,side:t,extensions:n}}function U(e,t){return{node:e,isRange:!1,...H(t)}}function W(e,t){return{node:e,isRange:!0,...H(t)}}function G(e,t,n){let r=e.createRange();if(r.selectNodeContents(t),n!==void 0){let e=Array.isArray(n)?n[0]:n;s(t)&&r.setStart(t,e||0)}return r}function K(e,t){let n=Array.isArray(t)?t[0]:t;return n===void 0||!s(e)?{node:e,offset:n}:d(e,n)}function q(e,t,n,r){let i=e;for(let e=n;e<t.length;e++){let n=t[e];if(!i||!n)break;if(B(n)){let a=u(i,n.index);if(!a){if(r)throw Error(`No character data chunk: ${n.index}`);i=null;break}if(a.kind===`boundary`){if(e<t.length-1){if(r)throw Error(`Empty character data chunk: ${n.index}`);i=null;break}return{node:a.parent,childIndex:a.childIndex}}i=a.node}else{let e=Array.from(i.childNodes).filter(e=>e.nodeType===Node.ELEMENT_NODE),t=Math.floor(n.index/2)-1;if(t>=0&&t<e.length){let n=e[t];n&&(i=n)}else{if(r)throw Error(`Invalid element step index: ${n.index}`);i=null;break}}}return i?{node:i}:null}function J(e,t,n={}){let{throwOnError:r=!1,asRange:i=!1}=n;if(!t){if(r)throw Error(`Document is not available`);return U(null)}let{node:a,remainingPathIndex:o}=te(t,e);if(a&&o>=e.length){let n=e.at(-1);return i?W(G(t,a,n?.offset),n):U(a,n)}let s=a||t.documentElement,c=a?o:0;if(i&&e.length>0){let n=e[e.length-1];if(n&&!B(n)){if(n.index===0&&s){let e=t.createRange();return e.setStart(s,0),e.setEnd(s,0),W(e,n)}let i=q(s,e.slice(0,-1),c,r)?.node;if(i){let e=Array.from(i.childNodes).filter(e=>e.nodeType===Node.ELEMENT_NODE);if(Math.floor(n.index/2)-1===e.length){let e=t.createRange();return e.selectNodeContents(i),e.collapse(!1),W(e,n)}}}}let l=q(s,e,c,r);if(!l){if(r)throw Error(`Failed to resolve CFI path`);return U(null)}let u=e.at(-1),d=l.childIndex===void 0?K(l.node,u?.offset):{node:l.node,offset:l.childIndex};return i?{...W(l.childIndex===void 0?G(t,d.node,d.offset):ee(t,l.node,l.childIndex),u),offset:d.offset}:{...U(d.node,u),offset:d.offset}}function ee(e,t,n){let r=e.createRange();return r.setStart(t,n),r.setEnd(t,n),r}function te(e,t){for(let n=t.length-1;n>=0;n--){let r=t[n];if(r?.id){let t=e.getElementById(r.id);if(t)return{node:t,remainingPathIndex:n+1}}}return{node:null,remainingPathIndex:0}}function ne(e){let t=`/${e.index}`;if(e.id){if(t+=`[${r(e.id)}`,e.extensions)for(let[n,i]of Object.entries(e.extensions))t+=`;${n}=${r(i)}`;t+=`]`}e.offset!==void 0&&(t+=`:${e.offset}`),e.temporal!==void 0&&(t+=`~${e.temporal}`),e.spatial&&e.spatial.length>0&&(t+=`@${e.spatial.join(`:`)}`);let n=[];if(e.text&&e.text.length>0&&n.push(e.text.map(r).join(`,`)),(e.side||e.extensions&&!e.id)&&(e.side&&n.push(`;s=${e.side}`),e.extensions&&!e.id))for(let[t,i]of Object.entries(e.extensions))n.push(`;${t}=${r(i)}`);return n.length>0&&(t+=`[${n.join(``)}]`),t}function Y(e){return e.map(e=>ne(e)).join(``)}function X(e){return Array.isArray(e)?`epubcfi(${e.map(Y).join(`!`)})`:`epubcfi(${e.parent.map(Y).join(`!`)},${e.start.map(Y).join(`!`)},${e.end.map(Y).join(`!`)})`}var Z=e=>(e+1)*2,Q=e=>e/2-1,re=e=>p(e)?e.parent[0]:e[0],$=e=>{let t=re(e);if(t?.[0]?.index===6)return t[1]},ie=(e,t)=>{let n={...e};for(let[e,r]of Object.entries(t))r===void 0?delete n[e]:n[e]=r;return Object.keys(n).length>0?n:void 0},ae=e=>{if(e.id!==void 0)return e.id;if(!(!e.text||e.text.length===0))return e.text.join(`,`)},oe=e=>{try{let t=$(b(e));return t?{spineIndex:Q(t.index),spineId:ae(t),extensions:t.extensions}:void 0}catch{return}},se=(e,t)=>{try{let n=b(e),r=$(n);return r?(t.spineIndex!==void 0&&(r.index=Z(t.spineIndex)),t.spineId!==void 0&&(r.id=t.spineId,r.text=void 0,r.side=void 0),t.extensions!==void 0&&(r.extensions=ie(r.extensions,t.extensions)),X(n)):void 0}catch{return}};exports.EPUB_CFI_PACKAGE_SPINE_STEP_INDEX=w,exports.compare=C,exports.epubCfiItemrefStepIndexToSpineIndex=Q,exports.generate=F,exports.getEpubCfiSpineItemref=oe,exports.isIndirectionOnly=f,exports.isParsedCfiRange=p,exports.parse=b,exports.resolve=I,exports.resolveExtensions=V,exports.serialize=X,exports.spineIndexToEpubCfiItemrefStepIndex=Z,exports.updateEpubCfiSpineItemref=se;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){let t=[e],n=e;for(;n.parentNode;)t.push(n.parentNode),n=n.parentNode;return t}function t(t,n){if(t===n)return t;let r=e(t),i=new Set(r),a=n;for(;a;){if(i.has(a))return a;a=a.parentNode}return null}var n=/[[\]^,();]/g;function r(e){return e.replace(n,`^$&`)}var i=/^epubcfi\((.*)\)$/,a=e=>e.nodeType===Node.ELEMENT_NODE,o=e=>typeof e==`object`&&!!e&&`nodeType`in e&&(e.nodeType===Node.ELEMENT_NODE||e.nodeType===Node.TEXT_NODE||e.nodeType===Node.CDATA_SECTION_NODE),s=e=>e.nodeType===Node.TEXT_NODE||e.nodeType===Node.CDATA_SECTION_NODE,c=e=>{let t=e.parentNode,n=0;for(let r=0;t&&r<t.childNodes.length;r++){let i=t.childNodes[r];if(!i||(a(i)&&n++,i===e))break}return n*2},l=e=>{let t=e.parentNode,n=0,r=0;for(let i=0;t&&i<t.childNodes.length;i++){let o=t.childNodes[i];if(!o||o===e)break;a(o)?(n++,r=0):s(o)&&(r+=o.data.length)}return{step:n*2+1,base:r}},u=(e,t)=>{let n=(t-1)/2,r=0,i=0;for(let t=0;t<e.childNodes.length;t++){let o=e.childNodes[t];if(!o)break;if(a(o)){if(r++,r>n)break;i=t+1}else if(r===n&&s(o))return{kind:`node`,node:o}}return r<n?void 0:{kind:`boundary`,parent:e,childIndex:i}},d=(e,t)=>{let n=e,r=0,i=e;for(;i&&!a(i);){if(s(i)){if(n=i,t<r+i.data.length)return{node:n,offset:t-r};r+=i.data.length}i=i.nextSibling}return{node:n,offset:t-(r-n.data.length)}};function f(e){if(p(e))return!1;let t=e[e.length-1];return e.length>1&&(t===void 0||t.length===0)}function p(e){return typeof e==`object`&&!!e&&`parent`in e&&`start`in e&&`end`in e}function m(e){let t=e.match(i);return t&&t[1]||e}function h(e){let t=[],n=null,r=!1,i=``,a=e=>{t.push(e),n=null,i=``},o=e=>{i+=e,r=!1},s=m(e).trim(),c=Array.from(s).concat(``);for(let e=0;e<c.length;e++){let t=c[e];if(!t){n===`/`||n===`:`?a([n,parseInt(i,10)]):n===`~`?a([`~`,parseFloat(i)]):n===`@`?a([`@`,parseFloat(i)]):n===`[`?a([`[`,i]):n===`;`||n?.startsWith(`;`)?a([n,i]):n===`!`&&a([`!`,0]);break}if(t===`^`&&!r){r=!0;continue}if(n===`!`)a([`!`,0]);else if(n===`,`)a([`,`,0]);else if(n===`/`||n===`:`){if(/^\d$/.test(t)){o(t);continue}a([n,parseInt(i,10)])}else if(n===`~`){if(/^\d$/.test(t)||t===`.`){o(t);continue}a([`~`,parseFloat(i)])}else if(n===`@`){if(t===`:`){a([`@`,parseFloat(i)]),n=`@`;continue}if(/^\d$/.test(t)||t===`.`){o(t);continue}a([`@`,parseFloat(i)])}else if(n===`[`){if(t===`;`&&!r)a([`[`,i]),n=`;`;else if(t===`]`&&!r)a([`[`,i]);else{o(t);continue}}else if(n===`;`){if(t===`=`&&!r)n=`;${i}`,i=``;else if(t===`;`&&!r)a([n,i]),n=`;`;else if(t===`]`&&!r)a([n,i]);else{o(t);continue}}else if(n?.startsWith(`;`)){if(t===`;`&&!r)a([n,i]),n=`;`;else if(t===`]`&&!r)a([n,i]);else{o(t);continue}}else n===null&&t===`;`&&(n=`;`);(t===`/`||t===`:`||t===`~`||t===`@`||t===`[`||t===`!`||t===`,`)&&(n=t)}return t}function g(e,t){return e?e.map((e,n)=>e[0]===t?n:null).filter(e=>e!==null):[]}function _(e,t){let n=[],r=0;for(let i of t)n.push(e.slice(r,i)),r=i;return n.push(e.slice(r)),n}function v(e){let t=[],n={},r=-1;for(let i=0;i<e.length;i++){let a=e[i];if(!a)continue;let[o,s]=a;o===`/`?(r++,t[r]={index:s},n[r]=[]):r>=0&&n[r]?.push(a)}for(let e=0;e<t.length;e++){let r=t[e];if(!r)continue;let i=n[e]||[];for(let e=0;e<i.length;e++){let t=i[e];if(!t)continue;let[n,a]=t;if(n===`:`)r.offset=a;else if(n===`~`)r.temporal=a;else if(n===`@`)r.spatial=(r.spatial||[]).concat(a);else if(n===`;s`)r.side=a;else if(n.startsWith(`;`)&&n!==`;s`){let e=n.substring(1);r.extensions||={},r.extensions[e]=a}else if(n===`[`){let t=typeof a==`string`&&!a.includes(` `)&&a.length<50;e===0&&t&&!r.id?r.id=a:a!==``&&(r.text=a.split(`,`).map(e=>e.trim()))}}}return t}function y(e){return _(e,g(e,`!`)).map(v)}function b(e){if(!e)throw Error(`CFI string cannot be empty`);let t=h(e);if(!t||t.length===0)throw Error(`Failed to tokenize CFI string`);let n=g(t,`,`);if(n.length===0)return y(t);let[r,i,a]=_(t,n);function o(e,t){if(!t||t.length===0)return[[]];let n=t.filter(e=>e[0]!==`,`);if(n.length===1&&n[0]&&n[0][0]===`:`){let t=e.length>0?e[e.length-1]:void 0,r=n[0];if(t&&t.length>0&&r){let e=t.map(e=>({...e})),n=e[e.length-1];return n&&(n.offset=r[1]),[e]}return[[]]}return y(t)}let s=y(r||[]);return{parent:s,start:o(s,i||[]),end:o(s,a||[])}}function x(e,t=!1){return typeof e==`string`?x(b(e),t):`parent`in e?t?e.parent.concat(e.end):e.parent.concat(e.start):e}function S(e){return e.offset===void 0?e.index===void 0?e.temporal!==void 0||e.spatial!==void 0?3:4:2:1}function C(e,t){let n=typeof e==`string`?b(e):e,r=typeof t==`string`?b(t):t;if(`parent`in n||`parent`in r)return C(x(n),x(r))||C(x(n,!0),x(r,!0));for(let e=0;e<Math.max(n.length,r.length);e++){let t=n[e]||[],i=r[e]||[],a=Math.max(t.length,i.length)-1;for(let e=0;e<=a;e++){let n=t[e],r=i[e];if(!n)return-1;if(!r||n.index>r.index)return 1;if(n.index<r.index)return-1;let o=S(n),s=S(r);if(o!==s)return o-s;let c=n.temporal!==void 0,l=r.temporal!==void 0;if(c&&!l)return 1;if(!c&&l)return-1;if(c&&l){if((n.temporal??0)>(r.temporal??0))return 1;if((n.temporal??0)<(r.temporal??0))return-1}let u=n.spatial!==void 0,d=r.spatial!==void 0;if(u&&!d)return 1;if(!u&&d)return-1;if(u&&d){let e=n.spatial?.[1]??0,t=r.spatial?.[1]??0;if(e>t)return 1;if(e<t)return-1;let i=n.spatial?.[0]??0,a=r.spatial?.[0]??0;if(i>a)return 1;if(i<a)return-1}if(e===a){if((n.offset??0)>(r.offset??0))return 1;if((n.offset??0)<(r.offset??0))return-1}}}return 0}var w=6;function T(e,t,n={}){if(!e.textContent||e.textContent.trim()===``)return null;let r=e.textContent,i=n.textAssertionLength||10;if(t!==void 0&&t<=r.length){let e=Math.floor(i/2),n=Math.max(0,t-e),a=Math.min(r.length,t+e);return r.substring(n,a)}return r.substring(0,Math.min(r.length,i))}function E(e){let[t,n]=e;return`@${Math.max(0,Math.min(100,t))}:${Math.max(0,Math.min(100,n))}`}function D(e,t,n){let r=e;return t!==void 0&&(r+=`~${t}`),n!==void 0&&(r+=E(n)),r}function O(e,t,n){let i=e;if(t&&(i+=`[${r(t)}]`),n){let e=n===`before`?`b`:`a`;t?i=`${i.substring(0,i.length-1)};s=${e}]`:i+=`[;s=${e}]`}return i}function k(e,t,n={},i){let o=``,u=e,d=null,f=t;if(e&&!a(e)){let n=e.parentNode;if(!n)throw Error(`Text node doesn't have a parent`);let{step:r,base:i}=l(e);o=`/${r}`,d=s(e)?e:null,f=t===void 0?void 0:i+t,u=n}let p=null;for(d&&n.includeTextAssertions&&(p=T(d,t,n));u?.parentNode;){let e=c(u);if(o=a(u)&&u.id?`/${e}[${r(u.id)}]${o}`:`/${e}${o}`,u.parentNode.nodeName.toLowerCase()===`html`)break;u=u.parentNode}return f!==void 0&&(o+=`:${f}`),o=D(o,i?.temporal,i?.spatial||n.spatialOffset),o=O(o,p,n.includeSideBias),o=M(o,n.extensions),o}function A(e,t,n,i={},o){let u=a(t)?0:l(t).base;if(e===t){let e=n===void 0?``:`:${u+n}`;return e=D(e,o?.temporal,o?.spatial),e}let d=[],f=t;for(;f&&f!==e;){let e=f.parentNode;if(!e)break;let t=a(f)?c(f):l(f).step;a(f)&&f.id?d.unshift(`/${t}[${r(f.id)}]`):d.unshift(`/${t}`),f=e}let p=d.join(``);if(n!==void 0&&(p+=`:${u+n}`),p=D(p,o?.temporal,o?.spatial),i.includeTextAssertions&&s(t)){let e=T(t,n,i);p=O(p,e,i.includeSideBias)}return p=M(p,i.extensions),p}var j=e=>Object.entries(e).map(([e,t])=>{let n=r(t);return`${e}=${encodeURIComponent(n)}`}).join(`;`);function M(e,t){if(!t||Object.keys(t).length===0)return e;let n=j(t);if(e.endsWith(`]`)){let t=e.lastIndexOf(`[`),r=e.substring(t+1,e.length-1);return r.includes(n)?e:`${e.substring(0,e.length-1)}${r.includes(`;`),`;`}${n}]`}return e.endsWith(`!`)?e:`${e}[;${n}]`}function N(e,n,r,i,a={},o,s){let c=t(e,r);if(!c)throw Error(`No common ancestor found`);let l=k(c,void 0,a),u=A(c,e,n,a,o),d=A(c,r,i,a,s);return a.extensions&&Object.keys(a.extensions).length>0?`${M(l,a.extensions)},${M(u,a.extensions)},${M(d,a.extensions)}`:`${l},${u},${d}`}var P=(e,t,n={},i)=>{let a=`/6/${(e+1)*2}`;return t&&(a+=`[${r(t)}]`),a=i?M(a,n.extensions):a,`${a}!`};function F(e,t={}){if(o(e))return`epubcfi(${k(e,void 0,t)})`;let n=``;if(!(`start`in e))return e.spineIndex!==void 0&&(n=P(e.spineIndex,e.spineId,t,!e.node),!e.node)||(n+=k(e.node??null,e.offset,t,e)),`epubcfi(${n})`;let{start:r,end:i}=e;return r.spineIndex!==void 0&&(n=P(r.spineIndex,r.spineId,t,!r.node)),r.node&&i.node&&(n+=N(r.node,r.offset??0,i.node,i.offset??0,t,r,i)),`epubcfi(${n})`}function I(e,t,n={}){try{return L(typeof e==`string`?b(e):e,t,n)}catch(e){if(n.throwOnError)throw e;return{node:null,isRange:!1}}}function L(e,t,n={asRange:!1}){if(p(e))return R(e,t);if(f(e))return U(null);let r=e.at(-1);if(r)return J(r,t,n);throw Error(`Invalid CFI structure`)}function R(e,t){let n=e.parent,r=e.start[0]||[],i=e.end[0]||[],a=t.documentElement;if(n.length>0){if(n.length>1){let e=n[0];if(e){let n=J(e,t);o(n.node)&&(a=n.node)}if(n.length>1&&a){let e=n[1];if(e){let n=J(e,t);o(n.node)&&(a=n.node)}}}else{let e=n[0];if(e){let n=J(e,t);o(n.node)&&(a=n.node)}}}if(!a)throw Error(`Failed to resolve parent node in CFI range`);let c=s(a),l,u,d=0,f=0;if(c){l=a,u=a;let e=r[r.length-1],t=i[i.length-1];d=(Array.isArray(e?.offset)?e.offset[0]:e?.offset)??0,f=(Array.isArray(t?.offset)?t.offset[0]:t?.offset)??0}else{let e=r.length===0,t=i.length===0;if(e){if(!a)throw Error(`Failed to resolve parent node in CFI range (start)`);l=a,d=r[0]?.offset??0}else{let e=q(a,r,0,!0);if(!e)throw Error(`Failed to resolve start node in CFI range`);l=e.node;let t=r[r.length-1];d=e.childIndex??(Array.isArray(t?.offset)?t.offset[0]:t?.offset)??0}if(t){if(!a)throw Error(`Failed to resolve parent node in CFI range (end)`);u=a,f=i[0]?.offset??0}else{let e=q(a,i,0,!0);if(!e)throw Error(`Failed to resolve end node in CFI range`);u=e.node;let t=i[i.length-1];f=e.childIndex??(Array.isArray(t?.offset)?t.offset[0]:t?.offset)??0}}let p=K(l,d),m=K(u,f),h=t.createRange();return h.setStart(p.node,p.offset??0),h.setEnd(m.node,m.offset??0),{...H(r[r.length-1]),node:h,isRange:!0,offset:p.offset}}function z(e){if(e?.side)return e.side;if(e?.text&&e.text.length>0){let t=e.text[0];if(t){let e=t.match(/^([ab])$/);if(e)return e[1]}}}function B(e){return e.index%2!=0}function V(e){return(p(e)?e.end:e).at(-1)?.at(-1)?.extensions}function H(e){let t=z(e),n=e?.extensions;return{offset:e?.offset,temporal:e?.temporal,spatial:e?.spatial,side:t,extensions:n}}function U(e,t){return{node:e,isRange:!1,...H(t)}}function W(e,t){return{node:e,isRange:!0,...H(t)}}function G(e,t,n){let r=e.createRange();if(r.selectNodeContents(t),n!==void 0){let e=Array.isArray(n)?n[0]:n;s(t)&&r.setStart(t,e||0)}return r}function K(e,t){let n=Array.isArray(t)?t[0]:t;return n===void 0||!s(e)?{node:e,offset:n}:d(e,n)}function q(e,t,n,r){let i=e;for(let e=n;e<t.length;e++){let n=t[e];if(!i||!n)break;if(B(n)){let a=u(i,n.index);if(!a){if(r)throw Error(`No character data chunk: ${n.index}`);i=null;break}if(a.kind===`boundary`){if(e<t.length-1){if(r)throw Error(`Empty character data chunk: ${n.index}`);i=null;break}return{node:a.parent,childIndex:a.childIndex}}i=a.node}else{let e=Array.from(i.childNodes).filter(e=>e.nodeType===Node.ELEMENT_NODE),t=Math.floor(n.index/2)-1;if(t>=0&&t<e.length){let n=e[t];n&&(i=n)}else{if(r)throw Error(`Invalid element step index: ${n.index}`);i=null;break}}}return i?{node:i}:null}function J(e,t,n={}){let{throwOnError:r=!1,asRange:i=!1}=n;if(!t){if(r)throw Error(`Document is not available`);return U(null)}let{node:a,remainingPathIndex:o}=te(t,e);if(a&&o>=e.length){let n=e.at(-1);return i?W(G(t,a,n?.offset),n):U(a,n)}let s=a||t.documentElement,c=a?o:0;if(i&&e.length>0){let n=e[e.length-1];if(n&&!B(n)){if(n.index===0&&s){let e=t.createRange();return e.setStart(s,0),e.setEnd(s,0),W(e,n)}let i=q(s,e.slice(0,-1),c,r)?.node;if(i){let e=Array.from(i.childNodes).filter(e=>e.nodeType===Node.ELEMENT_NODE);if(Math.floor(n.index/2)-1===e.length){let e=t.createRange();return e.selectNodeContents(i),e.collapse(!1),W(e,n)}}}}let l=q(s,e,c,r);if(!l){if(r)throw Error(`Failed to resolve CFI path`);return U(null)}let u=e.at(-1),d=l.childIndex===void 0?K(l.node,u?.offset):{node:l.node,offset:l.childIndex};return i?{...W(l.childIndex===void 0?G(t,d.node,d.offset):ee(t,l.node,l.childIndex),u),offset:d.offset}:{...U(d.node,u),offset:d.offset}}function ee(e,t,n){let r=e.createRange();return r.setStart(t,n),r.setEnd(t,n),r}function te(e,t){for(let n=t.length-1;n>=0;n--){let r=t[n];if(r?.id){let t=e.getElementById(r.id);if(t)return{node:t,remainingPathIndex:n+1}}}return{node:null,remainingPathIndex:0}}function ne(e){let t=`/${e.index}`;if(e.id){if(t+=`[${r(e.id)}`,e.extensions)for(let[n,i]of Object.entries(e.extensions))t+=`;${n}=${r(i)}`;t+=`]`}e.offset!==void 0&&(t+=`:${e.offset}`),e.temporal!==void 0&&(t+=`~${e.temporal}`),e.spatial&&e.spatial.length>0&&(t+=`@${e.spatial.join(`:`)}`);let n=[];if(e.text&&e.text.length>0&&n.push(e.text.map(r).join(`,`)),(e.side||e.extensions&&!e.id)&&(e.side&&n.push(`;s=${e.side}`),e.extensions&&!e.id))for(let[t,i]of Object.entries(e.extensions))n.push(`;${t}=${r(i)}`);return n.length>0&&(t+=`[${n.join(``)}]`),t}function Y(e){return e.map(e=>ne(e)).join(``)}function X(e){return Array.isArray(e)?`epubcfi(${e.map(Y).join(`!`)})`:`epubcfi(${e.parent.map(Y).join(`!`)},${e.start.map(Y).join(`!`)},${e.end.map(Y).join(`!`)})`}var Z=e=>(e+1)*2,Q=e=>e/2-1,re=e=>p(e)?e.parent[0]:e[0],$=e=>{let t=re(e);if(t?.[0]?.index===6)return t[1]},ie=(e,t)=>{let n={...e};for(let[e,r]of Object.entries(t))r===void 0?delete n[e]:n[e]=r;return Object.keys(n).length>0?n:void 0},ae=e=>{if(e.id!==void 0)return e.id;if(e.text&&e.text.length!==0)return e.text.join(`,`)},oe=e=>{try{let t=$(b(e));return t?{spineIndex:Q(t.index),spineId:ae(t),extensions:t.extensions}:void 0}catch{return}},se=(e,t)=>{try{let n=b(e),r=$(n);return r?(t.spineIndex!==void 0&&(r.index=Z(t.spineIndex)),t.spineId!==void 0&&(r.id=t.spineId,r.text=void 0,r.side=void 0),t.extensions!==void 0&&(r.extensions=ie(r.extensions,t.extensions)),X(n)):void 0}catch{return}};exports.EPUB_CFI_PACKAGE_SPINE_STEP_INDEX=w,exports.compare=C,exports.epubCfiItemrefStepIndexToSpineIndex=Q,exports.generate=F,exports.getEpubCfiSpineItemref=oe,exports.isIndirectionOnly=f,exports.isParsedCfiRange=p,exports.parse=b,exports.resolve=I,exports.resolveExtensions=V,exports.serialize=X,exports.spineIndexToEpubCfiItemrefStepIndex=Z,exports.updateEpubCfiSpineItemref=se;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/utils.ts","../src/parse.ts","../src/compare.ts","../src/constants.ts","../src/generate.ts","../src/resolve.ts","../src/serialize.ts","../src/spineItemref.ts"],"sourcesContent":["import type { CfiRange, ParsedCfi } from \"./parse\"\n\n/**\n * Get all ancestors of a node, including the node itself\n */\nexport function getAncestors(node: Node): Node[] {\n const ancestors: Node[] = [node]\n let current: Node | null = node\n\n while (current.parentNode) {\n ancestors.push(current.parentNode)\n current = current.parentNode\n }\n\n return ancestors\n}\n\n/**\n * Find the closest common ancestor of two nodes\n */\nexport function findCommonAncestor(nodeA: Node, nodeB: Node): Node | null {\n if (nodeA === nodeB) return nodeA\n\n const ancestorsA = getAncestors(nodeA)\n const ancestorsSet = new Set(ancestorsA)\n\n // Start with nodeB and traverse up until we find a common ancestor\n let current: Node | null = nodeB\n while (current) {\n if (ancestorsSet.has(current)) {\n return current\n }\n current = current.parentNode\n }\n\n return null\n}\n\n/**\n * Special characters in CFI that need to be escaped according to the spec\n * These are: [ ] ^ , ( ) ;\n */\nexport const CFI_SPECIAL_CHARS = /[[\\]^,();]/g\n\n/**\n * Escape special characters in a CFI string\n * @param str The string to escape\n * @returns The escaped string\n */\nexport function cfiEscape(str: string): string {\n return str.replace(CFI_SPECIAL_CHARS, `^$&`)\n}\n\n/**\n * Regular expression to check if a string is a valid CFI\n */\nexport const isCFI = /^epubcfi\\((.*)\\)$/\n\n/**\n * @important Make it non browser runtime specific\n */\nexport const isElement = (node: Node): node is Element =>\n node.nodeType === Node.ELEMENT_NODE\n\n/**\n * @important Make it non browser runtime specific\n */\n// biome-ignore lint/suspicious/noExplicitAny: TODO\nexport const isNode = (node: any): node is Node =>\n typeof node === \"object\" &&\n node !== null &&\n \"nodeType\" in node &&\n (node.nodeType === Node.ELEMENT_NODE ||\n node.nodeType === Node.TEXT_NODE ||\n node.nodeType === Node.CDATA_SECTION_NODE)\n\n/**\n * Text and CDATA nodes: the character data a CFI step can address\n * (EPUB CFI 3.1.1). Comments and processing instructions are ignored.\n */\nexport const isCharacterData = (node: Node): node is CharacterData =>\n node.nodeType === Node.TEXT_NODE || node.nodeType === Node.CDATA_SECTION_NODE\n\n/**\n * The even step of an element: twice its 1-based position among its parent's\n * element children.\n */\nexport const getElementStep = (node: Node): number => {\n const parent = node.parentNode\n let elements = 0\n\n for (let i = 0; parent && i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child) break\n if (isElement(child)) elements++\n if (child === node) break\n }\n\n return elements * 2\n}\n\n/**\n * The step of the character data chunk a node belongs to, and the number of\n * characters that chunk holds before the node.\n *\n * Character data between two element children forms one chunk with an odd\n * index (1 before the first element, 3 after it, and so on), whatever the\n * number of DOM nodes it spans: adjacent text nodes, CDATA sections and the\n * comments between them all belong to the same chunk, and a character offset\n * counts from its start.\n */\nexport const getCharacterDataStep = (\n node: Node,\n): { step: number; base: number } => {\n const parent = node.parentNode\n let elementsBefore = 0\n let base = 0\n\n for (let i = 0; parent && i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child || child === node) break\n\n if (isElement(child)) {\n elementsBefore++\n base = 0\n } else if (isCharacterData(child)) {\n base += child.data.length\n }\n }\n\n return { step: elementsBefore * 2 + 1, base }\n}\n\n/**\n * What an odd step addresses under `parent`: the first node of its chunk of\n * character data, or, when that chunk is empty, the boundary it stands for in\n * the parent (the child index right after the preceding element, 0 before\n * the first one). `undefined` when the parent has fewer element children\n * than the step needs.\n */\nexport type CharacterDataChunk =\n | { kind: \"node\"; node: CharacterData }\n | { kind: \"boundary\"; parent: Node; childIndex: number }\n\nexport const findCharacterDataChunk = (\n parent: Node,\n step: number,\n): CharacterDataChunk | undefined => {\n const chunk = (step - 1) / 2\n let elementsBefore = 0\n let childIndex = 0\n\n for (let i = 0; i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child) break\n\n if (isElement(child)) {\n elementsBefore++\n if (elementsBefore > chunk) break\n childIndex = i + 1\n } else if (elementsBefore === chunk && isCharacterData(child)) {\n return { kind: \"node\", node: child }\n }\n }\n\n return elementsBefore < chunk\n ? undefined\n : { kind: \"boundary\", parent, childIndex }\n}\n\n/**\n * The node and local offset a chunk offset lands on, counting from `first`,\n * the chunk's first node. A boundary between two nodes lands at the start of\n * the later one; an offset past the chunk keeps its excess on the last node.\n */\nexport const locateInCharacterDataChunk = (\n first: CharacterData,\n offset: number,\n): { node: CharacterData; offset: number } => {\n let node = first\n let base = 0\n let sibling: Node | null = first\n\n while (sibling && !isElement(sibling)) {\n if (isCharacterData(sibling)) {\n node = sibling\n\n if (offset < base + sibling.data.length) {\n return { node, offset: offset - base }\n }\n\n base += sibling.data.length\n }\n\n sibling = sibling.nextSibling\n }\n\n return { node, offset: offset - (base - node.data.length) }\n}\n\n/**\n * Checks if a parsed CFI only contains indirection with no further path\n * For example: epubcfi(/6/2[cover]!)\n */\nexport function isIndirectionOnly(parsed: ParsedCfi): boolean {\n // If it's a range, it can't be just indirection\n if (isParsedCfiRange(parsed)) {\n return false\n }\n\n // For an indirection-only CFI:\n // 1. It must have at least one part\n // 2. It must end with an indirection marker (!)\n // 3. There must be no content after the indirection marker\n\n // Check if there's indirection (marked by presence of multiple parts)\n // AND the last part is empty (nothing after the indirection marker)\n const lastPart = parsed[parsed.length - 1]\n\n return parsed.length > 1 && (lastPart === undefined || lastPart.length === 0)\n}\n\n/**\n * Check if a parsed CFI is a range\n */\nexport function isParsedCfiRange(parsed: ParsedCfi): parsed is CfiRange {\n return (\n parsed !== null &&\n typeof parsed === \"object\" &&\n \"parent\" in parsed &&\n \"start\" in parsed &&\n \"end\" in parsed\n )\n}\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n * Based on the EPUB CFI 1.1 specification: https://idpf.org/epub/linking/cfi/epub-cfi.html\n */\n\nimport { isCFI } from \"./utils\"\n\n/**\n * Interface for a parsed CFI part\n *\n * According to the EPUB CFI 1.1 specification, each step in a CFI path can have\n * its own properties including ID assertions, character offsets, and extension parameters.\n * Extensions are parameters in the form of key-value pairs that can be attached to any\n * step in the CFI path to provide additional information or metadata about that specific step.\n */\nexport interface CfiPart {\n index: number\n id?: string\n offset?: number\n temporal?: number\n spatial?: number[]\n text?: string[]\n side?: string\n /**\n * Extension parameters for this CFI path step\n *\n * The EPUB CFI spec allows for extension parameters to be attached to any step in the path.\n * These are key-value pairs that provide additional information or metadata.\n * For example, in /6/4[chap01ref]!/4[body01]/10[para05]/2/1:3[;vnd.example.param=value],\n * the extension parameter is \"vnd.example.param\" with value \"value\".\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Interface for a parsed CFI range\n */\nexport interface CfiRange {\n parent: CfiPart[][]\n start: CfiPart[][]\n end: CfiPart[][]\n}\n\n/**\n * Interface for a parsed CFI\n */\nexport type ParsedCfi = CfiPart[][] | CfiRange\n\n/**\n * Unwrap a CFI string from the epubcfi() function\n * @param cfi The CFI string to unwrap\n * @returns The unwrapped CFI string\n */\nexport function unwrapCfi(cfi: string): string {\n const match = cfi.match(isCFI)\n return match ? match[1] || cfi : cfi\n}\n\n/**\n * Token type for CFI parsing\n */\ntype CfiToken = [string, string | number]\n\n/**\n * Tokenize a CFI string into an array of tokens\n * @param cfi The CFI string to tokenize\n * @returns An array of tokens\n */\nfunction tokenize(cfi: string): CfiToken[] {\n const tokens: CfiToken[] = []\n let state: string | null = null\n let isEscaped = false\n let value = \"\"\n\n const push = (token: CfiToken) => {\n tokens.push(token)\n state = null\n value = \"\"\n }\n\n const cat = (c: string) => {\n value += c\n isEscaped = false\n }\n\n const unwrappedCfi = unwrapCfi(cfi).trim()\n const chars = Array.from(unwrappedCfi).concat(\"\")\n\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]\n\n if (!char) {\n // End of string, push any pending token\n if (state === \"/\" || state === \":\") {\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n push([\"[\", value])\n } else if (state === \";\" || state?.startsWith(\";\")) {\n push([state, value])\n } else if (state === \"!\") {\n // Make sure to push the '!' token at the end of the string\n push([\"!\", 0])\n }\n break\n }\n\n // Handle escape characters\n if (char === \"^\" && !isEscaped) {\n isEscaped = true\n continue\n }\n\n if (state === \"!\") {\n push([\"!\", 0])\n } else if (state === \",\") {\n push([\",\", 0])\n } else if (state === \"/\" || state === \":\") {\n if (/^\\d$/.test(char)) {\n cat(char)\n continue\n }\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n if (char === \":\") {\n push([\"@\", parseFloat(value)])\n state = \"@\"\n continue\n }\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n if (char === \";\" && !isEscaped) {\n push([\"[\", value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([\"[\", value])\n } else {\n cat(char)\n continue\n }\n } else if (state === \";\") {\n // Handle extension parameter key\n if (char === \"=\" && !isEscaped) {\n state = `;${value}`\n value = \"\"\n } else if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state?.startsWith(\";\")) {\n // Handle extension parameter value\n if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state === null && char === \";\") {\n // Handle standalone extension parameters (not inside brackets)\n state = \";\"\n }\n\n if (\n char === \"/\" ||\n char === \":\" ||\n char === \"~\" ||\n char === \"@\" ||\n char === \"[\" ||\n char === \"!\" ||\n char === \",\"\n ) {\n state = char\n }\n }\n\n return tokens\n}\n\n/**\n * Find indices of tokens with a specific type\n * @param tokens The tokens to search\n * @param type The type to find\n * @returns An array of indices\n */\nfunction findTokenIndices(\n tokens: CfiToken[] | undefined,\n type: string,\n): number[] {\n if (!tokens) {\n return []\n }\n\n return tokens\n .map((token, i) => (token[0] === type ? i : null))\n .filter((i): i is number => i !== null)\n}\n\n/**\n * Split an array at specific indices\n * @param arr The array to split\n * @param indices The indices to split at\n * @returns An array of arrays\n */\nfunction splitAt<T>(arr: T[], indices: number[]): T[][] {\n const result: T[][] = []\n let start = 0\n\n for (const index of indices) {\n result.push(arr.slice(start, index))\n start = index\n }\n\n result.push(arr.slice(start))\n return result\n}\n\n/**\n * Parse a single part of a CFI\n * @param tokens The tokens to parse\n * @returns An array of CFI parts\n */\nfunction parsePart(tokens: CfiToken[]): CfiPart[] {\n const parts: CfiPart[] = []\n\n // Group tokens by path step\n const pathStepTokens: { [key: number]: CfiToken[] } = {}\n let currentPathStep = -1\n\n // First pass: group tokens by path step\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \"/\") {\n currentPathStep++\n parts[currentPathStep] = { index: val as number }\n pathStepTokens[currentPathStep] = []\n } else if (currentPathStep >= 0) {\n pathStepTokens[currentPathStep]?.push(token)\n }\n }\n\n // Second pass: process tokens for each path step\n for (let stepIndex = 0; stepIndex < parts.length; stepIndex++) {\n const currentPart = parts[stepIndex]\n if (!currentPart) continue\n\n const stepsTokens = pathStepTokens[stepIndex] || []\n\n for (let i = 0; i < stepsTokens.length; i++) {\n const token = stepsTokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \":\") {\n currentPart.offset = val as number\n } else if (type === \"~\") {\n currentPart.temporal = val as number\n } else if (type === \"@\") {\n currentPart.spatial = (currentPart.spatial || []).concat(val as number)\n } else if (type === \";s\") {\n currentPart.side = val as string\n } else if (type.startsWith(\";\") && type !== \";s\") {\n // This is an extension parameter\n const paramName = type.substring(1)\n if (!currentPart.extensions) {\n currentPart.extensions = {}\n }\n currentPart.extensions[paramName] = val as string\n } else if (type === \"[\") {\n // Determine if this is an ID or text assertion\n const looksLikeId =\n typeof val === \"string\" && !val.includes(\" \") && val.length < 50\n\n if (i === 0 && looksLikeId && !currentPart.id) {\n currentPart.id = val as string\n } else {\n // Otherwise, it's a text assertion\n if (val !== \"\") {\n // Split on comma and trim each part\n const parts = (val as string).split(\",\").map((part) => part.trim())\n currentPart.text = parts\n }\n }\n }\n }\n }\n\n return parts\n}\n\n/**\n * Parse a CFI with indirections\n * @param tokens The tokens to parse\n * @returns An array of arrays of CFI parts\n */\nfunction parseIndirection(tokens: CfiToken[]): CfiPart[][] {\n const indirectionIndices = findTokenIndices(tokens, \"!\")\n\n return splitAt(tokens, indirectionIndices).map(parsePart)\n}\n\n/**\n * Parse a CFI string into a structured representation\n * @param cfi The CFI string to parse\n * @returns A parsed CFI\n */\nexport function parse(cfi: string): ParsedCfi {\n if (!cfi) {\n throw new Error(\"CFI string cannot be empty\")\n }\n\n const tokens = tokenize(cfi)\n if (!tokens || tokens.length === 0) {\n throw new Error(\"Failed to tokenize CFI string\")\n }\n\n const commaIndices = findTokenIndices(tokens, \",\")\n\n if (commaIndices.length === 0) {\n return parseIndirection(tokens)\n }\n\n const [parentTokens, startTokens, endTokens] = splitAt(tokens, commaIndices)\n\n // Patch: If startTokens or endTokens are just offsets, attach them to the last parent path\n function attachOffsetToParent(\n parent: CfiPart[][],\n offsetTokens: CfiToken[],\n ): CfiPart[][] {\n if (!offsetTokens || offsetTokens.length === 0) return [[]]\n\n // Filter out comma tokens and check if the remaining tokens are just an offset\n const filteredTokens = offsetTokens.filter((token) => token[0] !== \",\")\n\n // Only support a single offset (e.g., [:9] or [:25])\n if (\n filteredTokens.length === 1 &&\n filteredTokens[0] &&\n filteredTokens[0][0] === \":\"\n ) {\n // Clone the last parent path\n const lastParentPath =\n parent.length > 0 ? parent[parent.length - 1] : undefined\n const offsetToken = filteredTokens[0]\n if (lastParentPath && lastParentPath.length > 0 && offsetToken) {\n const pathClone = lastParentPath.map((part) => ({ ...part }))\n const lastPart = pathClone[pathClone.length - 1]\n if (lastPart) {\n lastPart.offset = offsetToken[1] as number\n }\n return [pathClone]\n }\n // fallback: just return an empty path\n return [[]]\n }\n // If not just an offset, parse as normal\n return parseIndirection(offsetTokens)\n }\n\n const parent = parseIndirection(parentTokens || [])\n const start = attachOffsetToParent(parent, startTokens || [])\n const end = attachOffsetToParent(parent, endTokens || [])\n\n return {\n parent,\n start,\n end,\n }\n}\n","import { type CfiPart, type ParsedCfi, parse } from \"./parse\"\n\n/**\n * Collapses a parsed CFI to a single path (private helper for compare)\n * @param parsed The parsed CFI to collapse\n * @param toEnd Whether to collapse to the end of a range\n * @returns A collapsed CFI\n */\nfunction collapse(parsed: ParsedCfi, toEnd = false): CfiPart[][] {\n if (typeof parsed === \"string\") {\n return collapse(parse(parsed), toEnd)\n }\n\n if (\"parent\" in parsed) {\n // It's a range\n if (toEnd) {\n return parsed.parent.concat(parsed.end)\n }\n return parsed.parent.concat(parsed.start)\n }\n\n // It's a single CFI\n return parsed\n}\n\n/**\n * Get the weight of a step type for sorting\n * According to rule 9: character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n */\nfunction getStepTypeWeight(part: CfiPart): number {\n if (part.offset !== undefined) return 1 // character offset (:)\n if (part.index !== undefined) return 2 // child (/)\n if (part.temporal !== undefined || part.spatial !== undefined) return 3 // temporal-spatial (~ or @)\n return 4 // reference (!)\n}\n\n/**\n * Compare two CFIs according to the EPUB CFI specification sorting rules (section 3.2)\n * @param a The first CFI\n * @param b The second CFI\n * @returns -1 if a < b, 0 if a = b, 1 if a > b\n */\nexport function compare(a: ParsedCfi | string, b: ParsedCfi | string): number {\n const aParsed = typeof a === \"string\" ? parse(a) : a\n const bParsed = typeof b === \"string\" ? parse(b) : b\n\n if (\"parent\" in aParsed || \"parent\" in bParsed) {\n // At least one is a range\n return (\n compare(collapse(aParsed), collapse(bParsed)) ||\n compare(collapse(aParsed, true), collapse(bParsed, true))\n )\n }\n\n // Both are single CFIs\n for (let i = 0; i < Math.max(aParsed.length, bParsed.length); i++) {\n const p = aParsed[i] || []\n const q = bParsed[i] || []\n const maxIndex = Math.max(p.length, q.length) - 1\n\n for (let i = 0; i <= maxIndex; i++) {\n const x = p[i]\n const y = q[i]\n\n if (!x) return -1\n if (!y) return 1\n\n // Compare element indices (rule 3 & 4)\n // The step index locates the node in the document, so it always wins.\n if (x.index > y.index) return 1\n if (x.index < y.index) return -1\n\n // Compare step types (rule 9), only as a tie-breaker between steps at\n // the same index (eg: an offset terminating at a node vs a child step\n // descending into it)\n // character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n const xStepType = getStepTypeWeight(x)\n const yStepType = getStepTypeWeight(y)\n\n if (xStepType !== yStepType) {\n return xStepType - yStepType\n }\n\n // Compare temporal positions (rule 7 & 8)\n // Temporal is more important than spatial\n const xTemporal = x.temporal !== undefined\n const yTemporal = y.temporal !== undefined\n\n if (xTemporal && !yTemporal) return 1\n if (!xTemporal && yTemporal) return -1\n\n if (xTemporal && yTemporal) {\n if ((x.temporal ?? 0) > (y.temporal ?? 0)) return 1\n if ((x.temporal ?? 0) < (y.temporal ?? 0)) return -1\n }\n\n // Compare spatial positions (rule 5 & 6)\n const xSpatial = x.spatial !== undefined\n const ySpatial = y.spatial !== undefined\n\n if (xSpatial && !ySpatial) return 1\n if (!xSpatial && ySpatial) return -1\n\n if (xSpatial && ySpatial) {\n // Y position is more important than X (rule 5)\n const xY = x.spatial?.[1] ?? 0\n const yY = y.spatial?.[1] ?? 0\n\n if (xY > yY) return 1\n if (xY < yY) return -1\n\n // Compare X positions if Y positions are equal\n const xX = x.spatial?.[0] ?? 0\n const yX = y.spatial?.[0] ?? 0\n\n if (xX > yX) return 1\n if (xX < yX) return -1\n }\n\n // Last part comparison including character offsets\n if (i === maxIndex) {\n if ((x.offset ?? 0) > (y.offset ?? 0)) return 1\n if ((x.offset ?? 0) < (y.offset ?? 0)) return -1\n }\n }\n }\n\n return 0\n}\n","/**\n * Standard EPUB CFIs that target a content document first address the\n * package document spine element.\n *\n * EPUB package documents order their element children as metadata, manifest, then spine.\n * CFI element steps are even-numbered, so the third element child is step 6.\n */\nexport const EPUB_CFI_PACKAGE_SPINE_STEP_INDEX = 6\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n */\n\nimport {\n cfiEscape,\n findCommonAncestor,\n getCharacterDataStep,\n getElementStep,\n isCharacterData,\n isElement,\n isNode,\n} from \"./utils\"\n\n/**\n * Options for generating CFIs\n */\nexport interface GenerateOptions {\n /**\n * Whether to include text assertions for more robust CFIs\n */\n includeTextAssertions?: boolean\n\n /**\n * The maximum length of text to use for text assertions\n * Default is 10 characters\n */\n textAssertionLength?: number\n\n /**\n * Whether to include a side bias assertion\n */\n includeSideBias?: \"before\" | \"after\"\n\n /**\n * Whether to include spatial coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatialOffset?: [number, number]\n\n /**\n * Extension parameters to include in the CFI\n * Keys should be parameter names, values should be the parameter values\n * Vendor-specific parameters should be prefixed with 'vnd.' followed by the vendor name\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Position in a document, consisting of a node and optional offset\n */\nexport interface CfiPosition {\n /**\n * The DOM node\n */\n node?: Node | null\n\n /**\n * Character offset within the node (for text nodes)\n */\n offset?: number\n\n /**\n * Temporal position in seconds (for audio/video content)\n */\n temporal?: number\n\n /**\n * Spatial position as [x,y] coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatial?: [number, number]\n\n /**\n * Spine index (0-based) for the document containing the node\n */\n spineIndex?: number\n\n /**\n * ID of the spine item\n */\n spineId?: string\n}\n\n/**\n * Extract a suitable text assertion from a text node\n */\nfunction extractTextAssertion(\n textNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n): string | null {\n if (!textNode.textContent || textNode.textContent.trim() === \"\") {\n return null\n }\n\n const textContent = textNode.textContent\n const maxLength = options.textAssertionLength || 10\n\n // If we have an offset, use the text around that position\n if (offset !== undefined && offset <= textContent.length) {\n // We'll take a portion before and after the offset\n const halfLength = Math.floor(maxLength / 2)\n const start = Math.max(0, offset - halfLength)\n const end = Math.min(textContent.length, offset + halfLength)\n return textContent.substring(start, end)\n }\n\n // Otherwise, just take the first part of the text\n return textContent.substring(0, Math.min(textContent.length, maxLength))\n}\n\n/**\n * Helper function to format spatial coordinates\n */\nfunction formatSpatialOffset(spatial: [number, number]): string {\n const [x, y] = spatial\n // Ensure values are within 0-100 range\n const safeX = Math.max(0, Math.min(100, x))\n const safeY = Math.max(0, Math.min(100, y))\n return `@${safeX}:${safeY}`\n}\n\n/**\n * Helper function to add temporal and spatial offsets to a CFI string\n */\nfunction addOffsets(\n cfi: string,\n temporal?: number,\n spatial?: [number, number],\n): string {\n let result = cfi\n\n if (temporal !== undefined) {\n result += `~${temporal}`\n }\n\n if (spatial !== undefined) {\n result += formatSpatialOffset(spatial)\n }\n\n return result\n}\n\n/**\n * Helper function to add text assertion and side bias to a CFI string\n */\nfunction addTextAssertionAndSideBias(\n cfi: string,\n textAssertion: string | null,\n sideBias?: \"before\" | \"after\",\n): string {\n let result = cfi\n\n if (textAssertion) {\n result += `[${cfiEscape(textAssertion)}]`\n }\n\n if (sideBias) {\n const sideBiasChar = sideBias === \"before\" ? \"b\" : \"a\"\n if (!textAssertion) {\n result += `[;s=${sideBiasChar}]`\n } else {\n result = `${result.substring(0, result.length - 1)};s=${sideBiasChar}]`\n }\n }\n\n return result\n}\n\n/**\n * Generate a CFI for a single node in the DOM\n */\nfunction generatePoint(\n node: Node | null,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n let cfi = \"\"\n let currentNode: Node | null = node\n let textNode: Node | null = null\n let cfiOffset = offset\n\n // Character data is addressed through the chunk of its parent it belongs\n // to, and the offset counts from the start of that chunk (EPUB CFI 3.1.1).\n // Any other non-element node (a comment) is ignored by the spec, so it gets\n // the position of the chunk it sits in.\n if (node && !isElement(node)) {\n const parentNode = node.parentNode\n\n if (!parentNode) {\n throw new Error(\"Text node doesn't have a parent\")\n }\n\n const { step, base } = getCharacterDataStep(node)\n\n cfi = `/${step}`\n textNode = isCharacterData(node) ? node : null\n cfiOffset = offset === undefined ? undefined : base + offset\n currentNode = parentNode\n }\n\n // Set up text assertion if needed\n let textAssertion: string | null = null\n if (textNode && options.includeTextAssertions) {\n textAssertion = extractTextAssertion(textNode, offset, options)\n }\n\n // Build the CFI path from the current node up to the html element\n while (currentNode?.parentNode) {\n const step = getElementStep(currentNode)\n\n // If the node has an ID, add it to the CFI\n if (isElement(currentNode) && currentNode.id) {\n cfi = `/${step}[${cfiEscape(currentNode.id)}]${cfi}`\n } else {\n cfi = `/${step}${cfi}`\n }\n\n // If we've reached the html element, stop traversing up\n if (currentNode.parentNode.nodeName.toLowerCase() === \"html\") {\n break\n }\n\n currentNode = currentNode.parentNode\n }\n\n // Add the character offset if provided\n if (cfiOffset !== undefined) {\n cfi += `:${cfiOffset}`\n }\n\n // Add temporal and spatial offsets using helper\n cfi = addOffsets(\n cfi,\n position?.temporal,\n position?.spatial || options.spatialOffset,\n )\n\n // Add text assertion and side bias using helper\n cfi = addTextAssertionAndSideBias(cfi, textAssertion, options.includeSideBias)\n\n cfi = addExtensions(cfi, options.extensions)\n\n return cfi\n}\n\n/**\n * Generate a relative path from one node to another\n */\nfunction generateRelativePath(\n fromNode: Node,\n toNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n // A character offset counts from the start of the chunk the node belongs to\n const base = isElement(toNode) ? 0 : getCharacterDataStep(toNode).base\n\n if (fromNode === toNode) {\n let result = offset !== undefined ? `:${base + offset}` : \"\"\n result = addOffsets(result, position?.temporal, position?.spatial)\n return result\n }\n\n const path: string[] = []\n let currentNode: Node | null = toNode\n\n // Build path from toNode up to fromNode (exclusive)\n while (currentNode && currentNode !== fromNode) {\n const parentNode: Node | null = currentNode.parentNode\n if (!parentNode) break\n\n const step = isElement(currentNode)\n ? getElementStep(currentNode)\n : getCharacterDataStep(currentNode).step\n\n // If the node has an ID, add it\n if (isElement(currentNode) && currentNode.id) {\n path.unshift(`/${step}[${cfiEscape(currentNode.id)}]`)\n } else {\n path.unshift(`/${step}`)\n }\n\n currentNode = parentNode\n }\n\n let relativePath = path.join(\"\")\n\n // Add offset if specified\n if (offset !== undefined) {\n relativePath += `:${base + offset}`\n }\n\n // Add temporal and spatial offsets using helper\n relativePath = addOffsets(relativePath, position?.temporal, position?.spatial)\n\n // Add text assertion and side bias using helper\n if (options.includeTextAssertions && isCharacterData(toNode)) {\n const textAssertion = extractTextAssertion(toNode, offset, options)\n relativePath = addTextAssertionAndSideBias(\n relativePath,\n textAssertion,\n options.includeSideBias,\n )\n }\n\n // Add extension parameters if specified\n relativePath = addExtensions(relativePath, options.extensions)\n\n return relativePath\n}\n\nconst serializeExtensions = (extensions: Record<string, string>) => {\n return Object.entries(extensions)\n .map(([key, value]) => {\n // Properly escape the value according to CFI spec\n const escapedValue = cfiEscape(value)\n // URL encode the value to handle special characters\n return `${key}=${encodeURIComponent(escapedValue)}`\n })\n .join(\";\")\n}\n\n/**\n * Add extension parameters to a CFI path\n */\nfunction addExtensions(\n cfi: string,\n extensions?: Record<string, string>,\n): string {\n if (!extensions || Object.keys(extensions).length === 0) {\n return cfi\n }\n\n const extensionString = serializeExtensions(extensions)\n\n // If we're dealing with a bracket at end\n if (cfi.endsWith(\"]\")) {\n // Check if there are already parameters\n const lastBracketIndex = cfi.lastIndexOf(\"[\")\n const content = cfi.substring(lastBracketIndex + 1, cfi.length - 1)\n\n // If content already has these exact extensions, return as is\n if (content.includes(extensionString)) {\n return cfi\n }\n\n // Add extensions with proper separator\n return `${cfi.substring(0, cfi.length - 1)}${content.includes(\";\") ? \";\" : \";\"}${extensionString}]`\n }\n\n // Special case for spine items with indirection\n if (cfi.endsWith(\"!\")) {\n return cfi\n }\n\n // No bracket at the end - add with new brackets\n return `${cfi}[;${extensionString}]`\n}\n\n/**\n * Generate a range CFI between two points in the document\n */\nfunction generateRange(\n startNode: Node,\n startOffset: number,\n endNode: Node,\n endOffset: number,\n options: GenerateOptions = {},\n startPosition?: CfiPosition,\n endPosition?: CfiPosition,\n): string {\n // Find common ancestor\n const ancestor = findCommonAncestor(startNode, endNode)\n\n if (!ancestor) {\n throw new Error(\"No common ancestor found\")\n }\n\n // Generate CFI from ancestor to document\n const ancestorCfi = generatePoint(ancestor, undefined, options)\n\n // Generate path from ancestor to start node\n const startPath = generateRelativePath(\n ancestor,\n startNode,\n startOffset,\n options,\n startPosition,\n )\n\n // Generate path from ancestor to end node\n const endPath = generateRelativePath(\n ancestor,\n endNode,\n endOffset,\n options,\n endPosition,\n )\n\n // For range CFIs, add extensions to each part separately\n if (options.extensions && Object.keys(options.extensions).length > 0) {\n // Add extensions to each part\n const ancestorWithExt = addExtensions(ancestorCfi, options.extensions)\n const startWithExt = addExtensions(startPath, options.extensions)\n const endWithExt = addExtensions(endPath, options.extensions)\n\n return `${ancestorWithExt},${startWithExt},${endWithExt}`\n }\n\n // Combine into a regular range CFI without extensions\n return `${ancestorCfi},${startPath},${endPath}`\n}\n\nconst generateSpineCfi = (\n spineIndex: number,\n spineId?: string,\n options: GenerateOptions = {},\n isFinal?: boolean,\n) => {\n const bracket = \"\"\n const cfiIndex = (spineIndex + 1) * 2\n let cfi = `/6/${cfiIndex}`\n\n if (spineId) {\n cfi += `[${cfiEscape(spineId)}]`\n }\n\n cfi = isFinal ? addExtensions(cfi, options.extensions) : cfi\n\n return `${cfi}${bracket}!`\n}\n\n/**\n * Generate a CFI from a DOM node or position\n *\n * @example\n * // Generate CFI for a single node\n * const cfi = generate(node);\n *\n * @example\n * // Generate CFI for a text node with offset\n * const cfi = generate({ node: textNode, offset: 5 });\n *\n * @example\n * // Generate CFI for a video with temporal offset\n * const cfi = generate({ node: videoElement, temporal: 45.5 });\n *\n * @example\n * // Generate CFI for an image with spatial coordinates\n * const cfi = generate({ node: imageElement, spatial: [50, 75] });\n *\n * @example\n * // Generate a range CFI\n * const cfi = generate({\n * start: { node: startNode, offset: 0 },\n * end: { node: endNode, offset: 10 }\n * });\n *\n * @example\n * // Generate a CFI with spine itemref reference\n * const cfi = generate({\n * node: chapterNode,\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a CFI for a spine item without a node\n * const cfi = generate({\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a robust CFI with text assertions\n * const cfi = generate(node, {\n * includeTextAssertions: true,\n * textAssertionLength: 15\n * });\n */\nexport function generate(\n position: Node | CfiPosition | { start: CfiPosition; end: CfiPosition },\n options: GenerateOptions = {},\n): string {\n // Case 1: Simple Node\n if (isNode(position)) {\n return `epubcfi(${generatePoint(position, undefined, options)})`\n }\n\n let cfi = \"\"\n\n // Non Range case\n if (!(\"start\" in position)) {\n // Add spine itemref reference if specified\n if (position.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n position.spineIndex,\n position.spineId,\n options,\n !position.node,\n )\n\n if (!position.node) {\n return `epubcfi(${cfi})`\n }\n }\n\n // Add the node path\n cfi += generatePoint(\n position.node ?? null,\n position.offset,\n options,\n position,\n )\n\n return `epubcfi(${cfi})`\n }\n\n // Range case\n const { start, end } = position\n\n // Add spine itemref reference if specified (use start position's spine info)\n if (start.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n start.spineIndex,\n start.spineId,\n options,\n !start.node,\n )\n }\n\n if (start.node && end.node) {\n // Add the range path\n cfi += generateRange(\n start.node,\n start.offset ?? 0,\n end.node,\n end.offset ?? 0,\n options,\n start,\n end,\n )\n }\n\n return `epubcfi(${cfi})`\n}\n","import { type CfiPart, type CfiRange, type ParsedCfi, parse } from \"./parse\"\nimport {\n findCharacterDataChunk,\n isCharacterData,\n isIndirectionOnly,\n isNode,\n isParsedCfiRange,\n locateInCharacterDataChunk,\n} from \"./utils\"\n\n/**\n * Options for resolving a CFI\n */\ninterface ResolveOptions {\n /**\n * Whether to throw an error if the CFI cannot be resolved\n * @default false\n */\n throwOnError?: boolean\n\n /**\n * Whether to return a range instead of a single node\n * @default false\n */\n asRange?: boolean\n}\n\n/**\n * Result of resolving a CFI\n */\ninterface ResolveRangeResult extends ResolveResultBase {\n node: Range | null\n isRange: true\n}\n\ninterface ResolveResultBase {\n offset?: number[] | number\n\n /**\n * The temporal offset if applicable\n */\n temporal?: number\n\n /**\n * The spatial offset if applicable\n */\n spatial?: number[]\n\n /**\n * The side bias if applicable\n */\n side?: string\n\n /**\n * Any extension parameters in the CFI\n */\n extensions?: Record<string, string>\n}\n\ninterface ResolveNodeResult extends ResolveResultBase {\n node: Node | null\n isRange: false\n}\n\ntype ResolveResult = ResolveNodeResult | ResolveRangeResult\n\n/**\n * Resolves a CFI string to a DOM node or range\n */\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options: Omit<ResolveOptions, \"asRange\"> & { asRange: true },\n): ResolveRangeResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options?: ResolveOptions,\n): ResolveResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n try {\n const parsedCfi = typeof cfi !== \"string\" ? cfi : parse(cfi)\n\n const result = resolveParsed(parsedCfi, document, options)\n\n return result\n } catch (error) {\n if (options.throwOnError) {\n throw error\n }\n\n return { node: null, isRange: false }\n }\n}\n\n/**\n * Resolves a parsed CFI to a DOM node or range\n */\nfunction resolveParsed(\n parsed: ParsedCfi,\n document: Document,\n options: { asRange?: boolean } = { asRange: false },\n): ResolveResult {\n if (isParsedCfiRange(parsed)) {\n // If it's a range CFI, always return a Range\n return resolveRange(parsed, document)\n }\n\n // Check if this is a CFI with only indirection (e.g., \"epubcfi(/6/2[cover]!)\")\n if (isIndirectionOnly(parsed)) {\n // According to the spec, we cannot resolve beyond the indirection point\n // so we return null for the node\n return createNodeResultObject(null)\n }\n\n const nonIndirectionPart = parsed.at(-1)\n\n // Handle path CFI (indirection)\n if (nonIndirectionPart) {\n return resolvePath(nonIndirectionPart, document, options)\n }\n\n throw new Error(\"Invalid CFI structure\")\n}\n\n/**\n * Resolves a CFI range to a DOM range\n */\nfunction resolveRange(range: CfiRange, document: Document): ResolveResult {\n // Get the parent paths and start/end paths\n const parentPaths = range.parent\n const startPath = range.start[0] || []\n const endPath = range.end[0] || []\n\n // Find the parent node\n let parentNode: Node | null = document.documentElement\n\n if (parentPaths.length > 0) {\n // If there's indirection (multiple parent paths), resolve the indirection first\n if (parentPaths.length > 1) {\n const indirectionPath = parentPaths[0]\n if (indirectionPath) {\n const indirectionResult = resolvePath(indirectionPath, document)\n if (isNode(indirectionResult.node)) {\n parentNode = indirectionResult.node\n }\n }\n if (parentPaths.length > 1 && parentNode) {\n const actualParentPath = parentPaths[1]\n if (actualParentPath) {\n const actualParentResult = resolvePath(actualParentPath, document)\n if (isNode(actualParentResult.node)) {\n parentNode = actualParentResult.node\n }\n }\n }\n } else {\n const parentPath = parentPaths[0]\n if (parentPath) {\n const parentResult = resolvePath(parentPath, document)\n if (isNode(parentResult.node)) {\n parentNode = parentResult.node\n }\n }\n }\n }\n\n if (!parentNode) {\n throw new Error(\"Failed to resolve parent node in CFI range\")\n }\n\n // If parentNode is a text node, use it directly for start/end\n const isParentTextNode = isCharacterData(parentNode)\n\n let startNode: Node\n let endNode: Node\n let startOffset = 0\n let endOffset = 0\n\n if (isParentTextNode) {\n startNode = parentNode\n endNode = parentNode\n // Use offsets from the last part of startPath/endPath\n const lastStartPart = startPath[startPath.length - 1]\n const lastEndPart = endPath[endPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n } else {\n // Offset-only local paths (`,:5`) are attached to the parent path during\n // parsing, so a non-empty path here always carries real steps that must be\n // traversed — a single step with an offset (`/1:2`) is a path to a child\n // node, not an offset on the parent.\n const isStartOffsetOnly = startPath.length === 0\n const isEndOffsetOnly = endPath.length === 0\n\n if (isStartOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (start)\")\n startNode = parentNode\n startOffset = startPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, startPath, 0, true)\n if (!traversed)\n throw new Error(\"Failed to resolve start node in CFI range\")\n startNode = traversed.node\n const lastStartPart = startPath[startPath.length - 1]\n startOffset =\n traversed.childIndex ??\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ??\n 0\n }\n\n if (isEndOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (end)\")\n endNode = parentNode\n endOffset = endPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, endPath, 0, true)\n if (!traversed) throw new Error(\"Failed to resolve end node in CFI range\")\n endNode = traversed.node\n const lastEndPart = endPath[endPath.length - 1]\n endOffset =\n traversed.childIndex ??\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ??\n 0\n }\n }\n\n // A chunk offset can fall in a node after the chunk's first one\n const start = locate(startNode, startOffset)\n const end = locate(endNode, endOffset)\n\n // Create and return a DOM range\n const domRange = document.createRange()\n domRange.setStart(start.node, start.offset ?? 0)\n domRange.setEnd(end.node, end.offset ?? 0)\n\n return {\n ...createBaseResultObject(startPath[startPath.length - 1]),\n node: domRange,\n isRange: true,\n offset: start.offset,\n }\n}\n\n/**\n * Extracts side bias from a CFI part\n */\nfunction extractSideBias(part: CfiPart | undefined): string | undefined {\n // Return the side if it exists\n if (part?.side) return part.side\n\n // Look for side bias in text assertions\n if (part?.text && part.text.length > 0) {\n const text = part.text[0]\n // The CFI spec says side bias can be a=after or b=before\n if (text) {\n const sideBiasMatch = text.match(/^([ab])$/)\n if (sideBiasMatch) {\n return sideBiasMatch[1]\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Whether a step addresses a chunk of character data: per the CFI spec,\n * element steps are even and character data steps odd.\n */\nfunction isCharacterDataStep(part: CfiPart): boolean {\n return part.index % 2 !== 0\n}\n\n/**\n * Returns the extensions from the last valid part of a parsed CFI\n */\nexport function resolveExtensions(parsedCfi: ParsedCfi) {\n const parts = isParsedCfiRange(parsedCfi) ? parsedCfi.end : parsedCfi\n const lastPart = parts.at(-1)\n const lastPartPath = lastPart?.at(-1)\n\n return lastPartPath?.extensions\n}\n\nfunction createBaseResultObject(part?: CfiPart) {\n const sideBias = extractSideBias(part)\n const extensions = part?.extensions\n\n return {\n offset: part?.offset,\n temporal: part?.temporal,\n spatial: part?.spatial,\n side: sideBias,\n extensions,\n }\n}\n\nfunction createNodeResultObject(\n node: Node | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: false,\n ...createBaseResultObject(part),\n }\n}\n\nfunction createRangeResultObject(\n node: Range | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: true,\n ...createBaseResultObject(part),\n }\n}\n\n/**\n * Creates a DOM range for a given node with optional offset\n */\nfunction createRangeForNode(\n document: Document,\n node: Node,\n offset?: number | number[],\n): Range {\n const range = document.createRange()\n range.selectNodeContents(node)\n\n if (offset !== undefined) {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n if (isCharacterData(node)) {\n range.setStart(node, offsetValue || 0)\n }\n }\n\n return range\n}\n\n/**\n * The node and offset a character offset lands on. An offset on a chunk step\n * counts across the whole chunk, so it can fall in a node after the first one.\n */\nfunction locate(\n node: Node,\n offset: number | number[] | undefined,\n): { node: Node; offset: number | undefined } {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n\n if (offsetValue === undefined || !isCharacterData(node)) {\n return { node, offset: offsetValue }\n }\n\n return locateInCharacterDataChunk(node, offsetValue)\n}\n\n/**\n * Where a path lands: a node, or, for a step into an empty chunk of character\n * data, the boundary that chunk stands for in its parent (a child index).\n */\ntype PathTarget = { node: Node; childIndex?: number }\n\n/**\n * Traverses the DOM tree based on CFI path parts\n */\nfunction traverseNodePath(\n currentNode: Node | null,\n path: CfiPart[],\n startIndex: number,\n throwOnError: boolean,\n): PathTarget | null {\n let _currentNode = currentNode\n\n for (let i = startIndex; i < path.length; i++) {\n const part = path[i]\n if (!_currentNode || !part) break\n\n if (isCharacterDataStep(part)) {\n const chunk = findCharacterDataChunk(_currentNode, part.index)\n\n if (!chunk) {\n if (throwOnError) {\n throw new Error(`No character data chunk: ${part.index}`)\n }\n _currentNode = null\n break\n }\n\n if (chunk.kind === \"boundary\") {\n // Nothing lies below an empty chunk\n if (i < path.length - 1) {\n if (throwOnError) {\n throw new Error(`Empty character data chunk: ${part.index}`)\n }\n _currentNode = null\n break\n }\n\n return { node: chunk.parent, childIndex: chunk.childIndex }\n }\n\n _currentNode = chunk.node\n } else {\n const childElements: Node[] = Array.from(_currentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n const index = Math.floor(part.index / 2) - 1\n\n if (index >= 0 && index < childElements.length) {\n const nextNode = childElements[index]\n if (nextNode) {\n _currentNode = nextNode\n }\n } else {\n if (throwOnError) {\n throw new Error(`Invalid element step index: ${part.index}`)\n }\n _currentNode = null\n break\n }\n }\n }\n\n return _currentNode ? { node: _currentNode } : null\n}\n\n/**\n * Resolves a CFI path to a DOM node\n */\nfunction resolvePath(\n path: CfiPart[],\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n const { throwOnError = false, asRange = false } = options\n\n if (!document) {\n if (throwOnError) {\n throw new Error(\"Document is not available\")\n }\n return createNodeResultObject(null)\n }\n\n // Look for an element with an ID first\n const { node: nodeById, remainingPathIndex } = findNodeById(document, path)\n\n // If there's no remaining path to process after the ID node, return the ID node\n if (nodeById && remainingPathIndex >= path.length) {\n const lastPart = path.at(-1)\n\n if (asRange) {\n const range = createRangeForNode(document, nodeById, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(nodeById, lastPart)\n }\n\n // Start traversal from the ID node if found, otherwise start from the document root\n const currentNode: Node | null = nodeById || document.documentElement\n const startIndex = nodeById ? remainingPathIndex : 0\n\n // Handle virtual positions\n if (asRange && path.length > 0) {\n const lastPart = path[path.length - 1]\n if (lastPart && !isCharacterDataStep(lastPart)) {\n // Handle position before first element (index 0)\n if (lastPart.index === 0 && currentNode) {\n const range = document.createRange()\n range.setStart(currentNode, 0)\n range.setEnd(currentNode, 0)\n return createRangeResultObject(range, lastPart)\n }\n\n // Handle position after last element\n const parentNode = traverseNodePath(\n currentNode,\n path.slice(0, -1),\n startIndex,\n throwOnError,\n )?.node\n if (parentNode) {\n const childElements: Node[] = Array.from(parentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n const index = Math.floor(lastPart.index / 2) - 1\n\n // If the index is equal to the number of child elements, it's a position after the last element\n if (index === childElements.length) {\n const range = document.createRange()\n range.selectNodeContents(parentNode)\n range.collapse(false) // Collapse to end\n return createRangeResultObject(range, lastPart)\n }\n }\n }\n }\n\n const target = traverseNodePath(currentNode, path, startIndex, throwOnError)\n\n if (!target) {\n if (throwOnError) {\n throw new Error(\"Failed to resolve CFI path\")\n }\n return createNodeResultObject(null)\n }\n\n const lastPart = path.at(-1)\n const located =\n target.childIndex === undefined\n ? locate(target.node, lastPart?.offset)\n : { node: target.node, offset: target.childIndex }\n\n if (asRange) {\n const range =\n target.childIndex === undefined\n ? createRangeForNode(document, located.node, located.offset)\n : createCollapsedRange(document, target.node, target.childIndex)\n return {\n ...createRangeResultObject(range, lastPart),\n offset: located.offset,\n }\n }\n\n return {\n ...createNodeResultObject(located.node, lastPart),\n offset: located.offset,\n }\n}\n\n/**\n * A range collapsed on a boundary of a node's children\n */\nfunction createCollapsedRange(\n document: Document,\n node: Node,\n childIndex: number,\n): Range {\n const range = document.createRange()\n range.setStart(node, childIndex)\n range.setEnd(node, childIndex)\n return range\n}\n\n/**\n * Find a node by ID from a CFI path.\n * Starting from the last part and working backwards.\n * Returns the node and the remaining path that needs to be processed.\n */\nfunction findNodeById(\n document: Document,\n parts: CfiPart[],\n): { node: Node | null; remainingPathIndex: number } {\n for (let i = parts.length - 1; i >= 0; i--) {\n const part = parts[i]\n if (part?.id) {\n const node = document.getElementById(part.id)\n if (node) return { node, remainingPathIndex: i + 1 }\n }\n }\n\n return { node: null, remainingPathIndex: 0 }\n}\n","import type { CfiPart, ParsedCfi } from \"./parse\"\nimport { cfiEscape } from \"./utils\"\n\n/**\n * Serialize a single CFI part into a string\n * @param part The CFI part to serialize\n * @returns The serialized string representation\n */\nfunction serializePart(part: CfiPart): string {\n let result = `/${part.index}`\n\n // Handle ID assertion first if present\n if (part.id) {\n result += `[${cfiEscape(part.id)}`\n // Add extensions inside ID brackets if present\n if (part.extensions) {\n for (const [key, value] of Object.entries(part.extensions)) {\n result += `;${key}=${cfiEscape(value)}`\n }\n }\n result += `]`\n }\n\n // Handle character offset\n if (part.offset !== undefined) {\n result += `:${part.offset}`\n }\n\n // Handle temporal offset\n if (part.temporal !== undefined) {\n result += `~${part.temporal}`\n }\n\n // Handle spatial offset\n if (part.spatial && part.spatial.length > 0) {\n result += `@${part.spatial.join(\":\")}`\n }\n\n // Handle text assertions and side bias in brackets\n const inBrackets: string[] = []\n\n // Handle text assertions\n if (part.text && part.text.length > 0) {\n inBrackets.push(part.text.map(cfiEscape).join(\",\"))\n }\n\n // Handle side bias and extensions in brackets\n if (part.side || (part.extensions && !part.id)) {\n if (part.side) {\n inBrackets.push(`;s=${part.side}`)\n }\n if (part.extensions && !part.id) {\n for (const [key, value] of Object.entries(part.extensions)) {\n inBrackets.push(`;${key}=${cfiEscape(value)}`)\n }\n }\n }\n\n // Add bracketed attributes if any\n if (inBrackets.length > 0) {\n result += `[${inBrackets.join(\"\")}]`\n }\n\n return result\n}\n\n/**\n * Serialize a single CFI path into a string\n * @param path The CFI path to serialize\n * @returns The serialized string representation\n */\nfunction serializePath(path: CfiPart[]): string {\n return path.map((part) => serializePart(part)).join(\"\")\n}\n\n/**\n * Serialize a parsed CFI into a string\n * @param parsed The parsed CFI to serialize\n * @returns The serialized CFI string\n */\nexport function serialize(parsed: ParsedCfi): string {\n if (Array.isArray(parsed)) {\n // Handle simple CFI or CFI with indirections\n return `epubcfi(${parsed.map(serializePath).join(\"!\")})`\n }\n\n // Handle CFI range\n const parent = parsed.parent.map(serializePath).join(\"!\")\n const start = parsed.start.map(serializePath).join(\"!\")\n const end = parsed.end.map(serializePath).join(\"!\")\n return `epubcfi(${parent},${start},${end})`\n}\n","import { EPUB_CFI_PACKAGE_SPINE_STEP_INDEX } from \"./constants\"\nimport type { CfiPart, ParsedCfi } from \"./parse\"\nimport { parse } from \"./parse\"\nimport { serialize } from \"./serialize\"\nimport { isParsedCfiRange } from \"./utils\"\n\nexport type EpubCfiSpineItemref = {\n spineIndex: number\n spineId?: string\n extensions?: Record<string, string>\n}\n\nexport type EpubCfiSpineItemrefUpdate = {\n spineIndex?: number\n spineId?: string\n extensions?: Record<string, string | undefined>\n}\n\nexport const spineIndexToEpubCfiItemrefStepIndex = (spineIndex: number) =>\n (spineIndex + 1) * 2\n\nexport const epubCfiItemrefStepIndexToSpineIndex = (stepIndex: number) =>\n stepIndex / 2 - 1\n\nconst getFirstPackageDocumentPath = (parsed: ParsedCfi) => {\n return isParsedCfiRange(parsed) ? parsed.parent[0] : parsed[0]\n}\n\nconst getSpineItemrefPart = (parsed: ParsedCfi): CfiPart | undefined => {\n const path = getFirstPackageDocumentPath(parsed)\n\n if (path?.[0]?.index !== EPUB_CFI_PACKAGE_SPINE_STEP_INDEX) return undefined\n\n return path[1]\n}\n\nconst mergeExtensions = (\n currentExtensions: Record<string, string> | undefined,\n extensionUpdates: Record<string, string | undefined>,\n) => {\n const nextExtensions = { ...currentExtensions }\n\n for (const [name, value] of Object.entries(extensionUpdates)) {\n if (value === undefined) {\n delete nextExtensions[name]\n } else {\n nextExtensions[name] = value\n }\n }\n\n return Object.keys(nextExtensions).length > 0 ? nextExtensions : undefined\n}\n\n/**\n * Per the EPUB CFI 1.1 spec, the bracket assertion attached to the spine\n * itemref step is unambiguously an `id_assertion` (the IDREF of the\n * `<itemref>` element) — `text_assertion`s only appear at leaf/character\n * steps. The shared CFI parser however uses heuristics (no spaces, short\n * length) to disambiguate id vs text assertions, so a spineId that\n * contains spaces or is long can end up classified as `text`. At this\n * position-known step we recover the intended id from the text fallback.\n */\nconst readSpineIdFromPart = (part: CfiPart): string | undefined => {\n if (part.id !== undefined) return part.id\n\n if (!part.text || part.text.length === 0) return undefined\n\n return part.text.join(\",\")\n}\n\nexport const getEpubCfiSpineItemref = (\n cfi: string,\n): EpubCfiSpineItemref | undefined => {\n try {\n const spineItemPart = getSpineItemrefPart(parse(cfi))\n\n if (!spineItemPart) return undefined\n\n return {\n spineIndex: epubCfiItemrefStepIndexToSpineIndex(spineItemPart.index),\n spineId: readSpineIdFromPart(spineItemPart),\n extensions: spineItemPart.extensions,\n }\n } catch {\n return undefined\n }\n}\n\nexport const updateEpubCfiSpineItemref = (\n cfi: string,\n update: EpubCfiSpineItemrefUpdate,\n) => {\n try {\n const parsed = parse(cfi)\n const spineItemPart = getSpineItemrefPart(parsed)\n\n if (!spineItemPart) return undefined\n\n if (update.spineIndex !== undefined) {\n spineItemPart.index = spineIndexToEpubCfiItemrefStepIndex(\n update.spineIndex,\n )\n }\n\n if (update.spineId !== undefined) {\n spineItemPart.id = update.spineId\n spineItemPart.text = undefined\n spineItemPart.side = undefined\n }\n\n if (update.extensions !== undefined) {\n spineItemPart.extensions = mergeExtensions(\n spineItemPart.extensions,\n update.extensions,\n )\n }\n\n return serialize(parsed)\n } catch {\n return undefined\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/utils.ts","../src/parse.ts","../src/compare.ts","../src/constants.ts","../src/generate.ts","../src/resolve.ts","../src/serialize.ts","../src/spineItemref.ts"],"sourcesContent":["import type { CfiRange, ParsedCfi } from \"./parse\"\n\n/**\n * Get all ancestors of a node, including the node itself\n */\nexport function getAncestors(node: Node): Node[] {\n const ancestors: Node[] = [node]\n let current: Node | null = node\n\n while (current.parentNode) {\n ancestors.push(current.parentNode)\n current = current.parentNode\n }\n\n return ancestors\n}\n\n/**\n * Find the closest common ancestor of two nodes\n */\nexport function findCommonAncestor(nodeA: Node, nodeB: Node): Node | null {\n if (nodeA === nodeB) return nodeA\n\n const ancestorsA = getAncestors(nodeA)\n const ancestorsSet = new Set(ancestorsA)\n\n // Start with nodeB and traverse up until we find a common ancestor\n let current: Node | null = nodeB\n while (current) {\n if (ancestorsSet.has(current)) {\n return current\n }\n current = current.parentNode\n }\n\n return null\n}\n\n/**\n * Special characters in CFI that need to be escaped according to the spec\n * These are: [ ] ^ , ( ) ;\n */\nexport const CFI_SPECIAL_CHARS = /[[\\]^,();]/g\n\n/**\n * Escape special characters in a CFI string\n * @param str The string to escape\n * @returns The escaped string\n */\nexport function cfiEscape(str: string): string {\n return str.replace(CFI_SPECIAL_CHARS, `^$&`)\n}\n\n/**\n * Regular expression to check if a string is a valid CFI\n */\nexport const isCFI = /^epubcfi\\((.*)\\)$/\n\n/**\n * @important Make it non browser runtime specific\n */\nexport const isElement = (node: Node): node is Element =>\n node.nodeType === Node.ELEMENT_NODE\n\n/**\n * @important Make it non browser runtime specific\n */\n// biome-ignore lint/suspicious/noExplicitAny: TODO\nexport const isNode = (node: any): node is Node =>\n typeof node === \"object\" &&\n node !== null &&\n \"nodeType\" in node &&\n (node.nodeType === Node.ELEMENT_NODE ||\n node.nodeType === Node.TEXT_NODE ||\n node.nodeType === Node.CDATA_SECTION_NODE)\n\n/**\n * Text and CDATA nodes: the character data a CFI step can address\n * (EPUB CFI 3.1.1). Comments and processing instructions are ignored.\n */\nexport const isCharacterData = (node: Node): node is CharacterData =>\n node.nodeType === Node.TEXT_NODE || node.nodeType === Node.CDATA_SECTION_NODE\n\n/**\n * The even step of an element: twice its 1-based position among its parent's\n * element children.\n */\nexport const getElementStep = (node: Node): number => {\n const parent = node.parentNode\n let elements = 0\n\n for (let i = 0; parent && i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child) break\n if (isElement(child)) elements++\n if (child === node) break\n }\n\n return elements * 2\n}\n\n/**\n * The step of the character data chunk a node belongs to, and the number of\n * characters that chunk holds before the node.\n *\n * Character data between two element children forms one chunk with an odd\n * index (1 before the first element, 3 after it, and so on), whatever the\n * number of DOM nodes it spans: adjacent text nodes, CDATA sections and the\n * comments between them all belong to the same chunk, and a character offset\n * counts from its start.\n */\nexport const getCharacterDataStep = (\n node: Node,\n): { step: number; base: number } => {\n const parent = node.parentNode\n let elementsBefore = 0\n let base = 0\n\n for (let i = 0; parent && i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child || child === node) break\n\n if (isElement(child)) {\n elementsBefore++\n base = 0\n } else if (isCharacterData(child)) {\n base += child.data.length\n }\n }\n\n return { step: elementsBefore * 2 + 1, base }\n}\n\n/**\n * What an odd step addresses under `parent`: the first node of its chunk of\n * character data, or, when that chunk is empty, the boundary it stands for in\n * the parent (the child index right after the preceding element, 0 before\n * the first one). `undefined` when the parent has fewer element children\n * than the step needs.\n */\nexport type CharacterDataChunk =\n | { kind: \"node\"; node: CharacterData }\n | { kind: \"boundary\"; parent: Node; childIndex: number }\n\nexport const findCharacterDataChunk = (\n parent: Node,\n step: number,\n): CharacterDataChunk | undefined => {\n const chunk = (step - 1) / 2\n let elementsBefore = 0\n let childIndex = 0\n\n for (let i = 0; i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child) break\n\n if (isElement(child)) {\n elementsBefore++\n if (elementsBefore > chunk) break\n childIndex = i + 1\n } else if (elementsBefore === chunk && isCharacterData(child)) {\n return { kind: \"node\", node: child }\n }\n }\n\n return elementsBefore < chunk\n ? undefined\n : { kind: \"boundary\", parent, childIndex }\n}\n\n/**\n * The node and local offset a chunk offset lands on, counting from `first`,\n * the chunk's first node. A boundary between two nodes lands at the start of\n * the later one; an offset past the chunk keeps its excess on the last node.\n */\nexport const locateInCharacterDataChunk = (\n first: CharacterData,\n offset: number,\n): { node: CharacterData; offset: number } => {\n let node = first\n let base = 0\n let sibling: Node | null = first\n\n while (sibling && !isElement(sibling)) {\n if (isCharacterData(sibling)) {\n node = sibling\n\n if (offset < base + sibling.data.length) {\n return { node, offset: offset - base }\n }\n\n base += sibling.data.length\n }\n\n sibling = sibling.nextSibling\n }\n\n return { node, offset: offset - (base - node.data.length) }\n}\n\n/**\n * Checks if a parsed CFI only contains indirection with no further path\n * For example: epubcfi(/6/2[cover]!)\n */\nexport function isIndirectionOnly(parsed: ParsedCfi): boolean {\n // If it's a range, it can't be just indirection\n if (isParsedCfiRange(parsed)) {\n return false\n }\n\n // For an indirection-only CFI:\n // 1. It must have at least one part\n // 2. It must end with an indirection marker (!)\n // 3. There must be no content after the indirection marker\n\n // Check if there's indirection (marked by presence of multiple parts)\n // AND the last part is empty (nothing after the indirection marker)\n const lastPart = parsed[parsed.length - 1]\n\n return parsed.length > 1 && (lastPart === undefined || lastPart.length === 0)\n}\n\n/**\n * Check if a parsed CFI is a range\n */\nexport function isParsedCfiRange(parsed: ParsedCfi): parsed is CfiRange {\n return (\n parsed !== null &&\n typeof parsed === \"object\" &&\n \"parent\" in parsed &&\n \"start\" in parsed &&\n \"end\" in parsed\n )\n}\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n * Based on the EPUB CFI 1.1 specification: https://idpf.org/epub/linking/cfi/epub-cfi.html\n */\n\nimport { isCFI } from \"./utils\"\n\n/**\n * Interface for a parsed CFI part\n *\n * According to the EPUB CFI 1.1 specification, each step in a CFI path can have\n * its own properties including ID assertions, character offsets, and extension parameters.\n * Extensions are parameters in the form of key-value pairs that can be attached to any\n * step in the CFI path to provide additional information or metadata about that specific step.\n */\nexport interface CfiPart {\n index: number\n id?: string\n offset?: number\n temporal?: number\n spatial?: number[]\n text?: string[]\n side?: string\n /**\n * Extension parameters for this CFI path step\n *\n * The EPUB CFI spec allows for extension parameters to be attached to any step in the path.\n * These are key-value pairs that provide additional information or metadata.\n * For example, in /6/4[chap01ref]!/4[body01]/10[para05]/2/1:3[;vnd.example.param=value],\n * the extension parameter is \"vnd.example.param\" with value \"value\".\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Interface for a parsed CFI range\n */\nexport interface CfiRange {\n parent: CfiPart[][]\n start: CfiPart[][]\n end: CfiPart[][]\n}\n\n/**\n * Interface for a parsed CFI\n */\nexport type ParsedCfi = CfiPart[][] | CfiRange\n\n/**\n * Unwrap a CFI string from the epubcfi() function\n * @param cfi The CFI string to unwrap\n * @returns The unwrapped CFI string\n */\nexport function unwrapCfi(cfi: string): string {\n const match = cfi.match(isCFI)\n return match ? match[1] || cfi : cfi\n}\n\n/**\n * Token type for CFI parsing\n */\ntype CfiToken = [string, string | number]\n\n/**\n * Tokenize a CFI string into an array of tokens\n * @param cfi The CFI string to tokenize\n * @returns An array of tokens\n */\nfunction tokenize(cfi: string): CfiToken[] {\n const tokens: CfiToken[] = []\n let state: string | null = null\n let isEscaped = false\n let value = \"\"\n\n const push = (token: CfiToken) => {\n tokens.push(token)\n state = null\n value = \"\"\n }\n\n const cat = (c: string) => {\n value += c\n isEscaped = false\n }\n\n const unwrappedCfi = unwrapCfi(cfi).trim()\n const chars = Array.from(unwrappedCfi).concat(\"\")\n\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]\n\n if (!char) {\n // End of string, push any pending token\n if (state === \"/\" || state === \":\") {\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n push([\"[\", value])\n } else if (state === \";\" || state?.startsWith(\";\")) {\n push([state, value])\n } else if (state === \"!\") {\n // Make sure to push the '!' token at the end of the string\n push([\"!\", 0])\n }\n break\n }\n\n // Handle escape characters\n if (char === \"^\" && !isEscaped) {\n isEscaped = true\n continue\n }\n\n if (state === \"!\") {\n push([\"!\", 0])\n } else if (state === \",\") {\n push([\",\", 0])\n } else if (state === \"/\" || state === \":\") {\n if (/^\\d$/.test(char)) {\n cat(char)\n continue\n }\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n if (char === \":\") {\n push([\"@\", parseFloat(value)])\n state = \"@\"\n continue\n }\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n if (char === \";\" && !isEscaped) {\n push([\"[\", value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([\"[\", value])\n } else {\n cat(char)\n continue\n }\n } else if (state === \";\") {\n // Handle extension parameter key\n if (char === \"=\" && !isEscaped) {\n state = `;${value}`\n value = \"\"\n } else if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state?.startsWith(\";\")) {\n // Handle extension parameter value\n if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state === null && char === \";\") {\n // Handle standalone extension parameters (not inside brackets)\n state = \";\"\n }\n\n if (\n char === \"/\" ||\n char === \":\" ||\n char === \"~\" ||\n char === \"@\" ||\n char === \"[\" ||\n char === \"!\" ||\n char === \",\"\n ) {\n state = char\n }\n }\n\n return tokens\n}\n\n/**\n * Find indices of tokens with a specific type\n * @param tokens The tokens to search\n * @param type The type to find\n * @returns An array of indices\n */\nfunction findTokenIndices(\n tokens: CfiToken[] | undefined,\n type: string,\n): number[] {\n if (!tokens) {\n return []\n }\n\n return tokens\n .map((token, i) => (token[0] === type ? i : null))\n .filter((i): i is number => i !== null)\n}\n\n/**\n * Split an array at specific indices\n * @param arr The array to split\n * @param indices The indices to split at\n * @returns An array of arrays\n */\nfunction splitAt<T>(arr: T[], indices: number[]): T[][] {\n const result: T[][] = []\n let start = 0\n\n for (const index of indices) {\n result.push(arr.slice(start, index))\n start = index\n }\n\n result.push(arr.slice(start))\n return result\n}\n\n/**\n * Parse a single part of a CFI\n * @param tokens The tokens to parse\n * @returns An array of CFI parts\n */\nfunction parsePart(tokens: CfiToken[]): CfiPart[] {\n const parts: CfiPart[] = []\n\n // Group tokens by path step\n const pathStepTokens: { [key: number]: CfiToken[] } = {}\n let currentPathStep = -1\n\n // First pass: group tokens by path step\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \"/\") {\n currentPathStep++\n parts[currentPathStep] = { index: val as number }\n pathStepTokens[currentPathStep] = []\n } else if (currentPathStep >= 0) {\n pathStepTokens[currentPathStep]?.push(token)\n }\n }\n\n // Second pass: process tokens for each path step\n for (let stepIndex = 0; stepIndex < parts.length; stepIndex++) {\n const currentPart = parts[stepIndex]\n if (!currentPart) continue\n\n const stepsTokens = pathStepTokens[stepIndex] || []\n\n for (let i = 0; i < stepsTokens.length; i++) {\n const token = stepsTokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \":\") {\n currentPart.offset = val as number\n } else if (type === \"~\") {\n currentPart.temporal = val as number\n } else if (type === \"@\") {\n currentPart.spatial = (currentPart.spatial || []).concat(val as number)\n } else if (type === \";s\") {\n currentPart.side = val as string\n } else if (type.startsWith(\";\") && type !== \";s\") {\n // This is an extension parameter\n const paramName = type.substring(1)\n if (!currentPart.extensions) {\n currentPart.extensions = {}\n }\n currentPart.extensions[paramName] = val as string\n } else if (type === \"[\") {\n // Determine if this is an ID or text assertion\n const looksLikeId =\n typeof val === \"string\" && !val.includes(\" \") && val.length < 50\n\n if (i === 0 && looksLikeId && !currentPart.id) {\n currentPart.id = val as string\n } else {\n // Otherwise, it's a text assertion\n if (val !== \"\") {\n // Split on comma and trim each part\n const parts = (val as string).split(\",\").map((part) => part.trim())\n currentPart.text = parts\n }\n }\n }\n }\n }\n\n return parts\n}\n\n/**\n * Parse a CFI with indirections\n * @param tokens The tokens to parse\n * @returns An array of arrays of CFI parts\n */\nfunction parseIndirection(tokens: CfiToken[]): CfiPart[][] {\n const indirectionIndices = findTokenIndices(tokens, \"!\")\n\n return splitAt(tokens, indirectionIndices).map(parsePart)\n}\n\n/**\n * Parse a CFI string into a structured representation\n * @param cfi The CFI string to parse\n * @returns A parsed CFI\n */\nexport function parse(cfi: string): ParsedCfi {\n if (!cfi) {\n throw new Error(\"CFI string cannot be empty\")\n }\n\n const tokens = tokenize(cfi)\n if (!tokens || tokens.length === 0) {\n throw new Error(\"Failed to tokenize CFI string\")\n }\n\n const commaIndices = findTokenIndices(tokens, \",\")\n\n if (commaIndices.length === 0) {\n return parseIndirection(tokens)\n }\n\n const [parentTokens, startTokens, endTokens] = splitAt(tokens, commaIndices)\n\n // Patch: If startTokens or endTokens are just offsets, attach them to the last parent path\n function attachOffsetToParent(\n parent: CfiPart[][],\n offsetTokens: CfiToken[],\n ): CfiPart[][] {\n if (!offsetTokens || offsetTokens.length === 0) return [[]]\n\n // Filter out comma tokens and check if the remaining tokens are just an offset\n const filteredTokens = offsetTokens.filter((token) => token[0] !== \",\")\n\n // Only support a single offset (e.g., [:9] or [:25])\n if (\n filteredTokens.length === 1 &&\n filteredTokens[0] &&\n filteredTokens[0][0] === \":\"\n ) {\n // Clone the last parent path\n const lastParentPath =\n parent.length > 0 ? parent[parent.length - 1] : undefined\n const offsetToken = filteredTokens[0]\n if (lastParentPath && lastParentPath.length > 0 && offsetToken) {\n const pathClone = lastParentPath.map((part) => ({ ...part }))\n const lastPart = pathClone[pathClone.length - 1]\n if (lastPart) {\n lastPart.offset = offsetToken[1] as number\n }\n return [pathClone]\n }\n // fallback: just return an empty path\n return [[]]\n }\n // If not just an offset, parse as normal\n return parseIndirection(offsetTokens)\n }\n\n const parent = parseIndirection(parentTokens || [])\n const start = attachOffsetToParent(parent, startTokens || [])\n const end = attachOffsetToParent(parent, endTokens || [])\n\n return {\n parent,\n start,\n end,\n }\n}\n","import { type CfiPart, type ParsedCfi, parse } from \"./parse\"\n\n/**\n * Collapses a parsed CFI to a single path (private helper for compare)\n * @param parsed The parsed CFI to collapse\n * @param toEnd Whether to collapse to the end of a range\n * @returns A collapsed CFI\n */\nfunction collapse(parsed: ParsedCfi, toEnd = false): CfiPart[][] {\n if (typeof parsed === \"string\") {\n return collapse(parse(parsed), toEnd)\n }\n\n if (\"parent\" in parsed) {\n // It's a range\n if (toEnd) {\n return parsed.parent.concat(parsed.end)\n }\n return parsed.parent.concat(parsed.start)\n }\n\n // It's a single CFI\n return parsed\n}\n\n/**\n * Get the weight of a step type for sorting\n * According to rule 9: character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n */\nfunction getStepTypeWeight(part: CfiPart): number {\n if (part.offset !== undefined) return 1 // character offset (:)\n if (part.index !== undefined) return 2 // child (/)\n if (part.temporal !== undefined || part.spatial !== undefined) return 3 // temporal-spatial (~ or @)\n return 4 // reference (!)\n}\n\n/**\n * Compare two CFIs according to the EPUB CFI specification sorting rules (section 3.2)\n * @param a The first CFI\n * @param b The second CFI\n * @returns -1 if a < b, 0 if a = b, 1 if a > b\n */\nexport function compare(a: ParsedCfi | string, b: ParsedCfi | string): number {\n const aParsed = typeof a === \"string\" ? parse(a) : a\n const bParsed = typeof b === \"string\" ? parse(b) : b\n\n if (\"parent\" in aParsed || \"parent\" in bParsed) {\n // At least one is a range\n return (\n compare(collapse(aParsed), collapse(bParsed)) ||\n compare(collapse(aParsed, true), collapse(bParsed, true))\n )\n }\n\n // Both are single CFIs\n for (let i = 0; i < Math.max(aParsed.length, bParsed.length); i++) {\n const p = aParsed[i] || []\n const q = bParsed[i] || []\n const maxIndex = Math.max(p.length, q.length) - 1\n\n for (let i = 0; i <= maxIndex; i++) {\n const x = p[i]\n const y = q[i]\n\n if (!x) return -1\n if (!y) return 1\n\n // Compare element indices (rule 3 & 4)\n // The step index locates the node in the document, so it always wins.\n if (x.index > y.index) return 1\n if (x.index < y.index) return -1\n\n // Compare step types (rule 9), only as a tie-breaker between steps at\n // the same index (eg: an offset terminating at a node vs a child step\n // descending into it)\n // character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n const xStepType = getStepTypeWeight(x)\n const yStepType = getStepTypeWeight(y)\n\n if (xStepType !== yStepType) {\n return xStepType - yStepType\n }\n\n // Compare temporal positions (rule 7 & 8)\n // Temporal is more important than spatial\n const xTemporal = x.temporal !== undefined\n const yTemporal = y.temporal !== undefined\n\n if (xTemporal && !yTemporal) return 1\n if (!xTemporal && yTemporal) return -1\n\n if (xTemporal && yTemporal) {\n if ((x.temporal ?? 0) > (y.temporal ?? 0)) return 1\n if ((x.temporal ?? 0) < (y.temporal ?? 0)) return -1\n }\n\n // Compare spatial positions (rule 5 & 6)\n const xSpatial = x.spatial !== undefined\n const ySpatial = y.spatial !== undefined\n\n if (xSpatial && !ySpatial) return 1\n if (!xSpatial && ySpatial) return -1\n\n if (xSpatial && ySpatial) {\n // Y position is more important than X (rule 5)\n const xY = x.spatial?.[1] ?? 0\n const yY = y.spatial?.[1] ?? 0\n\n if (xY > yY) return 1\n if (xY < yY) return -1\n\n // Compare X positions if Y positions are equal\n const xX = x.spatial?.[0] ?? 0\n const yX = y.spatial?.[0] ?? 0\n\n if (xX > yX) return 1\n if (xX < yX) return -1\n }\n\n // Last part comparison including character offsets\n if (i === maxIndex) {\n if ((x.offset ?? 0) > (y.offset ?? 0)) return 1\n if ((x.offset ?? 0) < (y.offset ?? 0)) return -1\n }\n }\n }\n\n return 0\n}\n","/**\n * Standard EPUB CFIs that target a content document first address the\n * package document spine element.\n *\n * EPUB package documents order their element children as metadata, manifest, then spine.\n * CFI element steps are even-numbered, so the third element child is step 6.\n */\nexport const EPUB_CFI_PACKAGE_SPINE_STEP_INDEX = 6\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n */\n\nimport {\n cfiEscape,\n findCommonAncestor,\n getCharacterDataStep,\n getElementStep,\n isCharacterData,\n isElement,\n isNode,\n} from \"./utils\"\n\n/**\n * Options for generating CFIs\n */\nexport interface GenerateOptions {\n /**\n * Whether to include text assertions for more robust CFIs\n */\n includeTextAssertions?: boolean\n\n /**\n * The maximum length of text to use for text assertions\n * Default is 10 characters\n */\n textAssertionLength?: number\n\n /**\n * Whether to include a side bias assertion\n */\n includeSideBias?: \"before\" | \"after\"\n\n /**\n * Whether to include spatial coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatialOffset?: [number, number]\n\n /**\n * Extension parameters to include in the CFI\n * Keys should be parameter names, values should be the parameter values\n * Vendor-specific parameters should be prefixed with 'vnd.' followed by the vendor name\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Position in a document, consisting of a node and optional offset\n */\nexport interface CfiPosition {\n /**\n * The DOM node\n */\n node?: Node | null\n\n /**\n * Character offset within the node (for text nodes)\n */\n offset?: number\n\n /**\n * Temporal position in seconds (for audio/video content)\n */\n temporal?: number\n\n /**\n * Spatial position as [x,y] coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatial?: [number, number]\n\n /**\n * Spine index (0-based) for the document containing the node\n */\n spineIndex?: number\n\n /**\n * ID of the spine item\n */\n spineId?: string\n}\n\n/**\n * Extract a suitable text assertion from a text node\n */\nfunction extractTextAssertion(\n textNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n): string | null {\n if (!textNode.textContent || textNode.textContent.trim() === \"\") {\n return null\n }\n\n const textContent = textNode.textContent\n const maxLength = options.textAssertionLength || 10\n\n // If we have an offset, use the text around that position\n if (offset !== undefined && offset <= textContent.length) {\n // We'll take a portion before and after the offset\n const halfLength = Math.floor(maxLength / 2)\n const start = Math.max(0, offset - halfLength)\n const end = Math.min(textContent.length, offset + halfLength)\n return textContent.substring(start, end)\n }\n\n // Otherwise, just take the first part of the text\n return textContent.substring(0, Math.min(textContent.length, maxLength))\n}\n\n/**\n * Helper function to format spatial coordinates\n */\nfunction formatSpatialOffset(spatial: [number, number]): string {\n const [x, y] = spatial\n // Ensure values are within 0-100 range\n const safeX = Math.max(0, Math.min(100, x))\n const safeY = Math.max(0, Math.min(100, y))\n return `@${safeX}:${safeY}`\n}\n\n/**\n * Helper function to add temporal and spatial offsets to a CFI string\n */\nfunction addOffsets(\n cfi: string,\n temporal?: number,\n spatial?: [number, number],\n): string {\n let result = cfi\n\n if (temporal !== undefined) {\n result += `~${temporal}`\n }\n\n if (spatial !== undefined) {\n result += formatSpatialOffset(spatial)\n }\n\n return result\n}\n\n/**\n * Helper function to add text assertion and side bias to a CFI string\n */\nfunction addTextAssertionAndSideBias(\n cfi: string,\n textAssertion: string | null,\n sideBias?: \"before\" | \"after\",\n): string {\n let result = cfi\n\n if (textAssertion) {\n result += `[${cfiEscape(textAssertion)}]`\n }\n\n if (sideBias) {\n const sideBiasChar = sideBias === \"before\" ? \"b\" : \"a\"\n if (!textAssertion) {\n result += `[;s=${sideBiasChar}]`\n } else {\n result = `${result.substring(0, result.length - 1)};s=${sideBiasChar}]`\n }\n }\n\n return result\n}\n\n/**\n * Generate a CFI for a single node in the DOM\n */\nfunction generatePoint(\n node: Node | null,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n let cfi = \"\"\n let currentNode: Node | null = node\n let textNode: Node | null = null\n let cfiOffset = offset\n\n // Character data is addressed through the chunk of its parent it belongs\n // to, and the offset counts from the start of that chunk (EPUB CFI 3.1.1).\n // Any other non-element node (a comment) is ignored by the spec, so it gets\n // the position of the chunk it sits in.\n if (node && !isElement(node)) {\n const parentNode = node.parentNode\n\n if (!parentNode) {\n throw new Error(\"Text node doesn't have a parent\")\n }\n\n const { step, base } = getCharacterDataStep(node)\n\n cfi = `/${step}`\n textNode = isCharacterData(node) ? node : null\n cfiOffset = offset === undefined ? undefined : base + offset\n currentNode = parentNode\n }\n\n // Set up text assertion if needed\n let textAssertion: string | null = null\n if (textNode && options.includeTextAssertions) {\n textAssertion = extractTextAssertion(textNode, offset, options)\n }\n\n // Build the CFI path from the current node up to the html element\n while (currentNode?.parentNode) {\n const step = getElementStep(currentNode)\n\n // If the node has an ID, add it to the CFI\n if (isElement(currentNode) && currentNode.id) {\n cfi = `/${step}[${cfiEscape(currentNode.id)}]${cfi}`\n } else {\n cfi = `/${step}${cfi}`\n }\n\n // If we've reached the html element, stop traversing up\n if (currentNode.parentNode.nodeName.toLowerCase() === \"html\") {\n break\n }\n\n currentNode = currentNode.parentNode\n }\n\n // Add the character offset if provided\n if (cfiOffset !== undefined) {\n cfi += `:${cfiOffset}`\n }\n\n // Add temporal and spatial offsets using helper\n cfi = addOffsets(\n cfi,\n position?.temporal,\n position?.spatial || options.spatialOffset,\n )\n\n // Add text assertion and side bias using helper\n cfi = addTextAssertionAndSideBias(cfi, textAssertion, options.includeSideBias)\n\n cfi = addExtensions(cfi, options.extensions)\n\n return cfi\n}\n\n/**\n * Generate a relative path from one node to another\n */\nfunction generateRelativePath(\n fromNode: Node,\n toNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n // A character offset counts from the start of the chunk the node belongs to\n const base = isElement(toNode) ? 0 : getCharacterDataStep(toNode).base\n\n if (fromNode === toNode) {\n let result = offset !== undefined ? `:${base + offset}` : \"\"\n result = addOffsets(result, position?.temporal, position?.spatial)\n return result\n }\n\n const path: string[] = []\n let currentNode: Node | null = toNode\n\n // Build path from toNode up to fromNode (exclusive)\n while (currentNode && currentNode !== fromNode) {\n const parentNode: Node | null = currentNode.parentNode\n if (!parentNode) break\n\n const step = isElement(currentNode)\n ? getElementStep(currentNode)\n : getCharacterDataStep(currentNode).step\n\n // If the node has an ID, add it\n if (isElement(currentNode) && currentNode.id) {\n path.unshift(`/${step}[${cfiEscape(currentNode.id)}]`)\n } else {\n path.unshift(`/${step}`)\n }\n\n currentNode = parentNode\n }\n\n let relativePath = path.join(\"\")\n\n // Add offset if specified\n if (offset !== undefined) {\n relativePath += `:${base + offset}`\n }\n\n // Add temporal and spatial offsets using helper\n relativePath = addOffsets(relativePath, position?.temporal, position?.spatial)\n\n // Add text assertion and side bias using helper\n if (options.includeTextAssertions && isCharacterData(toNode)) {\n const textAssertion = extractTextAssertion(toNode, offset, options)\n relativePath = addTextAssertionAndSideBias(\n relativePath,\n textAssertion,\n options.includeSideBias,\n )\n }\n\n // Add extension parameters if specified\n relativePath = addExtensions(relativePath, options.extensions)\n\n return relativePath\n}\n\nconst serializeExtensions = (extensions: Record<string, string>) => {\n return Object.entries(extensions)\n .map(([key, value]) => {\n // Properly escape the value according to CFI spec\n const escapedValue = cfiEscape(value)\n // URL encode the value to handle special characters\n return `${key}=${encodeURIComponent(escapedValue)}`\n })\n .join(\";\")\n}\n\n/**\n * Add extension parameters to a CFI path\n */\nfunction addExtensions(\n cfi: string,\n extensions?: Record<string, string>,\n): string {\n if (!extensions || Object.keys(extensions).length === 0) {\n return cfi\n }\n\n const extensionString = serializeExtensions(extensions)\n\n // If we're dealing with a bracket at end\n if (cfi.endsWith(\"]\")) {\n // Check if there are already parameters\n const lastBracketIndex = cfi.lastIndexOf(\"[\")\n const content = cfi.substring(lastBracketIndex + 1, cfi.length - 1)\n\n // If content already has these exact extensions, return as is\n if (content.includes(extensionString)) {\n return cfi\n }\n\n // Add extensions with proper separator\n return `${cfi.substring(0, cfi.length - 1)}${content.includes(\";\") ? \";\" : \";\"}${extensionString}]`\n }\n\n // Special case for spine items with indirection\n if (cfi.endsWith(\"!\")) {\n return cfi\n }\n\n // No bracket at the end - add with new brackets\n return `${cfi}[;${extensionString}]`\n}\n\n/**\n * Generate a range CFI between two points in the document\n */\nfunction generateRange(\n startNode: Node,\n startOffset: number,\n endNode: Node,\n endOffset: number,\n options: GenerateOptions = {},\n startPosition?: CfiPosition,\n endPosition?: CfiPosition,\n): string {\n // Find common ancestor\n const ancestor = findCommonAncestor(startNode, endNode)\n\n if (!ancestor) {\n throw new Error(\"No common ancestor found\")\n }\n\n // Generate CFI from ancestor to document\n const ancestorCfi = generatePoint(ancestor, undefined, options)\n\n // Generate path from ancestor to start node\n const startPath = generateRelativePath(\n ancestor,\n startNode,\n startOffset,\n options,\n startPosition,\n )\n\n // Generate path from ancestor to end node\n const endPath = generateRelativePath(\n ancestor,\n endNode,\n endOffset,\n options,\n endPosition,\n )\n\n // For range CFIs, add extensions to each part separately\n if (options.extensions && Object.keys(options.extensions).length > 0) {\n // Add extensions to each part\n const ancestorWithExt = addExtensions(ancestorCfi, options.extensions)\n const startWithExt = addExtensions(startPath, options.extensions)\n const endWithExt = addExtensions(endPath, options.extensions)\n\n return `${ancestorWithExt},${startWithExt},${endWithExt}`\n }\n\n // Combine into a regular range CFI without extensions\n return `${ancestorCfi},${startPath},${endPath}`\n}\n\nconst generateSpineCfi = (\n spineIndex: number,\n spineId?: string,\n options: GenerateOptions = {},\n isFinal?: boolean,\n) => {\n const bracket = \"\"\n const cfiIndex = (spineIndex + 1) * 2\n let cfi = `/6/${cfiIndex}`\n\n if (spineId) {\n cfi += `[${cfiEscape(spineId)}]`\n }\n\n cfi = isFinal ? addExtensions(cfi, options.extensions) : cfi\n\n return `${cfi}${bracket}!`\n}\n\n/**\n * Generate a CFI from a DOM node or position\n *\n * @example\n * // Generate CFI for a single node\n * const cfi = generate(node);\n *\n * @example\n * // Generate CFI for a text node with offset\n * const cfi = generate({ node: textNode, offset: 5 });\n *\n * @example\n * // Generate CFI for a video with temporal offset\n * const cfi = generate({ node: videoElement, temporal: 45.5 });\n *\n * @example\n * // Generate CFI for an image with spatial coordinates\n * const cfi = generate({ node: imageElement, spatial: [50, 75] });\n *\n * @example\n * // Generate a range CFI\n * const cfi = generate({\n * start: { node: startNode, offset: 0 },\n * end: { node: endNode, offset: 10 }\n * });\n *\n * @example\n * // Generate a CFI with spine itemref reference\n * const cfi = generate({\n * node: chapterNode,\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a CFI for a spine item without a node\n * const cfi = generate({\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a robust CFI with text assertions\n * const cfi = generate(node, {\n * includeTextAssertions: true,\n * textAssertionLength: 15\n * });\n */\nexport function generate(\n position: Node | CfiPosition | { start: CfiPosition; end: CfiPosition },\n options: GenerateOptions = {},\n): string {\n // Case 1: Simple Node\n if (isNode(position)) {\n return `epubcfi(${generatePoint(position, undefined, options)})`\n }\n\n let cfi = \"\"\n\n // Non Range case\n if (!(\"start\" in position)) {\n // Add spine itemref reference if specified\n if (position.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n position.spineIndex,\n position.spineId,\n options,\n !position.node,\n )\n\n if (!position.node) {\n return `epubcfi(${cfi})`\n }\n }\n\n // Add the node path\n cfi += generatePoint(\n position.node ?? null,\n position.offset,\n options,\n position,\n )\n\n return `epubcfi(${cfi})`\n }\n\n // Range case\n const { start, end } = position\n\n // Add spine itemref reference if specified (use start position's spine info)\n if (start.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n start.spineIndex,\n start.spineId,\n options,\n !start.node,\n )\n }\n\n if (start.node && end.node) {\n // Add the range path\n cfi += generateRange(\n start.node,\n start.offset ?? 0,\n end.node,\n end.offset ?? 0,\n options,\n start,\n end,\n )\n }\n\n return `epubcfi(${cfi})`\n}\n","import { type CfiPart, type CfiRange, type ParsedCfi, parse } from \"./parse\"\nimport {\n findCharacterDataChunk,\n isCharacterData,\n isIndirectionOnly,\n isNode,\n isParsedCfiRange,\n locateInCharacterDataChunk,\n} from \"./utils\"\n\n/**\n * Options for resolving a CFI\n */\ninterface ResolveOptions {\n /**\n * Whether to throw an error if the CFI cannot be resolved\n * @default false\n */\n throwOnError?: boolean\n\n /**\n * Whether to return a range instead of a single node\n * @default false\n */\n asRange?: boolean\n}\n\n/**\n * Result of resolving a CFI\n */\ninterface ResolveRangeResult extends ResolveResultBase {\n node: Range | null\n isRange: true\n}\n\ninterface ResolveResultBase {\n offset?: number[] | number\n\n /**\n * The temporal offset if applicable\n */\n temporal?: number\n\n /**\n * The spatial offset if applicable\n */\n spatial?: number[]\n\n /**\n * The side bias if applicable\n */\n side?: string\n\n /**\n * Any extension parameters in the CFI\n */\n extensions?: Record<string, string>\n}\n\ninterface ResolveNodeResult extends ResolveResultBase {\n node: Node | null\n isRange: false\n}\n\ntype ResolveResult = ResolveNodeResult | ResolveRangeResult\n\n/**\n * Resolves a CFI string to a DOM node or range\n */\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options: Omit<ResolveOptions, \"asRange\"> & { asRange: true },\n): ResolveRangeResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options?: ResolveOptions,\n): ResolveResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n try {\n const parsedCfi = typeof cfi !== \"string\" ? cfi : parse(cfi)\n\n const result = resolveParsed(parsedCfi, document, options)\n\n return result\n } catch (error) {\n if (options.throwOnError) {\n throw error\n }\n\n return { node: null, isRange: false }\n }\n}\n\n/**\n * Resolves a parsed CFI to a DOM node or range\n */\nfunction resolveParsed(\n parsed: ParsedCfi,\n document: Document,\n options: { asRange?: boolean } = { asRange: false },\n): ResolveResult {\n if (isParsedCfiRange(parsed)) {\n // If it's a range CFI, always return a Range\n return resolveRange(parsed, document)\n }\n\n // Check if this is a CFI with only indirection (e.g., \"epubcfi(/6/2[cover]!)\")\n if (isIndirectionOnly(parsed)) {\n // According to the spec, we cannot resolve beyond the indirection point\n // so we return null for the node\n return createNodeResultObject(null)\n }\n\n const nonIndirectionPart = parsed.at(-1)\n\n // Handle path CFI (indirection)\n if (nonIndirectionPart) {\n return resolvePath(nonIndirectionPart, document, options)\n }\n\n throw new Error(\"Invalid CFI structure\")\n}\n\n/**\n * Resolves a CFI range to a DOM range\n */\nfunction resolveRange(range: CfiRange, document: Document): ResolveResult {\n // Get the parent paths and start/end paths\n const parentPaths = range.parent\n const startPath = range.start[0] || []\n const endPath = range.end[0] || []\n\n // Find the parent node\n let parentNode: Node | null = document.documentElement\n\n if (parentPaths.length > 0) {\n // If there's indirection (multiple parent paths), resolve the indirection first\n if (parentPaths.length > 1) {\n const indirectionPath = parentPaths[0]\n if (indirectionPath) {\n const indirectionResult = resolvePath(indirectionPath, document)\n if (isNode(indirectionResult.node)) {\n parentNode = indirectionResult.node\n }\n }\n if (parentPaths.length > 1 && parentNode) {\n const actualParentPath = parentPaths[1]\n if (actualParentPath) {\n const actualParentResult = resolvePath(actualParentPath, document)\n if (isNode(actualParentResult.node)) {\n parentNode = actualParentResult.node\n }\n }\n }\n } else {\n const parentPath = parentPaths[0]\n if (parentPath) {\n const parentResult = resolvePath(parentPath, document)\n if (isNode(parentResult.node)) {\n parentNode = parentResult.node\n }\n }\n }\n }\n\n if (!parentNode) {\n throw new Error(\"Failed to resolve parent node in CFI range\")\n }\n\n // If parentNode is a text node, use it directly for start/end\n const isParentTextNode = isCharacterData(parentNode)\n\n let startNode: Node\n let endNode: Node\n let startOffset = 0\n let endOffset = 0\n\n if (isParentTextNode) {\n startNode = parentNode\n endNode = parentNode\n // Use offsets from the last part of startPath/endPath\n const lastStartPart = startPath[startPath.length - 1]\n const lastEndPart = endPath[endPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n } else {\n // Offset-only local paths (`,:5`) are attached to the parent path during\n // parsing, so a non-empty path here always carries real steps that must be\n // traversed — a single step with an offset (`/1:2`) is a path to a child\n // node, not an offset on the parent.\n const isStartOffsetOnly = startPath.length === 0\n const isEndOffsetOnly = endPath.length === 0\n\n if (isStartOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (start)\")\n startNode = parentNode\n startOffset = startPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, startPath, 0, true)\n if (!traversed)\n throw new Error(\"Failed to resolve start node in CFI range\")\n startNode = traversed.node\n const lastStartPart = startPath[startPath.length - 1]\n startOffset =\n traversed.childIndex ??\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ??\n 0\n }\n\n if (isEndOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (end)\")\n endNode = parentNode\n endOffset = endPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, endPath, 0, true)\n if (!traversed) throw new Error(\"Failed to resolve end node in CFI range\")\n endNode = traversed.node\n const lastEndPart = endPath[endPath.length - 1]\n endOffset =\n traversed.childIndex ??\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ??\n 0\n }\n }\n\n // A chunk offset can fall in a node after the chunk's first one\n const start = locate(startNode, startOffset)\n const end = locate(endNode, endOffset)\n\n // Create and return a DOM range\n const domRange = document.createRange()\n domRange.setStart(start.node, start.offset ?? 0)\n domRange.setEnd(end.node, end.offset ?? 0)\n\n return {\n ...createBaseResultObject(startPath[startPath.length - 1]),\n node: domRange,\n isRange: true,\n offset: start.offset,\n }\n}\n\n/**\n * Extracts side bias from a CFI part\n */\nfunction extractSideBias(part: CfiPart | undefined): string | undefined {\n // Return the side if it exists\n if (part?.side) return part.side\n\n // Look for side bias in text assertions\n if (part?.text && part.text.length > 0) {\n const text = part.text[0]\n // The CFI spec says side bias can be a=after or b=before\n if (text) {\n const sideBiasMatch = text.match(/^([ab])$/)\n if (sideBiasMatch) {\n return sideBiasMatch[1]\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Whether a step addresses a chunk of character data: per the CFI spec,\n * element steps are even and character data steps odd.\n */\nfunction isCharacterDataStep(part: CfiPart): boolean {\n return part.index % 2 !== 0\n}\n\n/**\n * Returns the extensions from the last valid part of a parsed CFI\n */\nexport function resolveExtensions(parsedCfi: ParsedCfi) {\n const parts = isParsedCfiRange(parsedCfi) ? parsedCfi.end : parsedCfi\n const lastPart = parts.at(-1)\n const lastPartPath = lastPart?.at(-1)\n\n return lastPartPath?.extensions\n}\n\nfunction createBaseResultObject(part?: CfiPart) {\n const sideBias = extractSideBias(part)\n const extensions = part?.extensions\n\n return {\n offset: part?.offset,\n temporal: part?.temporal,\n spatial: part?.spatial,\n side: sideBias,\n extensions,\n }\n}\n\nfunction createNodeResultObject(\n node: Node | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: false,\n ...createBaseResultObject(part),\n }\n}\n\nfunction createRangeResultObject(\n node: Range | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: true,\n ...createBaseResultObject(part),\n }\n}\n\n/**\n * Creates a DOM range for a given node with optional offset\n */\nfunction createRangeForNode(\n document: Document,\n node: Node,\n offset?: number | number[],\n): Range {\n const range = document.createRange()\n range.selectNodeContents(node)\n\n if (offset !== undefined) {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n if (isCharacterData(node)) {\n range.setStart(node, offsetValue || 0)\n }\n }\n\n return range\n}\n\n/**\n * The node and offset a character offset lands on. An offset on a chunk step\n * counts across the whole chunk, so it can fall in a node after the first one.\n */\nfunction locate(\n node: Node,\n offset: number | number[] | undefined,\n): { node: Node; offset: number | undefined } {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n\n if (offsetValue === undefined || !isCharacterData(node)) {\n return { node, offset: offsetValue }\n }\n\n return locateInCharacterDataChunk(node, offsetValue)\n}\n\n/**\n * Where a path lands: a node, or, for a step into an empty chunk of character\n * data, the boundary that chunk stands for in its parent (a child index).\n */\ntype PathTarget = { node: Node; childIndex?: number }\n\n/**\n * Traverses the DOM tree based on CFI path parts\n */\nfunction traverseNodePath(\n currentNode: Node | null,\n path: CfiPart[],\n startIndex: number,\n throwOnError: boolean,\n): PathTarget | null {\n let _currentNode = currentNode\n\n for (let i = startIndex; i < path.length; i++) {\n const part = path[i]\n if (!_currentNode || !part) break\n\n if (isCharacterDataStep(part)) {\n const chunk = findCharacterDataChunk(_currentNode, part.index)\n\n if (!chunk) {\n if (throwOnError) {\n throw new Error(`No character data chunk: ${part.index}`)\n }\n _currentNode = null\n break\n }\n\n if (chunk.kind === \"boundary\") {\n // Nothing lies below an empty chunk\n if (i < path.length - 1) {\n if (throwOnError) {\n throw new Error(`Empty character data chunk: ${part.index}`)\n }\n _currentNode = null\n break\n }\n\n return { node: chunk.parent, childIndex: chunk.childIndex }\n }\n\n _currentNode = chunk.node\n } else {\n const childElements: Node[] = Array.from(_currentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n const index = Math.floor(part.index / 2) - 1\n\n if (index >= 0 && index < childElements.length) {\n const nextNode = childElements[index]\n if (nextNode) {\n _currentNode = nextNode\n }\n } else {\n if (throwOnError) {\n throw new Error(`Invalid element step index: ${part.index}`)\n }\n _currentNode = null\n break\n }\n }\n }\n\n return _currentNode ? { node: _currentNode } : null\n}\n\n/**\n * Resolves a CFI path to a DOM node\n */\nfunction resolvePath(\n path: CfiPart[],\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n const { throwOnError = false, asRange = false } = options\n\n if (!document) {\n if (throwOnError) {\n throw new Error(\"Document is not available\")\n }\n return createNodeResultObject(null)\n }\n\n // Look for an element with an ID first\n const { node: nodeById, remainingPathIndex } = findNodeById(document, path)\n\n // If there's no remaining path to process after the ID node, return the ID node\n if (nodeById && remainingPathIndex >= path.length) {\n const lastPart = path.at(-1)\n\n if (asRange) {\n const range = createRangeForNode(document, nodeById, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(nodeById, lastPart)\n }\n\n // Start traversal from the ID node if found, otherwise start from the document root\n const currentNode: Node | null = nodeById || document.documentElement\n const startIndex = nodeById ? remainingPathIndex : 0\n\n // Handle virtual positions\n if (asRange && path.length > 0) {\n const lastPart = path[path.length - 1]\n if (lastPart && !isCharacterDataStep(lastPart)) {\n // Handle position before first element (index 0)\n if (lastPart.index === 0 && currentNode) {\n const range = document.createRange()\n range.setStart(currentNode, 0)\n range.setEnd(currentNode, 0)\n return createRangeResultObject(range, lastPart)\n }\n\n // Handle position after last element\n const parentNode = traverseNodePath(\n currentNode,\n path.slice(0, -1),\n startIndex,\n throwOnError,\n )?.node\n if (parentNode) {\n const childElements: Node[] = Array.from(parentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n const index = Math.floor(lastPart.index / 2) - 1\n\n // If the index is equal to the number of child elements, it's a position after the last element\n if (index === childElements.length) {\n const range = document.createRange()\n range.selectNodeContents(parentNode)\n range.collapse(false) // Collapse to end\n return createRangeResultObject(range, lastPart)\n }\n }\n }\n }\n\n const target = traverseNodePath(currentNode, path, startIndex, throwOnError)\n\n if (!target) {\n if (throwOnError) {\n throw new Error(\"Failed to resolve CFI path\")\n }\n return createNodeResultObject(null)\n }\n\n const lastPart = path.at(-1)\n const located =\n target.childIndex === undefined\n ? locate(target.node, lastPart?.offset)\n : { node: target.node, offset: target.childIndex }\n\n if (asRange) {\n const range =\n target.childIndex === undefined\n ? createRangeForNode(document, located.node, located.offset)\n : createCollapsedRange(document, target.node, target.childIndex)\n return {\n ...createRangeResultObject(range, lastPart),\n offset: located.offset,\n }\n }\n\n return {\n ...createNodeResultObject(located.node, lastPart),\n offset: located.offset,\n }\n}\n\n/**\n * A range collapsed on a boundary of a node's children\n */\nfunction createCollapsedRange(\n document: Document,\n node: Node,\n childIndex: number,\n): Range {\n const range = document.createRange()\n range.setStart(node, childIndex)\n range.setEnd(node, childIndex)\n return range\n}\n\n/**\n * Find a node by ID from a CFI path.\n * Starting from the last part and working backwards.\n * Returns the node and the remaining path that needs to be processed.\n */\nfunction findNodeById(\n document: Document,\n parts: CfiPart[],\n): { node: Node | null; remainingPathIndex: number } {\n for (let i = parts.length - 1; i >= 0; i--) {\n const part = parts[i]\n if (part?.id) {\n const node = document.getElementById(part.id)\n if (node) return { node, remainingPathIndex: i + 1 }\n }\n }\n\n return { node: null, remainingPathIndex: 0 }\n}\n","import type { CfiPart, ParsedCfi } from \"./parse\"\nimport { cfiEscape } from \"./utils\"\n\n/**\n * Serialize a single CFI part into a string\n * @param part The CFI part to serialize\n * @returns The serialized string representation\n */\nfunction serializePart(part: CfiPart): string {\n let result = `/${part.index}`\n\n // Handle ID assertion first if present\n if (part.id) {\n result += `[${cfiEscape(part.id)}`\n // Add extensions inside ID brackets if present\n if (part.extensions) {\n for (const [key, value] of Object.entries(part.extensions)) {\n result += `;${key}=${cfiEscape(value)}`\n }\n }\n result += `]`\n }\n\n // Handle character offset\n if (part.offset !== undefined) {\n result += `:${part.offset}`\n }\n\n // Handle temporal offset\n if (part.temporal !== undefined) {\n result += `~${part.temporal}`\n }\n\n // Handle spatial offset\n if (part.spatial && part.spatial.length > 0) {\n result += `@${part.spatial.join(\":\")}`\n }\n\n // Handle text assertions and side bias in brackets\n const inBrackets: string[] = []\n\n // Handle text assertions\n if (part.text && part.text.length > 0) {\n inBrackets.push(part.text.map(cfiEscape).join(\",\"))\n }\n\n // Handle side bias and extensions in brackets\n if (part.side || (part.extensions && !part.id)) {\n if (part.side) {\n inBrackets.push(`;s=${part.side}`)\n }\n if (part.extensions && !part.id) {\n for (const [key, value] of Object.entries(part.extensions)) {\n inBrackets.push(`;${key}=${cfiEscape(value)}`)\n }\n }\n }\n\n // Add bracketed attributes if any\n if (inBrackets.length > 0) {\n result += `[${inBrackets.join(\"\")}]`\n }\n\n return result\n}\n\n/**\n * Serialize a single CFI path into a string\n * @param path The CFI path to serialize\n * @returns The serialized string representation\n */\nfunction serializePath(path: CfiPart[]): string {\n return path.map((part) => serializePart(part)).join(\"\")\n}\n\n/**\n * Serialize a parsed CFI into a string\n * @param parsed The parsed CFI to serialize\n * @returns The serialized CFI string\n */\nexport function serialize(parsed: ParsedCfi): string {\n if (Array.isArray(parsed)) {\n // Handle simple CFI or CFI with indirections\n return `epubcfi(${parsed.map(serializePath).join(\"!\")})`\n }\n\n // Handle CFI range\n const parent = parsed.parent.map(serializePath).join(\"!\")\n const start = parsed.start.map(serializePath).join(\"!\")\n const end = parsed.end.map(serializePath).join(\"!\")\n return `epubcfi(${parent},${start},${end})`\n}\n","import { EPUB_CFI_PACKAGE_SPINE_STEP_INDEX } from \"./constants\"\nimport type { CfiPart, ParsedCfi } from \"./parse\"\nimport { parse } from \"./parse\"\nimport { serialize } from \"./serialize\"\nimport { isParsedCfiRange } from \"./utils\"\n\nexport type EpubCfiSpineItemref = {\n spineIndex: number\n spineId?: string\n extensions?: Record<string, string>\n}\n\nexport type EpubCfiSpineItemrefUpdate = {\n spineIndex?: number\n spineId?: string\n extensions?: Record<string, string | undefined>\n}\n\nexport const spineIndexToEpubCfiItemrefStepIndex = (spineIndex: number) =>\n (spineIndex + 1) * 2\n\nexport const epubCfiItemrefStepIndexToSpineIndex = (stepIndex: number) =>\n stepIndex / 2 - 1\n\nconst getFirstPackageDocumentPath = (parsed: ParsedCfi) => {\n return isParsedCfiRange(parsed) ? parsed.parent[0] : parsed[0]\n}\n\nconst getSpineItemrefPart = (parsed: ParsedCfi): CfiPart | undefined => {\n const path = getFirstPackageDocumentPath(parsed)\n\n if (path?.[0]?.index !== EPUB_CFI_PACKAGE_SPINE_STEP_INDEX) return undefined\n\n return path[1]\n}\n\nconst mergeExtensions = (\n currentExtensions: Record<string, string> | undefined,\n extensionUpdates: Record<string, string | undefined>,\n) => {\n const nextExtensions = { ...currentExtensions }\n\n for (const [name, value] of Object.entries(extensionUpdates)) {\n if (value === undefined) {\n delete nextExtensions[name]\n } else {\n nextExtensions[name] = value\n }\n }\n\n return Object.keys(nextExtensions).length > 0 ? nextExtensions : undefined\n}\n\n/**\n * Per the EPUB CFI 1.1 spec, the bracket assertion attached to the spine\n * itemref step is unambiguously an `id_assertion` (the IDREF of the\n * `<itemref>` element) — `text_assertion`s only appear at leaf/character\n * steps. The shared CFI parser however uses heuristics (no spaces, short\n * length) to disambiguate id vs text assertions, so a spineId that\n * contains spaces or is long can end up classified as `text`. At this\n * position-known step we recover the intended id from the text fallback.\n */\nconst readSpineIdFromPart = (part: CfiPart): string | undefined => {\n if (part.id !== undefined) return part.id\n\n if (!part.text || part.text.length === 0) return undefined\n\n return part.text.join(\",\")\n}\n\nexport const getEpubCfiSpineItemref = (\n cfi: string,\n): EpubCfiSpineItemref | undefined => {\n try {\n const spineItemPart = getSpineItemrefPart(parse(cfi))\n\n if (!spineItemPart) return undefined\n\n return {\n spineIndex: epubCfiItemrefStepIndexToSpineIndex(spineItemPart.index),\n spineId: readSpineIdFromPart(spineItemPart),\n extensions: spineItemPart.extensions,\n }\n } catch {\n return undefined\n }\n}\n\nexport const updateEpubCfiSpineItemref = (\n cfi: string,\n update: EpubCfiSpineItemrefUpdate,\n) => {\n try {\n const parsed = parse(cfi)\n const spineItemPart = getSpineItemrefPart(parsed)\n\n if (!spineItemPart) return undefined\n\n if (update.spineIndex !== undefined) {\n spineItemPart.index = spineIndexToEpubCfiItemrefStepIndex(\n update.spineIndex,\n )\n }\n\n if (update.spineId !== undefined) {\n spineItemPart.id = update.spineId\n spineItemPart.text = undefined\n spineItemPart.side = undefined\n }\n\n if (update.extensions !== undefined) {\n spineItemPart.extensions = mergeExtensions(\n spineItemPart.extensions,\n update.extensions,\n )\n }\n\n return serialize(parsed)\n } catch {\n return undefined\n }\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -124,26 +124,29 @@ function h(e) {
124
124
  continue;
125
125
  }
126
126
  a(["@", parseFloat(i)]);
127
- } else if (n === "[") if (t === ";" && !r) a(["[", i]), n = ";";
128
- else if (t === "]" && !r) a(["[", i]);
129
- else {
130
- o(t);
131
- continue;
132
- }
133
- else if (n === ";") if (t === "=" && !r) n = `;${i}`, i = "";
134
- else if (t === ";" && !r) a([n, i]), n = ";";
135
- else if (t === "]" && !r) a([n, i]);
136
- else {
137
- o(t);
138
- continue;
139
- }
140
- else if (n?.startsWith(";")) if (t === ";" && !r) a([n, i]), n = ";";
141
- else if (t === "]" && !r) a([n, i]);
142
- else {
143
- o(t);
144
- continue;
145
- }
146
- else n === null && t === ";" && (n = ";");
127
+ } else if (n === "[") {
128
+ if (t === ";" && !r) a(["[", i]), n = ";";
129
+ else if (t === "]" && !r) a(["[", i]);
130
+ else {
131
+ o(t);
132
+ continue;
133
+ }
134
+ } else if (n === ";") {
135
+ if (t === "=" && !r) n = `;${i}`, i = "";
136
+ else if (t === ";" && !r) a([n, i]), n = ";";
137
+ else if (t === "]" && !r) a([n, i]);
138
+ else {
139
+ o(t);
140
+ continue;
141
+ }
142
+ } else if (n?.startsWith(";")) {
143
+ if (t === ";" && !r) a([n, i]), n = ";";
144
+ else if (t === "]" && !r) a([n, i]);
145
+ else {
146
+ o(t);
147
+ continue;
148
+ }
149
+ } else n === null && t === ";" && (n = ";");
147
150
  (t === "/" || t === ":" || t === "~" || t === "@" || t === "[" || t === "!" || t === ",") && (n = t);
148
151
  }
149
152
  return t;
@@ -381,24 +384,26 @@ function L(e, t, n = { asRange: !1 }) {
381
384
  }
382
385
  function R(e, t) {
383
386
  let n = e.parent, r = e.start[0] || [], i = e.end[0] || [], a = t.documentElement;
384
- if (n.length > 0) if (n.length > 1) {
385
- let e = n[0];
386
- if (e) {
387
- let n = J(e, t);
388
- o(n.node) && (a = n.node);
389
- }
390
- if (n.length > 1 && a) {
391
- let e = n[1];
387
+ if (n.length > 0) {
388
+ if (n.length > 1) {
389
+ let e = n[0];
390
+ if (e) {
391
+ let n = J(e, t);
392
+ o(n.node) && (a = n.node);
393
+ }
394
+ if (n.length > 1 && a) {
395
+ let e = n[1];
396
+ if (e) {
397
+ let n = J(e, t);
398
+ o(n.node) && (a = n.node);
399
+ }
400
+ }
401
+ } else {
402
+ let e = n[0];
392
403
  if (e) {
393
404
  let n = J(e, t);
394
405
  o(n.node) && (a = n.node);
395
406
  }
396
- }
397
- } else {
398
- let e = n[0];
399
- if (e) {
400
- let n = J(e, t);
401
- o(n.node) && (a = n.node);
402
407
  }
403
408
  }
404
409
  if (!a) throw Error("Failed to resolve parent node in CFI range");
@@ -627,7 +632,7 @@ var Z = (e) => (e + 1) * 2, Q = (e) => e / 2 - 1, re = (e) => p(e) ? e.parent[0]
627
632
  return Object.keys(n).length > 0 ? n : void 0;
628
633
  }, ae = (e) => {
629
634
  if (e.id !== void 0) return e.id;
630
- if (!(!e.text || e.text.length === 0)) return e.text.join(",");
635
+ if (e.text && e.text.length !== 0) return e.text.join(",");
631
636
  }, oe = (e) => {
632
637
  try {
633
638
  let t = $(b(e));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/utils.ts","../src/parse.ts","../src/compare.ts","../src/constants.ts","../src/generate.ts","../src/resolve.ts","../src/serialize.ts","../src/spineItemref.ts"],"sourcesContent":["import type { CfiRange, ParsedCfi } from \"./parse\"\n\n/**\n * Get all ancestors of a node, including the node itself\n */\nexport function getAncestors(node: Node): Node[] {\n const ancestors: Node[] = [node]\n let current: Node | null = node\n\n while (current.parentNode) {\n ancestors.push(current.parentNode)\n current = current.parentNode\n }\n\n return ancestors\n}\n\n/**\n * Find the closest common ancestor of two nodes\n */\nexport function findCommonAncestor(nodeA: Node, nodeB: Node): Node | null {\n if (nodeA === nodeB) return nodeA\n\n const ancestorsA = getAncestors(nodeA)\n const ancestorsSet = new Set(ancestorsA)\n\n // Start with nodeB and traverse up until we find a common ancestor\n let current: Node | null = nodeB\n while (current) {\n if (ancestorsSet.has(current)) {\n return current\n }\n current = current.parentNode\n }\n\n return null\n}\n\n/**\n * Special characters in CFI that need to be escaped according to the spec\n * These are: [ ] ^ , ( ) ;\n */\nexport const CFI_SPECIAL_CHARS = /[[\\]^,();]/g\n\n/**\n * Escape special characters in a CFI string\n * @param str The string to escape\n * @returns The escaped string\n */\nexport function cfiEscape(str: string): string {\n return str.replace(CFI_SPECIAL_CHARS, `^$&`)\n}\n\n/**\n * Regular expression to check if a string is a valid CFI\n */\nexport const isCFI = /^epubcfi\\((.*)\\)$/\n\n/**\n * @important Make it non browser runtime specific\n */\nexport const isElement = (node: Node): node is Element =>\n node.nodeType === Node.ELEMENT_NODE\n\n/**\n * @important Make it non browser runtime specific\n */\n// biome-ignore lint/suspicious/noExplicitAny: TODO\nexport const isNode = (node: any): node is Node =>\n typeof node === \"object\" &&\n node !== null &&\n \"nodeType\" in node &&\n (node.nodeType === Node.ELEMENT_NODE ||\n node.nodeType === Node.TEXT_NODE ||\n node.nodeType === Node.CDATA_SECTION_NODE)\n\n/**\n * Text and CDATA nodes: the character data a CFI step can address\n * (EPUB CFI 3.1.1). Comments and processing instructions are ignored.\n */\nexport const isCharacterData = (node: Node): node is CharacterData =>\n node.nodeType === Node.TEXT_NODE || node.nodeType === Node.CDATA_SECTION_NODE\n\n/**\n * The even step of an element: twice its 1-based position among its parent's\n * element children.\n */\nexport const getElementStep = (node: Node): number => {\n const parent = node.parentNode\n let elements = 0\n\n for (let i = 0; parent && i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child) break\n if (isElement(child)) elements++\n if (child === node) break\n }\n\n return elements * 2\n}\n\n/**\n * The step of the character data chunk a node belongs to, and the number of\n * characters that chunk holds before the node.\n *\n * Character data between two element children forms one chunk with an odd\n * index (1 before the first element, 3 after it, and so on), whatever the\n * number of DOM nodes it spans: adjacent text nodes, CDATA sections and the\n * comments between them all belong to the same chunk, and a character offset\n * counts from its start.\n */\nexport const getCharacterDataStep = (\n node: Node,\n): { step: number; base: number } => {\n const parent = node.parentNode\n let elementsBefore = 0\n let base = 0\n\n for (let i = 0; parent && i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child || child === node) break\n\n if (isElement(child)) {\n elementsBefore++\n base = 0\n } else if (isCharacterData(child)) {\n base += child.data.length\n }\n }\n\n return { step: elementsBefore * 2 + 1, base }\n}\n\n/**\n * What an odd step addresses under `parent`: the first node of its chunk of\n * character data, or, when that chunk is empty, the boundary it stands for in\n * the parent (the child index right after the preceding element, 0 before\n * the first one). `undefined` when the parent has fewer element children\n * than the step needs.\n */\nexport type CharacterDataChunk =\n | { kind: \"node\"; node: CharacterData }\n | { kind: \"boundary\"; parent: Node; childIndex: number }\n\nexport const findCharacterDataChunk = (\n parent: Node,\n step: number,\n): CharacterDataChunk | undefined => {\n const chunk = (step - 1) / 2\n let elementsBefore = 0\n let childIndex = 0\n\n for (let i = 0; i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child) break\n\n if (isElement(child)) {\n elementsBefore++\n if (elementsBefore > chunk) break\n childIndex = i + 1\n } else if (elementsBefore === chunk && isCharacterData(child)) {\n return { kind: \"node\", node: child }\n }\n }\n\n return elementsBefore < chunk\n ? undefined\n : { kind: \"boundary\", parent, childIndex }\n}\n\n/**\n * The node and local offset a chunk offset lands on, counting from `first`,\n * the chunk's first node. A boundary between two nodes lands at the start of\n * the later one; an offset past the chunk keeps its excess on the last node.\n */\nexport const locateInCharacterDataChunk = (\n first: CharacterData,\n offset: number,\n): { node: CharacterData; offset: number } => {\n let node = first\n let base = 0\n let sibling: Node | null = first\n\n while (sibling && !isElement(sibling)) {\n if (isCharacterData(sibling)) {\n node = sibling\n\n if (offset < base + sibling.data.length) {\n return { node, offset: offset - base }\n }\n\n base += sibling.data.length\n }\n\n sibling = sibling.nextSibling\n }\n\n return { node, offset: offset - (base - node.data.length) }\n}\n\n/**\n * Checks if a parsed CFI only contains indirection with no further path\n * For example: epubcfi(/6/2[cover]!)\n */\nexport function isIndirectionOnly(parsed: ParsedCfi): boolean {\n // If it's a range, it can't be just indirection\n if (isParsedCfiRange(parsed)) {\n return false\n }\n\n // For an indirection-only CFI:\n // 1. It must have at least one part\n // 2. It must end with an indirection marker (!)\n // 3. There must be no content after the indirection marker\n\n // Check if there's indirection (marked by presence of multiple parts)\n // AND the last part is empty (nothing after the indirection marker)\n const lastPart = parsed[parsed.length - 1]\n\n return parsed.length > 1 && (lastPart === undefined || lastPart.length === 0)\n}\n\n/**\n * Check if a parsed CFI is a range\n */\nexport function isParsedCfiRange(parsed: ParsedCfi): parsed is CfiRange {\n return (\n parsed !== null &&\n typeof parsed === \"object\" &&\n \"parent\" in parsed &&\n \"start\" in parsed &&\n \"end\" in parsed\n )\n}\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n * Based on the EPUB CFI 1.1 specification: https://idpf.org/epub/linking/cfi/epub-cfi.html\n */\n\nimport { isCFI } from \"./utils\"\n\n/**\n * Interface for a parsed CFI part\n *\n * According to the EPUB CFI 1.1 specification, each step in a CFI path can have\n * its own properties including ID assertions, character offsets, and extension parameters.\n * Extensions are parameters in the form of key-value pairs that can be attached to any\n * step in the CFI path to provide additional information or metadata about that specific step.\n */\nexport interface CfiPart {\n index: number\n id?: string\n offset?: number\n temporal?: number\n spatial?: number[]\n text?: string[]\n side?: string\n /**\n * Extension parameters for this CFI path step\n *\n * The EPUB CFI spec allows for extension parameters to be attached to any step in the path.\n * These are key-value pairs that provide additional information or metadata.\n * For example, in /6/4[chap01ref]!/4[body01]/10[para05]/2/1:3[;vnd.example.param=value],\n * the extension parameter is \"vnd.example.param\" with value \"value\".\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Interface for a parsed CFI range\n */\nexport interface CfiRange {\n parent: CfiPart[][]\n start: CfiPart[][]\n end: CfiPart[][]\n}\n\n/**\n * Interface for a parsed CFI\n */\nexport type ParsedCfi = CfiPart[][] | CfiRange\n\n/**\n * Unwrap a CFI string from the epubcfi() function\n * @param cfi The CFI string to unwrap\n * @returns The unwrapped CFI string\n */\nexport function unwrapCfi(cfi: string): string {\n const match = cfi.match(isCFI)\n return match ? match[1] || cfi : cfi\n}\n\n/**\n * Token type for CFI parsing\n */\ntype CfiToken = [string, string | number]\n\n/**\n * Tokenize a CFI string into an array of tokens\n * @param cfi The CFI string to tokenize\n * @returns An array of tokens\n */\nfunction tokenize(cfi: string): CfiToken[] {\n const tokens: CfiToken[] = []\n let state: string | null = null\n let isEscaped = false\n let value = \"\"\n\n const push = (token: CfiToken) => {\n tokens.push(token)\n state = null\n value = \"\"\n }\n\n const cat = (c: string) => {\n value += c\n isEscaped = false\n }\n\n const unwrappedCfi = unwrapCfi(cfi).trim()\n const chars = Array.from(unwrappedCfi).concat(\"\")\n\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]\n\n if (!char) {\n // End of string, push any pending token\n if (state === \"/\" || state === \":\") {\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n push([\"[\", value])\n } else if (state === \";\" || state?.startsWith(\";\")) {\n push([state, value])\n } else if (state === \"!\") {\n // Make sure to push the '!' token at the end of the string\n push([\"!\", 0])\n }\n break\n }\n\n // Handle escape characters\n if (char === \"^\" && !isEscaped) {\n isEscaped = true\n continue\n }\n\n if (state === \"!\") {\n push([\"!\", 0])\n } else if (state === \",\") {\n push([\",\", 0])\n } else if (state === \"/\" || state === \":\") {\n if (/^\\d$/.test(char)) {\n cat(char)\n continue\n }\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n if (char === \":\") {\n push([\"@\", parseFloat(value)])\n state = \"@\"\n continue\n }\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n if (char === \";\" && !isEscaped) {\n push([\"[\", value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([\"[\", value])\n } else {\n cat(char)\n continue\n }\n } else if (state === \";\") {\n // Handle extension parameter key\n if (char === \"=\" && !isEscaped) {\n state = `;${value}`\n value = \"\"\n } else if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state?.startsWith(\";\")) {\n // Handle extension parameter value\n if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state === null && char === \";\") {\n // Handle standalone extension parameters (not inside brackets)\n state = \";\"\n }\n\n if (\n char === \"/\" ||\n char === \":\" ||\n char === \"~\" ||\n char === \"@\" ||\n char === \"[\" ||\n char === \"!\" ||\n char === \",\"\n ) {\n state = char\n }\n }\n\n return tokens\n}\n\n/**\n * Find indices of tokens with a specific type\n * @param tokens The tokens to search\n * @param type The type to find\n * @returns An array of indices\n */\nfunction findTokenIndices(\n tokens: CfiToken[] | undefined,\n type: string,\n): number[] {\n if (!tokens) {\n return []\n }\n\n return tokens\n .map((token, i) => (token[0] === type ? i : null))\n .filter((i): i is number => i !== null)\n}\n\n/**\n * Split an array at specific indices\n * @param arr The array to split\n * @param indices The indices to split at\n * @returns An array of arrays\n */\nfunction splitAt<T>(arr: T[], indices: number[]): T[][] {\n const result: T[][] = []\n let start = 0\n\n for (const index of indices) {\n result.push(arr.slice(start, index))\n start = index\n }\n\n result.push(arr.slice(start))\n return result\n}\n\n/**\n * Parse a single part of a CFI\n * @param tokens The tokens to parse\n * @returns An array of CFI parts\n */\nfunction parsePart(tokens: CfiToken[]): CfiPart[] {\n const parts: CfiPart[] = []\n\n // Group tokens by path step\n const pathStepTokens: { [key: number]: CfiToken[] } = {}\n let currentPathStep = -1\n\n // First pass: group tokens by path step\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \"/\") {\n currentPathStep++\n parts[currentPathStep] = { index: val as number }\n pathStepTokens[currentPathStep] = []\n } else if (currentPathStep >= 0) {\n pathStepTokens[currentPathStep]?.push(token)\n }\n }\n\n // Second pass: process tokens for each path step\n for (let stepIndex = 0; stepIndex < parts.length; stepIndex++) {\n const currentPart = parts[stepIndex]\n if (!currentPart) continue\n\n const stepsTokens = pathStepTokens[stepIndex] || []\n\n for (let i = 0; i < stepsTokens.length; i++) {\n const token = stepsTokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \":\") {\n currentPart.offset = val as number\n } else if (type === \"~\") {\n currentPart.temporal = val as number\n } else if (type === \"@\") {\n currentPart.spatial = (currentPart.spatial || []).concat(val as number)\n } else if (type === \";s\") {\n currentPart.side = val as string\n } else if (type.startsWith(\";\") && type !== \";s\") {\n // This is an extension parameter\n const paramName = type.substring(1)\n if (!currentPart.extensions) {\n currentPart.extensions = {}\n }\n currentPart.extensions[paramName] = val as string\n } else if (type === \"[\") {\n // Determine if this is an ID or text assertion\n const looksLikeId =\n typeof val === \"string\" && !val.includes(\" \") && val.length < 50\n\n if (i === 0 && looksLikeId && !currentPart.id) {\n currentPart.id = val as string\n } else {\n // Otherwise, it's a text assertion\n if (val !== \"\") {\n // Split on comma and trim each part\n const parts = (val as string).split(\",\").map((part) => part.trim())\n currentPart.text = parts\n }\n }\n }\n }\n }\n\n return parts\n}\n\n/**\n * Parse a CFI with indirections\n * @param tokens The tokens to parse\n * @returns An array of arrays of CFI parts\n */\nfunction parseIndirection(tokens: CfiToken[]): CfiPart[][] {\n const indirectionIndices = findTokenIndices(tokens, \"!\")\n\n return splitAt(tokens, indirectionIndices).map(parsePart)\n}\n\n/**\n * Parse a CFI string into a structured representation\n * @param cfi The CFI string to parse\n * @returns A parsed CFI\n */\nexport function parse(cfi: string): ParsedCfi {\n if (!cfi) {\n throw new Error(\"CFI string cannot be empty\")\n }\n\n const tokens = tokenize(cfi)\n if (!tokens || tokens.length === 0) {\n throw new Error(\"Failed to tokenize CFI string\")\n }\n\n const commaIndices = findTokenIndices(tokens, \",\")\n\n if (commaIndices.length === 0) {\n return parseIndirection(tokens)\n }\n\n const [parentTokens, startTokens, endTokens] = splitAt(tokens, commaIndices)\n\n // Patch: If startTokens or endTokens are just offsets, attach them to the last parent path\n function attachOffsetToParent(\n parent: CfiPart[][],\n offsetTokens: CfiToken[],\n ): CfiPart[][] {\n if (!offsetTokens || offsetTokens.length === 0) return [[]]\n\n // Filter out comma tokens and check if the remaining tokens are just an offset\n const filteredTokens = offsetTokens.filter((token) => token[0] !== \",\")\n\n // Only support a single offset (e.g., [:9] or [:25])\n if (\n filteredTokens.length === 1 &&\n filteredTokens[0] &&\n filteredTokens[0][0] === \":\"\n ) {\n // Clone the last parent path\n const lastParentPath =\n parent.length > 0 ? parent[parent.length - 1] : undefined\n const offsetToken = filteredTokens[0]\n if (lastParentPath && lastParentPath.length > 0 && offsetToken) {\n const pathClone = lastParentPath.map((part) => ({ ...part }))\n const lastPart = pathClone[pathClone.length - 1]\n if (lastPart) {\n lastPart.offset = offsetToken[1] as number\n }\n return [pathClone]\n }\n // fallback: just return an empty path\n return [[]]\n }\n // If not just an offset, parse as normal\n return parseIndirection(offsetTokens)\n }\n\n const parent = parseIndirection(parentTokens || [])\n const start = attachOffsetToParent(parent, startTokens || [])\n const end = attachOffsetToParent(parent, endTokens || [])\n\n return {\n parent,\n start,\n end,\n }\n}\n","import { type CfiPart, type ParsedCfi, parse } from \"./parse\"\n\n/**\n * Collapses a parsed CFI to a single path (private helper for compare)\n * @param parsed The parsed CFI to collapse\n * @param toEnd Whether to collapse to the end of a range\n * @returns A collapsed CFI\n */\nfunction collapse(parsed: ParsedCfi, toEnd = false): CfiPart[][] {\n if (typeof parsed === \"string\") {\n return collapse(parse(parsed), toEnd)\n }\n\n if (\"parent\" in parsed) {\n // It's a range\n if (toEnd) {\n return parsed.parent.concat(parsed.end)\n }\n return parsed.parent.concat(parsed.start)\n }\n\n // It's a single CFI\n return parsed\n}\n\n/**\n * Get the weight of a step type for sorting\n * According to rule 9: character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n */\nfunction getStepTypeWeight(part: CfiPart): number {\n if (part.offset !== undefined) return 1 // character offset (:)\n if (part.index !== undefined) return 2 // child (/)\n if (part.temporal !== undefined || part.spatial !== undefined) return 3 // temporal-spatial (~ or @)\n return 4 // reference (!)\n}\n\n/**\n * Compare two CFIs according to the EPUB CFI specification sorting rules (section 3.2)\n * @param a The first CFI\n * @param b The second CFI\n * @returns -1 if a < b, 0 if a = b, 1 if a > b\n */\nexport function compare(a: ParsedCfi | string, b: ParsedCfi | string): number {\n const aParsed = typeof a === \"string\" ? parse(a) : a\n const bParsed = typeof b === \"string\" ? parse(b) : b\n\n if (\"parent\" in aParsed || \"parent\" in bParsed) {\n // At least one is a range\n return (\n compare(collapse(aParsed), collapse(bParsed)) ||\n compare(collapse(aParsed, true), collapse(bParsed, true))\n )\n }\n\n // Both are single CFIs\n for (let i = 0; i < Math.max(aParsed.length, bParsed.length); i++) {\n const p = aParsed[i] || []\n const q = bParsed[i] || []\n const maxIndex = Math.max(p.length, q.length) - 1\n\n for (let i = 0; i <= maxIndex; i++) {\n const x = p[i]\n const y = q[i]\n\n if (!x) return -1\n if (!y) return 1\n\n // Compare element indices (rule 3 & 4)\n // The step index locates the node in the document, so it always wins.\n if (x.index > y.index) return 1\n if (x.index < y.index) return -1\n\n // Compare step types (rule 9), only as a tie-breaker between steps at\n // the same index (eg: an offset terminating at a node vs a child step\n // descending into it)\n // character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n const xStepType = getStepTypeWeight(x)\n const yStepType = getStepTypeWeight(y)\n\n if (xStepType !== yStepType) {\n return xStepType - yStepType\n }\n\n // Compare temporal positions (rule 7 & 8)\n // Temporal is more important than spatial\n const xTemporal = x.temporal !== undefined\n const yTemporal = y.temporal !== undefined\n\n if (xTemporal && !yTemporal) return 1\n if (!xTemporal && yTemporal) return -1\n\n if (xTemporal && yTemporal) {\n if ((x.temporal ?? 0) > (y.temporal ?? 0)) return 1\n if ((x.temporal ?? 0) < (y.temporal ?? 0)) return -1\n }\n\n // Compare spatial positions (rule 5 & 6)\n const xSpatial = x.spatial !== undefined\n const ySpatial = y.spatial !== undefined\n\n if (xSpatial && !ySpatial) return 1\n if (!xSpatial && ySpatial) return -1\n\n if (xSpatial && ySpatial) {\n // Y position is more important than X (rule 5)\n const xY = x.spatial?.[1] ?? 0\n const yY = y.spatial?.[1] ?? 0\n\n if (xY > yY) return 1\n if (xY < yY) return -1\n\n // Compare X positions if Y positions are equal\n const xX = x.spatial?.[0] ?? 0\n const yX = y.spatial?.[0] ?? 0\n\n if (xX > yX) return 1\n if (xX < yX) return -1\n }\n\n // Last part comparison including character offsets\n if (i === maxIndex) {\n if ((x.offset ?? 0) > (y.offset ?? 0)) return 1\n if ((x.offset ?? 0) < (y.offset ?? 0)) return -1\n }\n }\n }\n\n return 0\n}\n","/**\n * Standard EPUB CFIs that target a content document first address the\n * package document spine element.\n *\n * EPUB package documents order their element children as metadata, manifest, then spine.\n * CFI element steps are even-numbered, so the third element child is step 6.\n */\nexport const EPUB_CFI_PACKAGE_SPINE_STEP_INDEX = 6\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n */\n\nimport {\n cfiEscape,\n findCommonAncestor,\n getCharacterDataStep,\n getElementStep,\n isCharacterData,\n isElement,\n isNode,\n} from \"./utils\"\n\n/**\n * Options for generating CFIs\n */\nexport interface GenerateOptions {\n /**\n * Whether to include text assertions for more robust CFIs\n */\n includeTextAssertions?: boolean\n\n /**\n * The maximum length of text to use for text assertions\n * Default is 10 characters\n */\n textAssertionLength?: number\n\n /**\n * Whether to include a side bias assertion\n */\n includeSideBias?: \"before\" | \"after\"\n\n /**\n * Whether to include spatial coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatialOffset?: [number, number]\n\n /**\n * Extension parameters to include in the CFI\n * Keys should be parameter names, values should be the parameter values\n * Vendor-specific parameters should be prefixed with 'vnd.' followed by the vendor name\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Position in a document, consisting of a node and optional offset\n */\nexport interface CfiPosition {\n /**\n * The DOM node\n */\n node?: Node | null\n\n /**\n * Character offset within the node (for text nodes)\n */\n offset?: number\n\n /**\n * Temporal position in seconds (for audio/video content)\n */\n temporal?: number\n\n /**\n * Spatial position as [x,y] coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatial?: [number, number]\n\n /**\n * Spine index (0-based) for the document containing the node\n */\n spineIndex?: number\n\n /**\n * ID of the spine item\n */\n spineId?: string\n}\n\n/**\n * Extract a suitable text assertion from a text node\n */\nfunction extractTextAssertion(\n textNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n): string | null {\n if (!textNode.textContent || textNode.textContent.trim() === \"\") {\n return null\n }\n\n const textContent = textNode.textContent\n const maxLength = options.textAssertionLength || 10\n\n // If we have an offset, use the text around that position\n if (offset !== undefined && offset <= textContent.length) {\n // We'll take a portion before and after the offset\n const halfLength = Math.floor(maxLength / 2)\n const start = Math.max(0, offset - halfLength)\n const end = Math.min(textContent.length, offset + halfLength)\n return textContent.substring(start, end)\n }\n\n // Otherwise, just take the first part of the text\n return textContent.substring(0, Math.min(textContent.length, maxLength))\n}\n\n/**\n * Helper function to format spatial coordinates\n */\nfunction formatSpatialOffset(spatial: [number, number]): string {\n const [x, y] = spatial\n // Ensure values are within 0-100 range\n const safeX = Math.max(0, Math.min(100, x))\n const safeY = Math.max(0, Math.min(100, y))\n return `@${safeX}:${safeY}`\n}\n\n/**\n * Helper function to add temporal and spatial offsets to a CFI string\n */\nfunction addOffsets(\n cfi: string,\n temporal?: number,\n spatial?: [number, number],\n): string {\n let result = cfi\n\n if (temporal !== undefined) {\n result += `~${temporal}`\n }\n\n if (spatial !== undefined) {\n result += formatSpatialOffset(spatial)\n }\n\n return result\n}\n\n/**\n * Helper function to add text assertion and side bias to a CFI string\n */\nfunction addTextAssertionAndSideBias(\n cfi: string,\n textAssertion: string | null,\n sideBias?: \"before\" | \"after\",\n): string {\n let result = cfi\n\n if (textAssertion) {\n result += `[${cfiEscape(textAssertion)}]`\n }\n\n if (sideBias) {\n const sideBiasChar = sideBias === \"before\" ? \"b\" : \"a\"\n if (!textAssertion) {\n result += `[;s=${sideBiasChar}]`\n } else {\n result = `${result.substring(0, result.length - 1)};s=${sideBiasChar}]`\n }\n }\n\n return result\n}\n\n/**\n * Generate a CFI for a single node in the DOM\n */\nfunction generatePoint(\n node: Node | null,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n let cfi = \"\"\n let currentNode: Node | null = node\n let textNode: Node | null = null\n let cfiOffset = offset\n\n // Character data is addressed through the chunk of its parent it belongs\n // to, and the offset counts from the start of that chunk (EPUB CFI 3.1.1).\n // Any other non-element node (a comment) is ignored by the spec, so it gets\n // the position of the chunk it sits in.\n if (node && !isElement(node)) {\n const parentNode = node.parentNode\n\n if (!parentNode) {\n throw new Error(\"Text node doesn't have a parent\")\n }\n\n const { step, base } = getCharacterDataStep(node)\n\n cfi = `/${step}`\n textNode = isCharacterData(node) ? node : null\n cfiOffset = offset === undefined ? undefined : base + offset\n currentNode = parentNode\n }\n\n // Set up text assertion if needed\n let textAssertion: string | null = null\n if (textNode && options.includeTextAssertions) {\n textAssertion = extractTextAssertion(textNode, offset, options)\n }\n\n // Build the CFI path from the current node up to the html element\n while (currentNode?.parentNode) {\n const step = getElementStep(currentNode)\n\n // If the node has an ID, add it to the CFI\n if (isElement(currentNode) && currentNode.id) {\n cfi = `/${step}[${cfiEscape(currentNode.id)}]${cfi}`\n } else {\n cfi = `/${step}${cfi}`\n }\n\n // If we've reached the html element, stop traversing up\n if (currentNode.parentNode.nodeName.toLowerCase() === \"html\") {\n break\n }\n\n currentNode = currentNode.parentNode\n }\n\n // Add the character offset if provided\n if (cfiOffset !== undefined) {\n cfi += `:${cfiOffset}`\n }\n\n // Add temporal and spatial offsets using helper\n cfi = addOffsets(\n cfi,\n position?.temporal,\n position?.spatial || options.spatialOffset,\n )\n\n // Add text assertion and side bias using helper\n cfi = addTextAssertionAndSideBias(cfi, textAssertion, options.includeSideBias)\n\n cfi = addExtensions(cfi, options.extensions)\n\n return cfi\n}\n\n/**\n * Generate a relative path from one node to another\n */\nfunction generateRelativePath(\n fromNode: Node,\n toNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n // A character offset counts from the start of the chunk the node belongs to\n const base = isElement(toNode) ? 0 : getCharacterDataStep(toNode).base\n\n if (fromNode === toNode) {\n let result = offset !== undefined ? `:${base + offset}` : \"\"\n result = addOffsets(result, position?.temporal, position?.spatial)\n return result\n }\n\n const path: string[] = []\n let currentNode: Node | null = toNode\n\n // Build path from toNode up to fromNode (exclusive)\n while (currentNode && currentNode !== fromNode) {\n const parentNode: Node | null = currentNode.parentNode\n if (!parentNode) break\n\n const step = isElement(currentNode)\n ? getElementStep(currentNode)\n : getCharacterDataStep(currentNode).step\n\n // If the node has an ID, add it\n if (isElement(currentNode) && currentNode.id) {\n path.unshift(`/${step}[${cfiEscape(currentNode.id)}]`)\n } else {\n path.unshift(`/${step}`)\n }\n\n currentNode = parentNode\n }\n\n let relativePath = path.join(\"\")\n\n // Add offset if specified\n if (offset !== undefined) {\n relativePath += `:${base + offset}`\n }\n\n // Add temporal and spatial offsets using helper\n relativePath = addOffsets(relativePath, position?.temporal, position?.spatial)\n\n // Add text assertion and side bias using helper\n if (options.includeTextAssertions && isCharacterData(toNode)) {\n const textAssertion = extractTextAssertion(toNode, offset, options)\n relativePath = addTextAssertionAndSideBias(\n relativePath,\n textAssertion,\n options.includeSideBias,\n )\n }\n\n // Add extension parameters if specified\n relativePath = addExtensions(relativePath, options.extensions)\n\n return relativePath\n}\n\nconst serializeExtensions = (extensions: Record<string, string>) => {\n return Object.entries(extensions)\n .map(([key, value]) => {\n // Properly escape the value according to CFI spec\n const escapedValue = cfiEscape(value)\n // URL encode the value to handle special characters\n return `${key}=${encodeURIComponent(escapedValue)}`\n })\n .join(\";\")\n}\n\n/**\n * Add extension parameters to a CFI path\n */\nfunction addExtensions(\n cfi: string,\n extensions?: Record<string, string>,\n): string {\n if (!extensions || Object.keys(extensions).length === 0) {\n return cfi\n }\n\n const extensionString = serializeExtensions(extensions)\n\n // If we're dealing with a bracket at end\n if (cfi.endsWith(\"]\")) {\n // Check if there are already parameters\n const lastBracketIndex = cfi.lastIndexOf(\"[\")\n const content = cfi.substring(lastBracketIndex + 1, cfi.length - 1)\n\n // If content already has these exact extensions, return as is\n if (content.includes(extensionString)) {\n return cfi\n }\n\n // Add extensions with proper separator\n return `${cfi.substring(0, cfi.length - 1)}${content.includes(\";\") ? \";\" : \";\"}${extensionString}]`\n }\n\n // Special case for spine items with indirection\n if (cfi.endsWith(\"!\")) {\n return cfi\n }\n\n // No bracket at the end - add with new brackets\n return `${cfi}[;${extensionString}]`\n}\n\n/**\n * Generate a range CFI between two points in the document\n */\nfunction generateRange(\n startNode: Node,\n startOffset: number,\n endNode: Node,\n endOffset: number,\n options: GenerateOptions = {},\n startPosition?: CfiPosition,\n endPosition?: CfiPosition,\n): string {\n // Find common ancestor\n const ancestor = findCommonAncestor(startNode, endNode)\n\n if (!ancestor) {\n throw new Error(\"No common ancestor found\")\n }\n\n // Generate CFI from ancestor to document\n const ancestorCfi = generatePoint(ancestor, undefined, options)\n\n // Generate path from ancestor to start node\n const startPath = generateRelativePath(\n ancestor,\n startNode,\n startOffset,\n options,\n startPosition,\n )\n\n // Generate path from ancestor to end node\n const endPath = generateRelativePath(\n ancestor,\n endNode,\n endOffset,\n options,\n endPosition,\n )\n\n // For range CFIs, add extensions to each part separately\n if (options.extensions && Object.keys(options.extensions).length > 0) {\n // Add extensions to each part\n const ancestorWithExt = addExtensions(ancestorCfi, options.extensions)\n const startWithExt = addExtensions(startPath, options.extensions)\n const endWithExt = addExtensions(endPath, options.extensions)\n\n return `${ancestorWithExt},${startWithExt},${endWithExt}`\n }\n\n // Combine into a regular range CFI without extensions\n return `${ancestorCfi},${startPath},${endPath}`\n}\n\nconst generateSpineCfi = (\n spineIndex: number,\n spineId?: string,\n options: GenerateOptions = {},\n isFinal?: boolean,\n) => {\n const bracket = \"\"\n const cfiIndex = (spineIndex + 1) * 2\n let cfi = `/6/${cfiIndex}`\n\n if (spineId) {\n cfi += `[${cfiEscape(spineId)}]`\n }\n\n cfi = isFinal ? addExtensions(cfi, options.extensions) : cfi\n\n return `${cfi}${bracket}!`\n}\n\n/**\n * Generate a CFI from a DOM node or position\n *\n * @example\n * // Generate CFI for a single node\n * const cfi = generate(node);\n *\n * @example\n * // Generate CFI for a text node with offset\n * const cfi = generate({ node: textNode, offset: 5 });\n *\n * @example\n * // Generate CFI for a video with temporal offset\n * const cfi = generate({ node: videoElement, temporal: 45.5 });\n *\n * @example\n * // Generate CFI for an image with spatial coordinates\n * const cfi = generate({ node: imageElement, spatial: [50, 75] });\n *\n * @example\n * // Generate a range CFI\n * const cfi = generate({\n * start: { node: startNode, offset: 0 },\n * end: { node: endNode, offset: 10 }\n * });\n *\n * @example\n * // Generate a CFI with spine itemref reference\n * const cfi = generate({\n * node: chapterNode,\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a CFI for a spine item without a node\n * const cfi = generate({\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a robust CFI with text assertions\n * const cfi = generate(node, {\n * includeTextAssertions: true,\n * textAssertionLength: 15\n * });\n */\nexport function generate(\n position: Node | CfiPosition | { start: CfiPosition; end: CfiPosition },\n options: GenerateOptions = {},\n): string {\n // Case 1: Simple Node\n if (isNode(position)) {\n return `epubcfi(${generatePoint(position, undefined, options)})`\n }\n\n let cfi = \"\"\n\n // Non Range case\n if (!(\"start\" in position)) {\n // Add spine itemref reference if specified\n if (position.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n position.spineIndex,\n position.spineId,\n options,\n !position.node,\n )\n\n if (!position.node) {\n return `epubcfi(${cfi})`\n }\n }\n\n // Add the node path\n cfi += generatePoint(\n position.node ?? null,\n position.offset,\n options,\n position,\n )\n\n return `epubcfi(${cfi})`\n }\n\n // Range case\n const { start, end } = position\n\n // Add spine itemref reference if specified (use start position's spine info)\n if (start.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n start.spineIndex,\n start.spineId,\n options,\n !start.node,\n )\n }\n\n if (start.node && end.node) {\n // Add the range path\n cfi += generateRange(\n start.node,\n start.offset ?? 0,\n end.node,\n end.offset ?? 0,\n options,\n start,\n end,\n )\n }\n\n return `epubcfi(${cfi})`\n}\n","import { type CfiPart, type CfiRange, type ParsedCfi, parse } from \"./parse\"\nimport {\n findCharacterDataChunk,\n isCharacterData,\n isIndirectionOnly,\n isNode,\n isParsedCfiRange,\n locateInCharacterDataChunk,\n} from \"./utils\"\n\n/**\n * Options for resolving a CFI\n */\ninterface ResolveOptions {\n /**\n * Whether to throw an error if the CFI cannot be resolved\n * @default false\n */\n throwOnError?: boolean\n\n /**\n * Whether to return a range instead of a single node\n * @default false\n */\n asRange?: boolean\n}\n\n/**\n * Result of resolving a CFI\n */\ninterface ResolveRangeResult extends ResolveResultBase {\n node: Range | null\n isRange: true\n}\n\ninterface ResolveResultBase {\n offset?: number[] | number\n\n /**\n * The temporal offset if applicable\n */\n temporal?: number\n\n /**\n * The spatial offset if applicable\n */\n spatial?: number[]\n\n /**\n * The side bias if applicable\n */\n side?: string\n\n /**\n * Any extension parameters in the CFI\n */\n extensions?: Record<string, string>\n}\n\ninterface ResolveNodeResult extends ResolveResultBase {\n node: Node | null\n isRange: false\n}\n\ntype ResolveResult = ResolveNodeResult | ResolveRangeResult\n\n/**\n * Resolves a CFI string to a DOM node or range\n */\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options: Omit<ResolveOptions, \"asRange\"> & { asRange: true },\n): ResolveRangeResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options?: ResolveOptions,\n): ResolveResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n try {\n const parsedCfi = typeof cfi !== \"string\" ? cfi : parse(cfi)\n\n const result = resolveParsed(parsedCfi, document, options)\n\n return result\n } catch (error) {\n if (options.throwOnError) {\n throw error\n }\n\n return { node: null, isRange: false }\n }\n}\n\n/**\n * Resolves a parsed CFI to a DOM node or range\n */\nfunction resolveParsed(\n parsed: ParsedCfi,\n document: Document,\n options: { asRange?: boolean } = { asRange: false },\n): ResolveResult {\n if (isParsedCfiRange(parsed)) {\n // If it's a range CFI, always return a Range\n return resolveRange(parsed, document)\n }\n\n // Check if this is a CFI with only indirection (e.g., \"epubcfi(/6/2[cover]!)\")\n if (isIndirectionOnly(parsed)) {\n // According to the spec, we cannot resolve beyond the indirection point\n // so we return null for the node\n return createNodeResultObject(null)\n }\n\n const nonIndirectionPart = parsed.at(-1)\n\n // Handle path CFI (indirection)\n if (nonIndirectionPart) {\n return resolvePath(nonIndirectionPart, document, options)\n }\n\n throw new Error(\"Invalid CFI structure\")\n}\n\n/**\n * Resolves a CFI range to a DOM range\n */\nfunction resolveRange(range: CfiRange, document: Document): ResolveResult {\n // Get the parent paths and start/end paths\n const parentPaths = range.parent\n const startPath = range.start[0] || []\n const endPath = range.end[0] || []\n\n // Find the parent node\n let parentNode: Node | null = document.documentElement\n\n if (parentPaths.length > 0) {\n // If there's indirection (multiple parent paths), resolve the indirection first\n if (parentPaths.length > 1) {\n const indirectionPath = parentPaths[0]\n if (indirectionPath) {\n const indirectionResult = resolvePath(indirectionPath, document)\n if (isNode(indirectionResult.node)) {\n parentNode = indirectionResult.node\n }\n }\n if (parentPaths.length > 1 && parentNode) {\n const actualParentPath = parentPaths[1]\n if (actualParentPath) {\n const actualParentResult = resolvePath(actualParentPath, document)\n if (isNode(actualParentResult.node)) {\n parentNode = actualParentResult.node\n }\n }\n }\n } else {\n const parentPath = parentPaths[0]\n if (parentPath) {\n const parentResult = resolvePath(parentPath, document)\n if (isNode(parentResult.node)) {\n parentNode = parentResult.node\n }\n }\n }\n }\n\n if (!parentNode) {\n throw new Error(\"Failed to resolve parent node in CFI range\")\n }\n\n // If parentNode is a text node, use it directly for start/end\n const isParentTextNode = isCharacterData(parentNode)\n\n let startNode: Node\n let endNode: Node\n let startOffset = 0\n let endOffset = 0\n\n if (isParentTextNode) {\n startNode = parentNode\n endNode = parentNode\n // Use offsets from the last part of startPath/endPath\n const lastStartPart = startPath[startPath.length - 1]\n const lastEndPart = endPath[endPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n } else {\n // Offset-only local paths (`,:5`) are attached to the parent path during\n // parsing, so a non-empty path here always carries real steps that must be\n // traversed — a single step with an offset (`/1:2`) is a path to a child\n // node, not an offset on the parent.\n const isStartOffsetOnly = startPath.length === 0\n const isEndOffsetOnly = endPath.length === 0\n\n if (isStartOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (start)\")\n startNode = parentNode\n startOffset = startPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, startPath, 0, true)\n if (!traversed)\n throw new Error(\"Failed to resolve start node in CFI range\")\n startNode = traversed.node\n const lastStartPart = startPath[startPath.length - 1]\n startOffset =\n traversed.childIndex ??\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ??\n 0\n }\n\n if (isEndOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (end)\")\n endNode = parentNode\n endOffset = endPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, endPath, 0, true)\n if (!traversed) throw new Error(\"Failed to resolve end node in CFI range\")\n endNode = traversed.node\n const lastEndPart = endPath[endPath.length - 1]\n endOffset =\n traversed.childIndex ??\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ??\n 0\n }\n }\n\n // A chunk offset can fall in a node after the chunk's first one\n const start = locate(startNode, startOffset)\n const end = locate(endNode, endOffset)\n\n // Create and return a DOM range\n const domRange = document.createRange()\n domRange.setStart(start.node, start.offset ?? 0)\n domRange.setEnd(end.node, end.offset ?? 0)\n\n return {\n ...createBaseResultObject(startPath[startPath.length - 1]),\n node: domRange,\n isRange: true,\n offset: start.offset,\n }\n}\n\n/**\n * Extracts side bias from a CFI part\n */\nfunction extractSideBias(part: CfiPart | undefined): string | undefined {\n // Return the side if it exists\n if (part?.side) return part.side\n\n // Look for side bias in text assertions\n if (part?.text && part.text.length > 0) {\n const text = part.text[0]\n // The CFI spec says side bias can be a=after or b=before\n if (text) {\n const sideBiasMatch = text.match(/^([ab])$/)\n if (sideBiasMatch) {\n return sideBiasMatch[1]\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Whether a step addresses a chunk of character data: per the CFI spec,\n * element steps are even and character data steps odd.\n */\nfunction isCharacterDataStep(part: CfiPart): boolean {\n return part.index % 2 !== 0\n}\n\n/**\n * Returns the extensions from the last valid part of a parsed CFI\n */\nexport function resolveExtensions(parsedCfi: ParsedCfi) {\n const parts = isParsedCfiRange(parsedCfi) ? parsedCfi.end : parsedCfi\n const lastPart = parts.at(-1)\n const lastPartPath = lastPart?.at(-1)\n\n return lastPartPath?.extensions\n}\n\nfunction createBaseResultObject(part?: CfiPart) {\n const sideBias = extractSideBias(part)\n const extensions = part?.extensions\n\n return {\n offset: part?.offset,\n temporal: part?.temporal,\n spatial: part?.spatial,\n side: sideBias,\n extensions,\n }\n}\n\nfunction createNodeResultObject(\n node: Node | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: false,\n ...createBaseResultObject(part),\n }\n}\n\nfunction createRangeResultObject(\n node: Range | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: true,\n ...createBaseResultObject(part),\n }\n}\n\n/**\n * Creates a DOM range for a given node with optional offset\n */\nfunction createRangeForNode(\n document: Document,\n node: Node,\n offset?: number | number[],\n): Range {\n const range = document.createRange()\n range.selectNodeContents(node)\n\n if (offset !== undefined) {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n if (isCharacterData(node)) {\n range.setStart(node, offsetValue || 0)\n }\n }\n\n return range\n}\n\n/**\n * The node and offset a character offset lands on. An offset on a chunk step\n * counts across the whole chunk, so it can fall in a node after the first one.\n */\nfunction locate(\n node: Node,\n offset: number | number[] | undefined,\n): { node: Node; offset: number | undefined } {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n\n if (offsetValue === undefined || !isCharacterData(node)) {\n return { node, offset: offsetValue }\n }\n\n return locateInCharacterDataChunk(node, offsetValue)\n}\n\n/**\n * Where a path lands: a node, or, for a step into an empty chunk of character\n * data, the boundary that chunk stands for in its parent (a child index).\n */\ntype PathTarget = { node: Node; childIndex?: number }\n\n/**\n * Traverses the DOM tree based on CFI path parts\n */\nfunction traverseNodePath(\n currentNode: Node | null,\n path: CfiPart[],\n startIndex: number,\n throwOnError: boolean,\n): PathTarget | null {\n let _currentNode = currentNode\n\n for (let i = startIndex; i < path.length; i++) {\n const part = path[i]\n if (!_currentNode || !part) break\n\n if (isCharacterDataStep(part)) {\n const chunk = findCharacterDataChunk(_currentNode, part.index)\n\n if (!chunk) {\n if (throwOnError) {\n throw new Error(`No character data chunk: ${part.index}`)\n }\n _currentNode = null\n break\n }\n\n if (chunk.kind === \"boundary\") {\n // Nothing lies below an empty chunk\n if (i < path.length - 1) {\n if (throwOnError) {\n throw new Error(`Empty character data chunk: ${part.index}`)\n }\n _currentNode = null\n break\n }\n\n return { node: chunk.parent, childIndex: chunk.childIndex }\n }\n\n _currentNode = chunk.node\n } else {\n const childElements: Node[] = Array.from(_currentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n const index = Math.floor(part.index / 2) - 1\n\n if (index >= 0 && index < childElements.length) {\n const nextNode = childElements[index]\n if (nextNode) {\n _currentNode = nextNode\n }\n } else {\n if (throwOnError) {\n throw new Error(`Invalid element step index: ${part.index}`)\n }\n _currentNode = null\n break\n }\n }\n }\n\n return _currentNode ? { node: _currentNode } : null\n}\n\n/**\n * Resolves a CFI path to a DOM node\n */\nfunction resolvePath(\n path: CfiPart[],\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n const { throwOnError = false, asRange = false } = options\n\n if (!document) {\n if (throwOnError) {\n throw new Error(\"Document is not available\")\n }\n return createNodeResultObject(null)\n }\n\n // Look for an element with an ID first\n const { node: nodeById, remainingPathIndex } = findNodeById(document, path)\n\n // If there's no remaining path to process after the ID node, return the ID node\n if (nodeById && remainingPathIndex >= path.length) {\n const lastPart = path.at(-1)\n\n if (asRange) {\n const range = createRangeForNode(document, nodeById, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(nodeById, lastPart)\n }\n\n // Start traversal from the ID node if found, otherwise start from the document root\n const currentNode: Node | null = nodeById || document.documentElement\n const startIndex = nodeById ? remainingPathIndex : 0\n\n // Handle virtual positions\n if (asRange && path.length > 0) {\n const lastPart = path[path.length - 1]\n if (lastPart && !isCharacterDataStep(lastPart)) {\n // Handle position before first element (index 0)\n if (lastPart.index === 0 && currentNode) {\n const range = document.createRange()\n range.setStart(currentNode, 0)\n range.setEnd(currentNode, 0)\n return createRangeResultObject(range, lastPart)\n }\n\n // Handle position after last element\n const parentNode = traverseNodePath(\n currentNode,\n path.slice(0, -1),\n startIndex,\n throwOnError,\n )?.node\n if (parentNode) {\n const childElements: Node[] = Array.from(parentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n const index = Math.floor(lastPart.index / 2) - 1\n\n // If the index is equal to the number of child elements, it's a position after the last element\n if (index === childElements.length) {\n const range = document.createRange()\n range.selectNodeContents(parentNode)\n range.collapse(false) // Collapse to end\n return createRangeResultObject(range, lastPart)\n }\n }\n }\n }\n\n const target = traverseNodePath(currentNode, path, startIndex, throwOnError)\n\n if (!target) {\n if (throwOnError) {\n throw new Error(\"Failed to resolve CFI path\")\n }\n return createNodeResultObject(null)\n }\n\n const lastPart = path.at(-1)\n const located =\n target.childIndex === undefined\n ? locate(target.node, lastPart?.offset)\n : { node: target.node, offset: target.childIndex }\n\n if (asRange) {\n const range =\n target.childIndex === undefined\n ? createRangeForNode(document, located.node, located.offset)\n : createCollapsedRange(document, target.node, target.childIndex)\n return {\n ...createRangeResultObject(range, lastPart),\n offset: located.offset,\n }\n }\n\n return {\n ...createNodeResultObject(located.node, lastPart),\n offset: located.offset,\n }\n}\n\n/**\n * A range collapsed on a boundary of a node's children\n */\nfunction createCollapsedRange(\n document: Document,\n node: Node,\n childIndex: number,\n): Range {\n const range = document.createRange()\n range.setStart(node, childIndex)\n range.setEnd(node, childIndex)\n return range\n}\n\n/**\n * Find a node by ID from a CFI path.\n * Starting from the last part and working backwards.\n * Returns the node and the remaining path that needs to be processed.\n */\nfunction findNodeById(\n document: Document,\n parts: CfiPart[],\n): { node: Node | null; remainingPathIndex: number } {\n for (let i = parts.length - 1; i >= 0; i--) {\n const part = parts[i]\n if (part?.id) {\n const node = document.getElementById(part.id)\n if (node) return { node, remainingPathIndex: i + 1 }\n }\n }\n\n return { node: null, remainingPathIndex: 0 }\n}\n","import type { CfiPart, ParsedCfi } from \"./parse\"\nimport { cfiEscape } from \"./utils\"\n\n/**\n * Serialize a single CFI part into a string\n * @param part The CFI part to serialize\n * @returns The serialized string representation\n */\nfunction serializePart(part: CfiPart): string {\n let result = `/${part.index}`\n\n // Handle ID assertion first if present\n if (part.id) {\n result += `[${cfiEscape(part.id)}`\n // Add extensions inside ID brackets if present\n if (part.extensions) {\n for (const [key, value] of Object.entries(part.extensions)) {\n result += `;${key}=${cfiEscape(value)}`\n }\n }\n result += `]`\n }\n\n // Handle character offset\n if (part.offset !== undefined) {\n result += `:${part.offset}`\n }\n\n // Handle temporal offset\n if (part.temporal !== undefined) {\n result += `~${part.temporal}`\n }\n\n // Handle spatial offset\n if (part.spatial && part.spatial.length > 0) {\n result += `@${part.spatial.join(\":\")}`\n }\n\n // Handle text assertions and side bias in brackets\n const inBrackets: string[] = []\n\n // Handle text assertions\n if (part.text && part.text.length > 0) {\n inBrackets.push(part.text.map(cfiEscape).join(\",\"))\n }\n\n // Handle side bias and extensions in brackets\n if (part.side || (part.extensions && !part.id)) {\n if (part.side) {\n inBrackets.push(`;s=${part.side}`)\n }\n if (part.extensions && !part.id) {\n for (const [key, value] of Object.entries(part.extensions)) {\n inBrackets.push(`;${key}=${cfiEscape(value)}`)\n }\n }\n }\n\n // Add bracketed attributes if any\n if (inBrackets.length > 0) {\n result += `[${inBrackets.join(\"\")}]`\n }\n\n return result\n}\n\n/**\n * Serialize a single CFI path into a string\n * @param path The CFI path to serialize\n * @returns The serialized string representation\n */\nfunction serializePath(path: CfiPart[]): string {\n return path.map((part) => serializePart(part)).join(\"\")\n}\n\n/**\n * Serialize a parsed CFI into a string\n * @param parsed The parsed CFI to serialize\n * @returns The serialized CFI string\n */\nexport function serialize(parsed: ParsedCfi): string {\n if (Array.isArray(parsed)) {\n // Handle simple CFI or CFI with indirections\n return `epubcfi(${parsed.map(serializePath).join(\"!\")})`\n }\n\n // Handle CFI range\n const parent = parsed.parent.map(serializePath).join(\"!\")\n const start = parsed.start.map(serializePath).join(\"!\")\n const end = parsed.end.map(serializePath).join(\"!\")\n return `epubcfi(${parent},${start},${end})`\n}\n","import { EPUB_CFI_PACKAGE_SPINE_STEP_INDEX } from \"./constants\"\nimport type { CfiPart, ParsedCfi } from \"./parse\"\nimport { parse } from \"./parse\"\nimport { serialize } from \"./serialize\"\nimport { isParsedCfiRange } from \"./utils\"\n\nexport type EpubCfiSpineItemref = {\n spineIndex: number\n spineId?: string\n extensions?: Record<string, string>\n}\n\nexport type EpubCfiSpineItemrefUpdate = {\n spineIndex?: number\n spineId?: string\n extensions?: Record<string, string | undefined>\n}\n\nexport const spineIndexToEpubCfiItemrefStepIndex = (spineIndex: number) =>\n (spineIndex + 1) * 2\n\nexport const epubCfiItemrefStepIndexToSpineIndex = (stepIndex: number) =>\n stepIndex / 2 - 1\n\nconst getFirstPackageDocumentPath = (parsed: ParsedCfi) => {\n return isParsedCfiRange(parsed) ? parsed.parent[0] : parsed[0]\n}\n\nconst getSpineItemrefPart = (parsed: ParsedCfi): CfiPart | undefined => {\n const path = getFirstPackageDocumentPath(parsed)\n\n if (path?.[0]?.index !== EPUB_CFI_PACKAGE_SPINE_STEP_INDEX) return undefined\n\n return path[1]\n}\n\nconst mergeExtensions = (\n currentExtensions: Record<string, string> | undefined,\n extensionUpdates: Record<string, string | undefined>,\n) => {\n const nextExtensions = { ...currentExtensions }\n\n for (const [name, value] of Object.entries(extensionUpdates)) {\n if (value === undefined) {\n delete nextExtensions[name]\n } else {\n nextExtensions[name] = value\n }\n }\n\n return Object.keys(nextExtensions).length > 0 ? nextExtensions : undefined\n}\n\n/**\n * Per the EPUB CFI 1.1 spec, the bracket assertion attached to the spine\n * itemref step is unambiguously an `id_assertion` (the IDREF of the\n * `<itemref>` element) — `text_assertion`s only appear at leaf/character\n * steps. The shared CFI parser however uses heuristics (no spaces, short\n * length) to disambiguate id vs text assertions, so a spineId that\n * contains spaces or is long can end up classified as `text`. At this\n * position-known step we recover the intended id from the text fallback.\n */\nconst readSpineIdFromPart = (part: CfiPart): string | undefined => {\n if (part.id !== undefined) return part.id\n\n if (!part.text || part.text.length === 0) return undefined\n\n return part.text.join(\",\")\n}\n\nexport const getEpubCfiSpineItemref = (\n cfi: string,\n): EpubCfiSpineItemref | undefined => {\n try {\n const spineItemPart = getSpineItemrefPart(parse(cfi))\n\n if (!spineItemPart) return undefined\n\n return {\n spineIndex: epubCfiItemrefStepIndexToSpineIndex(spineItemPart.index),\n spineId: readSpineIdFromPart(spineItemPart),\n extensions: spineItemPart.extensions,\n }\n } catch {\n return undefined\n }\n}\n\nexport const updateEpubCfiSpineItemref = (\n cfi: string,\n update: EpubCfiSpineItemrefUpdate,\n) => {\n try {\n const parsed = parse(cfi)\n const spineItemPart = getSpineItemrefPart(parsed)\n\n if (!spineItemPart) return undefined\n\n if (update.spineIndex !== undefined) {\n spineItemPart.index = spineIndexToEpubCfiItemrefStepIndex(\n update.spineIndex,\n )\n }\n\n if (update.spineId !== undefined) {\n spineItemPart.id = update.spineId\n spineItemPart.text = undefined\n spineItemPart.side = undefined\n }\n\n if (update.extensions !== undefined) {\n spineItemPart.extensions = mergeExtensions(\n spineItemPart.extensions,\n update.extensions,\n )\n }\n\n return serialize(parsed)\n } catch {\n return undefined\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/utils.ts","../src/parse.ts","../src/compare.ts","../src/constants.ts","../src/generate.ts","../src/resolve.ts","../src/serialize.ts","../src/spineItemref.ts"],"sourcesContent":["import type { CfiRange, ParsedCfi } from \"./parse\"\n\n/**\n * Get all ancestors of a node, including the node itself\n */\nexport function getAncestors(node: Node): Node[] {\n const ancestors: Node[] = [node]\n let current: Node | null = node\n\n while (current.parentNode) {\n ancestors.push(current.parentNode)\n current = current.parentNode\n }\n\n return ancestors\n}\n\n/**\n * Find the closest common ancestor of two nodes\n */\nexport function findCommonAncestor(nodeA: Node, nodeB: Node): Node | null {\n if (nodeA === nodeB) return nodeA\n\n const ancestorsA = getAncestors(nodeA)\n const ancestorsSet = new Set(ancestorsA)\n\n // Start with nodeB and traverse up until we find a common ancestor\n let current: Node | null = nodeB\n while (current) {\n if (ancestorsSet.has(current)) {\n return current\n }\n current = current.parentNode\n }\n\n return null\n}\n\n/**\n * Special characters in CFI that need to be escaped according to the spec\n * These are: [ ] ^ , ( ) ;\n */\nexport const CFI_SPECIAL_CHARS = /[[\\]^,();]/g\n\n/**\n * Escape special characters in a CFI string\n * @param str The string to escape\n * @returns The escaped string\n */\nexport function cfiEscape(str: string): string {\n return str.replace(CFI_SPECIAL_CHARS, `^$&`)\n}\n\n/**\n * Regular expression to check if a string is a valid CFI\n */\nexport const isCFI = /^epubcfi\\((.*)\\)$/\n\n/**\n * @important Make it non browser runtime specific\n */\nexport const isElement = (node: Node): node is Element =>\n node.nodeType === Node.ELEMENT_NODE\n\n/**\n * @important Make it non browser runtime specific\n */\n// biome-ignore lint/suspicious/noExplicitAny: TODO\nexport const isNode = (node: any): node is Node =>\n typeof node === \"object\" &&\n node !== null &&\n \"nodeType\" in node &&\n (node.nodeType === Node.ELEMENT_NODE ||\n node.nodeType === Node.TEXT_NODE ||\n node.nodeType === Node.CDATA_SECTION_NODE)\n\n/**\n * Text and CDATA nodes: the character data a CFI step can address\n * (EPUB CFI 3.1.1). Comments and processing instructions are ignored.\n */\nexport const isCharacterData = (node: Node): node is CharacterData =>\n node.nodeType === Node.TEXT_NODE || node.nodeType === Node.CDATA_SECTION_NODE\n\n/**\n * The even step of an element: twice its 1-based position among its parent's\n * element children.\n */\nexport const getElementStep = (node: Node): number => {\n const parent = node.parentNode\n let elements = 0\n\n for (let i = 0; parent && i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child) break\n if (isElement(child)) elements++\n if (child === node) break\n }\n\n return elements * 2\n}\n\n/**\n * The step of the character data chunk a node belongs to, and the number of\n * characters that chunk holds before the node.\n *\n * Character data between two element children forms one chunk with an odd\n * index (1 before the first element, 3 after it, and so on), whatever the\n * number of DOM nodes it spans: adjacent text nodes, CDATA sections and the\n * comments between them all belong to the same chunk, and a character offset\n * counts from its start.\n */\nexport const getCharacterDataStep = (\n node: Node,\n): { step: number; base: number } => {\n const parent = node.parentNode\n let elementsBefore = 0\n let base = 0\n\n for (let i = 0; parent && i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child || child === node) break\n\n if (isElement(child)) {\n elementsBefore++\n base = 0\n } else if (isCharacterData(child)) {\n base += child.data.length\n }\n }\n\n return { step: elementsBefore * 2 + 1, base }\n}\n\n/**\n * What an odd step addresses under `parent`: the first node of its chunk of\n * character data, or, when that chunk is empty, the boundary it stands for in\n * the parent (the child index right after the preceding element, 0 before\n * the first one). `undefined` when the parent has fewer element children\n * than the step needs.\n */\nexport type CharacterDataChunk =\n | { kind: \"node\"; node: CharacterData }\n | { kind: \"boundary\"; parent: Node; childIndex: number }\n\nexport const findCharacterDataChunk = (\n parent: Node,\n step: number,\n): CharacterDataChunk | undefined => {\n const chunk = (step - 1) / 2\n let elementsBefore = 0\n let childIndex = 0\n\n for (let i = 0; i < parent.childNodes.length; i++) {\n const child = parent.childNodes[i]\n\n if (!child) break\n\n if (isElement(child)) {\n elementsBefore++\n if (elementsBefore > chunk) break\n childIndex = i + 1\n } else if (elementsBefore === chunk && isCharacterData(child)) {\n return { kind: \"node\", node: child }\n }\n }\n\n return elementsBefore < chunk\n ? undefined\n : { kind: \"boundary\", parent, childIndex }\n}\n\n/**\n * The node and local offset a chunk offset lands on, counting from `first`,\n * the chunk's first node. A boundary between two nodes lands at the start of\n * the later one; an offset past the chunk keeps its excess on the last node.\n */\nexport const locateInCharacterDataChunk = (\n first: CharacterData,\n offset: number,\n): { node: CharacterData; offset: number } => {\n let node = first\n let base = 0\n let sibling: Node | null = first\n\n while (sibling && !isElement(sibling)) {\n if (isCharacterData(sibling)) {\n node = sibling\n\n if (offset < base + sibling.data.length) {\n return { node, offset: offset - base }\n }\n\n base += sibling.data.length\n }\n\n sibling = sibling.nextSibling\n }\n\n return { node, offset: offset - (base - node.data.length) }\n}\n\n/**\n * Checks if a parsed CFI only contains indirection with no further path\n * For example: epubcfi(/6/2[cover]!)\n */\nexport function isIndirectionOnly(parsed: ParsedCfi): boolean {\n // If it's a range, it can't be just indirection\n if (isParsedCfiRange(parsed)) {\n return false\n }\n\n // For an indirection-only CFI:\n // 1. It must have at least one part\n // 2. It must end with an indirection marker (!)\n // 3. There must be no content after the indirection marker\n\n // Check if there's indirection (marked by presence of multiple parts)\n // AND the last part is empty (nothing after the indirection marker)\n const lastPart = parsed[parsed.length - 1]\n\n return parsed.length > 1 && (lastPart === undefined || lastPart.length === 0)\n}\n\n/**\n * Check if a parsed CFI is a range\n */\nexport function isParsedCfiRange(parsed: ParsedCfi): parsed is CfiRange {\n return (\n parsed !== null &&\n typeof parsed === \"object\" &&\n \"parent\" in parsed &&\n \"start\" in parsed &&\n \"end\" in parsed\n )\n}\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n * Based on the EPUB CFI 1.1 specification: https://idpf.org/epub/linking/cfi/epub-cfi.html\n */\n\nimport { isCFI } from \"./utils\"\n\n/**\n * Interface for a parsed CFI part\n *\n * According to the EPUB CFI 1.1 specification, each step in a CFI path can have\n * its own properties including ID assertions, character offsets, and extension parameters.\n * Extensions are parameters in the form of key-value pairs that can be attached to any\n * step in the CFI path to provide additional information or metadata about that specific step.\n */\nexport interface CfiPart {\n index: number\n id?: string\n offset?: number\n temporal?: number\n spatial?: number[]\n text?: string[]\n side?: string\n /**\n * Extension parameters for this CFI path step\n *\n * The EPUB CFI spec allows for extension parameters to be attached to any step in the path.\n * These are key-value pairs that provide additional information or metadata.\n * For example, in /6/4[chap01ref]!/4[body01]/10[para05]/2/1:3[;vnd.example.param=value],\n * the extension parameter is \"vnd.example.param\" with value \"value\".\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Interface for a parsed CFI range\n */\nexport interface CfiRange {\n parent: CfiPart[][]\n start: CfiPart[][]\n end: CfiPart[][]\n}\n\n/**\n * Interface for a parsed CFI\n */\nexport type ParsedCfi = CfiPart[][] | CfiRange\n\n/**\n * Unwrap a CFI string from the epubcfi() function\n * @param cfi The CFI string to unwrap\n * @returns The unwrapped CFI string\n */\nexport function unwrapCfi(cfi: string): string {\n const match = cfi.match(isCFI)\n return match ? match[1] || cfi : cfi\n}\n\n/**\n * Token type for CFI parsing\n */\ntype CfiToken = [string, string | number]\n\n/**\n * Tokenize a CFI string into an array of tokens\n * @param cfi The CFI string to tokenize\n * @returns An array of tokens\n */\nfunction tokenize(cfi: string): CfiToken[] {\n const tokens: CfiToken[] = []\n let state: string | null = null\n let isEscaped = false\n let value = \"\"\n\n const push = (token: CfiToken) => {\n tokens.push(token)\n state = null\n value = \"\"\n }\n\n const cat = (c: string) => {\n value += c\n isEscaped = false\n }\n\n const unwrappedCfi = unwrapCfi(cfi).trim()\n const chars = Array.from(unwrappedCfi).concat(\"\")\n\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]\n\n if (!char) {\n // End of string, push any pending token\n if (state === \"/\" || state === \":\") {\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n push([\"[\", value])\n } else if (state === \";\" || state?.startsWith(\";\")) {\n push([state, value])\n } else if (state === \"!\") {\n // Make sure to push the '!' token at the end of the string\n push([\"!\", 0])\n }\n break\n }\n\n // Handle escape characters\n if (char === \"^\" && !isEscaped) {\n isEscaped = true\n continue\n }\n\n if (state === \"!\") {\n push([\"!\", 0])\n } else if (state === \",\") {\n push([\",\", 0])\n } else if (state === \"/\" || state === \":\") {\n if (/^\\d$/.test(char)) {\n cat(char)\n continue\n }\n push([state, parseInt(value, 10)])\n } else if (state === \"~\") {\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"~\", parseFloat(value)])\n } else if (state === \"@\") {\n if (char === \":\") {\n push([\"@\", parseFloat(value)])\n state = \"@\"\n continue\n }\n if (/^\\d$/.test(char) || char === \".\") {\n cat(char)\n continue\n }\n push([\"@\", parseFloat(value)])\n } else if (state === \"[\") {\n if (char === \";\" && !isEscaped) {\n push([\"[\", value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([\"[\", value])\n } else {\n cat(char)\n continue\n }\n } else if (state === \";\") {\n // Handle extension parameter key\n if (char === \"=\" && !isEscaped) {\n state = `;${value}`\n value = \"\"\n } else if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state?.startsWith(\";\")) {\n // Handle extension parameter value\n if (char === \";\" && !isEscaped) {\n push([state, value])\n state = \";\"\n } else if (char === \"]\" && !isEscaped) {\n push([state, value])\n } else {\n cat(char)\n continue\n }\n } else if (state === null && char === \";\") {\n // Handle standalone extension parameters (not inside brackets)\n state = \";\"\n }\n\n if (\n char === \"/\" ||\n char === \":\" ||\n char === \"~\" ||\n char === \"@\" ||\n char === \"[\" ||\n char === \"!\" ||\n char === \",\"\n ) {\n state = char\n }\n }\n\n return tokens\n}\n\n/**\n * Find indices of tokens with a specific type\n * @param tokens The tokens to search\n * @param type The type to find\n * @returns An array of indices\n */\nfunction findTokenIndices(\n tokens: CfiToken[] | undefined,\n type: string,\n): number[] {\n if (!tokens) {\n return []\n }\n\n return tokens\n .map((token, i) => (token[0] === type ? i : null))\n .filter((i): i is number => i !== null)\n}\n\n/**\n * Split an array at specific indices\n * @param arr The array to split\n * @param indices The indices to split at\n * @returns An array of arrays\n */\nfunction splitAt<T>(arr: T[], indices: number[]): T[][] {\n const result: T[][] = []\n let start = 0\n\n for (const index of indices) {\n result.push(arr.slice(start, index))\n start = index\n }\n\n result.push(arr.slice(start))\n return result\n}\n\n/**\n * Parse a single part of a CFI\n * @param tokens The tokens to parse\n * @returns An array of CFI parts\n */\nfunction parsePart(tokens: CfiToken[]): CfiPart[] {\n const parts: CfiPart[] = []\n\n // Group tokens by path step\n const pathStepTokens: { [key: number]: CfiToken[] } = {}\n let currentPathStep = -1\n\n // First pass: group tokens by path step\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \"/\") {\n currentPathStep++\n parts[currentPathStep] = { index: val as number }\n pathStepTokens[currentPathStep] = []\n } else if (currentPathStep >= 0) {\n pathStepTokens[currentPathStep]?.push(token)\n }\n }\n\n // Second pass: process tokens for each path step\n for (let stepIndex = 0; stepIndex < parts.length; stepIndex++) {\n const currentPart = parts[stepIndex]\n if (!currentPart) continue\n\n const stepsTokens = pathStepTokens[stepIndex] || []\n\n for (let i = 0; i < stepsTokens.length; i++) {\n const token = stepsTokens[i]\n if (!token) continue\n\n const [type, val] = token\n\n if (type === \":\") {\n currentPart.offset = val as number\n } else if (type === \"~\") {\n currentPart.temporal = val as number\n } else if (type === \"@\") {\n currentPart.spatial = (currentPart.spatial || []).concat(val as number)\n } else if (type === \";s\") {\n currentPart.side = val as string\n } else if (type.startsWith(\";\") && type !== \";s\") {\n // This is an extension parameter\n const paramName = type.substring(1)\n if (!currentPart.extensions) {\n currentPart.extensions = {}\n }\n currentPart.extensions[paramName] = val as string\n } else if (type === \"[\") {\n // Determine if this is an ID or text assertion\n const looksLikeId =\n typeof val === \"string\" && !val.includes(\" \") && val.length < 50\n\n if (i === 0 && looksLikeId && !currentPart.id) {\n currentPart.id = val as string\n } else {\n // Otherwise, it's a text assertion\n if (val !== \"\") {\n // Split on comma and trim each part\n const parts = (val as string).split(\",\").map((part) => part.trim())\n currentPart.text = parts\n }\n }\n }\n }\n }\n\n return parts\n}\n\n/**\n * Parse a CFI with indirections\n * @param tokens The tokens to parse\n * @returns An array of arrays of CFI parts\n */\nfunction parseIndirection(tokens: CfiToken[]): CfiPart[][] {\n const indirectionIndices = findTokenIndices(tokens, \"!\")\n\n return splitAt(tokens, indirectionIndices).map(parsePart)\n}\n\n/**\n * Parse a CFI string into a structured representation\n * @param cfi The CFI string to parse\n * @returns A parsed CFI\n */\nexport function parse(cfi: string): ParsedCfi {\n if (!cfi) {\n throw new Error(\"CFI string cannot be empty\")\n }\n\n const tokens = tokenize(cfi)\n if (!tokens || tokens.length === 0) {\n throw new Error(\"Failed to tokenize CFI string\")\n }\n\n const commaIndices = findTokenIndices(tokens, \",\")\n\n if (commaIndices.length === 0) {\n return parseIndirection(tokens)\n }\n\n const [parentTokens, startTokens, endTokens] = splitAt(tokens, commaIndices)\n\n // Patch: If startTokens or endTokens are just offsets, attach them to the last parent path\n function attachOffsetToParent(\n parent: CfiPart[][],\n offsetTokens: CfiToken[],\n ): CfiPart[][] {\n if (!offsetTokens || offsetTokens.length === 0) return [[]]\n\n // Filter out comma tokens and check if the remaining tokens are just an offset\n const filteredTokens = offsetTokens.filter((token) => token[0] !== \",\")\n\n // Only support a single offset (e.g., [:9] or [:25])\n if (\n filteredTokens.length === 1 &&\n filteredTokens[0] &&\n filteredTokens[0][0] === \":\"\n ) {\n // Clone the last parent path\n const lastParentPath =\n parent.length > 0 ? parent[parent.length - 1] : undefined\n const offsetToken = filteredTokens[0]\n if (lastParentPath && lastParentPath.length > 0 && offsetToken) {\n const pathClone = lastParentPath.map((part) => ({ ...part }))\n const lastPart = pathClone[pathClone.length - 1]\n if (lastPart) {\n lastPart.offset = offsetToken[1] as number\n }\n return [pathClone]\n }\n // fallback: just return an empty path\n return [[]]\n }\n // If not just an offset, parse as normal\n return parseIndirection(offsetTokens)\n }\n\n const parent = parseIndirection(parentTokens || [])\n const start = attachOffsetToParent(parent, startTokens || [])\n const end = attachOffsetToParent(parent, endTokens || [])\n\n return {\n parent,\n start,\n end,\n }\n}\n","import { type CfiPart, type ParsedCfi, parse } from \"./parse\"\n\n/**\n * Collapses a parsed CFI to a single path (private helper for compare)\n * @param parsed The parsed CFI to collapse\n * @param toEnd Whether to collapse to the end of a range\n * @returns A collapsed CFI\n */\nfunction collapse(parsed: ParsedCfi, toEnd = false): CfiPart[][] {\n if (typeof parsed === \"string\") {\n return collapse(parse(parsed), toEnd)\n }\n\n if (\"parent\" in parsed) {\n // It's a range\n if (toEnd) {\n return parsed.parent.concat(parsed.end)\n }\n return parsed.parent.concat(parsed.start)\n }\n\n // It's a single CFI\n return parsed\n}\n\n/**\n * Get the weight of a step type for sorting\n * According to rule 9: character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n */\nfunction getStepTypeWeight(part: CfiPart): number {\n if (part.offset !== undefined) return 1 // character offset (:)\n if (part.index !== undefined) return 2 // child (/)\n if (part.temporal !== undefined || part.spatial !== undefined) return 3 // temporal-spatial (~ or @)\n return 4 // reference (!)\n}\n\n/**\n * Compare two CFIs according to the EPUB CFI specification sorting rules (section 3.2)\n * @param a The first CFI\n * @param b The second CFI\n * @returns -1 if a < b, 0 if a = b, 1 if a > b\n */\nexport function compare(a: ParsedCfi | string, b: ParsedCfi | string): number {\n const aParsed = typeof a === \"string\" ? parse(a) : a\n const bParsed = typeof b === \"string\" ? parse(b) : b\n\n if (\"parent\" in aParsed || \"parent\" in bParsed) {\n // At least one is a range\n return (\n compare(collapse(aParsed), collapse(bParsed)) ||\n compare(collapse(aParsed, true), collapse(bParsed, true))\n )\n }\n\n // Both are single CFIs\n for (let i = 0; i < Math.max(aParsed.length, bParsed.length); i++) {\n const p = aParsed[i] || []\n const q = bParsed[i] || []\n const maxIndex = Math.max(p.length, q.length) - 1\n\n for (let i = 0; i <= maxIndex; i++) {\n const x = p[i]\n const y = q[i]\n\n if (!x) return -1\n if (!y) return 1\n\n // Compare element indices (rule 3 & 4)\n // The step index locates the node in the document, so it always wins.\n if (x.index > y.index) return 1\n if (x.index < y.index) return -1\n\n // Compare step types (rule 9), only as a tie-breaker between steps at\n // the same index (eg: an offset terminating at a node vs a child step\n // descending into it)\n // character offset (:) < child (/) < temporal-spatial (~ or @) < reference (!)\n const xStepType = getStepTypeWeight(x)\n const yStepType = getStepTypeWeight(y)\n\n if (xStepType !== yStepType) {\n return xStepType - yStepType\n }\n\n // Compare temporal positions (rule 7 & 8)\n // Temporal is more important than spatial\n const xTemporal = x.temporal !== undefined\n const yTemporal = y.temporal !== undefined\n\n if (xTemporal && !yTemporal) return 1\n if (!xTemporal && yTemporal) return -1\n\n if (xTemporal && yTemporal) {\n if ((x.temporal ?? 0) > (y.temporal ?? 0)) return 1\n if ((x.temporal ?? 0) < (y.temporal ?? 0)) return -1\n }\n\n // Compare spatial positions (rule 5 & 6)\n const xSpatial = x.spatial !== undefined\n const ySpatial = y.spatial !== undefined\n\n if (xSpatial && !ySpatial) return 1\n if (!xSpatial && ySpatial) return -1\n\n if (xSpatial && ySpatial) {\n // Y position is more important than X (rule 5)\n const xY = x.spatial?.[1] ?? 0\n const yY = y.spatial?.[1] ?? 0\n\n if (xY > yY) return 1\n if (xY < yY) return -1\n\n // Compare X positions if Y positions are equal\n const xX = x.spatial?.[0] ?? 0\n const yX = y.spatial?.[0] ?? 0\n\n if (xX > yX) return 1\n if (xX < yX) return -1\n }\n\n // Last part comparison including character offsets\n if (i === maxIndex) {\n if ((x.offset ?? 0) > (y.offset ?? 0)) return 1\n if ((x.offset ?? 0) < (y.offset ?? 0)) return -1\n }\n }\n }\n\n return 0\n}\n","/**\n * Standard EPUB CFIs that target a content document first address the\n * package document spine element.\n *\n * EPUB package documents order their element children as metadata, manifest, then spine.\n * CFI element steps are even-numbered, so the third element child is step 6.\n */\nexport const EPUB_CFI_PACKAGE_SPINE_STEP_INDEX = 6\n","/**\n * EPUB Canonical Fragment Identifier (CFI) utilities\n */\n\nimport {\n cfiEscape,\n findCommonAncestor,\n getCharacterDataStep,\n getElementStep,\n isCharacterData,\n isElement,\n isNode,\n} from \"./utils\"\n\n/**\n * Options for generating CFIs\n */\nexport interface GenerateOptions {\n /**\n * Whether to include text assertions for more robust CFIs\n */\n includeTextAssertions?: boolean\n\n /**\n * The maximum length of text to use for text assertions\n * Default is 10 characters\n */\n textAssertionLength?: number\n\n /**\n * Whether to include a side bias assertion\n */\n includeSideBias?: \"before\" | \"after\"\n\n /**\n * Whether to include spatial coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatialOffset?: [number, number]\n\n /**\n * Extension parameters to include in the CFI\n * Keys should be parameter names, values should be the parameter values\n * Vendor-specific parameters should be prefixed with 'vnd.' followed by the vendor name\n */\n extensions?: Record<string, string>\n}\n\n/**\n * Position in a document, consisting of a node and optional offset\n */\nexport interface CfiPosition {\n /**\n * The DOM node\n */\n node?: Node | null\n\n /**\n * Character offset within the node (for text nodes)\n */\n offset?: number\n\n /**\n * Temporal position in seconds (for audio/video content)\n */\n temporal?: number\n\n /**\n * Spatial position as [x,y] coordinates (for image or video)\n * Values should be in the range 0-100, where (0,0) is top-left and (100,100) is bottom-right\n */\n spatial?: [number, number]\n\n /**\n * Spine index (0-based) for the document containing the node\n */\n spineIndex?: number\n\n /**\n * ID of the spine item\n */\n spineId?: string\n}\n\n/**\n * Extract a suitable text assertion from a text node\n */\nfunction extractTextAssertion(\n textNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n): string | null {\n if (!textNode.textContent || textNode.textContent.trim() === \"\") {\n return null\n }\n\n const textContent = textNode.textContent\n const maxLength = options.textAssertionLength || 10\n\n // If we have an offset, use the text around that position\n if (offset !== undefined && offset <= textContent.length) {\n // We'll take a portion before and after the offset\n const halfLength = Math.floor(maxLength / 2)\n const start = Math.max(0, offset - halfLength)\n const end = Math.min(textContent.length, offset + halfLength)\n return textContent.substring(start, end)\n }\n\n // Otherwise, just take the first part of the text\n return textContent.substring(0, Math.min(textContent.length, maxLength))\n}\n\n/**\n * Helper function to format spatial coordinates\n */\nfunction formatSpatialOffset(spatial: [number, number]): string {\n const [x, y] = spatial\n // Ensure values are within 0-100 range\n const safeX = Math.max(0, Math.min(100, x))\n const safeY = Math.max(0, Math.min(100, y))\n return `@${safeX}:${safeY}`\n}\n\n/**\n * Helper function to add temporal and spatial offsets to a CFI string\n */\nfunction addOffsets(\n cfi: string,\n temporal?: number,\n spatial?: [number, number],\n): string {\n let result = cfi\n\n if (temporal !== undefined) {\n result += `~${temporal}`\n }\n\n if (spatial !== undefined) {\n result += formatSpatialOffset(spatial)\n }\n\n return result\n}\n\n/**\n * Helper function to add text assertion and side bias to a CFI string\n */\nfunction addTextAssertionAndSideBias(\n cfi: string,\n textAssertion: string | null,\n sideBias?: \"before\" | \"after\",\n): string {\n let result = cfi\n\n if (textAssertion) {\n result += `[${cfiEscape(textAssertion)}]`\n }\n\n if (sideBias) {\n const sideBiasChar = sideBias === \"before\" ? \"b\" : \"a\"\n if (!textAssertion) {\n result += `[;s=${sideBiasChar}]`\n } else {\n result = `${result.substring(0, result.length - 1)};s=${sideBiasChar}]`\n }\n }\n\n return result\n}\n\n/**\n * Generate a CFI for a single node in the DOM\n */\nfunction generatePoint(\n node: Node | null,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n let cfi = \"\"\n let currentNode: Node | null = node\n let textNode: Node | null = null\n let cfiOffset = offset\n\n // Character data is addressed through the chunk of its parent it belongs\n // to, and the offset counts from the start of that chunk (EPUB CFI 3.1.1).\n // Any other non-element node (a comment) is ignored by the spec, so it gets\n // the position of the chunk it sits in.\n if (node && !isElement(node)) {\n const parentNode = node.parentNode\n\n if (!parentNode) {\n throw new Error(\"Text node doesn't have a parent\")\n }\n\n const { step, base } = getCharacterDataStep(node)\n\n cfi = `/${step}`\n textNode = isCharacterData(node) ? node : null\n cfiOffset = offset === undefined ? undefined : base + offset\n currentNode = parentNode\n }\n\n // Set up text assertion if needed\n let textAssertion: string | null = null\n if (textNode && options.includeTextAssertions) {\n textAssertion = extractTextAssertion(textNode, offset, options)\n }\n\n // Build the CFI path from the current node up to the html element\n while (currentNode?.parentNode) {\n const step = getElementStep(currentNode)\n\n // If the node has an ID, add it to the CFI\n if (isElement(currentNode) && currentNode.id) {\n cfi = `/${step}[${cfiEscape(currentNode.id)}]${cfi}`\n } else {\n cfi = `/${step}${cfi}`\n }\n\n // If we've reached the html element, stop traversing up\n if (currentNode.parentNode.nodeName.toLowerCase() === \"html\") {\n break\n }\n\n currentNode = currentNode.parentNode\n }\n\n // Add the character offset if provided\n if (cfiOffset !== undefined) {\n cfi += `:${cfiOffset}`\n }\n\n // Add temporal and spatial offsets using helper\n cfi = addOffsets(\n cfi,\n position?.temporal,\n position?.spatial || options.spatialOffset,\n )\n\n // Add text assertion and side bias using helper\n cfi = addTextAssertionAndSideBias(cfi, textAssertion, options.includeSideBias)\n\n cfi = addExtensions(cfi, options.extensions)\n\n return cfi\n}\n\n/**\n * Generate a relative path from one node to another\n */\nfunction generateRelativePath(\n fromNode: Node,\n toNode: Node,\n offset?: number,\n options: GenerateOptions = {},\n position?: CfiPosition,\n): string {\n // A character offset counts from the start of the chunk the node belongs to\n const base = isElement(toNode) ? 0 : getCharacterDataStep(toNode).base\n\n if (fromNode === toNode) {\n let result = offset !== undefined ? `:${base + offset}` : \"\"\n result = addOffsets(result, position?.temporal, position?.spatial)\n return result\n }\n\n const path: string[] = []\n let currentNode: Node | null = toNode\n\n // Build path from toNode up to fromNode (exclusive)\n while (currentNode && currentNode !== fromNode) {\n const parentNode: Node | null = currentNode.parentNode\n if (!parentNode) break\n\n const step = isElement(currentNode)\n ? getElementStep(currentNode)\n : getCharacterDataStep(currentNode).step\n\n // If the node has an ID, add it\n if (isElement(currentNode) && currentNode.id) {\n path.unshift(`/${step}[${cfiEscape(currentNode.id)}]`)\n } else {\n path.unshift(`/${step}`)\n }\n\n currentNode = parentNode\n }\n\n let relativePath = path.join(\"\")\n\n // Add offset if specified\n if (offset !== undefined) {\n relativePath += `:${base + offset}`\n }\n\n // Add temporal and spatial offsets using helper\n relativePath = addOffsets(relativePath, position?.temporal, position?.spatial)\n\n // Add text assertion and side bias using helper\n if (options.includeTextAssertions && isCharacterData(toNode)) {\n const textAssertion = extractTextAssertion(toNode, offset, options)\n relativePath = addTextAssertionAndSideBias(\n relativePath,\n textAssertion,\n options.includeSideBias,\n )\n }\n\n // Add extension parameters if specified\n relativePath = addExtensions(relativePath, options.extensions)\n\n return relativePath\n}\n\nconst serializeExtensions = (extensions: Record<string, string>) => {\n return Object.entries(extensions)\n .map(([key, value]) => {\n // Properly escape the value according to CFI spec\n const escapedValue = cfiEscape(value)\n // URL encode the value to handle special characters\n return `${key}=${encodeURIComponent(escapedValue)}`\n })\n .join(\";\")\n}\n\n/**\n * Add extension parameters to a CFI path\n */\nfunction addExtensions(\n cfi: string,\n extensions?: Record<string, string>,\n): string {\n if (!extensions || Object.keys(extensions).length === 0) {\n return cfi\n }\n\n const extensionString = serializeExtensions(extensions)\n\n // If we're dealing with a bracket at end\n if (cfi.endsWith(\"]\")) {\n // Check if there are already parameters\n const lastBracketIndex = cfi.lastIndexOf(\"[\")\n const content = cfi.substring(lastBracketIndex + 1, cfi.length - 1)\n\n // If content already has these exact extensions, return as is\n if (content.includes(extensionString)) {\n return cfi\n }\n\n // Add extensions with proper separator\n return `${cfi.substring(0, cfi.length - 1)}${content.includes(\";\") ? \";\" : \";\"}${extensionString}]`\n }\n\n // Special case for spine items with indirection\n if (cfi.endsWith(\"!\")) {\n return cfi\n }\n\n // No bracket at the end - add with new brackets\n return `${cfi}[;${extensionString}]`\n}\n\n/**\n * Generate a range CFI between two points in the document\n */\nfunction generateRange(\n startNode: Node,\n startOffset: number,\n endNode: Node,\n endOffset: number,\n options: GenerateOptions = {},\n startPosition?: CfiPosition,\n endPosition?: CfiPosition,\n): string {\n // Find common ancestor\n const ancestor = findCommonAncestor(startNode, endNode)\n\n if (!ancestor) {\n throw new Error(\"No common ancestor found\")\n }\n\n // Generate CFI from ancestor to document\n const ancestorCfi = generatePoint(ancestor, undefined, options)\n\n // Generate path from ancestor to start node\n const startPath = generateRelativePath(\n ancestor,\n startNode,\n startOffset,\n options,\n startPosition,\n )\n\n // Generate path from ancestor to end node\n const endPath = generateRelativePath(\n ancestor,\n endNode,\n endOffset,\n options,\n endPosition,\n )\n\n // For range CFIs, add extensions to each part separately\n if (options.extensions && Object.keys(options.extensions).length > 0) {\n // Add extensions to each part\n const ancestorWithExt = addExtensions(ancestorCfi, options.extensions)\n const startWithExt = addExtensions(startPath, options.extensions)\n const endWithExt = addExtensions(endPath, options.extensions)\n\n return `${ancestorWithExt},${startWithExt},${endWithExt}`\n }\n\n // Combine into a regular range CFI without extensions\n return `${ancestorCfi},${startPath},${endPath}`\n}\n\nconst generateSpineCfi = (\n spineIndex: number,\n spineId?: string,\n options: GenerateOptions = {},\n isFinal?: boolean,\n) => {\n const bracket = \"\"\n const cfiIndex = (spineIndex + 1) * 2\n let cfi = `/6/${cfiIndex}`\n\n if (spineId) {\n cfi += `[${cfiEscape(spineId)}]`\n }\n\n cfi = isFinal ? addExtensions(cfi, options.extensions) : cfi\n\n return `${cfi}${bracket}!`\n}\n\n/**\n * Generate a CFI from a DOM node or position\n *\n * @example\n * // Generate CFI for a single node\n * const cfi = generate(node);\n *\n * @example\n * // Generate CFI for a text node with offset\n * const cfi = generate({ node: textNode, offset: 5 });\n *\n * @example\n * // Generate CFI for a video with temporal offset\n * const cfi = generate({ node: videoElement, temporal: 45.5 });\n *\n * @example\n * // Generate CFI for an image with spatial coordinates\n * const cfi = generate({ node: imageElement, spatial: [50, 75] });\n *\n * @example\n * // Generate a range CFI\n * const cfi = generate({\n * start: { node: startNode, offset: 0 },\n * end: { node: endNode, offset: 10 }\n * });\n *\n * @example\n * // Generate a CFI with spine itemref reference\n * const cfi = generate({\n * node: chapterNode,\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a CFI for a spine item without a node\n * const cfi = generate({\n * spineIndex: 1,\n * spineId: 'chap01ref'\n * });\n *\n * @example\n * // Generate a robust CFI with text assertions\n * const cfi = generate(node, {\n * includeTextAssertions: true,\n * textAssertionLength: 15\n * });\n */\nexport function generate(\n position: Node | CfiPosition | { start: CfiPosition; end: CfiPosition },\n options: GenerateOptions = {},\n): string {\n // Case 1: Simple Node\n if (isNode(position)) {\n return `epubcfi(${generatePoint(position, undefined, options)})`\n }\n\n let cfi = \"\"\n\n // Non Range case\n if (!(\"start\" in position)) {\n // Add spine itemref reference if specified\n if (position.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n position.spineIndex,\n position.spineId,\n options,\n !position.node,\n )\n\n if (!position.node) {\n return `epubcfi(${cfi})`\n }\n }\n\n // Add the node path\n cfi += generatePoint(\n position.node ?? null,\n position.offset,\n options,\n position,\n )\n\n return `epubcfi(${cfi})`\n }\n\n // Range case\n const { start, end } = position\n\n // Add spine itemref reference if specified (use start position's spine info)\n if (start.spineIndex !== undefined) {\n cfi = generateSpineCfi(\n start.spineIndex,\n start.spineId,\n options,\n !start.node,\n )\n }\n\n if (start.node && end.node) {\n // Add the range path\n cfi += generateRange(\n start.node,\n start.offset ?? 0,\n end.node,\n end.offset ?? 0,\n options,\n start,\n end,\n )\n }\n\n return `epubcfi(${cfi})`\n}\n","import { type CfiPart, type CfiRange, type ParsedCfi, parse } from \"./parse\"\nimport {\n findCharacterDataChunk,\n isCharacterData,\n isIndirectionOnly,\n isNode,\n isParsedCfiRange,\n locateInCharacterDataChunk,\n} from \"./utils\"\n\n/**\n * Options for resolving a CFI\n */\ninterface ResolveOptions {\n /**\n * Whether to throw an error if the CFI cannot be resolved\n * @default false\n */\n throwOnError?: boolean\n\n /**\n * Whether to return a range instead of a single node\n * @default false\n */\n asRange?: boolean\n}\n\n/**\n * Result of resolving a CFI\n */\ninterface ResolveRangeResult extends ResolveResultBase {\n node: Range | null\n isRange: true\n}\n\ninterface ResolveResultBase {\n offset?: number[] | number\n\n /**\n * The temporal offset if applicable\n */\n temporal?: number\n\n /**\n * The spatial offset if applicable\n */\n spatial?: number[]\n\n /**\n * The side bias if applicable\n */\n side?: string\n\n /**\n * Any extension parameters in the CFI\n */\n extensions?: Record<string, string>\n}\n\ninterface ResolveNodeResult extends ResolveResultBase {\n node: Node | null\n isRange: false\n}\n\ntype ResolveResult = ResolveNodeResult | ResolveRangeResult\n\n/**\n * Resolves a CFI string to a DOM node or range\n */\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options: Omit<ResolveOptions, \"asRange\"> & { asRange: true },\n): ResolveRangeResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options?: ResolveOptions,\n): ResolveResult\nexport function resolve(\n cfi: string | ParsedCfi,\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n try {\n const parsedCfi = typeof cfi !== \"string\" ? cfi : parse(cfi)\n\n const result = resolveParsed(parsedCfi, document, options)\n\n return result\n } catch (error) {\n if (options.throwOnError) {\n throw error\n }\n\n return { node: null, isRange: false }\n }\n}\n\n/**\n * Resolves a parsed CFI to a DOM node or range\n */\nfunction resolveParsed(\n parsed: ParsedCfi,\n document: Document,\n options: { asRange?: boolean } = { asRange: false },\n): ResolveResult {\n if (isParsedCfiRange(parsed)) {\n // If it's a range CFI, always return a Range\n return resolveRange(parsed, document)\n }\n\n // Check if this is a CFI with only indirection (e.g., \"epubcfi(/6/2[cover]!)\")\n if (isIndirectionOnly(parsed)) {\n // According to the spec, we cannot resolve beyond the indirection point\n // so we return null for the node\n return createNodeResultObject(null)\n }\n\n const nonIndirectionPart = parsed.at(-1)\n\n // Handle path CFI (indirection)\n if (nonIndirectionPart) {\n return resolvePath(nonIndirectionPart, document, options)\n }\n\n throw new Error(\"Invalid CFI structure\")\n}\n\n/**\n * Resolves a CFI range to a DOM range\n */\nfunction resolveRange(range: CfiRange, document: Document): ResolveResult {\n // Get the parent paths and start/end paths\n const parentPaths = range.parent\n const startPath = range.start[0] || []\n const endPath = range.end[0] || []\n\n // Find the parent node\n let parentNode: Node | null = document.documentElement\n\n if (parentPaths.length > 0) {\n // If there's indirection (multiple parent paths), resolve the indirection first\n if (parentPaths.length > 1) {\n const indirectionPath = parentPaths[0]\n if (indirectionPath) {\n const indirectionResult = resolvePath(indirectionPath, document)\n if (isNode(indirectionResult.node)) {\n parentNode = indirectionResult.node\n }\n }\n if (parentPaths.length > 1 && parentNode) {\n const actualParentPath = parentPaths[1]\n if (actualParentPath) {\n const actualParentResult = resolvePath(actualParentPath, document)\n if (isNode(actualParentResult.node)) {\n parentNode = actualParentResult.node\n }\n }\n }\n } else {\n const parentPath = parentPaths[0]\n if (parentPath) {\n const parentResult = resolvePath(parentPath, document)\n if (isNode(parentResult.node)) {\n parentNode = parentResult.node\n }\n }\n }\n }\n\n if (!parentNode) {\n throw new Error(\"Failed to resolve parent node in CFI range\")\n }\n\n // If parentNode is a text node, use it directly for start/end\n const isParentTextNode = isCharacterData(parentNode)\n\n let startNode: Node\n let endNode: Node\n let startOffset = 0\n let endOffset = 0\n\n if (isParentTextNode) {\n startNode = parentNode\n endNode = parentNode\n // Use offsets from the last part of startPath/endPath\n const lastStartPart = startPath[startPath.length - 1]\n const lastEndPart = endPath[endPath.length - 1]\n startOffset =\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ?? 0\n endOffset =\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ?? 0\n } else {\n // Offset-only local paths (`,:5`) are attached to the parent path during\n // parsing, so a non-empty path here always carries real steps that must be\n // traversed — a single step with an offset (`/1:2`) is a path to a child\n // node, not an offset on the parent.\n const isStartOffsetOnly = startPath.length === 0\n const isEndOffsetOnly = endPath.length === 0\n\n if (isStartOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (start)\")\n startNode = parentNode\n startOffset = startPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, startPath, 0, true)\n if (!traversed)\n throw new Error(\"Failed to resolve start node in CFI range\")\n startNode = traversed.node\n const lastStartPart = startPath[startPath.length - 1]\n startOffset =\n traversed.childIndex ??\n (Array.isArray(lastStartPart?.offset)\n ? lastStartPart.offset[0]\n : lastStartPart?.offset) ??\n 0\n }\n\n if (isEndOffsetOnly) {\n if (!parentNode)\n throw new Error(\"Failed to resolve parent node in CFI range (end)\")\n endNode = parentNode\n endOffset = endPath[0]?.offset ?? 0\n } else {\n const traversed = traverseNodePath(parentNode, endPath, 0, true)\n if (!traversed) throw new Error(\"Failed to resolve end node in CFI range\")\n endNode = traversed.node\n const lastEndPart = endPath[endPath.length - 1]\n endOffset =\n traversed.childIndex ??\n (Array.isArray(lastEndPart?.offset)\n ? lastEndPart.offset[0]\n : lastEndPart?.offset) ??\n 0\n }\n }\n\n // A chunk offset can fall in a node after the chunk's first one\n const start = locate(startNode, startOffset)\n const end = locate(endNode, endOffset)\n\n // Create and return a DOM range\n const domRange = document.createRange()\n domRange.setStart(start.node, start.offset ?? 0)\n domRange.setEnd(end.node, end.offset ?? 0)\n\n return {\n ...createBaseResultObject(startPath[startPath.length - 1]),\n node: domRange,\n isRange: true,\n offset: start.offset,\n }\n}\n\n/**\n * Extracts side bias from a CFI part\n */\nfunction extractSideBias(part: CfiPart | undefined): string | undefined {\n // Return the side if it exists\n if (part?.side) return part.side\n\n // Look for side bias in text assertions\n if (part?.text && part.text.length > 0) {\n const text = part.text[0]\n // The CFI spec says side bias can be a=after or b=before\n if (text) {\n const sideBiasMatch = text.match(/^([ab])$/)\n if (sideBiasMatch) {\n return sideBiasMatch[1]\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Whether a step addresses a chunk of character data: per the CFI spec,\n * element steps are even and character data steps odd.\n */\nfunction isCharacterDataStep(part: CfiPart): boolean {\n return part.index % 2 !== 0\n}\n\n/**\n * Returns the extensions from the last valid part of a parsed CFI\n */\nexport function resolveExtensions(parsedCfi: ParsedCfi) {\n const parts = isParsedCfiRange(parsedCfi) ? parsedCfi.end : parsedCfi\n const lastPart = parts.at(-1)\n const lastPartPath = lastPart?.at(-1)\n\n return lastPartPath?.extensions\n}\n\nfunction createBaseResultObject(part?: CfiPart) {\n const sideBias = extractSideBias(part)\n const extensions = part?.extensions\n\n return {\n offset: part?.offset,\n temporal: part?.temporal,\n spatial: part?.spatial,\n side: sideBias,\n extensions,\n }\n}\n\nfunction createNodeResultObject(\n node: Node | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: false,\n ...createBaseResultObject(part),\n }\n}\n\nfunction createRangeResultObject(\n node: Range | null,\n part?: CfiPart,\n): ResolveResult {\n return {\n node,\n isRange: true,\n ...createBaseResultObject(part),\n }\n}\n\n/**\n * Creates a DOM range for a given node with optional offset\n */\nfunction createRangeForNode(\n document: Document,\n node: Node,\n offset?: number | number[],\n): Range {\n const range = document.createRange()\n range.selectNodeContents(node)\n\n if (offset !== undefined) {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n if (isCharacterData(node)) {\n range.setStart(node, offsetValue || 0)\n }\n }\n\n return range\n}\n\n/**\n * The node and offset a character offset lands on. An offset on a chunk step\n * counts across the whole chunk, so it can fall in a node after the first one.\n */\nfunction locate(\n node: Node,\n offset: number | number[] | undefined,\n): { node: Node; offset: number | undefined } {\n const offsetValue = Array.isArray(offset) ? offset[0] : offset\n\n if (offsetValue === undefined || !isCharacterData(node)) {\n return { node, offset: offsetValue }\n }\n\n return locateInCharacterDataChunk(node, offsetValue)\n}\n\n/**\n * Where a path lands: a node, or, for a step into an empty chunk of character\n * data, the boundary that chunk stands for in its parent (a child index).\n */\ntype PathTarget = { node: Node; childIndex?: number }\n\n/**\n * Traverses the DOM tree based on CFI path parts\n */\nfunction traverseNodePath(\n currentNode: Node | null,\n path: CfiPart[],\n startIndex: number,\n throwOnError: boolean,\n): PathTarget | null {\n let _currentNode = currentNode\n\n for (let i = startIndex; i < path.length; i++) {\n const part = path[i]\n if (!_currentNode || !part) break\n\n if (isCharacterDataStep(part)) {\n const chunk = findCharacterDataChunk(_currentNode, part.index)\n\n if (!chunk) {\n if (throwOnError) {\n throw new Error(`No character data chunk: ${part.index}`)\n }\n _currentNode = null\n break\n }\n\n if (chunk.kind === \"boundary\") {\n // Nothing lies below an empty chunk\n if (i < path.length - 1) {\n if (throwOnError) {\n throw new Error(`Empty character data chunk: ${part.index}`)\n }\n _currentNode = null\n break\n }\n\n return { node: chunk.parent, childIndex: chunk.childIndex }\n }\n\n _currentNode = chunk.node\n } else {\n const childElements: Node[] = Array.from(_currentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n const index = Math.floor(part.index / 2) - 1\n\n if (index >= 0 && index < childElements.length) {\n const nextNode = childElements[index]\n if (nextNode) {\n _currentNode = nextNode\n }\n } else {\n if (throwOnError) {\n throw new Error(`Invalid element step index: ${part.index}`)\n }\n _currentNode = null\n break\n }\n }\n }\n\n return _currentNode ? { node: _currentNode } : null\n}\n\n/**\n * Resolves a CFI path to a DOM node\n */\nfunction resolvePath(\n path: CfiPart[],\n document: Document,\n options: ResolveOptions = {},\n): ResolveResult {\n const { throwOnError = false, asRange = false } = options\n\n if (!document) {\n if (throwOnError) {\n throw new Error(\"Document is not available\")\n }\n return createNodeResultObject(null)\n }\n\n // Look for an element with an ID first\n const { node: nodeById, remainingPathIndex } = findNodeById(document, path)\n\n // If there's no remaining path to process after the ID node, return the ID node\n if (nodeById && remainingPathIndex >= path.length) {\n const lastPart = path.at(-1)\n\n if (asRange) {\n const range = createRangeForNode(document, nodeById, lastPart?.offset)\n return createRangeResultObject(range, lastPart)\n }\n\n return createNodeResultObject(nodeById, lastPart)\n }\n\n // Start traversal from the ID node if found, otherwise start from the document root\n const currentNode: Node | null = nodeById || document.documentElement\n const startIndex = nodeById ? remainingPathIndex : 0\n\n // Handle virtual positions\n if (asRange && path.length > 0) {\n const lastPart = path[path.length - 1]\n if (lastPart && !isCharacterDataStep(lastPart)) {\n // Handle position before first element (index 0)\n if (lastPart.index === 0 && currentNode) {\n const range = document.createRange()\n range.setStart(currentNode, 0)\n range.setEnd(currentNode, 0)\n return createRangeResultObject(range, lastPart)\n }\n\n // Handle position after last element\n const parentNode = traverseNodePath(\n currentNode,\n path.slice(0, -1),\n startIndex,\n throwOnError,\n )?.node\n if (parentNode) {\n const childElements: Node[] = Array.from(parentNode.childNodes).filter(\n (node) => node.nodeType === Node.ELEMENT_NODE,\n )\n const index = Math.floor(lastPart.index / 2) - 1\n\n // If the index is equal to the number of child elements, it's a position after the last element\n if (index === childElements.length) {\n const range = document.createRange()\n range.selectNodeContents(parentNode)\n range.collapse(false) // Collapse to end\n return createRangeResultObject(range, lastPart)\n }\n }\n }\n }\n\n const target = traverseNodePath(currentNode, path, startIndex, throwOnError)\n\n if (!target) {\n if (throwOnError) {\n throw new Error(\"Failed to resolve CFI path\")\n }\n return createNodeResultObject(null)\n }\n\n const lastPart = path.at(-1)\n const located =\n target.childIndex === undefined\n ? locate(target.node, lastPart?.offset)\n : { node: target.node, offset: target.childIndex }\n\n if (asRange) {\n const range =\n target.childIndex === undefined\n ? createRangeForNode(document, located.node, located.offset)\n : createCollapsedRange(document, target.node, target.childIndex)\n return {\n ...createRangeResultObject(range, lastPart),\n offset: located.offset,\n }\n }\n\n return {\n ...createNodeResultObject(located.node, lastPart),\n offset: located.offset,\n }\n}\n\n/**\n * A range collapsed on a boundary of a node's children\n */\nfunction createCollapsedRange(\n document: Document,\n node: Node,\n childIndex: number,\n): Range {\n const range = document.createRange()\n range.setStart(node, childIndex)\n range.setEnd(node, childIndex)\n return range\n}\n\n/**\n * Find a node by ID from a CFI path.\n * Starting from the last part and working backwards.\n * Returns the node and the remaining path that needs to be processed.\n */\nfunction findNodeById(\n document: Document,\n parts: CfiPart[],\n): { node: Node | null; remainingPathIndex: number } {\n for (let i = parts.length - 1; i >= 0; i--) {\n const part = parts[i]\n if (part?.id) {\n const node = document.getElementById(part.id)\n if (node) return { node, remainingPathIndex: i + 1 }\n }\n }\n\n return { node: null, remainingPathIndex: 0 }\n}\n","import type { CfiPart, ParsedCfi } from \"./parse\"\nimport { cfiEscape } from \"./utils\"\n\n/**\n * Serialize a single CFI part into a string\n * @param part The CFI part to serialize\n * @returns The serialized string representation\n */\nfunction serializePart(part: CfiPart): string {\n let result = `/${part.index}`\n\n // Handle ID assertion first if present\n if (part.id) {\n result += `[${cfiEscape(part.id)}`\n // Add extensions inside ID brackets if present\n if (part.extensions) {\n for (const [key, value] of Object.entries(part.extensions)) {\n result += `;${key}=${cfiEscape(value)}`\n }\n }\n result += `]`\n }\n\n // Handle character offset\n if (part.offset !== undefined) {\n result += `:${part.offset}`\n }\n\n // Handle temporal offset\n if (part.temporal !== undefined) {\n result += `~${part.temporal}`\n }\n\n // Handle spatial offset\n if (part.spatial && part.spatial.length > 0) {\n result += `@${part.spatial.join(\":\")}`\n }\n\n // Handle text assertions and side bias in brackets\n const inBrackets: string[] = []\n\n // Handle text assertions\n if (part.text && part.text.length > 0) {\n inBrackets.push(part.text.map(cfiEscape).join(\",\"))\n }\n\n // Handle side bias and extensions in brackets\n if (part.side || (part.extensions && !part.id)) {\n if (part.side) {\n inBrackets.push(`;s=${part.side}`)\n }\n if (part.extensions && !part.id) {\n for (const [key, value] of Object.entries(part.extensions)) {\n inBrackets.push(`;${key}=${cfiEscape(value)}`)\n }\n }\n }\n\n // Add bracketed attributes if any\n if (inBrackets.length > 0) {\n result += `[${inBrackets.join(\"\")}]`\n }\n\n return result\n}\n\n/**\n * Serialize a single CFI path into a string\n * @param path The CFI path to serialize\n * @returns The serialized string representation\n */\nfunction serializePath(path: CfiPart[]): string {\n return path.map((part) => serializePart(part)).join(\"\")\n}\n\n/**\n * Serialize a parsed CFI into a string\n * @param parsed The parsed CFI to serialize\n * @returns The serialized CFI string\n */\nexport function serialize(parsed: ParsedCfi): string {\n if (Array.isArray(parsed)) {\n // Handle simple CFI or CFI with indirections\n return `epubcfi(${parsed.map(serializePath).join(\"!\")})`\n }\n\n // Handle CFI range\n const parent = parsed.parent.map(serializePath).join(\"!\")\n const start = parsed.start.map(serializePath).join(\"!\")\n const end = parsed.end.map(serializePath).join(\"!\")\n return `epubcfi(${parent},${start},${end})`\n}\n","import { EPUB_CFI_PACKAGE_SPINE_STEP_INDEX } from \"./constants\"\nimport type { CfiPart, ParsedCfi } from \"./parse\"\nimport { parse } from \"./parse\"\nimport { serialize } from \"./serialize\"\nimport { isParsedCfiRange } from \"./utils\"\n\nexport type EpubCfiSpineItemref = {\n spineIndex: number\n spineId?: string\n extensions?: Record<string, string>\n}\n\nexport type EpubCfiSpineItemrefUpdate = {\n spineIndex?: number\n spineId?: string\n extensions?: Record<string, string | undefined>\n}\n\nexport const spineIndexToEpubCfiItemrefStepIndex = (spineIndex: number) =>\n (spineIndex + 1) * 2\n\nexport const epubCfiItemrefStepIndexToSpineIndex = (stepIndex: number) =>\n stepIndex / 2 - 1\n\nconst getFirstPackageDocumentPath = (parsed: ParsedCfi) => {\n return isParsedCfiRange(parsed) ? parsed.parent[0] : parsed[0]\n}\n\nconst getSpineItemrefPart = (parsed: ParsedCfi): CfiPart | undefined => {\n const path = getFirstPackageDocumentPath(parsed)\n\n if (path?.[0]?.index !== EPUB_CFI_PACKAGE_SPINE_STEP_INDEX) return undefined\n\n return path[1]\n}\n\nconst mergeExtensions = (\n currentExtensions: Record<string, string> | undefined,\n extensionUpdates: Record<string, string | undefined>,\n) => {\n const nextExtensions = { ...currentExtensions }\n\n for (const [name, value] of Object.entries(extensionUpdates)) {\n if (value === undefined) {\n delete nextExtensions[name]\n } else {\n nextExtensions[name] = value\n }\n }\n\n return Object.keys(nextExtensions).length > 0 ? nextExtensions : undefined\n}\n\n/**\n * Per the EPUB CFI 1.1 spec, the bracket assertion attached to the spine\n * itemref step is unambiguously an `id_assertion` (the IDREF of the\n * `<itemref>` element) — `text_assertion`s only appear at leaf/character\n * steps. The shared CFI parser however uses heuristics (no spaces, short\n * length) to disambiguate id vs text assertions, so a spineId that\n * contains spaces or is long can end up classified as `text`. At this\n * position-known step we recover the intended id from the text fallback.\n */\nconst readSpineIdFromPart = (part: CfiPart): string | undefined => {\n if (part.id !== undefined) return part.id\n\n if (!part.text || part.text.length === 0) return undefined\n\n return part.text.join(\",\")\n}\n\nexport const getEpubCfiSpineItemref = (\n cfi: string,\n): EpubCfiSpineItemref | undefined => {\n try {\n const spineItemPart = getSpineItemrefPart(parse(cfi))\n\n if (!spineItemPart) return undefined\n\n return {\n spineIndex: epubCfiItemrefStepIndexToSpineIndex(spineItemPart.index),\n spineId: readSpineIdFromPart(spineItemPart),\n extensions: spineItemPart.extensions,\n }\n } catch {\n return undefined\n }\n}\n\nexport const updateEpubCfiSpineItemref = (\n cfi: string,\n update: EpubCfiSpineItemrefUpdate,\n) => {\n try {\n const parsed = parse(cfi)\n const spineItemPart = getSpineItemrefPart(parsed)\n\n if (!spineItemPart) return undefined\n\n if (update.spineIndex !== undefined) {\n spineItemPart.index = spineIndexToEpubCfiItemrefStepIndex(\n update.spineIndex,\n )\n }\n\n if (update.spineId !== undefined) {\n spineItemPart.id = update.spineId\n spineItemPart.text = undefined\n spineItemPart.side = undefined\n }\n\n if (update.extensions !== undefined) {\n spineItemPart.extensions = mergeExtensions(\n spineItemPart.extensions,\n update.extensions,\n )\n }\n\n return serialize(parsed)\n } catch {\n return undefined\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prose-reader/cfi",
3
- "version": "1.371.0",
3
+ "version": "1.373.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "/dist"
@@ -29,5 +29,5 @@
29
29
  "typescript": "*",
30
30
  "vite": "^8"
31
31
  },
32
- "gitHead": "72600d81c2b4fd1ce8d65131add14b0ba906f0ec"
32
+ "gitHead": "a58b61c522a90addce2baaab1b268d2e9b8fed73"
33
33
  }