# What survives a round trip The editor rebuilds a document's body from what you see on screen and carries the rest of the original package across. This is the ledger of what that costs. Measured over 66 real-world Word documents found on a working machine (CVs, contracts, invoices, forms, letters — Greek, Russian, German and English): clean round-trip : 65 content drift : 1 (a 7 MB WMF picture, see "Dropped") invalid package : 0 threw : 0 "Clean" means: read the file, save it, read it again, and the two editor documents are identical — same blocks, same attributes, same marks on the same text. The saved package is also checked for well-formed XML, no dangling relationship ids and no relationships pointing at parts that aren't there. Reproduce with: cd addon-build/docx-editor npm install && npm run build node test/roundtrip.mjs # the built-in fixture node test/corpus.mjs # a real corpus ## How the two halves work Reading uses [mammoth](https://github.com/mwilliamson/mammoth.js), but not its HTML. mammoth's converter is deliberately semantic, and HTML has nowhere to put a run's colour or a paragraph's line spacing, so it drops them. We take its parsed *document model* instead, through the public `transformDocument` hook, and walk that into the editor's model. See `addon-build/docx-editor/patches.mjs` for the six properties we taught that model to carry: run colour, paragraph spacing, paragraph bottom border, image display size, numbering format and numbering id. Writing uses [docx](https://www.npmjs.com/package/docx), which always builds a brand-new package. Anything living outside the document body would therefore vanish, so `lib/pkg.js` grafts it back: headers, footers, footnotes, endnotes, the style catalogue, the theme and the page setup, re-wiring relationship ids and content types as it goes. ## Kept | | How | |---|---| | Headers and footers | The parts are copied across with their own relationships and images, and re-referenced from the new `sectPr`. | | Footnotes and endnotes | The markers survive in the body as their own node; `footnotes.xml` is copied wholesale so the ids still match. | | Page size, orientation, margins, gutter, title page | Read off the first `sectPr` and handed to the builder. | | The document's styles | The original `styles.xml` is merged over the builder's. Where both define a style id the original wins — it is what the document actually looked like. `docDefaults` comes across too, so unstyled paragraphs don't shift. | | Theme, fonts | `theme1.xml` is copied. | | Title, author, subject, keywords | From `docProps/core.xml`. | | Bold, italic, underline, strike, super/subscript, all-caps, small-caps | | | Font family, size, colour, highlight | Highlight is Word's 15-value enum, not a hex colour, so it passes through exactly. | | Alignment, indent, line spacing, space before/after | | | Headings 1–6, quotes, code blocks | Code blocks ride on a `SourceCode` paragraph style. | | Bulleted and numbered lists, nested, with their numbering format | Format is kept per level, so a list that is decimal at the top and lettered underneath stays that way. | | Where one list ends and the next begins | Tracked by Word's `numId`, so a second list still restarts at 1. | | Tables, including merged cells | Both directions. A 12-row vertical merge comes back as a 12-row vertical merge. | | Images | At the size Word was displaying them, to EMU precision, not the file's natural size. | | Links, internal anchors | | | Page breaks, horizontal rules | A rule is Word's empty paragraph with a bottom border, and is written back as one. | ## Dropped These are detected when the file opens and named in a banner before any editing, and again in the About dialog. The original file on disk is never overwritten — a save downloads `-edited.docx`. - **Tracked changes.** mammoth renders insertions as ordinary text and drops deletions, so a save would silently accept every pending revision. A document with them opens read-only until you explicitly choose "Accept all and edit". - **Comments.** Same gate as tracked changes. - **Equations** (OMML), **shapes, text boxes and WordArt**, **content controls**. - **Fields** — page numbers, tables of contents, cross-references. The text Word last calculated is kept; the field code that would recalculate it is not. - **Bookmarks.** - **Section breaks and multi-column layout.** Only the first section's page setup is kept. - **Metafile pictures (WMF/EMF).** Word's vector picture format: browsers can't display it and the builder can't write it. This is the single drift in the corpus above — one CV with a 7 MB WMF. ## Kept, but not exactly - **Paragraph borders and shading.** Only the rule under an empty paragraph round-trips. A box around a paragraph, or a shaded paragraph, is lost. - **Custom tab stops.** Tab characters are kept; the stop positions are not. - **Exact line spacing.** `atLeast` and `exact` line rules are read but the editor has no control for them, so they are written back as-is only when the paragraph is untouched. - **Table borders.** mammoth doesn't report the borders it read, so every table is written with a plain single-line border. A borderless table gains lines. - **List indentation depth.** A list Word started at level 2 with no level 0 or 1 above it becomes a top-level list, and is written at level 0. - **An empty paragraph after a table inside a cell.** OOXML forbids a cell that ends with a table, so every such document carries a paragraph the author never typed. It is dropped on read and put back on write. ## Decisions worth knowing about **Why not preserve the dropped features as raw XML?** `docx` can embed raw OOXML (`ImportedXmlComponent`), so it is technically possible. What makes it expensive is position: mammoth silently discards the elements it can't model and reports no location for them, so anchoring a passthrough node in the right place needs a second OOXML reader running alongside mammoth purely to recover block order. That is a large amount of machinery whose failure mode is a subtly corrupt package, which is worse than an honest warning. The grafting approach gets headers, footers, notes, page setup and styles — the things most real documents actually have — without that risk. **Why patch mammoth instead of using it as shipped?** Colour, line spacing and numbering format are all editable in this editor's ribbon. Shipping without the patches would mean the editor shows a control for something it silently eats on the next save. The patches are six string replacements applied at build time and each one asserts its anchor, so a mammoth upgrade that moves the code fails the build instead of quietly shipping a lossy reader. **Why is the original file never overwritten?** Because of everything on the "Dropped" list. A save is a download of a new file, so the original is always still there to fall back on.