Something went wrong. Try again.
Something went wrong. Try again.
2.8 kB · 79 lines
JavaScript
at main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980// Ported from FilesPage.html lines 3478 (formatBytes), 3811-3857, 3979.
/** Escape a string for safe insertion into XML attribute values / text content. (3811) */export function xmlEscape(str) { return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');}
/** (3815) */export function escapeRegex(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
/** * Decode a URI-encoded href (e.g., "my%20image.jpg" -> "my image.jpg"). * Handles double-encoding gracefully. (3821) */export function decodeHref(href) { try { return decodeURIComponent(href); } catch (e) { return href; }}
/** * Safely read a text file from the zip, handling BOM and encoding. * Strips UTF-8 BOM. Detects encoding from XML declaration or meta tag. (3830) * `fileObj` is a JSZip file object. */export async function safeReadText(fileObj) { const raw = await fileObj.async('uint8array');
// Detect and strip UTF-8 BOM (EF BB BF) let offset = 0; if (raw.length >= 3 && raw[0] === 0xEF && raw[1] === 0xBB && raw[2] === 0xBF) { offset = 3; }
// Try UTF-8 first (vast majority of EPUBs) const utf8 = new TextDecoder('utf-8', { fatal: true }); try { return utf8.decode(raw.subarray(offset)); } catch (e) { /* not valid UTF-8 */ }
// Peek at XML declaration or meta charset for encoding hint const ascii = new TextDecoder('ascii', { fatal: false }).decode(raw.subarray(offset, offset + 512)); const encodingMatch = ascii.match(/encoding=["']([^"']+)["']/i) || ascii.match(/charset=["']?([^"'\s;]+)/i); const encoding = encodingMatch ? encodingMatch[1].toLowerCase() : 'windows-1252';
try { return new TextDecoder(encoding, { fatal: false }).decode(raw.subarray(offset)); } catch (e) { // Last resort: lossy latin1 return new TextDecoder('iso-8859-1', { fatal: false }).decode(raw.subarray(offset)); }}
/** * Resolve a relative href against a base file path. * Handles multiple ../, ./, absolute /, and bare relative paths. (3979) */export function resolvePath(basePath, href) { if (href.startsWith('/')) return href.substring(1); href = href.replace(/^\.\//, ''); const baseDir = basePath.includes('/') ? basePath.substring(0, basePath.lastIndexOf('/')) : ''; const baseParts = baseDir ? baseDir.split('/') : []; const hrefParts = href.split('/'); while (hrefParts.length > 0 && hrefParts[0] === '..') { hrefParts.shift(); if (baseParts.length > 0) baseParts.pop(); } const resolved = [...baseParts, ...hrefParts].join('/'); return resolved.replace(/\/+/g, '/');}
/** (3478) */export function formatBytes(b) { if (!b) return '0 B'; const k = 1024; const s = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(b) / Math.log(k)); return (b / Math.pow(k, i)).toFixed(1) + ' ' + s[i];}