docx_plus.numbering.define¶
Authoring list definitions in numbering.xml — the largest remaining
python-docx gap. python-docx has no CT_AbstractNum and no CT_Lvl
class, so nothing in it can express what a list looks like.
OOXML splits a list in two: a <w:abstractNum> holds up to nine <w:lvl>
children describing each depth, and a <w:num> is an instance pointing
at one. Paragraphs reference the instance, never the abstract definition
— which is what makes restarting possible.
Architecture walkthrough: Custom numbering.
Size the hanging indent to the number
hanging is the width reserved for the number, and the gap between
number and text is a tab stop at indent. If the number is wider
than hanging the tab collapses and a cumulative outline renders
1.1.1.On-call lead rather than 1.1.1. On-call lead. Deeper levels
of a %1.%2.%3. outline need progressively larger values.
docx_plus.numbering.define ¶
Authoring list definitions in numbering.xml.
python-docx cannot write a list definition at all. It ships a
NumberingPart, but docx/oxml/numbering.py defines classes only
for w:numbering, w:num, w:lvlOverride, and w:numPr —
there is no CT_AbstractNum and no CT_Lvl, so nothing in it can
express what a list looks like: the number format, the level text, the
start value, the indents, the bullet glyph. Callers hand-write XML.
The OOXML model has two halves:
<w:abstractNum>— the definition. Up to nine<w:lvl>children, each describing one outline level.<w:num>— an instance pointing at an abstract definition by id. Paragraphs reference this id, never the abstract one.
The indirection is what makes restarting possible: a second w:num
over the same w:abstractNum is an independent sequence with
identical formatting. See :func:~docx_plus.numbering.restart_list.
This module imports only from docx_plus.core and its sibling
docx_plus.numbering.registry (SPEC §9.1).
LevelDefinition
dataclass
¶
LevelDefinition(
fmt: str = "decimal",
text: str = "%1.",
start: int = 1,
indent: int | None = None,
hanging: int | None = None,
justify: Justification = "left",
suffix: Suffix = "tab",
restart_after: int | None = None,
font: str | None = None,
)
One outline level of a list definition — a <w:lvl>.
Attributes:
| Name | Type | Description |
|---|---|---|
fmt |
str
|
ECMA-376 17.18.59 |
text |
str
|
The |
start |
int
|
First value of the counter. Defaults to |
indent |
int | None
|
Left indent in twips (1/20 pt; 720 = 0.5"). |
hanging |
int | None
|
Hanging indent in twips — the width reserved for the
number, measured back from Make this wider than the rendered |
justify |
Justification
|
How the number is aligned within the hanging indent. |
suffix |
Suffix
|
What separates the number from the text —
|
restart_after |
int | None
|
The |
font |
str | None
|
Font applied to the number or bullet glyph only, not the
paragraph text. Required in practice for symbol bullets —
|
Raises:
| Type | Description |
|---|---|
InvalidLevelError
|
If any field is outside its ECMA-376 type. |
__post_init__ ¶
Validate the fields against their ECMA-376 simple types.
Source code in docx_plus/numbering/define.py
InvalidLevelError ¶
Bases: DocxPlusError, ValueError
Raised for a malformed :class:LevelDefinition, level list, or level index.
Also what :func:apply_list / :func:restart_list raise for a
level outside 0–8 or a negative start, so one except covers
every "that is not a valid level" complaint in this package. Subclasses
ValueError so existing except ValueError: clauses still catch
it; also subclasses :class:DocxPlusError per SPEC §9.7.
define_list_definition ¶
define_list_definition(
doc: Document,
*,
levels: list[LevelDefinition] | tuple[LevelDefinition, ...],
name: str | None = None,
style_link: str | None = None,
num_style_link: str | None = None,
multi_level_type: MultiLevelType | None = None,
num_registry: NumIdRegistry | None = None,
abstract_registry: AbstractNumIdRegistry | None = None,
) -> int
Write a list definition and return the numId to apply.
Creates one <w:abstractNum> holding levels, plus one
<w:num> instance pointing at it. The returned id is the
instance's — that is what :func:~docx_plus.numbering.apply_list
takes and what a paragraph's w:numPr stores.
numbering.xml is created if the document has none. That is not
the same as python-docx's doc.part.numbering_part, which
fabricates through an unimplemented stub and raises; see
:data:~docx_plus.core.parts.NUMBERING_SPEC.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc
|
Document
|
The python-docx :class: |
required |
levels
|
list[LevelDefinition] | tuple[LevelDefinition, ...]
|
One :class: |
required |
name
|
str | None
|
Optional |
None
|
style_link
|
str | None
|
Style id this definition is the numbering for —
the paired half of a "list style". Mutually exclusive with
|
None
|
num_style_link
|
str | None
|
Style id whose numbering this definition
defers to. Mutually exclusive with |
None
|
multi_level_type
|
MultiLevelType | None
|
|
None
|
num_registry
|
NumIdRegistry | None
|
Pre-existing |
None
|
abstract_registry
|
AbstractNumIdRegistry | None
|
Pre-existing |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The |
Raises:
| Type | Description |
|---|---|
InvalidLevelError
|
If |
ValueError
|
If both |
Example
from docx import Document from docx_plus.numbering import LevelDefinition, apply_list, define_list_definition doc = Document() num = define_list_definition(doc, levels=[ ... LevelDefinition(fmt="decimal", text="%1.", indent=720, hanging=360), ... LevelDefinition(fmt="lowerLetter", text="%2)", indent=1440, hanging=360), ... ]) apply_list(doc.add_paragraph("top level"), num) apply_list(doc.add_paragraph("nested"), num, level=1)
Source code in docx_plus/numbering/define.py
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 | |
define_bullet_list ¶
define_bullet_list(
doc: Document,
*,
levels: int = 1,
indent_step: int = 720,
hanging: int = 360,
num_registry: NumIdRegistry | None = None,
abstract_registry: AbstractNumIdRegistry | None = None,
) -> int
Define a bulleted list with Word's default glyph cycle.
Word cycles three bullets by depth — a filled round bullet, a hollow
o, then a filled square — each needing its own symbol font to
render as anything but a Latin letter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc
|
Document
|
The python-docx :class: |
required |
levels
|
int
|
How many outline levels to define, 1 to 9. |
1
|
indent_step
|
int
|
Twips of left indent added per level (720 = 0.5"). |
720
|
hanging
|
int
|
Hanging indent in twips for every level. |
360
|
num_registry
|
NumIdRegistry | None
|
Pre-existing |
None
|
abstract_registry
|
AbstractNumIdRegistry | None
|
Pre-existing |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The |
int
|
func: |
Raises:
| Type | Description |
|---|---|
InvalidLevelError
|
If |
Example
from docx import Document from docx_plus.numbering import apply_list, define_bullet_list doc = Document() bullets = define_bullet_list(doc, levels=2) apply_list(doc.add_paragraph("first"), bullets)
Source code in docx_plus/numbering/define.py
define_numbered_list ¶
define_numbered_list(
doc: Document,
*,
levels: int = 1,
indent_step: int = 720,
hanging: int = 360,
num_registry: NumIdRegistry | None = None,
abstract_registry: AbstractNumIdRegistry | None = None,
) -> int
Define a numbered list with Word's default format cycle.
Word cycles 1. → a. → i. by depth. Each level's counter
stands alone; for the legal-outline shape (1.1, 1.1.1) build
the levels yourself with text="%1.%2." and pass them to
:func:define_list_definition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc
|
Document
|
The python-docx :class: |
required |
levels
|
int
|
How many outline levels to define, 1 to 9. |
1
|
indent_step
|
int
|
Twips of left indent added per level (720 = 0.5"). |
720
|
hanging
|
int
|
Hanging indent in twips for every level. |
360
|
num_registry
|
NumIdRegistry | None
|
Pre-existing |
None
|
abstract_registry
|
AbstractNumIdRegistry | None
|
Pre-existing |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The |
int
|
func: |
Raises:
| Type | Description |
|---|---|
InvalidLevelError
|
If |
Example
from docx import Document from docx_plus.numbering import apply_list, define_numbered_list doc = Document() steps = define_numbered_list(doc, levels=3) apply_list(doc.add_paragraph("step one"), steps)