Files
hermes-agent/skills/productivity/xlsx/references/restructuring.md
T
Teknium fad88cf130 feat: extend clean-room office skills toward full parity
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.
2026-08-08 10:46:20 -07:00

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/expression rules (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.