Skip to content

Style and document cleanup — remove unused styles, numbering and media #115

Closed

Description

@toxicphreAK

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 list

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.xml is 5.5 KB in a document with no lists. An w:abstractNum no w:num points at, and a w:num no w:numPr points 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.xml lists fonts no w:rFonts names. 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 from w:lvl/w:pStyle in 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 basedOn must either be refused or must repoint the dependants. Silently orphaning them changes their formatting, because basedOn pointing nowhere means "based on docDefaults".
  • Latent styles are a behaviour declaration for styles that are not defined — removing an lsdException changes 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

  1. toxicphreAK commented on Aug 5, 2026

    @toxicphreAK
    OwnerAuthor

    Landed in #139. Styles.remove_unused(), LatentStyles.trim() and Document.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. Normal is never removed and the w: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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions