Format: Keep a Changelog.
[Unreleased]
[1.0.0] - 2026-10-07
Added
- Core package container (L0) —
pkgPackage,pkgManifest,pkgMimetyperead / write ZIP-based ODF packages, enforcing themimetype-first-STORED convention and parsingMETA-INF/manifest.xml. Namespace URIs, theODF_VERSIONconstant, XML declaration variants and the frozen MIME table (CT.ODT/.ODS/ .ODP/.XML/.FORMULA) are centralized in the sharedodfSharedfactory (see below) rather than duplicated per module. - Typed errors — the
odfErrorsfactory exposesOdfError,ParseError,RenderError,ContractErrorandisOdfError; consumers resolve it viaruntime.resolve('odfErrors')orodfErrors.factory()in tests. Error codes are namespaced by origin (odf/parse-error/<part>—/odt,/ods,/odp,/manifest,/meta,/settings,/styles,/mimetype,/pkg,/chart,/math,/limit), and API/contract violations (odt.write(null),pkg.write({})without mimetype,pkgMimetype.parse/renderwith bad input, …) raiseContractError('odf/contract-error/<module>', …)instead ofParseError. All raised errors carry a richcontext: { part, module, ... }and preservecause. - Metadata / settings / styles —
odfMetaparses / rendersmeta.xml(<office:document-meta>→<office:meta>carryingdc:title,dc:creator,dc:date,meta:generator,meta:initial-creator,meta:creation-date, preserve-unknowns for the rest).odfSettingsparses / renderssettings.xml, preserving<config:config-item-set>children as raw element nodes.odfStylesparses / rendersstyles.xmlwith the three standard buckets (office:styles,office:automatic-styles,office:master-styles) as raw element node arrays. - Text (L0/L1) —
textParagraphtypes<text:p>/<text:span>plustext:s,text:tab,text:line-breakrun kinds.textHeading(<text:h>+outlineLevel),textList(<text:list>+<text:list-item>, incl. nested lists),textSection(<text:section>),textBookmarks(inline<text:bookmark*>/<text:reference-mark*>),textFields(<text:date>,<text:page-number>,<text:variable-*>,<text:bookmark-ref>+ twelve other inline field elements),textContent(body orchestrator:parseBody,renderBody,bodyText). - Table (L1) —
tableCell(typed value/formula/spans/repeated; the opt-inmaxRepeatoption ofparseCellraisesParseError('odf/parse-error/limit')whentable:number-columns-repeatedexceeds the ceiling),tableRow(cells + repeated, same option onparseRow),tableTable(columns, header rows, rows). - Draw (L1) —
drawImage(<draw:image>+ magic-byte sniffer for PNG/JPEG/GIF/BMP/WebP/TIFF/SVG) anddrawFrame(<draw:frame>wrapping image / text-box / object children). - Style (L1) —
styleAutomatic(typed<office:automatic-styles>with per-family property bags),stylePageLayout(<style:page-layout>+ header/footer styles),styleMasterPage(<style:master-page>with header/footer/header-left/footer-left preserved as raw XML). - Number (L1) —
numberFormatsparses / renders date / time / number / currency / percentage / boolean / text styles, preserving ordered typedparts. .odtorchestrator — theodtfactory composes the stack to read / write a complete.odtpackage (mimetypeSTORED +META-INF/manifest.xml+content.xml+styles.xml+meta.xmlsettings.xml), exposingread,write,empty,paragraph,fromText,toText. The body consumestextContent:read()returns mixed typed nodes (paragraphs / headings / lists / sections / soft-page-breaks /unknown),toText()walks them recursively.
- ODS (L2) —
spreadsheettypes<office:spreadsheet>as{ tables, namedExpressions?, dataValidations?, _extras? }(named expressions and content validations preserved as raw XML).odsis the.odsorchestrator (mimetypeapplication/vnd.oasis.opendocument.spreadsheet), exposingread,write,empty,sheet,fromArrays,cell,toText,CT_ODS.tableCellgained typedoffice:value-typesupport forfloat/percentage/currency/date/time/boolean/stringwith the matchingoffice:value/office:date-value/office:time-value/office:string-value/office:boolean-value/office:currencyattrs;table:formulapreserves the OpenFormulaof:=…prefix transparently. - ODP (L2) —
presentationStyletypes<presentation:placeholder>and<presentation:notes>(basic; animations/transitions deferred to L3).slidetypes<draw:page>as{ name, masterPageName?, styleName?, layoutName?, frames, notes?, _extras? }.odpis the.odporchestrator (mimetypeapplication/vnd.oasis.opendocument.presentation), exposingread,write,empty,slide,fromSlides,toText,CT_ODP. - L3 core surface —
textTracked(<text:tracked-changes>container:changed-region× {insertion|deletion|format-change} withoffice:change-info, plus inlinetext:change/text:change-start/text:change-endmarkers).drawShape(typeddraw:rect,draw:circle,draw:ellipse,draw:line,draw:polyline,draw:polygon,draw:path,draw:custom-shapewith optionaldraw:enhanced-geometry).chartChart(<chart:chart>root parser/renderer preserving title/subtitle/legend/plot-area, axes, series, data-points, plusbytesOf/parseBytesfor the chart content.xml sub-document).mathMath(opaque passthrough for embedded MathML<math:math>+bytesOf/parseBytes).odpAnimations(recursive parse/render foranim:*trees —par,seq,set,animate,animateColor,animateMotion,animateTransform,transitionFilter,audio,command,iterate,param— plusparseTransition/renderTransitionforpresentation:transition).formForms(<office:forms>container with the typedform:*control set: button, text, checkbox, listbox, combobox, radio, date, time, file, hidden, image-frame, formatted-text, fixed-text, password, textarea, generic-control, value-range, column, grid, item, option, properties, property, list-property, connection-resource).dr3dScene(<dr3d:scene>with typed lights anddr3d:cube/dr3d:sphere/dr3d:extrude/dr3d:rotate).odfMc(markup-compatibility helpersversionOf,meetsVersion, and aprocessno-op mirroring OOXML'smarkupCompatibility.processfor future stripping/promotion logic). - Extension hook
.use()— one walker module per top-level orchestrator (odtWalker,odsWalker,odpWalker), each exposingcreateWalker()→{ use, applyHydrate, applyDehydrate, hasExtensions }. Theodt/ods/odpfactories wire the walker intoread()(post-parse hydrate) andwrite()(pre-render dehydrate) and expose.use(...extensions)for idempotent registration of opt-in extras. Hook surface:hydrate*/dehydrate*forParagraph,Span,Heading,List,Table,Cell,Frame,Slide, plusMetadata,Settings,Styles. The three walkers share their dispatch/indexing engine via theodfWalkerfactory (see below) instead of duplicating ~100 LOC each; the hook index is keyed by hook name and rebuilt lazily after eachuse(...), so on a*-fullbundle (≈29 extensions, ~10 000 paragraphs) the dispatch table only holds extensions that actually implement the requested hook. - Coverage extras — P0 (typed, always shipped in the
*-largebundles):textTrackedChanges(text:tracked-changes,text:changed-region,text:insertion,text:deletion,text:format-change+ inline change markers),textFieldsExtended(text:variable-*,text:user-field-*,text:sequence-decl,text:expression,text:database-*,text:hidden-*,text:conditional-text,text:placeholder,text:execute-macro,text:dde-connection*,text:meta-field),textListDetailed(text:list-style,text:list-level-style-*,text:outline-style,text:outline-level-style,text:list-header),tableAdvanced(table:table-template,table:database-range,table:filter*,table:scenario,table:sort*,table:data-pilot-*recursive tree),stylePage(style:page-layout,style:page-layout-properties,style:master-page,style:header(-left|-first)?,style:footer(-left|-first)?,style:background-image,style:column*,style:footnote-sep,style:layout-grid-properties),stylePropertiesTyped(promotes a curated subset of attrs on everystyle:*-propertieselement into typed fields, unknown attrs preserved),drawShapes(draw:rect,draw:circle,draw:ellipse,draw:line,draw:polyline,draw:polygon,draw:path,draw:regular-polygon,draw:connector,draw:caption,draw:measure,draw:control,draw:custom-shapewithdraw:enhanced-geometry+draw:equation+draw:handle,draw:contour-*),presentationTyped(presentation:placeholder,presentation:notes,presentation:settings,presentation:show*,presentation:hide*,presentation:dim,presentation:play,presentation:event-listener(s)?,presentation:sound,presentation:date-time(-decl)?,presentation:footer(-decl)?,presentation:header(-decl)?,presentation:animations,presentation:transition). - Bundles
*-large—@awacloud/odf/odt-large(core odt + the 7 P0 odt-relevant extras, descriptorodtLargeBundle),@awacloud/odf/ods-large(core ods + the 6 P0 ods-relevant extras,odsLargeBundle),@awacloud/odf/odp-large(core odp + the 6 P0 odp-relevant extras,odpLargeBundle). Each*-largebundle leaves P1/P2/P3 extras out; the descriptors replaced earlier imperativebuild*helpers (see Removed). - Coverage extras — P1 (deep complementary typing):
textMetaExtended(text:meta,text:meta-field,text:rdf-metadata+ paragraph RDFa attrs),textSectionsAdvanced(text:section-source,text:section-decl,text:dde-connection+ protection attrs),textTocIndex(every ODF index family — TOC, alphabetical, user, object, illustration, table, bibliography — + all*-source/*-entry-template/index-title-template/index-bodychildren),drawImageExtended(draw:area-*,draw:image-map,draw:gradient/hatch/fill-image/opacity/marker/stroke-dash,draw:layer*,draw:applet/plugin/floating-frame/object/object-ole),chartTyped(deepchart:*tree: title, subtitle, footer, legend, plot-area, axis, categories, grid, series, domain, data-point, mean-value, regression-curve, error-indicator, stock-gain/loss/range, wall/floor, label-separator, equation, data-label),animationsSmil(typedanim:*— par, seq, iterate, audio, command, set, animate, animateColor, animateMotion, animateTransform, transitionFilter, param),formsControls(typed fullform:*control set, ~30 controls + properties + event-listener),numberFormatExtended(typednumber:*sub-elements: number, scientific-number, fraction, currency-symbol, all date/time/boolean sub-elements),metaExtended(fullmeta:*/dc:*set in<office:meta>: generator, initial-creator, creation-date, document-statistic, user-defined, keyword, editing-cycles, editing-duration, …),mathMathml(typed<math:math>passthrough + manifest wiring helpers, MathML body kept as raw XML). - Coverage extras — P2 (secondary domains):
dr3d3d(deeper typing ofdr3d:scene/cube/sphere/extrude/rotate/light),databaseSources(db:*, ~50 elements, typed passthrough via the sharedodfTypedHelper),settingsExtended(deeper typing ofconfig:config-item*),scriptMacros(typedoffice:scripts/scriptoffice:event-listeners+script:event-listener),dsigSignatures(typeddsig:document-signatures+ raw XML-DSig body preservation +META-INF/documentsignatures.xmlmanifest entry helper).
- Coverage extras — P3 (catch-all
_passthrough: true):textMisc,styleMisc,drawMisc,tableMisc,officeMisc(residualtext:*/style:*/draw:*/table:*/office:*not covered by P0/P1),legacyStaroffice(preserves any element with a StarOffice 5.x/6.x namespace prefix —so:,so20:,so52:,ooo:,ooow:,oooc:— as_legacy: true). - Bundles
*-full—@awacloud/odf/odt-full(odt-large+ the 17 odt-relevant P1/P2/P3 extras, descriptorodtFullBundle),@awacloud/odf/ods-full(ods-large+ the 16 ods-relevant P1/P2/P3 extras,odsFullBundle),@awacloud/odf/odp-full(odp-large+ the 18 odp-relevant P1/P2/P3 extras,odpFullBundle). - Shared helper factories —
odfShared(deps['odfErrors', 'xml']) centralizes the canonical ODF namespace map (ODF_NS, 24 frozen URIs),ODF_VERSION, the XML declaration variants (XML_DECL,XML_DECL_STANDALONE), the frozen MIME table, the singleton-backed UTF-8 codec (encodeText/decodeText), and the stateless helpersparseXmlOrThrow,readSidecars,writeSidecars,findDeep,intAttr,boundedIntAttr.odfWalker(deps[]) exposescreateWalker(config), the shared engine behind the three format walkers.odfMiscHelper(deps['xml']) exposesbuildMiscPassthrough(elementNames, ns);odfTypedHelperexposesbuildTypedFamily(elementNames, ns, typeTag)— both promoted from dead ESM helpers (src/extra/_misc-helper.js/_typed-helper.js, shipped but never imported) to worker-safe factories, with the legacy named exports preserved for direct importers.src/main.jsre-exports all four;odfSharedandodfWalkerare registered throughmodules[], the two helper factories throughextras[]. - Pre-built single-factory bundles — two-surface
dist/.tools/generate-bundles.mjs(bun run gen:bundles, a thin wrapper around@awacloud/tool-prebuild-generator) emits, per assembly root (9 :odt/odt-large/odt-fulland the same forods,odp), two path-discriminated surfaces side by side underdist/:dist/standalone/<root>.{js,min.js,meta.json}(dependencies: [], every fw + odf-local factory inlined, zero runtime registration) anddist/build/<root>.{js,min.js,meta.json}(declares the 6 fw modules —xml,bitstream,huffman,deflate,zip,crc32— as dependencies, inlines only the odf-local factories, smallest payload). Each.jshas a minified.min.jstwin and a.meta.jsonsidecar (fwDependencies, sourcemodules, byte sizes);dist/build/index.jsis a barrel re-exporting the whole@awacloud/odfnamespace for bulk registration on an@awacloud/fwruntime. Both surfaces expose a single factory call returning the same enriched core orchestrator as the legacy declarative*Bundledescriptors, under the resolve keys<root>Bundled(standalone) /<root>Package(build) — e.g.odtLargeBundled/odtLargePackage— unchanged from the retired generated-descriptor layout (see Removed).package.jsonexposes./build/*and./standalone/*. Documented indocs/api/bundles/prebuilt/README.md. Output is byte-deterministic (see Changed — Dist). - Documentation —
docs/README.mdtop-level index;docs/api/one page per source module (YAML frontmatter: module / category / dependencies / returns / worker-safe / status), including the pages underdocs/api/extra/*, the bundle pages (docs/api/bundles/od{t,s,p}-{large,full}.md) plus a reference for the two shared factories (docs/api/_shared/README.md);docs/guide/guides — getting-started (incl. an added security section on XML parsing, see Security below), read-write-odt, pkg-overview, read-write-ods, read-write-odp, coverage (maturity tiers). The L3 audit (P0/P1/P2/P3 breakdown) and the L3 → L4 finalisation report (15 items across risks/perf/errors/maintainability) were working documents and are not shipped; see Changed below for the net result. - Tests + integration — the suite grew from the initial 108
unit tests (14 files, 334 assertions) at L0 through the L2 (ODS/ODP
parse/render/roundtrip/error suites) and L3 (58 new unit tests across
the eight new modules, extended roundtrip + fuzz coverage, shared
test helpers exposing
tracked/shape/chart/math/forms/dr3d/mcinstances) additions to 483 pass / 0 fail across 87 files after the L3 → L4 finalisation and dedup passes. Coverage includes a roundtrip integration test wired through fw'sModuleRuntime(tests/roundtrip.integration.test.js— ODT paragraphs/headings/ lists/sections, 2-sheet ODS with formula + typed values, 3-slide ODP with placeholders/notes, tracked-changes +draw:rect/draw:custom-shape+chartChart/mathMath+anim:par+<office:forms>+dr3d:scenesurvival) and a fuzz/malformed-input suite (tests/fuzz.test.js— typedParseErroron garbage/empty/ corrupt input and bad-mimetype/missing-content.xml/write(undefined)for every module through L3). - Architecture — mirrors the architecture of
@awacloud/ooxml(same factory pattern, file layout, test/doc conventions, error hierarchy shape); layered architecture (stable L0 core with room for L1+ extensions); preserve-unknowns (every typed parser keeps unrecognised attributes/children in_extrasfor roundtrip fidelity); factory pattern ({ name, dependencies, factory }) compatible with@awacloud/fwModuleRuntimeDI; worker-safe (each factory is self-contained, no closure on mutable module-level state); browser-only (Uint8Array,TextEncoder,TextDecoderonly); zero external dependency beyond@awacloud/fw(workspace). - Package surface — sub-path exports grew across the maturity
levels: L0 shipped 3 (
.→src/main.js;./odt→.odtorchestrator;./pkg→ ODF container); L2 added./odsand./odp(awa.maturity: "L2"); L3 added./chart,./math,./form,./dr3d(awa.maturity: "L3"). The full current map (also./errors,./text,./table,./draw, the*-large/*-fullbundles,./extra/*,./bundles/*,./build/*,./standalone/*) is the README table "Exposed sub-paths". - Typed presentation capability — a
consumer can now produce a presentable
.odtwithout authoring ODF XML. (a)odfStyles.serializeaccepts typed named-style SPECS ({ name, family, displayName?, parentStyleName?, nextStyleName?, defaultOutlineLevel?, class?, properties?, _extras? }) in its three buckets next to raw elements (raw entries stay byte-identical), through the new publicodfStyles.namedStyle(spec); a missing/non-stringname/familyraisesContractError('odf/contract-error/styles');parsestays raw. (b) Body-table grid seam: atextContenttablenode'sgrid: truerenders through the newtextStyleRegistryregistry memberscellStyle({bordered: true})→awa-c-b(table-cell,fo:border0.5pt solid #000000,fo:padding0.097cm) andtableStyle({align: 'margins'})→awa-tb-m(table:align="margins"), explicitstyleNames winning; on read the new resolver memberscellBorders/tableAlignrecognise it back (honesty rule: fully mapped ornull), restoringgrid: trueand consuming the auto styles, soodt.read(odt.write({ body: [gridTable] })).autoStylesisundefined. (c) Every generatedtext:list-stylelevel (1..10, bullet and number) carries one label-alignmentstyle:list-level-propertieschild (fo:margin-left/ tab stop1.27cm…6.985cm,fo:text-indent-0.635cm) — no forced bullet font. Committeddist/**bundles regenerated. - Bounded ZIP reading —
read(bytes, opts)caps onpkgPackageand on theodt,odsandodpfacades, and the frozenpkgPackage.DEFAULT_LIMITS(see Security). - Typed images — a typed
imageparagraph run anddoc.pictures. - Slide text —
slide.slideText. - Namespace declarations —
odfShared.ODF_PREFIXES(frozen prefix to URI table),odfShared.sourceNamespaces(sourcePkg, partPath)andodfShared.declareNamespaces(rootEl, opts?);odfStyles.serialize,odfMeta.serializeandodfSettings.serializeaccept(model, opts?)withopts.namespaces;writeSidecarsforwards the source parts' declarations.
Changed
-
API reference and source comments describe each opt-in module by what it covers — internal milestone labels are removed from the
extra/module comments and the ODT guide. -
Dist — dist regenerated with the licence banner: every committed
dist/**/*.js/.min.jsopens with the row's/*! … */legal block (content fromdocs/publication/license-matrix.json), each*.meta.jsonbytesentry is measured on the final bytes, and thebuiltAttimestamp is gone —bun run gen:bundlesis now byte-deterministic. -
Documentation pass — the README now follows the published-package skeleton (every
exportskey has a row in "Exposed sub-paths", identity values read frompackage.json) and states howodt.read()resolves styles; the Quick Start and the getting-started guide registerfw_require(the former snippets omitted thexmlmodule and failed to resolveodt); the guides open with their purpose and prerequisites; the package-local working notes and audit pages are no longer part of the package, and this changelog no longer cites them. -
Package contents — the npm tarball now ships
NOTICE(dual licence + third-party attributions) next toLICENSE. -
Shared-helper deduplication — the
odfSharedandodfWalkerfactories (see Added) replaced ~210 LOC of duplicated boilerplate acrosspkg/*(mimetype constants,parseXmlOrThrow, namespaces, codec singletons),meta/settings/styles, the three.odXorchestrators, the three format walkers,chart/math, andtable/cell+table/row; a second pass throughodfMiscHelper/odfTypedHelperremoved a further ~380 LOC across 5 misc extras (textMisc,styleMisc,officeMisc,drawMisc,tableMisc) and 7 typed extras (chartTyped,animationsSmil,dr3d3d,formsControls,textSectionsAdvanced,textTocIndex,drawImageExtended).database-sources.jswas refactored to a thin shell overodfTypedHelper.buildTypedFamily(ELEMENTS, 'db:', 'db-node', { passthrough: true }). Public output APIs are unaffected — the factory signatures of the refactored modules changed (breaking for directfactory(...)callers, transparent for DI consumers registering viaruntime.register(...)):Module Old signature New signature pkgMimetypefactory(errors)factory(errors, shared)pkgManifestfactory(errors, xml)factory(errors, shared, xml)pkgPackagefactory(errors, zip, ...)factory(errors, shared, zip, ...)odfMeta/odfSettings/odfStylesfactory(errors, xml)factory(errors, shared, xml)tableCell/tableRowfactory(errors, xml)factory(errors, shared, xml)chartChart/mathMathfactory(errors, xml)factory(errors, shared, xml)odt/ods/odpfactory(errors, pkg, xml, ...)factory(errors, shared, pkg, xml, ...)odtWalker/odsWalker/odpWalkerfactory()factory(odfWalker.factory())databaseSourcesfactory(xml)factory(odfTypedHelper.factory(xml)) -
Worker-safe factories. Every factory in the package was made serializable and usable in a Worker: module-level constants (
Set/Map/regex/tables) and utility helpers previously declared outside a factory body and referenced from it were moved or inlined inside each consuming factory (36 files across the source tree). The helper modules (src/_shared/index.js,src/extra/_misc-helper.js,src/extra/_typed-helper.js) remain exported for their sibling tests, but their logic is duplicated inside every consuming factory; the error classes (ParseError/RenderError/ContractError/OdfError) stayed imported at module level to preserve the class identity expected byexpect(...).toThrow(ParseError)in tests. No regression: 483 tests passed at the end of this pass. -
Modular error handling.
errors.jsno longer exportsOdfError/ParseError/RenderError/ContractErrorat the top level — the classes are declared inside theodfErrorsfactory body, which returns{ OdfError, ParseError, RenderError, ContractError, isOdfError }. Every consumer that used the error classes now declares'odfErrors'as its first dependency and destructures the classes in its factory body (no more top-levelimport { ParseError } from '../errors.js'); as a side effect this fixed two latent bugs wherechart.js/math.jsreferencedParseErrorwithout importing it (see Fixed). The shared helpers (odfShared, insrc/_shared/index.js) receiveodfErrorsby dependency injection (factory(errors, xml)), so every helper raises the same error classes as the module that calls it. -
Bundles simplified to pure factories. The six
*-large/*-fulldescriptors (odt-large,odt-full,ods-large,ods-full,odp-large,odp-full) are now minimal fw descriptors{ name, dependencies, factory }: the factory retrieves the already-built core and extras and callscore.use(...extras), returning the enriched core. Consumption:runtime.resolve('odtLargeBundle')after registering all core modules + extras (see Removed for the dropped imperative helpers). -
Error codes by origin (breaking for
instanceofconsumers). Every parse-error site moved from the single'odf/parse-error'code to'odf/parse-error/<part>'(/odt,/ods,/odp,/manifest,/meta,/settings,/styles,/mimetype,/pkg,/chart,/math,/limit); back-compat is preserved viae.code.startsWith('odf/parse-error'). -
ContractErrorfor API/contract violations.odt.write(null),ods.write(null),odp.write(null),pkg.write({})(no mimetype),pkgMimetype.parse(<not Uint8Array>),pkgMimetype.render('')now raiseContractError('odf/contract-error/<module>', …)instead ofParseError. All stillinstanceof OdfError; a consumer relying oninstanceof ParseErrorfor these must switch toOdfErrororContractError. Sibling tests (odt/ods/odp/pkg/package/pkg/mimetype) andtests/fuzz.test.jswere updated accordingly. -
Performance.
odtWalker/odsWalker/odpWalkerindex their hooks by name instead of iterating every registered extension on each visit (index invalidated after eachuse(...), rebuilt lazily).TextEncoder/TextDecodermoved to the singleton-backed codec inodfSharedinstead of per-call allocation. -
Maintainability. New
src/_shared/folder mutualizing boilerplate across 11 modules (index.jshostingodfShared,walker.jshostingodfWalker— see above).package.json'sfilesarray explicitly excludestmp//**/tmp/**.package.jsongaineddescription,keywords,engines,sideEffects: false;awa.maturitymoved from"L3"to"L4". It now declareslicense(AGPL-3.0-only),author,repository,bugsandhomepage,LICENSEcarries the AGPL-3.0 text andNOTICEthe copyright and dual-licence statement. -
Error context. Every orchestrator
ParseError(odt,ods,odp+manifest,meta,settings,styles,pkg,mimetype) now carries acontext: { part, module, ... }(andexpectedfor mimetype mismatches);causeis preserved as-is byparseXmlOrThrow. -
xmldependency migration (internal, breaking). The localodfXmlmodule is gone (see Removed) — all ~25 consumer modules now declaredependencies: ['xml', …]and receive thexmlfactory from@awacloud/fw/io/codec/xml.jsinstead of the local clone. The 9 call sites that invokexml.parse(...)on potentially malformed input (pkgManifest,odfMeta,odfSettings,odfStyles,odt,ods,odp) translate fw'sXmlParseErrorintoParseErrorwith acause, preserving the public typed-error contract verified by fuzz tests. Consumers must register@awacloud/fw/io/codec/xml.jsin theirModuleRuntimealongside the odf modules. -
odp.toTextrenders the slides' text (text-box frames, shapes and tables; notes opt-in) instead of the slide names. -
write()round-trip.odt,odsandodpwrite()re-emit the read sidecars and parts (see Fixed). -
read()options.odt.read,ods.readandodp.readtake an optional second argument forwarded topkgPackage.read(maxParts,maxUncompressed,maxRatio). -
Image MIME type attribute.
drawImagewrites the image MIME type as the ODF 1.4draw:mime-typeattribute (it read and wrote the LibreOffice extensionloext:mime-type; both are still read). -
Documentation describes behaviour without maturity-stage tags and says what is preserved on a round-trip, including the
text:spanlimitation. -
Tests. The dist freshness test now proves, in every mode, that its byte comparisons can run.
-
Span runs gain
runs(present only when the<text:span>has an element child: its children in document order — text, spacing, nested spans, links, and every other element as the raw node at its position) and_extras.attrs(every span attribute other thantext:style-name).valueis unchanged (the flattened character data); render emitsrunswhen present.toTexthonours spacing inside spans. -
Default archive entry cap —
pkgPackage.DEFAULT_LIMITS.maxPartsis 4096 (was 1024), for every ODF package read throughpkgPackage.read(henceodt/ods/odpread): LibreOffice documents with many embedded objects carry a directory entry and several parts per object.maxUncompressedandmaxRatioare unchanged; explicit limits still override through every facade.
Removed
- Top-level error class exports (
OdfError,ParseError,RenderError,ContractError) from@awacloud/odf/@awacloud/odf/errors. Migration:const { ParseError } = runtime.resolve('odfErrors')orconst { ParseError } = odfErrors.factory(). buildOdtLarge/buildOdtFull/buildOdsLarge/buildOdsFull/buildOdpLarge/buildOdpFullimperative builder helpers. Migration: registerodtLargeBundle(etc.) in aModuleRuntimeandresolve(...)it.- Bundles no longer re-export extras individually. Migration: import
directly from
@awacloud/odf/extra/<extra-name>, or from@awacloud/odf(the main entry re-exports every extra). - The local
odfXmlmodule (its parser source, ~240 LOC, and its test file) and its API page — superseded by@awacloud/fw/io/codec/xml.js(see Changed). - The
KNOWNSetinsrc/meta/meta.js, which had no discriminant effect; the surrounding comment now documents the delegation to the opt-inmetaExtendedmodule for the rest of themeta:*/dc:*vocabulary. - The generated
prebuiltdescriptor directory undersrc/bundles/(18 descriptors) and its generator (bun run gen:prebuilds) — retired in favour of the two-surfacedist/build/+dist/standalone/convention (see Added). The exported factory names and resolve keys are unchanged, only the on-disk location and generator moved (tools/generate-bundles.mjs/bun run gen:bundles).
Fixed
-
Documentation links resolve from the npm tarball. Links that pointed outside the package, or at files the tarball does not ship, now point at the public repository at this release's tag, so they resolve from the tarball.
-
chart.js/math.jsreferencedParseErrorwithout importing it — a latent bug fixed as a side effect of wiringodfErrorsin as an explicit dependency (see Changed — Modular error handling). -
write(read(x))onodt,odsandodpno longer drops unmodelled parts and named styles: every part of the read package that the writer does not regenerate (pictures, thumbnails,Configurations2/, embedded objects) is re-emitted byte-for-byte with the media type the source manifest declared, together with the manifest's directory entries, andstyles.xml,settings.xmlandmeta.xmltravel unlessopts.*overrides them (the order isopts.*, then the read model'sdoc.*, then empty). A writer-supplied part (odtdoc.pictures) wins over the carried copy, and deletingdoc.packagedrops the carried material. -
meta:generatornames@awacloud/odfon everyodt,odsandodpwrite — a read document's previous generator is replaced — unless the caller passes an explicitopts.meta.generator. -
Every written XML part now declares each namespace prefix it uses: the image MIME type attribute, carried form controls and graphic styles, and third-party extension markup kept in
_extrasno longer produce undeclared prefixes; a prefix with no known or source declaration makeswritethrowodf/render-error/namespace. The chart and math sub-document writers (bytesOf) now declare their prefixes too. -
odp.toText/slide.slideTextnow include tables held by a frame or placed directly on the slide, and text boxes nested in groups. -
odt.toText(textContent.bodyText) now includes the text of text boxes anchored in paragraphs, headings, list items, table cells and at body level, after the line of the element that holds them. -
textListdegrades to an unstyled list when the write context has nolistStyle(and reads without numbering when it has nolistNumbering) instead of throwing aTypeError. -
Paragraph and heading attributes other than the style name (
xml:id,text:class-names,text:cond-style-name,text:is-list-header, extension attributes) survive a round-trip: they are kept in_extras.attrsand re-emitted, with their namespace prefixes declared; a heading's outline level is typed once (outlineLevel). -
Span markup survives a round-trip: spacing elements, nested spans with their own styles, links, fields and frames anchored inside a
<text:span>keep their position instead of being flattened to the span's character data. -
A LibreOffice document with more than 1024 archive entries (many embedded objects) reads with the default limits.
Security
-
ZIP decompression is bounded:
pkgPackage.read(bytes, opts?)rejects archives with more than 1024 entries (maxParts), more than 256 MiB of uncompressed content in total (maxUncompressed), or an entry whose compression ratio exceeds 200:1 (maxRatio), checked per entry before inflation;0disables a cap. A breach throwsParseError('odf/parse-error/zip-bomb')withcontext.limit, and the defaults are exposed, frozen, asDEFAULT_LIMITS. -
Added a dedicated XML-security section to
docs/guide/getting-started.mddocumenting that XXE and billion-laughs attacks are not applicable: the fw parser skips<!DOCTYPE>and limits the entity table to the 5 standard XML entities plus validated code points.