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

8.8 KiB
Raw Blame History

Load / save snippets per format (browser)

All library calls below were run against the pinned versions. pickFile(accept, asText, cb) and download(name, data, type) are the helpers from the single-file-webapp skill (patterns.md); download accepts a string or a Blob.

Libraries - copy what you need

ESM (import map):

<script type="importmap">{ "imports": {
  "js-yaml":  "https://cdn.jsdelivr.net/npm/js-yaml@5.5.0/dist/js-yaml.mjs",
  "pdfjs":    "https://cdn.jsdelivr.net/npm/pdfjs-dist@6.4.299/build/pdf.min.mjs",
  "docx":     "https://cdn.jsdelivr.net/npm/docx@9.13.0/dist/index.mjs"
}}</script>

Classic scripts (global objects Papa, XLSX, mammoth, PDFLib, jspdf, JSZip) - put them BEFORE your module script:

<script src="https://cdn.jsdelivr.net/npm/papaparse@5.7.0/papaparse.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mammoth@1.13.0/mammoth.browser.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf@4.2.1/dist/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.2/dist/jszip.min.js"></script>

Images (png / jpg / webp) - no library

// load: file -> <img> -> pixels
pickFile('image/*', false, (buf, name) => {
  const img = new Image();
  img.onload = () => {
    const c = Object.assign(document.createElement('canvas'), { width: img.width, height: img.height });
    const ctx = c.getContext('2d'); ctx.drawImage(img, 0, 0);
    const { data } = ctx.getImageData(0, 0, c.width, c.height);   // RGBA bytes, row-major
    console.log(name, img.width, 'x', img.height, 'first pixel', [...data.slice(0, 4)]);
    URL.revokeObjectURL(img.src);
  };
  img.src = URL.createObjectURL(new Blob([buf]));
});
// save: canvas -> file
canvas.toBlob(blob => download('out.png', blob), 'image/png');
canvas.toBlob(blob => download('out.jpg', blob), 'image/jpeg', 0.9);   // quality 0..1, no alpha

SVG - no library (three.js for extrusion)

