jcode/skills/html-file-formats/SKILL.md

4.6 KiB

name description
html-file-formats Load, inspect and save files inside a single-file HTML app (browser, no server): images (png/jpg/webp), svg, stl, json, yaml, csv, xls/xlsx, docx, pdf and zip - which pinned CDN library to use, how to read the file the user picks, extract text/tables/pixels/geometry from it, and generate a download of your own data in that format. Use whenever a web app must open or export one of these formats; for .doc or other formats without a browser parser it says what to do instead.

File formats in a browser app

Everything runs in the page: the user picks a file (<input type=file>), you parse it in JS, and you save by generating bytes and triggering a download. Nothing is written to disk by the app itself - "save" always means download. Verified library versions and calls: formats.md. Copy the import map / script tags from there; pin versions, never @latest.

Format Load (file -> data) Save (data -> file) Library Notes
png / jpg / webp Image + <canvas> -> pixels, size canvas.toBlob(cb, 'image/png') none jpg has no alpha; toBlob('image/jpeg', 0.9)
svg DOMParser -> DOM (paths, sizes); rasterize via Image serialize DOM / build string none; three.js SVGLoader for extrusion keep viewBox; CSS inside <style>
stl STLLoader.parse(ArrayBuffer) -> geometry STLExporter three.js addons details: threejs-csg skill, loaders.md
json JSON.parse(text) JSON.stringify(data, null, 2) none full recipe: save-load-json skill
yaml yaml.load(text) yaml.dump(obj) js-yaml 5.5.0 (ESM) load is safe (no code execution)
csv / tsv Papa.parse(text, {header, dynamicTyping}) Papa.unparse(rows) papaparse 5.7.0 delimiter auto-detected; encoding for Latin-1
xlsx / xls XLSX.read(ArrayBuffer) -> sheets -> json/csv XLSX.write(wb, {bookType}) xlsx 0.18.5 (SheetJS) reads legacy .xls too; formulas come as values + f
docx mammoth.convertToHtml({arrayBuffer}) / extractRawText docx 9.13.0 Packer.toBlob mammoth 1.13.0, docx 9.13.0 mammoth: text, headings, tables, images; no layout
doc (legacy) no browser parser - - convert first: soffice --headless --convert-to docx x.doc (run_command), or ask for .docx
pdf pdf.js getTextContent() per page, render page to canvas pdf-lib (precise, merge/modify) or jsPDF (quick text/images) pdfjs-dist 6.4.299, pdf-lib 1.17.1, jspdf 4.2.1 scanned PDFs have no text layer: render + OCR (tesseract.js)
zip JSZip.loadAsync(ArrayBuffer) zip.generateAsync({type:'blob'}) jszip 3.10.2 bundle a project: json + assets

Workflow

  1. Pick the row, copy its library line from formats.md into the page (import map entry or <script> tag) and the load/save snippet.
  2. Read the file as the right type: text for json/yaml/csv/svg, ArrayBuffer for everything binary (images, stl, xlsx, docx, pdf, zip). Helpers pickFile/download are in the single-file-webapp skill.
  3. Parse into plain data first (rows, records, geometry, text per page), THEN build app objects from it through the app's normal constructor. Never keep the library's document object as app state.
  4. Save = build from app state -> library -> Blob -> download(name, blob). The button handler calls one exportX() function that RETURNS the Blob, so the browser-test skill can test it (h.download('#btn')).
  5. Test with real files: put small fixtures in fixtures/ (a 2-row csv, a 3-cell xlsx, a one-page pdf), drive the real buttons with h.upload, assert on the parsed data; then round-trip your own export through your own importer.

Pitfalls

  • FileReader.readAsText guesses UTF-8; pass 'windows-1252' for old CSV/TXT exports from Excel, or detect the BOM.
  • pdf.js transfers the ArrayBuffer you give it (it becomes empty). Pass new Uint8Array(bytes) or bytes.slice() if you need the bytes afterwards (e.g. to merge with pdf-lib).
  • pdf.js needs its worker: set GlobalWorkerOptions.workerSrc to the CDN worker URL of the SAME version, or every call fails with a worker error.
  • SheetJS dates: cellDates: true on read, else dates arrive as serial numbers.
  • Big files: parse in a Worker or at least await in chunks (Papa.parse has step/chunk callbacks); the UI freezes above a few MB otherwise.
  • canvas.toBlob on an image from another origin throws (tainted canvas): images must come from the user's file or the same origin.
  • Binary formats never go through readAsText; a docx/xlsx/pdf opened as text is garbage and the library will say "corrupt file".