Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Implement NumberingPart.new() to auto-create an empty numbering part
DocumentPart.numbering_part documents that it creates an empty numbering
part when one is not present, but NumberingPart.new() raised
NotImplementedError, so opening a .docx without a numbering part and
accessing the numbering part failed. Implement NumberingPart.new(package)
mirroring the StylesPart/SettingsPart/CommentsPart factories, backed by a
new minimal default-numbering.xml template, and pass the package from
DocumentPart.numbering_part.
  • Loading branch information
xtaypp committed Sep 8, 2026
commit 4749cb88dfee8f912c2ecc00bbf95925c7ed3a9f
3 changes: 2 additions & 1 deletion src/docx/parts/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ def numbering_part(self) -> NumberingPart:
try:
return cast(NumberingPart, self.part_related_by(RT.NUMBERING))
except KeyError:
numbering_part = NumberingPart.new()
assert self.package is not None
numbering_part = NumberingPart.new(self.package)
self.relate_to(numbering_part, RT.NUMBERING)
return numbering_part

Expand Down
34 changes: 29 additions & 5 deletions src/docx/parts/numbering.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,41 @@
"""|NumberingPart| and closely related objects."""

from ..opc.part import XmlPart
from ..shared import lazyproperty
from __future__ import annotations

import os
from typing import TYPE_CHECKING, cast

from docx.opc.constants import CONTENT_TYPE as CT
from docx.opc.packuri import PackURI
from docx.opc.part import XmlPart
from docx.oxml.parser import parse_xml
from docx.shared import lazyproperty

if TYPE_CHECKING:
from docx.oxml.numbering import CT_Numbering
from docx.package import Package


class NumberingPart(XmlPart):
"""Proxy for the numbering.xml part containing numbering definitions for a document
or glossary."""

@classmethod
def new(cls) -> "NumberingPart":
"""Newly created numbering part, containing only the root ``<w:numbering>`` element."""
raise NotImplementedError
def new(cls, package: Package) -> "NumberingPart":
"""Return a newly created numbering part, containing only the root ``<w:numbering>``
element."""
partname = PackURI("/word/numbering.xml")
content_type = CT.WML_NUMBERING
element = cast("CT_Numbering", parse_xml(cls._default_numbering_xml()))
return cls(partname, content_type, element, package)

@classmethod
def _default_numbering_xml(cls) -> bytes:
"""Return a bytestream containing XML for a default numbering part."""
path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-numbering.xml")
with open(path, "rb") as f:
xml_bytes = f.read()
return xml_bytes

@lazyproperty
def numbering_definitions(self):
Expand Down
5 changes: 5 additions & 0 deletions src/docx/templates/default-numbering.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:numbering
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
/>