Pin the Word-2024 namespace baseline on the <w:document> root: every prefix Microsoft Word declares (w14, w15, w16, w16cid, w16se, w16cex, w16du, w16sdtdh, w16sdtfl, cx, cx1..cx8, aink, am3d, oel, wp14, wpc, wpg, wpi, wne, wps, ...) plus a matching mc:Ignorable whitelist. Protects against silent regressions of the python-docx 2026.05.2 template update.
Library verdicts: python-docx: — docxjs: —
| Feature id | docx/mc-namespace-declarations |
|---|---|
| Format | docx |
| Category | markup-compatibility |
| Spec | ecma-376-5-part-3 § 10 mc:Ignorable |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
mc-ignorable-attribute-presentword/document.xml | existThe root <w:document> element must carry an mc:Ignorable attribute. | /w:document/@mc:Ignorable | — | — |
mc-ignorable-lists-w14word/document.xml | match = (^|\s)w14(\s|$)The Word-2010 prefix w14 must appear as a whitespace-separated token in mc:Ignorable. | /w:document/@mc:Ignorable | — | — |
mc-ignorable-lists-w15word/document.xml | match = (^|\s)w15(\s|$)The Word-2013 prefix w15 must appear as a whitespace-separated token in mc:Ignorable. | /w:document/@mc:Ignorable | — | — |
mc-ignorable-lists-w16seword/document.xml | match = (^|\s)w16se(\s|$) | /w:document/@mc:Ignorable | — | — |
mc-ignorable-lists-w16cidword/document.xml | match = (^|\s)w16cid(\s|$) | /w:document/@mc:Ignorable | — | — |
mc-ignorable-lists-w16word/document.xml | match = (^|\s)w16(\s|$) | /w:document/@mc:Ignorable | — | — |
mc-ignorable-lists-w16cexword/document.xml | match = (^|\s)w16cex(\s|$) | /w:document/@mc:Ignorable | — | — |
mc-ignorable-lists-w16sdtdhword/document.xml | match = (^|\s)w16sdtdh(\s|$) | /w:document/@mc:Ignorable | — | — |
mc-ignorable-lists-w16sdtflword/document.xml | match = (^|\s)w16sdtfl(\s|$) | /w:document/@mc:Ignorable | — | — |
mc-ignorable-lists-w16duword/document.xml | match = (^|\s)w16du(\s|$) | /w:document/@mc:Ignorable | — | — |
mc-ignorable-lists-wp14word/document.xml | match = (^|\s)wp14(\s|$) | /w:document/@mc:Ignorable | — | — |
mc-namespace-w14-declaredword/document.xml | equal = http://schemas.microsoft.com/office/word/2010/wordmlThe w14 prefix on <w:document> must be bound to the Word 2010 wordml namespace URI. | string(/w:document/namespace::*[name()='w14']) | — | — |
mc-namespace-w15-declaredword/document.xml | equal = http://schemas.microsoft.com/office/word/2012/wordml | string(/w:document/namespace::*[name()='w15']) | — | — |
mc-namespace-w16cid-declaredword/document.xml | equal = http://schemas.microsoft.com/office/word/2016/wordml/cid | string(/w:document/namespace::*[name()='w16cid']) | — | — |
mc-namespace-w16se-declaredword/document.xml | equal = http://schemas.microsoft.com/office/word/2015/wordml/symex | string(/w:document/namespace::*[name()='w16se']) | — | — |
mc-namespace-w16cex-declaredword/document.xml | equal = http://schemas.microsoft.com/office/word/2018/wordml/cex | string(/w:document/namespace::*[name()='w16cex']) | — | — |
mc-namespace-w16du-declaredword/document.xml | equal = http://schemas.microsoft.com/office/word/2023/wordml/word16du | string(/w:document/namespace::*[name()='w16du']) | — | — |
mc-namespace-w16sdtdh-declaredword/document.xml | equal = http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash | string(/w:document/namespace::*[name()='w16sdtdh']) | — | — |
mc-namespace-w16sdtfl-declaredword/document.xml | equal = http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock | string(/w:document/namespace::*[name()='w16sdtfl']) | — | — |
mc-namespace-cx-declaredword/document.xml | equal = http://schemas.microsoft.com/office/drawing/2014/chartex | string(/w:document/namespace::*[name()='cx']) | — | — |
mc-namespace-cx1-declaredword/document.xml | equal = http://schemas.microsoft.com/office/drawing/2015/9/8/chartex | string(/w:document/namespace::*[name()='cx1']) | — | — |
mc-namespace-cx8-declaredword/document.xml | equal = http://schemas.microsoft.com/office/drawing/2016/5/14/chartexAnchor the end of the cx1..cx8 range so a partial regression (cx declared but cx8 dropped) does not slip by. | string(/w:document/namespace::*[name()='cx8']) | — | — |
mc-namespace-mc-declaredword/document.xml | equal = http://schemas.openxmlformats.org/markup-compatibility/2006 | string(/w:document/namespace::*[name()='mc']) | — | — |
No render assertions declared.
scripts/gen_mc_namespace_declarations.py
#!/usr/bin/env python3
"""Generate ``fixtures/docx/mc-namespace-declarations.docx``.
Pins the Word-2024 namespace baseline that python-docx's ``default.docx``
template is expected to declare on every ``<w:document>`` root. The
fixture asserts both the prefix->URI bindings and the ``mc:Ignorable``
whitelist that tells a non-Word consumer which extension prefixes may be
safely ignored.
The fixture is built by running ``docx.Document()`` and then rewriting
``word/document.xml``'s root element so it carries the full Word-2024
namespace set. This is important: python-docx 2026.05.2 updated the
*unzipped* ``default-docx-template/word/document.xml`` source to declare
the full set, but the zipped ``default.docx`` that ``Document()`` loads
at runtime is stale and still declares only the minimal ``w``, ``w14``,
``wp14`` subset. This generator patches the produced .docx so the
fixture unambiguously represents the intended post-2026.05.2 shape. When
python-docx's ``default.docx`` is regenerated to match the source
template, this generator can drop the patch step and produce the same
fixture byte-for-byte.
Design note: this fixture is ``reproducible=True`` with no paragraphs,
headers, or footers, so the only meaningful surface is the root
element's namespace map and ``mc:Ignorable`` attribute.
"""
from __future__ import annotations
import io
import zipfile
from pathlib import Path
from docx import Document
from lxml import etree
_REPO_ROOT = Path(__file__).resolve().parent.parent
_OUT = _REPO_ROOT / "fixtures" / "docx" / "mc-namespace-declarations.docx"
# The complete Word-2024 namespace map emitted on <w:document> by
# Microsoft Word. The order matches what Word writes so diffs against
# Office-authored files are minimal.
_WORD_2024_NSMAP = [
("wpc", "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"),
("cx", "http://schemas.microsoft.com/office/drawing/2014/chartex"),
("cx1", "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex"),
("cx2", "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex"),
("cx3", "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex"),
("cx4", "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex"),
("cx5", "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex"),
("cx6", "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex"),
("cx7", "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex"),
("cx8", "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex"),
("mc", "http://schemas.openxmlformats.org/markup-compatibility/2006"),
("aink", "http://schemas.microsoft.com/office/drawing/2016/ink"),
("am3d", "http://schemas.microsoft.com/office/drawing/2017/model3d"),
("o", "urn:schemas-microsoft-com:office:office"),
("oel", "http://schemas.microsoft.com/office/2019/extlst"),
("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships"),
("m", "http://schemas.openxmlformats.org/officeDocument/2006/math"),
("v", "urn:schemas-microsoft-com:vml"),
("wp14", "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"),
("wp", "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"),
("w10", "urn:schemas-microsoft-com:office:word"),
("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"),
("w14", "http://schemas.microsoft.com/office/word/2010/wordml"),
("w15", "http://schemas.microsoft.com/office/word/2012/wordml"),
("w16cex", "http://schemas.microsoft.com/office/word/2018/wordml/cex"),
("w16cid", "http://schemas.microsoft.com/office/word/2016/wordml/cid"),
("w16", "http://schemas.microsoft.com/office/word/2018/wordml"),
("w16du", "http://schemas.microsoft.com/office/word/2023/wordml/word16du"),
("w16sdtdh", "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash"),
("w16sdtfl", "http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock"),
("w16se", "http://schemas.microsoft.com/office/word/2015/wordml/symex"),
("wpg", "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"),
("wpi", "http://schemas.microsoft.com/office/word/2010/wordprocessingInk"),
("wne", "http://schemas.microsoft.com/office/word/2006/wordml"),
("wps", "http://schemas.microsoft.com/office/word/2010/wordprocessingShape"),
]
# Word's mc:Ignorable value: the whitespace-separated prefix list that
# non-Word consumers may safely skip. Order matches Word's output.
_MC_IGNORABLE = "w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14"
_W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
_MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006"
def _rewrite_root_namespaces(document_xml: bytes) -> bytes:
"""Return ``document_xml`` with the full Word-2024 nsmap + mc:Ignorable.
lxml does not permit mutating an existing element's ``nsmap``, so we
parse, construct a fresh root with the target namespace map, move
children + attributes over, and serialise. Children are re-parented
(their inherited nsmap survives because each was parsed separately).
"""
tree = etree.fromstring(document_xml)
nsmap = dict(_WORD_2024_NSMAP)
new_root = etree.Element(
f"{{{_W_NS}}}document",
nsmap=nsmap,
)
# Copy attributes except mc:Ignorable — we rewrite that explicitly.
for k, v in tree.attrib.items():
if k == f"{{{_MC_NS}}}Ignorable":
continue
new_root.set(k, v)
new_root.set(f"{{{_MC_NS}}}Ignorable", _MC_IGNORABLE)
# Re-parent children.
for child in list(tree):
new_root.append(child)
return etree.tostring(
new_root, xml_declaration=True, encoding="UTF-8", standalone=True
)
def _repack(src: bytes, patched_document_xml: bytes) -> bytes:
"""Rewrite the zip so ``word/document.xml`` carries the patched bytes.
All other parts are copied verbatim. Zip entry metadata (date/time)
is preserved so the fixture stays byte-stable under reproducible
saves.
"""
out = io.BytesIO()
with zipfile.ZipFile(io.BytesIO(src)) as zin, zipfile.ZipFile(
out, "w", zipfile.ZIP_DEFLATED
) as zout:
for info in zin.infolist():
data = (
patched_document_xml
if info.filename == "word/document.xml"
else zin.read(info.filename)
)
# Preserve date/time and CRC-safe by rebuilding the ZipInfo.
new_info = zipfile.ZipInfo(
filename=info.filename,
date_time=info.date_time,
)
new_info.compress_type = info.compress_type
zout.writestr(new_info, data)
return out.getvalue()
def main() -> int:
doc = Document()
buf = io.BytesIO()
doc.save(buf, reproducible=True)
patched = _repack(
buf.getvalue(),
_rewrite_root_namespaces(
zipfile.ZipFile(io.BytesIO(buf.getvalue())).read("word/document.xml")
),
)
_OUT.parent.mkdir(parents=True, exist_ok=True)
_OUT.write_bytes(patched)
print(_OUT)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/mc-namespace-declarations",
"title": "Markup Compatibility: namespace declarations + mc:Ignorable baseline",
"format": "docx",
"category": "markup-compatibility",
"summary": "Pin the Word-2024 namespace baseline on the <w:document> root: every prefix Microsoft Word declares (w14, w15, w16, w16cid, w16se, w16cex, w16du, w16sdtdh, w16sdtfl, cx, cx1..cx8, aink, am3d, oel, wp14, wpc, wpg, wpi, wne, wps, ...) plus a matching mc:Ignorable whitelist. Protects against silent regressions of the python-docx 2026.05.2 template update.",
"spec": {
"source": "ecma-376-5-part-3",
"clause": "10",
"element": "mc:Ignorable",
"notes": "ECMA-376 Part 3 §10 defines the Markup Compatibility namespace (http://schemas.openxmlformats.org/markup-compatibility/2006) and its attributes mc:Ignorable, mc:ProcessContent, mc:MustUnderstand. A producer that emits elements from a namespace outside Part 1/2/4 must list the prefix in the enclosing element's mc:Ignorable attribute so legacy consumers can skip rather than reject those elements. Word 2010/2013/2016/2019/2021/2024 all extend this: Word 2010 added w14/wp14, 2013 added w15, 2015 w16se, 2016 w16cid+cx/cx1..cx8+aink, 2018 w16+w16cex, 2019 oel, 2020 w16sdtdh, 2023 w16du, 2024 w16sdtfl. This manifest pins the complete set as of Word 2024."
},
"fixtures": {
"machine": "docx/mc-namespace-declarations"
},
"generator": {
"python": "scripts/gen_mc_namespace_declarations.py"
},
"assertions": [
{
"id": "mc-ignorable-attribute-present",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "exist",
"description": "The root <w:document> element must carry an mc:Ignorable attribute."
},
{
"id": "mc-ignorable-lists-w14",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "match",
"value": "(^|\\s)w14(\\s|$)",
"description": "The Word-2010 prefix w14 must appear as a whitespace-separated token in mc:Ignorable."
},
{
"id": "mc-ignorable-lists-w15",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "match",
"value": "(^|\\s)w15(\\s|$)",
"description": "The Word-2013 prefix w15 must appear as a whitespace-separated token in mc:Ignorable."
},
{
"id": "mc-ignorable-lists-w16se",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "match",
"value": "(^|\\s)w16se(\\s|$)"
},
{
"id": "mc-ignorable-lists-w16cid",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "match",
"value": "(^|\\s)w16cid(\\s|$)"
},
{
"id": "mc-ignorable-lists-w16",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "match",
"value": "(^|\\s)w16(\\s|$)"
},
{
"id": "mc-ignorable-lists-w16cex",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "match",
"value": "(^|\\s)w16cex(\\s|$)"
},
{
"id": "mc-ignorable-lists-w16sdtdh",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "match",
"value": "(^|\\s)w16sdtdh(\\s|$)"
},
{
"id": "mc-ignorable-lists-w16sdtfl",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "match",
"value": "(^|\\s)w16sdtfl(\\s|$)"
},
{
"id": "mc-ignorable-lists-w16du",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "match",
"value": "(^|\\s)w16du(\\s|$)"
},
{
"id": "mc-ignorable-lists-wp14",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"mc": "http://schemas.openxmlformats.org/markup-compatibility/2006"
},
"xpath": "/w:document/@mc:Ignorable",
"must": "match",
"value": "(^|\\s)wp14(\\s|$)"
},
{
"id": "mc-namespace-w14-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='w14'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/word/2010/wordml",
"description": "The w14 prefix on <w:document> must be bound to the Word 2010 wordml namespace URI."
},
{
"id": "mc-namespace-w15-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='w15'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/word/2012/wordml"
},
{
"id": "mc-namespace-w16cid-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='w16cid'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/word/2016/wordml/cid"
},
{
"id": "mc-namespace-w16se-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='w16se'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/word/2015/wordml/symex"
},
{
"id": "mc-namespace-w16cex-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='w16cex'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/word/2018/wordml/cex"
},
{
"id": "mc-namespace-w16du-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='w16du'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/word/2023/wordml/word16du"
},
{
"id": "mc-namespace-w16sdtdh-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='w16sdtdh'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash"
},
{
"id": "mc-namespace-w16sdtfl-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='w16sdtfl'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock"
},
{
"id": "mc-namespace-cx-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='cx'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/drawing/2014/chartex"
},
{
"id": "mc-namespace-cx1-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='cx1'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex"
},
{
"id": "mc-namespace-cx8-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='cx8'])",
"must": "equal",
"value": "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex",
"description": "Anchor the end of the cx1..cx8 range so a partial regression (cx declared but cx8 dropped) does not slip by."
},
{
"id": "mc-namespace-mc-declared",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(/w:document/namespace::*[name()='mc'])",
"must": "equal",
"value": "http://schemas.openxmlformats.org/markup-compatibility/2006"
}
]
}
No rendered reference is available for this case.