docx/mc-alternate-content--choice-requires

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: —

Metadata

Feature iddocx/mc-alternate-content--choice-requires
Formatdocx
Categorymarkup-compatibility
Familydocx/mc-alternate-content
Axis valuescase=choice-requires
Spececma-376-5-part-3 § 10.2 mc:AlternateContent

XPath assertions

ID / partPredicateXPathpython-docxdocxjs
alternate-content-present-choice-requires
word/document.xml
exist
Every case must emit at least one <mc:AlternateContent> element inside the main story.
//mc:AlternateContent
choice-requires-attribute-choice-requires
word/document.xml
equal = w14
The <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-choice-requires
word/document.xml
exist
Every <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-choice-requires
word/document.xml
exist
Per 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]

Render assertions

No render assertions declared.

Generator source

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>&#9744;</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())

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/mc-alternate-content--choice-requires",
  "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--choice-requires"
  },
  "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": "choice-requires"
    }
  },
  "assertions": [
    {
      "id": "alternate-content-present-choice-requires",
      "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-choice-requires",
      "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-choice-requires",
      "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-choice-requires",
      "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."
    }
  ]
}

Fixture

Download mc-alternate-content--choice-requires.docx (18.7 KB)

Reference preview

No rendered reference is available for this case.

Spec 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.