feat: the raw layer — lossless, addressable, writable OOXML access (v0.4.0)#19
Merged
Conversation
…XML parts The foundation of the raw layer (the lossless twin of the extraction pipeline): contextifier.raw with OpcPackage (lazy parts, rels graphs, content-types, and a save that emits untouched parts byte-identical — contract-tested incl. BOM/whitespace quirks and real xlsx/docx files), XmlPart (lazy lxml facade with explicit dirty tracking), RawDocumentBase (flush-on-save plumbing for the format models), the frozen ChartModel interface contract (implemented in C3), and the public open_raw() entry that dispatches xlsx/docx/pptx models (landing in C2/C4/C5). lxml promoted to an explicit dependency. 499 tests pass (485 + 14 new). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… and README Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DocxRawDocument over word/document.xml with python-docx-compatible addressing (body-level w:p / w:tbl indices, grid-resolved cells with gridSpan/vMerge semantics matching row.cells — interchangeable with edit2docs addresses). - set_paragraph_text: run-preserving replace (P0-3 fix) — text lands in the first pure-text run keeping its rPr; drawings/pictures/OLE, bookmarks, math etc. stay untouched in place; hyperlink elements survive (emptied), removable via strip_empty_hyperlinks incl. orphan rel cleanup. - RawCell.set_text: same rules per paragraph — image/nested-table paragraphs untouched, other pure-text paragraphs emptied, never removed (fixes cell-image destruction). - insert_paragraph_after (-1 = body start, sectPr-safe), delete_paragraph. - RawTable: n_rows/n_cols, cell(r,c), nested_tables(r,c), insert_row/delete_row (template deep-copy, text cleared, props kept). - chart_part_names + lazy charts (C3 ChartModel contract), read-only headers/footers text, body_order() for outline builders. Tests: addressing parity vs python-docx incl. merges, run/image preservation proofs, hyperlink policy, row & paragraph editing, nested tables, byte-preservation (only document.xml differs after one edit). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PptxRawDocument (format="pptx") on top of the C1 OPC container: - slides: p:sldIdLst order via presentation rels; RawSlide exposes a shape inventory (text/picture/table/chart/diagram/group kinds, ids from p:cNvPr, group recursion), format-preserving get_text/set_text (first a:r keeps its a:rPr; a:fld/a:br survive), native a:tbl tables (cell read/write, insert_row/delete_row via row-template deep copy), chart_part_names + lazy ChartModel list, and notes_text. - RawSlide.replace_content(new_slide_xml, preserve_native=True): the edit2docs enabler — swaps the slide XML for generated markup while lifting the original chart/table/diagram graphicFrames (and optionally pictures) into the new spTree with renumbered cNvPr ids; rels are untouched so carried-over r:id/r:embed keep resolving. - remove_slide(index): drops the sldId + pres rel, deletes the slide, its rels and notes slide, then reference-counts every transitively reachable part against all remaining rels and sweeps true orphans (chart, embedded workbook, images) plus their content-type overrides — no more orphan-part bloat. 16 tests round-trip every edit through python-pptx and pin the byte-preservation contract (single set_text touches exactly one part). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the C3 stub with the shared chart model used by all three OOXML format models: - kind: classic plot-type mapping (barDir-aware bar/column, 3D variants fold to base kinds) + "chartex:<layoutId>" for cx: charts - title: a:t runs joined, autoTitleDeleted-aware; chartEx cx:title - series: str/num cache parsing (ptCount-sized, gaps → None), scatter/bubble xVal/yVal, chartEx strDim/numDim via dataId - set_title: replaces runs in the first paragraph keeping the first run's a:rPr, creates c:title when absent, drops autoTitleDeleted=1 - set_data: rewrites c:cat/c:val (or xVal/yVal) refs + caches against the learned sheet name, clones/removes c:ser on series-count change, and REGENERATES the embedded workbook with openpyxl (data-only) - embedded_workbook_part + load_chart convenience helper - chartEx writes raise RawUnsupportedError (documented limitation) Tests build real files (openpyxl / python-pptx / hand-built packages) and prove Office-readability by reopening saved bytes with python-pptx, plus the byte-preservation guarantee for untouched parts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
XlsxRawDocument(RawDocumentBase) over the OPC byte-preservation container: - sheets accessor (by name or index) -> RawSheet with get_cell / get_formula / set_cell / append_rows / iter_rows / dimensions / merged_ranges; sheet_names, defined_names, chart_part_names (charts defers to ChartModel, landing in C3) - set_cell is surgical: rows/cells created sorted by @r, style attr preserved, strings written as inlineStr (sharedStrings never touched or created), formula overwrite drops <f> and ensures <calcPr fullCalcOnLoad="1"/> so Excel recalculates stale caches; dimension ref extended when the edit grows the sheet - only the touched worksheet (+ workbook.xml for calcPr) is ever re-serialized; chart styles, custom XML, sparkline extLst, media and everything else round-trip byte-identical — the exact parts an openpyxl round-trip destroys (proven by the contrast test) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er corrupts the source reader Found during C2 verification: zipfile.writestr mutates the passed ZipInfo (header_offset/sizes), so reusing source infos broke any second save on the same package with BadZipFile. Regression-tested. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI installs latest ruff; 0.15.x flags ~200 pre-existing violations (unused imports/vars, ambiguous 'l' names, semicolons, formatting) across handlers/tests untouched by the raw layer. Auto-fixed + renamed lambda/comprehension 'l' -> 'ln' in pdf_plus. No behavior changes; 598 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Contextifier gains its second view: alongside the AI-friendly extraction pipeline,
open_raw()returns a lossless, addressable, writable model of OOXML documents.replace_content+ orphan-sweepingremove_slide; XLSX surgical cells +calcPr fullCalcOnLoad.open_raw+DocumentProcessor.open_raw(); every model exposes.packagefor part-level OPC work.writestrZipInfo mutation corrupting repeated saves.Verification
598 passed (499 baseline + 99 new): byte-preservation contract tests (synthetic + real files + BOM/whitespace quirks), openpyxl-destruction contrast test, Office-library reopen assertions after every mutation (openpyxl / python-docx / python-pptx), cross-format integration of the README flows. ruff clean.
🤖 Generated with Claude Code