office-oxide 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- office_oxide-0.1.3/CHANGELOG.md +592 -0
- office_oxide-0.1.3/Cargo.lock +671 -0
- office_oxide-0.1.3/Cargo.toml +132 -0
- office_oxide-0.1.3/LICENSE-APACHE +176 -0
- office_oxide-0.1.3/LICENSE-MIT +25 -0
- office_oxide-0.1.3/PKG-INFO +33 -0
- office_oxide-0.1.3/README.md +451 -0
- office_oxide-0.1.3/cbindgen.toml +38 -0
- office_oxide-0.1.3/include/office_oxide_c/office_oxide.h +323 -0
- office_oxide-0.1.3/pyproject.toml +57 -0
- office_oxide-0.1.3/python/office_oxide/__init__.py +48 -0
- office_oxide-0.1.3/python/office_oxide/_native.pyi +118 -0
- office_oxide-0.1.3/python/office_oxide/py.typed +0 -0
- office_oxide-0.1.3/src/cfb/blip.rs +242 -0
- office_oxide-0.1.3/src/cfb/directory.rs +185 -0
- office_oxide-0.1.3/src/cfb/error.rs +30 -0
- office_oxide-0.1.3/src/cfb/header.rs +222 -0
- office_oxide-0.1.3/src/cfb/mod.rs +29 -0
- office_oxide-0.1.3/src/cfb/reader.rs +635 -0
- office_oxide-0.1.3/src/convert_doc.rs +63 -0
- office_oxide-0.1.3/src/convert_docx.rs +791 -0
- office_oxide-0.1.3/src/convert_ppt.rs +83 -0
- office_oxide-0.1.3/src/convert_pptx.rs +415 -0
- office_oxide-0.1.3/src/convert_xls.rs +62 -0
- office_oxide-0.1.3/src/convert_xlsx.rs +342 -0
- office_oxide-0.1.3/src/core/content_types.rs +245 -0
- office_oxide-0.1.3/src/core/core_properties.rs +162 -0
- office_oxide-0.1.3/src/core/editable.rs +156 -0
- office_oxide-0.1.3/src/core/embedded_fonts.rs +119 -0
- office_oxide-0.1.3/src/core/error.rs +56 -0
- office_oxide-0.1.3/src/core/mod.rs +38 -0
- office_oxide-0.1.3/src/core/opc.rs +692 -0
- office_oxide-0.1.3/src/core/parallel.rs +21 -0
- office_oxide-0.1.3/src/core/properties.rs +413 -0
- office_oxide-0.1.3/src/core/relationships.rs +423 -0
- office_oxide-0.1.3/src/core/theme.rs +577 -0
- office_oxide-0.1.3/src/core/traits.rs +14 -0
- office_oxide-0.1.3/src/core/units.rs +277 -0
- office_oxide-0.1.3/src/core/xml.rs +381 -0
- office_oxide-0.1.3/src/create.rs +1163 -0
- office_oxide-0.1.3/src/doc/document.rs +234 -0
- office_oxide-0.1.3/src/doc/error.rs +30 -0
- office_oxide-0.1.3/src/doc/fib.rs +171 -0
- office_oxide-0.1.3/src/doc/images.rs +178 -0
- office_oxide-0.1.3/src/doc/mod.rs +21 -0
- office_oxide-0.1.3/src/doc/piece_table.rs +371 -0
- office_oxide-0.1.3/src/docx/document.rs +29 -0
- office_oxide-0.1.3/src/docx/edit.rs +152 -0
- office_oxide-0.1.3/src/docx/error.rs +18 -0
- office_oxide-0.1.3/src/docx/formatting.rs +1129 -0
- office_oxide-0.1.3/src/docx/headers.rs +91 -0
- office_oxide-0.1.3/src/docx/hyperlink.rs +21 -0
- office_oxide-0.1.3/src/docx/image.rs +81 -0
- office_oxide-0.1.3/src/docx/mod.rs +1857 -0
- office_oxide-0.1.3/src/docx/numbering.rs +297 -0
- office_oxide-0.1.3/src/docx/paragraph.rs +54 -0
- office_oxide-0.1.3/src/docx/styles.rs +340 -0
- office_oxide-0.1.3/src/docx/table.rs +106 -0
- office_oxide-0.1.3/src/docx/text.rs +373 -0
- office_oxide-0.1.3/src/docx/write.rs +3556 -0
- office_oxide-0.1.3/src/edit.rs +233 -0
- office_oxide-0.1.3/src/error.rs +38 -0
- office_oxide-0.1.3/src/ffi.rs +1030 -0
- office_oxide-0.1.3/src/format.rs +122 -0
- office_oxide-0.1.3/src/ir.rs +1178 -0
- office_oxide-0.1.3/src/ir_from_markdown.rs +573 -0
- office_oxide-0.1.3/src/ir_render.rs +826 -0
- office_oxide-0.1.3/src/lib.rs +473 -0
- office_oxide-0.1.3/src/ppt/document.rs +307 -0
- office_oxide-0.1.3/src/ppt/error.rs +26 -0
- office_oxide-0.1.3/src/ppt/images.rs +7 -0
- office_oxide-0.1.3/src/ppt/mod.rs +22 -0
- office_oxide-0.1.3/src/ppt/records.rs +214 -0
- office_oxide-0.1.3/src/ppt/text.rs +302 -0
- office_oxide-0.1.3/src/pptx/edit.rs +141 -0
- office_oxide-0.1.3/src/pptx/error.rs +15 -0
- office_oxide-0.1.3/src/pptx/mod.rs +296 -0
- office_oxide-0.1.3/src/pptx/presentation.rs +146 -0
- office_oxide-0.1.3/src/pptx/shape.rs +239 -0
- office_oxide-0.1.3/src/pptx/slide.rs +2019 -0
- office_oxide-0.1.3/src/pptx/text.rs +814 -0
- office_oxide-0.1.3/src/pptx/write.rs +1355 -0
- office_oxide-0.1.3/src/python.rs +513 -0
- office_oxide-0.1.3/src/wasm.rs +58 -0
- office_oxide-0.1.3/src/xls/cell.rs +448 -0
- office_oxide-0.1.3/src/xls/error.rs +30 -0
- office_oxide-0.1.3/src/xls/images.rs +188 -0
- office_oxide-0.1.3/src/xls/mod.rs +23 -0
- office_oxide-0.1.3/src/xls/records.rs +183 -0
- office_oxide-0.1.3/src/xls/sst.rs +248 -0
- office_oxide-0.1.3/src/xls/workbook.rs +568 -0
- office_oxide-0.1.3/src/xlsx/cell.rs +169 -0
- office_oxide-0.1.3/src/xlsx/date.rs +386 -0
- office_oxide-0.1.3/src/xlsx/edit.rs +196 -0
- office_oxide-0.1.3/src/xlsx/error.rs +18 -0
- office_oxide-0.1.3/src/xlsx/mod.rs +1237 -0
- office_oxide-0.1.3/src/xlsx/numfmt.rs +515 -0
- office_oxide-0.1.3/src/xlsx/shared_strings.rs +284 -0
- office_oxide-0.1.3/src/xlsx/styles.rs +627 -0
- office_oxide-0.1.3/src/xlsx/text.rs +355 -0
- office_oxide-0.1.3/src/xlsx/workbook.rs +220 -0
- office_oxide-0.1.3/src/xlsx/worksheet.rs +856 -0
- office_oxide-0.1.3/src/xlsx/write.rs +1786 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.3] - 2026-07-04
|
|
9
|
+
|
|
10
|
+
> Security patch: clears every open RustSec advisory in the dependency tree — an untrusted-XML DoS (quick-xml), two pyo3 soundness/OOB fixes, and a memmap2 unsound-pointer fix — plus Linux aarch64 wheels on PyPI.
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
|
|
14
|
+
- **quick-xml 0.40 → 0.41 (RUSTSEC-2026-0194 / RUSTSEC-2026-0195)** — quick-xml 0.40's `NamespaceResolver::push` performs an unbounded per-`xmlns` heap allocation inside `NsReader` *before* the parse event is returned, so a crafted Office document (DOCX/XLSX/PPTX) declaring many namespaces could exhaust memory and crash the reader — a denial-of-service on untrusted input. Upgraded to quick-xml 0.41, which bounds the allocation. Thanks @smissingham (#58); tracked in #59.
|
|
15
|
+
- **pyo3 0.28 → 0.29 (RUSTSEC-2026-0176 / RUSTSEC-2026-0177)** — fixes an out-of-bounds read in `nth` / `nth_back` for `PyList` and `PyTuple` iterators, and a missing `Sync` bound on `PyCFunction::new_closure` closures, in the Python binding. Via Dependabot (#52).
|
|
16
|
+
- **memmap2 0.9.10 → 0.9.11 (RUSTSEC-2026-0186)** — fixes an unchecked pointer offset (unsoundness). Via Dependabot (#55).
|
|
17
|
+
|
|
18
|
+
### Packaging
|
|
19
|
+
|
|
20
|
+
- **Linux aarch64 wheels + source distribution on PyPI (#51)** — `pip install office-oxide` on Linux/arm64 (AWS Graviton, Apple-Silicon Docker, Raspberry Pi) failed with "no compatible wheel"; the release workflow now builds `manylinux` aarch64 wheels and an sdist alongside the existing x86_64/macOS wheels. Thanks @regularkevvv.
|
|
21
|
+
|
|
22
|
+
## [0.1.2] - 2026-05-14
|
|
23
|
+
|
|
24
|
+
> Round-trip fidelity, IR layout features, embedded fonts, XLSX number formatting, and an O(1) style-lookup perf win.
|
|
25
|
+
|
|
26
|
+
### Performance
|
|
27
|
+
|
|
28
|
+
- **XLSX styles**: cell-format lookups now use a `HashMap`, replacing
|
|
29
|
+
the linear `Vec` scan in `format_cell_value` / `is_date_cell`.
|
|
30
|
+
Per-cell formatting becomes O(1); large styled workbooks parse
|
|
31
|
+
noticeably faster with no API change.
|
|
32
|
+
|
|
33
|
+
### Round-trip fidelity (PDF → office → PDF)
|
|
34
|
+
|
|
35
|
+
- **Alignment, spacing, footers, and horizontal rules** preserved end-to-end
|
|
36
|
+
through both `to_docx` and `to_pptx` writers.
|
|
37
|
+
- **Images, fonts, and column layouts** preserved across DOCX, PPTX, and
|
|
38
|
+
XLSX. Source-PDF font programs that previously registered as empty
|
|
39
|
+
subsets now embed correctly.
|
|
40
|
+
- **`Element::ThematicBreak`** encoded in PPTX as a centered 30-char run
|
|
41
|
+
of `U+2500 BOX DRAWINGS LIGHT HORIZONTAL`. Downstream PDF renderers
|
|
42
|
+
detect the all-U+2500 content and re-emit a real horizontal rule.
|
|
43
|
+
- **DOCX horizontal rules** recovered from the conventional encoding
|
|
44
|
+
(empty paragraph + `<w:pBdr><w:bottom/>`) back into `Element::ThematicBreak`.
|
|
45
|
+
|
|
46
|
+
### DOCX
|
|
47
|
+
|
|
48
|
+
- **`<w:framePr>` parsed into IR** as `FramePosition` (twips, page-anchored)
|
|
49
|
+
on both `Paragraph` and `Heading`. Used by layout-preserving paths
|
|
50
|
+
(e.g. pdf_oxide's `to_docx_bytes_layout`).
|
|
51
|
+
- **Floating drawings and vector shapes**: `<wp:anchor>` images plus
|
|
52
|
+
`<wps:wsp>` preset shapes (line, rect) with stroke/fill RGB and
|
|
53
|
+
stroke width round-trip through `DrawingInfo`.
|
|
54
|
+
- **Per-section page sizes** preserved through `to_ir`; multi-section IR
|
|
55
|
+
emits per-section `<w:sectPr>`.
|
|
56
|
+
- **`<w:sz>` preserved** through to IR's `font_size_half_pt`.
|
|
57
|
+
- **Run colour** from `<w:rPr><w:color w:val="RRGGBB"/>` propagated into
|
|
58
|
+
`TextSpan.color` during `to_ir`, so PDF→DOCX→PDF round-trips keep
|
|
59
|
+
coloured text. Only the `ColorRef::Rgb` variant is plumbed today;
|
|
60
|
+
theme / system / `auto` colours still fall through to the renderer
|
|
61
|
+
default (proper resolution needs `theme.xml` threaded into the
|
|
62
|
+
convert path).
|
|
63
|
+
- **Headers and footers** now included in `to_markdown` and `to_ir`
|
|
64
|
+
(previously silently dropped).
|
|
65
|
+
- **Embedded fonts** under `/word/fonts/` exposed on
|
|
66
|
+
`DocxDocument.embedded_fonts`. `strip_embedded_font_filename` recovers
|
|
67
|
+
the original face name from `font_<n>_<face>.<ext>` (fixes greedy
|
|
68
|
+
alphabetic-trim regression where `TeXGyreTermesX-` was returned
|
|
69
|
+
instead of `TeXGyreTermesX-Regular`).
|
|
70
|
+
- **`parse_drawing` decomposed** into focused recursive helpers
|
|
71
|
+
(`parse_inline_or_anchor_body`, `parse_anchor_position`,
|
|
72
|
+
`parse_shape_properties`, etc.) for readability.
|
|
73
|
+
- **Run-level `<w:rFonts w:ascii>` plumbed into `TextSpan.font_name`**;
|
|
74
|
+
`<w:cols>` propagated to `Section.columns`.
|
|
75
|
+
|
|
76
|
+
### PPTX
|
|
77
|
+
|
|
78
|
+
- **Pagination**: each slide forces a `SectionBreakType::NextPage` so two
|
|
79
|
+
slides never share a rendered page.
|
|
80
|
+
- **Real Title+Body slide layout** emitted by the writer instead of a blank
|
|
81
|
+
layout, so PowerPoint shows placeholder hints in edit mode.
|
|
82
|
+
- **Slide background**: `<p:cSld><p:bg><p:bgPr><a:solidFill><a:srgbClr>`
|
|
83
|
+
parsed into `Slide.background_rgb` and propagated to `Section.background_rgb`.
|
|
84
|
+
- **Positioned text boxes**: shapes with explicit `<a:xfrm>` coordinates
|
|
85
|
+
wrap their content in `Element::TextBox` so downstream renderers can
|
|
86
|
+
place them at absolute EMU coordinates. Zero-size shapes skip the wrapper.
|
|
87
|
+
- **Slide size → page setup**: `<p:sldSz cx=… cy=…>` propagated to each
|
|
88
|
+
section's `PageSetup`.
|
|
89
|
+
- **Run font sizes preserved** via new `TextRun.font_size_hundredths_pt`
|
|
90
|
+
(parsed from `<a:rPr sz="…"/>`).
|
|
91
|
+
- **Run colour preserved** via new `TextRun.color_rgb: Option<[u8; 3]>`
|
|
92
|
+
parsed from `<a:rPr><a:solidFill><a:srgbClr val="RRGGBB"/></a:solidFill>`
|
|
93
|
+
and propagated to `TextSpan.color` in IR. The parser tracks an
|
|
94
|
+
`in_solid_fill` flag so sibling effects (e.g. `<a:hl><a:srgbClr/>`
|
|
95
|
+
for hyperlink colour) don't leak into the run's own fill; non-sRGB
|
|
96
|
+
fills (gradient, scheme colour) fall back to `None`.
|
|
97
|
+
- **Paragraph alignment** parsed from `<a:pPr algn="…"/>` (all five
|
|
98
|
+
variants: `l` / `ctr` / `r` / `just` / `dist`) into
|
|
99
|
+
`TextParagraph.alignment`. **Space-before** parsed from
|
|
100
|
+
`<a:spcBef><a:spcPts val=…/>`.
|
|
101
|
+
- **Title alignment propagation**: `find_title` returns text + first
|
|
102
|
+
paragraph's alignment, seeding both `Section.title` and the synthesised
|
|
103
|
+
level-2 Heading's alignment.
|
|
104
|
+
- **Picture shapes** now carry `embed_rid`, `data`, and `format`
|
|
105
|
+
(resolved via a pre-built media map at parse time, so the parallel
|
|
106
|
+
slide parser doesn't need the OPC reader).
|
|
107
|
+
- **Font embedding** under `/ppt/fonts/`.
|
|
108
|
+
- **Structured chart text extraction**: `<c:chart>` parts parsed into
|
|
109
|
+
per-chart text blocks rendered as `## Chart N` in markdown / search /
|
|
110
|
+
PDF without needing a graphical chart renderer.
|
|
111
|
+
- **Compaction**: consecutive H1/H2 cover-page headings fold into one
|
|
112
|
+
slide instead of fragmenting; long XLSX paragraphs split across cells
|
|
113
|
+
to respect ~32k char-per-cell limits.
|
|
114
|
+
- **Slide cap**: writer caps at ~250 slides (PowerPoint's hard limit).
|
|
115
|
+
|
|
116
|
+
### XLSX
|
|
117
|
+
|
|
118
|
+
- **Per-worksheet `page_setup`** round-trips via `<pageMargins>` (inches)
|
|
119
|
+
and `<pageSetup>` (paperWidth/paperHeight with mm/cm/in suffix or
|
|
120
|
+
`paperSize` enum 1–13). New `Worksheet.page_setup`.
|
|
121
|
+
- **`numfmt` module** (`crate::xlsx::numfmt`): built-in IDs 0–44 (general,
|
|
122
|
+
fixed, commas, percent, currency, scientific, accounting) and a
|
|
123
|
+
simplified custom format-string parser (multi-section, `[Red]` color
|
|
124
|
+
directives stripped, currency prefix from `[$€-407]`, quoted literal
|
|
125
|
+
suffix, percent and thousands separators). Applied to numeric cells
|
|
126
|
+
during `format_cell_value` and `write_cell_value_fast`.
|
|
127
|
+
- **Font sizes** preserved through IR; long-text single-column sheets
|
|
128
|
+
emit as paragraphs instead of a tall 1-column GFM table.
|
|
129
|
+
- **Unique worksheet names** in `ir_to_xlsx` (duplicates suffixed with
|
|
130
|
+
`_2`, `_3`, …).
|
|
131
|
+
- **Drawings**: `xl/drawings/drawingN.xml` parsed into
|
|
132
|
+
`Worksheet.images` (`WorksheetPicture` with EMU coords + bytes) and
|
|
133
|
+
`Worksheet.text_shapes` (`WorksheetTextShape` for layout-mode text
|
|
134
|
+
boxes from `to_xlsx_bytes_layout`).
|
|
135
|
+
- **Embedded fonts** under `/xl/fonts/`.
|
|
136
|
+
|
|
137
|
+
### IR enrichment
|
|
138
|
+
|
|
139
|
+
- **New types**: `Shape` (vector shape anchored at absolute EMU coords),
|
|
140
|
+
`ShapeGeom` (`Line`, `Rect`), `FramePosition` (twip-anchored frame).
|
|
141
|
+
- **`Heading`** gains `frame_position` + `alignment`.
|
|
142
|
+
- **`Section`** gains `background_rgb`.
|
|
143
|
+
- **`ParagraphAlignment`** gains the `Distribute` variant.
|
|
144
|
+
- **`Element::Shape(Shape)`** variant for vector shapes.
|
|
145
|
+
- **New helpers**: `first_inline_font_size_pt`, `inline_to_element_block`,
|
|
146
|
+
`build_nested_list` (flat / 2-level / 3-level recursion).
|
|
147
|
+
- **Centralized defaults** in `ir_render::block_default`: ThematicBreak
|
|
148
|
+
renders as `"---"` / `<hr />`; PageBreak / ColumnBreak / Shape are
|
|
149
|
+
invisible in flow; TextBox / Footnote / Endnote recursively render
|
|
150
|
+
children. Adding a new `Element` variant forces a compile error
|
|
151
|
+
in `block_default::default_plain` instead of silent fallthrough.
|
|
152
|
+
|
|
153
|
+
### Core
|
|
154
|
+
|
|
155
|
+
- **`crate::core::core_properties`**: shared `docProps/core.xml` generator
|
|
156
|
+
used by all three writers. Emits `dc:title`, `dc:creator`, `dc:subject`,
|
|
157
|
+
`dc:description`, `cp:keywords`, `dcterms:created`, `dcterms:modified`
|
|
158
|
+
from the IR's `Metadata`. Empty fields are omitted entirely.
|
|
159
|
+
- **`crate::core::embedded_fonts`**: unified font-embedding helper
|
|
160
|
+
(`write_embedded_fonts`, `sanitize_font_filename`). All three formats
|
|
161
|
+
share the layout `<prefix>font_<n>_<safe_name>.ttf`.
|
|
162
|
+
- **`HalfPoint::from_word_sz` / `from_drawingml_sz` / `to_drawingml_sz` /
|
|
163
|
+
`from_points_rounded`**: cross-format font-size invariants
|
|
164
|
+
(DrawingML hundredths-of-a-point vs WML half-points).
|
|
165
|
+
|
|
166
|
+
### Dependencies
|
|
167
|
+
|
|
168
|
+
- **`quick-xml` 0.37 → 0.40**: upstream removed `BytesText::unescape()`
|
|
169
|
+
and deprecated `Attribute::unescape_value()` (its replacement
|
|
170
|
+
`normalized_value()` has different semantics — no entity
|
|
171
|
+
unescaping). Migration added two helpers in `core::xml`:
|
|
172
|
+
`unescape_text(BytesText) -> Result<String>` (used by 6 call sites)
|
|
173
|
+
and `unescape_attr_value` (used by 6 call sites, with
|
|
174
|
+
`#[allow(deprecated)]` localised to the helper so call sites stay
|
|
175
|
+
deprecation-free). 535 / 535 tests still pass; clippy clean.
|
|
176
|
+
- **`koffi` 2.16.1 → 2.16.2** in `js/` (patch bump).
|
|
177
|
+
|
|
178
|
+
### Documentation
|
|
179
|
+
|
|
180
|
+
- **CLI / MCP crate-level docs**: `office_oxide_cli` and
|
|
181
|
+
`office_oxide_mcp` previously opened with `mod commands;` /
|
|
182
|
+
`mod protocol;` and had no crate-level rustdoc. Added a short
|
|
183
|
+
`//!` block plus `#![warn(missing_docs)]` so future items in
|
|
184
|
+
either binary stay documented.
|
|
185
|
+
`RUSTDOCFLAGS="-D missing_docs" cargo doc --workspace --no-deps
|
|
186
|
+
--features parallel,mmap` now passes with zero errors.
|
|
187
|
+
|
|
188
|
+
### Tests
|
|
189
|
+
|
|
190
|
+
- **+98 unit tests** across the modules touched in this release:
|
|
191
|
+
`core::embedded_fonts`, `core::core_properties`, `core::units`,
|
|
192
|
+
`xlsx::numfmt`, `xlsx::worksheet`, `docx::formatting`, `docx::mod`,
|
|
193
|
+
`pptx::slide`, `ir`, `ir_render`.
|
|
194
|
+
- **535 / 535 tests pass** across default, `--features parallel`,
|
|
195
|
+
`--features mmap`, and `--features parallel,mmap` builds.
|
|
196
|
+
- `cargo fmt` clean. `cargo clippy --workspace --all-targets -- -D warnings`
|
|
197
|
+
clean.
|
|
198
|
+
|
|
199
|
+
### Bindings
|
|
200
|
+
|
|
201
|
+
- **Python wheel** (maturin, PyO3 0.28) builds cleanly and exposes
|
|
202
|
+
`Document`, `EditableDocument`, `XlsxWriter`, `PptxWriter`,
|
|
203
|
+
`OfficeOxideError`, `create_from_markdown`, `extract_text`,
|
|
204
|
+
`to_markdown`, `to_html`, `version`.
|
|
205
|
+
- **WASM** package (`wasm-pack build --target web/node/bundler`) builds
|
|
206
|
+
cleanly with `--features wasm`.
|
|
207
|
+
- **C#** package bumped to 0.1.2 (csproj only — no API changes).
|
|
208
|
+
|
|
209
|
+
[0.1.2]: https://github.com/yfedoseev/office_oxide/compare/v0.1.1...v0.1.2
|
|
210
|
+
|
|
211
|
+
## [0.1.1] - 2026-04-30
|
|
212
|
+
|
|
213
|
+
> Richer IR type system, DOCX writer output, improved PPTX/XLSX IR renderers, and writer APIs in all language bindings
|
|
214
|
+
|
|
215
|
+
### IR — extended type system
|
|
216
|
+
|
|
217
|
+
- **`TextSpan`** gains nine typography fields: `font_name`, `font_size_half_pt`,
|
|
218
|
+
`color`, `highlight`, `underline` (`UnderlineStyle` enum), `vertical_align`
|
|
219
|
+
(`VerticalAlign`: Superscript / Subscript / Baseline), `all_caps`,
|
|
220
|
+
`small_caps`, `char_spacing_half_pt`.
|
|
221
|
+
- **`Paragraph`** gains twelve layout fields: `alignment` (`ParagraphAlignment`:
|
|
222
|
+
Left / Center / Right / Justify / Distribute), `indent_left_twips`,
|
|
223
|
+
`indent_right_twips`, `first_line_indent_twips`, `space_before_twips`,
|
|
224
|
+
`space_after_twips`, `line_spacing` (`LineSpacing`: Auto / Multiple / Exact /
|
|
225
|
+
AtLeast), `background_color`, `border` (`ParagraphBorder`), `keep_with_next`,
|
|
226
|
+
`keep_together`, `page_break_before`, `outline_level`.
|
|
227
|
+
- **`Table`** gains `width_twips`, `column_widths_twips`, `border`
|
|
228
|
+
(`TableBorder`), `alignment` (`TableAlignment`), `indent_left_twips`,
|
|
229
|
+
`cell_padding_twips`, `caption`.
|
|
230
|
+
- **`TableRow`** gains `height_twips`, `allow_break`, `repeat_as_header`.
|
|
231
|
+
- **`TableCell`** gains `background_color`, `border`, `vertical_align`
|
|
232
|
+
(`CellVerticalAlign`), `text_align`, `width_twips`, `padding` (`CellPadding`),
|
|
233
|
+
`text_direction` (`TextDirection`).
|
|
234
|
+
- **`Image`** gains `data`, `format` (`ImageFormat`), `pixel_width`,
|
|
235
|
+
`pixel_height`, `display_width_emu`, `display_height_emu`, `decorative`,
|
|
236
|
+
`positioning` (`ImagePositioning`: Inline or Floating with `FloatingImage`).
|
|
237
|
+
- **`Section`** gains `page_setup` (`PageSetup`), `columns` (`ColumnLayout`),
|
|
238
|
+
`break_type` (`SectionBreakType`), and six header/footer slots
|
|
239
|
+
(`header`, `footer`, `first_page_header`, `first_page_footer`,
|
|
240
|
+
`even_page_header`, `even_page_footer`).
|
|
241
|
+
- **`Metadata`** gains `author`, `subject`, `keywords`, `created`, `modified`,
|
|
242
|
+
`description` (written to `docProps/core.xml`).
|
|
243
|
+
- **New `Element` variants**: `TextBox`, `PageBreak`, `ColumnBreak`,
|
|
244
|
+
`Footnote(Note)`, `Endnote(Note)`, `CodeBlock`.
|
|
245
|
+
- **`List`** gains `start_number`, `style` (`ListStyle`), `level`.
|
|
246
|
+
`ListItem.content` promoted from `Vec<InlineContent>` to `Vec<Element>`
|
|
247
|
+
to allow block-level content (tables, images) inside list items.
|
|
248
|
+
- **`InlineContent`** gains `FootnoteRef` and `EndnoteRef` variants.
|
|
249
|
+
- **New supporting types**: `BorderLine`, `TableBorder`, `ParagraphBorder`,
|
|
250
|
+
`CellPadding`, `FloatingImage`, `HeaderFooter`, `TextBox`, `Note`,
|
|
251
|
+
`FootnoteRef`, `CodeBlock`, `PageSetup`, `ColumnLayout`.
|
|
252
|
+
- **New enums**: `UnderlineStyle`, `ParagraphAlignment`, `LineSpacing`,
|
|
253
|
+
`BorderStyle`, `CellVerticalAlign`, `TableAlignment`, `TextDirection`,
|
|
254
|
+
`ImageFormat`, `ImagePositioning`, `SectionBreakType`, `VerticalAlign`,
|
|
255
|
+
`FloatAnchor`, `TextWrap`, `ListStyle`.
|
|
256
|
+
- All new fields are `Option<_>` or default to `false`/`None` — fully
|
|
257
|
+
backwards compatible; existing callers require only `..Default::default()`
|
|
258
|
+
on struct literals.
|
|
259
|
+
|
|
260
|
+
### DocxWriter — OOXML emission for all new fields
|
|
261
|
+
|
|
262
|
+
- **Run properties**: `<w:rFonts>`, `<w:sz>`/`<w:szCs>`, `<w:color>`,
|
|
263
|
+
`<w:shd>` (highlight), `<w:u>`, `<w:vertAlign>`, `<w:caps>`,
|
|
264
|
+
`<w:smallCaps>`, `<w:spacing>` (character spacing).
|
|
265
|
+
- **Paragraph properties**: `<w:jc>`, `<w:ind>`, `<w:spacing>` (before/after/
|
|
266
|
+
line), `<w:pBdr>`, `<w:shd>`, `<w:keepNext>`, `<w:keepLines>`,
|
|
267
|
+
`<w:pageBreakBefore>`, `<w:outlineLvl>`.
|
|
268
|
+
- **Table**: `<w:tblW>`, `<w:tblInd>`, `<w:tblBorders>`, `<w:jc>` (table),
|
|
269
|
+
`<w:tblCellMar>`, `<w:tblGrid>`/`<w:gridCol>`.
|
|
270
|
+
- **Table row**: `<w:trHeight>`, `<w:tblHeader>`, `<w:cantSplit>`.
|
|
271
|
+
- **Table cell**: `<w:tcW>`, `<w:gridSpan>`, `<w:vMerge>`, `<w:shd>` (cell),
|
|
272
|
+
`<w:tcBorders>`, `<w:vAlign>`, `<w:textDirection>`, `<w:tcMar>` (per-edge
|
|
273
|
+
padding), cell-level `text_align` propagated to contained paragraphs.
|
|
274
|
+
- **Table caption**: emitted as a `Caption`-styled paragraph before `<w:tbl>`.
|
|
275
|
+
- **Images**: inline `<wp:inline>` and floating `<wp:anchor>` with
|
|
276
|
+
`<wp:wrapSquare>` / `<wp:wrapTight>` / `<wp:wrapThrough>` /
|
|
277
|
+
`<wp:wrapTopAndBottom>` / `<wp:wrapNone>`.
|
|
278
|
+
- **Text boxes**: `<wp:anchor>` + `<wps:txbx>` + `<w:txbxContent>`.
|
|
279
|
+
- **Sections**: `<w:sectPr>` with `<w:pgSz>`, `<w:pgMar>`, `<w:cols>` (uniform
|
|
280
|
+
and per-column widths with separator rule), `<w:type>` (Continuous / NextPage
|
|
281
|
+
/ EvenPage / OddPage). Header/footer parts written to `/word/header*.xml` and
|
|
282
|
+
`/word/footer*.xml` with correct relationship entries.
|
|
283
|
+
- **Footnotes / endnotes**: `/word/footnotes.xml` and `/word/endnotes.xml`
|
|
284
|
+
parts; inline `<w:footnoteReference>` / `<w:endnoteReference>` runs with
|
|
285
|
+
`FootnoteReference` / `EndnoteReference` character styles.
|
|
286
|
+
- **`{PAGE}` / `{NUMPAGES}` sentinels** in header/footer text spans emitted as
|
|
287
|
+
`<w:fldChar>` / `<w:instrText>` field runs.
|
|
288
|
+
- **Page break**: `<w:br w:type="page">`. **Column break**: `<w:br
|
|
289
|
+
w:type="column">`.
|
|
290
|
+
- **Code blocks**: `Code`-styled paragraph with preserved whitespace and
|
|
291
|
+
line breaks.
|
|
292
|
+
- **Lists**: `<w:numFmt>` driven by `ListStyle`; `<w:startOverride>` for
|
|
293
|
+
non-1 `start_number`; block-level item content (tables, images) written
|
|
294
|
+
alongside the numbered paragraph.
|
|
295
|
+
- **Metadata**: `author`, `subject`, `keywords`, `description`, `created`,
|
|
296
|
+
`modified` written to `docProps/core.xml` as Dublin Core properties.
|
|
297
|
+
|
|
298
|
+
### PPTX IR renderer — `ir_to_pptx`
|
|
299
|
+
|
|
300
|
+
- **Rich text runs**: paragraphs and headings now emit styled `<a:r>` runs via
|
|
301
|
+
`add_rich_text()` — bold, italic, font size, color, and font name from
|
|
302
|
+
`TextSpan` are all preserved. Previously all formatting was stripped.
|
|
303
|
+
- **Table elements**: rendered as tab-separated cell text (rows joined with `\n`)
|
|
304
|
+
instead of being silently dropped.
|
|
305
|
+
- **Image elements**: embedded as native PPTX media via the new
|
|
306
|
+
`SlideData::add_image()` API — writes a `<p:pic>` shape with `<p:blipFill>`
|
|
307
|
+
and an OPC media part. PNG, JPEG, and GIF are supported.
|
|
308
|
+
- **CodeBlock elements**: rendered with Courier New font run instead of being
|
|
309
|
+
silently dropped.
|
|
310
|
+
- **Slide dimensions**: first `Section.page_setup` is now forwarded to
|
|
311
|
+
`PptxWriter::set_presentation_size()` (1 twip = 914 400/1 440 EMU), fixing
|
|
312
|
+
clipped output for landscape A4 and other non-16:9 documents.
|
|
313
|
+
- **New `PptxWriter::set_presentation_size(cx, cy)`** method; emits `<p:sldSz>`
|
|
314
|
+
with the correct EMU values instead of always writing the 16:9 default.
|
|
315
|
+
|
|
316
|
+
### XLSX IR renderer — `ir_to_xlsx`
|
|
317
|
+
|
|
318
|
+
- **Header row styling**: rows with `TableRow.is_header = true` are now written
|
|
319
|
+
with bold weight and a grey (`D3D3D3`) background via `set_cell_styled()`.
|
|
320
|
+
`TableCell.background_color` overrides the default grey when set.
|
|
321
|
+
- **Cell background color**: non-header cells with `TableCell.background_color`
|
|
322
|
+
set now get a solid fill style applied.
|
|
323
|
+
- **Column widths**: `Table.column_widths_twips` is now converted to Excel
|
|
324
|
+
character-width units (`twips × 96 / (1440 × 7)`, clamped 3–80) and written
|
|
325
|
+
via `set_column_width()`.
|
|
326
|
+
- **Merged cells**: `TableCell.col_span` and `row_span` > 1 now emit a
|
|
327
|
+
`<mergeCells>/<mergeCell ref="…"/>` block in the worksheet XML instead of
|
|
328
|
+
being ignored.
|
|
329
|
+
- **New `SheetData::merge_cells(row, col, row_span, col_span)`** method; inserts
|
|
330
|
+
`<mergeCells>` between `</sheetData>` and `</worksheet>`.
|
|
331
|
+
- Row cursor tracks absolute position across all elements in a section, so
|
|
332
|
+
paragraphs and headings interleaved with tables land in the correct rows.
|
|
333
|
+
|
|
334
|
+
### Writer APIs — all language bindings
|
|
335
|
+
|
|
336
|
+
`XlsxWriter` and `PptxWriter` (previously Rust-only) are now callable from
|
|
337
|
+
every binding layer via a new index-based C FFI surface:
|
|
338
|
+
|
|
339
|
+
**New C FFI symbols** (`include/office_oxide_c/office_oxide.h`):
|
|
340
|
+
|
|
341
|
+
- `office_xlsx_writer_new/free`, `office_xlsx_writer_add_sheet` (returns sheet
|
|
342
|
+
index), `office_xlsx_sheet_set_cell`, `office_xlsx_sheet_set_cell_styled`
|
|
343
|
+
(bold + hex background), `office_xlsx_sheet_merge_cells`,
|
|
344
|
+
`office_xlsx_sheet_set_column_width`, `office_xlsx_writer_save`,
|
|
345
|
+
`office_xlsx_writer_to_bytes`
|
|
346
|
+
- `office_pptx_writer_new/free`, `office_pptx_writer_set_presentation_size`,
|
|
347
|
+
`office_pptx_writer_add_slide` (returns slide index),
|
|
348
|
+
`office_pptx_slide_set_title`, `office_pptx_slide_add_text`,
|
|
349
|
+
`office_pptx_slide_add_image` (PNG/JPEG/GIF bytes + EMU geometry),
|
|
350
|
+
`office_pptx_writer_save`, `office_pptx_writer_to_bytes`
|
|
351
|
+
|
|
352
|
+
**Go** — `XlsxWriter` and `PptxWriter` structs with CGo wrappers and
|
|
353
|
+
`runtime.SetFinalizer` for safe GC.
|
|
354
|
+
|
|
355
|
+
**C# / .NET** — new `OfficeOxide.XlsxWriter` and `OfficeOxide.PptxWriter`
|
|
356
|
+
classes (`IDisposable`); P/Invoke declarations added to `NativeMethods`.
|
|
357
|
+
|
|
358
|
+
**Node.js** — `XlsxWriter` and `PptxWriter` ESM + CJS classes; koffi
|
|
359
|
+
function prototypes; TypeScript `ImageFormat` type and class declarations.
|
|
360
|
+
|
|
361
|
+
**Python** — `XlsxWriter` and `PyO3PptxWriter` PyO3 classes calling Rust
|
|
362
|
+
directly (no C FFI round-trip); exported from `office_oxide` with full type
|
|
363
|
+
stubs in `_native.pyi`.
|
|
364
|
+
|
|
365
|
+
### Bug fixes
|
|
366
|
+
|
|
367
|
+
- `TableCell.padding` (`CellPadding`) was defined in the IR but silently
|
|
368
|
+
dropped by the writer; now emits `<w:tcMar>` with per-edge twip values.
|
|
369
|
+
- `TableCell.text_align` was defined in the IR but silently dropped; now
|
|
370
|
+
propagated to contained paragraphs (respects pre-existing paragraph
|
|
371
|
+
alignment, so explicit paragraph alignment is never overwritten).
|
|
372
|
+
- `Table.caption` was defined in the IR but silently dropped; now emitted
|
|
373
|
+
as a `Caption`-styled paragraph immediately before the table.
|
|
374
|
+
|
|
375
|
+
## [0.1.0] - 2026-04-27
|
|
376
|
+
|
|
377
|
+
> Initial public release
|
|
378
|
+
|
|
379
|
+
### Cross-language bindings
|
|
380
|
+
|
|
381
|
+
- **Rust core** (`office_oxide` on crates.io): unified `Document` handle for
|
|
382
|
+
all six formats, `EditableDocument` for DOCX/XLSX/PPTX editing, format-
|
|
383
|
+
agnostic `DocumentIR`.
|
|
384
|
+
- **Python** (`office-oxide` on PyPI): context-manager `Document` /
|
|
385
|
+
`EditableDocument`, `os.PathLike` support, complete type stubs in
|
|
386
|
+
`_native.pyi` (`Literal` format names, `_Path` alias).
|
|
387
|
+
- **Go** (`github.com/yfedoseev/office_oxide/go`): CGo wrapper over the C FFI
|
|
388
|
+
with idiomatic `Open` / `Close` / error-return API, `go/cmd/install` helper
|
|
389
|
+
that fetches the matching native archive and prints the
|
|
390
|
+
`CGO_CFLAGS` / `CGO_LDFLAGS` to export.
|
|
391
|
+
- **C# / .NET** (`OfficeOxide` on NuGet): `LibraryImport` P/Invoke,
|
|
392
|
+
`IDisposable`, `async/await`, `IsAotCompatible=true`, `IsTrimmable=true`.
|
|
393
|
+
Four `SetCell` overloads + `SetCellEmpty`. Net 8 and net 10 target
|
|
394
|
+
frameworks.
|
|
395
|
+
- **Node.js native** (`office-oxide` on npm): [koffi](https://koffi.dev)-based,
|
|
396
|
+
no node-gyp, ESM + CJS entry points with an `exports` map, TypeScript
|
|
397
|
+
definitions, `Symbol.dispose` support, platform prebuilds staged into
|
|
398
|
+
`prebuilds/<platform>-<arch>/`.
|
|
399
|
+
- **WASM** (`office-oxide-wasm` on npm): three sub-path exports — default
|
|
400
|
+
ESM for bundlers, `office-oxide-wasm/node` for CJS, `office-oxide-wasm/web`
|
|
401
|
+
for native-ESM browser imports. TypeScript definitions shipped.
|
|
402
|
+
- **C FFI** (`include/office_oxide_c/office_oxide.h`): stable
|
|
403
|
+
`office_document_*` / `office_editable_*` surface with out-param error
|
|
404
|
+
codes and explicit memory ownership. Exported from the cdylib + staticlib;
|
|
405
|
+
the substrate that Go, C#, and Node-native link against.
|
|
406
|
+
|
|
407
|
+
### Tooling
|
|
408
|
+
|
|
409
|
+
- **CLI** (`office-oxide` binary): `text`, `markdown`, `html`, `info`, `ir`
|
|
410
|
+
subcommands.
|
|
411
|
+
- **MCP server** (`office-oxide-mcp` binary): `extract` and `info` tools
|
|
412
|
+
over JSON-RPC 2.0 / stdio.
|
|
413
|
+
|
|
414
|
+
### Performance
|
|
415
|
+
|
|
416
|
+
- Up to 100× faster than `python-docx`, `openpyxl`, `python-pptx`, `xlrd`.
|
|
417
|
+
- Beats `calamine` on XLSX and all Rust / Python alternatives on .xls.
|
|
418
|
+
- **100% pass rate on valid Office files** (6,062-file corpus: LibreOffice,
|
|
419
|
+
Apache POI, python-pptx, python-docx, Pandoc, etc.). All 97 non-passing
|
|
420
|
+
files are invalid inputs — corrupted ZIPs, missing required parts, malformed
|
|
421
|
+
XML, or non-Office files with Office extensions.
|
|
422
|
+
|
|
423
|
+
### Documentation & examples
|
|
424
|
+
|
|
425
|
+
- Per-language getting-started guides in [`docs/`](docs/): Rust, Python,
|
|
426
|
+
Go, C#, JavaScript (native), WASM, and C / raw FFI.
|
|
427
|
+
- Per-binding READMEs in [`python/`](python/README.md), [`go/`](go/README.md),
|
|
428
|
+
[`csharp/OfficeOxide/`](csharp/OfficeOxide/README.md),
|
|
429
|
+
[`js/`](js/README.md), [`wasm-pkg/`](wasm-pkg/README.md).
|
|
430
|
+
- Identical `extract` / `replace` / `read_xlsx` demos per language under
|
|
431
|
+
[`examples/`](examples/).
|
|
432
|
+
|
|
433
|
+
### Release CI
|
|
434
|
+
|
|
435
|
+
- Version parity across `Cargo.toml`, `pyproject.toml`,
|
|
436
|
+
`wasm-pkg/package.json`, `js/package.json`, and
|
|
437
|
+
`csharp/OfficeOxide/OfficeOxide.csproj`.
|
|
438
|
+
- 6-target native-lib build matrix producing `.tar.gz` / `.zip` archives
|
|
439
|
+
with the shared library, static archive, and public header.
|
|
440
|
+
- 3-target WASM build (bundler / nodejs / web) with per-target module-type
|
|
441
|
+
hints so Node + bundlers + browsers each load the right code.
|
|
442
|
+
- NuGet packaging with `runtimes/<rid>/native/` prebuilts, Node
|
|
443
|
+
`prebuilds/<platform>-<arch>/` staging, and a `go/v*` module tag for the
|
|
444
|
+
Go module proxy.
|
|
445
|
+
|
|
446
|
+
## [0.1.0-draft] - 2026-04-07 (not released)
|
|
447
|
+
|
|
448
|
+
> Internal milestone before the public release above.
|
|
449
|
+
|
|
450
|
+
Single consolidated `office_oxide` crate with modules per format, plus
|
|
451
|
+
companion workspace crates `office_oxide_cli` and `office_oxide_mcp`.
|
|
452
|
+
|
|
453
|
+
### Added
|
|
454
|
+
|
|
455
|
+
- **Unified API** (`crate::Document`)
|
|
456
|
+
- `Document::open()`, `Document::from_reader()`, `plain_text()`,
|
|
457
|
+
`to_markdown()`, `to_html()`, `to_ir()`, `format_name()`
|
|
458
|
+
- Format auto-detection from file extension for all 6 formats
|
|
459
|
+
- `as_docx()` / `as_xlsx()` / `as_pptx()` / `as_doc()` / `as_xls()` /
|
|
460
|
+
`as_ppt()` escape hatches for format-specific types
|
|
461
|
+
- Convenience functions: `extract_text()`, `to_markdown()`, `to_html()`
|
|
462
|
+
at crate root
|
|
463
|
+
|
|
464
|
+
- **Format-agnostic IR** (`crate::ir::DocumentIR`)
|
|
465
|
+
- Sections, Elements (Heading, Paragraph, Table, List, Image,
|
|
466
|
+
ThematicBreak), serializable to/from JSON
|
|
467
|
+
- DOCX→IR: heading detection via outline level + style resolution,
|
|
468
|
+
list grouping, table vMerge → row_span
|
|
469
|
+
- XLSX→IR: worksheets → sections, cell grids → tables, first row as
|
|
470
|
+
header
|
|
471
|
+
- PPTX→IR: slides → sections, spatial sort, title placeholder → section
|
|
472
|
+
title
|
|
473
|
+
- IR renderers: `plain_text()` and `to_markdown()`
|
|
474
|
+
|
|
475
|
+
- **OOXML module — `crate::docx`** (36 tests)
|
|
476
|
+
- SAX-style parsing of `document.xml`, `styles.xml`, `numbering.xml`
|
|
477
|
+
- Hyperlink resolution, heading detection, list grouping
|
|
478
|
+
- Plain text, Markdown, HTML extraction
|
|
479
|
+
|
|
480
|
+
- **OOXML module — `crate::xlsx`** (57 tests)
|
|
481
|
+
- Shared string table, cell parsing, 1900 date system with Lotus bug
|
|
482
|
+
- Built-in + custom number format detection
|
|
483
|
+
- CSV (RFC 4180), Markdown (pipe tables), HTML output
|
|
484
|
+
- Rich-text and style support
|
|
485
|
+
|
|
486
|
+
- **OOXML module — `crate::pptx`** (40 tests)
|
|
487
|
+
- Slide parsing with spatial sort, shape types (AutoShape, Picture,
|
|
488
|
+
Group, GraphicFrame, Connector), text body extraction
|
|
489
|
+
- Notes slide support, inline hyperlink resolution
|
|
490
|
+
- Plain text, Markdown, HTML extraction
|
|
491
|
+
|
|
492
|
+
- **Legacy module — `crate::cfb`** (18 tests)
|
|
493
|
+
- Pure Rust CFBF / OLE2 container reader
|
|
494
|
+
- Supports v3 (512-byte) and v4 (4096-byte) sectors, mini-streams,
|
|
495
|
+
case-insensitive stream access, path-based lookup
|
|
496
|
+
|
|
497
|
+
- **Legacy module — `crate::doc`** (15 tests)
|
|
498
|
+
- Word Binary (.doc) parser built on `crate::cfb`
|
|
499
|
+
- FIB parsing, piece-table extraction, dual encoding
|
|
500
|
+
(compressed CP1252 + Unicode UTF-16LE)
|
|
501
|
+
- Field-code stripping and special-char sanitization
|
|
502
|
+
|
|
503
|
+
- **Legacy module — `crate::xls`** (24 tests)
|
|
504
|
+
- Excel Binary (.xls) BIFF8 parser built on `crate::cfb`
|
|
505
|
+
- CONTINUE record merging, SST (compressed + wide + rich text),
|
|
506
|
+
RK decode
|
|
507
|
+
- Cell records: LABELSST, NUMBER, RK, MULRK, FORMULA, BOOLERR,
|
|
508
|
+
LABEL, BLANK
|
|
509
|
+
|
|
510
|
+
- **Legacy module — `crate::ppt`** (15 tests)
|
|
511
|
+
- PowerPoint Binary (.ppt) parser built on `crate::cfb`
|
|
512
|
+
- 8-byte record headers, container vs atom records
|
|
513
|
+
- Text extraction from TextCharsAtom (UTF-16LE) and TextBytesAtom
|
|
514
|
+
(Latin-1), SlideListWithText grouping
|
|
515
|
+
|
|
516
|
+
- **Shared core — `crate::core`** (55 tests)
|
|
517
|
+
- `OpcReader` / `OpcWriter` for ZIP-based OPC packages
|
|
518
|
+
- Theme parsing, color resolution, unit types (`Twip`, `HalfPoint`,
|
|
519
|
+
`Emu`)
|
|
520
|
+
- Namespace-aware XML utilities with OOXML Strict support
|
|
521
|
+
|
|
522
|
+
- **Creation API** — write OOXML documents from scratch
|
|
523
|
+
- `crate::docx::write::DocxWriter`, `crate::xlsx::write::XlsxWriter`,
|
|
524
|
+
`crate::pptx::write::PptxWriter`
|
|
525
|
+
- `crate::create::create_from_ir()` and
|
|
526
|
+
`create_from_ir_to_writer()` for IR-to-format conversion
|
|
527
|
+
|
|
528
|
+
- **Editing API** — modify existing documents while preserving unmodified
|
|
529
|
+
parts
|
|
530
|
+
- `crate::docx::edit::EditableDocx`, `crate::xlsx::edit::EditableXlsx`,
|
|
531
|
+
`crate::pptx::edit::EditablePptx`
|
|
532
|
+
- Unified `crate::edit::EditableDocument` with `replace_text()`
|
|
533
|
+
and `set_cell()`
|
|
534
|
+
- `crate::core::editable::EditablePackage` round-trips rels via
|
|
535
|
+
`RelationshipsBuilder::add_with_id()`
|
|
536
|
+
|
|
537
|
+
- **Python bindings** — PyO3 0.28, extension module `office_oxide._native`
|
|
538
|
+
- Type stubs (`_native.pyi`) and PEP 561 `py.typed` marker
|
|
539
|
+
- Wheels for Linux, macOS, Windows across Python 3.8–3.14
|
|
540
|
+
|
|
541
|
+
- **WASM bindings** — `wasm-bindgen`, `WasmDocument` class
|
|
542
|
+
- `new(data, format)`, `plainText()`, `toMarkdown()`, `toHtml()`,
|
|
543
|
+
`toIr()`
|
|
544
|
+
- npm package `office-oxide-wasm`
|
|
545
|
+
|
|
546
|
+
- **CLI** — `office_oxide_cli` workspace crate, binary `office-oxide`
|
|
547
|
+
- Subcommands: `text`, `markdown`, `html`, `info`, `ir`
|
|
548
|
+
|
|
549
|
+
- **MCP server** — `office_oxide_mcp` workspace crate, binary
|
|
550
|
+
`office-oxide-mcp`
|
|
551
|
+
- JSON-RPC 2.0 over stdin/stdout
|
|
552
|
+
- Tools: `extract` (text / markdown / html / ir), `info`
|
|
553
|
+
|
|
554
|
+
- **Feature flags**
|
|
555
|
+
- `python` — PyO3 extension module
|
|
556
|
+
- `wasm` — wasm-bindgen bindings
|
|
557
|
+
- `mmap` — `memmap2`-backed file reading
|
|
558
|
+
- `parallel` — `rayon`-based parallel parsing
|
|
559
|
+
|
|
560
|
+
### Robustness
|
|
561
|
+
|
|
562
|
+
- OOXML Strict namespace support via dual-namespace matching
|
|
563
|
+
(`strict_alternate()`) and relationship-type normalization
|
|
564
|
+
- Case-insensitive ZIP entry lookup with backslash path normalization
|
|
565
|
+
- Namespace-agnostic attribute lookup (`optional_prefixed_attr_str()`)
|
|
566
|
+
for `d3p1:id` and similar
|
|
567
|
+
- Percent-encoding decoding in `PartName::new()`
|
|
568
|
+
- CRC checksum tolerance in `read_zip_entry()` (accepts data on mismatch)
|
|
569
|
+
- Tolerant numeric parsing: `parse_numeric()` strips unit suffixes,
|
|
570
|
+
handles decimals
|
|
571
|
+
- Shared-string DoS cap: `MAX_CELL_STRING_LEN = 32_768` in `xlsx`
|
|
572
|
+
- Optional parts (`numbering.xml`, theme) degrade gracefully on read
|
|
573
|
+
errors
|
|
574
|
+
- XLSX border aliases: `start`/`end` mapped to `left`/`right` for Strict
|
|
575
|
+
OOXML
|
|
576
|
+
|
|
577
|
+
### Validation
|
|
578
|
+
|
|
579
|
+
- **98.4% pass rate on a 6,062-file corpus** (5,965 / 6,062) across 11
|
|
580
|
+
open-source test suites (LibreOffice Core, Apache POI, Open XML SDK,
|
|
581
|
+
ClosedXML, Pandoc, python-docx, python-pptx, Apache Tika, calamine,
|
|
582
|
+
openpreserve, oletools)
|
|
583
|
+
- Zero failures on legitimate Word 97+ / Excel 97+ / PowerPoint 97+
|
|
584
|
+
files — all 97 non-passing files are invalid inputs: 43 invalid
|
|
585
|
+
ZIP/CFB archives, 21 missing required parts, 18 malformed XML, and
|
|
586
|
+
15 non-Office files (WordPerfect, pre-OLE2 Excel 3/4) misnamed with
|
|
587
|
+
Office extensions
|
|
588
|
+
- See [BENCHMARKS.md](BENCHMARKS.md) for per-format timings and the
|
|
589
|
+
full failure breakdown
|
|
590
|
+
|
|
591
|
+
[0.1.1]: https://github.com/yfedoseev/office_oxide/compare/v0.1.0...v0.1.1
|
|
592
|
+
[0.1.0]: https://github.com/yfedoseev/office_oxide/releases/tag/v0.1.0
|