{"_id":"@ariadng/office","_rev":"3-6cd3c0a201ad05ff5e03d579af54e589","name":"@ariadng/office","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@ariadng/office","version":"0.1.0","keywords":["ooxml","office","docx","xlsx","pptx","word","excel","powerpoint","openxml","opc","wordprocessingml","spreadsheetml","presentationml","zero-dependency"],"author":{"name":"Ariadng"},"license":"MIT","_id":"@ariadng/office@0.1.0","maintainers":[{"name":"ariadng","email":"ariadng@gmail.com"}],"homepage":"https://github.com/ariadng/office-js#readme","bugs":{"url":"https://github.com/ariadng/office-js/issues"},"dist":{"shasum":"12da33ca5948a2f4a7144d920f3d04ddc49cbedb","tarball":"https://registry.npmjs.org/@ariadng/office/-/office-0.1.0.tgz","fileCount":59,"integrity":"sha512-8MqRnxanlNDL9/nmj4+Q8ADcG3RVb3644C9bKDQXpA2PkdIuu3RNudW1PhjWj+cHKhfPZv9KFwWKOrrKAMq+Aw==","signatures":[{"sig":"MEQCIAWqFy7oLe0jxUJhxEAZ/n82REFzs3JWWwVN2YAPI6v2AiAgGyTfMYuFTV08KUa/K+Zkm27Cj6FOGzDlnLdqg8kbKw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":649721},"type":"module","_from":"file:C:/Users/dhana/AppData/Local/Temp/claude/C--Users-dhana/f46aa395-4e0c-4169-96ef-460b287c760e/scratchpad/ariadng-office-0.1.0.tgz","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./mce":{"types":"./dist/mce/index.d.ts","default":"./dist/mce/index.js"},"./opc":{"types":"./dist/opc/index.d.ts","default":"./dist/opc/index.js"},"./xml":{"types":"./dist/xml/index.d.ts","default":"./dist/xml/index.js"},"./zip":{"types":"./dist/zip/index.d.ts","default":"./dist/zip/index.js"},"./docx":{"types":"./dist/docx/index.d.ts","default":"./dist/docx/index.js"},"./pptx":{"types":"./dist/pptx/index.d.ts","default":"./dist/pptx/index.js"},"./xlsx":{"types":"./dist/xlsx/index.d.ts","default":"./dist/xlsx/index.js"},"./document":{"types":"./dist/document/index.d.ts","default":"./dist/document/index.js"},"./package.json":"./package.json","./preservation":{"types":"./dist/preservation/index.d.ts","default":"./dist/preservation/index.js"}},"scripts":{"test":"vitest run","build":"node -e \"fs.rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","conformance":"tsx conformance/runner/run.ts","prepublishOnly":"npm run build && vitest run","conformance:cases":"tsx conformance/runner-ts/run.ts"},"_npmUser":{"name":"ariadng","email":"ariadng@gmail.com"},"_resolved":"C:\\Users\\dhana\\AppData\\Local\\Temp\\claude\\C--Users-dhana\\f46aa395-4e0c-4169-96ef-460b287c760e\\scratchpad\\ariadng-office-0.1.0.tgz","_integrity":"sha512-8MqRnxanlNDL9/nmj4+Q8ADcG3RVb3644C9bKDQXpA2PkdIuu3RNudW1PhjWj+cHKhfPZv9KFwWKOrrKAMq+Aw==","repository":{"url":"git+https://github.com/ariadng/office-js.git","type":"git"},"_npmVersion":"11.16.0","description":"Zero-runtime-dependency TypeScript library for reading and writing Microsoft Office OOXML files (docx, xlsx, pptx and their macro/template siblings) with lossless preservation of unmodeled content.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.1","vitest":"^3.0.0","typescript":"^5.6.0","@types/node":"^22.10.0"},"_npmOperationalInternal":{"tmp":"tmp/office_0.1.0_1784460805991_0.13400820077930264","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@ariadng/office","version":"0.2.0","keywords":["ooxml","office","docx","xlsx","pptx","word","excel","powerpoint","openxml","opc","wordprocessingml","spreadsheetml","presentationml","zero-dependency"],"author":{"name":"Ariadng"},"license":"MIT","_id":"@ariadng/office@0.2.0","maintainers":[{"name":"ariadng","email":"ariadng@gmail.com"}],"homepage":"https://github.com/ariadng/office-js#readme","bugs":{"url":"https://github.com/ariadng/office-js/issues"},"dist":{"shasum":"8b68a321b54715e52196fbcfc523bf935d3b2fe1","tarball":"https://registry.npmjs.org/@ariadng/office/-/office-0.2.0.tgz","fileCount":135,"integrity":"sha512-KxXldKBFWz3qbd7X08htcRTxEZ1Y1GVPlVkeooZzoygmDlV+YM7IJY2fhXg745WrlGb8NkeUspJGqU8pycbyhA==","signatures":[{"sig":"MEQCICrX3QHU2Ob1CWONCqKQEAO4B1Y2x1Z+76aMgMHKiDC+AiAt5eOm+Gcuf/erlBjwCGS/SSUuJSoUMPpM9fmkmfBK7Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1572471},"type":"module","_from":"file:C:/Users/dhana/AppData/Local/Temp/claude/C--Users-dhana/f46aa395-4e0c-4169-96ef-460b287c760e/scratchpad/ariadng-office-0.2.0.tgz","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./mce":{"types":"./dist/mce/index.d.ts","default":"./dist/mce/index.js"},"./opc":{"types":"./dist/opc/index.d.ts","default":"./dist/opc/index.js"},"./xml":{"types":"./dist/xml/index.d.ts","default":"./dist/xml/index.js"},"./zip":{"types":"./dist/zip/index.d.ts","default":"./dist/zip/index.js"},"./docx":{"types":"./dist/docx/index.d.ts","default":"./dist/docx/index.js"},"./pptx":{"types":"./dist/pptx/index.d.ts","default":"./dist/pptx/index.js"},"./xlsx":{"types":"./dist/xlsx/index.d.ts","default":"./dist/xlsx/index.js"},"./schema":{"types":"./dist/schema/index.d.ts","default":"./dist/schema/index.js"},"./document":{"types":"./dist/document/index.d.ts","default":"./dist/document/index.js"},"./package.json":"./package.json","./preservation":{"types":"./dist/preservation/index.d.ts","default":"./dist/preservation/index.js"}},"scripts":{"test":"vitest run","build":"node -e \"fs.rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","conformance":"tsx conformance/runner/run.ts","prepublishOnly":"npm run build && vitest run","conformance:cases":"tsx conformance/runner-ts/run.ts"},"_npmUser":{"name":"ariadng","email":"ariadng@gmail.com"},"_resolved":"C:\\Users\\dhana\\AppData\\Local\\Temp\\claude\\C--Users-dhana\\f46aa395-4e0c-4169-96ef-460b287c760e\\scratchpad\\ariadng-office-0.2.0.tgz","_integrity":"sha512-KxXldKBFWz3qbd7X08htcRTxEZ1Y1GVPlVkeooZzoygmDlV+YM7IJY2fhXg745WrlGb8NkeUspJGqU8pycbyhA==","repository":{"url":"git+https://github.com/ariadng/office-js.git","type":"git"},"_npmVersion":"11.16.0","description":"Zero-runtime-dependency TypeScript library for reading and writing Microsoft Office OOXML files (docx, xlsx, pptx and their macro/template siblings) with lossless preservation of unmodeled content.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.1","vitest":"^3.0.0","typescript":"^5.6.0","@types/node":"^22.10.0"},"_npmOperationalInternal":{"tmp":"tmp/office_0.2.0_1784470699229_0.4478935459738389","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@ariadng/office","version":"0.3.0","description":"Zero-runtime-dependency TypeScript library for reading and writing Microsoft Office OOXML files (docx, xlsx, pptx and their macro/template siblings) with lossless preservation of unmodeled content.","keywords":["ooxml","office","docx","xlsx","pptx","word","excel","powerpoint","openxml","opc","wordprocessingml","spreadsheetml","presentationml","zero-dependency"],"author":{"name":"Ariadng"},"homepage":"https://github.com/ariadng/office-js#readme","bugs":{"url":"https://github.com/ariadng/office-js/issues"},"repository":{"type":"git","url":"git+https://github.com/ariadng/office-js.git"},"publishConfig":{"access":"public"},"type":"module","license":"MIT","engines":{"node":">=20"},"sideEffects":false,"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./zip":{"types":"./dist/zip/index.d.ts","default":"./dist/zip/index.js"},"./xml":{"types":"./dist/xml/index.d.ts","default":"./dist/xml/index.js"},"./opc":{"types":"./dist/opc/index.d.ts","default":"./dist/opc/index.js"},"./mce":{"types":"./dist/mce/index.d.ts","default":"./dist/mce/index.js"},"./preservation":{"types":"./dist/preservation/index.d.ts","default":"./dist/preservation/index.js"},"./schema":{"types":"./dist/schema/index.d.ts","default":"./dist/schema/index.js"},"./document":{"types":"./dist/document/index.d.ts","default":"./dist/document/index.js"},"./docx":{"types":"./dist/docx/index.d.ts","default":"./dist/docx/index.js"},"./xlsx":{"types":"./dist/xlsx/index.d.ts","default":"./dist/xlsx/index.js"},"./pptx":{"types":"./dist/pptx/index.d.ts","default":"./dist/pptx/index.js"},"./package.json":"./package.json"},"scripts":{"build":"node -e \"fs.rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test":"vitest run","conformance":"tsx conformance/runner/run.ts","conformance:cases":"tsx conformance/runner-ts/run.ts","prepublishOnly":"npm run build && vitest run"},"devDependencies":{"@types/node":"^22.10.0","tsx":"^4.23.1","typescript":"^5.6.0","vitest":"^3.0.0"},"_id":"@ariadng/office@0.3.0","_integrity":"sha512-a1x/o66QXd23ak/vd7K1P0LWFQAkisplk1d8ZMbQ6P2+Hx6WXeA7qIqKDcMEyZWec72b+hcLHyRnKUQi7qZ3Mg==","_resolved":"C:\\Users\\dhana\\AppData\\Local\\Temp\\claude\\C--Users-dhana\\f46aa395-4e0c-4169-96ef-460b287c760e\\scratchpad\\ariadng-office-0.3.0.tgz","_from":"file:C:/Users/dhana/AppData/Local/Temp/claude/C--Users-dhana/f46aa395-4e0c-4169-96ef-460b287c760e/scratchpad/ariadng-office-0.3.0.tgz","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-a1x/o66QXd23ak/vd7K1P0LWFQAkisplk1d8ZMbQ6P2+Hx6WXeA7qIqKDcMEyZWec72b+hcLHyRnKUQi7qZ3Mg==","shasum":"6e47b8224f12241ab7067e5deaa40f79ce963107","tarball":"https://registry.npmjs.org/@ariadng/office/-/office-0.3.0.tgz","fileCount":159,"unpackedSize":1852634,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDXipcV9/hYRcu9oGz+tq69K5opvE7nuo0LBu75qoQIewIhAP8WsEgS1k/uQN7TW0DniZqoaGIbtfrIdhbQyXVyzP+j"}]},"_npmUser":{"name":"ariadng","email":"ariadng@gmail.com"},"directories":{},"maintainers":[{"name":"ariadng","email":"ariadng@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/office_0.3.0_1784477267257_0.3602701796840708"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-19T11:33:25.853Z","modified":"2026-07-19T16:07:47.588Z","0.1.0":"2026-07-19T11:33:26.180Z","0.2.0":"2026-07-19T14:18:19.398Z","0.3.0":"2026-07-19T16:07:47.424Z"},"bugs":{"url":"https://github.com/ariadng/office-js/issues"},"author":{"name":"Ariadng"},"license":"MIT","homepage":"https://github.com/ariadng/office-js#readme","keywords":["ooxml","office","docx","xlsx","pptx","word","excel","powerpoint","openxml","opc","wordprocessingml","spreadsheetml","presentationml","zero-dependency"],"repository":{"type":"git","url":"git+https://github.com/ariadng/office-js.git"},"description":"Zero-runtime-dependency TypeScript library for reading and writing Microsoft Office OOXML files (docx, xlsx, pptx and their macro/template siblings) with lossless preservation of unmodeled content.","maintainers":[{"name":"ariadng","email":"ariadng@gmail.com"}],"readme":"# @ariadng/office\r\n\r\nA TypeScript library that reads and writes modern Microsoft Office files —\r\nWord, Excel, and PowerPoint, plus every macro, template, slideshow, and\r\nadd-in variant. Its core promise is simple: **anything you don't touch comes\r\nback exactly as it went in**, byte for byte.\r\n\r\n```sh\r\nnpm install @ariadng/office\r\n```\r\n\r\n- **Zero runtime dependencies.** ZIP and XML are implemented in-house on\r\n  platform primitives (`Uint8Array`, `TextEncoder`/`TextDecoder`,\r\n  `CompressionStream`/`DecompressionStream('deflate-raw')`). Runs on Node ≥ 20\r\n  and evergreen browsers; Deno and Bun ride along. ESM only.\r\n- **Open → edit → save without collateral damage.** Parts you never touch are\r\n  copied byte-for-byte from the source file. Content the library has no API\r\n  for — pivot tables, animations, macros, vendor extensions — survives\r\n  automatically, because nothing ever rebuilds it.\r\n- **Create from scratch.** `Document.create()`, `Workbook.create()`, and\r\n  `Presentation.create()` produce minimal files that real Office opens with no\r\n  repair prompt.\r\n- **Content-based detection.** The format is detected from the package bytes\r\n  (main-part content type), never from the file extension.\r\n\r\n## Scope\r\n\r\nAll 16 modern OOXML extensions are supported for open → edit → save:\r\n\r\n| App | Formats |\r\n|---|---|\r\n| Word | `.docx` `.docm` `.dotx` `.dotm` |\r\n| Excel | `.xlsx` `.xlsm` `.xltx` `.xltm` `.xlam` |\r\n| PowerPoint | `.pptx` `.pptm` `.potx` `.potm` `.ppsx` `.ppsm` `.ppam` |\r\n\r\nMacro-enabled variants keep their `vbaProject.bin` as an opaque part — it is\r\npreserved bit-for-bit, never parsed.\r\n\r\nDeliberately **out of scope**:\r\n\r\n- Legacy binary formats (`.doc`, `.xls`, `.ppt`) — permanently.\r\n- `.xlsb` (Excel Binary Workbook) — permanently.\r\n- Rendering, layout, PDF export, page counting.\r\n- Formula **evaluation** (formula *parsing* to a typed AST is fully\r\n  supported; computing values never will be).\r\n- Reading/writing VBA module source (preserve-only).\r\n\r\n## The fidelity ladder\r\n\r\nNo library can promise byte-identical output for XML it has *edited* —\r\nattribute order, whitespace, and ZIP metadata all shift. So fidelity is\r\ndefined as three concrete levels, and every conformance test says which\r\nlevel it checks.\r\n\r\n| Level | Name | Guarantee |\r\n|---|---|---|\r\n| **L1** | Semantic equivalence | A reserialized part compares structurally equal to its source (namespace-aware, prefix-insensitive, attribute-order-insensitive). Minimum bar for every part we reserialize. |\r\n| **L2** | Office-clean | The output opens in real Word/Excel/PowerPoint with **no repair prompt**. Acceptance bar for `create()` outputs and edited files, enforced by the COM harness. |\r\n| **L3** | Byte-stable | Every part **not touched** since open is written byte-for-byte identical (its compressed stream is copied straight through the zip layer). Mandatory behavior of `save()`. |\r\n\r\nOpen → save with zero mutations is L3 for *all* parts — you can prove it\r\nyourself:\r\n\r\n```ts\r\nimport { readFile } from 'node:fs/promises';\r\nimport { OfficeDocument } from '@ariadng/office/document';\r\nimport { comparePackages } from '@ariadng/office/preservation';\r\n\r\nconst bytes = await readFile('budget.xlsx');\r\nconst office = await OfficeDocument.open(bytes);\r\nconsole.log(office.format.extension, office.family); // 'xlsx' 'excel'\r\n\r\n// Zero mutations, so every part raw-copies at save (L3):\r\nconst out = await office.save();\r\nconst report = await comparePackages(bytes, out);\r\nconsole.log(report.equal); // true — every entry byte-identical\r\n```\r\n\r\nThe normative rules (L1 comparison, dirty tracking, the L3 save algorithm)\r\nlive in [`PRESERVATION.md`](./PRESERVATION.md).\r\n\r\n## Architecture: lenses over the DOM\r\n\r\nOne rule underpins the whole library:\r\n\r\n> High-level models (`Paragraph`, `Worksheet`, `Slide`, …) are **lenses over\r\n> the parsed XML tree**. They read and edit the live tree **in place**. They\r\n> never copy it into their own data structures and rebuild the XML from those.\r\n\r\nConsequences:\r\n\r\n- **Unknown content survives by construction.** Markup the model layer has\r\n  never heard of is just a tree node nobody touches; saving reserializes the\r\n  same tree.\r\n- **Reading is free.** Walking the DOM, resolving namespaces, or computing\r\n  MCE reading views mutates nothing — the part stays on the byte-copy path.\r\n- **One tree, many views.** A mutation through any lens is immediately\r\n  visible through the raw DOM and every other lens over the same nodes.\r\n- **Dirtiness is a per-part bit.** Every mutating lens method marks the\r\n  affected part(s) dirty; `save()` reserializes dirty parts and raw-copies\r\n  the rest.\r\n\r\nThe module layering (each is also a subpath export, e.g.\r\n`@ariadng/office/opc`):\r\n\r\n```\r\nzip ─── opc ─── document ─── docx / xlsx / pptx     (task-shaped lenses)\r\nxml ─── opc, mce, preservation, document, docx, xlsx, pptx\r\nmce ─── non-destructive MCE reading views (AlternateContent, Ignorable)\r\npreservation ─── the L1 comparator + package-level round-trip oracle\r\n```\r\n\r\nThe lens API never locks you out of the underlying XML — drop down whenever\r\nthe task-shaped surface runs out, and take over the dirty flag yourself:\r\n\r\n```ts\r\nimport { readFile, writeFile } from 'node:fs/promises';\r\nimport { Document, WML_NAMESPACE } from '@ariadng/office/docx';\r\n\r\nconst doc = await Document.open(await readFile('report.docx'));\r\n\r\n// The live parsed w:body of /word/document.xml — not a copy.\r\nconst body = doc.body();\r\nconst pgSz = body.find(WML_NAMESPACE, 'sectPr')?.find(WML_NAMESPACE, 'pgSz');\r\nconsole.log('page width (twips):', pgSz?.getAttributeNs(WML_NAMESPACE, 'w'));\r\n\r\n// Direct DOM mutations are allowed — you then own the dirty flag:\r\npgSz?.setAttributeNs(WML_NAMESPACE, 'w', '12240'); // resize to US Letter\r\npgSz?.setAttributeNs(WML_NAMESPACE, 'h', '15840');\r\ndoc.office.markDirty(doc.office.mainPart.name);\r\nawait writeFile('report-edited.docx', await doc.save());\r\n```\r\n\r\nThe full per-module behavior contract is in\r\n[`CONTRACTS.md`](./CONTRACTS.md).\r\n\r\n## Quickstart\r\n\r\n### Word (`@ariadng/office/docx`)\r\n\r\n```ts\r\nimport { readFile, writeFile } from 'node:fs/promises';\r\nimport { Document } from '@ariadng/office/docx';\r\n\r\n// Open → edit → save. Styles, numbering, images, themes, and unknown\r\n// markup you don't touch round-trip byte-identically.\r\nconst doc = await Document.open(await readFile('report.docx'));\r\n\r\nfor (const p of doc.paragraphs()) {\r\n  console.log(p.styleId() ?? '(default)', JSON.stringify(p.text()));\r\n}\r\n\r\ndoc.addParagraph('Reviewed and approved.', { bold: true });\r\nawait writeFile('report-reviewed.docx', await doc.save());\r\n```\r\n\r\n```ts\r\nimport { writeFile } from 'node:fs/promises';\r\nimport { Document } from '@ariadng/office/docx';\r\n\r\n// Create from scratch — opens in Word with no repair prompt.\r\nconst doc = Document.create();\r\ndoc.addParagraph('Quarterly Report');\r\ndoc.addParagraph('Everything is on track.', { italic: true });\r\nawait writeFile('fresh.docx', await doc.save());\r\n```\r\n\r\nText extraction is MCE-aware: content inside `mc:AlternateContent` is read\r\nthrough the branch a current Office build would select, never both.\r\n\r\nWord modeling goes well beyond plain text. `format()` resolves the full\r\nstyle cascade — docDefaults → the `w:basedOn` style chain → numbering →\r\ndirect formatting — with correct ECMA-376 §17.7.3 toggle (XOR) semantics\r\nand theme fonts resolved at open. `list()` reports a paragraph's numbering:\r\n\r\n```ts\r\nimport { readFile } from 'node:fs/promises';\r\nimport { Document } from '@ariadng/office/docx';\r\n\r\nconst doc = await Document.open(await readFile('report.docx'));\r\n\r\nconst heading = doc.paragraphs()[0];\r\nconst pf = heading.format(); // resolved paragraph facts\r\nconsole.log(pf.styleName, pf.spacingBeforeTwips); // 'heading 1' 360\r\n\r\nconst rf = heading.runs()[0].format(); // resolved run facts\r\nconsole.log(rf.sizePt, rf.color, rf.font); // 20 '0F4761' 'Aptos Display'\r\n\r\nconsole.log(heading.list()); // null — the heading is not in a list\r\n```\r\n\r\nTables, inline images, headers/footers, and lists all have task-shaped\r\ncreate/read APIs:\r\n\r\n```ts\r\nimport { writeFile } from 'node:fs/promises';\r\nimport { Document } from '@ariadng/office/docx';\r\n\r\nconst doc = Document.create();\r\ndoc.addParagraph('Quarterly Report', { style: 'Heading1' });\r\n\r\n// A visible table (single-line borders when no style is given):\r\nconst table = doc.addTable(2, 3);\r\n['Region', 'Product', 'Revenue'].forEach((h, c) => table.cell(0, c).setText(h));\r\ntable.cell(1, 0).setText('North');\r\nconst row = table.addRow(); // copies the last row's cell widths\r\nrow.cells()[0].setText('South');\r\n\r\n// Each list call is its own numbering definition (numbered lists restart at 1):\r\ndoc.addNumberedList(['Prepare', 'Execute', 'Verify']);\r\nconst bullets = doc.addBulletList(['Alpha', { text: 'Nested', level: 1 }, 'Beta']);\r\nconsole.log(bullets.at(-1)?.list()?.isBullet); // true — round-trips through the resolver\r\n\r\n// An inline image — PNG/JPEG sniffed from magic bytes, sized at 96 DPI:\r\nconst png = Uint8Array.from(\r\n  atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='),\r\n  (c) => c.charCodeAt(0),\r\n);\r\nconst img = doc.addImage(png, { widthPx: 120, altText: 'logo' });\r\nconsole.log(img.format, img.partName); // 'png' '/word/media/image1.png'\r\n\r\n// Headers/footers are wired into every section reference for you:\r\ndoc.setHeader('ACME Corp — Confidential');\r\ndoc.setFooter('Page footer', { type: 'first' }); // sets w:titlePg\r\n\r\nawait writeFile('built.docx', await doc.save());\r\n```\r\n\r\n### Excel (`@ariadng/office/xlsx`)\r\n\r\n```ts\r\nimport { readFile, writeFile } from 'node:fs/promises';\r\nimport { Workbook } from '@ariadng/office/xlsx';\r\n\r\nconst wb = await Workbook.open(await readFile('budget.xlsx'));\r\n\r\nconst data = wb.sheet(0); // by index — or wb.sheet('Data') by name\r\nif (data === undefined) throw new Error('workbook has no sheets');\r\nconsole.log(data.name, data.getCell('A1'));\r\n\r\n// Typed reads: shared/inline strings, numbers, booleans, Dates for\r\n// date-formatted cells, cached formula results:\r\nconsole.log(data.getCellInfo('B2'));\r\n\r\ndata.setCell('B2', 1234.5);\r\ndata.setCell('B3', 'paid'); // interned in the shared string table\r\ndata.setCell('B4', new Date(Date.UTC(2026, 6, 19))); // serial + date format\r\nawait writeFile('budget-edited.xlsx', await wb.save());\r\n```\r\n\r\n```ts\r\nimport { writeFile } from 'node:fs/promises';\r\nimport { Workbook } from '@ariadng/office/xlsx';\r\n\r\n// Create from scratch — one empty 'Sheet1', minimal stylesheet.\r\nconst wb = Workbook.create();\r\nconst sheet = wb.sheet('Sheet1');\r\nif (sheet === undefined) throw new Error('unreachable');\r\nsheet.setCell('A1', 'Item');\r\nsheet.setCell('B1', 'Price');\r\nsheet.setCell('A2', 'Rice (5 kg)');\r\nsheet.setCell('B2', 79000);\r\n\r\nconst notes = wb.addSheet('Notes');\r\nnotes.setCell('A1', 'Generated by @ariadng/office');\r\nawait writeFile('fresh.xlsx', await wb.save());\r\n```\r\n\r\nWrites match what Excel itself produces: strings go through\r\n`/xl/sharedStrings.xml` (deduplicated), dates get a cell format derived from\r\nthe cell's current style so fills/borders/fonts survive, and rows/cells are\r\nkept in the order Excel requires.\r\n\r\nStyling, merges, real Excel tables, defined names, formulas, and\r\nconstant-memory row streaming:\r\n\r\n```ts\r\nimport { writeFile } from 'node:fs/promises';\r\nimport { Workbook, readRows } from '@ariadng/office/xlsx';\r\n\r\nconst wb = Workbook.create();\r\nconst sheet = wb.sheet('Sheet1');\r\nif (sheet === undefined) throw new Error('unreachable');\r\n\r\nconst rows = [\r\n  ['Region', 'Product', 'Revenue'],\r\n  ['North', 'Widget', 5300],\r\n  ['South', 'Gadget', 4100],\r\n] as const;\r\nrows.forEach(([region, product, revenue], i) => {\r\n  sheet.setCell(`A${i + 1}`, region);\r\n  sheet.setCell(`B${i + 1}`, product);\r\n  sheet.setCell(`C${i + 1}`, revenue);\r\n});\r\n\r\n// Styles are flat, partial writes — only what you name changes, and\r\n// repeated styling never bloats styles.xml (everything is deduplicated).\r\nsheet.setCellStyle('A1:C1', { bold: true, fill: '#4472C4', color: '#FFFFFF' });\r\nsheet.setCellStyle('C2:C3', { numberFormat: '#,##0.00' });\r\nsheet.setColumnWidth('B', 14.5);\r\nconsole.log(sheet.getCellStyle('A1').font.bold); // true — fully resolved read\r\n\r\n// A real Excel table (with autofilter) over the data:\r\nconst table = await sheet.addTable('A1:C3', { name: 'Sales' });\r\nconsole.log(table.columns); // ['Region', 'Product', 'Revenue']\r\n\r\n// Formulas are validated by a real parser before anything is written:\r\nsheet.setFormula('C4', '=SUM(Sales[Revenue])');\r\nconsole.log(sheet.getFormula('C4')); // 'SUM(Sales[Revenue])'\r\n\r\nwb.setDefinedName('Total', 'Sheet1!$C$4');\r\nconsole.log(wb.getDefinedName('Total')?.ref); // 'Sheet1!$C$4'\r\n\r\nconst bytes = await wb.save();\r\nawait writeFile('sales.xlsx', bytes);\r\n\r\n// Read rows back without ever building the sheet DOM (million-row safe):\r\nfor await (const row of readRows(bytes, { sheet: 'Sheet1', range: 'A2:C3' })) {\r\n  console.log(row.index, row.values); // 2 ['North', 'Widget', 5300] …\r\n}\r\n```\r\n\r\nThe formula toolkit is available standalone — parse to a typed AST,\r\nserialize back, convert between A1 and R1C1:\r\n\r\n```ts\r\nimport { parseFormula, formulaToText, a1ToR1C1, r1c1ToA1 } from '@ariadng/office/xlsx';\r\n\r\nconst ast = parseFormula('=XLOOKUP(A2,Table1[SKU],Table1[Price])');\r\nconsole.log(ast.kind); // 'call' — stored XML says _xlfn.XLOOKUP; the AST says XLOOKUP\r\nconsole.log(formulaToText(ast)); // 'XLOOKUP(A2,Table1[SKU],Table1[Price])'\r\n\r\nconsole.log(a1ToR1C1('SUM(B2:C2)', 'D2')); // 'SUM(RC[-2]:RC[-1])'\r\nconsole.log(r1c1ToA1('SUM(RC[-2]:RC[-1])', 'D2')); // 'SUM(B2:C2)'\r\n```\r\n\r\n### PowerPoint (`@ariadng/office/pptx`)\r\n\r\n```ts\r\nimport { readFile, writeFile } from 'node:fs/promises';\r\nimport { Presentation } from '@ariadng/office/pptx';\r\n\r\nconst deck = await Presentation.open(await readFile('deck.pptx'));\r\n\r\nfor (const slide of deck.slides()) {\r\n  console.log(slide.partName, slide.text()); // one string per a:p\r\n}\r\n\r\ndeck.slides()[0].setTitle('FY26 Kickoff');\r\ndeck.addSlide(); // new slide referencing the deck's first layout\r\nawait writeFile('deck-edited.pptx', await deck.save());\r\n```\r\n\r\n```ts\r\nimport { writeFile } from 'node:fs/promises';\r\nimport { Presentation } from '@ariadng/office/pptx';\r\n\r\n// Create from scratch: master + layout + a complete theme, all wired —\r\n// PowerPoint opens it with no repair prompt.\r\nconst deck = Presentation.create();\r\ndeck.addSlide();\r\nconsole.log(deck.slides().length); // 2\r\nawait writeFile('fresh.pptx', await deck.save());\r\n```\r\n\r\nTransitions, animations, and media are untouched by slide operations and\r\ntherefore preserved byte-for-byte.\r\n\r\n## Support matrix\r\n\r\nTwo tiers of support for every feature of every format:\r\n\r\n- **Modeled** — a task-shaped API reads and writes it. Mutations mark only\r\n  the affected part(s) dirty.\r\n- **Preserve-only** — no API surface yet, but the content survives open →\r\n  edit → save **byte-for-byte** (see [why this is safe](#why-preserve-only-content-is-safe)\r\n  below). You can still reach it through the raw XML DOM (`body()`,\r\n  `root()`, `OfficeDocument.xml()`) and own the dirty flag yourself.\r\n\r\n### Word (`@ariadng/office/docx`)\r\n\r\n| Modeled | Preserve-only |\r\n|---|---|\r\n| `open` / `create` / `save` | Style *definitions* authoring (no new `w:style` elements; existing styles are read and resolved) |\r\n| Paragraph enumeration (`paragraphs()`), body text extraction (`text()`, MCE-aware) | Footnotes, endnotes, comments, tracked changes, fields, content controls |\r\n| `addParagraph(text, { style, bold, italic })`; per-paragraph `text()` / `styleId()` / `runs()`; per-run `text()` / `bold()` / `italic()` / `setText()` | Anchored/floating image *creation* (existing anchored images are listed read-only; inline creation is supported) |\r\n| **Effective formatting**: `Paragraph.format()` / `Run.format()` — full cascade (docDefaults → `w:basedOn` chain → numbering → direct), toggle XOR (§17.7.3), theme fonts | Cell merging *creation* (`w:gridSpan` / `w:vMerge`), table-style authoring, `w:tblStylePr` folding into effective formats |\r\n| **Numbering**: `Paragraph.list()` read; list creation `addBulletList` / `addNumberedList` | Section insertion & page setup editing (page size/margins/columns) |\r\n| **Tables**: `tables()` / `addTable`; `Table` / `TableRow` / `TableCell` (read, `setText`, `addRow`) | Themes beyond font resolution, drawings/shapes/text boxes, settings, macros (`vbaProject.bin`) |\r\n| **Images**: `images()` / `addImage` (inline PNG/JPEG, magic-byte sniffed); `pxToEmu` / `emuToPx` / `EMU_PER_PIXEL`. **Headers/footers**: `headers()` / `footers()` / `setHeader` / `setFooter`. Raw DOM: `body()`, `WML_NAMESPACE` | |\r\n\r\n### Excel (`@ariadng/office/xlsx`)\r\n\r\n| Modeled | Preserve-only |\r\n|---|---|\r\n| `open` / `create` / `save` | Formula **evaluation** (permanent non-goal; cached results are readable) |\r\n| Sheet enumeration/lookup (`sheets()`, `sheet(nameOrIndex)`), `addSheet(name)` | Theme & indexed **colors** (read as `color: undefined`; survive untouched unless that property is overwritten) |\r\n| Typed cell reads: `getCell`, `getCellInfo` — shared/inline strings, numbers, booleans, dates (via number formats), errors, formula text + cached results | Conditional formatting, data validation, comments/notes |\r\n| `setCell(ref, value)` — string (shared-string interned), number, boolean, `Date`, `null` to clear | Pivot tables, charts, rich-text run styling inside cells |\r\n| Cell styling: `getCellStyle` (fully resolved read) / `setCellStyle` (flat partial writes: font, fill, border, number format, alignment — deduplicated into styles.xml) | Table totals rows & table style *definitions* (referenced by name only) |\r\n| Column widths / row heights: `getColumnWidth` / `setColumnWidth`, `getRowHeight` / `setRowHeight` | Shared-formula group rewriting, array-formula creation (existing ones preserved) |\r\n| Formulas: `setFormula` (parser-validated, calc chain handled) / `getFormula` (shared-formula dependents translated); AST toolkit `parseFormula` / `formulaToText`, `a1ToR1C1` / `r1c1ToA1` | Sheet rename/remove/reorder, freeze panes, print setup, other row/column properties (hidden, outline), macros |\r\n| Merged cells (`merge` / `unmerge` / `merges`); Excel tables (`addTable` / `tables`); defined names (`definedNames` / `getDefinedName` / `setDefinedName` / `removeDefinedName`) | |\r\n| Streaming row reads: `readRows` — iterate any sheet without building its DOM | |\r\n| A1/range helpers: `parseCellRef` / `formatCellRef` / `parseRangeRef` / `formatRangeRef`; 1904 date system; calcChain invalidation on formula overwrite | |\r\n\r\n### PowerPoint (`@ariadng/office/pptx`)\r\n\r\n| Modeled | Preserve-only |\r\n|---|---|\r\n| `open` / `create` / `save` | Adding/positioning shapes, text boxes, pictures |\r\n| Slide enumeration (`slides()`), per-slide `text()` (one string per `a:p`, MCE-aware) | Editing non-title text on existing slides (title only via `setTitle`) |\r\n| `setTitle(text)` — replaces the title placeholder's text (throws if the slide has none; `create()` slides are blank) | Animations, transitions, media |\r\n| `addSlide({ layoutIndex \\| layoutPartName })` / `removeSlide(index)`; `layouts()` introspection | Speaker notes, comments |\r\n| Raw DOM access: `root()`, `partName` / `layoutPartName` | Themes, masters & layout *editing*, SmartArt, charts, macros |\r\n\r\n### Why preserve-only content is safe\r\n\r\nThe [fidelity ladder](#the-fidelity-ladder) is what makes \"no API yet\"\r\ndifferent from \"unsupported\":\r\n\r\n- **L3 — untouched parts never get rewritten.** Dirtiness is tracked per\r\n  part; `save()` copies every non-dirty part's compressed bytes straight\r\n  through the ZIP layer. A pivot table, animation, or macro lives in parts\r\n  your edits never dirty, so it comes back **byte-identical** — not\r\n  \"re-emitted and hopefully equivalent\".\r\n- **L1 — within a dirty part, unknown markup survives by construction.**\r\n  Models are lenses over the parsed XML DOM: adding a paragraph appends\r\n  nodes to the live tree, and everything else in that tree (unknown\r\n  elements, attributes, namespaces, vendor extensions) reserializes with\r\n  structural equality guaranteed by the L1 comparator.\r\n- **L2 — real Office is the referee.** The conformance suite opens every\r\n  runner output in actual Word/Excel/PowerPoint via COM with auto-repair\r\n  disabled; a repair prompt is a test failure.\r\n\r\nSo a file full of features this library has never heard of can be opened,\r\nedited at the cells/paragraphs/slides level, and saved — and every one of\r\nthose features survives.\r\n\r\n## Repository layout: two repos, two products\r\n\r\nThis project ships as two sibling git repositories:\r\n\r\n| Repo | Product |\r\n|---|---|\r\n| `office` (this repo) | **Product 1** — the TypeScript reference implementation: `src/**`, the two conformance runners (`conformance/runner`, `conformance/runner-ts`), and the library docs (`CONTRACTS.md`, `PRESERVATION.md`). |\r\n| `office-spec` | **Product 2** — the language-agnostic specification (`spec/` chapters), the machine-readable conformance suite (`conformance/cases`, `conformance/corpus`), the fixture generators (`tools/gen-fixtures`), and the real-Office L2 harness (`tools/office-harness`). |\r\n\r\nEverything here that needs spec-repo files (fixtures, cases, the harness)\r\nlocates the spec repo through one module —\r\n`src/test-support/spec-dir.ts`:\r\n\r\n1. `OFFICE_SPEC_DIR` environment variable, if set, is the spec repo root;\r\n2. otherwise the sibling checkout `<thisRepo>/../office-spec` is used.\r\n\r\nSo the default setup is simply two sibling clones:\r\n\r\n```\r\n<parent>/office        this repo\r\n<parent>/office-spec   the spec + conformance suite\r\n```\r\n\r\nSet `OFFICE_SPEC_DIR` only when the spec repo lives somewhere else.\r\n\r\n## Testing, conformance & the Office harness\r\n\r\nThree layers of validation, all run from the repo root (all three need the\r\nspec repo checkout — see the layout section above):\r\n\r\n```sh\r\nnpx tsc --noEmit          # strict typecheck\r\nnpx vitest run            # unit tests, colocated as src/**/*.test.ts\r\nnpm run conformance:cases # machine-readable conformance cases (spec 14)\r\nnpm run conformance       # round-trip matrix over the spec repo's corpus\r\n```\r\n\r\nThe conformance runner exercises every package in the spec repo's\r\n`conformance/corpus/**` (hand-crafted minimal packages plus files generated\r\nby COM-driving real Office) through three steps:\r\n\r\n- **A — untouched round-trip:** open → save → every part must be\r\n  byte-identical (L3).\r\n- **B — trivial edit per family:** add a paragraph / set a cell / add a\r\n  slide → save → reopen → the edit is visible and only the documented parts\r\n  differ.\r\n- **C — L2 harness:** every output is opened in real Word, Excel, and\r\n  PowerPoint via COM, with auto-repair disabled so corruption fails loudly.\r\n\r\nStep C needs Windows with Microsoft Office installed; elsewhere it is\r\nskipped automatically (or explicitly with\r\n`npm run conformance -- --skip-harness`). The harness (which lives in the\r\nspec repo) can also be pointed at any folder directly:\r\n\r\n```powershell\r\npowershell -NoProfile -ExecutionPolicy Bypass -File ../office-spec/tools/office-harness/check.ps1 -Folder conformance/out\r\n```\r\n\r\nIt prints a JSON report per file and exits with the number of files that\r\nfailed to open clean. Corpus regeneration scripts\r\n(`../office-spec/tools/gen-fixtures/`) and harness details are documented in\r\nthe spec repo's `conformance/README.md`; the runner contract is in\r\n[`conformance/runner/README.md`](./conformance/runner/README.md).\r\n\r\nThe spec repo's T3 adversarial corpus is generated from *this* repo:\r\n`npx tsx tools/gen-t3/generate.ts` deterministically (byte-identically)\r\nrewrites `../office-spec/conformance/corpus/t3-adversarial/` using this\r\nlibrary's ZIP writer, locating the spec repo via the same\r\n`src/test-support/spec-dir.ts` contract.\r\n\r\nEvery `ts` code block in this README is executable: `scratch/readme-check.mts`\r\nextracts and runs them all against the real corpus fixtures\r\n(`npx tsx scratch/readme-check.mts` from the repo root).\r\n\r\n## Roadmap\r\n\r\nThis repo is the TypeScript reference implementation of a larger phased plan:\r\n\r\n- **Here today:** the container core (zip, xml, opc, mce, preservation),\r\n  format detection and the L3 save pipeline, a schema layer generated from\r\n  the ECMA-376 XSDs (element ordering), task-shaped lenses for Word, Excel,\r\n  and PowerPoint, deep Word modeling — the style-cascade and numbering\r\n  resolvers (`format()` / `list()`), tables, inline images, and\r\n  headers/footers — deep Excel modeling — cell styles, column/row sizing,\r\n  merged cells, tables, defined names, a full formula parser (typed AST,\r\n  A1↔R1C1) and constant-memory row streaming — plus fuzzing gates, all\r\n  validated against a real-Office corpus.\r\n- **Next, per the plan:** Word tracked changes and section/page-setup\r\n  editing; PowerPoint placeholder/property inheritance resolution and\r\n  slide copy/reorder; Excel conditional formatting and data validation.\r\n- **Post-1.0:** encryption ([MS-OFFCRYPTO]), digital signatures, deeper\r\n  chart/pivot/animation models.\r\n\r\nThe companion product is a **language-agnostic specification** plus a\r\nmachine-readable conformance suite (the sibling `office-spec` repo: `spec/`\r\nchapters and `conformance/cases/`) so the same behavior can be\r\nre-implemented in any language and *proven* against the same fixtures.\r\n\r\nReference documents:\r\n\r\n| Document | Contents |\r\n|---|---|\r\n| [`CONTRACTS.md`](./CONTRACTS.md) | Normative per-module API behavior contract (this repo) |\r\n| [`PRESERVATION.md`](./PRESERVATION.md) | Fidelity ladder, dirty tracking, L1 comparison rules (this repo) |\r\n| `../office-spec/conformance/README.md` | Corpus tiers, fixture provenance, harness usage (spec repo) |\r\n| `../office-spec/spec/` | Language-agnostic spec chapters (spec repo) |\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md"}