A family covering the three major shapes of <mc:AlternateContent> Word writes to keep documents readable by older Word versions: (1) modern wps:wsp drawings with a VML shape fallback, (2) w14:checkbox content controls with a FORMCHECKBOX field fallback, (3) bare <mc:Choice Requires="w14"> blocks wrapping a Word-2010-only run property (w14:glow).
Library verdicts: python-docx: — docxjs: —
| Feature id | docx/mc-alternate-content--checkbox-fallback |
|---|---|
| Format | docx |
| Category | markup-compatibility |
| Family | docx/mc-alternate-content |
| Axis values | case=checkbox-fallback |
| Spec | ecma-376-5-part-3 § 10.2 mc:AlternateContent |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
alternate-content-present-checkbox-fallbackword/document.xml | existEvery case must emit at least one <mc:AlternateContent> element inside the main story. | //mc:AlternateContent | — | — |
choice-requires-attribute-checkbox-fallbackword/document.xml | equal = w14The <mc:Choice> element must carry a Requires attribute naming the extension prefix(es) a reader needs to honour this branch. | //mc:AlternateContent/mc:Choice/@Requires | — | — |
fallback-present-checkbox-fallbackword/document.xml | existEvery <mc:AlternateContent> in this family must carry an <mc:Fallback> sibling so consumers lacking the Requires-listed extension still render the run. | //mc:AlternateContent/mc:Fallback | — | — |
choice-before-fallback-checkbox-fallbackword/document.xml | existPer Part 3 §10.2.1, <mc:Choice> children must precede <mc:Fallback>. Assertion checks that no mc:Fallback appears before mc:Choice. | //mc:AlternateContent[mc:Choice and mc:Fallback and count(mc:Choice/preceding-sibling::mc:Fallback) = 0] | — | — |
No render assertions declared.
scripts/gen_mc_alternate_content.py
#!/usr/bin/env python3
"""Generate ``fixtures/docx/mc-alternate-content--<case>.docx``.
Parameterised generator for the ``docx/mc-alternate-content`` feature
family. Each case exercises a distinct shape of
``<mc:AlternateContent>`` that python-docx does not build via its
high-level API but must round-trip intact. The generator authors the
XML directly via ``lxml`` / ``docx.oxml.ns.qn`` then hands the element
to a paragraph via ``paragraph._p.append(...)``.
Cases:
* ``shape-compat``: ``<mc:Choice Requires="wps">`` wraps a modern
``<wps:wsp>`` (Word 2010+ WordprocessingShape). ``<mc:Fallback>``
provides a legacy VML ``<v:rect>`` for consumers that predate
WordprocessingShape.
* ``checkbox-fallback``: ``<mc:Choice Requires="w14">`` wraps a
``<w14:checkbox>`` form control introduced in Word 2010.
``<mc:Fallback>`` provides the classic ``<w:fldChar>`` begin/end pair
carrying a ``FORMCHECKBOX`` field so Word 2007 can render an
equivalent checkbox.
* ``choice-requires``: a minimal ``<mc:Choice Requires="w14">`` wrapping
a ``<w14:defaultImageDpi>`` setting with a plain text fallback. Pins
the bare-bones ``Requires``-attribute shape without either flavour of
shape payload.
"""
from __future__ import annotations
import argparse
import io
import zipfile
from pathlib import Path
from docx import Document
from lxml import etree
_REPO_ROOT = Path(__file__).resolve().parent.parent
_MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006"
_W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
_W14_NS = "http://schemas.microsoft.com/office/word/2010/wordml"
_WPS_NS = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
_WP_NS = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
_A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main"
_V_NS = "urn:schemas-microsoft-com:vml"
_O_NS = "urn:schemas-microsoft-com:office:office"
def _shape_compat_xml() -> bytes:
"""Return an ``<mc:AlternateContent>`` with a modern wps:wsp shape
in <mc:Choice Requires="wps"> and a VML <v:rect> in <mc:Fallback>.
This mimics what Word emits when a drawing is expressed both in
DrawingML (2007+) and VML (Word 2003 legacy) for maximum reader
compatibility.
"""
xml = f"""
<w:r xmlns:w="{_W_NS}" xmlns:mc="{_MC_NS}" xmlns:w14="{_W14_NS}"
xmlns:wps="{_WPS_NS}" xmlns:wp="{_WP_NS}" xmlns:a="{_A_NS}"
xmlns:v="{_V_NS}" xmlns:o="{_O_NS}">
<mc:AlternateContent>
<mc:Choice Requires="wps">
<w:drawing>
<wp:inline distT="0" distB="0" distL="0" distR="0">
<wp:extent cx="914400" cy="914400"/>
<wp:docPr id="1" name="Shape 1"/>
<wp:cNvGraphicFramePr/>
<a:graphic>
<a:graphicData uri="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
<wps:wsp>
<wps:cNvSpPr/>
<wps:spPr>
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="914400" cy="914400"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
</wps:spPr>
<wps:bodyPr/>
</wps:wsp>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</mc:Choice>
<mc:Fallback>
<w:pict>
<v:rect style="width:72pt;height:72pt" fillcolor="white"
strokecolor="black" o:gfxdata=""/>
</w:pict>
</mc:Fallback>
</mc:AlternateContent>
</w:r>
""".strip()
return xml.encode("utf-8")
def _checkbox_fallback_xml() -> bytes:
"""Return an ``<mc:AlternateContent>`` with a w14:checkbox in <mc:Choice>
and the legacy FORMCHECKBOX field-character sequence in <mc:Fallback>.
"""
xml = f"""
<w:r xmlns:w="{_W_NS}" xmlns:mc="{_MC_NS}" xmlns:w14="{_W14_NS}">
<mc:AlternateContent>
<mc:Choice Requires="w14">
<w:sdt>
<w:sdtPr>
<w14:checkbox>
<w14:checked w14:val="0"/>
<w14:checkedState w14:val="2612" w14:font="MS Gothic"/>
<w14:uncheckedState w14:val="2610" w14:font="MS Gothic"/>
</w14:checkbox>
</w:sdtPr>
<w:sdtContent>
<w:r>
<w:t>☐</w:t>
</w:r>
</w:sdtContent>
</w:sdt>
</mc:Choice>
<mc:Fallback>
<w:r>
<w:fldChar w:fldCharType="begin">
<w:ffData>
<w:name w:val="Check1"/>
<w:enabled/>
<w:checkBox>
<w:sizeAuto/>
<w:default w:val="0"/>
</w:checkBox>
</w:ffData>
</w:fldChar>
<w:instrText xml:space="preserve"> FORMCHECKBOX </w:instrText>
<w:fldChar w:fldCharType="end"/>
</w:r>
</mc:Fallback>
</mc:AlternateContent>
</w:r>
""".strip()
return xml.encode("utf-8")
def _choice_requires_xml() -> bytes:
"""Return a bare ``<mc:AlternateContent>`` wrapping a w14:styleParaGuid
(a run-level Word 2010+ marker) with a minimal text fallback. Covers
the ``Requires`` attribute without either drawing flavour.
"""
xml = f"""
<w:r xmlns:w="{_W_NS}" xmlns:mc="{_MC_NS}" xmlns:w14="{_W14_NS}">
<mc:AlternateContent>
<mc:Choice Requires="w14">
<w:r>
<w:rPr>
<w14:glow w14:rad="63500">
<w14:schemeClr w14:val="accent1"/>
</w14:glow>
</w:rPr>
<w:t>Modern run with w14:glow</w:t>
</w:r>
</mc:Choice>
<mc:Fallback>
<w:r>
<w:t>Plain run without glow</w:t>
</w:r>
</mc:Fallback>
</mc:AlternateContent>
</w:r>
""".strip()
return xml.encode("utf-8")
_BUILDERS = {
"shape-compat": _shape_compat_xml,
"checkbox-fallback": _checkbox_fallback_xml,
"choice-requires": _choice_requires_xml,
}
def _write(case: str, out: Path) -> None:
doc = Document()
paragraph = doc.add_paragraph()
xml = _BUILDERS[case]()
fragment = etree.fromstring(xml)
# A <w:r> element — append as a child of the paragraph's <w:p>.
paragraph._p.append(fragment)
out.parent.mkdir(parents=True, exist_ok=True)
doc.save(out, reproducible=True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--case", required=True, choices=sorted(_BUILDERS))
parser.add_argument("--out", required=True)
args = parser.parse_args()
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = _REPO_ROOT / out_path
_write(args.case, out_path)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/mc-alternate-content--checkbox-fallback",
"kind": "literal",
"title": "Markup Compatibility: mc:AlternateContent / mc:Choice / mc:Fallback",
"format": "docx",
"category": "markup-compatibility",
"summary": "A family covering the three major shapes of <mc:AlternateContent> Word writes to keep documents readable by older Word versions: (1) modern wps:wsp drawings with a VML shape fallback, (2) w14:checkbox content controls with a FORMCHECKBOX field fallback, (3) bare <mc:Choice Requires=\"w14\"> blocks wrapping a Word-2010-only run property (w14:glow).",
"spec": {
"source": "ecma-376-5-part-3",
"clause": "10.2",
"element": "mc:AlternateContent",
"notes": "ECMA-376 Part 3 §10.2.1 defines <mc:AlternateContent> as a selection element: readers evaluate <mc:Choice Requires=\"...\"> blocks in document order and pick the first whose Requires-whitelist they can honour; otherwise they fall back to <mc:Fallback>. Requires names namespace prefixes (bound via xmlns declarations on the element's ancestor axis). The fallback block is restricted — it may only use elements from the base Part 1/2/4 schemas plus attributes from mc: itself. The three cases in this family hit (a) the shape-compatibility use that motivates AlternateContent, (b) a Word-2010 form control with a Word-2007 equivalent, (c) a minimal Requires/Fallback pair without drawing markup, so the pin survives even if shape support regresses."
},
"fixtures": {
"machine": "docx/mc-alternate-content--checkbox-fallback"
},
"generator": {
"python": "scripts/gen_mc_alternate_content.py",
"arg_template": "--case {case.id} --out fixtures/docx/mc-alternate-content--{case.id}.docx"
},
"_expansion": {
"parent_id": "docx/mc-alternate-content",
"bindings": {
"case": "checkbox-fallback"
}
},
"assertions": [
{
"id": "alternate-content-present-checkbox-fallback",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "//mc:AlternateContent",
"must": "exist",
"description": "Every case must emit at least one <mc:AlternateContent> element inside the main story."
},
{
"id": "choice-requires-attribute-checkbox-fallback",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "//mc:AlternateContent/mc:Choice/@Requires",
"must": "equal",
"value": "w14",
"description": "The <mc:Choice> element must carry a Requires attribute naming the extension prefix(es) a reader needs to honour this branch."
},
{
"id": "fallback-present-checkbox-fallback",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "//mc:AlternateContent/mc:Fallback",
"must": "exist",
"description": "Every <mc:AlternateContent> in this family must carry an <mc:Fallback> sibling so consumers lacking the Requires-listed extension still render the run."
},
{
"id": "choice-before-fallback-checkbox-fallback",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "//mc:AlternateContent[mc:Choice and mc:Fallback and count(mc:Choice/preceding-sibling::mc:Fallback) = 0]",
"must": "exist",
"description": "Per Part 3 §10.2.1, <mc:Choice> children must precede <mc:Fallback>. Assertion checks that no mc:Fallback appears before mc:Choice."
}
]
}
No rendered reference is available for this case.