Separate OOXML parts¶
v0.1 capabilities (styles, fields, controls, protection) only mutated
the main document part and settings.xml. v0.2 introduces three
capabilities backed by separate parts that may not exist in a
fresh document:
/word/comments.xml(relationshipRT.COMMENTS)/word/footnotes.xml(relationshipRT.FOOTNOTES)/word/endnotes.xml(relationshipRT.ENDNOTES)
v0.4 adds a fourth for comment threading:
/word/commentsExtended.xml(relationshipRT_COMMENTS_EXTENDED)
v0.5 adds three more — the two remaining comment side-parts, and numbering:
/word/commentsIds.xml(relationshipRT_COMMENTS_IDS)/word/people.xml(relationshipRT_PEOPLE)/word/numbering.xml(relationshipRT.NUMBERING)
core/parts.py:get_or_create_part(doc, spec) is the single entry
point. Given a PartSpec describing the target, it tries
doc.part.part_related_by(spec.relationship_type); on KeyError it
parses spec.root_xml for the empty default root element, looks up
the correct part class from PartFactory.part_type_for, constructs the
part, and wires the relationship. Returns (part, root_element).
python-docx already registers CommentsPart for WML_COMMENTS — and
NumberingPart for WML_NUMBERING — at package-import time. It does
not register the footnote, endnote, commentsExtended, commentsIds, or
people content types, so core/parts.py does — installing internal
_FootnotesPart / _EndnotesPart / _CommentsExtendedPart /
_CommentsIdsPart / _PeoplePart subclasses of XmlPart with
PartFactory.part_type_for.setdefault(...). Without that registration,
an existing document with footnotes would deserialize the part as the
default Part (blob-only), and part.element would not exist.
Seven pre-baked PartSpec constants cover every need through v0.5:
COMMENTS_SPEC, COMMENTS_EXTENDED_SPEC, COMMENTS_IDS_SPEC,
PEOPLE_SPEC, NUMBERING_SPEC, FOOTNOTES_SPEC, ENDNOTES_SPEC.
Custom callers can build their own. The comment side-part content and
relationship types are Microsoft extensions with no member in
python-docx's enums, so they ship alongside as the
CT_COMMENTS_EXTENDED / RT_COMMENTS_EXTENDED, CT_COMMENTS_IDS /
RT_COMMENTS_IDS, and CT_PEOPLE / RT_PEOPLE string constants.
Numbering is the one that routes around a broken path¶
NUMBERING_SPEC exists because python-docx's own path is broken, not
merely absent. DocumentPart.numbering_part is documented as creating
an empty part when none is present, but it does that through
NumberingPart.new() — an unimplemented stub that raises
NotImplementedError. It therefore works only for documents that
already carry numbering.xml, which the bundled template does, hiding
the failure. Any document from LibreOffice, Pandoc, or a stripped
template hits the stub.
NUMBERING_SPEC routes around it, and the cascade
resolver's _numbering_root (layer 4) reads the
relationship directly for the same reason — note that getattr(part,
"numbering_part", None) does not protect you here, since getattr's
default only swallows AttributeError.