Skip to content

ElliotTheGreek/ts-docx

Repository files navigation

ts-docx

Read, edit, and save existing Word (.docx) files in Node — a TypeScript port of python-docx.

Status: scaffold only — this folder holds this README and nothing else. The port is built in a dedicated session off this brief. Standalone and FlowDot-agnostic; the flowdot-documents engine (separate project) wraps it. This is the docx sibling of ts-pptx — same architecture, same fidelity guarantees, same conventions. Where this brief is thin, ts-pptx's shipped code is the reference (its src/xml/ lossless DOM, src/opc/ package layer, and fidelity model port directly).

The JS ecosystem has no python-docx equivalent for editing: mammoth only reads (→HTML/text), the docx package only creates. ts-docx opens an existing document, lets you read and edit its paragraphs/runs/tables/sections/styles, and saves it back — in pure TypeScript, with jszip as the only runtime dependency. Runs anywhere Node ≥ 18 runs (including the Electron main process); no native modules, no DOM.

  • Node ≥ 18 · ESM · MIT · one runtime dep (jszip)
  • Ergonomics mirror python-docx (snake_casecamelCase), so its docs and examples translate directly.

Why this exists

python-docx (MIT) is the proven design for programmatic Word editing. Port its layered architecture, not just its feature list. FlowDot's document engine needs full docx read/edit/save in pure TS so it runs identically in the Electron native app, the CLI, and the published MCP server — and so agents and users can edit real Word documents without a Python dependency or a bespoke local MCP server.

