docx/preserve-styles-with-effects

A Word-authored .docx that carries word/stylesWithEffects.xml (a Word-2013+ companion to styles.xml that encodes text/shape effects for Word-2010 downgrade compatibility) must still contain that part after a python-docx load + save round-trip.

Library verdicts: python-docx: — docxjs: —

Metadata

Feature iddocx/preserve-styles-with-effects
Formatdocx
Categoryroundtrip-fidelity
Spececma-376-5-part-1 § 17.7 w:styles

XPath assertions

ID / partPredicateXPathpython-docxdocxjs
styles-with-effects-override-preserved
[Content_Types].xml
exist
The Override ContentType for word/stylesWithEffects.xml must still be declared after round-trip.
//ct:Override[@PartName='/word/stylesWithEffects.xml']
styles-with-effects-rel-preserved
word/_rels/document.xml.rels
exist
The document-level rel pointing to stylesWithEffects.xml must still exist after round-trip.
//pr:Relationship[@Target='stylesWithEffects.xml']

Render assertions

No render assertions declared.

Generator source

scripts/gen_preserve_styles_with_effects.py

#!/usr/bin/env python3
"""Generate ``fixtures/docx/preserve-styles-with-effects.docx``.

The machine fixture is produced by round-tripping a Word-authored source
file through python-docx: open with ``docx.Document(<office>)`` then save
back with ``reproducible=True``. The source (.office.docx companion) is a
verbatim Word-authored file that carries ``word/stylesWithEffects.xml``;
the corresponding manifest asserts the part's content-type Override and
its document-level relationship both survive the round-trip.

Runs against current python-docx master will FAIL the manifest — that is
the point. python-docx currently drops this part unconditionally in
``parts/document.py::DocumentPart._drop_unused_optional_parts``.
"""

from __future__ import annotations

from pathlib import Path

from docx import Document

_REPO_ROOT = Path(__file__).resolve().parent.parent
_OFFICE_SRC = (
    _REPO_ROOT / "fixtures" / "docx" / "preserve-styles-with-effects.office.docx"
)
_OUT = _REPO_ROOT / "fixtures" / "docx" / "preserve-styles-with-effects.docx"


def main() -> int:
    doc = Document(str(_OFFICE_SRC))
    _OUT.parent.mkdir(parents=True, exist_ok=True)
    doc.save(str(_OUT), reproducible=True)
    print(_OUT)
    return 0


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

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/preserve-styles-with-effects",
  "title": "Preserve word/stylesWithEffects.xml on load+save",
  "format": "docx",
  "category": "roundtrip-fidelity",
  "roles": [
    "authoring"
  ],
  "summary": "A Word-authored .docx that carries word/stylesWithEffects.xml (a Word-2013+ companion to styles.xml that encodes text/shape effects for Word-2010 downgrade compatibility) must still contain that part after a python-docx load + save round-trip.",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "17.7",
    "element": "w:styles",
    "notes": "stylesWithEffects.xml is an MS-specific extension part (content type application/vnd.ms-word.stylesWithEffects+xml, relationship type .../2007/relationships/stylesWithEffects) that Word ships alongside styles.xml. It is NOT defined in ECMA-376 itself but is documented in [MS-DOCX] and is emitted by every copy of Word from 2010 onward that has ever opened the file. It encodes style-level graphical effects (glow, reflection, shadow) that Word 2010 could not read from the Word-2013 w14: extensions in styles.xml; Word 2013+ writes BOTH parts so a downgrade to Word 2010 stays legible. python-docx unconditionally drops this part in parts/document.py::DocumentPart._drop_unused_optional_parts because 'python-docx never produces effects-style content' — but that reasoning only applies to documents python-docx creates from scratch, not to documents it loads from Word. The .office.docx companion is verbatim Word output; the 'machine' fixture is produced by python-docx loading and saving that file with reproducible=True."
  },
  "fixtures": {
    "machine": "docx/preserve-styles-with-effects",
    "office": "docx/preserve-styles-with-effects"
  },
  "generator": {
    "python": "scripts/gen_preserve_styles_with_effects.py"
  },
  "assertions": [
    {
      "id": "styles-with-effects-override-preserved",
      "part": "[Content_Types].xml",
      "namespaces": {
        "ct": "http://schemas.openxmlformats.org/package/2006/content-types"
      },
      "xpath": "//ct:Override[@PartName='/word/stylesWithEffects.xml']",
      "must": "exist",
      "description": "The Override ContentType for word/stylesWithEffects.xml must still be declared after round-trip."
    },
    {
      "id": "styles-with-effects-rel-preserved",
      "part": "word/_rels/document.xml.rels",
      "namespaces": {
        "pr": "http://schemas.openxmlformats.org/package/2006/relationships"
      },
      "xpath": "//pr:Relationship[@Target='stylesWithEffects.xml']",
      "must": "exist",
      "description": "The document-level rel pointing to stylesWithEffects.xml must still exist after round-trip."
    }
  ]
}

Fixture

Download preserve-styles-with-effects.docx (9.3 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

stylesWithEffects.xml is an MS-specific extension part (content type application/vnd.ms-word.stylesWithEffects+xml, relationship type .../2007/relationships/stylesWithEffects) that Word ships alongside styles.xml. It is NOT defined in ECMA-376 itself but is documented in [MS-DOCX] and is emitted by every copy of Word from 2010 onward that has ever opened the file. It encodes style-level graphical effects (glow, reflection, shadow) that Word 2010 could not read from the Word-2013 w14: extensions in styles.xml; Word 2013+ writes BOTH parts so a downgrade to Word 2010 stays legible. python-docx unconditionally drops this part in parts/document.py::DocumentPart._drop_unused_optional_parts because 'python-docx never produces effects-style content' — but that reasoning only applies to documents python-docx creates from scratch, not to documents it loads from Word. The .office.docx companion is verbatim Word output; the 'machine' fixture is produced by python-docx loading and saving that file with reproducible=True.