Repository navigation
Style and document cleanup — remove unused styles, numbering and media #115
Description
Once #114 can say which styles are reachable, the useful operation is dropping the rest.
BaseStyle.delete() already exists, so this is not about removing one style — it is about removing the right set safely, and about the neighbouring dead weight that travels with it.
The motivating number: a document created by this library carries 164 style definitions and 137 latent-style exceptions. A one-paragraph document references one style. word/styles.xml is 349 KB of the 826 KB uncompressed total.
Shape
document.styles.remove_unused() # prune to the reachable closure
document.styles.remove_unused(keep=["Quote", "Caption"]) # plus ones you plan to apply
document.styles.latent_styles.trim() # drop the lsdException listStyles.remove_unused(*, keep=(), keep_defaults=True)— deletes every style outside the reachability closure from Style usage analysis — which styles a document actually uses #114, plus anything named inkeepand its own closure- Returns what it removed, so the caller can report it
Beyond styles — the same cleanup family
Worth deciding whether these ride along or get their own issue. They are the same "this document carries things nothing points at" problem:
- Unused numbering definitions.
word/numbering.xmlis 5.5 KB in a document with no lists. Anw:abstractNumnow:numpoints at, and aw:numnow:numPrpoints at, are both dead. Make numbering definitions writable - define a list from scratch #98 is adding the write side of numbering, so the machinery to walk it is arriving anyway. - Orphan media. An image part related from nothing, left behind when a run or a floating shape was deleted. The
.delete()API added in 2.0.0 makes this reachable from ordinary use — deleting a paragraph containing a picture leaves the image part in the package. - Unused fonts.
word/fontTable.xmllists fonts now:rFontsnames. Cosmetic; low value; mentioned only so it is not rediscovered later.
A single Document.cleanup() that runs the lot, with flags, is probably the shape people want. Keep the individual operations public too — "remove unused styles but leave my media alone" is a reasonable thing to want.
Watch out for
- This is destructive and the closure is the only thing standing between it and a broken document. If Style usage analysis — which styles a document actually uses #114 misses an edge — a style referenced only from a header, only as a
w:basedOn, only fromw:lvl/w:pStylein numbering —remove_unused()silently produces a document whose formatting has changed. Land the analysis with its tests first, and make the closure the single implementation both features use. - Never remove the
w:default="1"styles for any type, whatever the closure says. - Never remove
Normal. Word repairs a document that lacks it, and the repair dialogue is worse than the bloat. - Deleting a style that other styles are
basedOnmust either be refused or must repoint the dependants. Silently orphaning them changes their formatting, becausebasedOnpointing nowhere means "based on docDefaults". - Latent styles are a behaviour declaration for styles that are not defined — removing an
lsdExceptionchanges whether the style shows in Word's gallery, not the document's appearance. Different risk profile from removing a definition; keep the two operations separate. - Byte-reproducible output (
tests/test_reproducible.py) must still hold after a cleanup pass.
Note on scope
This does not do anything to the bundled template — that is #113. This is an operation callers run on their own documents. The two overlap only in that the bundled template is the most obvious thing to point it at.
Activity
toxicphreAK commentedon Aug 5, 2026
Landed in #139.
Styles.remove_unused(),LatentStyles.trim()andDocument.cleanup(), which also drops orphan numbering and media. The closure from #114 is the single implementation behind both, which is the safeguard the issue asked for.Normalis never removed and thew:default="1"styles are kept unless explicitly asked for. Latent styles are a separate flag, off by default, because they change Word's gallery rather than the rendering.