What to port (python-docx's layered architecture)

Mirror ts-pptx's layering exactly:

  1. src/xml/ — lossless XML parser / DOM / serializer. Reuse ts-pptx's (byte-span preserving; unknown elements survive verbatim). This is the backbone of the fidelity guarantee.
  2. src/opc/ — OPC package: zip open/save via jszip, [Content_Types].xml, relationships, part loading/serialization. Near-identical to ts-pptx's src/opc/.
  3. src/oxml/ — typed wrappers over WordprocessingML elements (w:document, w:body, w:p, w:r, w:rPr, w:pPr, w:tbl, w:sectPr, …).
  4. Public API (src/{text,table,section,styles,shape,parts}/), mirroring python-docx:
import { Document, Pt, Inches } from "ts-docx";

const doc = await Document.open("report.docx");     // path, Uint8Array, or ArrayBuffer

for (const p of doc.paragraphs) {
  console.log(p.style?.name, p.text);
  for (const run of p.runs) { /* run.text, run.bold, run.font.* */ }
}

// the formatting-preserving edit — change wording, keep the run's look (rPr untouched)
doc.paragraphs[3].runs[0].text = "revised sentence.";

doc.addHeading("Appendix", 1);
const p = doc.addParagraph("Body text ", "Normal");
p.addRun("bold bit").bold = true;

const table = doc.addTable(2, 3);
table.cell(0, 0).text = "Header";

await doc.save("report-edited.docx");               // or: const bytes = await doc.toBuffer();

Everything except open, save, toBuffer is synchronous (only those touch the zip container). The object model — paragraphs, runs, tables, sections — is all sync.

API surface to cover (python-docx parity + the additions below)

  • Document: Document.open(), save, toBuffer, paragraphs, tables, sections, styles, inlineShapes, coreProperties, addParagraph, addHeading, addPageBreak, addPicture, addTable, addSection.
  • Paragraph: text (get/set), runs, style, alignment (WD_ALIGN_PARAGRAPH), paragraphFormat (indents, spacing, keepWithNext, pageBreakBefore, tabs), addRun, insertParagraphBefore, clear.
  • Run: text (the formatting-preserving setter — rPr untouched, matching ts-pptx's run.text), bold/italic/underline, font (name, size, color.rgb / color.themeColor, highlightColor, strike, subscript/superscript, allCaps, smallCaps), styleId, addBreak, addTab, addPicture.
  • Tables: table.rows, columns, cell(r,c), rows[i].cells, cell.text, cell.paragraphs, cell.merge(other), addRow, addColumn, table style, alignment, autofit, column widths, cell vertical alignment/margins; read existing merged cells (cell.isMergeOrigin / span info).
  • Sections: pageWidth/pageHeight, leftMargin/right/top/bottom/gutter/header/footer, orientation, startType; headers & footers (section.header, section.footer, .isLinkedToPrevious, first-page/even-page variants) with their own paragraphs.
  • Styles: doc.styles collection — read/apply paragraph, character, table, list styles; style inheritance (basedOn).
  • Numbering / lists: apply list styles; read list level/numId (full numbering authoring can be a later milestone — flag, don't silently drop).
  • Images: addPicture(bytes|path, width?, height?) (inline), read inline shapes, replace image bytes keeping the shape (mirror ts-pptx picture.replaceImage).
  • Core properties: title/author/subject/keywords/comments/category/created/modified/lastModifiedBy/revision.

Additions beyond python-docx (high-value, python-docx lacks these)

  • Run-aware find & replacedoc.replaceText(search, replace, { all? }) that correctly handles text split across w:r runs (Word constantly splits mid-word on rsid/spellcheck boundaries). This is the #1 real docx-editing operation and the exact thing naive raw-XML regex replace (e.g. the old document-mcp-server) gets wrong. Extract run text into a logical string with an index→run map, match on the logical string, splice runs at boundaries, inherit the matched run's rPr. Ship it with a tests corpus of run-split fixtures.
  • Tracked changes & comments (display + preserve on round-trip at minimum; authoring is a flagged later milestone). Preservation is free from the fidelity model; authoring w:ins/w:del/comments is additive.
  • Reads never mutate (see below).

Units

EMU throughout for lengths, as branded number (mirror ts-pptx). Word also uses twips (page/margins) and half-points (font size) internally — expose the same unit constructors (Pt, Inches, Cm, Mm, Emu, Twips) and a Length converter; the wrappers handle the internal unit per property so callers always work in real units.

The fidelity model (identical to ts-pptx — non-negotiable)

  • Open → save with no edits is loss-free. Every untouched part is written back byte-identical (decompressed entry bytes; the zip container is re-deflated). Unknown/vendor XML survives untouched. Zip entries unreachable from the relationship graph are carried through verbatim (python-docx drops some of these — ts-docx must not).
  • An edit touches only what it must. Change one run's text and only that run's w:t changes.
  • Reads never mutate. Accessing font.color, paragraphFormat, etc. to read never writes XML (a deliberate divergence from python-docx, whose accessors can rewrite on read — required for the loss-free guarantee).

Constraints

  • Pure TypeScript. No native modules. Node ≥ 18 (Electron 28 main floor). Runs in Node and the Electron main process; no DOM.
  • Dependencies pass the FlowDot sanity test: frictionless in the native app, no security exposure, "should we build it ourselves?" answered honestly. Keep the tree at jszip only (build thin XML handling — ported from ts-pptx — rather than importing a framework).
  • License: MIT (© FlowDot LLC). All deps permissive. python-docx's MIT copyright/permission notice reproduced in LICENSE under "Third-party notices," as its license requires.
  • .docx is the open ECMA-376 (Office Open XML) standard.

Testing

  • Fixture corpus of real Word-authored documents in tests/assets/ (round-trip oracle) and fixtures/ (authored corpus): headings, styles, tables (incl. merged + nested), sections with headers/footers, inline images, lists, tracked changes, comments, run-split paragraphs.
  • Backbone test: round-trip every XML entry of every doc through parse → serialize, assert byte-equality.
  • API unit tests per surface; a dedicated run-split find-&-replace suite.
  • scripts/make-mN-gate.mjs per milestone → out/mN-*.docx for manual review in Word.
  • Vitest; tsc --strict clean; publish infra mirrors ts-pptx / ts-pdf-edit (eslint, .npmignore, verify-pack, CI publish with --provenance).

Reference

  • python-docx source + docs (MIT) — the architecture and API being ported.
  • ts-pptx shipped source — the XML/OPC layers and fidelity model to reuse verbatim.
  • ECMA-376 (WordprocessingML) for the XML specifics.
  • Consumer: flowdot-documents; keep this library FlowDot-agnostic.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors