v7.6.1
v7.6.1: 🔗 Structured-Editor HTML Interop, Lossless Markdown Round-Trips & Footnote Fidelity
I am pleased to announce the release of officeParser v7.6.1! This release teaches HtmlParser the on-the-wire shapes that structured (Tiptap-style) editors actually emit, and makes a document survive the full save → load → save cycle without quietly losing footnotes, highlights, horizontal rules, or frontmatter types along the way. The guiding rule throughout: expand, never inhibit — every parser change only widens what is accepted, and every new generator behaviour is off by default, so existing output is byte-identical except for the deliberate fixes called out below.
(This release also carries the changes staged as 7.6.0, which was merged to master but never separately published — everything is delivered together here.)
Thanks to @pipaacebedo (#109) and @MohammedAlkindi (#111), whose reports drove the fixes below.
Warning
Behavior changes
- A DOCX paragraph mark's run properties no longer bleed onto every run. Bold/italic/underline/colour/size/font set on a paragraph mark (
<w:pPr><w:rPr>) were folded into the base formatting of all the paragraph's runs. Per OOXML ISO 29500 §17.3.1.29 those properties format only the mark glyph; runs now inherit only from the style chain and their own run properties, so formatting on affected runs differs from prior releases. (#109) - Thematic breaks (
---/<hr>) now survive a save. A Markdown---and an HTML<hr>parsed to a page break, which the Markdown generator emits as a bare newline — so a horizontal rule silently vanished on the first save. It is now a distinctbreakType: 'thematic'that emits---in Markdown and<hr>in HTML; an office page break (<hr class="page-break">) stays a page break. - Highlights are generated as
<mark>, not<span style="background-color">. Editors whose highlight extension matches only themarkelement (e.g. Tiptap's Highlight) now rehydrate a highlighted run that previously came back as plain text.<mark>anddata-colorare also parsed on import. - Footnotes no longer grow a
### Notesheading on every save, and empty metadata no longer corrupts the cycle. The Markdown footnote section is emitted as bare[^id]:definitions (byte-stable across cycles), and a document with no metadata fields no longer emits an---\n---block that reparsed as a heading. - Frontmatter scalar types are preserved. A quoted
version: "123"stays a string across a round trip instead of coercing to a number; only bare scalars coerce (YAML semantics). EmbedMetadatawidened.embedTypeis now'youtube' | 'iframe',videoIdis optional, and aheightfield is added. Strict TypeScript consumers that readvideoIdas a non-optionalstring, or switched exhaustively onembedType, may need a small type adjustment.- The footnote-definition HTML is a
<div data-footnote-id>, not a<p>wrapping block content (which every DOM parser split). Default footnote-definition markup changes.
🌟 What's New
1. Attribute-Driven HTML Interop for Wikilinks, Citations, Math and Mermaid
HtmlParser now accepts the shapes structured editors serialize, so content authored in an editor round-trips through officeParser instead of flattening to plain text on the way back:
a[data-wikilink]— page indata-target, display text from the anchor body ordata-alias.span.citation[data-key]— the same bare-key citation node as<cite data-citation-key>.data-math— disambiguated by value: the library's owndata-math="inline|block"is read exactly as before, while any other value is taken as the raw LaTeX (previously read as inline math with the attribute ignored).div[data-mermaid]/div.mermaid/pre.mermaid— mapped to amermaidcode node (previously the div flattened to paragraph text).
The complementary emission is opt-in behind one behavior-named key, HtmlGeneratorConfig.sourceAttributes (default false), so an attribute-driven consumer can rehydrate each node from a data-* attribute. Off by default, output is byte-identical; on, the widened parser reads back every shape it emits, so output stays self-round-trippable. Every sink is entity-escaped, and PDF/EPUB generation force the flag off.
// Round-trips cleanly with the editor's own serialization:
const html = String((await ast.to('html', { htmlConfig: { sourceAttributes: true } })).value);
// <a data-wikilink="true" data-target="Page" data-alias="Alias">…</a>
// <span class="citation" data-key="smith2020">…</span>
// <div class="mermaid" data-mermaid="graph TD; A-->B">…</div>2. Markdown Footnotes That Survive the Round Trip
Footnotes were the single biggest source of quiet corruption on the editor's save/load path, and this release closes every case that surfaced:
- Multi-line definitions continue across indented lines (Pandoc/GFM) and re-emit indented, instead of being cut short at the first newline.
- Orphan definitions — a
[^x]: …with no matching reference — are preserved on both sides (recovered from.mdand from asection[data-footnotes]) with no dangling back-link, so they survive a full md → HTML → md trip instead of being dropped. - Repeated references to one id stay
[^1]/[^1]with a single definition, rather than renumbering to[^1]/[^2]and duplicating the body. Office notes that merely share a numeric id (a footnote and an endnote both numbered1) remain distinct. - A footnote referenced in a table cell is defined exactly once; the generator no longer double-processes cells and pushes the note twice.
- Footnote and endnote bodies now reach RAG chunks (office and Markdown origin), folded into the referencing node's chunk text — searchable where they were previously absent.
3. Blob / File Input in the Browser
parseOffice and OfficeConverter.convert accept a web Blob/File (or any BlobLike with an arrayBuffer() method), so browser callers no longer convert to a Buffer first. A filename drives extension-based detection; a nameless blob resolves through magic-byte sniffing.
const file = document.querySelector('input[type=file]').files[0];
const ast = await parseOffice(file); // Blob/File accepted directly4. Opt-In Inline Formatting and Iframe Preservation
MdGeneratorConfig.fallbackToHtml.inlineFormatting(defaultfalse, opt-in even whenfallbackToHtmlistrue) round-trips inline colour, highlight and font size through.mdas a sanitized<span style>run — formatting that has no Markdown syntax and was otherwise lost when.mdis the storage format.HtmlParserConfig.preserveIframes(defaultfalse) keeps non-YouTube<iframe>embeds that are otherwise dropped.truepreserves any iframe; an array is a hostname allowlist. The src is scheme-checked on generation, so ajavascript:/data:src never survives.
🔧 Also Fixed
- Chunking dropped HTML- and Markdown-origin paragraph text.
ChunkingGeneratorread a node's own.textwith no fallback to its children, so paragraphs built as{ children: [...] }chunked to empty. It now collects text recursively, restoring near-parity with.to('text'). <pre><code>blocks did not decode HTML entities. Escaped characters (<,>,&— e.g. a mermaid-->arrow) surfaced still-escaped in text/Markdown/chunk output and double-escaped a little more each HTML round trip. They are now decoded to their literal characters.npm testnow runs end-to-end on Windows. The build/test scripts use Node'sfsinstead ofmkdir -p/cp/rm -rf, the ESM test helper loads the bundle via afile://URL, and the CLI test launches the CLI throughnoderather than thenpxshim (which a directspawnSynccannot start on Windows). Test-tooling only — the published library is unchanged. (#111)- The public config and
*Metadatatypes are exported from the package root, soimport type { HtmlGeneratorConfig } from 'officeparser'resolves (previously only the browser.d.tscarried them). rtfConfigwas the one generator sub-config not deep-merged in config resolution; the merge is corrected so future fields behave like every other sub-config.- Documentation:
generate(ast, 'chunks')returns anOfficeChunk[]array, not a JSON string; consumers serialize to JSON/JSONL themselves.
🛠 Getting Started
npm install officeparser@7.6.1🔗 Full Changelog: View v7.6.1 details
🔗 Documentation & Visualizer: officeparser.harshankur.com