pickFile('.svg', true, text => {
  const svg = new DOMParser().parseFromString(text, 'image/svg+xml').documentElement;
  if (svg.querySelector('parsererror')) throw new Error('invalid SVG');
  const vb = svg.getAttribute('viewBox')?.split(/\s+|,/).map(Number);        // [x, y, w, h]
  const paths = [...svg.querySelectorAll('path')].map(p => p.getAttribute('d'));
  // rasterize: draw into a canvas at any size
  const img = new Image(); img.onload = () => ctx.drawImage(img, 0, 0, 512, 512);
  img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(text);
});
// save: build the markup (keep viewBox so it scales) - or serialize a DOM you built
const svgText = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="tomato"/></svg>`;
download('shape.svg', svgText, 'image/svg+xml');
// 3D: SVGLoader + ExtrudeGeometry -> threejs-csg skill, loaders.md

STL -> threejs-csg skill, loaders.md (STLLoader.parse(arrayBuffer), STLExporter)

JSON -> save-load-json skill

YAML (js-yaml 5.5.0, ESM)

import { load as yamlLoad, dump as yamlDump } from 'js-yaml';
pickFile('.yaml,.yml', true, text => { const cfg = yamlLoad(text); /* plain objects/arrays */ });
download('config.yaml', yamlDump(cfg, { noRefs: true, lineWidth: 100 }), 'text/yaml');

load never executes code (safe for untrusted files). Multi-document files: loadAll(text).

CSV / TSV (papaparse 5.7.0)

pickFile('.csv,.tsv,.txt', true, text => {
  const { data, errors, meta } = Papa.parse(text, { header: true, dynamicTyping: true, skipEmptyLines: true });
  // data: [{col: value, ...}], meta.delimiter: ',' ';' or '\t' (auto), errors: [{row, message}]
});
download('table.csv', Papa.unparse(rows), 'text/csv');            // rows: array of objects or arrays
// Excel-friendly: '' + Papa.unparse(rows, { delimiter: ';' })   (BOM + semicolon for German Excel)

Latin-1 files: new TextDecoder('windows-1252').decode(arrayBuffer) before parsing.

XLSX / XLS (SheetJS xlsx 0.18.5)

pickFile('.xlsx,.xls,.ods', false, buf => {
  const wb = XLSX.read(buf, { type: 'array', cellDates: true });
  for (const name of wb.SheetNames) {
    const ws = wb.Sheets[name];
    const rows = XLSX.utils.sheet_to_json(ws);                // [{Header: value}], first row = headers
    const grid = XLSX.utils.sheet_to_json(ws, { header: 1 }); // [[cell, cell, ...]] raw grid
    const csv  = XLSX.utils.sheet_to_csv(ws);
    const cell = ws['B2'];                                      // {v: value, t: 'n'|'s'|'d'|'b', f: 'A2*2' (formula), w: formatted text}
  }
});
// save
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(rows), 'Data');   // or aoa_to_sheet([[...], [...]])
const bytes = XLSX.write(wb, { type: 'array', bookType: 'xlsx' });          // 'xls' for legacy Excel, 'csv', 'ods'
download('data.xlsx', new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }));

Formulas on write: ws['C1'] = { t: 'n', f: 'A1+B1' }. Column widths: ws['!cols'] = [{ wch: 20 }].

DOCX - read with mammoth 1.13.0, write with docx 9.13.0

// read: text and simple HTML (headings, lists, tables, bold/italic, images as data URIs)
pickFile('.docx', false, async buf => {
  const { value: html, messages } = await mammoth.convertToHtml({ arrayBuffer: buf });
  const { value: text } = await mammoth.extractRawText({ arrayBuffer: buf });
  // tables: parse the html with DOMParser and read <table><tr><td>
});
// write
import { Document, Packer, Paragraph, TextRun, HeadingLevel, Table, TableRow, TableCell, ImageRun } from 'docx';
const doc = new Document({ sections: [{ children: [
  new Paragraph({ text: 'Report', heading: HeadingLevel.HEADING_1 }),
  new Paragraph({ children: [new TextRun('Normal '), new TextRun({ text: 'bold', bold: true })] }),
  new Table({ rows: [new TableRow({ children: [new TableCell({ children: [new Paragraph('cell')] })] })] }),
  // new Paragraph({ children: [new ImageRun({ type: 'png', data: pngArrayBuffer, transformation: { width: 300, height: 200 } })] }),
]}]});
download('report.docx', await Packer.toBlob(doc));

.doc (Word 97-2003): no browser parser. Convert with LibreOffice via run_command (soffice --headless --convert-to docx file.doc) or ask for a .docx export.

PDF - read with pdf.js 6.4.299, write with pdf-lib 1.17.1 or jsPDF 4.2.1

import * as pdfjs from 'pdfjs';
pdfjs.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@6.4.299/build/pdf.worker.min.mjs';
pickFile('.pdf', false, async buf => {
  const pdf = await pdfjs.getDocument({ data: new Uint8Array(buf) }).promise;   // pass a COPY: pdf.js empties the buffer it gets
  for (let i = 1; i <= pdf.numPages; i++) {
    const page = await pdf.getPage(i);
    const text = (await page.getTextContent()).items.map(it => it.str).join(' ');   // '' for scanned pages
    const vp = page.getViewport({ scale: 1.5 });                                   // render to a canvas
    const c = Object.assign(document.createElement('canvas'), { width: vp.width, height: vp.height });
    await page.render({ canvasContext: c.getContext('2d'), viewport: vp }).promise;
  }
});
// write (pdf-lib): exact placement, merge, fill forms, modify existing files
const { PDFDocument, StandardFonts, rgb } = PDFLib;
const out = await PDFDocument.create();
const page = out.addPage([595, 842]);                                             // A4 points
const font = await out.embedFont(StandardFonts.Helvetica);
page.drawText('Invoice 4711', { x: 50, y: 790, size: 14, font, color: rgb(0, 0, 0) });
const png = await out.embedPng(pngBytes); page.drawImage(png, { x: 50, y: 500, width: 200, height: 150 });
download('out.pdf', new Blob([await out.save()], { type: 'application/pdf' }));
// merge
const merged = await PDFDocument.create();
for (const bytes of [a, b]) { const src = await PDFDocument.load(bytes); (await merged.copyPages(src, src.getPageIndices())).forEach(p => merged.addPage(p)); }
// write (jsPDF): quick text/HTML-ish reports
const doc = new jspdf.jsPDF(); doc.text('Hello', 20, 20); doc.addImage(canvas, 'PNG', 20, 30, 100, 60);
download('report.pdf', doc.output('blob'));

Scanned PDFs: getTextContent() is empty - render the page and run OCR (tesseract.js 5: Tesseract.recognize(canvas, 'eng')), or say the file has no text layer.

ZIP (jszip 3.10.2) - bundle a project with assets

const zip = new JSZip();
zip.file('scene.json', JSON.stringify(toData(), null, 2));
zip.folder('textures').file('wood.png', pngBlob);
download('project.zip', await zip.generateAsync({ type: 'blob' }));
pickFile('.zip', false, async buf => {
  const z = await JSZip.loadAsync(buf);
  const scene = JSON.parse(await z.file('scene.json').async('string'));
  const tex = await z.file('textures/wood.png')?.async('blob');
});