Same clean-room discipline as the initial rewrite (isolated subagents, functional specs only, predecessor content banned including via git history; transcripts retained). All additions test-proven. docx (13->29 tests): - docx_revisions.py: tracked changes list/accept/reject (all or by id), incl. tables and headers/footers, via direct oxml manipulation - docx_comments.py: list/add/delete comments (native python-docx >=1.2 API with XML fallback), anchored-text extraction - docx_validate.py: package health check (rels, images, styles, CRC) with JSON severity report — explicitly not XSD validation - docx_edit.py: run normalization; TOC + PAGE/NUMPAGES field insertion xlsx (5->12 tests): - xlsx_restructure.py: reference-aware insert/delete rows/cols — rewrites formulas on all sheets (absolute refs, ranges, cross-sheet, quoted names), shifts merges/autofilter/freeze/validation/CF ranges, tables, defined names; JSON report incl. honest not_shifted list - native Excel tables, named ranges, hyperlinks, cell notes, sheet protection (documented as strippable, not security) - xlsx_recalc.py: headless LibreOffice recalc with graceful degrade powerpoint (11->21 tests): - pptx_render.py: all slides -> PNGs (soffice + pdftoppm/pdftocairo), wired to vision_analyze review loop in SKILL.md - run-merge normalize before replace (identical-format splits lossless) - surgical chart ops (series/category/title) wrapping replace_data - slide duplication with rel remap (clean refusal on chart slides) - backgrounds, hyperlinks, slide numbers, footers, notes editing pdf (8->21 tests): - pdf_make_form.py: JSON spec -> AcroForm (text/checkbox/radio/dropdown) - pdf_form_layout.py: pre-build layout lint (bounds/overlap/pairing) + rendered box overlay for vision_analyze review - pdf_page_image.py + shared _raster.py: pypdfium2 -> pdftoppm chain, graceful degrade; connected to scanned-PDF triage flow - pdf_stamp.py: text/image stamps at coordinates (rotation/opacity) - pdf_meta.py: DocInfo metadata + attachments round-trip Gates re-verified independently: 83 skill tests green under LC_ALL=C, repo invariant suite 29/29, SkillEvaluator pii+unicode+lint 3/3 x4.
3.6 KiB
Reference-aware restructuring (xlsx_restructure.py)
scripts/xlsx_restructure.py performs one row/column insert or delete
and rewrites everything that references the moved cells. This document
gives the exact rewrite rules and honest limits.
What gets rewritten
| Artifact | Scope | Behavior |
|---|---|---|
| Formula references | ALL sheets | A1 refs into the edited sheet are shifted; refs into a fully deleted region become #REF! |
| Merged-cell ranges | edited sheet | shifted; expanded when they span the insertion point; dropped (reported to: null) when fully deleted |
| Autofilter ref | edited sheet | shifted/expanded like a range |
| Freeze panes | edited sheet | anchor cell shifted (never below row/col of the pane's own minimum) |
| Data validations | edited sheet | each range in the sqref shifted; deleted ranges removed |
| Conditional formats | edited sheet | applied range (sqref) shifted |
| Native tables | edited sheet | table ref shifted/expanded |
| Defined names | workbook scope | attr_text refs into the edited sheet rewritten |
| Row heights / column widths | edited sheet | dimension keys re-indexed |
Reference grammar handled
- Relative and absolute coordinates in any mix:
B2,$B2,B$2,$B$2— the$flags are preserved through the shift. - Ranges
B2:D9, including partial-absolute endpoints. - Cross-sheet refs:
Data!B2,'My Sheet'!$A$1:$C$9(quoted names may contain doubled quotes''). Only refs whose sheet qualifier matches the edited sheet are touched; unqualified refs are interpreted relative to the formula's own sheet. - String literals inside formulas (
"See B2") are never rewritten. - Function names that look like cells (
LOG10(...)) are not touched (a reference is never followed by(). - Whole-row/column refs (
B:B,2:2) pass through unchanged — Excel semantics keep them valid across inserts within the span.
Shift semantics
Insert of N at index i: every coordinate >= i moves +N; range endpoints move independently, so a range spanning i grows by N.
Delete of N at index i: coordinates before i are unchanged; coordinates
past the deleted block move -N; a single cell inside the block becomes
#REF!; a RANGE partially covering the block is clamped (Excel does the
same); a range entirely inside the block becomes #REF! (formulas) or
is removed (merges/validations).
What it CANNOT shift (honest limits)
- Chart anchors and plotted ranges — openpyxl chart objects are not reliably round-tripped; anchors stay where they were. Re-create charts after restructuring if their data moved.
- Images / drawings — same reason.
- Conditional-format RULE formulas — the applied range (sqref) is
shifted, but formulas inside
cell_is/expressionrules (e.g.$B1>100) are left as-is. Review them if they reference moved cells. - Sheet-local defined names and names using R1C1 or union/ intersection operators are rewritten only if they parse as plain A1 refs; anything else passes through untouched.
- Structured table references in formulas (
Table1[Sales]) don't need shifting (they follow the table), and are left alone.
Every run prints a JSON report listing exactly which formulas, merges,
tables, names, and ranges were changed, plus a fixed not_shifted list
of the above limits — inspect it after any structural edit.
One op per invocation
The CLI takes exactly one of --insert-rows/--delete-rows/ --insert-cols/--delete-cols (as IDX[:N]; columns accept letters).
For multiple operations run it repeatedly — ordering compound shifts in
one pass is where spreadsheet tools historically corrupt references.