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

67 lines
4.6 KiB
Markdown

---
name: html-file-formats
description: >
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](./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".