Skip to content

docx_io split - Slice 3: extract zip_postprocess.py (_rewrite_docx_zip) #74

Description

@LegalMarc

Design doc (read in full first): docs/design/docx_io_package_split.md — §2, §4 Slice 3.

Slice 3 of 5 of the docx_io.py package split. Highest line-count but most mechanical slice — self-contained, and already exercised end-to-end by tests/test_metadata_scrubbing.py.

Scope

Move _rewrite_docx_zip() (docx_io.py:382) and its ~20 nested closures into marcut/docx/zip_postprocess.py, converting the closures to module-level (or small-class) functions taking explicit parameters instead of closing over method-local state.

The closures include (per §1 of the doc): relationship-target sanitization (_is_unc_path, _is_user_path, _is_file_path, _is_internal_url, _sanitize_target); JPEG/PNG binary metadata stripping (_strip_jpeg_metadata, _strip_png_metadata); namespace/element pruning (_remove_lang_elements, _strip_form_defaults, _strip_alternate_content, _strip_nonstandard_elements); custom-style renaming (_rename_custom_styles, _update_style_references); chart-label redaction (_clean_chart_labels); .rels/[Content_Types].xml rewriting (_scrub_rels, _update_content_types); and the final orphan-detection + ZIP-rewrite loop.

This code operates only on raw bytes / zipfile.ZipFile / lxml.etree — it must never import docx.Document. It takes a file path and a MetadataCleaningSettings and mutates the file on disk; it has no dependency on DocxMap instance state beyond those parameters.

Non-negotiable requirements

  • Behavior-identical. This is the metadata-scrubbing critical path — a dropped closure or a mis-wired parameter can silently stop scrubbing a relationship target, hidden run, chart label, or image's binary metadata while save() still "succeeds". The golden-file harness (#70) diffing to zero is the gate that proves this.
  • No new python-docx import in zip_postprocess.py — it stays a raw-bytes/zipfile/lxml pass, matching how it already behaves as a path-in/path-out post-processor.
  • Preserve _safe_fromstring usage (now imported from marcut/docx/xml_utils.py per #73 if that landed first; otherwise from its current location).
  • Use the package name agreed in #72.

Acceptance criteria

  • _rewrite_docx_zip + closures moved to marcut/docx/zip_postprocess.py as explicit-parameter functions; no docx.Document import there.
  • DocxMap._postprocess_zip/save() call into the new module; public DocxMap surface unchanged.
  • tests/test_metadata_scrubbing.py passes with only import-path edits.
  • Golden-file harness (#70) diffs to zero (this is the primary safety proof for this slice).
  • One real marcut redact run diffed against pre-slice baseline → no difference.

Verification loop (design doc §3.3)

  1. PYTHONPATH=src/python python3 -m pytest tests/test_docx_io.py tests/test_metadata_scrubbing.py tests/test_pipeline.py tests/test_url_redaction.py tests/test_failure_scenarios.py tests/test_security.py -q.
  2. Golden-file harness — empty diff.
  3. marcut redact against a sample-files/ fixture, diffed vs baseline.

Plus the standard gate: full pytest -q, swift build, swift test, bash scripts/release_preflight.sh.

Dependencies

Blocked by #70. Best sequenced after #73 (so it can import _safe_fromstring from xml_utils.py), but per the design doc slices are technically independent — if it lands before B2, import _safe_fromstring from its current location and update in B2.


Land as its own PR; do not combine with other issues in this set. Do not reopen design-spike #25.

Metadata

Metadata

Assignees

No one assigned

    Labels

    afkAutonomous workflow-loop eligible

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions