docx/round-trip-identity--table-basic

Load each of 10 diverse committed fixtures with python-docx and immediately re-save with reproducible=True. Assert the twin retains core structural invariants of the original: paragraph count, presence of the fixture's defining run text, image count, and sectPr presence.

Library verdicts: python-docx: — docxjs: —

Metadata

Feature iddocx/round-trip-identity--table-basic
Formatdocx
Categoryround-trip
Familydocx/round-trip-identity
Axis valuessource=table-basic
Spececma-376-5-part-1 § 11.1

XPath assertions

ID / partPredicateXPathpython-docxdocxjs
paragraph-count-preserved-table-basic
word/document.xml
match = ^4(\.0)?$
The twin must carry exactly the same number of <w:p> elements as the original.
count(//w:body//w:p)
sectPr-preserved-table-basic
word/document.xml
exist
The body's terminating <w:sectPr> must survive round-trip — Word refuses to open documents without one.
//w:body/w:sectPr
document-rels-present-table-basic
word/_rels/document.xml.rels
exist
The document's relationship to styles.xml must survive — its absence is a classic round-trip loss mode.
//r:Relationships/r:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']
content-types-document-main-table-basic
[Content_Types].xml
exist
[Content_Types].xml must still declare the document main part.
//ct:Override[@ContentType='application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml']

Render assertions

No render assertions declared.

Generator source

scripts/gen_round_trip_identity.py

#!/usr/bin/env python3
"""Generate round-trip-identity twin fixtures.

For each source fixture, load it via python-docx and immediately save
to ``fixtures/docx/round-trip-identity--<source-id>.docx`` with
``reproducible=True``. The committed twin is what the manifest's
assertions run against; the whole point is to catch python-docx losing
or mutating something on a pure load-save cycle.
"""

from __future__ import annotations

import argparse
import shutil
import sys
from pathlib import Path

from docx import Document

_REPO_ROOT = Path(__file__).resolve().parent.parent
_FIX = _REPO_ROOT / "fixtures" / "docx"


def round_trip(source: Path, out: Path) -> None:
    doc = Document(str(source))
    out.parent.mkdir(parents=True, exist_ok=True)
    doc.save(str(out), reproducible=True)


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--source", required=True, help="Source fixture logical name (e.g. 'bold-text').")
    parser.add_argument("--out", required=True, help="Output fixture path (repo-relative).")
    ns = parser.parse_args(argv)

    source_path = _FIX / f"{ns.source}.docx"
    out_path = _REPO_ROOT / ns.out
    if not source_path.exists():
        print(f"Source fixture missing: {source_path}", file=sys.stderr)
        return 1
    round_trip(source_path, out_path)
    print(out_path)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/round-trip-identity--table-basic",
  "kind": "literal",
  "title": "Round-trip identity (python-docx)",
  "format": "docx",
  "category": "round-trip",
  "summary": "Load each of 10 diverse committed fixtures with python-docx and immediately re-save with reproducible=True. Assert the twin retains core structural invariants of the original: paragraph count, presence of the fixture's defining run text, image count, and sectPr presence.",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "11.1",
    "element": "",
    "notes": "This family does not target a single OOXML element. It targets the python-docx authoring library's load-save round-trip contract: a file it loads and immediately re-saves must preserve the fixture's externally observable structure. Bugs in this space (e.g. dropped child elements, mutated element ordering, renumbered relationships) do not show up in direct-authoring fixtures because the library also authors the assertion target. Round-trip inverts that."
  },
  "fixtures": {
    "machine": "docx/round-trip-identity--table-basic"
  },
  "round_trip": {
    "library": "python-docx",
    "source_fixture": "docx/round-trip-identity",
    "notes": "Mode B (generator-embedded). The generator commits the twin fixture under `round-trip-identity--<source.id>.docx`; this manifest's `source_fixture` value is parameterised per case via the `source` axis."
  },
  "generator": {
    "python": "scripts/gen_round_trip_identity.py",
    "arg_template": "--source {source.id} --out fixtures/docx/round-trip-identity--{source.id}.docx"
  },
  "_expansion": {
    "parent_id": "docx/round-trip-identity",
    "bindings": {
      "source": "table-basic"
    }
  },
  "assertions": [
    {
      "id": "paragraph-count-preserved-table-basic",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "count(//w:body//w:p)",
      "must": "match",
      "value": "^4(\\.0)?$",
      "description": "The twin must carry exactly the same number of <w:p> elements as the original."
    },
    {
      "id": "sectPr-preserved-table-basic",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "//w:body/w:sectPr",
      "must": "exist",
      "description": "The body's terminating <w:sectPr> must survive round-trip — Word refuses to open documents without one."
    },
    {
      "id": "document-rels-present-table-basic",
      "part": "word/_rels/document.xml.rels",
      "namespaces": {
        "r": "http://schemas.openxmlformats.org/package/2006/relationships"
      },
      "xpath": "//r:Relationships/r:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']",
      "must": "exist",
      "description": "The document's relationship to styles.xml must survive — its absence is a classic round-trip loss mode."
    },
    {
      "id": "content-types-document-main-table-basic",
      "part": "[Content_Types].xml",
      "namespaces": {
        "ct": "http://schemas.openxmlformats.org/package/2006/content-types"
      },
      "xpath": "//ct:Override[@ContentType='application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml']",
      "must": "exist",
      "description": "[Content_Types].xml must still declare the document main part."
    }
  ]
}

Fixture

Download round-trip-identity--table-basic.docx (18.7 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

This family does not target a single OOXML element. It targets the python-docx authoring library's load-save round-trip contract: a file it loads and immediately re-saves must preserve the fixture's externally observable structure. Bugs in this space (e.g. dropped child elements, mutated element ordering, renumbered relationships) do not show up in direct-authoring fixtures because the library also authors the assertion target. Round-trip inverts that.