# python-docx-ng > A Python library for creating and updating Microsoft Word (.docx) files. python-docx-ng is a Python library for creating and updating Microsoft Word (.docx) files. It is a downstream superset of python-docx; the importable package is `docx`. # User guide # python-docx-ng `python-docx-ng` is a Python library for creating and updating Microsoft Word (`.docx`) files. It is a downstream superset of [python-docx](https://github.com/python-openxml/python-docx) — the distribution is `python-docx-ng`, but the importable package is `docx`: ``` from docx import Document ``` The two distributions cannot be installed side by side. ## What it can do ``` from docx import Document from docx.shared import Inches document = Document() document.add_heading("Document Title", 0) p = document.add_paragraph("A plain paragraph having some ") p.add_run("bold").bold = True p.add_run(" and some ") p.add_run("italic.").italic = True document.add_heading("Heading, level 1", level=1) document.add_paragraph("Intense quote", style="Intense Quote") document.add_paragraph("first item in unordered list", style="List Bullet") document.add_paragraph("first item in ordered list", style="List Number") document.add_picture("monty-truth.png", width=Inches(1.25)) records = ( (3, "101", "Spam"), (7, "422", "Eggs"), (4, "631", "Spam, spam, eggs, and spam"), ) table = document.add_table(rows=1, cols=3) hdr_cells = table.rows[0].cells hdr_cells[0].text = "Qty" hdr_cells[1].text = "Id" hdr_cells[2].text = "Desc" for qty, id, desc in records: row_cells = table.add_row().cells row_cells[0].text = str(qty) row_cells[1].text = id row_cells[2].text = desc document.add_page_break() document.save("demo.docx") ``` ## Getting started - **[Installation](https://toxicphreak.github.io/python-docx-ng/user/install/index.md)** — install from PyPI with `pip` or `uv`. - **[Quickstart](https://toxicphreak.github.io/python-docx-ng/user/quickstart/index.md)** — open a document, add content, save it. - **[API reference](https://toxicphreak.github.io/python-docx-ng/api/docx/index.md)** — every module, class and property. - **[Migrating from 0.9](https://toxicphreak.github.io/python-docx-ng/user/migrating-from-0-9/index.md)** — what changed in 2.0.0. ## For language models The documentation is published in the [llms.txt](https://toxicphreak.github.io/python-docx-ng/llms.txt) format, with the full text at [llms-full.txt](https://toxicphreak.github.io/python-docx-ng/llms-full.txt). Point any tool that reads `llms.txt` at those URLs — for example [mcpdoc](https://github.com/langchain-ai/mcpdoc), which serves them to an editor over MCP. # API basics The API for `python-docx` is designed to make doing simple things simple, while allowing more complex results to be achieved with a modest and incremental investment of understanding. It's possible to create a basic document using only a single object, the `api-Document` object returned when opening a file. The methods on `api-Document` allow *block-level* objects to be added to the end of the document. Block-level objects include paragraphs, inline pictures, and tables. Headings, bullets, and numbered lists are simply paragraphs with a particular style applied. In this way, a document can be "written" from top to bottom, roughly like a person would if they knew exactly what they wanted to say This basic use case, where content is always added to the end of the document, is expected to account for perhaps 80% of actual use cases, so it's a priority to make it as simple as possible without compromising the power of the overall API. ## Inline objects Each block-level method on `api-Document`, such as `add_paragraph()`, returns the block-level object created. Often the reference is unneeded; but when inline objects must be created individually, you'll need the block-item reference to do it. ... add example here as API solidifies ... # Bookmarks A bookmark names a span of content so something else can point at it — a cross-reference, a hyperlink, or a `PAGEREF` field asking which page it fell on. In OOXML a bookmark is not a container. It is a pair of markers, `w:bookmarkStart` and `w:bookmarkEnd`, that can sit anywhere relative to the content between them; that is why a bookmark can span paragraphs, or half a table. ## Creating one Over a whole paragraph: ``` from docx import Document document = Document() heading = document.add_heading("Introduction", level=1) heading.add_bookmark("intro") ``` Over a narrower range, from one run through another: ``` paragraph = document.add_paragraph() first = paragraph.add_run("the ") middle = paragraph.add_run("important") last = paragraph.add_run(" bit") first.mark_bookmark_range(last, "highlight") ``` Passing the same run as both ends bookmarks just that run. Warning A bookmark name must be unique in the document. Word treats a duplicate as a second, separate bookmark, and the two then compete for anything that refers to the name. ## Reading them Document.bookmarks supports `len()`, iteration, indexed access and lookup by name: ``` len(document.bookmarks) document.bookmarks[0].name document.bookmarks["intro"].text document.bookmarks.get("missing") # -> None rather than KeyError ``` Each Bookmark carries: - name and id - text — the text it spans - is_closed — whether the matching `w:bookmarkEnd` is present. A start without an end is malformed but does occur in the wild - is_hidden — whether Word maintains it for itself ### Word's own bookmarks Word keeps bookmarks of its own: `_GoBack` for the last edit position, and a `_Toc…` anchor for every heading a table of contents points at. These are hidden by default, because they are noise in almost every case: ``` for bookmark in document.bookmarks.iter_all(include_hidden=True): print(bookmark.name) ``` ## Deleting ``` document.bookmarks["intro"].delete() ``` This removes both markers and leaves the content between them alone. ## Pointing at a bookmark Bookmarks exist to be referred to. From a field, so Word renders the target's text or page number ([Fields](https://toxicphreak.github.io/python-docx-ng/user/fields/index.md)): ``` from docx import fields paragraph = document.add_paragraph("See ") paragraph.add_field(fields.cross_reference("intro")) paragraph.add_run(" on page ") paragraph.add_field(fields.page_reference("intro")) ``` Or from a hyperlink, which jumps there when clicked: ``` paragraph = document.add_paragraph() paragraph.add_hyperlink("back to the introduction", fragment="intro") ``` add_hyperlink() takes `address` for an external URL and `fragment` for a location inside the document; give both to link to an anchor in another document. # Style usage and cleanup A document created by this library defines 168 styles. A one-paragraph document references ten of them. Most of `word/styles.xml` is Word's built-in gallery, carried along because the template it came from carried it. This page is about finding that out and doing something about it. The command-line front end for everything here is [`python -m docx`](https://toxicphreak.github.io/python-docx-ng/user/cli/index.md). ## Which styles are in use ``` usage = document.styles.usage() print(usage) # 168 styles defined, 10 in use, 158 unused; 131 latent style exceptions ``` Styles.usage() returns a StyleUsage, a named tuple of style **ids**: | Field | Meaning | | ------------------ | ------------------------------------------------------------------------------------------- | | `defined` | every style id in the styles part, in document order | | `used` | the ids in the reachability closure of what the document applies | | `unused` | `defined` minus `used`, in document order | | `reference_counts` | how many times each id is *directly* applied; a style reachable only indirectly counts zero | | `latent` | names declared in `w:latentStyles` that the document does not define | Iterating a `StyleUsage` yields the ids in use. The convenience accessors work in names, as the rest of the styles API does: ``` for style in document.styles.unused: print(style.name) document.styles["Heading 7"].in_use # -> False ``` ### "Used" is a closure, not a scan A scan of `w:pStyle` in `word/document.xml` gets the wrong answer in four ways, and each of them is a real document: - **Every story part counts, not just the body.** Headers, footers, footnotes, endnotes and comments are separate parts with their own style references. - **A style can be reachable without ever being applied** — as the `w:basedOn` of a used style, as its `w:next`, as its `w:link`, from a numbering level's `w:pStyle`, or from the `w:tblStylePr` conditional formatting inside a table style. - **The `w:default="1"` styles apply to content that names no style at all.** They are used by definition and have zero direct references. - **`Normal` is never dead.** Word repairs a document that lacks it, and the repair dialogue is worse than the bloat. So "used" is computed as a reachability closure: seed from the direct applications, the default styles and `Normal`, then follow the reference edges until the set stops growing. A dangling edge — a `w:basedOn` naming a style that is not defined — is a dead end rather than an error, because that is legal and common. Two keyword arguments adjust the seed: ``` # -- treat a style you are about to apply as used, along with its dependencies -- document.styles.usage(keep=("Quote",)) # -- the narrower question: what is reachable by reference alone? -- document.styles.usage(seed_defaults=False) ``` ## Removing the unused ones ``` removed = document.styles.remove_unused() len(removed) # -> 158 ``` Styles.remove_unused() returns the ids it removed. It takes the same `keep`, and `keep_defaults` (`True` by default) protects the `w:default="1"` styles. Warning This is destructive and it is not undoable within the open document. A style you intend to apply later is unused *now*, so name it in `keep`: ``` document.styles.remove_unused(keep=("Quote", "Intense Quote")) ``` ## Cleaning up the whole document Document.cleanup() runs three passes and returns a CleanupResult: ``` result = document.cleanup() print(result) # removed 158 styles, 3 numbering definitions, 3 abstract numbering # definitions, 0 media parts and 0 latent style exceptions result.styles # -> the style ids result.num_ids # -> the w:num numIds result.abstract_num_ids # -> the w:abstractNum abstractNumIds result.media # -> the partnames of the image parts result.latent_styles # -> how many w:lsdException overrides went ``` The three passes, each of which can be turned off: **Styles** — the closure above. **Numbering definitions** — a `w:num` no `w:numPr` points at is dead, and a `w:abstractNum` no surviving `w:num` points at is dead too. Chains through `w:numStyleLink` are followed, so a definition kept alive only indirectly survives. **Orphan media** — an image part related from nothing, which is what .delete() on a paragraph holding a picture leaves behind. Only the document part's own image relationships are considered: a header image belongs to the header part and is not orphaned by anything happening in the body. ``` document.cleanup( styles=True, numbering=True, media=True, latent_styles=False, # -- off by default, see below -- keep=("Quote",), ) ``` Styles are pruned before numbering, so a numbering definition kept alive only by a style that is about to go is correctly seen as dead. ### Latent styles are separate on purpose `latent_styles` is `False` by default. A `w:lsdException` is a *behaviour declaration* for a style the document does not define — whether it shows in Word's style gallery, and in what order. Removing one changes what a user sees in the UI rather than how the document renders, which is a different kind of change from removing a style definition. LatentStyles.trim() does it on its own, and returns how many went. It removes *every* override — the bundled template carries 137, one per built-in Word might offer — while leaving the defaults on the `w:latentStyles` element itself, which are what the overrides were overriding: ``` document.styles.latent_styles.trim() # -> 137 ``` That count is not the same as `usage.latent`, which is the narrower set of latent names the document does not also *define*. The two answer different questions and it is worth not confusing them. ## What this is worth For a document generated from the bundled template and then cleaned: | | Uncompressed | On disk | | ----------------- | ------------ | ------- | | As generated | 376 KB | 20 KB | | After `cleanup()` | 40 KB | 9 KB | Almost all of it is `word/styles.xml`. Whether that matters depends on what you are doing — it is nothing for one document and a great deal for a hundred thousand of them. ## Moving styles between documents The other side of the same coin: rather than pruning what a template brought, bring only what you want. See [Templates and embedded files](https://toxicphreak.github.io/python-docx-ng/user/templates/#importing-a-templates-styles). # Command line `python -m docx` answers the questions people ask about a `.docx` one at a time: what styles it defines, which of them are actually used, and why the file is 900 KB. It is deliberately thin. Every subcommand maps onto one public library operation and holds no logic of its own, so there is nothing reachable through the command line that is not reachable from Python. There is no new dependency — it is `argparse` only. ``` $ python -m docx --help usage: python -m docx [-h] {info,styles,cleanup} ... Inspect and clean up Word documents. positional arguments: {info,styles,cleanup} info list the parts of a document with their sizes styles inspect a document's styles cleanup remove unused styles, numbering and media ``` ## `info` — what is in the package The fastest answer to "why is this file like this": the part inventory, biggest first. ``` $ python -m docx info report.docx 350527 word/styles.xml 7642 word/theme/theme1.xml 5513 word/numbering.xml 2811 word/fontTable.xml 2535 word/settings.xml 1586 word/document.xml ... 375746 TOTAL uncompressed (20199 on disk) ``` This is how the 438 KB `word/stylesWithEffects.xml` that used to ship in the bundled template turned up. `--json` emits the same data for a script to consume. ## `styles` — what it defines and what it uses ``` $ python -m docx styles report report.docx 168 styles defined (30 character, 1 list, 37 paragraph, 100 table) 10 in use, 158 unused 131 latent style exceptions ``` "In use" is a reachability closure over every story part, not a scan of the body — see [Style usage and cleanup](https://toxicphreak.github.io/python-docx-ng/user/cleanup/index.md) for what that means and why the distinction matters. `styles list` prints the names, optionally filtered: ``` $ python -m docx styles list report.docx --used Normal Default Paragraph Font Normal Table No List List Bullet ... ``` `--used` and `--unused` are mutually exclusive; asking for both would match nothing. `styles extract` writes the styles out to a document of their own, which is how you turn a document you like the look of into a template: ``` $ python -m docx styles extract house-style.docx -o house.dotx --as-template $ python -m docx styles extract report.docx -o headings.docx --names "Heading 1,Heading 2" ``` Named styles come with their `basedOn` / `next` / `link` closure, so the extract is a document that opens without repair. ## `cleanup` — remove what nothing points at ``` $ python -m docx cleanup report.docx -o small.docx removed 158 styles, 3 numbering definitions, 3 abstract numbering definitions, 0 media parts and 0 latent style exceptions wrote small.docx ``` Two rules this command keeps: **It never writes to its input.** `-o` is required. Someone will point it at their only copy. **`--check` is a CI gate.** It reports what *would* be removed and exits non-zero if anything would be, so a pipeline can fail a build that has grown dead weight: ``` $ python -m docx cleanup report.docx --check would have removed 158 styles, 3 numbering definitions, ... $ echo $? 1 ``` | Flag | Effect | | ----------------- | ----------------------------------------------------------------------------------------------------------------------- | | `--keep NAMES` | comma-separated style names to preserve, along with their dependencies | | `--no-styles` | leave the style definitions alone | | `--no-numbering` | leave the numbering definitions alone | | `--no-media` | leave orphaned image parts alone | | `--latent-styles` | *also* drop the latent-style exceptions — off by default, because this changes what a user sees in Word's style gallery | | `--json` | emit the full list of what went, not just the counts | ## Exit codes These end up in scripts, so they are part of the contract: | Code | Meaning | | ---- | ---------------------------------------------------------------------------------- | | `0` | success | | `1` | `cleanup --check` found something to remove | | `2` | the document could not be opened, or an argument named something that is not there | A document that cannot be opened is reported as a message on stderr, never as a traceback — including the password-protected case, which is a different thing from a corrupt file: ``` $ python -m docx info nope.docx error: cannot open document: Package not found at 'nope.docx' ``` # Working with Comments Word allows *comments* to be added to a document. This is an aspect of the *reviewing* feature-set and is typically used by a second party to provide feedback to the author without changing the document itself. The procedure is simple: - You select some range of text with the mouse or Shift+Arrow keys - You press the *New Comment* button (Review toolbar) - You type or paste in your comment A comment can only be added to the main document. A comment cannot be added in a header, a footer, or within a comment. A comment can\_ be added to a footnote or endnote, but those are not yet supported by *python-docx*. **Comment Anatomy.** Each comment has two parts, the *comment-reference* and the *comment-content*: The **comment-refererence**, sometimes *comment-anchor*, is the text in the main document you selected before pressing the *New Comment* button. It is a so-called *range* in the main document that starts at the first selected character and ends after the last one. The **comment-content**, sometimes just *comment*, is whatever content you typed or pasted in. The content for each comment is stored in a separate comment object, and these comment objects are stored in a separate *comments-part* (part-name `word/comments.xml`), not in the main document. Each comment is assigned a unique id when it is created, allowing the comment reference to be associated with its content and vice versa. **Comment Reference.** The comment-reference is a *range*. A range must both start and end at an even *run* boundary. Intuitively, a range corresponds to a *selection* of text in the Word UI, one formed by dragging with the mouse or using the *Shift-Arrow* keys. In the XML, this range is delimited by a start marker *\* and an end marker *\*, both of which contain the *id* of the comment they delimit. The start marker appears before the run starting with the first character of the range and the end marker appears immediately after the run ending with the last character of the range. Adding a comment that references an arbitrary range of text in an existing document may require splitting runs on the desired character boundaries. In general a range can span paragraphs, such that the range begins in one paragraph and ends in a later paragraph. However, a range must enclose *contiguous* runs, such that a range that contains only two vertically adjacent cells in a multi-column table is not possible (even though Word allows such a selection with the mouse). **Comment Content.** Interestingly, although commonly used to contain a single line of plain text, the comment-content can contain essentially any content that can appear in the document body. This includes rich text with emphasis, runs with a different typeface and size, both paragraph and character styles, hyperlinks, images, and tables. Note that tables do not appear in the comment as displayed in the *comment-sidebar* although they do apper in the *reviewing-pane*. **Comment Metadata.** Each comment can be assigned *author*, *initals*, and *date* metadata. In Word, these fields are assigned automatically based on values in `Settings > User` of the installed Word application. These might be configured automatically in an enterprise installation, based on the user account, but by default they are empty. *author* metadata is required, although silently assigned the empty string by Word if the user name is not configured. *initials* is optional, but always set by Word, to the empty string if not configured. *date* is also optional, but always set by Word to the UTC date and time the comment was added, with seconds resolution (no milliseconds or microseconds). **Additional Features.** Later versions of Word allow a comment to be *resolved*. A comment in this state will appear grayed-out in the Word UI. Later versions of Word also allow a comment to be *replied to*, forming a *comment thread*. Neither of these features is supported by the initial implementation of comments in *python-docx*. **Applicability.** Note that comments cannot be added to a header or footer and cannot be nested inside a comment itself. In general the *python-docx* API will not allow these operations but if you outsmart it then the resulting comment will either be silently removed or trigger a repair error when the document is loaded by Word. ## Adding a Comment A simple example is adding a comment to a paragraph: ``` >>> from docx import Document >>> document = Document() >>> paragraph = document.add_paragraph("Hello, world!") >>> comment = document.add_comment( ... runs=paragraph.runs, ... text="I have this to say about that" ... author="Steve Canny", ... initials="SC", ... ) >>> comment >>> comment.id 0 >>> comment.author 'Steve Canny' >>> comment.initials 'SC' >>> comment.date datetime.datetime(2025, 6, 11, 20, 42, 30, 0, tzinfo=datetime.timezone.utc) >>> comment.text 'I have this to say about that' ``` The API documentation for Document.add_comment provides further details. ## Accessing and using the Comments collection The comments collection is accessed via the Document.comments property: ``` >>> comments = document.comments >>> comments >>> len(comments) 1 ``` The comments collection supports random access to a comment by its id: ``` >>> comment = comments.get(0) >>> comment ``` ## Adding rich content to a comment A comment is a block-item container, just like the document body or a table cell, so it can contain any content that can appear in those places. It does not contain page-layout sections and cannot contain a comment reference, but it can contain multiple paragraphs and/or tables, and runs within paragraphs can have emphasis such as bold or italic, and have images or hyperlinks. A comment created with *text=""* will contain a single paragraph with a single empty run containing the so-called *annotation reference* but no text. It's probably best to leave this run as it is but you can freely add additional runs to the paragraph that contain whatever content you like. The methods for adding this content are the same as those used for the document and table cells: ``` >>> paragraph = document.add_paragraph("The rain in Spain.") >>> comment = document.add_comment( ... runs=paragraph.runs, ... text="", ... ) >>> cmt_para = comment.paragraphs[0] >>> cmt_para.add_run("Please finish this thought. I believe it should be ") >>> cmt_para.add_run("falls mainly in the plain.").bold = True ``` ## Updating comment metadata The author and initials metadata can be updated as desired: ``` >>> comment.author = "John Smith" >>> comment.initials = "JS" >>> comment.author 'John Smith' >>> comment.initials 'JS' ``` # Copying content `copy_to()` duplicates a paragraph, a run, a table row or a whole table — into the same document or a different one. ``` paragraph.copy_to(document) row.copy_to(table) table.copy_to(other_document) run.copy_to(paragraph) ``` ## Why not `copy.deepcopy()` Duplicating content is the operation most often written by hand against this library, and the hand-written version works for plain text and quietly breaks for anything interesting. `copy_to()` is a deep copy plus a repair for each of these: - **A picture's `r:embed`** names a relationship id belonging to the *source* part, so a copied image is either the wrong image or a dangling reference. - **A hyperlink's `r:id`** has the same problem, and points at an external target that may not exist in the destination package. - **`wp:docPr/@id`** must be unique document-wide, and a deep copy duplicates it. - **A bookmark name** is document-wide, and a duplicate is not a copy of a bookmark: Word treats it as a second bookmark of the same name, and anything referring to that name resolves to whichever it finds first. - **Across documents**, a `w:pStyle` names a style that may not be there and a `w:numPr` names a `numId` that certainly means something else. Relating the same image into the destination reuses the sha1 deduplication, so copying a picture into a document that already has it does not add a second copy of the bytes. ## Where the copy goes Appended to the end of the container by default; `before` and `after` place it: ``` paragraph.copy_to(document) # -> at the end of the body paragraph.copy_to(document, before=document.paragraphs[0]) paragraph.copy_to(cell) # -> into a table cell row.copy_to(table, after=table.rows[0]) ``` Appending to a body that ends in a `w:sectPr` inserts before it, since the section properties have to stay last. Passing both `before` and `after` raises `ValueError`. Each returns a proxy for the copy, so a repeated row is one line: ``` template_row = table.rows[0] for record in records: new_row = template_row.copy_to(table) new_row.cells[0].text = record.name ``` ## Copying into another document A cross-document copy brings what the content refers to with it. `missing_style` decides what happens to a style the destination does not define: | Value | Behaviour | | ------------------ | ------------------------------------------------------------------------------- | | `"copy"` (default) | copy the style across with its `basedOn` / `next` / `link` closure | | `"drop"` | remove the style reference; the content falls back to the destination's default | | `"raise"` | raise `ValueError` | ``` paragraph.copy_to(other_document, missing_style="drop") ``` A style reference the *source* cannot resolve either is left alone — it is already broken there, and inventing a target would be worse than carrying the break over. Numbering comes across too, and this is the part that is easy to get wrong by hand. A `w:numId` means something else in the destination, so leaving it alone numbers the copied paragraph according to whichever list happens to hold that id — a silent wrong answer rather than a visible failure. The definition is copied and the copy repointed at it, and the source's `w:nsid` is dropped so the destination's list gallery does not show two definitions as the same list. ## Bookmarks are dropped, not renamed ``` paragraph.add_bookmark("Target") copy = paragraph.copy_to(document) [b.name for b in document.bookmarks] # -> ["Target"], not two of them ``` This is the one place `copy_to()` deliberately loses something. Renaming would leave a bookmark nothing points at; keeping the name would leave two competing for every reference to it. Dropping is the only outcome that is not silently wrong — use Paragraph.add_bookmark() on the copy to bookmark it afresh. # Document properties Word keeps three separate sets of properties, in three parts of the package. They are not interchangeable, and which one you want depends on who wrote the value. | | Part | Written by | | -------------------------------- | --------------------- | --------------------------------- | | [Core](#core-properties) | `docProps/core.xml` | you, and Word's File → Info panel | | [Extended](#extended-properties) | `docProps/app.xml` | Word, mostly statistics | | [Custom](#custom-properties) | `docProps/custom.xml` | you, under any name you like | ## Core properties The standard Dublin Core set — the fields Word shows under File → Info: ``` from docx import Document document = Document() core = document.core_properties core.title = "Quarterly report" core.author = "Ada Lovelace" core.subject = "Finance" core.keywords = "quarterly, finance, 2026" core.comments = "Draft for review" core.category = "Report" core.content_status = "Draft" core.language = "en-GB" ``` The date fields take a `datetime`: ``` import datetime as dt core.created = dt.datetime(2026, 3, 14, 9, 0) core.modified = dt.datetime.now() ``` Also available: `identifier`, `last_modified_by`, `last_printed`, `revision` (an `int`) and `version`. Unset string properties read as `""` rather than `None`, and unset dates as `None`. The date properties accept only a `datetime` on assignment — assigning `None` to clear one raises `ValueError`. ## Extended properties `docProps/app.xml` is mostly Word's own bookkeeping — how many pages, words and characters the document had when Word last saved it: ``` extended = document.extended_properties extended.pages extended.words extended.characters extended.paragraphs extended.lines extended.total_time # editing minutes extended.application # e.g. "Microsoft Office Word" extended.app_version ``` Warning These statistics are whatever Word wrote when it last saved. This library does not recompute them, so a document you have edited here reports the old counts. Treat them as a record of Word's last visit, not as a live measure. A few are yours to set, and Word displays them: ``` extended.company = "Analytical Engines Ltd" extended.manager = "Charles Babbage" extended.hyperlink_base = "https://example.com/docs/" ``` `template` names the template the document was created from. Note `CT_Properties` is an `xsd:all` type, so Word writes these children in an order that does not match the order the schema lists them in. That is expected, and nothing in the API depends on their order. ## Custom properties `docProps/custom.xml` holds properties under any name you choose, which is what makes it useful for carrying your own metadata through a document. It behaves as a `dict`: ``` import datetime as dt props = document.custom_properties props["Matter number"] = 4242 props["Reviewed"] = True props["Reviewed on"] = dt.datetime(2026, 3, 14) props["Rate"] = 1.5 props["Client"] = "Analytical Engines Ltd" props["Matter number"] # -> 4242 "Client" in props # -> True len(props) list(props) # -> the names del props["Reviewed"] ``` A value may be a `str`, `int`, `float`, `bool` or `datetime`; those are the variant types Word writes, and each reads back as the same Python type. Assigning anything else raises `ValueError` rather than writing a file Word would refuse to open. Names are unique and case-sensitive, and may contain spaces. ### Showing one in the document A `DOCPROPERTY` field puts a custom property's value into the text, so it updates wherever it appears when the property changes ([Fields](https://toxicphreak.github.io/python-docx-ng/user/fields/index.md)): ``` from docx import fields paragraph = document.add_paragraph("Matter: ") paragraph.add_field(fields.doc_property("Matter number")) ``` ## The custom XML data store A different thing from the properties above, and easy to confuse with them. The three kinds of properties are flat named scalars — a string, a number, a date. The **custom XML data store** holds arbitrary XML documents, each in a part of its own (`customXml/item1.xml`, with an `itemProps1.xml` sidecar declaring its schemas). This is where a document-generation pipeline keeps the structured data its content controls are bound to. Word's data binding points a content control at an XPath into one of these items, so the control's displayed text and the stored data stay the same thing. ``` part = document.add_custom_xml_part( "" "2026-0141450.00" "", schema_refs=("urn:example:invoice",), ) part.item_id # -> "{...}", the GUID Word identifies the item by part.schema_refs # -> ("urn:example:invoice",) part.xml # -> the XML as text ``` Reading them back: ``` for part in document.custom_xml_parts: print(part.partname, part.item_id, part.schema_refs) print(part.xml) ``` Document.custom_xml_parts is a tuple in relationship order, empty for a document that has no data store. add_custom_xml_part() accepts a `str` or `bytes`, allocates the next free partname, and generates the `w:itemProps` sidecar with a GUID of its own — Word requires the sidecar, and an item without one is a repair prompt. `schema_refs` names the namespaces the item uses. It is optional and it is what Word's XML mapping pane lists, so supplying it is what makes the item usable for data binding through the UI. Warning **The generated GUID is the one thing in this library's output that is not a function of its input.** Everything else about a saved document is deterministic — see [Reproducible output](https://toxicphreak.github.io/python-docx-ng/user/documents/#reproducible-output). If that matters to you, supply the `item_id`: ``` document.add_custom_xml_part(xml, item_id="{...}") ``` It only has to be unique within the document. Note A document created by this library carries **no** data store. The bundled template used to ship an empty bibliography item left over from its author's Word session, which meant `custom_xml_parts` reported a store the caller had never added. That is gone as of 2.1.0. # Working with Documents `python-docx` allows you to create new documents as well as make changes to existing ones. Actually, it only lets you make changes to existing documents; it's just that if you start with a document that doesn't have any content, it might feel at first like you're creating one from scratch. This characteristic is a powerful one. A lot of how a document looks is determined by the parts that are left when you delete all the content. Things like styles and page headers and footers are contained separately from the main content, allowing you to place a good deal of customization in your starting document that then appears in the document you produce. Let's walk through the steps to create a document one example at a time, starting with two of the main things you can do with a document, open it and save it. ## Opening a document The simplest way to get started is to open a new document without specifying a file to open: ``` from docx import Document document = Document() document.save('test.docx') ``` This creates a new document from the built-in default template and saves it unchanged to a file named 'test.docx'. The so-called "default template" is actually just a Word file having no content, stored with the installed `python-docx` package. It's roughly the same as you get by picking the *Word Document* template after selecting Word's **File > New from Template...** menu item. ## REALLY opening a document If you want more control over the final document, or if you want to change an existing document, you need to open one with a filename: ``` document = Document('existing-document-file.docx') document.save('new-file-name.docx') ``` Things to note: - You can open any Word 2007 or later file this way (`.doc` files from Word 2003 and earlier won't work). Macro-enabled `.docm` files and `.dotx` / `.dotm` templates open too. - Not every part of a document has an API yet — charts and SmartArt diagrams, for instance. Anything without one is left untouched and written back out unchanged on save, so opening and re-saving a document never silently discards content it does not understand. - If you use the same filename to open and save the file, `python-docx` will obediently overwrite the original file without a peep. You'll want to make sure that's what you intend. ## Files that cannot be opened A few kinds of file look like an ordinary `.docx` and are not one. Each raises something that says which: | Raised | Meaning | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PackageNotFoundError` | No file at that path, or the file is not a readable OPC package — a truncated download, or not a zip archive at all. | | `EncryptedPackageError` | The document is password-protected. It is an OLE compound file wrapping the encrypted package, so it cannot be read without the password. Subclasses `PackageNotFoundError`. | | `StrictOoxmlNotSupportedError` | The document was saved as **Strict Open XML Document**, an option in Word's Save As dialogue and the default in some regulated environments. It uses the same element names in the ISO Strict namespaces rather than the Transitional ones this library reads. Re-saving from Word as "Word Document (.docx)" converts it. | | `ValueError` | The package is a valid OPC package but not a Word one — a `.xlsx` or `.pptx`, for instance. | ## Opening a 'file-like' document `python-docx` can open a document from a so-called *file-like* object. It can also save to a file-like object. This can be handy when you want to get the source or target document over a network connection or from a database and don't want to (or aren't allowed to) interact with the file system. In practice this means you can pass an open file or StringIO/BytesIO stream object to open or save a document like so: ``` f = open('foobar.docx', 'rb') document = Document(f) f.close() # or with open('foobar.docx', 'rb') as f: source_stream = StringIO(f.read()) document = Document(source_stream) source_stream.close() ... target_stream = StringIO() document.save(target_stream) ``` The `'rb'` file open mode parameter isn't required on all operating systems. It defaults to `'r'` which is enough sometimes, but the 'b' (selecting binary mode) is required on Windows and at least some versions of Linux to allow Zipfile to open the file. Okay, so you've got a document open and are pretty sure you can save it somewhere later. Next step is to get some content in there ... ## Reproducible output The same document data serialises to the same bytes, every time, on any machine. Three things make that true: - Every zip member is stamped with a fixed timestamp — the zip epoch, 1980-01-01 — rather than the time of the save. - Parts are written in partname order. - Relationships are written in numeric `rId` order. So a build that generates a document twice produces two identical files, and a checksum is a meaningful thing to compare. `tests/test_reproducible.py` pins this. The one exception is add_custom_xml_part(), which generates a random GUID unless you supply one — see [The custom XML data store](https://toxicphreak.github.io/python-docx-ng/user/document-properties/#the-custom-xml-data-store). Anything you put in a document yourself is of course yours to keep deterministic. A timestamp in [core properties](https://toxicphreak.github.io/python-docx-ng/user/document-properties/index.md), or text built from `datetime.now()`, changes the input and therefore the output. ## Reducing the size of what you produce A document generated from the bundled template is around 20 KB on disk, and most of that is the 168 style definitions Word's own template carries. `Document.cleanup()` removes what nothing points at and takes it to about 9 KB: ``` document.cleanup() ``` See [Style usage and cleanup](https://toxicphreak.github.io/python-docx-ng/user/cleanup/index.md), and [`python -m docx cleanup`](https://toxicphreak.github.io/python-docx-ng/user/cli/index.md) for doing it to a file you already have. ## Parts beyond the body A `.docx` is a package of parts, and several of them have an API of their own. The ones not covered by another page: | | | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | Document.theme | the theme fonts and colours — see [Theme fonts](https://toxicphreak.github.io/python-docx-ng/user/text/#theme-fonts) | | Document.custom_xml_parts | the custom XML data store — see [Document properties](https://toxicphreak.github.io/python-docx-ng/user/document-properties/#the-custom-xml-data-store) | | Document.vba_project | the macro project — see [Macros](https://toxicphreak.github.io/python-docx-ng/user/templates/#macros) | | Document.embedded_objects | embedded OLE objects — see [Embedded OLE objects](https://toxicphreak.github.io/python-docx-ng/user/templates/#embedded-ole-objects) | | Document.images | the images the body embeds — see [Images](https://toxicphreak.github.io/python-docx-ng/user/images/#reading-the-images-already-in-a-document) | | Document.math | the equations — see [Equations](https://toxicphreak.github.io/python-docx-ng/user/text/#equations) | | Document.endnotes | the endnotes — see [Footnotes](https://toxicphreak.github.io/python-docx-ng/user/footnotes/#endnotes) | `Document.part.package.iter_parts()` reaches everything, typed where a part class exists and as a plain `Part` where one does not. # Fields and tables of contents A field is how Word represents anything it works out for itself: page numbers, a table of contents, cross-references, captions that renumber, dates, and references to document properties. Every one of those is the same feature with a different instruction string. Warning **This library cannot compute a field result, and no API can change that.** A table of contents added here is empty, a `PAGE` field has no number, and a cross-reference shows nothing, because all three depend on how Word lays the document out. Fields are written asking Word to refresh them when it opens the file — see [Getting Word to fill them in](#getting-word-to-fill-them-in). ## Adding a field Paragraph.add_field() takes a field code. The builders in docx.fields write the ones people usually want, so you rarely need to remember the switch syntax: ``` from docx import Document, fields document = Document() paragraph = document.add_paragraph("Page ") paragraph.add_field(fields.page_number()) paragraph.add_run(" of ") paragraph.add_field(fields.page_count()) ``` The builders return the instruction string, not the field, so they compose with anything that takes a field code: | Builder | Field | | ------------------- | ------------------------------------ | | page_number() | `PAGE` — the current page | | page_count() | `NUMPAGES` — pages in the document | | table_of_contents() | `TOC` | | cross_reference() | `REF` — text of a bookmark | | page_reference() | `PAGEREF` — page a bookmark is on | | sequence() | `SEQ` — caption and figure numbering | | date() | `DATE` | | doc_property() | `DOCPROPERTY` | | styleref() | `STYLEREF` — nearest text in a style | An instruction Word understands but no builder covers can be passed directly: ``` paragraph.add_field(r'INCLUDEPICTURE "logo.png" \d') ``` ## A table of contents ``` from docx import Document, fields document = Document() document.add_paragraph("Contents", style="Heading 1") toc = document.add_paragraph() toc.add_field(fields.table_of_contents(levels=(1, 3))) document.add_page_break() document.add_heading("Introduction", level=1) document.add_paragraph("...") document.settings.update_fields_on_open = True document.save("with-toc.docx") ``` `levels` is the heading range to include. The document opens showing "Update this document?" and the contents appear once Word rebuilds them. ## Cross-references A cross-reference points at a bookmark, so bookmark the target first: ``` from docx import Document, fields document = Document() intro = document.add_heading("Introduction", level=1) intro.add_bookmark("intro") body = document.add_paragraph("See ") body.add_field(fields.cross_reference("intro")) body.add_run(" on page ") body.add_field(fields.page_reference("intro")) ``` cross_reference() writes the bookmark's *text* and page_reference() the *page number* it falls on. See [Bookmarks](https://toxicphreak.github.io/python-docx-ng/user/bookmarks/index.md) for the ways to create one. ## Captions Document.add_caption() writes the whole thing — the label, the self-renumbering `SEQ` field, and the bookmark a cross-reference needs: ``` document.add_picture("architecture.png") caption = document.add_caption("Figure", "the architecture") ``` That gives a paragraph in the "Caption" style that Word renders as "Figure 1 the architecture", where the number is a field it recomputes whenever figures are inserted or removed. Read back before Word has computed it, the number is simply missing: `caption.text` is `"Figure the architecture"`. ``` caption.paragraph # -> the Paragraph, for any further formatting caption.label # -> "Figure" caption.text # -> the text as the document currently reads it caption.number # -> the number Word last computed, or None caption.bookmark_name # -> "_Ref000000001" ``` `number` is `None` for a caption this library has just written: a `SEQ` field's result is cached in the document, so it reads back only after a round trip through Word. Referring to it later is one call: ``` document.add_paragraph("See ").add_field( fields.cross_reference(caption.bookmark_name) ) ``` **The bookmark name is the part that matters.** Word's cross-reference dialogue offers only targets whose bookmark name follows its own `_Ref` convention, so a caption bookmarked with an arbitrary name works but is invisible in the UI — the user cannot reference it. `add_caption()` generates names in that shape. (Word derives its own from a timestamp; a counter is used here instead, because a timestamp would make the same document generate different bytes on each run.) That shape also means the bookmark does not appear in Document.bookmarks, which leaves out the ones Word maintains for itself; `Bookmarks.iter_all()` reaches it. Four keyword arguments adjust the result: ``` document.add_caption("Table", "Revenue by region", separator=": ") document.add_caption("Figure", "A cat", restart_at_heading_level=1) # -> "Figure 3-2" document.add_caption("Figure", "A cat", before=document.paragraphs[0]) document.add_caption("Figure", "A cat", style=None) # -> no style applied ``` `separator` is what goes between the number and the text. `restart_at_heading_level` starts the numbering over at each chapter, which is the "Figure 3-2" convention. \_Cell.add_caption() does the same inside a table cell. Each label is its own sequence, so figures, tables and equations number independently. ### Assembling one by hand `SEQ` is the counter underneath, and it is still there if you want to build a caption yourself: ``` caption = document.add_paragraph("Figure ", style="Caption") caption.add_field(fields.sequence("Figure")) caption.add_run(": the architecture") ``` ``` caption.add_field(fields.sequence("Figure", restart_at_heading_level=1)) ``` This is what `add_caption()` does, minus the bookmark. ## Reading the fields in a document Document.fields returns every field in the body, and Paragraph.fields the fields of one paragraph: ``` for field in document.fields: print(field.type, field.instruction, repr(field.result_text)) ``` - Field.instruction — the field code, switches and all - Field.type — the first word of it, e.g. `"PAGE"` - Field.result_text — the cached result Word last computed, which is what a reader saw - Field.is_simple — whether Word stored it as a self-contained `w:fldSimple` or spread it across runs A field's cached result counts towards Paragraph.text, so the page number a reader saw is part of the text you read back. Complex fields nest — a `TOC` result is full of `PAGEREF` fields — and each inner field is a field in its own right. ## Getting Word to fill them in Two things ask Word to compute results, and they are not the same: ``` document.settings.update_fields_on_open = True ``` Settings.update_fields_on_open sets a document-wide flag; Word prompts on open and refreshes every field. This is what a generated table of contents needs. Individual fields are written with `w:dirty` set, which asks Word to refresh that field alone. Pass `dirty=False` to add_field() to suppress it. To keep the field from showing as blank before the first refresh, supply cached result text of your own. That requires `simple=True`, because only a `w:fldSimple` stores its result as plain content — a complex field's result is whatever sits between its `separate` and `end` field characters, which Word writes when it computes it: ``` paragraph.add_field(fields.page_number(), simple=True, result="1") ``` # Footnotes A footnote lives in its own part, `word/footnotes.xml`, and the document body carries only a reference to it. Creating one is therefore two steps: add the footnote, then reference it from a run. ## Adding a footnote ``` from docx import Document document = Document() paragraph = document.add_paragraph("The engine was never built") footnote = document.footnotes.add_footnote("Babbage, 1837.") paragraph.runs[-1].add_footnote_reference(footnote) ``` add_footnote_reference() appends the reference mark to the end of the run, so which run you call it on decides where the superscript number appears. A footnote can hold more than the one paragraph its text argument creates: ``` footnote = document.footnotes.add_footnote("See also:") footnote.add_paragraph("Menabrea, 1842.") footnote.add_paragraph("Lovelace, Note G.", style="Quote") ``` ## Reading them Document.footnotes supports `len()` and iteration, and looks a footnote up by id: ``` len(document.footnotes) for footnote in document.footnotes: print(footnote.footnote_id, footnote.text) document.footnotes.get(2) # -> the Footnote, or None ``` Note Word reserves the first two ids for the separator and continuation separator — the little rules drawn above footnote text. Iterating skips those, so what you get is the footnotes a reader would count. Numbering is Word's to compute, as with any field: `footnote_id` is an identifier, not the number printed in the margin. Note The API changed in 2.0.0. The 0.9.x line had `Paragraph.add_footnote()`; footnotes are now Document.footnotes plus Run.add_footnote_reference(), which separates creating the note from placing the mark. ## Endnotes Endnotes are the same shape, one part over: they live in `word/endnotes.xml` and collect at the end of the document or section rather than at the foot of the page. ``` paragraph = document.add_paragraph("The engine was never built") endnote = document.endnotes.add_endnote("Babbage, 1837.") paragraph.runs[-1].add_endnote_reference(endnote) ``` Document.endnotes supports `len()`, iteration and lookup by id, exactly as `footnotes` does, and an Endnote has `.text`, `.endnote_id` and `.add_paragraph()`: ``` for endnote in document.endnotes: print(endnote.endnote_id, endnote.text) document.endnotes.get(1) # -> the Endnote, or None ``` `word/endnotes.xml` is created on demand the first time you add one, as the footnotes part is, so a document that has no endnotes carries no endnotes part. Note As in the footnotes part, ids `-1` and `0` are taken by the separator and continuation separator — the rules drawn above endnote text — so the first real endnote is id `1`. Iterating skips the separators, so what you get is the endnotes a reader would count. A document can carry both kinds at once — they are independent sequences, numbered separately, and Word renders footnotes in Arabic numerals and endnotes in lower-case Roman by default. replace_text() reaches endnotes under `footnotes=True`, which covers both note stories: ``` document.replace_text("Babbage", "Charles Babbage", footnotes=True) ``` # Form fields and content controls Word has two mechanisms for "a place in the document where someone fills something in", from two different eras. Both are read here; which one a document uses depends on which Word feature built it. - **Legacy form fields** (`w:fldChar` with `w:ffData`) — the text input, check box and drop-down from the old Forms toolbar. Values are read *and written*. - **Content controls** (`w:sdt`, "structured document tags") — the modern replacement, and also what a mail-merge or template tool typically inserts. Read-only here. ## Legacy form fields Document.form_fields returns every one in the body, and Paragraph.form_fields those of a single paragraph: ``` from docx import Document document = Document("form.docx") for field in document.form_fields: print(field.name, field.type, field.value) ``` ``` Surname WD_FORM_FIELD_TYPE.TEXT 'Lovelace' Agreed WD_FORM_FIELD_TYPE.CHECK_BOX True Department WD_FORM_FIELD_TYPE.DROP_DOWN 'Engineering' ``` FormField.value reads and writes: ``` field.value = "Babbage" # a text input field.value = True # a check box field.value = "Finance" # a drop-down, by entry text ``` What value means follows the field type: | Type | Value | | ----------- | ------------------------------------------------- | | `TEXT` | the result text Word last rendered | | `CHECK_BOX` | a `bool` | | `DROP_DOWN` | the selected entry, `""` when nothing is selected | Warning Word renders an empty text field as filler — five spaces or similar — and that filler is what `value` returns, because it is what the document actually contains. Test against default rather than against `""` when you need to know whether a field was filled in. The other properties describe how Word presents the field: ``` field.name # the bookmark name Word gives it field.default field.enabled field.help_text field.status_text field.max_length # text inputs field.text_type # WD_TEXT_FORM_FIELD_TYPE: REGULAR_TEXT, NUMBER_TEXT, DATE_TEXT, ... field.items # drop-down entries field.calc_on_exit ``` ## Content controls A `w:sdt` wraps content rather than standing in for it, which has one consequence worth knowing: **text inside a content control is ordinary document text**. Before 2.0.0 it was invisible to the API — a document built from a template could read back as empty. Document.content_controls and Paragraph.content_controls give you the controls themselves: ``` for control in document.content_controls: print(control.tag, control.alias, repr(control.text)) ``` - tag — the machine-readable name, which is what a template tool keys on - alias — the title Word shows the user - type — rich text, plain text, date picker, and so on - text — everything inside, paragraphs separated by newlines, as for a table cell - showing_placeholder — whether what you are reading is the grey prompt text rather than a real value - is_block_level — whether it wraps whole paragraphs and tables, or sits inline within a paragraph Reach the content through runs, paragraphs, tables or iter_inner_content(): ``` control = document.content_controls[0] for run in control.runs: run.bold = True ``` To change the text in a control, edit those runs, or use replace_text(), which reaches inside content controls like any other text. See [Finding and replacing text](https://toxicphreak.github.io/python-docx-ng/user/search-replace/index.md). # Working with Headers and Footers Word supports *page headers* and *page footers*. A page header is text that appears in the top margin area of each page, separated from the main body of text, and usually conveying context information, such as the document title, author, creation date, or the page number. The page headers in a document are the same from page to page, with only small differences in content, such as a changing section title or page number. A page header is also known as a *running head*. A *page footer* is analogous in every way to a page header except that it appears at the bottom of a page. It should not be confused with a footnote, which is not uniform between pages. For brevity's sake, the term *header* is often used here to refer to what may be either a header or footer object, trusting the reader to understand its applicability to both object types. ## Accessing the header for a section Headers and footers are linked to a *section*; this allows each section to have a distinct header and/or footer. For example, a landscape section might have a wider header than a portrait section. Each section object has a `.header` property providing access to a \_Header object for that section: ``` >>> document = Document() >>> section = document.sections[0] >>> header = section.header >>> header ``` A \_Header object is *always* present on `Section.header`, even when no header is defined for that section. The presence of an actual header definition is indicated by `_Header.is_linked_to_previous`: ``` >>> header.is_linked_to_previous True ``` A value of `True` indicates the \_Header object contains no header definition and the section will display the same header as the previous section. This "inheritance" behavior is recursive, such that a "linked" header actually gets its definition from the first prior section having a header definition. This "linked" state is indicated as *"Same as previous"* in the Word UI. A new document does not have a header (on the single section it contains) and so `.is_linked_to_previous` is `True` in that case. Note this case may be a bit counterintuitive in that there *is no previous section header* to link to. In this "no previous header" case, no header is displayed. ## Adding a header (simple case) A header can be added to a new document simply by editing the content of the \_Header object. A \_Header object is a "story" container and its content is edited just like a Document object. Note that like a new document, a new header already contains a single (empty) paragraph: ``` >>> paragraph = header.paragraphs[0] >>> paragraph.text = "Title of my document" ``` Note also that the act of adding content (or even just accessing `header.paragraphs`) added a header definition and changed the state of `.is_linked_to_previous`: ``` >>> header.is_linked_to_previous False ``` ## Adding "zoned" header content A header with multiple "zones" is often accomplished using carefully placed tab stops. The required tab-stops for a center and right-aligned "zone" are part of the `Header` and `Footer` styles in Word. If you're using a custom template rather than the *python-docx* default, it probably makes sense to define that style in your template. Inserted tab characters (`"\t"`) are used to separate left, center, and right-aligned header content: ``` >>> paragraph = header.paragraphs[0] >>> paragraph.text = "Left Text\tCenter Text\tRight Text" >>> paragraph.style = document.styles["Header"] ``` The `Header` style is automatically applied to a new header, so the third line just above (applying the `Header` style) is unnecessary in this case, but included here to illustrate the general case. ## Removing a header An unwanted header can be removed by assigning `True` to its `.is_linked_to_previous` attribute: ``` >>> header.is_linked_to_previous = True >>> header.is_linked_to_previous True ``` The content for a header is irreversably deleted when `True` is assigned to `.is_linked_to_previous`. ## Understanding headers in a multi-section document The "just start editing" approach works fine for the simple case, but to make sense of header behaviors in a multi-section document, a few simple concepts will be helpful. Here they are in a nutshell: 1. Each section can have its own header definition (but doesn't have to). 1. A section that lacks a header definition inherits the header of the section before it. The `_Header.is_linked_to_previous` property simply reflects the presence of a header definition, `False` when a definition is present and `True` when not. 1. Lacking a header definition is the default state. A new document has no defined header and neither does a newly-inserted section. `.is_linked_to_previous` reports `True` in both those cases. 1. The content of a `_Header` object is its own content if it has a header definition. If not, its content is that of the first prior section that *does* have a header definition. If no sections have a header definition, a new one is added on the first section and all other sections inherit that one. This adding of a header definition happens the first time header content is accessed, perhaps by referencing `header.paragraphs`. ## Adding a header definition (general case) An explicit header definition can be given to a section that lacks one by assigning `False` to its `.is_linked_to_previous` property: ``` >>> header.is_linked_to_previous True >>> header.is_linked_to_previous = False >>> header.is_linked_to_previous False ``` The newly added header definition contains a single empty paragraph. Note that leaving the header this way is occasionally useful as it effectively "turns-off" a header for that section and those after it until the next section with a defined header. Assigning `False` to `.is_linked_to_previous` on a header that already has a header definition does nothing. ### Inherited content is automatically located Editing the content of a header edits the content of the *source* header, taking into account any "inheritance". So for example, if the section 2 header inherits from section 1 and you edit the section 2 header, you actually change the contents of the section 1 header. A new header definition is not added for section 2 unless you first explicitly assign `False` to its `.is_linked_to_previous` property. # Images ## Inline pictures An inline picture sits in the text flow like a very large character: ``` from docx import Document from docx.shared import Inches document = Document() document.add_picture("logo.png", width=Inches(1.25)) ``` Document.add_picture() puts the image in a paragraph of its own. To place one within a paragraph you are building, use Run.add_picture(): ``` paragraph = document.add_paragraph("As shown here: ") paragraph.add_run().add_picture("figure.png", width=Inches(2)) ``` Giving only `width` or only `height` scales the other to match, preserving the aspect ratio. Giving neither uses the image's native size, from its own DPI. ## Floating pictures A floating picture is anchored to something on the page and text wraps around it — a logo in a corner, a figure beside a paragraph: ``` from docx.enum.shape import WD_WRAP_TYPE from docx.shared import Cm run = document.add_paragraph("Text that flows around the figure. ").add_run() shape = run.add_float_picture( "figure.png", width=Cm(4), left=Cm(1), top=Cm(0.5), wrap_type=WD_WRAP_TYPE.SQUARE, ) ``` `left` and `top` are offsets from whatever the image is positioned against, which `relative_from_h` and `relative_from_v` choose — the column and the paragraph by default, or the page, the margin, a specific margin, or the character position. WD_WRAP_TYPE controls how text behaves around it: | Value | Effect | | ------------ | ----------------------------------------------------------- | | `SQUARE` | text keeps clear of the image's bounding box | | `TIGHT` | text follows the image's outline | | `THROUGH` | text also fills enclosed transparent areas | | `TOP_BOTTOM` | text breaks above and below, none beside | | `NONE` | text ignores the image, which then overlaps or underlies it | To put an image *behind* the text, both settings are needed — `behind_text` takes effect only when nothing is keeping text out of the way in the first place: ``` shape.wrap_type = WD_WRAP_TYPE.NONE shape.behind_text = True ``` For the specific case of a background image on every page, see [Watermarks](https://toxicphreak.github.io/python-docx-ng/user/watermarks/index.md), which handles the header placement for you. Document.floating_shapes lists the ones already in a document, and each shape exposes `width`, `height`, `left`, `top`, `horizontal_align`, `vertical_align`, `z_order`, `allow_overlap` and `set_wrap_distance()`. ## Alternative text Screen readers announce the description; the title is presented separately and generally not announced, so accessibility depends on the description: ``` picture = document.add_picture("chart.png", description="Revenue by quarter, 2026") picture.title = "Revenue chart" ``` Both are read/write on InlineShape and FloatingShape, and both add_picture() and add_float_picture() accept them as arguments. Assigning `None` removes them: ``` for shape in document.inline_shapes: if not shape.description: print("missing alt text") ``` ## Supported formats PNG, JPEG, GIF, BMP, TIFF, WebP, SVG, EMF and WMF. SVG needs care, because Word will not render one without a raster fallback — it stores both and shows the fallback in any version that cannot draw the vector: ``` document.add_picture("diagram.svg", svg_fallback="diagram.png") ``` Without `svg_fallback` the image is embedded but may not display. The format is detected from the file's own header rather than its extension, so a mislabelled file still works. An unrecognised one raises UnrecognizedImageError. ## Photos from a phone: EXIF orientation A camera does not rotate the pixels when you turn it sideways. It stores the sensor's landscape frame and writes an EXIF `Orientation` tag saying which way up it goes. A portrait photo off a phone is therefore a *landscape* JPEG with a tag on it, and inserting it naively puts it in the document on its side — and, if you gave only a width, at the wrong aspect ratio too, because the height was derived from the stored dimensions. add_picture() and add_float_picture() honour the tag: ``` shape = document.add_picture("photo-from-phone.jpg", width=Inches(2)) shape.width # -> Inches(2) shape.height # -> Inches(4), from the displayed 2:4 shape, not the stored 4:2 ``` The rotation goes into the DrawingML — `a:xfrm/@rot`, in sixtieths of a degree — and never into the pixels. The image part stays byte-identical to the file on disk, so the sha1 deduplication keeps working and the same photo inserted twice is still one part. Pass `honor_exif_orientation=False` to insert the stored frame unrotated. Image reports both sets of dimensions, and the distinction is the whole point: ``` from docx.image.image import Image image = Image.from_file("photo-from-phone.jpg") image.orientation # -> 6, "rotate 90° clockwise to display" image.is_rotated # -> True, this orientation exchanges width and height image.px_width # -> 4032, the frame as stored image.px_display_width # -> 3024, the frame as a viewer shows it image.width # -> the stored width as a Length image.display_width # -> the displayed width as a Length ``` `px_width` and `px_height` keep meaning what they always meant — the stored dimensions — so nothing that read them changed behaviour. The `display_*` accessors are the new ones. scaled_dimensions() scales from the display aspect ratio by default and takes the same `honor_exif_orientation=False`. An image whose format cannot carry the tag, or which does not set it, reports `orientation == 1`, for which everything above is a no-op. ## Reading the images already in a document The counterpart of `add_picture()`. Each shape hands back the image it displays: ``` for shape in document.inline_shapes: image = shape.image if image is None: continue # -- a chart, a diagram, a linked picture print(image.filename, image.content_type, image.px_width, image.px_height) Path(image.filename).write_bytes(image.blob) ``` InlineShape.image and FloatingShape.image are `None` rather than an error when there is no image to give — a chart, a SmartArt diagram, a picture linked to a file on disk rather than embedded, or a relationship the document does not resolve. For an SVG picture, `.image` is the raster fallback Word displays and .svg_image the vector source: ``` shape.image.content_type # -> "image/png", the fallback shape.svg_image.content_type # -> "image/svg+xml", the original ``` Document.images is the package-level view — the distinct images the body embeds, deduplicated, with no shape needed to reach them: ``` for image in document.images: print(image.filename, len(image.blob)) ``` # Installing ``` pip install python-docx-ng ``` The import name is `docx` The distribution is `python-docx-ng`, the importable package is `docx`: ``` import docx from docx import Document ``` That is deliberate — it makes this a drop-in replacement for `python-docx`, so existing code and every upstream example keep working. It also means **`python-docx-ng` and `python-docx` cannot be installed side by side**: they claim the same import name, and whichever was installed last wins. Uninstall one before installing the other. ``` pip uninstall python-docx pip install python-docx-ng ``` ## Requirements | | | | ---------------------------------------------------------------- | ---------- | | Python | 3.9 – 3.14 | | [lxml](https://pypi.org/project/lxml/) | >= 6.1.0 | | [typing_extensions](https://pypi.org/project/typing-extensions/) | >= 4.9.0 | Both dependencies are installed for you. There are no others — no test framework, no documentation tooling. lxml is floored at 6.1.0 because that is the first release fixing CVE-2026-41066, an XXE-to-local-files hole in the default configuration of `iterparse()` and `ETCompatXMLParser()`; every 4.x and 5.x release is affected. This library uses neither API and sets `resolve_entities=False` on its own parser, so it was never exposed itself, but it will not pull a known-vulnerable XML parser into your dependency tree. If you are pinned below lxml 6 Another package in your environment may cap lxml below 6. In that case this release will not resolve, and there is no supported workaround — the floor is a security boundary rather than a compatibility one. ## Other installers ``` uv add python-docx-ng poetry add python-docx-ng pipenv install python-docx-ng ``` ## Upgrading from 0.9.x 2.0.0 rebases onto upstream python-docx v1.2.0 and **contains breaking changes**. Read the [migration guide](https://toxicphreak.github.io/python-docx-ng/user/migrating-from-0-9/index.md) before upgrading. The 0.9.x releases are yanked on PyPI. They require `lxml<5`, which has no wheels for Python 3.13 or later, and they declare a test framework as a runtime dependency. An exact pin such as `python-docx-ng==0.9.7` still resolves, so existing locked builds are unaffected, but nothing new will select one. ## Installing from source ``` git clone https://github.com/toxicphreAK/python-docx-ng.git cd python-docx-ng uv sync ``` [uv](https://docs.astral.sh/uv/) is what this project uses; `uv sync` creates the environment and installs the development dependencies. See [CONTRIBUTING.md](https://github.com/toxicphreAK/python-docx-ng/blob/main/CONTRIBUTING.md) for running the tests. # Migrating from 0.9.x Version 2.0.0 restarts *python-docx-ng* from upstream python-docx v1.2.0. The 0.9.x line had diverged from upstream v0.8.11 in 2021 and drifted for four years. Rather than merge four years of upstream change into that tree, 2.0.0 branches from upstream and re-applies the python-docx-ng features on top, one at a time. The benefit is a typed, tested core that tracks upstream; the cost is that several 0.9.x additions are gone, replaced by upstream implementations of the same features that are shaped differently. **This is a breaking release.** Read this page before upgrading. Code that only used the parts of the API that upstream python-docx also has is very likely unaffected. ## Features upstream has since implemented These existed in 0.9.x as fork additions. Upstream has since implemented the same features, and 2.0.0 uses upstream's version. ### Comments 0.9.x added comments to a paragraph. Upstream models them as a collection on the document, with the comment range marked on runs. ``` # -- 0.9.x -- paragraph.add_comment("Needs a citation.", author="RS", initials="rs") paragraph.comments # -- 2.0.0 -- comment = document.add_comment(paragraph.runs, "Needs a citation.", author="RS", initials="rs") document.comments # -- the Comments collection -- document.comments.get(comment_id) run.mark_comment_range(last_run, comment_id) # -- for finer control -- ``` The `docx.text.comment` module is gone; the objects live in `docx.comments`. A comment is now a block-item container, so it can hold several paragraphs and even tables, and `comment.text` joins its paragraphs with newlines. See [comments](https://toxicphreak.github.io/python-docx-ng/user/comments/index.md) for the full API. ### Hyperlinks 0.9.x added its own hyperlink handling. Upstream's Hyperlink reads links already in a document and exposes `.address`, `.fragment`, `.runs`, `.text` and `.contains_page_break`; Paragraph.hyperlinks lists them, and `paragraph.text` includes hyperlink text, which it did not in 0.8.11. Paragraph.add_hyperlink is still present — that is a python-docx-ng addition upstream has not adopted — but its signature has changed: ``` # -- 2.0.0 -- paragraph.add_hyperlink("python-docx-ng", address="https://example.com/") paragraph.add_hyperlink("see above", fragment="my_bookmark") # -- internal link -- ``` It returns a Hyperlink, not a run. ### Table cell access This is the change most likely to affect existing code. In 0.9.x, `Table._cells` built a row-major matrix of the layout grid, and `Table.cell()`, `Table.row_cells()` and `Table.column_cells()` indexed into it. Upstream returns a flat list, and — importantly — `_Row.cells` returns only the cells that are actually present. Word allows a row to start late or end early, so rows of the same table can have different numbers of cells. Two new properties report the gap: ``` for row in table.rows: leading = row.grid_cols_before # -- unpopulated grid columns before the first cell -- trailing = row.grid_cols_after # -- and after the last -- for cell in row.cells: ... ``` If you were relying on every row having one cell per column, add `grid_cols_before` and `grid_cols_after` to your indexing, or use Table.cell, which addresses the layout grid directly and raises `IndexError` for a grid position the row does not occupy. `Table.cell()` now locates its target without materializing the whole grid, so reading a table cell-by-cell costs time proportional to the number of cells rather than to its square. `Table.row_cells()` is deprecated in favour of `table.rows[i].cells`. ## Behaviour changes ### `Paragraph.text` with tracked changes In 0.9.x — and in upstream — revision markup was not modelled at all. Runs inside a `w:ins` were skipped, so inserted text was missing, and deleted text was missing too because it lives in `w:delText` rather than `w:t`. The result was neither the original nor the final version of the document but a third thing matching no view Word offers, and it was wrong silently. `Paragraph.text` is now the document **as it now reads** — every revision accepted: ``` paragraph.text # -- insertions in, deletions out -- paragraph.original_text # -- deletions in, insertions out -- ``` `Paragraph.runs` follows the same reading, so a run inside a `w:ins` now appears in it and a run inside a `w:del` does not. If you were relying on the old behaviour to strip insertions, use `original_text`. If you want the revision markup gone from the file altogether, call `document.accept_all_revisions()` or `document.reject_all_revisions()`. See Document.revisions for reading the individual changes. ### `Paragraph.text` with simple fields The cached result of a `w:fldSimple` — the page number a `PAGE` field displays, the text a cross-reference resolves to — is now part of `Paragraph.text`. It was previously skipped, so such text went missing. This is displayed text and belongs there; a field *instruction* (`w:instrText`) is still never reported as text, because it is not. ### `Font.highlight_color` In 0.9.x this fell back to the `w:shd` shading fill when no highlight was set, and accepted RGB values on assignment. Highlighting and shading are different things in Word, and conflating them meant you could not tell them apart. They are now separate: ``` font.highlight_color # -- a WD_COLOR_INDEX member or None, never an RGB value -- font.shading_fill # -- an RGBColor, the string "auto", or None -- ``` `highlight_color` also distinguishes `WD_COLOR_INDEX.NO_HIGHLIGHT` — highlighting explicitly turned off, which Word writes as `w:highlight w:val="none"` — from `None`, which means no setting is present and the value is inherited. ### `ParagraphFormat.outline_level` 0.9.x returned `9` when no outline level was set. `None` and `9` are different things: `None` means the level is inherited from the style hierarchy, and `9` is Word's explicit "Body Text" level, which deliberately excludes the paragraph from the outline. 2.0.0 reports them distinctly, and assigning a value outside 0–9 raises `ValueError` rather than writing an invalid document. ### `Section.orientation` Assigning an orientation now exchanges `page_width` and `page_height` too, so the page is actually rotated. Previously it set `w:pgSz/@w:orient` alone and left the dimensions where they were, giving a section declared landscape at portrait dimensions — which Word renders as portrait. **Remove the workaround if you have one.** Nearly everyone who hit this wrote something like: ``` section.orientation = WD_ORIENT.LANDSCAPE section.page_width, section.page_height = section.page_height, section.page_width # -- delete this -- ``` The two swaps now cancel out and the page comes back the size it started, which is the same broken result as before, arrived at from the other direction. Delete the second line: ``` section.orientation = WD_ORIENT.LANDSCAPE section.page_width, section.page_height # -- (11in, 8.5in) -- ``` Setting the orientation a section already has does nothing, so the assignment is safe to repeat. For a page size that is not simply the rotation of the current one, set `page_width` and `page_height` explicitly afterwards. Margins are not moved. ## Removed without replacement `Section.paragraphs`\ Use Section.iter_inner_content, which generates the paragraphs and tables of a section in document order rather than paragraphs alone. `Table.section`\ Removed. The 0.9.x implementation walked to the first following `w:sectPr`, which returns the wrong section for every table but those in the last section of a multi-section document. There is no correct one-line replacement; iterate Document.sections and Section.iter_inner_content to find the section a table belongs to. ## Fork features, re-applied and reshaped These are python-docx-ng features that upstream does not have. They are back on the 2.0.0 base, in some cases with a different API than 0.9.x had. ### Table and cell borders 0.9.x exposed borders as a sequence with `add_border()` and `remove_border()` methods, a `_Border.name` setter that returned a value Python discarded, and line styles as bare strings validated against a 200-entry tuple. 2.0.0 makes it a mapping keyed by edge: ``` # -- 0.9.x -- table.borders.add_border("top", "single", sz=8, color="FF0000") # -- 2.0.0 -- from docx.enum.table import WD_LINE_STYLE from docx.shared import Pt, RGBColor table.borders["top"].line = WD_LINE_STYLE.SINGLE table.borders["top"].size = Pt(1) table.borders["top"].color = RGBColor(0xFF, 0x00, 0x00) table.borders["top"].line = None # -- remove the edge -- ``` Edges are named for the XML: `top`, `start`, `left`, `bottom`, `end`, `right`, `insideH` and `insideV`, plus `tl2br` and `tr2bl` on a cell. Sizes are Length values, so `Pt(1)` composes as it does everywhere else, rather than a raw count of eighths of a point. ### Footnotes 0.9.x had `paragraph.add_footnote(text)`, derived from bayoo-docx, and a `paragraph.footnotes` that returned a bool. The 2.0.0 implementation mirrors the shape upstream gave comments: ``` footnote = document.footnotes.add_footnote("See Smith (2019), p. 42.") paragraph.add_run().add_footnote_reference(footnote) len(document.footnotes) document.footnotes.get(footnote.footnote_id) ``` Creating a footnote and referencing it are separate steps, because a footnote can be referenced from anywhere and one that is never referenced does not render. A footnote is a block-item container, so it can hold several paragraphs, tables and images. Word keeps two structural footnotes at ids -1 and 0 for the separator rules. Those are in the part but never in the collection, so authored footnotes are numbered from 1. See [footnotes](https://toxicphreak.github.io/python-docx-ng/api/docx/footnotes/index.md). ### Form fields 0.9.x exposed only element classes; there was no proxy API and no way to read or set a field value without dropping to the XML. 2.0.0 adds one: ``` values = {f.name: f.value for f in document.form_fields} field = document.form_fields[0] field.value = "Carol Chen" # -- str, or bool for a check box -- field.items # -- the entries of a drop-down -- ``` `.value` reads and writes in the form natural to the kind of field. See FormField. ### AltChunk 0.9.x had an `AltchunkPart` but no public API. 2.0.0 adds one: ``` document.add_alt_chunk(b"

Imported.

", "text/html") document.alt_chunks ``` Word performs the import when it opens the document, so the embedded content stays invisible to this library until then. ## Still to come Some 0.9.x features have not been re-applied yet. Check the [2.0.0 milestone](https://github.com/toxicphreAK/python-docx-ng/milestone/1) before assuming something is gone for good. # Numbered and bulleted lists A list number is nowhere in the document body. Word stores which list a paragraph belongs to and computes "1.", "a)" or "iii." from `numbering.xml` at display time, which is why reading a document gives you paragraph text with the numbers missing. This library computes them the same way Word does. ## Reading the numbers Document.list_numbers pairs every list paragraph in the body with the number a reader sees: ``` from docx import Document document = Document("report.docx") for paragraph, number in document.list_numbers: print(number, paragraph.text) ``` ``` 1. First point 2. Second point a) A sub-point 3. Third point ``` Paragraphs inside tables are included, because they count towards the same lists. Paragraph.list_number gives the number for a single paragraph, and `None` when it is not in a list: ``` paragraph.list_number # -> "2." or None ``` Warning A list number depends on every paragraph before it, so reading Paragraph.list_number in a loop is quadratic. Use Document.list_numbers, which walks the document once. ## Which list a paragraph is in Paragraph.numbering returns a ParagraphNumbering, or `None` when the paragraph is not in a list: ``` item = document.add_paragraph("first item", style="List Number") numbering = item.numbering numbering.num_id # -> the list this paragraph belongs to numbering.level # -> indent level, 0 for the outermost numbering.from_style # -> True when the style applies it, not the paragraph numbering.level_definition # -> the NumberingLevel, for format and start value ``` The level definition is where the appearance lives: ``` level = item.numbering.level_definition level.number_format # -> WD_NUMBER_FORMAT.DECIMAL level.start # -> 1 level.is_bullet # -> False level.format_number(3) # -> "3." ``` format_number() applies the level's own template, so a level written as `%1)` in lower letters gives `"c)"` for the same input. WD_NUMBER_FORMAT lists the formats. ## Creating a list The simplest lists are the built-in styles, which carry their own numbering: ``` document.add_paragraph("first item", style="List Bullet") document.add_paragraph("first item", style="List Number") ``` To put further paragraphs in the *same* list as an existing one, take its `num_id`: ``` first = document.add_paragraph("one", style="List Number") second = document.add_paragraph("two") second.set_numbering(first.numbering.num_id) ``` set_numbering() also takes a level, so nesting is a matter of saying which: ``` sub = document.add_paragraph("one, continued") sub.set_numbering(first.numbering.num_id, level=1) ``` Numbering applied directly like this overrides whatever the paragraph's style would apply. remove_numbering() takes it off again. ## Restarting numbering ``` item.restart_numbering() # begins again at 1 item.restart_numbering(10) # begins again at 10 ``` This paragraph and every later one in the same list start a new sequence; paragraphs before it keep the original one. That is what Word's own "Restart at 1" does. It is worth knowing what happens underneath, because it explains the return value. OOXML has no counter to reset. A second `w:num` is created against the same abstract definition, carrying a `w:startOverride`, and the affected paragraphs are pointed at it — so restart_numbering() returns the `num_id` of that new list: ``` new_num_id = item.restart_numbering() later = document.add_paragraph("still in the restarted list") later.set_numbering(new_num_id) ``` It raises `ValueError` when the paragraph is not in a list. ## The numbering part Document.numbering is the whole of `numbering.xml`, for finding a list to join or inspecting one: ``` definition = document.numbering.get(item.numbering.num_id) definition.num_id definition.abstract_num_id definition.levels # -> the NumberingLevel objects, one per level definition.level(0) ``` Numbering.restart() creates the overriding definition directly, when you want the new list without repointing any paragraphs: ``` restarted = document.numbering.restart(item.numbering.num_id, ilvl=0, start=1) ``` ## Defining a list from scratch Everything above joins or restarts a list the numbering part already defines. When the format you want is not in the template — Roman numerals at the top level, a bullet character of your own, a particular indent step — define one: ``` definition = document.numbering.add_numbered_definition() document.add_paragraph("one").set_numbering(definition.num_id) ``` add_numbered_definition() and add_bulleted_definition() are the two common cases. Both take a `depth` (nine levels by default, which is what Word writes) and an `indent_step`: ``` from docx.enum.numbering import WD_NUMBER_FORMAT from docx.shared import Inches legal = document.numbering.add_numbered_definition( depth=3, formats=[ WD_NUMBER_FORMAT.UPPER_ROMAN, WD_NUMBER_FORMAT.UPPER_LETTER, WD_NUMBER_FORMAT.DECIMAL, ], indent_step=Inches(0.3), ) bullets = document.numbering.add_bulleted_definition(bullets=["—", "·"]) ``` add_definition() is the general form, taking a level specification directly. Both shorthands are built on it: ``` definition = document.numbering.add_definition([ {"number_format": WD_NUMBER_FORMAT.DECIMAL, "level_text": "%1.", "start": 1}, {"number_format": WD_NUMBER_FORMAT.LOWER_LETTER, "level_text": "%2)"}, ]) ``` In a `level_text`, `%1` interpolates the counter of level 0, `%2` that of level 1, and so on — `"%1.%2."` is what produces "2.3.". A definition has at most nine levels; more raises `ValueError`. Each returns a NumberingDefinition, whose `num_id` is what set_numbering() takes. Note `w:nsid` and `w:tmpl` are deliberately not written. They are the identifiers Word uses to recognise a definition as one of its own and to match it against a gallery entry; inventing values would make Word treat unrelated lists as the same list, and they are optional. ## Changing a level NumberingLevel.set() changes an existing level in place, and returns the level so calls chain: ``` definition.level(0).set( number_format=WD_NUMBER_FORMAT.UPPER_ROMAN, level_text="%1.", suffix="tab", alignment="left", indent=Inches(0.5), hanging_indent=Inches(0.25), start=1, ) ``` `suffix` is what follows the number — `"tab"`, `"space"` or `"nothing"` — and `restart_after_level` is what makes a sub-list start over when the level above it advances. Every argument is keyword-only and optional; the ones you leave out are left alone. # Quickstart Getting started with `python-docx-ng` is easy. Let's walk through the basics. ## Opening a document First thing you'll need is a document to work on. The easiest way is this: ``` from docx import Document document = Document() ``` This opens up a blank document based on the default "template", pretty much what you get when you start a new document in Word using the built-in defaults. You can open and work on an existing Word document using `python-docx`, but we'll keep things simple for the moment. ## Adding a paragraph Paragraphs are fundamental in Word. They're used for body text, but also for headings and list items like bullets. Here's the simplest way to add one: ``` paragraph = document.add_paragraph('Lorem ipsum dolor sit amet.') ``` This method returns a reference to a paragraph, newly added paragraph at the end of the document. The new paragraph reference is assigned to `paragraph` in this case, but I'll be leaving that out in the following examples unless I have a need for it. In your code, often times you won't be doing anything with the item after you've added it, so there's not a lot of sense in keep a reference to it hanging around. It's also possible to use one paragraph as a "cursor" and insert a new paragraph directly above it: ``` prior_paragraph = paragraph.insert_paragraph_before('Lorem ipsum') ``` This allows a paragraph to be inserted in the middle of a document, something that's often important when modifying an existing document rather than generating one from scratch. ## Adding a heading In anything but the shortest document, body text is divided into sections, each of which starts with a heading. Here's how to add one: ``` document.add_heading('The REAL meaning of the universe') ``` By default, this adds a top-level heading, what appears in Word as 'Heading 1'. When you want a heading for a sub-section, just specify the level you want as an integer between 1 and 9: ``` document.add_heading('The role of dolphins', level=2) ``` If you specify a level of 0, a "Title" paragraph is added. This can be handy to start a relatively short document that doesn't have a separate title page. ## Adding a page break Every once in a while you want the text that comes next to go on a separate page, even if the one you're on isn't full. A "hard" page break gets this done: ``` document.add_page_break() ``` If you find yourself using this very often, it's probably a sign you could benefit by better understanding paragraph styles. One paragraph style property you can set is to break a page immediately before each paragraph having that style. So you might set your headings of a certain level to always start a new page. More on styles later. They turn out to be critically important for really getting the most out of Word. ## Adding a table One frequently encounters content that lends itself to tabular presentation, lined up in neat rows and columns. Word does a pretty good job at this. Here's how to add a table: ``` table = document.add_table(rows=2, cols=2) ``` Tables have several properties and methods you'll need in order to populate them. Accessing individual cells is probably a good place to start. As a baseline, you can always access a cell by its row and column indicies: ``` cell = table.cell(0, 1) ``` This gives you the right-hand cell in the top row of the table we just created. Note that row and column indicies are zero-based, just like in list access. Once you have a cell, you can put something in it: ``` cell.text = 'parrot, possibly dead' ``` Frequently it's easier to access a row of cells at a time, for example when populating a table of variable length from a data source. The `.rows` property of a table provides access to individual rows, each of which has a `.cells` property. The `.cells` property on both `Row` and `Column` supports indexed access, like a list: ``` row = table.rows[1] row.cells[0].text = 'Foo bar to you.' row.cells[1].text = 'And a hearty foo bar to you too sir!' ``` The `.rows` and `.columns` collections on a table are iterable, so you can use them directly in a `for` loop. Same with the `.cells` sequences on a row or column: ``` for row in table.rows: for cell in row.cells: print(cell.text) ``` If you want a count of the rows or columns in the table, just use `len()` on the sequence: ``` row_count = len(table.rows) col_count = len(table.columns) ``` You can also add rows to a table incrementally like so: ``` row = table.add_row() ``` This can be very handy for the variable length table scenario we mentioned above: ``` # get table data ------------- items = ( (7, '1024', 'Plush kittens'), (3, '2042', 'Furbees'), (1, '1288', 'French Poodle Collars, Deluxe'), ) # add table ------------------ table = document.add_table(1, 3) # populate header row -------- heading_cells = table.rows[0].cells heading_cells[0].text = 'Qty' heading_cells[1].text = 'SKU' heading_cells[2].text = 'Description' # add a data row for each item for item in items: cells = table.add_row().cells cells[0].text = str(item.qty) cells[1].text = item.sku cells[2].text = item.desc ``` The same works for columns, although I've yet to see a use case for it. Word has a set of pre-formatted table styles you can pick from its table style gallery. You can apply one of those to the table like this: ``` table.style = 'LightShading-Accent1' ``` The style name is formed by removing all the spaces from the table style name. You can find the table style name by hovering your mouse over its thumbnail in Word's table style gallery. ## Adding a picture Word lets you place an image in a document using the `Insert > Photo > Picture from file...` menu item. Here's how to do it in `python-docx`: ``` document.add_picture('image-filename.png') ``` This example uses a path, which loads the image file from the local filesystem. You can also use a *file-like object*, essentially any object that acts like an open file. This might be handy if you're retrieving your image from a database or over a network and don't want to get the filesystem involved. ### Image size By default, the added image appears at *native* size. This is often bigger than you want. Native size is calculated as `pixels / dpi`. So a 300x300 pixel image having 300 dpi resolution appears in a one inch square. The problem is most images don't contain a dpi property and it defaults to 72 dpi. This would make the same image appear 4.167 inches on a side, somewhere around half the page. To get the image the size you want, you can specify either its width or height in convenient units, like inches or centimeters: ``` from docx.shared import Inches document.add_picture('image-filename.png', width=Inches(1.0)) ``` You're free to specify both width and height, but usually you wouldn't want to. If you specify only one, `python-docx` uses it to calculate the properly scaled value of the other. This way the *aspect ratio* is preserved and your picture doesn't look stretched. The `Inches` and `Cm` classes are provided to let you specify measurements in handy units. Internally, `python-docx` uses English Metric Units, 914400 to the inch. So if you forget and just put something like `width=2` you'll get an extremely small image :). You'll need to import them from the `docx.shared` sub-package. You can use them in arithmetic just like they were an integer, which in fact they are. So an expression like `width = Inches(3) / thing_count` works just fine. ## Applying a paragraph style If you don't know what a Word paragraph style is you should definitely check it out. Basically it allows you to apply a whole set of formatting options to a paragraph at once. It's a lot like CSS styles if you know what those are. You can apply a paragraph style right when you create a paragraph: ``` document.add_paragraph('Lorem ipsum dolor sit amet.', style='ListBullet') ``` This particular style causes the paragraph to appear as a bullet, a very handy thing. You can also apply a style afterward. These two lines are equivalent to the one above: ``` paragraph = document.add_paragraph('Lorem ipsum dolor sit amet.') paragraph.style = 'List Bullet' ``` The style is specified using its style name, 'List Bullet' in this example. Generally, the style name is exactly as it appears in the Word user interface (UI). ## Applying bold and italic In order to understand how bold and italic work, you need to understand a little about what goes on inside a paragraph. The short version is this: 1. A paragraph holds all the *block-level* formatting, like indentation, line height, tabs, and so forth. 1. Character-level formatting, such as bold and italic, are applied at the *run* level. All content within a paragraph must be within a run, but there can be more than one. So a paragraph with a bold word in the middle would need three runs, a normal one, a bold one containing the word, and another normal one for the text after. When you add a paragraph by providing text to the `.add_paragraph()` method, it gets put into a single run. You can add more using the `.add_run()` method on the paragraph: ``` paragraph = document.add_paragraph('Lorem ipsum ') paragraph.add_run('dolor sit amet.') ``` This produces a paragraph that looks just like one created from a single string. It's not apparent where paragraph text is broken into runs unless you look at the XML. Note the trailing space at the end of the first string. You need to be explicit about where spaces appear at the beginning and end of a run. They're not automatically inserted between runs. Expect to be caught by that one a few times :). Run objects have both a `.bold` and `.italic` property that allows you to set their value for a run: ``` paragraph = document.add_paragraph('Lorem ipsum ') run = paragraph.add_run('dolor') run.bold = True paragraph.add_run(' sit amet.') ``` which produces text that looks like this: 'Lorem ipsum **dolor** sit amet.' Note that you can set bold or italic right on the result of `.add_run()` if you don't need it for anything else: ``` paragraph.add_run('dolor').bold = True # is equivalent to: run = paragraph.add_run('dolor') run.bold = True # except you don't have a reference to `run` afterward ``` It's not necessary to provide text to the `.add_paragraph()` method. This can make your code simpler if you're building the paragraph up from runs anyway: ``` paragraph = document.add_paragraph() paragraph.add_run('Lorem ipsum ') paragraph.add_run('dolor').bold = True paragraph.add_run(' sit amet.') ``` ## Applying a character style In addition to paragraph styles, which specify a group of paragraph-level settings, Word has *character styles* which specify a group of run-level settings. In general you can think of a character style as specifying a font, including its typeface, size, color, bold, italic, etc. Like paragraph styles, a character style must already be defined in the document you open with the `Document()` call (*see* [understanding styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/index.md)). A character style can be specified when adding a new run: ``` paragraph = document.add_paragraph('Normal text, ') paragraph.add_run('text with emphasis.', 'Emphasis') ``` You can also apply a style to a run after it is created. This code produces the same result as the lines above: ``` paragraph = document.add_paragraph('Normal text, ') run = paragraph.add_run('text with emphasis.') run.style = 'Emphasis' ``` As with a paragraph style, the style name is as it appears in the Word UI. # Tracked changes A document edited with Word's "Track Changes" on carries its history: insertions in `w:ins` elements, deletions in `w:del`, and the same treatment for formatting changes, paragraph marks and table rows. This library reads that history and can accept or reject any part of it. ## What `Paragraph.text` means here Text in a document with tracked changes has two well-defined readings, and this library gives you both: ``` paragraph.text # the document as it now reads paragraph.original_text # the document as it read before the changes ``` text includes insertions and excludes deletions — what you would get by accepting everything. original_text does the reverse. For a paragraph with no revisions the two are identical. Neither is "the text with markup shown". Word displays deletions struck through *alongside* insertions, which is a rendering rather than a string. Note This changed in 2.0.0. The 0.9.x line dropped both insertions and deletions, so the result matched neither reading. Paragraph.runs likewise now includes runs inside a `w:ins`. ## Reading the revisions Document.revisions returns every revision in the body, in document order: ``` from docx import Document document = Document("reviewed.docx") for revision in document.revisions: print(revision.type, revision.author, revision.date, repr(revision.text)) ``` ``` WD_REVISION_TYPE.INSERTION Ada Lovelace 2026-03-14 09:12:00 'and therefore ' WD_REVISION_TYPE.DELETION Charles Babbage 2026-03-14 10:03:00 'possibly ' ``` Each Revision carries: - type — a WD_REVISION_TYPE member - author and date — `date` is `None` when Word recorded none - text — the text inserted or deleted - is_paragraph_mark — a paragraph split or merge rather than a text change - is_row — a table row inserted or deleted Paragraph.revisions narrows this to one paragraph. ### Who changed what ``` authors = {revision.author for revision in document.revisions} ``` ## Accepting and rejecting Individually: ``` for revision in document.revisions: if revision.author == "Ada Lovelace": revision.accept() else: revision.reject() ``` Accepting an insertion keeps the text and drops the marker; rejecting it removes the text. For a deletion it is the other way round. Wholesale, on the document or on one paragraph — each returns how many revisions it handled: ``` document.accept_all_revisions() # -> 12 document.reject_all_revisions() paragraph.accept_all_revisions() paragraph.reject_all_revisions() ``` Warning Accepting or rejecting mutates the tree, so a list of revisions taken beforehand goes stale. Re-read Document.revisions rather than holding onto revisions across a change, and iterate over a snapshot — as in the loop above, which reads the list once — rather than over a live traversal. ## Turning tracking on Settings.track_revisions is the document-wide flag Word's "Track Changes" button sets: ``` document.settings.track_revisions = True document.save("for-review.docx") ``` This asks Word to record changes made from now on. It does not cause edits made through this library to be recorded as revisions — those are written directly, as if tracking were off. # Finding, replacing and deleting text ## Why `run.text = ...` so often does nothing Word splits a paragraph into runs for reasons that have nothing to do with formatting: spell-check state, language tagging, revision marks, or simply where the cursor happened to be. A placeholder you can see as one word is routinely three runs: ``` {{na me }} ``` No single run contains `{{name}}`, so no assignment to `run.text` replaces it. This is the single most common surprise in working with `.docx` files. ## Replacing text replace_text() matches against the paragraph's text as a whole, so it succeeds whether or not Word split it up. It returns how many replacements it made: ``` from docx import Document document = Document() paragraph = document.add_paragraph("Dear {{name}}, welcome.") paragraph.replace_text("{{name}}", "Ada") # -> 1 paragraph.text # -> 'Dear Ada, welcome.' ``` It is available on paragraphs, on anything that contains block items, and on the document as a whole. ### Across a whole document Document.replace_text() makes what gets searched explicit, because "replace it everywhere" means different things to different callers and getting it wrong stays invisible until someone reads the header: ``` document.replace_text("{{name}}", "Ada") # body only document.replace_text("{{name}}", "Ada", headers_footers=True) # and those document.replace_text("{{name}}", "Ada", footnotes=True) document.replace_text("{{name}}", "Ada", tables=False) # skip tables ``` Tables are searched by default; headers, footers and footnotes are not. ### Regular expressions ``` import re document.replace_text(r"\{\{(\w+)\}\}", r"<\1>", regex=True) document.replace_text("draft", "final", regex=True, flags=re.IGNORECASE) ``` `count` limits how many replacements are made; the default of `-1` means all of them. ### What happens to formatting The replacement takes the formatting of the run holding the first replaced character. When a match spans runs formatted differently, the rest of the matched text is removed along with its formatting — but the runs themselves stay. A hyperlink, bookmark, comment range or field that the match only partly covers therefore keeps its structure rather than being torn in half. ## Formatting part of a paragraph The same run-splitting problem applies when you want to embolden a phrase that is not already its own run. isolate_run() splits the runs as needed so a character range becomes exactly one run, which can then be formatted on its own: ``` paragraph = document.add_paragraph("the important part matters") paragraph.isolate_run(4, 13).bold = True ``` Offsets are measured against Paragraph.text, so a tab counts as one character and a line break as one newline. Where the range already lines up with a run, nothing is split. This is what replace_text() is built on. ## Deleting content `.delete()` removes an object from its parent, and exists on paragraphs, runs, tables, rows and columns: ``` paragraph.delete() run.delete() table.delete() table.rows[1].delete() table.columns[0].delete() ``` Deletion is not just an unlink. A relationship — a hyperlink target, an image — referenced only from the deleted content is dropped with it, and a range marker left unmatched (half a bookmark, half a comment range) is removed rather than left dangling to corrupt the file. Two cases follow Word rather than the literal XML: - Deleting a **row** does not drop a vertically merged cell whose span began in it. The row below inherits the merge, so it continues to render. - Deleting a **column** narrows a cell that spans it and others by one, rather than removing the cell, so the rest of the span survives. Warning Deleting while iterating invalidates the collection you are iterating. Take a list first: ``` for paragraph in list(document.paragraphs): if not paragraph.text.strip(): paragraph.delete() ``` # Working with Sections Word supports the notion of a *section*, a division of a document having the same page layout settings, such as margins and page orientation. This is how, for example, a document can contain some pages in portrait layout and others in landscape. Each section also defines the headers and footers that apply to the pages of that section. Most Word documents have only the single section that comes by default and further, most of those have no reason to change the default margins or other page layout. But when you *do* need to change the page layout, you'll need to understand sections to get it done. ## Accessing sections Access to document sections is provided by the `sections` property on the Document object: ``` >>> document = Document() >>> sections = document.sections >>> sections >>> len(sections) 3 >>> section = sections[0] >>> section >>> for section in sections: ... print(section.start_type) ... NEW_PAGE (2) EVEN_PAGE (3) ODD_PAGE (4) ``` It's theoretically possible for a document not to have any explicit sections, although I've yet to see this occur in the wild. If you're accessing an unpredictable population of .docx files you may want to provide for that possibility using a `len()` check or `try` block to avoid an uncaught `IndexError` exception stopping your program. ## Adding a new section The Document.add_section method allows a new section to be started at the end of the document. Paragraphs and tables added after calling this method will appear in the new section: ``` >>> current_section = document.sections[-1] # last section in document >>> current_section.start_type NEW_PAGE (2) >>> new_section = document.add_section(WD_SECTION.ODD_PAGE) >>> new_section.start_type ODD_PAGE (4) ``` ## Section properties The Section object has eleven properties that allow page layout settings to be discovered and specified. ### Section start type Section.start_type describes the type of break that precedes the section: ``` >>> section.start_type NEW_PAGE (2) >>> section.start_type = WD_SECTION.ODD_PAGE >>> section.start_type ODD_PAGE (4) ``` Values of `start_type` are members of the WdSectionStart enumeration. ### Page dimensions and orientation Three properties on Section describe page dimensions and orientation. Together these can be used, for example, to change the orientation of a section from portrait to landscape: ``` >>> section.orientation, section.page_width, section.page_height (PORTRAIT (0), 7772400, 10058400) # (Inches(8.5), Inches(11)) >>> new_width, new_height = section.page_height, section.page_width >>> section.orientation = WD_ORIENT.LANDSCAPE >>> section.page_width = new_width >>> section.page_height = new_height >>> section.orientation, section.page_width, section.page_height (LANDSCAPE (1), 10058400, 7772400) ``` ### Page margins Seven properties on Section together specify the various edge spacings that determine where text appears on the page: ``` >>> from docx.shared import Inches >>> section.left_margin, section.right_margin (1143000, 1143000) # (Inches(1.25), Inches(1.25)) >>> section.top_margin, section.bottom_margin (914400, 914400) # (Inches(1), Inches(1)) >>> section.gutter 0 >>> section.header_distance, section.footer_distance (457200, 457200) # (Inches(0.5), Inches(0.5)) >>> section.left_margin = Inches(1.5) >>> section.right_margin = Inches(1) >>> section.left_margin, section.right_margin (1371600, 914400) ``` ## Multiple text columns A section can lay its text out in columns, as a newsletter does: ``` from docx.shared import Cm section = document.sections[0] section.column_count = 2 section.column_spacing = Cm(1) ``` `column_count` is the number of columns, and `column_spacing` the gap between them as a Length; `column_spacing` reads as `None` when the document does not specify one, in which case Word applies its own default. Because this is a section property, changing the number of columns partway through a document means starting a new section at that point: ``` from docx.enum.section import WD_SECTION two_up = document.add_section(WD_SECTION.CONTINUOUS) two_up.column_count = 2 ``` ## Page borders A page border is a section property, spelled the same way as the table, cell and paragraph borders: ``` from docx.enum.table import WD_LINE_STYLE from docx.shared import Pt, RGBColor borders = document.sections[0].page_borders for edge in ("top", "bottom", "left", "right"): borders[edge].line = WD_LINE_STYLE.SINGLE borders[edge].size = Pt(1) borders[edge].color = RGBColor(0x33, 0x33, 0x33) ``` Each edge exposes `line`, `size`, `color` and `space`, and `borders.clear()` removes the lot. Three settings apply to the frame as a whole rather than to one edge: ``` borders.display = "firstPage" # -- which pages get one borders.offset_from = "text" # -- measured from the text, not the page edge borders.z_order = "back" # -- drawn behind the page content ``` | Property | Values | Word's default when absent | | ------------- | --------------------------------------------- | -------------------------- | | `display` | `"allPages"`, `"firstPage"`, `"notFirstPage"` | all pages | | `offset_from` | `"page"`, `"text"` | page | | `z_order` | `"front"`, `"back"` | front | These three take the attribute value itself rather than an enumeration member, because each is a two- or three-value set with no `WdEnumeration` counterpart to mirror. A value outside the set raises `ValueError` rather than writing a document Word will reject. `offset_from` is the one that catches people out. Measured from the page edge — the default — the `space` on each edge is the distance in from the paper; measured from the text it is the distance out from the text block, which is what Word's "Measure from: Text" setting does and what a border that has to clear a header needs. ## Right-to-left and vertical text Section.bidi sets the default base direction for the section, and Section.text_direction the default flow direction. Both are defaults a paragraph or a cell can override — see [Right-to-left and vertical text](https://toxicphreak.github.io/python-docx-ng/user/text/#right-to-left-and-vertical-text). ``` section.bidi = True ``` # Understanding pictures and other shapes Conceptually, Word documents have two *layers*, a *text layer* and a *drawing layer*. In the text layer, text objects are flowed from left to right and from top to bottom, starting a new page when the prior one is filled. In the drawing layer, drawing objects, called *shapes*, are placed at arbitrary positions. These are sometimes referred to as *floating* shapes. A picture is a shape that can appear in either the text or drawing layer. When it appears in the text layer it is called an *inline shape*, or more specifically, an *inline picture*. Inline shapes are treated like a big text character (a *character glyph*). The line height is increased to accomodate the shape and the shape is wrapped to a line it will fit on width-wise, just like text. Inserting text in front of it will cause it to move to the right. Often, a picture is placed in a paragraph by itself, but this is not required. It can have text before and after it in the paragraph in which it's placed. Both kinds are supported. Document.add_picture() adds an inline picture at the end of the document in a paragraph of its own, and Run.add_picture() places one mid-paragraph, so you can have text on either side of it or both. For a picture in the drawing layer — one the text flows around rather than sitting on a line — see Run.add_float_picture() and [Floating images](https://toxicphreak.github.io/python-docx-ng/user/images/index.md). ## Reading the picture back A shape hands back the image it displays through InlineShape.image and FloatingShape.image, which is `None` for a shape that is not a picture — a chart or a SmartArt diagram. See [Reading the images already in a document](https://toxicphreak.github.io/python-docx-ng/user/images/#reading-the-images-already-in-a-document). # Understanding Styles **Grasshopper:**\ *"Master, why doesn't my paragraph appear with the style I specified?"* **Master:**\ *"You have come to the right page Grasshopper; read on ..."* ## What is a style in Word? Documents communicate better when like elements are formatted consistently. To achieve that consistency, professional document designers develop a *style sheet* which defines the document element types and specifies how each should be formatted. For example, perhaps body paragraphs are to be set in 9 pt Times Roman with a line height of 11 pt, justified flush left, ragged right. When these specifications are applied to each of the elements of the document, a consistent and polished look is achieved. A style in Word is such a set of specifications that may be applied, all at once, to a document element. Word has paragraph styles, character styles, table styles, and numbering definitions. These are applied to a paragraph, a span of text, a table, and a list, respectively. Experienced programmers will recognize styles as a level of indirection. The great thing about those is it allows you to define something once, then apply that definition many times. This saves the work of defining the same thing over an over; but more importantly it allows you to change the definition and have that change reflected in all the places you have applied it. ## Why doesn't the style I applied show up? This is likely to show up quite a bit until I can add some fancier features to work around it, so here it is up top. 1. When you're working in Word, there are all these styles you can apply to things, pretty good looking ones that look all the better because you don't have to make them yourself. Most folks never look further than the built-in styles. 1. Although those styles show up in the UI, they're not actually in the document you're creating, at least not until you use it for the first time. That's kind of a good thing. They take up room and there's a lot of them. The file would get a little bloated if it contained all the style definitions you could use but haven't. 1. If you apply a style using `python-docx` that's not defined in your file (in the styles.xml part if you're curious), Word just ignores it. It doesn't complain, it just doesn't change how things are formatted. I'm sure there's a good reason for this. But it can present as a bit of a puzzle if you don't understand how Word works that way. 1. When you use a style, Word adds it to the file. Once there, it stays. I imagine there's a way to get rid of it, but you have to work at it. If you apply a style, delete the content you applied it to, and then save the document; the style definition stays in the saved file. All this adds up to the following: If you want to use a style in a document you create with `python-docx`, the document you start with must contain the style definition. Otherwise it just won't work. It won't raise an exception, it just won't work. If you use the "default" template document, it contains the styles listed below, most of the ones you're likely to want if you're not designing your own. If you're using your own starting document, you need to use each of the styles you want at least once in it. You don't have to keep the content, but you need to apply the style to something at least once before saving the document. Creating a one-word paragraph, applying five styles to it in succession and then deleting the paragraph works fine. That's how I got the ones below into the default template :). ## Glossary style definition\ A `` element in the styles part of a document that explicitly defines the attributes of a style. defined style\ A style that is explicitly defined in a document. Contrast with *latent style*. built-in style\ One of the set of 276 pre-set styles built into Word, such as "Heading 1". A built-in style can be either defined or latent. A built-in style that is not yet defined is known as a *latent style*. Both defined and latent built-in styles may appear as options in Word's style panel and style gallery. custom style\ Also known as a *user defined style*, any style defined in a Word document that is not a built-in style. Note that a custom style cannot be a latent style. latent style\ A built-in style having no definition in a particular document is known as a *latent style* in that document. A latent style can appear as an option in the Word UI depending on the settings in the LatentStyles object for the document. recommended style list\ A list of styles that appears in the styles toolbox or panel when "Recommended" is selected from the "List:" dropdown box. Style Gallery\ The selection of example styles that appear in the ribbon of the Word UI and which may be applied by clicking on one of them. ## Identifying a style A style has three identifying properties, *name*, *style_id*, and *type*. Each style's name property is its stable, unique identifier for access purposes. A style's style_id is used internally to key a content object such as a paragraph to its style. However this value is generated automatically by Word and is not guaranteed to be stable across saves. In general, the style id is formed simply by removing spaces from the *localized* style name, however there are exceptions. Users of `python-docx` should generally avoid using the style id unless they are confident with the internals involved. A style's type is set at creation time and cannot be changed. ## Built-in styles Word comes with almost 300 so-called *built-in* styles like *Normal*, *Heading 1*, and *List Bullet*. Style definitions are stored in the *styles.xml* part of a .docx package, but built-in style definitions are stored in the Word application itself and are not written to *styles.xml* until they are actually used. This is a sensible strategy because they take up considerable room and would be largely redundant and useless overhead in every .docx file otherwise. The fact that built-in styles are not written to the .docx package until used gives rise to the need for *latent style* definitions, explained below. ## Style Behavior In addition to collecting a set of formatting properties, a style has five properties that specify its *behavior*. This behavior is relatively simple, basically amounting to when and where the style appears in the Word or LibreOffice UI. The key notion to understanding style behavior is the recommended list. In the style pane in Word, the user can select which list of styles they want to see. One of these is named *Recommended* and is known as the *recommended list*. All five behavior properties affect some aspect of the style’s appearance in this list and in the style gallery. In brief, a style appears in the recommended list if its hidden property is `False` (the default). If a style is not hidden and its quick_style property is `True`, it also appears in the style gallery. If a hidden style's unhide_when_used property is `True`, its hidden property is set `False` the first time it is used. Styles in the style lists and style gallery are sorted in priority order, then alphabetically for styles of the same priority. If a style's locked property is `True` and formatting restrictions are turned on for the document, the style will not appear in any list or the style gallery and cannot be applied to content. ## Latent styles The need to specify the UI behavior of built-in styles not defined in *styles.xml* gives rise to the need for *latent style* definitions. A latent style definition is basically a stub style definition that has at most the five behavior attributes in addition to the style name. Additional space is saved by defining defaults for each of the behavior attributes, so only those that differ from the default need be defined and styles that match all defaults need no latent style definition. Latent style definitions are specified using the *w:latentStyles* and *w:lsdException* elements appearing in *styles.xml*. A latent style definition is only required for a built-in style because only a built-in style can appear in the UI without a style definition in *styles.xml*. ## Style inheritance A style can inherit properties from another style, somewhat similarly to how Cascading Style Sheets (CSS) works. Inheritance is specified using the `base_style` attribute. By basing one style on another, an inheritance hierarchy of arbitrary depth can be formed. A style having no base style inherits properties from the document defaults. ## Paragraph styles in default template - Normal - Body Text - Body Text 2 - Body Text 3 - Caption - Heading 1 - Heading 2 - Heading 3 - Heading 4 - Heading 5 - Heading 6 - Heading 7 - Heading 8 - Heading 9 - Intense Quote - List - List 2 - List 3 - List Bullet - List Bullet 2 - List Bullet 3 - List Continue - List Continue 2 - List Continue 3 - List Number - List Number 2 - List Number 3 - List Paragraph - Macro Text - No Spacing - Quote - Subtitle - TOCHeading - Title ## Character styles in default template - Body Text Char - Body Text 2 Char - Body Text 3 Char - Book Title - Default Paragraph Font - Emphasis - Heading 1 Char - Heading 2 Char - Heading 3 Char - Heading 4 Char - Heading 5 Char - Heading 6 Char - Heading 7 Char - Heading 8 Char - Heading 9 Char - Intense Emphasis - Intense Quote Char - Intense Reference - Macro Text Char - Quote Char - Strong - Subtitle Char - Subtle Emphasis - Subtle Reference - Title Char ## Table styles in default template - Table Normal - Colorful Grid - Colorful Grid Accent 1 - Colorful Grid Accent 2 - Colorful Grid Accent 3 - Colorful Grid Accent 4 - Colorful Grid Accent 5 - Colorful Grid Accent 6 - Colorful List - Colorful List Accent 1 - Colorful List Accent 2 - Colorful List Accent 3 - Colorful List Accent 4 - Colorful List Accent 5 - Colorful List Accent 6 - Colorful Shading - Colorful Shading Accent 1 - Colorful Shading Accent 2 - Colorful Shading Accent 3 - Colorful Shading Accent 4 - Colorful Shading Accent 5 - Colorful Shading Accent 6 - Dark List - Dark List Accent 1 - Dark List Accent 2 - Dark List Accent 3 - Dark List Accent 4 - Dark List Accent 5 - Dark List Accent 6 - Light Grid - Light Grid Accent 1 - Light Grid Accent 2 - Light Grid Accent 3 - Light Grid Accent 4 - Light Grid Accent 5 - Light Grid Accent 6 - Light List - Light List Accent 1 - Light List Accent 2 - Light List Accent 3 - Light List Accent 4 - Light List Accent 5 - Light List Accent 6 - Light Shading - Light Shading Accent 1 - Light Shading Accent 2 - Light Shading Accent 3 - Light Shading Accent 4 - Light Shading Accent 5 - Light Shading Accent 6 - Medium Grid 1 - Medium Grid 1 Accent 1 - Medium Grid 1 Accent 2 - Medium Grid 1 Accent 3 - Medium Grid 1 Accent 4 - Medium Grid 1 Accent 5 - Medium Grid 1 Accent 6 - Medium Grid 2 - Medium Grid 2 Accent 1 - Medium Grid 2 Accent 2 - Medium Grid 2 Accent 3 - Medium Grid 2 Accent 4 - Medium Grid 2 Accent 5 - Medium Grid 2 Accent 6 - Medium Grid 3 - Medium Grid 3 Accent 1 - Medium Grid 3 Accent 2 - Medium Grid 3 Accent 3 - Medium Grid 3 Accent 4 - Medium Grid 3 Accent 5 - Medium Grid 3 Accent 6 - Medium List 1 - Medium List 1 Accent 1 - Medium List 1 Accent 2 - Medium List 1 Accent 3 - Medium List 1 Accent 4 - Medium List 1 Accent 5 - Medium List 1 Accent 6 - Medium List 2 - Medium List 2 Accent 1 - Medium List 2 Accent 2 - Medium List 2 Accent 3 - Medium List 2 Accent 4 - Medium List 2 Accent 5 - Medium List 2 Accent 6 - Medium Shading 1 - Medium Shading 1 Accent 1 - Medium Shading 1 Accent 2 - Medium Shading 1 Accent 3 - Medium Shading 1 Accent 4 - Medium Shading 1 Accent 5 - Medium Shading 1 Accent 6 - Medium Shading 2 - Medium Shading 2 Accent 1 - Medium Shading 2 Accent 2 - Medium Shading 2 Accent 3 - Medium Shading 2 Accent 4 - Medium Shading 2 Accent 5 - Medium Shading 2 Accent 6 - Table Grid # Working with Styles This page uses concepts developed in the prior page without introduction. If a term is unfamiliar, consult the prior page [understanding styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/index.md) for a definition. ## Access a style Styles are accessed using the Document.styles attribute: ``` >>> document = Document() >>> styles = document.styles >>> styles ``` The Styles object provides dictionary-style access to defined styles by name: ``` >>> styles['Normal'] ``` Note Built-in styles are stored in a WordprocessingML file using their English name, e.g. 'Heading 1', even though users working on a localized version of Word will see native language names in the UI, e.g. 'Kop 1'. Because `python-docx` operates on the WordprocessingML file, style lookups must use the English name. A document available on this external site allows you to create a mapping between local language names and English style names: > User-defined styles, also known as *custom styles*, are not localized and are accessed with the name exactly as it appears in the Word UI. The Styles object is also iterable. By using the identification properties on BaseStyle, various subsets of the defined styles can be generated. For example, this code will produce a list of the defined paragraph styles: ``` >>> from docx.enum.style import WD_STYLE_TYPE >>> styles = document.styles >>> paragraph_styles = [ ... s for s in styles if s.type == WD_STYLE_TYPE.PARAGRAPH ... ] >>> for style in paragraph_styles: ... print(style.name) ... Normal Body Text List Bullet ``` ## Apply a style The Paragraph, Run, and Table objects each have a style attribute. Assigning a style object to this attribute applies that style: ``` >>> document = Document() >>> paragraph = document.add_paragraph() >>> paragraph.style >>> paragraph.style.name 'Normal' >>> paragraph.style = document.styles['Heading 1'] >>> paragraph.style.name 'Heading 1' ``` A style name can also be assigned directly, in which case `python-docx` will do the lookup for you: ``` >>> paragraph.style = 'List Bullet' >>> paragraph.style >>> paragraph.style.name 'List Bullet' ``` A style can also be applied at creation time using either the style object or its name: ``` >>> paragraph = document.add_paragraph(style='Body Text') >>> paragraph.style.name 'Body Text' >>> body_text_style = document.styles['Body Text'] >>> paragraph = document.add_paragraph(style=body_text_style) >>> paragraph.style.name 'Body Text' ``` ## Add or delete a style A new style can be added to the document by specifying a unique name and a style type: ``` >>> from docx.enum.style import WD_STYLE_TYPE >>> styles = document.styles >>> style = styles.add_style('Citation', WD_STYLE_TYPE.PARAGRAPH) >>> style.name 'Citation' >>> style.type PARAGRAPH (1) ``` Use the `base_style` property to specify a style the new style should inherit formatting settings from: ``` >>> style.base_style None >>> style.base_style = styles['Normal'] >>> style.base_style >>> style.base_style.name 'Normal' ``` A style can be removed from the document simply by calling its delete method: ``` >>> styles = document.styles >>> len(styles) 10 >>> styles['Citation'].delete() >>> len(styles) 9 ``` Note The `Style.delete` method removes the style's definition from the document. It does not affect content in the document to which that style is applied. Content having a style not defined in the document is rendered using the default style for that content object, e.g. 'Normal' in the case of a paragraph. ## Document defaults Below every style in the inheritance chain sits `w:docDefaults`, the formatting that applies to content no style has spoken for. For many documents in the wild it is the only place the base font is set, so a style walk that stops at `Normal` reads the wrong answer: ``` document.styles.default_font.name # -> the document-wide default typeface document.styles.default_font.size document.styles.default_paragraph_format.space_after document.styles.default_paragraph_format.line_spacing ``` Styles.default_font is a Font and Styles.default_paragraph_format a ParagraphFormat, so everything documented for those works here. Both are writable, and setting a document default is the broadest change you can make to a document's appearance in one statement: ``` from docx.shared import Pt document.styles.default_font.name = "Calibri" document.styles.default_font.size = Pt(11) ``` The inheritance order, most specific first: direct formatting on a run, then its character style, then the paragraph style, then `w:docDefaults`. ## Define character formatting Character, paragraph, and table styles can all specify character formatting to be applied to content with that style. All the character formatting that can be applied directly to text can be specified in a style. Examples include font typeface and size, bold, italic, and underline. Each of these three style types have a `font` attribute providing access to a Font object. A style's Font object provides properties for getting and setting the character formatting for that style. Several examples are provided here. For a complete set of the available properties, see the Font API documentation. The font for a style can be accessed like this: ``` >>> from docx import Document >>> document = Document() >>> style = document.styles['Normal'] >>> font = style.font ``` Typeface and size are set like this: ``` >>> from docx.shared import Pt >>> font.name = 'Calibri' >>> font.size = Pt(12) ``` Many font properties are *tri-state*, meaning they can take the values `True`, `False`, and `None`. `True` means the property is "on", `False` means it is "off". Conceptually, the `None` value means "inherit". Because a style exists in an inheritance hierarchy, it is important to have the ability to specify a property at the right place in the hierarchy, generally as far up the hierarchy as possible. For example, if all headings should be in the Arial typeface, it makes more sense to set that property on the *Heading 1* style and have *Heading 2* inherit from *Heading 1*. Bold and italic are tri-state properties, as are all-caps, strikethrough, superscript, and many others. See the Font API documentation for a full list: ``` >>> font.bold, font.italic (None, None) >>> font.italic = True >>> font.italic True >>> font.italic = False >>> font.italic False >>> font.italic = None >>> font.italic None ``` Underline is a bit of a special case. It is a hybrid of a tri-state property and an enumerated value property. `True` means single underline, by far the most common. `False` means no underline, but more often `None` is the right choice if no underlining is wanted since it is rare to inherit it from a base style. The other forms of underlining, such as double or dashed, are specified with a member of the WdUnderline enumeration: ``` >>> font.underline None >>> font.underline = True >>> # or perhaps >>> font.underline = WD_UNDERLINE.DOT_DASH ``` ## Define paragraph formatting Both a paragraph style and a table style allow paragraph formatting to be specified. These styles provide access to a ParagraphFormat object via their `paragraph_format` property. Paragraph formatting includes layout behaviors such as justification, indentation, space before and after, page break before, and widow/orphan control. For a complete list of the available properties, consult the API documentation page for the ParagraphFormat object. Here's an example of how you would create a paragraph style having hanging indentation of 1/4 inch, 12 points spacing above, and widow/orphan control: ``` >>> from docx.enum.style import WD_STYLE_TYPE >>> from docx.shared import Inches, Pt >>> document = Document() >>> style = document.styles.add_style('Indent', WD_STYLE_TYPE.PARAGRAPH) >>> paragraph_format = style.paragraph_format >>> paragraph_format.left_indent = Inches(0.25) >>> paragraph_format.first_line_indent = Inches(-0.25) >>> paragraph_format.space_before = Pt(12) >>> paragraph_format.widow_control = True ``` ## Use paragraph-specific style properties A paragraph style has a `next_paragraph_style` property that specifies the style to be applied to new paragraphs inserted after a paragraph of that style. This is most useful when the style would normally appear only once in a sequence, such as a heading. In that case, the paragraph style can automatically be set back to a body style after completing the heading. In the most common case (body paragraphs), subsequent paragraphs should receive the same style as the current paragraph. The default handles this case well by applying the same style if a next paragraph style is not specified. Here's an example of how you would change the next paragraph style of the *Heading 1* style to *Body Text*: ``` >>> from docx import Document >>> document = Document() >>> styles = document.styles >>> styles['Heading 1'].next_paragraph_style = styles['Body Text'] ``` The default behavior can be restored by assigning `None` or the style itself: ``` >>> heading_1_style = styles['Heading 1'] >>> heading_1_style.next_paragraph_style.name 'Body Text' >>> heading_1_style.next_paragraph_style = heading_1_style >>> heading_1_style.next_paragraph_style.name 'Heading 1' >>> heading_1_style.next_paragraph_style = None >>> heading_1_style.next_paragraph_style.name 'Heading 1' ``` ## Control how a style appears in the Word UI The properties of a style fall into two categories, *behavioral properties* and *formatting properties*. Its behavioral properties control when and where the style appears in the Word UI. Its formatting properties determine the formatting of content to which the style is applied, such as the size of the font and its paragraph indentation. There are five behavioral properties of a style: - hidden - unhide_when_used - priority - quick_style - locked See the [style behavior](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/#style-behavior) section in [understanding styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/index.md) for a description of how these behavioral properties interact to determine when and where a style appears in the Word UI. The priority property takes an integer value. The other four style behavior properties are *tri-state*, meaning they can take the value `True` (on), `False` (off), or `None` (inherit). ### Display a style in the style gallery The following code will cause the 'Body Text' paragraph style to appear first in the style gallery: ``` >>> from docx import Document >>> document = Document() >>> style = document.styles['Body Text'] >>> style.hidden = False >>> style.quick_style = True >>> style.priorty = 1 ``` ### Remove a style from the style gallery This code will remove the 'Normal' paragraph style from the style gallery, but allow it to remain in the recommended list: ``` >>> style = document.styles['Normal'] >>> style.hidden = False >>> style.quick_style = False ``` ## Working with Latent Styles See the [builtin styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/#built-in-styles) and [latent styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/#latent-styles) sections in [understanding styles](https://toxicphreak.github.io/python-docx-ng/user/styles-understanding/index.md) for a description of how latent styles define the behavioral properties of built-in styles that are not yet defined in the *styles.xml* part of a .docx file. ### Access the latent styles in a document The latent styles in a document are accessed from the styles object: ``` >>> document = Document() >>> latent_styles = document.styles.latent_styles ``` A LatentStyles object supports `len`, iteration, and dictionary-style access by style name: ``` >>> len(latent_styles) 161 >>> latent_style_names = [ls.name for ls in latent_styles] >>> latent_style_names ['Normal', 'Heading 1', 'Heading 2', ... 'TOC Heading'] >>> latent_quote = latent_styles['Quote'] >>> latent_quote >>> latent_quote.priority 29 ``` ### Change latent style defaults The LatentStyles object also provides access to the default behavioral properties for built-in styles in the current document. These defaults provide the value for any undefined attributes of the \_LatentStyle definitions and to all behavioral properties of built-in styles having no explicit latent style definition. See the API documentation for the LatentStyles object for the complete set of available properties: ``` >>> latent_styles.default_to_locked False >>> latent_styles.default_to_locked = True >>> latent_styles.default_to_locked True ``` ### Add a latent style definition A new latent style can be added using the add_latent_style method on LatentStyles. This code adds a new latent style for the builtin style 'List Bullet', setting it to appear in the style gallery: ``` >>> latent_style = latent_styles['List Bullet'] KeyError: no latent style with name 'List Bullet' >>> latent_style = latent_styles.add_latent_style('List Bullet') >>> latent_style.hidden = False >>> latent_style.priority = 2 >>> latent_style.quick_style = True ``` ### Delete a latent style definition A latent style definition can be deleted by calling its `delete` method: ``` >>> latent_styles['Light Grid'] >>> latent_styles['Light Grid'].delete() >>> latent_styles['Light Grid'] KeyError: no latent style with name 'Light Grid' ``` ## Which styles are in use ``` document.styles.usage() # -> a full StyleUsage report document.styles.unused # -> the styles nothing reaches document.styles["Quote"].in_use # -> True or False ``` "Used" is a reachability closure over every story part in the document, not a scan of the body — a style that is only ever the `basedOn` of another one is used. See [Style usage and cleanup](https://toxicphreak.github.io/python-docx-ng/user/cleanup/index.md), which also covers Styles.remove_unused() and Document.cleanup(). ## Moving styles between documents Styles.copy_style_from() copies one style with its dependency closure. Three operations build on it, for whole sets of styles at a time: ``` # -- pull a house template's styles into a generated document -- report = document.styles.import_from("house-style.dotx") # -- push a document's styles out into a template of their own -- document.styles.extract("house.dotx", as_template=True) # -- or just the styles.xml bytes -- xml = document.styles.extract_xml(["Heading 1", "Heading 2"]) ``` See [Templates and embedded files](https://toxicphreak.github.io/python-docx-ng/user/templates/#importing-a-templates-styles) for what each of them does with a name collision and what `report` contains. # Working with Tables Word provides sophisticated capabilities to create tables. As usual, this power comes with additional conceptual complexity. This complexity becomes most apparent when *reading* tables, in particular from documents drawn from the wild where there is limited or no prior knowledge as to what the tables might contain or how they might be structured. These are some of the important concepts you'll need to understand. ## Concept: Simple (uniform) tables ``` +---+---+---+ | a | b | c | +---+---+---+ | d | e | f | +---+---+---+ | g | h | i | +---+---+---+ ``` The basic concept of a table is intuitive enough. You have *rows* and *columns*, and at each (row, column) position is a different *cell*. It can be described as a *grid* or a *matrix*. Let's call this concept a *uniform table*. A relational database table and a Pandas dataframe are both examples of a uniform table. The following invariants apply to uniform tables: - Each row has the same number of cells, one for each column. - Each column has the same number of cells, one for each row. ## Complication 1: Merged Cells ``` +---+---+---+ +---+---+---+ | a | b | | | b | c | +---+---+---+ + a +---+---+ | c | d | e | | | d | e | +---+---+---+ +---+---+---+ | f | g | h | | f | g | h | +---+---+---+ +---+---+---+ ``` While very suitable for data processing, a uniform table lacks expressive power desireable for tables intended for a human reader. Perhaps the most important characteristic a uniform table lacks is *merged cells*. It is very common to want to group multiple cells into one, for example to form a column-group heading or provide the same value for a sequence of cells rather than repeat it for each cell. These make a rendered table more *readable* by reducing the cognitive load on the human reader and make certain relationships explicit that might easily be missed otherwise. Unfortunately, accommodating merged cells breaks both the invariants of a uniform table: - Each row can have a different number of cells. - Each column can have a different number of cells. This challenges reading table contents programatically. One might naturally want to read the table into a uniform matrix data structure like a 3 x 3 "2D array" (list of lists perhaps), but this is not directly possible when the table is not known to be uniform. ## Concept: The layout grid ``` + - + - + - + | | | | + - + - + - + | | | | + - + - + - + | | | | + - + - + - + ``` In Word, each table has a *layout grid*. - The layout grid is *uniform*. There is a layout position for every (layout-row, layout-column) pair. - The layout grid itself is not visible. However it is represented and referenced by certain elements and attributes within the table XML - Each table cell is located at a layout-grid position; i.e. the top-left corner of each cell is the top-left corner of a layout-grid cell. - Each table cell occupies one or more whole layout-grid cells. A merged cell will occupy multiple layout-grid cells. No table cell can occupy a partial layout-grid cell. - Another way of saying this is that every vertical boundary (left and right) of a cell aligns with a layout-grid vertical boundary, likewise for horizontal boundaries. But not all layout-grid boundaries need be occupied by a cell boundary of the table. ## Complication 2: Omitted Cells ``` +---+---+ +---+---+---+ | a | b | | a | b | c | +---+---+---+ +---+---+---+ | c | d | | d | +---+---+ +---+---+---+ | e | | e | f | g | +---+ +---+---+---+ ``` Word is unusual in that it allows cells to be omitted from the beginning or end (but not the middle) of a row. A typical practical example is a table with both a row of column headings and a column of row headings, but no top-left cell (position 0, 0), such as this XOR truth table. ``` +---+---+ | T | F | +---+---+---+ | T | F | T | +---+---+---+ | F | T | F | +---+---+---+ ``` In *python-docx*, omitted cells in a \_Row object are represented by the `.grid_cols_before` and `.grid_cols_after` properties. In the example above, for the first row, `.grid_cols_before` would equal `1` and `.grid_cols_after` would equal `0`. Note that omitted cells are not just "empty" cells. They represent layout-grid positions that are unoccupied by a cell and they cannot be represented by a \_Cell object. This distinction becomes important when trying to produce a uniform representation (e.g. a 2D array) for an arbitrary Word table. ## Concept: *python-docx* approximates uniform tables by default To accurately represent an arbitrary table would require a complex graph data structure. Navigating this data structure would be at least as complex as navigating the *python-docx* object graph for a table. When extracting content from a collection of arbitrary Word files, such as for indexing the document, it is common to choose a simpler data structure and *approximate* the table in that structure. Reflecting on how a relational table or dataframe represents tabular information, a straightforward approximation would simply repeat merged-cell values for each layout-grid cell occupied by the merged cell: ``` +---+---+---+ +---+---+---+ | a | b | -> | a | a | b | +---+---+---+ +---+---+---+ | | d | e | -> | c | d | e | + c +---+---+ +---+---+---+ | | f | g | -> | c | f | g | +---+---+---+ +---+---+---+ ``` This is what `_Row.cells` does by default. Conceptually: ``` >>> [tuple(c.text for c in r.cells) for r in table.rows] [ (a, a, b), (c, d, e), (c, f, g), ] ``` Note this only produces a uniform "matrix" of cells when there are no omitted cells. Dealing with omitted cells requires a more sophisticated approach when maintaining column integrity is required: ``` # +---+---+ # | a | b | # +---+---+---+ # | c | d | # +---+---+ # | e | # +---+ def iter_row_cell_texts(row: _Row) -> Iterator[str]: for _ in range(row.grid_cols_before): yield "" for c in row.cells: yield c.text for _ in range(row.grid_cols_after): yield "" ``` ``` >>> [tuple(iter_row_cell_texts(r)) for r in table.rows] [ ("", "a", "b"), ("c", "d", ""), ("", "e", ""), ] ``` ## Complication 3: Tables are Recursive Further complicating table processing is their recursive nature. In Word, as in HTML, a table cell can itself include one or more tables. These can be detected using `_Cell.tables` or `_Cell.iter_inner_content()`. The latter preserves the document order of the table with respect to paragraphs also in the cell. ## Borders Table.borders and \_Cell.borders are mappings keyed by edge name: ``` from docx.enum.table import WD_LINE_STYLE from docx.shared import Pt, RGBColor table = document.add_table(rows=2, cols=2) table.borders["top"].line = WD_LINE_STYLE.SINGLE table.borders["top"].size = Pt(1) table.borders["top"].color = RGBColor(0xFF, 0x00, 0x00) ``` A table admits `left`, `right`, `top`, `bottom`, and `insideH` and `insideV` for the horizontal and vertical borders *between* its cells. A cell admits the same four edges plus the two diagonals, `tl2br` and `tr2bl`: ``` cell = table.cell(0, 0) cell.borders["bottom"].line = WD_LINE_STYLE.DOUBLE ``` A border set on a cell takes precedence over the table border at the same edge. Each edge exposes `line` (a WD_LINE_STYLE member), `size` (a Length), `color` (an RGBColor, not a hex string) and `space`. Setting `line` to `WD_LINE_STYLE.NONE` removes the border. ## Table width, indent and cell margins Table.width is the table's *preferred* width — Word treats it as a request and may narrow the table to fit its container. It takes either a Length or a percentage: ``` from docx.shared import Inches, Pct table.width = Inches(4) table.width = Pct(50) # -> half the container width table.width = None # -> auto-fit, which is the default ``` Pct is deliberately *not* a `Length`. Every `Length` unit is absolute and reduces to EMU; a percentage does not, and cannot be converted to one without knowing what it is a percentage of. Reading `width` back gives whichever of the two the table actually carries. Table.indent moves the whole table in from the margin, and Table.cell_margins sets the default padding inside every cell: ``` table.indent = Inches(0.5) table.cell_margins.left = Inches(0.1) table.cell_margins.top = Inches(0.05) table.cell_margins.clear() # -> back to inherited ``` The mapping admits `top`, `bottom`, `left`, `right` and the direction-relative `start` and `end`. These are the table-wide defaults; a cell's own `w:tcMar` overrides them where it has one. ## Which parts of a table style apply A table style can define different formatting for the first row, the last row, the first and last columns, and alternating bands. Which of those *apply* is not part of the style — it is a set of flags on the table, the ones Word shows as the "Table Style Options" checkboxes: ``` table.look.first_row = True # -- header row formatting on table.look.horizontal_banding = True # -- alternating row shading on table.look.last_column = False ``` Table.look exposes `first_row`, `last_row`, `first_column`, `last_column`, `horizontal_banding` and `vertical_banding`. Each is `True` or `False` — never `None`, because `w:tblLook` has a defined default for each flag rather than an inherited one. Note `w:tblLook` carries both modern per-flag attributes and a legacy `@w:val` bitmask, and older versions of Word read the bitmask. Setting a flag rewrites both, as Word does, so the table looks the same wherever it is opened. ## Row properties ``` from docx.enum.table import WD_TABLE_ALIGNMENT from docx.shared import Inches row = table.rows[0] row.repeat_as_header = True # -- "Repeat Header Rows" row.hidden = False row.alignment = WD_TABLE_ALIGNMENT.CENTER row.cell_spacing = Inches(0.02) row.width_before = Inches(0.5) row.width_after = Inches(0.5) ``` repeat_as_header is the useful one: it is what makes a header row reappear at the top of every page a long table spans. All of these are tri-state where the XML is — `None` means the value is inherited — and \_Row.height, `height_rule` and \_Row.dont_split round out `w:trPr`. `width_before` and `width_after` are the widths of the grid positions a row leaves unpopulated, the companions to the `grid_cols_before` and `grid_cols_after` counts described above. ## Text direction in a cell \_Cell.text_direction rotates the text in a cell, which is how a narrow column gets a readable heading: ``` from docx.enum.text import WD_TEXT_DIRECTION table.cell(0, 1).text_direction = WD_TEXT_DIRECTION.BT_LR # -- bottom-to-top ``` See [Right-to-left and vertical text](https://toxicphreak.github.io/python-docx-ng/user/text/#right-to-left-and-vertical-text) for the full set of WD_TEXT_DIRECTION values. ## Alternative text A table carries the same two alt-text values Word's "Alt Text" pane writes for a picture, and they matter for the same reason — an accessibility check on a generated document flags a table without them: ``` table = document.add_table(rows=2, cols=2) table.title = "Quarterly revenue" table.description = "Revenue by region for Q1 through Q4 2026, in thousands of euro." ``` Both are read/write on Table and both are `None` when unset; assigning `None` removes them. They are stored as `w:tblCaption` and `w:tblDescription` and are never rendered — this is metadata read by assistive technology, not a visible caption above or below the table. They can also be given when the table is created, which saves the round trip and matches the way add_picture() takes them: ``` table = document.add_table( rows=2, cols=2, style="Light Grid Accent 1", title="Quarterly revenue", description="Revenue by region for Q1 through Q4 2026, in thousands of euro.", ) ``` Both are keyword-only, both default to `None`, and omitting them writes nothing. \_Cell.add_table() and BlockItemContainer.add_table() take them too. ## Captions \_Cell.add_caption() puts a self-renumbering caption inside a cell. For a caption above or below a table, use Document.add_caption() — see [Captions](https://toxicphreak.github.io/python-docx-ng/user/fields/#captions). # Templates, styles across documents, embedded files and macros ## Word templates A `.dotx` or `.dotm` template holds exactly the same markup as a document. It differs only in the content type of its main part, which is what tells Word to start a *new* document from it rather than open it for editing. Templates open like anything else: ``` from docx import Document document = Document("house-style.dotx") document.is_template # -> True ``` save() keeps whichever it already is, so a template opened and saved is still a template. `as_template` overrides that, which is how you generate a document from a template — or turn a document into one: ``` document.save("report.docx", as_template=False) # a document from a template document.save("house-style.dotx", as_template=True) # a template from a document ``` Macro-enabled input stays macro-enabled either way, so a `.dotm` saved with `as_template=False` is a `.docm`. Note This sets the content type. It does not choose the file extension for you — pass a name whose extension matches, or Word will complain about the mismatch. ## Copying a style between documents Applying a style by name fails with `KeyError: no style with name 'Callout'` whenever the target document's style part lacks it, which is routine when content is assembled from several sources. copy_style_from() brings one across: ``` template = Document("house-style.dotx") report = Document() callout = template.styles["Callout"] report.styles.copy_style_from(callout) report.add_paragraph("Mind the gap", style="Callout") ``` **The dependency closure is the point.** A style is not a self-contained object: `w:basedOn` names the style it inherits from, `w:next` the style for the following paragraph, and `w:link` the paired character or paragraph style. Copying one `w:style` element by hand gives a style whose `basedOn` target is missing, which then renders as if it inherited from Normal. Those are followed and copied too, unless you say otherwise: ``` report.styles.copy_style_from(callout, include_dependencies=False) ``` A list style references `numbering.xml`, so `include_numbering` (on by default) copies the `w:num` and `w:abstractNum` behind it and rewrites the reference to the new id. ### Name collisions ``` report.styles.copy_style_from(callout, name="House Callout") report.styles.copy_style_from(callout, on_collision="overwrite") ``` `on_collision` decides what happens when the target already has a style of that name: | Value | Behaviour | | ------------------ | ----------------------------------------------- | | `"skip"` (default) | leave the existing style alone and return it | | `"overwrite"` | replace its definition | | `"rename"` | copy under a free name — "Callout 2", and so on | | `"raise"` | raise `ValueError` | Warning **Theme fonts are not carried over.** A style referencing `w:asciiTheme` resolves against *this* document's theme part, so a copied style can legitimately look different in its new home. ## Importing a template's styles `copy_style_from()` moves one style. Two operations built on it move whole sets, which is what a house template usually calls for: ``` report = Document() result = report.styles.import_from("house-style.dotx") # -> {"Callout": "added", "Heading 1": "skipped", ...} ``` Styles.import_from() accepts a path, a stream or an already-open `Document` — a `.dotx` opens without special handling, since a template holds the same main part as a document. It returns a report keyed by UI name, saying what it did with each: `"added"`, `"replaced"` or `"skipped"`. ``` report.styles.import_from("house-style.dotx", ["Callout", "Sidebar"]) report.styles.import_from("house-style.dotx", overwrite=True) report.styles.import_from("house-style.dotx", include_latent=True) ``` Without `overwrite`, a name the destination already defines is skipped and reported as such — the existing definition wins, which is what "import these styles into my document" almost always means. `include_latent` also brings the source's `w:latentStyles` exceptions across, and is off by default because that changes which of Word's built-ins appear in the destination's style gallery. The other direction pulls styles *out* of a document into a template of their own: ``` added = report.styles.extract("house.dotx", as_template=True) added = report.styles.extract("headings.docx", ["Heading 1", "Heading 2"]) xml = report.styles.extract_xml(["Heading 1"]) # -> just the styles.xml bytes ``` Styles.extract() writes an otherwise empty document carrying the named styles and their dependency closure, and returns the names it added. Naming nothing extracts every style the document defines. The extract starts from a template whose own unused styles have been pruned, so what comes out is the styles you named plus the handful the closure keeps alive — `Normal`, `Default Paragraph Font` and the other defaults — rather than those plus the 168 the bundled template ships. `as_template=True` writes a `.dotx` — the same content with the template content type, so Word treats it as a template rather than a document. ## Embedded OLE objects An OLE object is a whole file carried *inside* the document and shown as an icon or a preview image that opens the original application on double-click. This is a different thing from an `altChunk`: an `altChunk` is dissolved into the document when Word opens the file, whereas an embedded object stays a distinct file forever. The read side matters on its own. A document with attachments embedded in it previously gave no way to discover that they exist, let alone get them out: ``` from pathlib import Path for obj in document.embedded_objects: print(obj.prog_id, obj.content_type, obj.filename) if obj.blob is not None: Path(obj.filename or "attachment").write_bytes(obj.blob) ``` Document.embedded_objects covers the body and Run.embedded_objects one run. Each EmbeddedObject offers: | | | | --------------- | --------------------------------------------------------------------------------------------------------------------------- | | `prog_id` | the application Word launches, e.g. `"Excel.Sheet.12"` | | `blob` | the bytes of the embedded file, or `None` | | `content_type` | the content type of the embedded part | | `filename` | the basename of the part it landed in, e.g. `"oleObject1.bin"` | | `is_linked` | `True` when the object *links* to an external file instead of embedding it, in which case there are no bytes in the package | | `shows_icon` | `True` when Word shows an icon rather than a preview | | `image` | the icon or preview image Word displays | | `embedded_part` | the package part itself | Note OOXML does not record the original file name of an embedded object. `filename` is the name of the part it was stored in, which is what a caller extracting it has to work with. Writing one takes an icon, and the icon is required: ``` run = document.add_paragraph().add_run() run.add_embedded_object( "budget.xlsx", icon="excel-icon.png", prog_id="Excel.Sheet.12", ) ``` Word cannot render the embedded file itself, so without an image there is nothing to draw where the object sits. `prog_id` defaults to `"Package"`, the generic value Word uses for a file it has no better name for — an object whose `ProgID` names no installed application is one Word displays but cannot open, so pass the right one when you know it. `width` and `height` default to the icon's own size. The visual is VML, not DrawingML, because that is what Word writes for an OLE object. ## Macros A macro-enabled document keeps its VBA project as a single opaque blob, `word/vbaProject.bin`: ``` document = Document("macros.docm") document.has_macros # -> True len(document.vba_project) # -> the project bytes ``` Reading, transplanting and stripping one are all expressible: ``` # -- move a project into a generated document -- generated = Document() generated.vba_project = Document("macros.docm").vba_project generated.save("generated.docm") # -- strip the macros out of a document you received -- received = Document("received.docm") del received.vba_project # -- or: received.vba_project = None received.save("safe.docx") received.remove_vba_project() # -> how many were removed, 0 or 1 ``` Assigning or removing a project **switches the main part's content type with it**, which is the part that is easy to get wrong by hand: Word ignores macros in a document that does not claim to be macro-enabled, and warns about macros in a document that claims to be macro-enabled but is not. A template switches to the macro-enabled *template* type rather than the document one. Note The blob is not parsed. The project is an OLE compound file with compressed module streams inside it; reading the source of a macro is a separate matter and is not supported. ## Embedding another document An `altChunk` embeds a whole file — HTML, RTF, another `.docx` — and lets Word import it on open: ``` document = Document() document.add_alt_chunk(b"

Report

Generated.

", content_type="text/html") ``` `chunk` may be bytes, a path, or a file-like object open for binary read. Note that a `str` is read as a **path**, not as content — pass content as bytes, as above. `content_type` must be right, because Word picks its importer from it: | Content type | Format | | ------------------------------------------------------------------------- | ---------- | | `text/html` | HTML | | `text/plain` | plain text | | `application/rtf` | RTF | | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | `.docx` | ``` with open("appendix.docx", "rb") as f: document.add_alt_chunk( f, content_type=( "application/vnd.openxmlformats-officedocument" ".wordprocessingml.document" ), ) ``` Warning **Word performs the import when it opens the document, so the embedded content is not visible to this library.** Its paragraphs and tables do not appear in Document.paragraphs, Document.tables or iter_inner_content(), and nothing here can style it. It is a handover to Word, not a merge. Readers other than Word may ignore `altChunk` entirely. Document.alt_chunks lists what has been embedded. # Working with Text To work effectively with text, it's important to first understand a little about block-level elements like paragraphs and inline-level objects like runs. ## Block-level vs. inline text objects The paragraph is the primary block-level object in Word. A block-level item flows the text it contains between its left and right edges, adding an additional line each time the text extends beyond its right boundary. For a paragraph, the boundaries are generally the page margins, but they can also be column boundaries if the page is laid out in columns, or cell boundaries if the paragraph occurs inside a table cell. A table is also a block-level object. An inline object is a portion of the content that occurs inside a block-level item. An example would be a word that appears in bold or a sentence in all-caps. The most common inline object is a *run*. All content within a block container is inside of an inline object. Typically, a paragraph contains one or more runs, each of which contain some part of the paragraph's text. The attributes of a block-level item specify its placement on the page, such items as indentation and space before and after a paragraph. The attributes of an inline item generally specify the font in which the content appears, things like typeface, font size, bold, and italic. ## Paragraph properties A paragraph has a variety of properties that specify its placement within its container (typically a page) and the way it divides its content into separate lines. In general, it's best to define a *paragraph style* collecting these attributes into a meaningful group and apply the appropriate style to each paragraph, rather than repeatedly apply those properties directly to each paragraph. This is analogous to how Cascading Style Sheets (CSS) work with HTML. All the paragraph properties described here can be set using a style as well as applied directly to a paragraph. The formatting properties of a paragraph are accessed using the ParagraphFormat object available using the paragraph's paragraph_format property. ### Horizontal alignment (justification) Also known as *justification*, the horizontal alignment of a paragraph can be set to left, centered, right, or fully justified (aligned on both the left and right sides) using values from the enumeration `WdParagraphAlignment`: ``` >>> from docx.enum.text import WD_ALIGN_PARAGRAPH >>> document = Document() >>> paragraph = document.add_paragraph() >>> paragraph_format = paragraph.paragraph_format >>> paragraph_format.alignment None # indicating alignment is inherited from the style hierarchy >>> paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER >>> paragraph_format.alignment CENTER (1) ``` ### Indentation Indentation is the horizontal space between a paragraph and edge of its container, typically the page margin. A paragraph can be indented separately on the left and right side. The first line can also have a different indentation than the rest of the paragraph. A first line indented further than the rest of the paragraph has *first line indent*. A first line indented less has a *hanging indent*. Indentation is specified using a Length value, such as Inches, Pt, or Cm. Negative values are valid and cause the paragraph to overlap the margin by the specified amount. A value of `None` indicates the indentation value is inherited from the style hierarchy. Assigning `None` to an indentation property removes any directly-applied indentation setting and restores inheritance from the style hierarchy: ``` >>> from docx.shared import Inches >>> paragraph = document.add_paragraph() >>> paragraph_format = paragraph.paragraph_format >>> paragraph_format.left_indent None # indicating indentation is inherited from the style hierarchy >>> paragraph_format.left_indent = Inches(0.5) >>> paragraph_format.left_indent 457200 >>> paragraph_format.left_indent.inches 0.5 ``` Right-side indent works in a similar way: ``` >>> from docx.shared import Pt >>> paragraph_format.right_indent None >>> paragraph_format.right_indent = Pt(24) >>> paragraph_format.right_indent 304800 >>> paragraph_format.right_indent.pt 24.0 ``` First-line indent is specified using the first_line_indent property and is interpreted relative to the left indent. A negative value indicates a hanging indent: ``` >>> paragraph_format.first_line_indent None >>> paragraph_format.first_line_indent = Inches(-0.25) >>> paragraph_format.first_line_indent -228600 >>> paragraph_format.first_line_indent.inches -0.25 ``` ### Indentation in character units Word can express an indent as a number of characters rather than an absolute distance, which is what East Asian typesetting conventions expect and what Word's own dialogue offers when the document language calls for it. Those values are a separate set of properties, because they are a different unit and the two cannot both apply: ``` >>> paragraph_format.first_line_indent_chars = 200 # -> two characters >>> paragraph_format.left_indent_chars = 100 # -> one character >>> paragraph_format.right_indent_chars None ``` Values are in **hundredths of a character**, matching the XML — `200` is two characters. Setting a character-unit property clears its twips counterpart and vice versa, so the two can never disagree about the same edge. The absolute properties also read the `w:start` and `w:end` spellings Word writes in recent files, not only the older `w:left` and `w:right`, so a document produced by a current version of Word reports the indents it actually has. ### Tab stops A tab stop determines the rendering of a tab character in the text of a paragraph. In particular, it specifies the position where the text following the tab character will start, how it will be aligned to that position, and an optional leader character that will fill the horizontal space spanned by the tab. The tab stops for a paragraph or style are contained in a TabStops object accessed using the tab_stops property on ParagraphFormat: ``` >>> tab_stops = paragraph_format.tab_stops >>> tab_stops ``` A new tab stop is added using the add_tab_stop method: ``` >>> tab_stop = tab_stops.add_tab_stop(Inches(1.5)) >>> tab_stop.position 1371600 >>> tab_stop.position.inches 1.5 ``` Alignment defaults to left, but may be specified by providing a member of the WdTabAlignment enumeration. The leader character defaults to spaces, but may be specified by providing a member of the WdTabLeader enumeration: ``` >>> from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER >>> tab_stop = tab_stops.add_tab_stop(Inches(1.5), WD_TAB_ALIGNMENT.RIGHT, WD_TAB_LEADER.DOTS) >>> print(tab_stop.alignment) RIGHT (2) >>> print(tab_stop.leader) DOTS (1) ``` Existing tab stops are accessed using sequence semantics on TabStops: ``` >>> tab_stops[0] ``` More details are available in the TabStops and TabStop API documentation ### Paragraph spacing The space_before and space_after properties control the spacing between subsequent paragraphs, controlling the spacing before and after a paragraph, respectively. Inter-paragraph spacing is *collapsed* during page layout, meaning the spacing between two paragraphs is the maximum of the *space_after* for the first paragraph and the *space_before* of the second paragraph. Paragraph spacing is specified as a Length value, often using Pt: ``` >>> paragraph_format.space_before, paragraph_format.space_after (None, None) # inherited by default >>> paragraph_format.space_before = Pt(18) >>> paragraph_format.space_before.pt 18.0 >>> paragraph_format.space_after = Pt(12) >>> paragraph_format.space_after.pt 12.0 ``` ### Line spacing Line spacing is the distance between subsequent baselines in the lines of a paragraph. Line spacing can be specified either as an absolute distance or relative to the line height (essentially the point size of the font used). A typical absolute measure would be 18 points. A typical relative measure would be double-spaced (2.0 line heights). The default line spacing is single-spaced (1.0 line heights). Line spacing is controlled by the interaction of the line_spacing and line_spacing_rule properties. line_spacing is either a Length value, a (small-ish) `float`, or None. A Length value indicates an absolute distance. A `float` indicates a number of line heights. `None` indicates line spacing is inherited. line_spacing_rule is a member of the WdLineSpacing enumeration or `None`: ``` >>> from docx.shared import Length >>> paragraph_format.line_spacing None >>> paragraph_format.line_spacing_rule None >>> paragraph_format.line_spacing = Pt(18) >>> isinstance(paragraph_format.line_spacing, Length) True >>> paragraph_format.line_spacing.pt 18.0 >>> paragraph_format.line_spacing_rule EXACTLY (4) >>> paragraph_format.line_spacing = 1.75 >>> paragraph_format.line_spacing 1.75 >>> paragraph_format.line_spacing_rule MULTIPLE (5) ``` ### Spacing in line units As with indentation, Word can express paragraph spacing as a number of lines rather than an absolute distance: ``` >>> paragraph_format.space_before_lines = 100 # -> one line >>> paragraph_format.space_after_lines = 50 # -> half a line ``` Values are in **hundredths of a line**. These are `w:spacing/@w:beforeLines` and `@w:afterLines`, and Word applies them in preference to the absolute values when both are present — so as with the character units, setting one clears the other. ### Paragraph borders A paragraph's borders are spelled the same way as a table's or a cell's — a mapping keyed by edge name: ``` from docx.enum.table import WD_LINE_STYLE from docx.shared import Pt, RGBColor paragraph_format.borders["bottom"].line = WD_LINE_STYLE.SINGLE paragraph_format.borders["bottom"].size = Pt(1) paragraph_format.borders["bottom"].color = RGBColor(0x99, 0x99, 0x99) ``` A paragraph with only a bottom border is how Word draws a horizontal rule, which is the common reason to want this. The edges are `top`, `bottom`, `left`, `right`, plus two a table does not have: - `between` — drawn between *consecutive* paragraphs that carry the same border setting, not around each one. - `bar` — a vertical line at the outer edge, used to mark changed text. Each edge exposes `line`, `size`, `color` and `space`, and setting `line` to `WD_LINE_STYLE.NONE` removes the border. For borders around a whole page, see [Page borders](https://toxicphreak.github.io/python-docx-ng/user/sections/#page-borders). ### The paragraph mark A paragraph ends with a mark — the ¶ Word shows with formatting marks turned on — and that mark has run properties of its own, stored in `w:pPr/w:rPr`. They are what an *empty* paragraph is formatted with, because there is no run in it to carry formatting: ``` paragraph = document.add_paragraph() # -- empty paragraph.paragraph_format.mark_font.size = Pt(4) ``` That is the way to make a blank spacer paragraph small, and it is the only place the formatting of an empty paragraph lives. mark_font is a full Font, so everything in [Apply character formatting](#apply-character-formatting) applies to it. It also affects the mark of a *non*-empty paragraph, which is what decides how tall the last line is. ### Pagination properties Four paragraph properties, keep_together, keep_with_next, page_break_before, and widow_control control aspects of how the paragraph behaves near page boundaries. keep_together causes the entire paragraph to appear on the same page, issuing a page break before the paragraph if it would otherwise be broken across two pages. keep_with_next keeps a paragraph on the same page as the subsequent paragraph. This can be used, for example, to keep a section heading on the same page as the first paragraph of the section. page_break_before causes a paragraph to be placed at the top of a new page. This could be used on a chapter heading to ensure chapters start on a new page. widow_control breaks a page to avoid placing the first or last line of the paragraph on a separate page from the rest of the paragraph. All four of these properties are *tri-state*, meaning they can take the value `True`, `False`, or `None`. `None` indicates the property value is inherited from the style hierarchy. `True` means "on" and `False` means "off": ``` >>> paragraph_format.keep_together None # all four inherit by default >>> paragraph_format.keep_with_next = True >>> paragraph_format.keep_with_next True >>> paragraph_format.page_break_before = False >>> paragraph_format.page_break_before False ``` ## Apply character formatting Character formatting is applied at the Run level. Examples include font typeface and size, bold, italic, and underline. A Run object has a read-only font property providing access to a Font object. A run's Font object provides properties for getting and setting the character formatting for that run. Several examples are provided here. For a complete set of the available properties, see the Font API documentation. The font for a run can be accessed like this: ``` >>> from docx import Document >>> document = Document() >>> run = document.add_paragraph().add_run() >>> font = run.font ``` Typeface and size are set like this: ``` >>> from docx.shared import Pt >>> font.name = 'Calibri' >>> font.size = Pt(12) ``` Many font properties are *tri-state*, meaning they can take the values `True`, `False`, and `None`. `True` means the property is "on", `False` means it is "off". Conceptually, the `None` value means "inherit". A run exists in the style inheritance hierarchy and by default inherits its character formatting from that hierarchy. Any character formatting directly applied using the Font object overrides the inherited values. Bold and italic are tri-state properties, as are all-caps, strikethrough, superscript, and many others. See the Font API documentation for a full list: ``` >>> font.bold, font.italic (None, None) >>> font.italic = True >>> font.italic True >>> font.italic = False >>> font.italic False >>> font.italic = None >>> font.italic None ``` Underline is a bit of a special case. It is a hybrid of a tri-state property and an enumerated value property. `True` means single underline, by far the most common. `False` means no underline, but more often `None` is the right choice if no underlining is wanted. The other forms of underlining, such as double or dashed, are specified with a member of the WdUnderline enumeration: ``` >>> font.underline None >>> font.underline = True >>> # or perhaps >>> font.underline = WD_UNDERLINE.DOT_DASH ``` ### Font color Each Font object has a ColorFormat object that provides access to its color, accessed via its read-only color property. Apply a specific RGB color to a font: ``` >>> from docx.shared import RGBColor >>> font.color.rgb = RGBColor(0x42, 0x24, 0xE9) ``` A font can also be set to a theme color by assigning a member of the MsoThemeColorIndex enumeration: ``` >>> from docx.enum.dml import MSO_THEME_COLOR >>> font.color.theme_color = MSO_THEME_COLOR.ACCENT_1 ``` A font's color can be restored to its default (inherited) value by assigning `None` to either the rgb or theme_color attribute of ColorFormat: ``` >>> font.color.rgb = None ``` Determining the color of a font begins with determining its color type: ``` >>> font.color.type RGB (1) ``` The value of the type property can be a member of the MsoColorType enumeration or None. *MSO_COLOR_TYPE.RGB* indicates it is an RGB color. *MSO_COLOR_TYPE.THEME* indicates a theme color. *MSO_COLOR_TYPE.AUTO* indicates its value is determined automatically by the application, usually set to black. (This value is relatively rare.) `None` indicates no color is applied and the color is inherited from the style hierarchy; this is the most common case. When the color type is *MSO_COLOR_TYPE.RGB*, the rgb property will be an RGBColor value indicating the RGB color: ``` >>> font.color.rgb RGBColor(0x42, 0x24, 0xe9) ``` When the color type is *MSO_COLOR_TYPE.THEME*, the theme_color property will be a member of MsoThemeColorIndex indicating the theme color: ``` >>> font.color.theme_color ACCENT_1 (5) ``` ## East Asian and complex-script typefaces Word stores up to four typefaces for a run, one per script, and applies whichever matches the characters being rendered. Font.name is the Latin one; the others have their own properties: ``` font = paragraph.add_run("mixed script text").font font.name = "Calibri" # Latin font.east_asian_name = "MS Mincho" font.complex_script_name = "Arial" font.high_ansi_name = "Calibri" ``` Complex scripts also carry their own size, which is why a run can render at one size in Latin and another in Arabic or Hebrew: ``` from docx.shared import Pt font.size = Pt(11) font.cs_size = Pt(13) ``` ## Character scaling Horizontal scaling stretches or condenses the glyphs, as a whole percentage of normal width: ``` font.scaling = 150 # half again as wide font.scaling = 80 # condensed font.scaling = None # inherit from the style hierarchy ``` ## Shading Shading fills the background behind text. It exists on a run and on a paragraph, and takes an RGB hex string: ``` font.shading_fill = "FFFF00" paragraph.paragraph_format.shading_fill = "EEEEEE" ``` Word draws a *pattern* in a foreground colour over that fill. The usual case is no pattern at all, `WD_SHADING_PATTERN.CLEAR`, which is what the two assignments above produce and what leaves the fill as a plain background. The percentage patterns are how Word produces a tint of one colour over another: ``` from docx.enum.text import WD_SHADING_PATTERN font.shading_fill = "FFFFFF" # -- background -- font.shading_color = "FF0000" # -- pattern foreground -- font.shading_pattern = WD_SHADING_PATTERN.PCT_25 # -- 25% red over white -- ``` Setting `shading_pattern` to `None` removes the shading entirely, as does setting `shading_fill` to `None`. Note A shading pattern is valid with no fill — `` is what Word writes for several of its Shading presets. Reading `shading_fill` on such a run returns `None` rather than raising. Note In 0.9.x, Font.highlight_color fell back to reading `w:shd`. It no longer does: it is strictly a WD_COLOR_INDEX member — Word's highlighter pen, which has a fixed palette — and `shading_fill` is the arbitrary-colour fill. ## Theme fonts A run's typeface can be set to a *theme token* rather than a font name — `minorHAnsi` for body text, `majorHAnsi` for headings — in which case the concrete font comes from the document's theme. Font.name reports `None` for such a run, because there is no font name in the run to report: ``` run.font.theme = "minorHAnsi" run.font.name # -> None run.font.theme # -> "minorHAnsi" run.font.theme_typeface # -> "Cambria" ``` Font.theme_typeface resolves the token through the theme part, and for a document whose fonts come only from its theme this is the only way to find out what the text is actually rendered in. It is `None` when the run carries no token, when the theme leaves that slot empty, or when the `Font` was built over a bare element with no part behind it. The theme itself is Document.theme: ``` theme = document.theme theme.name # -> "Office Theme" theme.minor_font.latin # -> "Cambria" theme.major_font.latin # -> "Calibri" theme.minor_font.east_asian # -> None, the default theme leaves it empty theme.minor_font.complex_script # -> None theme.typeface("minorHAnsi") # -> "Cambria" ``` The twelve theme colours are there too, keyed by the slot names as they appear in the XML — `dk1`, `lt1`, `dk2`, `lt2`, `accent1` through `accent6`, `hlink` and `folHlink`: ``` theme.color("accent1") # -> RGBColor(0x4F, 0x81, 0xBD) theme.colors # -> the twelve, as a dict, in schema order ``` A slot name that does not exist raises `ValueError`; a slot the theme omits gives `None`. A system colour such as `dk1` reports the value the producing application last resolved it to, which is the only concrete value available off that operating system. Note Document.theme is `None` for a document with no theme part, and — unlike the styles and settings parts — one is never created on demand. A theme is a design the document was authored against; an empty one synthesised on the spot would answer the typeface question with a fiction. ## Right-to-left and vertical text Two separate things, often confused: **Base direction** is whether a paragraph reads right-to-left. It decides where the first character goes, which way punctuation faces, and which edge `start` and `end` mean: ``` paragraph.paragraph_format.bidi = True ``` **Flow direction** is which way the lines themselves run, and it rotates the text: ``` from docx.enum.text import WD_TEXT_DIRECTION paragraph.paragraph_format.text_direction = WD_TEXT_DIRECTION.TB_RL ``` | WD_TEXT_DIRECTION | Flow | | ----------------- | --------------------------------------------------------------------- | | `LR_TB` | left to right, then top to bottom — the default | | `TB_RL` | top to bottom, then right to left — rotates the text 90° clockwise | | `BT_LR` | bottom to top, then left to right — rotates it 90° anticlockwise | | `LR_TB_V` | left to right, then top to bottom, rotating each East Asian character | | `TB_RL_V` | top to bottom, then right to left, with each character upright | | `TB_LR_V` | top to bottom, then left to right, Mongolian vertical layout | Both exist at three levels, and the more specific wins: ``` document.sections[0].bidi = True # -- the section default document.sections[0].text_direction = WD_TEXT_DIRECTION.TB_RL paragraph.paragraph_format.text_direction = ... # -- one paragraph table.cell(0, 0).text_direction = ... # -- one cell ``` All of them are `None` when the value is inherited. ## Equations An equation is OMML — `m:oMath` — a markup language of its own that shares nothing with WordprocessingML but the file it lives in. Reading is supported; there is no builder. ``` for equation in document.math: print(equation.text, equation.is_display) ``` Document.math, BlockItemContainer.math and Paragraph.math each return the Math objects in document order: - `.text` — the characters of the equation, in reading order, with the structure flattened away. A fraction reads as its numerator then its denominator; there is no LaTeX here. - `.xml` — the OMML itself, which is what to use if you need the structure. - `.is_display` — `True` for a display equation in a `m:oMathPara` of its own, `False` for one inline in a sentence. Note **Equation text is deliberately not part of Paragraph.text.** Including it would describe the document more truthfully. But replace_text() and the run-isolating machinery under it measure offsets against `Paragraph.text` and can only cut at run boundaries. Text they cannot reach would silently mis-target every replacement after the first equation in a paragraph, and a wrong edit is worse than a missing character. Read `paragraph.math` when you want the equations, and `paragraph.text` when you want what is safely editable. # Watermarks A watermark is the faint "DRAFT" or "CONFIDENTIAL" behind the content, or a logo sitting under it. Word implements one as a shape anchored in the *header*, which is what makes it appear on every page. ## Adding a text watermark ``` from docx import Document document = Document() document.add_text_watermark("DRAFT") ``` That is the whole of the common case. The defaults reproduce Word's own "Semitransparent" watermark: light grey Calibri, rotated 315°, sized to the page. Every section is covered, and within each section the default, first-page and even-page headers alike — so the watermark does not vanish on a page that uses a different header. A header shared between sections is written to once. Appearance is controlled by keyword: ``` document.add_text_watermark( "CONFIDENTIAL", font="Arial", color="FF0000", angle=0, opacity=0.4, bold=True, ) ``` - `color` is an RGB hex string, without a leading `#` - `angle` is degrees; `315` is Word's diagonal, `0` is horizontal - `opacity` sets true VML transparency, which is subtler than picking a pale colour - `font_size`, `width` and `height` take a Length; leaving `font_size` unset scales the text to the box ## An image watermark ``` document.add_image_watermark("logo.png", scale=0.75) ``` `washout` is on by default and applies Word's brightness-and-contrast correction — that is what makes a logo read as a background rather than sitting opaquely over the text. Turn it off for an image that is already faint: ``` document.add_image_watermark("logo.png", washout=False) ``` ## One section only The same three methods exist on Section, which is how a watermark is applied to part of a document rather than all of it: ``` document.sections[0].add_text_watermark("DRAFT") ``` ## Reading and removing Document.watermarks returns what is there: ``` for watermark in document.watermarks: print(watermark.text, watermark.is_image) ``` Watermark.text is `None` for an image watermark. Remove them all, or one at a time: ``` document.remove_watermark() # -> how many were removed for watermark in list(document.watermarks): watermark.remove() ``` remove_watermark() exists on Section too, for removing a single section's watermark. # API reference - [docx](https://toxicphreak.github.io/python-docx-ng/api/docx/index.md) - [altchunk](https://toxicphreak.github.io/python-docx-ng/api/docx/altchunk/index.md) - [api](https://toxicphreak.github.io/python-docx-ng/api/docx/api/index.md) - [blkcntnr](https://toxicphreak.github.io/python-docx-ng/api/docx/blkcntnr/index.md) - [bookmark](https://toxicphreak.github.io/python-docx-ng/api/docx/bookmark/index.md) - [borders](https://toxicphreak.github.io/python-docx-ng/api/docx/borders/index.md) - [caption](https://toxicphreak.github.io/python-docx-ng/api/docx/caption/index.md) - [cleanup](https://toxicphreak.github.io/python-docx-ng/api/docx/cleanup/index.md) - [cli](https://toxicphreak.github.io/python-docx-ng/api/docx/cli/index.md) - [comments](https://toxicphreak.github.io/python-docx-ng/api/docx/comments/index.md) - [copy](https://toxicphreak.github.io/python-docx-ng/api/docx/copy/index.md) - [dml](https://toxicphreak.github.io/python-docx-ng/api/docx/dml/index.md) - [color](https://toxicphreak.github.io/python-docx-ng/api/docx/dml/color/index.md) - [document](https://toxicphreak.github.io/python-docx-ng/api/docx/document/index.md) - [drawing](https://toxicphreak.github.io/python-docx-ng/api/docx/drawing/index.md) - [enum](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/index.md) - [base](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/base/index.md) - [dml](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/dml/index.md) - [numbering](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/numbering/index.md) - [revision](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/revision/index.md) - [section](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/section/index.md) - [shape](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/shape/index.md) - [style](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/style/index.md) - [table](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/table/index.md) - [text](https://toxicphreak.github.io/python-docx-ng/api/docx/enum/text/index.md) - [exceptions](https://toxicphreak.github.io/python-docx-ng/api/docx/exceptions/index.md) - [fields](https://toxicphreak.github.io/python-docx-ng/api/docx/fields/index.md) - [footnotes](https://toxicphreak.github.io/python-docx-ng/api/docx/footnotes/index.md) - [formfield](https://toxicphreak.github.io/python-docx-ng/api/docx/formfield/index.md) - [image](https://toxicphreak.github.io/python-docx-ng/api/docx/image/index.md) - [bmp](https://toxicphreak.github.io/python-docx-ng/api/docx/image/bmp/index.md) - [constants](https://toxicphreak.github.io/python-docx-ng/api/docx/image/constants/index.md) - [emf](https://toxicphreak.github.io/python-docx-ng/api/docx/image/emf/index.md) - [exceptions](https://toxicphreak.github.io/python-docx-ng/api/docx/image/exceptions/index.md) - [gif](https://toxicphreak.github.io/python-docx-ng/api/docx/image/gif/index.md) - [helpers](https://toxicphreak.github.io/python-docx-ng/api/docx/image/helpers/index.md) - [image](https://toxicphreak.github.io/python-docx-ng/api/docx/image/image/index.md) - [jpeg](https://toxicphreak.github.io/python-docx-ng/api/docx/image/jpeg/index.md) - [png](https://toxicphreak.github.io/python-docx-ng/api/docx/image/png/index.md) - [svg](https://toxicphreak.github.io/python-docx-ng/api/docx/image/svg/index.md) - [tiff](https://toxicphreak.github.io/python-docx-ng/api/docx/image/tiff/index.md) - [webp](https://toxicphreak.github.io/python-docx-ng/api/docx/image/webp/index.md) - [wmf](https://toxicphreak.github.io/python-docx-ng/api/docx/image/wmf/index.md) - [math](https://toxicphreak.github.io/python-docx-ng/api/docx/math/index.md) - [numbering](https://toxicphreak.github.io/python-docx-ng/api/docx/numbering/index.md) - [object](https://toxicphreak.github.io/python-docx-ng/api/docx/object/index.md) - [opc](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/index.md) - [constants](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/constants/index.md) - [coreprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/coreprops/index.md) - [customprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/customprops/index.md) - [exceptions](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/exceptions/index.md) - [extendedprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/extendedprops/index.md) - [oxml](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/oxml/index.md) - [package](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/package/index.md) - [packuri](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/packuri/index.md) - [part](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/part/index.md) - [parts](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/parts/index.md) - [coreprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/parts/coreprops/index.md) - [custom_xml](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/parts/custom_xml/index.md) - [customprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/parts/customprops/index.md) - [extendedprops](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/parts/extendedprops/index.md) - [phys_pkg](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/phys_pkg/index.md) - [pkgreader](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/pkgreader/index.md) - [pkgwriter](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/pkgwriter/index.md) - [rel](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/rel/index.md) - [shared](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/shared/index.md) - [spec](https://toxicphreak.github.io/python-docx-ng/api/docx/opc/spec/index.md) - [oxml](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/index.md) - [bookmark](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/bookmark/index.md) - [comments](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/comments/index.md) - [coreprops](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/coreprops/index.md) - [customprops](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/customprops/index.md) - [customxml](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/customxml/index.md) - [deletion](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/deletion/index.md) - [document](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/document/index.md) - [drawing](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/drawing/index.md) - [exceptions](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/exceptions/index.md) - [extendedprops](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/extendedprops/index.md) - [footnotes](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/footnotes/index.md) - [math](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/math/index.md) - [ns](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/ns/index.md) - [numbering](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/numbering/index.md) - [object](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/object/index.md) - [parser](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/parser/index.md) - [revision](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/revision/index.md) - [sdt](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/sdt/index.md) - [section](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/section/index.md) - [settings](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/settings/index.md) - [shape](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/shape/index.md) - [shared](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/shared/index.md) - [simpletypes](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/simpletypes/index.md) - [styles](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/styles/index.md) - [table](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/table/index.md) - [text](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/index.md) - [font](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/font/index.md) - [form](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/form/index.md) - [hyperlink](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/hyperlink/index.md) - [isolate](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/isolate/index.md) - [pagebreak](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/pagebreak/index.md) - [paragraph](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/paragraph/index.md) - [parfmt](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/parfmt/index.md) - [run](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/text/run/index.md) - [theme](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/theme/index.md) - [xmlchemy](https://toxicphreak.github.io/python-docx-ng/api/docx/oxml/xmlchemy/index.md) - [package](https://toxicphreak.github.io/python-docx-ng/api/docx/package/index.md) - [parts](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/index.md) - [altchunk](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/altchunk/index.md) - [comments](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/comments/index.md) - [document](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/document/index.md) - [endnotes](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/endnotes/index.md) - [footnotes](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/footnotes/index.md) - [hdrftr](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/hdrftr/index.md) - [image](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/image/index.md) - [numbering](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/numbering/index.md) - [settings](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/settings/index.md) - [story](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/story/index.md) - [styles](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/styles/index.md) - [theme](https://toxicphreak.github.io/python-docx-ng/api/docx/parts/theme/index.md) - [revisions](https://toxicphreak.github.io/python-docx-ng/api/docx/revisions/index.md) - [sdt](https://toxicphreak.github.io/python-docx-ng/api/docx/sdt/index.md) - [section](https://toxicphreak.github.io/python-docx-ng/api/docx/section/index.md) - [settings](https://toxicphreak.github.io/python-docx-ng/api/docx/settings/index.md) - [shape](https://toxicphreak.github.io/python-docx-ng/api/docx/shape/index.md) - [shared](https://toxicphreak.github.io/python-docx-ng/api/docx/shared/index.md) - [styles](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/index.md) - [copy](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/copy/index.md) - [latent](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/latent/index.md) - [style](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/style/index.md) - [styles](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/styles/index.md) - [transfer](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/transfer/index.md) - [usage](https://toxicphreak.github.io/python-docx-ng/api/docx/styles/usage/index.md) - [table](https://toxicphreak.github.io/python-docx-ng/api/docx/table/index.md) - [text](https://toxicphreak.github.io/python-docx-ng/api/docx/text/index.md) - [font](https://toxicphreak.github.io/python-docx-ng/api/docx/text/font/index.md) - [hyperlink](https://toxicphreak.github.io/python-docx-ng/api/docx/text/hyperlink/index.md) - [pagebreak](https://toxicphreak.github.io/python-docx-ng/api/docx/text/pagebreak/index.md) - [paragraph](https://toxicphreak.github.io/python-docx-ng/api/docx/text/paragraph/index.md) - [parfmt](https://toxicphreak.github.io/python-docx-ng/api/docx/text/parfmt/index.md) - [run](https://toxicphreak.github.io/python-docx-ng/api/docx/text/run/index.md) - [search](https://toxicphreak.github.io/python-docx-ng/api/docx/text/search/index.md) - [tabstops](https://toxicphreak.github.io/python-docx-ng/api/docx/text/tabstops/index.md) - [theme](https://toxicphreak.github.io/python-docx-ng/api/docx/theme/index.md) - [types](https://toxicphreak.github.io/python-docx-ng/api/docx/types/index.md) - [watermark](https://toxicphreak.github.io/python-docx-ng/api/docx/watermark/index.md) ## docx Initialize `docx` package. Export the `Document` constructor function and establish the mapping of part-type to the part-classe that implements that type. ### Document ``` Document( docx: str | PathLike[str] | IO[bytes] | None = None, ) -> Document ``` Return a Document object loaded from `docx`, where `docx` can be either a path to a `.docx` file (a string or `os.PathLike`) or a file-like object. Macro-enabled `.docm` files and Word templates — `.dotx` and `.dotm` — are also accepted. Their macro storage is preserved when the document is saved, but this library provides no API to read or modify it. A template opened this way is still a template when saved; pass `as_template=False` to Document.save to write it out as an ordinary document instead. If `docx` is missing or `None`, the built-in default document "template" is loaded. Source code in `src/docx/api.py` ``` def Document(docx: str | os.PathLike[str] | IO[bytes] | None = None) -> DocumentObject: """Return a |Document| object loaded from `docx`, where `docx` can be either a path to a ``.docx`` file (a string or ``os.PathLike``) or a file-like object. Macro-enabled ``.docm`` files and Word templates — ``.dotx`` and ``.dotm`` — are also accepted. Their macro storage is preserved when the document is saved, but this library provides no API to read or modify it. A template opened this way is still a template when saved; pass ``as_template=False`` to :meth:`.Document.save` to write it out as an ordinary document instead. If `docx` is missing or ``None``, the built-in default document "template" is loaded. """ docx = _default_docx_path() if docx is None else docx if isinstance(docx, os.PathLike): docx = os.fspath(docx) document_part = cast("DocumentPart", Package.open(docx).main_document_part) if document_part.content_type not in _WORD_MAIN_CONTENT_TYPES: tmpl = "file '%s' is not a Word file, content type is '%s'" raise ValueError(tmpl % (docx, document_part.content_type)) return document_part.document ``` ## altchunk The AltChunk object, an embedded document Word imports when it opens the file. ### AltChunk ``` AltChunk(altChunk: CT_AltChunk, parent: ProvidesStoryPart) ``` Bases: `StoryChild` Proxy for a `w:altChunk` element, an "alternative format import". An alt-chunk holds a document in a format other than WordprocessingML — HTML, RTF, plain text, MHTML, or another .docx. Word converts it and splices the result into the document at this position when it opens the file. The conversion is Word's, and it happens on open. This library stores and returns the embedded bytes unchanged and does not read into them, so the paragraphs and tables the alt-chunk will eventually contribute do not appear in `Document.paragraphs`, `Document.tables` or `Document.iter_inner_content()`. Source code in `src/docx/altchunk.py` ``` def __init__(self, altChunk: CT_AltChunk, parent: t.ProvidesStoryPart): super().__init__(parent) self._element = self._altChunk = altChunk ``` #### blob ``` blob: bytes ``` The bytes of the embedded document, exactly as they were provided. #### content_type ``` content_type: str ``` The content type of the embedded document, e.g. `"text/html"`. ## api Directly exposed API functions and classes, Document for now. Provides a syntactically more convenient API for interacting with the OpcPackage graph. ### Document ``` Document( docx: str | PathLike[str] | IO[bytes] | None = None, ) -> Document ``` Return a Document object loaded from `docx`, where `docx` can be either a path to a `.docx` file (a string or `os.PathLike`) or a file-like object. Macro-enabled `.docm` files and Word templates — `.dotx` and `.dotm` — are also accepted. Their macro storage is preserved when the document is saved, but this library provides no API to read or modify it. A template opened this way is still a template when saved; pass `as_template=False` to Document.save to write it out as an ordinary document instead. If `docx` is missing or `None`, the built-in default document "template" is loaded. Source code in `src/docx/api.py` ``` def Document(docx: str | os.PathLike[str] | IO[bytes] | None = None) -> DocumentObject: """Return a |Document| object loaded from `docx`, where `docx` can be either a path to a ``.docx`` file (a string or ``os.PathLike``) or a file-like object. Macro-enabled ``.docm`` files and Word templates — ``.dotx`` and ``.dotm`` — are also accepted. Their macro storage is preserved when the document is saved, but this library provides no API to read or modify it. A template opened this way is still a template when saved; pass ``as_template=False`` to :meth:`.Document.save` to write it out as an ordinary document instead. If `docx` is missing or ``None``, the built-in default document "template" is loaded. """ docx = _default_docx_path() if docx is None else docx if isinstance(docx, os.PathLike): docx = os.fspath(docx) document_part = cast("DocumentPart", Package.open(docx).main_document_part) if document_part.content_type not in _WORD_MAIN_CONTENT_TYPES: tmpl = "file '%s' is not a Word file, content type is '%s'" raise ValueError(tmpl % (docx, document_part.content_type)) return document_part.document ``` ## blkcntnr Block item container, used by body, cell, header, etc. Block level items are things like paragraph and table, although there are a few other specialized ones like structured document tags. ### BlockItemContainer ``` BlockItemContainer( element: BlockItemElement, parent: ProvidesStoryPart ) ``` Bases: `StoryChild` Base class for proxy objects that can contain block items. These containers include \_Body, \_Cell, header, footer, footnote, endnote, comment, and text box objects. Provides the shared functionality to add a block item like a paragraph or table. Source code in `src/docx/blkcntnr.py` ``` def __init__(self, element: BlockItemElement, parent: t.ProvidesStoryPart): super(BlockItemContainer, self).__init__(parent) self._element = element ``` #### math ``` math: List[Math] ``` The equations in this container, in document order. Includes equations inside tables in this container. See Paragraph.math for why equation text is not part of Paragraph.text. #### content_controls ``` content_controls: list[ContentControl] ``` The structured document tags (content controls) in this container. Nested controls are included, in document order, outermost first. The content of a control appears in `.paragraphs` and `.iter_inner_content()` as though the wrapper were not there; this is how the wrapper itself is reached. #### paragraphs ``` paragraphs ``` A list containing the paragraphs in this container, in document order. Includes paragraphs wrapped in a `w:sdt` (content control). Read-only. #### tables ``` tables ``` A list containing the tables in this container, in document order. Includes tables wrapped in a `w:sdt` (content control). Read-only. #### add_paragraph ``` add_paragraph( text: str = "", style: str | ParagraphStyle | None = None, ) -> Paragraph ``` Return paragraph newly added to the end of the content in this container. The paragraph has `text` in a single run if present, and is given paragraph style `style`. If `style` is `None`, no paragraph style is applied, which has the same effect as applying the 'Normal' style. Source code in `src/docx/blkcntnr.py` ``` def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph: """Return paragraph newly added to the end of the content in this container. The paragraph has `text` in a single run if present, and is given paragraph style `style`. If `style` is |None|, no paragraph style is applied, which has the same effect as applying the 'Normal' style. """ paragraph = self._add_paragraph() if text: paragraph.add_run(text) if style is not None: paragraph.style = style return paragraph ``` #### add_table ``` add_table( rows: int, cols: int, width: Length, *, title: str | None = None, description: str | None = None, ) -> Table ``` Return table of `width` having `rows` rows and `cols` columns. The table is appended appended at the end of the content in this container. `width` is evenly distributed between the table columns. `description` is the table's alternative text, which is what a screen reader announces and what an accessibility check looks for. `title` is the separate, caption-like field Word writes alongside it. Both are omitted from the XML when `None`, and are equivalent to assigning `Table.title` and `Table.description` after the fact. Source code in `src/docx/blkcntnr.py` ``` def add_table( self, rows: int, cols: int, width: Length, *, title: str | None = None, description: str | None = None, ) -> Table: """Return table of `width` having `rows` rows and `cols` columns. The table is appended appended at the end of the content in this container. `width` is evenly distributed between the table columns. `description` is the table's alternative text, which is what a screen reader announces and what an accessibility check looks for. `title` is the separate, caption-like field Word writes alongside it. Both are omitted from the XML when |None|, and are equivalent to assigning `Table.title` and `Table.description` after the fact. """ from docx.table import Table tbl = CT_Tbl.new_tbl(rows, cols, width) self._element._insert_tbl(tbl) # pyright: ignore[reportPrivateUsage] table = Table(tbl, self) if title is not None: table.title = title if description is not None: table.description = description return table ``` #### iter_inner_content ``` iter_inner_content() -> Iterator[Paragraph | Table] ``` Generate each `Paragraph` or `Table` in this container in document order. Source code in `src/docx/blkcntnr.py` ``` def iter_inner_content(self) -> Iterator[Paragraph | Table]: """Generate each `Paragraph` or `Table` in this container in document order.""" from docx.table import Table for element in self._element.inner_content_elements: yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self)) ``` #### iter_paragraphs ``` iter_paragraphs(tables: bool = True) -> Iterator[Paragraph] ``` Generate every Paragraph in this container, in document order. Unlike paragraphs, this descends into tables when `tables` is `True`, including tables nested inside other tables, so it reaches every paragraph in the container rather than only the top-level ones. Source code in `src/docx/blkcntnr.py` ``` def iter_paragraphs(self, tables: bool = True) -> Iterator[Paragraph]: """Generate every |Paragraph| in this container, in document order. Unlike :attr:`paragraphs`, this descends into tables when `tables` is |True|, including tables nested inside other tables, so it reaches every paragraph in the container rather than only the top-level ones. """ for item in self.iter_inner_content(): if isinstance(item, Paragraph): yield item elif tables: for row in item.rows: for cell in row.cells: yield from cell.iter_paragraphs(tables=True) ``` #### replace_text ``` replace_text( old: str, new: str, *, count: int = -1, regex: bool = False, flags: int = 0, tables: bool = True, ) -> int ``` Replace occurrences of `old` with `new` in this container; return how many. Each paragraph is replaced in as described by Paragraph.replace_text, which is where the details of matching and formatting are documented. Tables are included unless `tables` is `False`; `count` of -1 replaces every match and any other value is a limit on the total across the whole container. Source code in `src/docx/blkcntnr.py` ``` def replace_text( self, old: str, new: str, *, count: int = -1, regex: bool = False, flags: int = 0, tables: bool = True, ) -> int: """Replace occurrences of `old` with `new` in this container; return how many. Each paragraph is replaced in as described by :meth:`.Paragraph.replace_text`, which is where the details of matching and formatting are documented. Tables are included unless `tables` is |False|; `count` of -1 replaces every match and any other value is a limit on the total across the whole container. """ from docx.text.search import compile_pattern, replace_in_paragraph if count == 0: return 0 pattern = compile_pattern(old, regex, flags) replaced = 0 for paragraph in self.iter_paragraphs(tables=tables): remaining = -1 if count < 0 else count - replaced replaced += replace_in_paragraph( paragraph._p, # pyright: ignore[reportPrivateUsage] pattern, new, remaining, regex, ) if count >= 0 and replaced >= count: break return replaced ``` ## bookmark The Bookmark and Bookmarks proxy objects. A bookmark names a range of a document. It is the anchor mechanism everything that refers to a place in a document is built on: internal hyperlinks, cross-references, captions that renumber, and table-of-contents entries. ### Bookmark ``` Bookmark( bookmarkStart: CT_BookmarkStart, parent: ProvidesStoryPart, ) ``` Bases: `Parented` Proxy for a `w:bookmarkStart` element and the range it names. Source code in `src/docx/bookmark.py` ``` def __init__(self, bookmarkStart: CT_BookmarkStart, parent: t.ProvidesStoryPart): super(Bookmark, self).__init__(parent) self._element = self._bookmarkStart = bookmarkStart ``` #### id ``` id: int ``` The `w:id` pairing this bookmark's two delimiters. Unique across the document. #### is_closed ``` is_closed: bool ``` `True` when this bookmark has a matching `w:bookmarkEnd`. An unmatched start is invalid but appears in real documents, so it is reported rather than raised on. The `.text` of an unclosed bookmark is the empty string. #### is_hidden ``` is_hidden: bool ``` `True` for a bookmark Word maintains for itself. `_GoBack` records the last edit position and `_Toc…` anchors a table-of-contents entry. Bookmarks leaves these out by default. #### name ``` name: str ``` The name of this bookmark, as shown in Word's bookmark dialog. #### text ``` text: str ``` The text of the content this bookmark spans. Paragraph boundaries inside the range become newlines, as they do for a table cell. The empty string when the bookmark is unclosed or spans no text. #### delete ``` delete() -> None ``` Remove this bookmark, leaving the content it named in place. Removes both delimiters, including an unmatched one. Source code in `src/docx/bookmark.py` ``` def delete(self) -> None: """Remove this bookmark, leaving the content it named in place. Removes both delimiters, including an unmatched one. """ bookmarkEnd = self._bookmarkStart.bookmarkEnd if bookmarkEnd is not None: bookmarkEnd.getparent().remove(bookmarkEnd) self._bookmarkStart.getparent().remove(self._bookmarkStart) ``` ### Bookmarks ``` Bookmarks( element: ProvidesXmlPart, parent: ProvidesStoryPart ) ``` Bases: `Parented`, `Sequence[Bookmark]` The bookmarks in a document, in document order. Supports `len()`, iteration, indexed access and lookup by name: ``` document.bookmarks["Introduction"].text ``` Bookmarks Word maintains for itself, such as `_GoBack` and the `_Toc…` anchors, are left out; pass `include_hidden=True` to iter_all to see them. Source code in `src/docx/bookmark.py` ``` def __init__(self, element: t.ProvidesXmlPart, parent: t.ProvidesStoryPart): super(Bookmarks, self).__init__(parent) self._element = element ``` #### get ``` get( name: str, default: Bookmark | None = None ) -> Bookmark | None ``` The bookmark named `name`, or `default` when there is none. Source code in `src/docx/bookmark.py` ``` def get(self, name: str, default: Bookmark | None = None) -> Bookmark | None: """The bookmark named `name`, or `default` when there is none.""" try: return self[name] except KeyError: return default ``` #### iter_all ``` iter_all(include_hidden: bool = True) -> Iterator[Bookmark] ``` Generate every bookmark in the document, in document order. Includes Word's own bookmarks unless `include_hidden` is `False`, and includes a bookmark whose `w:bookmarkEnd` is missing. Source code in `src/docx/bookmark.py` ``` def iter_all(self, include_hidden: bool = True) -> Iterator[Bookmark]: """Generate every bookmark in the document, in document order. Includes Word's own bookmarks unless `include_hidden` is |False|, and includes a bookmark whose `w:bookmarkEnd` is missing. """ for bookmarkStart in self._element.xpath("//w:bookmarkStart"): bookmark = Bookmark(bookmarkStart, self._parent) if include_hidden or not bookmark.is_hidden: yield bookmark ``` ## borders The border-edge proxy objects shared by tables, cells, paragraphs and pages. Four containers in WordprocessingML carry a set of border edges — `w:tblBorders`, `w:tcBorders`, `w:pBdr` and `w:pgBorders` — and they differ only in which edges they admit and where the element lives. The mapping proxy and the per-edge proxy are defined here once so all four spell the same; each container supplies its own small subclass naming its edges and saying how to reach its element. ### \_Border ``` _Border(borders: _Borders, edge: str) ``` One border edge of a table, cell, paragraph or page, e.g. `table.borders["top"]`. A border edge that is not set has `None` for every property, meaning the effective appearance of that edge is inherited from the style hierarchy. Assigning to any property other than line on an edge that is not set creates it with a line style of `WD_LINE_STYLE.SINGLE`, because a border with no line style is not valid XML. Assigning `None` to line removes the edge entirely. Source code in `src/docx/borders.py` ``` def __init__(self, borders: _Borders, edge: str): self._borders = borders self._edge = edge ``` #### color ``` color: RGBColor | None ``` RGBColor of this border edge, or `None` when it has no explicit color. As for ColorFormat, a border whose color is the automatic color reads as `None`; Word chooses that color at render time, so there is no RGB value to report. #### line ``` line: WD_LINE_STYLE | None ``` Member of WdLineStyle, or `None` when this edge is not set. #### size ``` size: Length | None ``` Width of this border line, or `None` when it has no explicit width. The underlying `w:sz` attribute counts eighths of a point, so an assigned value is rounded to the nearest eighth of a point. #### space ``` space: Length | None ``` Offset of this border from the content it surrounds, or `None` when not set. The underlying `w:space` attribute counts whole points, so an assigned value is rounded to the nearest point. ### \_Borders ``` _Borders(edges: tuple[str, ...]) ``` Bases: `Mapping[str, _Border]` The border edges of a table, cell, paragraph or page, keyed by edge name. A read-only mapping in the sense that the set of keys is fixed; the \_Border object each key maps to is what you assign through: ``` table.borders["top"].line = WD_LINE_STYLE.SINGLE ``` Every edge admitted by the schema is always a key, whether or not it is set, so iterating yields edges with a \_Border.line of `None` as well. Edge names are the local names used in the XML. A table admits `top`, `start`, `left`, `bottom`, `end`, `right`, `insideH` and `insideV`; a cell adds `tl2br` and `tr2bl`; a paragraph has `top`, `left`, `bottom`, `right`, `between` and `bar`; a page has the plain four. Word writes `left` and `right` for a left-to-right table and `start` and `end` for a right-to-left one. Source code in `src/docx/borders.py` ``` def __init__(self, edges: tuple[str, ...]): self._edges = edges ``` #### clear ``` clear() -> None ``` Remove every border edge, restoring inheritance from the style hierarchy. Source code in `src/docx/borders.py` ``` @abstractmethod def clear(self) -> None: """Remove every border edge, restoring inheritance from the style hierarchy.""" ``` ## caption Captions — a label, a self-renumbering sequence field, and a cross-reference target. Everything a caption is built from already existed: `Paragraph.add_field()`, the `SEQ` builder in `docx.fields`, `REF` for the reference, and bookmarks. What was missing was the one call that puts them together, because assembling one by hand means: 1. insert a paragraph in the "Caption" style 1. add the literal label text and separator 1. add a `SEQ Figure \* ARABIC` field 1. wrap the whole thing in a bookmark with a `_Ref`-prefixed name and an unused id 1. remember that name so a later `REF` field can point at it Step 4 is the one people get wrong. Word's own cross-reference dialogue offers only targets whose bookmark name follows the `_Ref` convention, so a caption bookmarked with an arbitrary name is invisible in it — the caption works, and the user cannot reference it from the UI. ### Caption ``` Caption( paragraph: Paragraph, bookmark_name: str, label: str ) ``` A caption paragraph, carrying the bookmark name a cross-reference points at. Returned by Document.add_caption. It is a thin wrapper over the paragraph; paragraph is the Paragraph itself for any further formatting. Source code in `src/docx/caption.py` ``` def __init__(self, paragraph: Paragraph, bookmark_name: str, label: str): self._paragraph = paragraph self._bookmark_name = bookmark_name self._label = label ``` #### paragraph ``` paragraph: Paragraph ``` The Paragraph this caption is. #### bookmark_name ``` bookmark_name: str ``` The bookmark name a cross-reference to this caption uses: ``` document.add_paragraph().add_field( fields.cross_reference(caption.bookmark_name) ) ``` Generated in Word's own `_Ref` shape, so Word's cross-reference dialogue offers this caption as a target. That shape also means the bookmark does not appear in Document.bookmarks, which leaves out the ones Word maintains for itself; Bookmarks.iter_all reaches it. #### label ``` label: str ``` The caption's series, e.g. `"Figure"`. Word numbers each independently. #### text ``` text: str ``` The caption's text as the document currently reads it. The number is a `SEQ` field, so it shows only once Word has computed it; before that this reads as the label and the caption text with the number missing. #### number ``` number: str | None ``` The number Word last computed for this caption, or `None`. A `SEQ` field's result is cached in the document, so this reads back after a round trip through Word. It is `None` for a caption this library has just written, which has no cached result yet. ### next_ref_bookmark_name ``` next_ref_bookmark_name(document_element: object) -> str ``` A `_Ref`-prefixed bookmark name unused in the document. Word derives its own from a timestamp; a counter is used here instead, since a timestamp would make the same document generate different bytes on each run and byte-reproducible output is something this library keeps. Source code in `src/docx/caption.py` ``` def next_ref_bookmark_name(document_element: object) -> str: """A `_Ref`-prefixed bookmark name unused in the document. Word derives its own from a timestamp; a counter is used here instead, since a timestamp would make the same document generate different bytes on each run and byte-reproducible output is something this library keeps. """ highest = 0 for name in document_element.xpath( # pyright: ignore[reportAttributeAccessIssue] "//w:bookmarkStart/@w:name" ): match = _REF_NAME_RE.match(name) if match: highest = max(highest, int(match.group(1))) return "%s%0*d" % (_REF_PREFIX, _REF_NAME_DIGITS, highest + 1) ``` ### add_caption ``` add_caption( container: object, label: str, text: str = "", *, style: str | None = "Caption", separator: str = " ", restart_at_heading_level: int | None = None, before: Paragraph | None = None, ) -> Caption ``` Build a caption paragraph in `container`; see Document.add_caption. Source code in `src/docx/caption.py` ``` def add_caption( container: object, label: str, text: str = "", *, style: str | None = "Caption", separator: str = " ", restart_at_heading_level: int | None = None, before: Paragraph | None = None, ) -> Caption: """Build a caption paragraph in `container`; see :meth:`.Document.add_caption`.""" from docx.text.paragraph import Paragraph paragraph = container.add_paragraph() # pyright: ignore[reportAttributeAccessIssue] if before is not None: before._p.addprevious(paragraph._p) # pyright: ignore[reportPrivateUsage] if style is not None: paragraph.style = style paragraph.add_run(label + " ") paragraph.add_field( fields.sequence(label, restart_at_heading_level=restart_at_heading_level) ) if text: paragraph.add_run(separator + text) part = paragraph.part bookmark_name = next_ref_bookmark_name(part.element) paragraph._p.add_bookmark_around_content( # pyright: ignore[reportPrivateUsage] part.next_bookmark_id, bookmark_name ) assert isinstance(paragraph, Paragraph) return Caption(paragraph, bookmark_name, label) ``` ### caption_bookmark_names ``` caption_bookmark_names( document_element: object, ) -> tuple[str, ...] ``` Every `_Ref`-prefixed bookmark name in the document, in document order. Source code in `src/docx/caption.py` ``` def caption_bookmark_names(document_element: object) -> tuple[str, ...]: """Every `_Ref`-prefixed bookmark name in the document, in document order.""" return tuple( name for name in document_element.xpath( # pyright: ignore[reportAttributeAccessIssue] "//w:bookmarkStart/@w:name" ) if _REF_NAME_RE.match(name) ) ``` ## cleanup Removing the things a document carries that nothing points at. Three separate kinds of dead weight, each reached the same way — find what is referenced, drop the rest: - **Styles.** A document created by this library defines 164 of them and a one-paragraph document references one. This is Styles.remove_unused, built on the reachability closure in `docx.styles.usage`. - **Numbering definitions.** A `w:abstractNum` no `w:num` points at, and a `w:num` no `w:numPr` points at, are both dead. - **Orphan media.** An image part related from nothing, left behind when the run or shape that displayed it was deleted. The `.delete()` API added in 2.0.0 makes this reachable from ordinary use. cleanup runs the lot; the individual operations stay public, because "remove unused styles but leave my media alone" is a reasonable thing to want. ### CleanupResult Bases: `NamedTuple` What a cleanup pass removed. ### remove_unused_numbering ``` remove_unused_numbering( document_part: DocumentPart, ) -> tuple[Tuple[int, ...], Tuple[int, ...]] ``` Drop the numbering definitions nothing references; return (numIds, abstractNumIds). A `w:num` is dead when no `w:numPr` in any story part or style names its numId. A `w:abstractNum` is dead when no surviving `w:num` points at it *and* no surviving abstract definition chains to it through `w:numStyleLink`. Does nothing, and reports nothing removed, for a document with no numbering part — reading one would create it. Source code in `src/docx/cleanup.py` ``` def remove_unused_numbering(document_part: DocumentPart) -> tuple[Tuple[int, ...], Tuple[int, ...]]: """Drop the numbering definitions nothing references; return (numIds, abstractNumIds). A `w:num` is dead when no `w:numPr` in any story part or style names its numId. A `w:abstractNum` is dead when no surviving `w:num` points at it *and* no surviving abstract definition chains to it through `w:numStyleLink`. Does nothing, and reports nothing removed, for a document with no numbering part — reading one would create it. """ if not document_part.has_numbering_part: return (), () numbering = document_part.numbering_part.element referenced = _referenced_num_ids(document_part) removed_num_ids: list[int] = [] for num in list(numbering.num_lst): if num.numId not in referenced: removed_num_ids.append(num.numId) numbering.remove(num) live_abstract_ids = {num.abstractNumId.val for num in numbering.num_lst} # -- an abstract definition can point at another through `w:numStyleLink`, so the # -- survivors' own references have to be followed before deciding what is dead -- by_id = {a.abstractNumId: a for a in numbering.abstractNum_lst} pending = set(live_abstract_ids) reachable: Set[int] = set() while pending: abstract_id = pending.pop() if abstract_id in reachable or abstract_id not in by_id: continue reachable.add(abstract_id) for style_id in by_id[abstract_id].xpath("./w:numStyleLink/@w:val"): for other in numbering.abstractNum_lst: if other.xpath("./w:styleLink/@w:val") == [style_id]: pending.add(other.abstractNumId) removed_abstract_ids: list[int] = [] for abstractNum in list(numbering.abstractNum_lst): if abstractNum.abstractNumId not in reachable: removed_abstract_ids.append(abstractNum.abstractNumId) numbering.remove(abstractNum) return tuple(removed_num_ids), tuple(removed_abstract_ids) ``` ### remove_orphan_media ``` remove_orphan_media( document_part: DocumentPart, ) -> Tuple[str, ...] ``` Drop image relationships nothing in the document part's XML refers to. An `r:embed`, `r:link` or `r:id` naming the relationship is what keeps an image alive. Deleting a paragraph that held a picture leaves the relationship and the part behind; this is what removes them. Only the document part's own image relationships are considered. A picture in a header or a footnote belongs to that part's relationships and is not this pass's business — a header image is not orphaned by anything happening in the body. Source code in `src/docx/cleanup.py` ``` def remove_orphan_media(document_part: DocumentPart) -> Tuple[str, ...]: """Drop image relationships nothing in the document part's XML refers to. An `r:embed`, `r:link` or `r:id` naming the relationship is what keeps an image alive. Deleting a paragraph that held a picture leaves the relationship and the part behind; this is what removes them. Only the document part's own image relationships are considered. A picture in a header or a footnote belongs to that part's relationships and is not this pass's business — a header image is not orphaned by anything happening in the body. """ referenced = set( document_part.element.xpath("//@r:embed | //@r:link | //@r:id") ) removed: list[str] = [] for rId, rel in list(document_part.rels.items()): if rel.reltype != RT.IMAGE or rel.is_external: continue if rId not in referenced: removed.append(str(rel.target_part.partname)) document_part.drop_rel(rId) return tuple(removed) ``` ### cleanup ``` cleanup( document_part: DocumentPart, *, styles: bool = True, numbering: bool = True, media: bool = True, latent_styles: bool = False, keep: tuple[str, ...] = (), ) -> CleanupResult ``` Remove what this document carries that nothing points at. Each flag turns one pass on or off; `keep` names styles to preserve along with their dependencies, as for Styles.remove_unused. `latent_styles` is `False` by default and separate from `styles` on purpose: removing a `w:lsdException` changes what a user sees in Word's style gallery rather than how the document renders, which is a different kind of change from removing a style definition. Styles are pruned before numbering, so a numbering definition kept alive only by a style that is about to go is correctly seen as dead. Source code in `src/docx/cleanup.py` ``` def cleanup( document_part: DocumentPart, *, styles: bool = True, numbering: bool = True, media: bool = True, latent_styles: bool = False, keep: tuple[str, ...] = (), ) -> CleanupResult: """Remove what this document carries that nothing points at. Each flag turns one pass on or off; `keep` names styles to preserve along with their dependencies, as for :meth:`.Styles.remove_unused`. `latent_styles` is |False| by default and separate from `styles` on purpose: removing a `w:lsdException` changes what a user sees in Word's style gallery rather than how the document renders, which is a different kind of change from removing a style definition. Styles are pruned before numbering, so a numbering definition kept alive only by a style that is about to go is correctly seen as dead. """ removed_styles: Tuple[str, ...] = () if styles: removed_styles = document_part.styles.remove_unused(keep=keep) removed_nums: Tuple[int, ...] = () removed_abstract_nums: Tuple[int, ...] = () if numbering: removed_nums, removed_abstract_nums = remove_unused_numbering(document_part) removed_media: Tuple[str, ...] = () if media: removed_media = remove_orphan_media(document_part) trimmed = 0 if latent_styles: trimmed = document_part.styles.latent_styles.trim() return CleanupResult( styles=removed_styles, num_ids=removed_nums, abstract_num_ids=removed_abstract_nums, media=removed_media, latent_styles=trimmed, ) ``` ## cli A command-line front end for the inspection and cleanup operations. `python -m docx` answers the questions people ask about a `.docx` one at a time: what styles it defines, which are actually used, and why the file is 900 KB. Those are diagnostic operations — you run them to find out something about a file, not as part of an application — and a diagnostic API with no command-line front end mostly does not get used. Deliberately thin: every subcommand maps onto one public library operation and holds no logic of its own, so the CLI cannot drift from the API or grow behaviour that is only reachable through it. argparse only; no new runtime dependency. Two rules the commands keep: - **Never modify the input.** `cleanup` writes to `-o` and refuses without it. Someone will point it at their only copy. - **Exit codes matter**, because this ends up in scripts: non-zero for a document that cannot be opened, and `--check` reports what would be removed and exits non-zero if anything would be, so it can be a CI gate. ### main ``` main( argv: Sequence[str] | None = None, stdout: IO[str] | None = None, ) -> int ``` Run the CLI; return the process exit code. `argv` and `stdout` are injectable so the tests do not have to drive a subprocess. Source code in `src/docx/cli.py` ``` def main(argv: Sequence[str] | None = None, stdout: IO[str] | None = None) -> int: """Run the CLI; return the process exit code. `argv` and `stdout` are injectable so the tests do not have to drive a subprocess. """ out = sys.stdout if stdout is None else stdout parser = _build_parser() args = parser.parse_args(argv) if getattr(args, "func", None) is None: parser.print_help(out) return 0 try: return args.func(args, out) except FileNotFoundError as e: print("error: no such file: %s" % e.filename, file=sys.stderr) return EXIT_CANNOT_OPEN except EncryptedPackageError: print("error: document is password-protected", file=sys.stderr) return EXIT_CANNOT_OPEN except (PackageNotFoundError, zipfile.BadZipFile) as e: print("error: cannot open document: %s" % e, file=sys.stderr) return EXIT_CANNOT_OPEN except KeyError as e: # -- `styles extract --names` and `cleanup --keep` name styles, and a name the # -- document does not define surfaces here rather than as a traceback -- message = e.args[0] if e.args else str(e) print("error: %s" % message, file=sys.stderr) return EXIT_CANNOT_OPEN ``` ## comments Collection providing access to comments added to this document. ### Comments ``` Comments( comments_elm: CT_Comments, comments_part: CommentsPart ) ``` Collection containing the comments added to this document. Source code in `src/docx/comments.py` ``` def __init__(self, comments_elm: CT_Comments, comments_part: CommentsPart): self._comments_elm = comments_elm self._comments_part = comments_part ``` #### add_comment ``` add_comment( text: str = "", author: str = "", initials: str | None = "", ) -> Comment ``` Add a new comment to the document and return it. The comment is added to the end of the comments collection and is assigned a unique comment-id. If `text` is provided, it is added to the comment. This option provides for the common case where a comment contains a modest passage of plain text. Multiple paragraphs can be added using the `text` argument by separating their text with newlines (`"\\n"`). Between newlines, text is interpreted as it is in `Document.add_paragraph(text=...)`. The default is to place a single empty paragraph in the comment, which is the same behavior as the Word UI when you add a comment. New runs can be added to the first paragraph in the empty comment with `comments.paragraphs[0].add_run()` to adding more complex text with emphasis or images. Additional paragraphs can be added using `.add_paragraph()`. `author` is a required attribute, set to the empty string by default. `initials` is an optional attribute, set to the empty string by default. Passing `None` for the `initials` parameter causes that attribute to be omitted from the XML. Source code in `src/docx/comments.py` ``` def add_comment(self, text: str = "", author: str = "", initials: str | None = "") -> Comment: """Add a new comment to the document and return it. The comment is added to the end of the comments collection and is assigned a unique comment-id. If `text` is provided, it is added to the comment. This option provides for the common case where a comment contains a modest passage of plain text. Multiple paragraphs can be added using the `text` argument by separating their text with newlines (`"\\\\n"`). Between newlines, text is interpreted as it is in `Document.add_paragraph(text=...)`. The default is to place a single empty paragraph in the comment, which is the same behavior as the Word UI when you add a comment. New runs can be added to the first paragraph in the empty comment with `comments.paragraphs[0].add_run()` to adding more complex text with emphasis or images. Additional paragraphs can be added using `.add_paragraph()`. `author` is a required attribute, set to the empty string by default. `initials` is an optional attribute, set to the empty string by default. Passing |None| for the `initials` parameter causes that attribute to be omitted from the XML. """ comment_elm = self._comments_elm.add_comment() comment_elm.author = author comment_elm.initials = initials comment_elm.date = dt.datetime.now(dt.timezone.utc) comment = Comment(comment_elm, self._comments_part) if text == "": return comment para_text_iter = iter(text.split("\n")) first_para_text = next(para_text_iter) first_para = comment.paragraphs[0] first_para.add_run(first_para_text) for s in para_text_iter: comment.add_paragraph(text=s) return comment ``` #### get ``` get(comment_id: int) -> Comment | None ``` Return the comment identified by `comment_id`, or `None` if not found. Source code in `src/docx/comments.py` ``` def get(self, comment_id: int) -> Comment | None: """Return the comment identified by `comment_id`, or |None| if not found.""" comment_elm = self._comments_elm.get_comment_by_id(comment_id) return Comment(comment_elm, self._comments_part) if comment_elm is not None else None ``` ### Comment ``` Comment( comment_elm: CT_Comment, comments_part: CommentsPart ) ``` Bases: `BlockItemContainer` Proxy for a single comment in the document. Provides methods to access comment metadata such as author, initials, and date. A comment is also a block-item container, similar to a table cell, so it can contain both paragraphs and tables and its paragraphs can contain rich text, hyperlinks and images, although the common case is that a comment contains a single paragraph of plain text like a sentence or phrase. Note that certain content like tables may not be displayed in the Word comment sidebar due to space limitations. Such "over-sized" content can still be viewed in the review pane. Source code in `src/docx/comments.py` ``` def __init__(self, comment_elm: CT_Comment, comments_part: CommentsPart): super().__init__(comment_elm, comments_part) self._comment_elm = comment_elm ``` #### author ``` author: str ``` Read/write. The recorded author of this comment. This field is required but can be set to the empty string. #### comment_id ``` comment_id: int ``` The unique identifier of this comment. #### initials ``` initials: str | None ``` Read/write. The recorded initials of the comment author. This attribute is optional in the XML, returns `None` if not set. Assigning `None` removes any existing initials from the XML. #### text ``` text: str ``` The text content of this comment as a string. Only content in paragraphs is included and of course all emphasis and styling is stripped. Paragraph boundaries are indicated with a newline (`"\\n"`) #### timestamp ``` timestamp: datetime | None ``` The date and time this comment was authored. This attribute is optional in the XML, returns `None` if not set. #### add_paragraph ``` add_paragraph( text: str = "", style: str | ParagraphStyle | None = None, ) -> Paragraph ``` Return paragraph newly added to the end of the content in this container. The paragraph has `text` in a single run if present, and is given paragraph style `style`. When `style` is `None` or ommitted, the "CommentText" paragraph style is applied, which is the default style for comments. Source code in `src/docx/comments.py` ``` def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph: """Return paragraph newly added to the end of the content in this container. The paragraph has `text` in a single run if present, and is given paragraph style `style`. When `style` is |None| or ommitted, the "CommentText" paragraph style is applied, which is the default style for comments. """ paragraph = super().add_paragraph(text, style) # -- have to assign style directly to element because `paragraph.style` raises when # -- a style is not present in the styles part if style is None: paragraph._p.style = "CommentText" # pyright: ignore[reportPrivateUsage] return paragraph ``` ## copy Duplicating content — a paragraph, a run, a table row, a whole table. `copy.deepcopy(paragraph._p)` followed by an `addnext()` works for plain text and quietly breaks for anything interesting. Everything this module does beyond the deep copy is a repair of one of those breakages: - a picture's `r:embed` names a relationship id belonging to the *source* part, so a copied image is either the wrong image or a dangling reference; - a hyperlink's `r:id` has the same problem, and points at an external target that may not exist in the destination package; - `wp:docPr/@id` must be unique document-wide, and a deep copy duplicates it; - bookmark ids and names collide, and Word treats a duplicate bookmark name as a second bookmark competing for anything that refers to it; - a copy into a *different* document carries a `w:pStyle` naming a style that may not be there, and a `w:numPr` naming a `numId` that certainly is not. The two hard pieces already existed: `Styles.copy_style_from()` resolves the style closure and carries numbering across, and `StoryPart.next_id` allocates non-colliding drawing ids. This is what uses them. ### copy_content ``` copy_content( element: BaseOxmlElement, source_part: StoryPart, dest_part: StoryPart, *, missing_style: str = "copy", ) -> BaseOxmlElement ``` A deep copy of `element` fit to be inserted into `dest_part`. The copy is not attached to anything; the caller places it. See Paragraph.copy_to for what `missing_style` means. Source code in `src/docx/copy.py` ``` def copy_content( element: BaseOxmlElement, source_part: StoryPart, dest_part: StoryPart, *, missing_style: str = "copy", ) -> BaseOxmlElement: """A deep copy of `element` fit to be inserted into `dest_part`. The copy is not attached to anything; the caller places it. See :meth:`.Paragraph.copy_to` for what `missing_style` means. """ if missing_style not in _MISSING_STYLE_POLICIES: raise ValueError( "missing_style must be one of %s, got %r" % (", ".join(repr(p) for p in _MISSING_STYLE_POLICIES), missing_style) ) new_element = copymod.deepcopy(element) _remap_relationships(new_element, source_part, dest_part) _reassign_drawing_ids(new_element, dest_part) _strip_bookmarks(new_element) if source_part is not dest_part: _carry_styles(new_element, source_part, dest_part, missing_style) _carry_numbering(new_element, source_part, dest_part) return new_element ``` ### destination_for ``` destination_for( container: object, ) -> tuple[StoryPart, BaseOxmlElement] ``` The `(part, element)` a copy goes into for `container`. A Document is not itself a block-item container — its body is — so it is unwrapped here rather than at each call site. Source code in `src/docx/copy.py` ``` def destination_for(container: object) -> tuple[StoryPart, BaseOxmlElement]: """The `(part, element)` a copy goes into for `container`. A |Document| is not itself a block-item container — its body is — so it is unwrapped here rather than at each call site. """ from docx.document import Document if isinstance(container, Document): body = container._body # pyright: ignore[reportPrivateUsage] return container.part, body._element # pyright: ignore[reportPrivateUsage] return ( container.part, # pyright: ignore[reportAttributeAccessIssue] container._element, # pyright: ignore[reportAttributeAccessIssue,reportPrivateUsage] ) ``` ### place ``` place( new_element: BaseOxmlElement, dest_element: BaseOxmlElement, before: object = None, after: object = None, ) -> None ``` Insert `new_element` into `dest_element`, relative to `before` or `after`. With neither, the copy is appended. A body ending in a `w:sectPr` appends before it, since the section properties must stay last. Source code in `src/docx/copy.py` ``` def place( new_element: BaseOxmlElement, dest_element: BaseOxmlElement, before: object = None, after: object = None, ) -> None: """Insert `new_element` into `dest_element`, relative to `before` or `after`. With neither, the copy is appended. A body ending in a `w:sectPr` appends before it, since the section properties must stay last. """ if before is not None and after is not None: raise ValueError("pass at most one of `before` and `after`") if before is not None: _element_of(before).addprevious(new_element) return if after is not None: _element_of(after).addnext(new_element) return sectPr = dest_element.find(qn("w:sectPr")) if sectPr is not None: sectPr.addprevious(new_element) else: dest_element.append(new_element) ``` ## document Document and closely related objects. ### Document ``` Document(element: CT_Document, part: DocumentPart) ``` Bases: `ElementProxy` WordprocessingML (WML) document. Not intended to be constructed directly. Use `docx.Document` to open or create a document. Source code in `src/docx/document.py` ``` def __init__(self, element: CT_Document, part: DocumentPart): super(Document, self).__init__(element) self._element = element self._part = part self.__body = None ``` #### alt_chunks ``` alt_chunks: List[AltChunk] ``` The AltChunk objects in the document body, in document order. Only alt-chunks that are direct children of the body appear here; the schema also allows one inside a table cell or other block container. #### comments ``` comments: Comments ``` A Comments object providing access to comments added to the document. #### custom_properties ``` custom_properties: CustomProperties ``` A CustomProperties object providing the arbitrary named values attached to this document. Behaves as a mutable mapping of name to value. The part holding them is created the first time this is used, so a document that never touches it gains no `/docProps/custom.xml`. #### core_properties ``` core_properties ``` A CoreProperties object providing Dublin Core properties of document. #### extended_properties ``` extended_properties ``` An ExtendedProperties object providing the application-specific properties of the document, such as word count and producing application. #### footnotes ``` footnotes: Footnotes ``` A Footnotes object providing access to the footnotes of this document. The footnotes part is created the first time this is used, so a document that never touches it gains no `/word/footnotes.xml`. #### custom_xml_parts ``` custom_xml_parts: Tuple[CustomXmlPart, ...] ``` The custom XML data store items of this document, in relationship order. Each part offers `.item_id`, `.schema_refs`, `.element` and `.xml`. The item content is arbitrary caller-supplied XML, so `.element` is a plain parsed tree with no element classes of its own. #### has_macros ``` has_macros: bool ``` `True` when this document carries a VBA project. The cheap predicate; vba_project is what reads the bytes. #### vba_project ``` vba_project: bytes | None ``` The macro project of this document as bytes, or `None` when it has none. A `.docm` or `.dotm` carries its macros in `word/vbaProject.bin`, an OLE compound file. This library does not parse it, but it round-trips untouched, so the two operations people actually want are expressible: **Strip the macros** from a document received from elsewhere: ``` del document.vba_project document.save("clean.docx") ``` **Transplant a project** authored in Word into a generated document: ``` document.vba_project = donor.vba_project ``` Assigning switches the main part to the macro-enabled content type, and removing switches it back. Word silently ignores macros in a document whose main part does not claim to be macro-enabled, and warns the user about macros in one that claims to be but is not, so the two are kept in step rather than left to the caller. Note this sets the content type; it does not choose the file extension for you. A macro-enabled document conventionally has a `.docm` extension. #### endnotes ``` endnotes: Endnotes ``` An Endnotes object providing access to the endnotes of this document. The endnotes part is created the first time this is used, so a document that never touches it gains no `/word/endnotes.xml`. #### fields ``` fields: List[Field] ``` A Field for each field in the document body, in document order. Outermost first, so a `PAGEREF` nested in a table-of-contents entry follows the `TOC` field containing it. Fields in a header or footer are not in the document part and so are not included; reach those through the paragraphs of the header or footer. #### form_fields ``` form_fields: List[FormField] ``` A FormField instance for each legacy form field in the document body. Fields appear in document order, including those inside tables. Fields in a header or footer are not in the document part and so are not included; reach those through the paragraphs of the header or footer. #### floating_shapes ``` floating_shapes ``` The FloatingShapes collection for this document. A floating shape is anchored rather than inline: it is positioned against the page, the margin, the column or the paragraph, and text wraps around it. These do not appear in inline_shapes, whose position properties would be meaningless for them. #### embedded_objects ``` embedded_objects: List[EmbeddedObject] ``` The OLE objects embedded in the document body, in document order. An embedded object is a whole file carried inside the document — a spreadsheet, a PDF, another document — which Word opens in its own application on double-click. Extracting them is the useful half: ``` for obj in document.embedded_objects: if obj.blob is not None: Path(obj.filename or "attachment").write_bytes(obj.blob) ``` Objects in a header, a footer or a footnote belong to those parts and are not included; reach them through the container concerned. #### images ``` images: Tuple[Image, ...] ``` The distinct images embedded in this document's body, in relationship order. This is the package-level view, the counterpart of reaching an image through the shape that displays it. Several shapes can share one image part, so this is shorter than inline_shapes whenever a picture is used twice, and it includes images no shape displays — a picture left behind when its paragraph was deleted, for instance. Only images related from the main document part appear here. A picture in a header, a footer or a comment belongs to that part's relationships instead. A *linked* image is not included: its bytes are not in the package. Neither is a relationship of image type whose target is not an image part, which does occur — see the same guard in `Package._gather_image_parts()`. #### theme ``` theme: Theme | None ``` The document's Theme, or `None` when it carries no theme part. The theme is where a theme typeface token such as `"minorHAnsi"` becomes a real font name, and where a theme colour becomes an RGB value: ``` document.theme.minor_font.latin # -> 'Calibri' document.theme.color("accent1") ``` For the large class of documents that set no explicit `w:rFonts/@w:ascii` anywhere, this is the only place the typeface the text is actually rendered in can be found; see also Font.theme_typeface. #### inline_shapes ``` inline_shapes ``` The InlineShapes collection for this document. An inline shape is a graphical object, such as a picture, contained in a run of text and behaving like a character glyph, being flowed like other text in a paragraph. #### content_controls ``` content_controls: List[ContentControl] ``` The structured document tags (content controls) in the document body. In document order, outermost first. The content of a control appears in `.paragraphs`, `.tables` and `.iter_inner_content()` as though the wrapper were not there; this is how the wrapper itself is reached. #### math ``` math: List[Math] ``` The equations in the document body, in document order. Equations in a header, a footer, a footnote or a comment are in those parts rather than the body and are not included; reach them through the container concerned. See Paragraph.math for why equation text is not part of Paragraph.text. #### numbering ``` numbering: Numbering ``` A Numbering object providing access to the list definitions of this document. The numbering part is created the first time this is used, so a document that never touches it gains no `/word/numbering.xml`. #### list_numbers ``` list_numbers: List[tuple[Paragraph, str]] ``` `(paragraph, number)` for each list paragraph in the body, in document order. The number is what a reader sees — "1.", "a)", "iii." — which Word computes from `numbering.xml` at display time rather than storing in the body: ``` for paragraph, number in document.list_numbers: print(number, paragraph.text) ``` Paragraphs inside tables are included, since they count towards the same lists. This walks the document once, which is why it exists alongside Paragraph.list_number: reading that for every paragraph is quadratic. #### paragraphs ``` paragraphs: List[Paragraph] ``` The Paragraph instances in the document, in document order. A paragraph wrapped in a `w:sdt` (content control) appears in this list, in the position of its wrapper. A revision mark such as `w:ins` or `w:del` wraps runs rather than paragraphs, so it does not affect which paragraphs appear here; it affects their text. See Paragraph.text and Paragraph.original_text. #### part ``` part: DocumentPart ``` The DocumentPart object of this document. #### is_template ``` is_template: bool ``` `True` when this document is a Word template, a `.dotx` or `.dotm`. A template holds the same markup as a document and differs only in the content type of its main part, which is what tells Word to start a new document from it rather than open it for editing. #### revisions ``` revisions: List[Revision] ``` A Revision for each tracked change in the document body, in document order. Empty for a document that has not been through review. Revisions in a header, footer or footnote are not in the document part and so are not included; reach those through the paragraphs of the story concerned. #### sections ``` sections: Sections ``` Sections object providing access to each section in this document. #### watermarks ``` watermarks: List[Watermark] ``` Every watermark in the document, in section and header order. Empty when the document has none. Ordinarily one per header rather than one per document, since a watermark is a shape in a header and each header carries its own. #### settings ``` settings: Settings ``` A Settings object providing access to the document-level settings. #### styles ``` styles ``` A Styles object providing access to the styles in this document. #### tables ``` tables: List[Table] ``` All Table instances in the document, in document order. Note that only tables appearing at the top level of the document appear in this list; a table nested inside a table cell does not appear. A table wrapped in a `w:sdt` (content control) does appear. A row marked as inserted or deleted appears as an ordinary row; see revisions. #### add_alt_chunk ``` add_alt_chunk( chunk: bytes | str | PathLike[str] | IO[bytes], content_type: str, ) -> AltChunk ``` Return an AltChunk newly added at the end of the document body. `chunk` is the embedded document, given as bytes, as a path to a file (a string or `os.PathLike`), or as a file-like object open for binary read. `content_type` states its format, e.g. `"text/html"`, `"application/rtf"` or `"application/vnd.openxmlformats-officedocument.wordprocessingml.document"`; Word chooses an importer from it, so it must be right. Word performs the import when it opens the document, which means the embedded content is not visible to this library. Its paragraphs and tables do not appear in `Document.paragraphs`, `Document.tables` or `Document.iter_inner_content()`, and it contributes no styles, numbering or images to this document until Word has rewritten the file. Source code in `src/docx/document.py` ``` def add_alt_chunk( self, chunk: bytes | str | os.PathLike[str] | IO[bytes], content_type: str ) -> AltChunk: """Return an |AltChunk| newly added at the end of the document body. `chunk` is the embedded document, given as bytes, as a path to a file (a string or ``os.PathLike``), or as a file-like object open for binary read. `content_type` states its format, e.g. `"text/html"`, `"application/rtf"` or `"application/vnd.openxmlformats-officedocument.wordprocessingml.document"`; Word chooses an importer from it, so it must be right. Word performs the import when it opens the document, which means the embedded content is not visible to this library. Its paragraphs and tables do not appear in `Document.paragraphs`, `Document.tables` or `Document.iter_inner_content()`, and it contributes no styles, numbering or images to this document until Word has rewritten the file. """ blob = chunk if isinstance(chunk, bytes) else _read_blob(chunk) rId = self._part.add_alt_chunk_part(blob, content_type) altChunk = self._element.body.add_altChunk() altChunk.rId = rId return AltChunk(altChunk, self._part) ``` #### add_comment ``` add_comment( runs: Run | Sequence[Run], text: str | None = "", author: str = "", initials: str | None = "", ) -> Comment ``` Add a comment to the document, anchored to the specified runs. `runs` can be a single `Run` object or a non-empty sequence of `Run` objects. Only the first and last run of a sequence are used, it's just more convenient to pass a whole sequence when that's what you have handy, like `paragraph.runs` for example. When `runs` contains a single `Run` object, that run serves as both the first and last run. A comment can be anchored only on an even run boundary, meaning the text the comment "references" must be a non-zero integer number of consecutive runs. The runs need not be *contiguous* per se, like the first can be in one paragraph and the last in the next paragraph, but all runs between the first and the last will be included in the reference. The comment reference range is delimited by placing a `w:commentRangeStart` element before the first run and a `w:commentRangeEnd` element after the last run. This is why only the first and last run are required and why a single run can serve as both first and last. Word works out which text to highlight in the UI based on these range markers. `text` allows the contents of a simple comment to be provided in the call, providing for the common case where a comment is a single phrase or sentence without special formatting such as bold or italics. More complex comments can be added using the returned `Comment` object in much the same way as a `Document` or (table) `Cell` object, using methods like `.add_paragraph()`, .add_run()\`, etc. The `author` and `initials` parameters allow that metadata to be set for the comment. `author` is a required attribute on a comment and is the empty string by default. `initials` is optional on a comment and may be omitted by passing `None`, but Word adds an `initials` attribute by default and we follow that convention by using the empty string when no `initials` argument is provided. Source code in `src/docx/document.py` ``` def add_comment( self, runs: Run | Sequence[Run], text: str | None = "", author: str = "", initials: str | None = "", ) -> Comment: """Add a comment to the document, anchored to the specified runs. `runs` can be a single `Run` object or a non-empty sequence of `Run` objects. Only the first and last run of a sequence are used, it's just more convenient to pass a whole sequence when that's what you have handy, like `paragraph.runs` for example. When `runs` contains a single `Run` object, that run serves as both the first and last run. A comment can be anchored only on an even run boundary, meaning the text the comment "references" must be a non-zero integer number of consecutive runs. The runs need not be _contiguous_ per se, like the first can be in one paragraph and the last in the next paragraph, but all runs between the first and the last will be included in the reference. The comment reference range is delimited by placing a `w:commentRangeStart` element before the first run and a `w:commentRangeEnd` element after the last run. This is why only the first and last run are required and why a single run can serve as both first and last. Word works out which text to highlight in the UI based on these range markers. `text` allows the contents of a simple comment to be provided in the call, providing for the common case where a comment is a single phrase or sentence without special formatting such as bold or italics. More complex comments can be added using the returned `Comment` object in much the same way as a `Document` or (table) `Cell` object, using methods like `.add_paragraph()`, .add_run()`, etc. The `author` and `initials` parameters allow that metadata to be set for the comment. `author` is a required attribute on a comment and is the empty string by default. `initials` is optional on a comment and may be omitted by passing |None|, but Word adds an `initials` attribute by default and we follow that convention by using the empty string when no `initials` argument is provided. """ # -- normalize `runs` to a sequence of runs -- runs = [runs] if isinstance(runs, Run) else runs first_run = runs[0] last_run = runs[-1] # -- Note that comments can only appear in the document part -- comment = self.comments.add_comment(text=text, author=author, initials=initials) # -- let the first run orchestrate placement of the comment range start and end -- first_run.mark_comment_range(last_run, comment.comment_id) return comment ``` #### add_caption ``` add_caption( label: str, text: str = "", *, style: str | None = "Caption", separator: str = " ", restart_at_heading_level: int | None = None, before: Paragraph | None = None, ) -> Caption ``` Add a numbered, cross-referenceable caption and return it. Word numbers each `label` series independently and renumbers the whole series when one is inserted, which is the point of using a `SEQ` field rather than a typed number. The number is therefore *not* in the document until Word computes it; set Settings.update_fields_on_open to have it do so on open. The caption is bookmarked with a `_Ref`-prefixed name and the returned object carries it, so a cross-reference is a one-liner: ``` caption = document.add_caption("Figure", "Cross-section of the assembly") document.add_paragraph().add_field( fields.cross_reference(caption.bookmark_name) ) ``` The `_Ref` naming is not decoration: Word's own cross-reference dialogue offers only targets whose bookmark name follows it, so a caption bookmarked with an arbitrary name is one the user cannot reference from the UI. `style` is the paragraph style, "Caption" by default, which is what Word uses; pass `None` to leave the paragraph unstyled. `separator` goes between the number and `text`. `restart_at_heading_level` restarts the numbering at each heading of that level, giving the "Figure 2-1" style. `before` places the caption immediately before an existing paragraph, which is where a table caption goes. Source code in `src/docx/document.py` ``` def add_caption( self, label: str, text: str = "", *, style: str | None = "Caption", separator: str = " ", restart_at_heading_level: int | None = None, before: Paragraph | None = None, ) -> Caption: """Add a numbered, cross-referenceable caption and return it. Word numbers each `label` series independently and renumbers the whole series when one is inserted, which is the point of using a `SEQ` field rather than a typed number. The number is therefore *not* in the document until Word computes it; set :attr:`.Settings.update_fields_on_open` to have it do so on open. The caption is bookmarked with a `_Ref`-prefixed name and the returned object carries it, so a cross-reference is a one-liner:: caption = document.add_caption("Figure", "Cross-section of the assembly") document.add_paragraph().add_field( fields.cross_reference(caption.bookmark_name) ) The `_Ref` naming is not decoration: Word's own cross-reference dialogue offers only targets whose bookmark name follows it, so a caption bookmarked with an arbitrary name is one the user cannot reference from the UI. `style` is the paragraph style, "Caption" by default, which is what Word uses; pass |None| to leave the paragraph unstyled. `separator` goes between the number and `text`. `restart_at_heading_level` restarts the numbering at each heading of that level, giving the "Figure 2-1" style. `before` places the caption immediately before an existing paragraph, which is where a table caption goes. """ from docx.caption import add_caption return add_caption( self._body, label, text, style=style, separator=separator, restart_at_heading_level=restart_at_heading_level, before=before, ) ``` #### add_heading ``` add_heading(text: str = '', level: int = 1) ``` Return a heading paragraph newly added to the end of the document. The heading paragraph will contain `text` and have its paragraph style determined by `level`. If `level` is 0, the style is set to `Title`. If `level` is 1 (or omitted), `Heading 1` is used. Otherwise the style is set to `Heading {level}`. Raises `ValueError` if `level` is outside the range 0-9. Source code in `src/docx/document.py` ``` def add_heading(self, text: str = "", level: int = 1): """Return a heading paragraph newly added to the end of the document. The heading paragraph will contain `text` and have its paragraph style determined by `level`. If `level` is 0, the style is set to `Title`. If `level` is 1 (or omitted), `Heading 1` is used. Otherwise the style is set to `Heading {level}`. Raises |ValueError| if `level` is outside the range 0-9. """ if not 0 <= level <= 9: raise ValueError("level must be in range 0-9, got %d" % level) style = "Title" if level == 0 else "Heading %d" % level return self.add_paragraph(text, style) ``` #### add_page_break ``` add_page_break() ``` Return newly Paragraph object containing only a page break. Source code in `src/docx/document.py` ``` def add_page_break(self): """Return newly |Paragraph| object containing only a page break.""" paragraph = self.add_paragraph() paragraph.add_run().add_break(WD_BREAK.PAGE) return paragraph ``` #### add_paragraph ``` add_paragraph( text: str = "", style: str | ParagraphStyle | None = None, ) -> Paragraph ``` Return paragraph newly added to the end of the document. The paragraph is populated with `text` and having paragraph style `style`. `text` can contain tab (`\t`) characters, which are converted to the appropriate XML form for a tab. `text` can also include newline (`\n`) or carriage return (`\r`) characters, each of which is converted to a line break. Source code in `src/docx/document.py` ``` def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph: """Return paragraph newly added to the end of the document. The paragraph is populated with `text` and having paragraph style `style`. `text` can contain tab (``\\t``) characters, which are converted to the appropriate XML form for a tab. `text` can also include newline (``\\n``) or carriage return (``\\r``) characters, each of which is converted to a line break. """ return self._body.add_paragraph(text, style) ``` #### add_picture ``` add_picture( image_path_or_stream: str | PathLike[str] | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, description: str | None = None, title: str | None = None, svg_fallback: str | PathLike[str] | IO[bytes] | None = None, honor_exif_orientation: bool = True, ) ``` Return new picture shape added in its own paragraph at end of the document. The picture contains the image at `image_path_or_stream`, scaled based on `width` and `height`. If neither width nor height is specified, the picture appears at its native size. If only one is specified, it is used to compute a scaling factor that is then applied to the unspecified dimension, preserving the aspect ratio of the image. The native size of the picture is calculated using the dots-per-inch (dpi) value specified in the image file, defaulting to 72 dpi if no value is specified, as is often the case. `description` is the picture's alternative text, which is what a screen reader announces and what an accessibility check looks for; `title` is the separate caption-like field Word writes alongside it. `svg_fallback` is the raster image shown in place of an SVG wherever the vector source cannot be rendered, and `honor_exif_orientation` applies a photo's EXIF `Orientation` as a rotation in the DrawingML; see `Run.add_picture()` for both. Source code in `src/docx/document.py` ``` def add_picture( self, image_path_or_stream: str | os.PathLike[str] | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, description: str | None = None, title: str | None = None, svg_fallback: str | os.PathLike[str] | IO[bytes] | None = None, honor_exif_orientation: bool = True, ): """Return new picture shape added in its own paragraph at end of the document. The picture contains the image at `image_path_or_stream`, scaled based on `width` and `height`. If neither width nor height is specified, the picture appears at its native size. If only one is specified, it is used to compute a scaling factor that is then applied to the unspecified dimension, preserving the aspect ratio of the image. The native size of the picture is calculated using the dots-per-inch (dpi) value specified in the image file, defaulting to 72 dpi if no value is specified, as is often the case. `description` is the picture's alternative text, which is what a screen reader announces and what an accessibility check looks for; `title` is the separate caption-like field Word writes alongside it. `svg_fallback` is the raster image shown in place of an SVG wherever the vector source cannot be rendered, and `honor_exif_orientation` applies a photo's EXIF `Orientation` as a rotation in the DrawingML; see `Run.add_picture()` for both. """ run = self.add_paragraph().add_run() return run.add_picture( image_path_or_stream, width, height, description=description, title=title, svg_fallback=svg_fallback, honor_exif_orientation=honor_exif_orientation, ) ``` #### add_section ``` add_section(start_type: WD_SECTION = NEW_PAGE) ``` Return a Section object newly added at the end of the document. The optional `start_type` argument must be a member of the WdSectionStart enumeration, and defaults to `WD_SECTION.NEW_PAGE` if not provided. Source code in `src/docx/document.py` ``` def add_section(self, start_type: WD_SECTION = WD_SECTION.NEW_PAGE): """Return a |Section| object newly added at the end of the document. The optional `start_type` argument must be a member of the :ref:`WdSectionStart` enumeration, and defaults to ``WD_SECTION.NEW_PAGE`` if not provided. """ new_sectPr = self._element.body.add_section_break() new_sectPr.start_type = start_type return Section(new_sectPr, self._part) ``` #### add_table ``` add_table( rows: int, cols: int, style: str | _TableStyle | None = None, *, title: str | None = None, description: str | None = None, ) ``` Add a table having row and column counts of `rows` and `cols` respectively. `style` may be a table style object or a table style name. If `style` is `None`, the table inherits the default table style of the document. `description` is the table's alternative text, which is what a screen reader announces and what an accessibility check looks for. `title` is the separate, caption-like field Word writes alongside it. Both are omitted from the XML when `None`. Source code in `src/docx/document.py` ``` def add_table( self, rows: int, cols: int, style: str | _TableStyle | None = None, *, title: str | None = None, description: str | None = None, ): """Add a table having row and column counts of `rows` and `cols` respectively. `style` may be a table style object or a table style name. If `style` is |None|, the table inherits the default table style of the document. `description` is the table's alternative text, which is what a screen reader announces and what an accessibility check looks for. `title` is the separate, caption-like field Word writes alongside it. Both are omitted from the XML when |None|. """ table = self._body.add_table( rows, cols, self._block_width, title=title, description=description ) table.style = style return table ``` #### bookmarks ``` bookmarks() -> Bookmarks ``` The Bookmarks in this document, in document order. Bookmarks Word maintains for itself, such as `_GoBack` and the `_Toc…` anchors, are left out of the collection; reach them through `.iter_all()`. Source code in `src/docx/document.py` ``` @lazyproperty def bookmarks(self) -> Bookmarks: """The |Bookmarks| in this document, in document order. Bookmarks Word maintains for itself, such as `_GoBack` and the `_Toc…` anchors, are left out of the collection; reach them through `.iter_all()`. """ return Bookmarks(self._element, self._part) ``` #### cleanup ``` cleanup( *, styles: bool = True, numbering: bool = True, media: bool = True, latent_styles: bool = False, keep: Tuple[str, ...] = (), ) -> CleanupResult ``` Remove what this document carries that nothing points at; report what went. A document created by this library defines 164 styles and references one, and carries numbering definitions for lists it does not have: ``` >>> print(document.cleanup()) removed 152 styles, 3 numbering definitions, ... ``` Three separate kinds of dead weight, each with its own flag: unused style definitions, numbering definitions no content or style references, and image parts nothing in the document part refers to — the last of which the `.delete()` methods leave behind as a matter of course. **This is destructive.** For styles, the reachability closure in Styles.usage is the only thing standing between it and a document whose formatting has quietly changed; `keep` names styles to preserve along with their dependencies, for ones you plan to apply but have not yet. `latent_styles` is off by default and separate from `styles` on purpose: dropping a `w:lsdException` changes what a user sees in Word's style gallery rather than how the document renders. Source code in `src/docx/document.py` ``` def cleanup( self, *, styles: bool = True, numbering: bool = True, media: bool = True, latent_styles: bool = False, keep: Tuple[str, ...] = (), ) -> CleanupResult: """Remove what this document carries that nothing points at; report what went. A document created by this library defines 164 styles and references one, and carries numbering definitions for lists it does not have:: >>> print(document.cleanup()) removed 152 styles, 3 numbering definitions, ... Three separate kinds of dead weight, each with its own flag: unused style definitions, numbering definitions no content or style references, and image parts nothing in the document part refers to — the last of which the `.delete()` methods leave behind as a matter of course. **This is destructive.** For styles, the reachability closure in :meth:`.Styles.usage` is the only thing standing between it and a document whose formatting has quietly changed; `keep` names styles to preserve along with their dependencies, for ones you plan to apply but have not yet. `latent_styles` is off by default and separate from `styles` on purpose: dropping a `w:lsdException` changes what a user sees in Word's style gallery rather than how the document renders. """ from docx.cleanup import cleanup return cleanup( self._part, styles=styles, numbering=numbering, media=media, latent_styles=latent_styles, keep=keep, ) ``` #### add_custom_xml_part ``` add_custom_xml_part( xml: str | bytes, schema_refs: Tuple[str, ...] = (), *, item_id: str | None = None, ) -> CustomXmlPart ``` Add an item to the custom XML data store and return its part. The custom XML data store is where a document-generation pipeline keeps its data: whole XML documents against a caller-supplied schema, which content controls in the document bind to through `w:dataBinding` and Word keeps in step with what it displays: ``` document.add_custom_xml_part( "42.00", schema_refs=("urn:example:invoice",), ) ``` This is a different thing from custom_properties, which is a flat list of named scalars in `docProps/custom.xml`. A `customXml/itemN.xml` part is created for `xml`, along with the `itemPropsN.xml` sidecar Word identifies it by, carrying the namespaces named in `schema_refs` and a GUID. `item_id` is that GUID, in Word's `"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"` shape. One is generated at random when it is omitted, which is what Word does — but a random value is the one thing in this library's output that is not a function of its input, so pass an `item_id` of your own when byte-reproducible output matters. It only has to be unique within the document. Source code in `src/docx/document.py` ``` def add_custom_xml_part( self, xml: str | bytes, schema_refs: Tuple[str, ...] = (), *, item_id: str | None = None, ) -> CustomXmlPart: """Add an item to the custom XML data store and return its part. The custom XML data store is where a document-generation pipeline keeps its data: whole XML documents against a caller-supplied schema, which content controls in the document bind to through `w:dataBinding` and Word keeps in step with what it displays:: document.add_custom_xml_part( "42.00", schema_refs=("urn:example:invoice",), ) This is a different thing from :attr:`custom_properties`, which is a flat list of named scalars in `docProps/custom.xml`. A `customXml/itemN.xml` part is created for `xml`, along with the `itemPropsN.xml` sidecar Word identifies it by, carrying the namespaces named in `schema_refs` and a GUID. `item_id` is that GUID, in Word's `"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"` shape. One is generated at random when it is omitted, which is what Word does — but a random value is the one thing in this library's output that is not a function of its input, so pass an `item_id` of your own when byte-reproducible output matters. It only has to be unique within the document. """ return self._part.add_custom_xml_part(xml, schema_refs, item_id=item_id) ``` #### remove_vba_project ``` remove_vba_project() -> int ``` Remove this document's VBA project; return how many parts were removed. The `word/vbaData.xml` sibling, which holds command-bar and macro-name customisations, goes with it rather than being left orphaned. Zero for a document that carries no project. Equivalent to `del document.vba_project`. Source code in `src/docx/document.py` ``` def remove_vba_project(self) -> int: """Remove this document's VBA project; return how many parts were removed. The `word/vbaData.xml` sibling, which holds command-bar and macro-name customisations, goes with it rather than being left orphaned. Zero for a document that carries no project. Equivalent to ``del document.vba_project``. """ return self._part.remove_vba_project() ``` #### iter_inner_content ``` iter_inner_content() -> Iterator[Paragraph | Table] ``` Generate each `Paragraph` or `Table` in this document in document order. Source code in `src/docx/document.py` ``` def iter_inner_content(self) -> Iterator[Paragraph | Table]: """Generate each `Paragraph` or `Table` in this document in document order.""" return self._body.iter_inner_content() ``` #### replace_text ``` replace_text( old: str, new: str, *, count: int = -1, regex: bool = False, flags: int = 0, tables: bool = True, headers_footers: bool = False, footnotes: bool = False, ) -> int ``` Replace occurrences of `old` with `new` in this document; return how many. The match is made against each paragraph's text as a whole, so it succeeds whether or not Word split the text across runs; see Paragraph.replace_text for what happens to formatting. What gets searched is explicit rather than incidental, because "replace it everywhere" means different things to different callers and getting it wrong is invisible until someone reads the header: ``` document.replace_text("{{name}}", "Ada") # body only document.replace_text("{{name}}", "Ada", headers_footers=True) # and those ``` The document body, including tables unless `tables` is `False`, is always searched. Headers and footers of every section — default, first-page and even-page alike — are searched when `headers_footers` is `True`, and footnotes and endnotes when `footnotes` is `True`. Comments are never searched: a comment is somebody's remark about the document rather than part of it. `count` of -1 replaces every match; any other value limits the total across everything searched, in the order given above. `regex` and `flags` are as for Paragraph.replace_text. Source code in `src/docx/document.py` ``` def replace_text( self, old: str, new: str, *, count: int = -1, regex: bool = False, flags: int = 0, tables: bool = True, headers_footers: bool = False, footnotes: bool = False, ) -> int: """Replace occurrences of `old` with `new` in this document; return how many. The match is made against each paragraph's text as a whole, so it succeeds whether or not Word split the text across runs; see :meth:`.Paragraph.replace_text` for what happens to formatting. What gets searched is explicit rather than incidental, because "replace it everywhere" means different things to different callers and getting it wrong is invisible until someone reads the header:: document.replace_text("{{name}}", "Ada") # body only document.replace_text("{{name}}", "Ada", headers_footers=True) # and those The document body, including tables unless `tables` is |False|, is always searched. Headers and footers of every section — default, first-page and even-page alike — are searched when `headers_footers` is |True|, and footnotes and endnotes when `footnotes` is |True|. Comments are never searched: a comment is somebody's remark about the document rather than part of it. `count` of -1 replaces every match; any other value limits the total across everything searched, in the order given above. `regex` and `flags` are as for :meth:`.Paragraph.replace_text`. """ if count == 0: return 0 containers: List[BlockItemContainer] = [self._body] if headers_footers: for section in self.sections: # -- a header that inherits from the prior section has no definition of # -- its own; searching it would visit the inherited one a second time, # -- and merely reaching for it would create a part in the first section containers.extend( hdrftr for hdrftr in section.iter_headers_footers() if not hdrftr.is_linked_to_previous ) if footnotes and self._part.has_footnotes_part: containers.extend(self.footnotes) if footnotes and self._part.has_endnotes_part: containers.extend(self.endnotes) replaced = 0 for container in containers: replaced += container.replace_text( old, new, count=-1 if count < 0 else count - replaced, regex=regex, flags=flags, tables=tables, ) if count >= 0 and replaced >= count: break return replaced ``` #### save ``` save( path_or_stream: str | PathLike[str] | IO[bytes], as_template: bool | None = None, ) ``` Save this document to `path_or_stream`. `path_or_stream` can be either a path to a filesystem location (a string or `os.PathLike`) or a file-like object. `as_template` selects whether the result is a Word template (`.dotx` / `.dotm`) or an ordinary document (`.docx` / `.docm`). The default of `None` keeps whichever this document already is, so a template opened and saved is still a template. Pass `False` to generate a document from a template, or `True` to turn a document into one. Macro-enabled input stays macro-enabled either way. Note this sets the content type; it does not choose the file extension for you. Source code in `src/docx/document.py` ``` def save( self, path_or_stream: str | os.PathLike[str] | IO[bytes], as_template: bool | None = None, ): """Save this document to `path_or_stream`. `path_or_stream` can be either a path to a filesystem location (a string or ``os.PathLike``) or a file-like object. `as_template` selects whether the result is a Word template (``.dotx`` / ``.dotm``) or an ordinary document (``.docx`` / ``.docm``). The default of |None| keeps whichever this document already is, so a template opened and saved is still a template. Pass ``False`` to generate a document from a template, or ``True`` to turn a document into one. Macro-enabled input stays macro-enabled either way. Note this sets the content type; it does not choose the file extension for you. """ if as_template is not None: self._part.content_type = _document_content_type( self._part.content_type, as_template=as_template ) if isinstance(path_or_stream, os.PathLike): path_or_stream = os.fspath(path_or_stream) self._part.save(path_or_stream) ``` #### accept_all_revisions ``` accept_all_revisions() -> int ``` Accept every tracked change in the document body; return how many. Insertions become ordinary text, deletions go, formatting-change records are dropped leaving the current formatting, and a deleted paragraph mark merges its paragraph with the one after it. The result is the document as Paragraph.text already reads it. Source code in `src/docx/document.py` ``` def accept_all_revisions(self) -> int: """Accept every tracked change in the document body; return how many. Insertions become ordinary text, deletions go, formatting-change records are dropped leaving the current formatting, and a deleted paragraph mark merges its paragraph with the one after it. The result is the document as :attr:`.Paragraph.text` already reads it. """ from docx.revisions import apply_all return apply_all(self._element, self._part, accept=True) ``` #### reject_all_revisions ``` reject_all_revisions() -> int ``` Reject every tracked change in the document body; return how many. The reverse of accept_all_revisions: the result is the document as Paragraph.original_text reads it. Source code in `src/docx/document.py` ``` def reject_all_revisions(self) -> int: """Reject every tracked change in the document body; return how many. The reverse of :meth:`accept_all_revisions`: the result is the document as :attr:`.Paragraph.original_text` reads it. """ from docx.revisions import apply_all return apply_all(self._element, self._part, accept=False) ``` #### add_text_watermark ``` add_text_watermark( text: str, *, font: str = "Calibri", font_size: Length | int | None = None, color: str = "C0C0C0", opacity: float | None = None, angle: float = 315, width: Length | int = Pt(468), height: Length | int = Pt(234), bold: bool = False, italic: bool = False, ) -> List[Watermark] ``` Add a text watermark to the whole document, returning the watermarks added. The faint "DRAFT" or "CONFIDENTIAL" behind the content: ``` document.add_text_watermark("DRAFT") document.add_text_watermark("CONFIDENTIAL", color="FF0000", angle=0) ``` Every section is covered, and within each the default, first-page and even-page headers alike, so the watermark does not disappear on a page that uses a different header. A header shared between sections is written to once. The arguments are as for Section.add_text_watermark, which is also how a watermark is applied to one section rather than the whole document. Source code in `src/docx/document.py` ``` def add_text_watermark( self, text: str, *, font: str = "Calibri", font_size: Length | int | None = None, color: str = "C0C0C0", opacity: float | None = None, angle: float = 315, width: Length | int = Pt(468), height: Length | int = Pt(234), bold: bool = False, italic: bool = False, ) -> List[Watermark]: """Add a text watermark to the whole document, returning the watermarks added. The faint "DRAFT" or "CONFIDENTIAL" behind the content:: document.add_text_watermark("DRAFT") document.add_text_watermark("CONFIDENTIAL", color="FF0000", angle=0) Every section is covered, and within each the default, first-page and even-page headers alike, so the watermark does not disappear on a page that uses a different header. A header shared between sections is written to once. The arguments are as for :meth:`.Section.add_text_watermark`, which is also how a watermark is applied to one section rather than the whole document. """ from docx.watermark import add_text_watermark, iter_watermark_headers return add_text_watermark( iter_watermark_headers(self.sections), text, font=font, font_size=font_size, color=color, opacity=opacity, angle=angle, width=width, height=height, bold=bold, italic=italic, ) ``` #### add_image_watermark ``` add_image_watermark( image_path_or_stream: str | PathLike[str] | IO[bytes], *, width: Length | int | None = None, height: Length | int | None = None, washout: bool = True, scale: float = 1.0, ) -> List[Watermark] ``` Add an image watermark to the whole document; see add_text_watermark. `washout` applies Word's brightness-and-contrast correction, which is what makes a logo read as a background rather than sitting opaquely over the text. Source code in `src/docx/document.py` ``` def add_image_watermark( self, image_path_or_stream: str | os.PathLike[str] | IO[bytes], *, width: Length | int | None = None, height: Length | int | None = None, washout: bool = True, scale: float = 1.0, ) -> List[Watermark]: """Add an image watermark to the whole document; see :meth:`add_text_watermark`. `washout` applies Word's brightness-and-contrast correction, which is what makes a logo read as a background rather than sitting opaquely over the text. """ from docx.watermark import add_image_watermark, iter_watermark_headers return add_image_watermark( iter_watermark_headers(self.sections), image_path_or_stream, width=width, height=height, washout=washout, scale=scale, ) ``` #### remove_watermark ``` remove_watermark() -> int ``` Remove every watermark from the document, returning how many were removed. Source code in `src/docx/document.py` ``` def remove_watermark(self) -> int: """Remove every watermark from the document, returning how many were removed.""" from docx.watermark import iter_watermark_headers, remove_watermarks return remove_watermarks(iter_watermark_headers(self.sections)) ``` ### \_Body ``` _Body(body_elm: CT_Body, parent: ProvidesStoryPart) ``` Bases: `BlockItemContainer` Proxy for `` element in this document. It's primary role is a container for document content. Source code in `src/docx/document.py` ``` def __init__(self, body_elm: CT_Body, parent: t.ProvidesStoryPart): super(_Body, self).__init__(body_elm, parent) self._body = body_elm ``` #### clear_content ``` clear_content() -> _Body ``` Return this \_Body instance after clearing it of all content. Section properties for the main document story, if present, are preserved. Source code in `src/docx/document.py` ``` def clear_content(self) -> _Body: """Return this |_Body| instance after clearing it of all content. Section properties for the main document story, if present, are preserved. """ self._body.clear_content() return self ``` ## exceptions Exceptions used with python-docx. The base exception class is PythonDocxError. ### PythonDocxError Bases: `Exception` Generic error class. ### InvalidSpanError Bases: `PythonDocxError` Raised when an invalid merge region is specified in a request to merge table cells. ### InvalidXmlError Bases: `PythonDocxError` Raised when invalid XML is encountered, such as on attempt to access a missing required child element. ### StrictOoxmlNotSupportedError Bases: `PythonDocxError` Raised on opening an ISO/IEC 29500 Strict document. Word's "Strict Open XML Document" save format writes the same element names in the Strict namespaces (`http://purl.oclc.org/ooxml/...`) rather than the Transitional ones this library reads. The file is an ordinary-looking `.docx`, so without this the failure is an `AttributeError` naming an lxml internal, which says nothing about what is actually wrong or what to do about it. Strict is the default in some regulated and public-sector environments. Re-saving the file from Word as "Word Document (.docx)" produces the Transitional form. ## fields The Field object and the instruction builders that go with it. A field is how Word represents anything it works out for itself: page numbers, a table of contents, cross-references, captions that renumber, dates, and references to document properties. Every one of those is the same feature with a different instruction string. Word writes a field in one of two forms. A **simple field** is self-contained — the instruction is an attribute of `w:fldSimple` and the cached result is its content: ``` 7 ``` A **complex field** is spread across sibling runs, delimited by field characters: ``` PAGE 7 ``` Both forms are read here and Field presents them the same way. Complex fields nest — a `TOC` result is full of `PAGEREF` fields — and the nesting is tracked, so an inner field is a field in its own right and its text also counts towards the outer field's result. **This library cannot compute a field result.** A table of contents added here is empty, a `PAGE` field has no number, and a cross-reference shows nothing, because all three depend on how Word lays the document out. Fields are written with `w:dirty="true"` so Word refreshes them when it opens the document; setting Settings.update_fields_on_open asks it to refresh every field in the document, which is what a generated table of contents needs. No amount of API changes this. ### Field ``` Field( element: CT_SimpleField | CT_FldChar, parent: ProvidesStoryPart, instruction: str = "", result_text: str = "", ) ``` Bases: `StoryChild` A field in a document — a page number, a table of contents, a cross-reference. Not constructed directly; reached through Paragraph.fields, Document.fields or as the return value of Paragraph.add_field. Source code in `src/docx/fields.py` ``` def __init__( self, element: CT_SimpleField | CT_FldChar, parent: t.ProvidesStoryPart, instruction: str = "", result_text: str = "", ): super().__init__(parent) self._element = element self._instruction = instruction self._result_text = result_text ``` #### dirty ``` dirty: bool ``` True when Word will refresh this field the next time it opens the document. Read/write. A field written by this library is dirty by default, since its cached result is empty and only Word can fill it in. #### instruction ``` instruction: str ``` The field instruction, e.g. `' TOC \\o "1-3" \\h '`. This is the whole instruction including its switches, with the surrounding spaces Word writes. It is the concatenation of every `w:instrText` of a complex field, so an instruction Word split across runs reads as one string here. #### is_simple ``` is_simple: bool ``` True when this is a `w:fldSimple` rather than a complex field. #### result_text ``` result_text: str ``` The result Word last rendered for this field, the empty string if none. A field this library has just added has no result: only Word can compute one. #### text ``` text: str ``` The text this field displays, which is its result_text. #### type ``` type: str | None ``` The field type in upper case — `"PAGE"`, `"TOC"`, `"REF"` — or `None`. This is the first token of the instruction. `None` when the instruction is empty or begins with a switch, which is malformed but does occur. A plain string rather than an enumeration: ISO/IEC 29500 defines around ninety field types and Word accepts more, so a closed set would reject valid documents. ### \_ComplexFieldBuilder ``` _ComplexFieldBuilder(begin: CT_FldChar, position: int) ``` Accumulates the parts of one complex field while its subtree is walked. Source code in `src/docx/fields.py` ``` def __init__(self, begin: CT_FldChar, position: int): self.begin = begin self.position = position self.separate_seen = False self.instruction_parts: List[str] = [] self.result_parts: List[str] = [] ``` ### iter_fields ``` iter_fields( element: _Element, parent: ProvidesStoryPart ) -> Iterator[Field] ``` Generate a Field for each field in the subtree of `element`. Fields appear in document order, outermost first. A complex field nested inside another — a `PAGEREF` in a table-of-contents entry — is generated in its own right, after the field containing it. A complex field with no "end" field-character is malformed and is skipped rather than raising: such a document exists in the wild and reading the fields that are well-formed is more useful than refusing the whole document. Source code in `src/docx/fields.py` ``` def iter_fields(element: _Element, parent: t.ProvidesStoryPart) -> Iterator[Field]: """Generate a |Field| for each field in the subtree of `element`. Fields appear in document order, outermost first. A complex field nested inside another — a `PAGEREF` in a table-of-contents entry — is generated in its own right, after the field containing it. A complex field with no "end" field-character is malformed and is skipped rather than raising: such a document exists in the wild and reading the fields that are well-formed is more useful than refusing the whole document. """ stack: List[_ComplexFieldBuilder] = [] complete: List[tuple[int, Field]] = [] for position, node in enumerate(cast("List[_Element]", element.xpath(_FIELD_NODE_XPATH))): tag = node.tag if tag == qn("w:fldSimple"): simple = cast("CT_SimpleField", node) # -- read `w:instr` off the attribute rather than through the declared # -- required attribute, which raises for the malformed `w:fldSimple` # -- lacking one; a missing instruction is an empty one here -- instr = node.get(qn("w:instr")) or "" complete.append((position, Field(simple, parent, instr, simple.result_text))) continue if tag == qn("w:fldChar"): fldChar = cast("CT_FldChar", node) fldCharType = node.get(qn("w:fldCharType")) if fldCharType == "begin": stack.append(_ComplexFieldBuilder(fldChar, position)) elif fldCharType == "separate": if stack: stack[-1].separate_seen = True elif fldCharType == "end" and stack: builder = stack.pop() complete.append((builder.position, builder.build(parent))) continue if tag == qn("w:instrText"): # -- an instruction belongs to the innermost field that has not yet reached # -- its "separate"; past that point the field is showing its result -- for builder in reversed(stack): if not builder.separate_seen: builder.instruction_parts.append(str(node)) break continue # -- a text-bearing element: part of the result of every enclosing field that # -- has passed its "separate" marker -- for builder in stack: if builder.separate_seen: builder.result_parts.append(str(node)) return (field for _, field in sorted(complete, key=lambda pair: pair[0])) ``` ### new_complex_field ``` new_complex_field( instruction: str, dirty: bool = True ) -> List[_Element] ``` The runs of a complex field for `instruction`, ready to append to a paragraph. No "separate" field-character is written, because there is no cached result to put after one; Word adds both when it computes the result. `dirty` sets `w:dirty` on the "begin" field-character, asking Word to refresh the field when it opens the file. Source code in `src/docx/fields.py` ``` def new_complex_field(instruction: str, dirty: bool = True) -> List[_Element]: """The runs of a complex field for `instruction`, ready to append to a paragraph. No "separate" field-character is written, because there is no cached result to put after one; Word adds both when it computes the result. `dirty` sets `w:dirty` on the "begin" field-character, asking Word to refresh the field when it opens the file. """ begin = OxmlElement("w:fldChar") begin.set(qn("w:fldCharType"), "begin") if dirty: begin.set(qn("w:dirty"), "true") instrText = OxmlElement("w:instrText") instrText.set(qn("xml:space"), "preserve") instrText.text = instruction end = OxmlElement("w:fldChar") end.set(qn("w:fldCharType"), "end") runs: List[_Element] = [] for child in (begin, instrText, end): r = OxmlElement("w:r") r.append(child) runs.append(r) return runs ``` ### page_number ``` page_number() -> str ``` A `PAGE` field instruction — the number of the page the field is on. Source code in `src/docx/fields.py` ``` def page_number() -> str: """A `PAGE` field instruction — the number of the page the field is on.""" return " PAGE " ``` ### page_count ``` page_count() -> str ``` A `NUMPAGES` field instruction — the number of pages in the document. Source code in `src/docx/fields.py` ``` def page_count() -> str: """A `NUMPAGES` field instruction — the number of pages in the document.""" return " NUMPAGES " ``` ### table_of_contents ``` table_of_contents( levels: tuple[int, int] = (1, 3), hyperlinks: bool = True, use_outline_levels: bool = True, hide_tab_and_page_numbers_in_web: bool = True, ) -> str ``` A `TOC` field instruction, the switches matching what Word's own dialog writes. `levels` is the inclusive range of heading levels to include. `hyperlinks` makes each entry a link to its heading (`\h`), `use_outline_levels` includes paragraphs given an outline level without a heading style (`\u`), and `hide_tab_and_page_numbers_in_web` is Word's `\z`, which suppresses the leader and page number in web layout where there are no pages. The table is empty until Word builds it; see the module docstring. Source code in `src/docx/fields.py` ``` def table_of_contents( levels: tuple[int, int] = (1, 3), hyperlinks: bool = True, use_outline_levels: bool = True, hide_tab_and_page_numbers_in_web: bool = True, ) -> str: """A `TOC` field instruction, the switches matching what Word's own dialog writes. `levels` is the inclusive range of heading levels to include. `hyperlinks` makes each entry a link to its heading (`\\h`), `use_outline_levels` includes paragraphs given an outline level without a heading style (`\\u`), and `hide_tab_and_page_numbers_in_web` is Word's `\\z`, which suppresses the leader and page number in web layout where there are no pages. The table is empty until Word builds it; see the module docstring. """ first, last = levels if not 1 <= first <= last <= 9: raise ValueError(f"levels must be a range within 1-9, got {levels!r}") return " TOC {} ".format( _switches( f'\\o "{first}-{last}"', "\\h" if hyperlinks else None, "\\z" if hide_tab_and_page_numbers_in_web else None, "\\u" if use_outline_levels else None, ) ) ``` ### cross_reference ``` cross_reference( bookmark: str, hyperlink: bool = True, insert_paragraph_number: bool = False, ) -> str ``` A `REF` field instruction referring to `bookmark`. `hyperlink` makes the reference clickable (`\h`), and `insert_paragraph_number` shows the referenced paragraph's number rather than its text (`\n`). The bookmark must exist in the document, or Word displays "Error! Bookmark not defined." See Paragraph.add_bookmark for creating one. Source code in `src/docx/fields.py` ``` def cross_reference( bookmark: str, hyperlink: bool = True, insert_paragraph_number: bool = False ) -> str: """A `REF` field instruction referring to `bookmark`. `hyperlink` makes the reference clickable (`\\h`), and `insert_paragraph_number` shows the referenced paragraph's number rather than its text (`\\n`). The bookmark must exist in the document, or Word displays "Error! Bookmark not defined." See :meth:`.Paragraph.add_bookmark` for creating one. """ return " REF {} ".format( _switches( _quote(bookmark), "\\h" if hyperlink else None, "\\n" if insert_paragraph_number else None, ) ) ``` ### page_reference ``` page_reference( bookmark: str, hyperlink: bool = True ) -> str ``` A `PAGEREF` field instruction — the page number `bookmark` appears on. Source code in `src/docx/fields.py` ``` def page_reference(bookmark: str, hyperlink: bool = True) -> str: """A `PAGEREF` field instruction — the page number `bookmark` appears on.""" return " PAGEREF {} ".format(_switches(_quote(bookmark), "\\h" if hyperlink else None)) ``` ### sequence ``` sequence( name: str, restart_at_heading_level: int | None = None ) -> str ``` A `SEQ` field instruction — the caption numbering of the `name` series. `name` is the caption label, conventionally "Figure", "Table" or "Equation". Word numbers each series independently and renumbers the whole series when one is inserted, which is the point of using a field rather than a typed number. `restart_at_heading_level` restarts numbering at each heading of that level (`\s`), giving the "Figure 3-2" style of numbering. Source code in `src/docx/fields.py` ``` def sequence(name: str, restart_at_heading_level: int | None = None) -> str: """A `SEQ` field instruction — the caption numbering of the `name` series. `name` is the caption label, conventionally "Figure", "Table" or "Equation". Word numbers each series independently and renumbers the whole series when one is inserted, which is the point of using a field rather than a typed number. `restart_at_heading_level` restarts numbering at each heading of that level (`\\s`), giving the "Figure 3-2" style of numbering. """ return " SEQ {} ".format( _switches( _quote(name), "\\* ARABIC", None if restart_at_heading_level is None else f"\\s {restart_at_heading_level}", ) ) ``` ### date ``` date( format: str | None = None, save_date: bool = False ) -> str ``` A `DATE` field instruction, or `SAVEDATE` when `save_date` is `True`. `format` is a Word date-time picture such as `"d MMMM yyyy"`; the document's default format is used when it is omitted. Note a `DATE` field shows the date the document was *opened*, not the date it was generated — for a fixed date, write the text rather than a field. Source code in `src/docx/fields.py` ``` def date(format: str | None = None, save_date: bool = False) -> str: """A `DATE` field instruction, or `SAVEDATE` when `save_date` is |True|. `format` is a Word date-time picture such as ``"d MMMM yyyy"``; the document's default format is used when it is omitted. Note a `DATE` field shows the date the document was *opened*, not the date it was generated — for a fixed date, write the text rather than a field. """ keyword = "SAVEDATE" if save_date else "DATE" return " {} ".format(_switches(keyword, None if format is None else f'\\@ "{format}"')) ``` ### doc_property ``` doc_property(name: str) -> str ``` A `DOCPROPERTY` field instruction showing document property `name`. `name` is a built-in property such as `"Title"` or `"Author"`, or the name of a custom property; see Document.core_properties and Document.custom_properties. Source code in `src/docx/fields.py` ``` def doc_property(name: str) -> str: """A `DOCPROPERTY` field instruction showing document property `name`. `name` is a built-in property such as ``"Title"`` or ``"Author"``, or the name of a custom property; see :attr:`.Document.core_properties` and :attr:`.Document.custom_properties`. """ return f" DOCPROPERTY {_quote(name)} " ``` ### styleref ``` styleref( style_name: str, search_from_bottom: bool = False ) -> str ``` A `STYLEREF` field instruction showing the nearest text in `style_name`. This is how a running header repeats the current chapter title. Source code in `src/docx/fields.py` ``` def styleref(style_name: str, search_from_bottom: bool = False) -> str: """A `STYLEREF` field instruction showing the nearest text in `style_name`. This is how a running header repeats the current chapter title. """ return " STYLEREF {} ".format( _switches(_quote(style_name), "\\l" if search_from_bottom else None) ) ``` ## footnotes Collections providing access to the footnotes and endnotes of the document. Footnotes and endnotes are the same feature placed differently — a footnote at the foot of its page, an endnote at the end of the document or section. They share a complex type in the schema and share their implementation here; see `docx.oxml.footnotes`. ### \_Notes ``` _Notes( notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart ) ``` Common behavior of the Footnotes and Endnotes collections. Only the notes an author wrote are in the collection. Word keeps two more in the same part, at ids -1 and 0, which hold the rule it draws above the note area and its continuation on the next page; those are structural and never appear here. Source code in `src/docx/footnotes.py` ``` def __init__(self, notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart): self._notes_elm = notes_elm self._notes_part = notes_part ``` ### \_Note ``` _Note(note_elm: CT_FtnEdn, notes_part: StoryPart) ``` Bases: `BlockItemContainer` Common behavior of the Footnote and Endnote proxies. A note is a block-item container, like a table cell, so it can hold both paragraphs and tables and its paragraphs can hold rich text, hyperlinks and images. The common case is a single paragraph of plain text. Source code in `src/docx/footnotes.py` ``` def __init__(self, note_elm: CT_FtnEdn, notes_part: StoryPart): super().__init__(note_elm, notes_part) self._note_elm = note_elm ``` #### text ``` text: str ``` The text content of this note as a string. Only content in paragraphs is included, and all emphasis and styling is stripped. Paragraph boundaries are indicated with a newline (`"\\n"`). #### add_paragraph ``` add_paragraph( text: str = "", style: str | ParagraphStyle | None = None, ) -> Paragraph ``` Return a paragraph newly added to the end of this note. The paragraph holds `text` in a single run if present and is given paragraph style `style`. When `style` is omitted or `None`, the style Word uses for this kind of note's content is applied — "FootnoteText" or "EndnoteText". Source code in `src/docx/footnotes.py` ``` def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph: """Return a paragraph newly added to the end of this note. The paragraph holds `text` in a single run if present and is given paragraph style `style`. When `style` is omitted or |None|, the style Word uses for this kind of note's content is applied — "FootnoteText" or "EndnoteText". """ paragraph = super().add_paragraph(text, style) # -- assign the style directly to the element, since `paragraph.style` raises # -- when the style is not defined in the styles part and Word supplies this one # -- as a latent style if style is None: paragraph._p.style = self._para_style # pyright: ignore[reportPrivateUsage] return paragraph ``` ### Footnote ``` Footnote(note_elm: CT_FtnEdn, notes_part: StoryPart) ``` Bases: `_Note` Proxy for a single footnote in the document. Source code in `src/docx/footnotes.py` ``` def __init__(self, note_elm: CT_FtnEdn, notes_part: StoryPart): super().__init__(note_elm, notes_part) self._note_elm = note_elm ``` #### footnote_id ``` footnote_id: int ``` The identifier a `w:footnoteReference` uses to cite this footnote. ### Endnote ``` Endnote(note_elm: CT_FtnEdn, notes_part: StoryPart) ``` Bases: `_Note` Proxy for a single endnote in the document. Source code in `src/docx/footnotes.py` ``` def __init__(self, note_elm: CT_FtnEdn, notes_part: StoryPart): super().__init__(note_elm, notes_part) self._note_elm = note_elm ``` #### endnote_id ``` endnote_id: int ``` The identifier a `w:endnoteReference` uses to cite this endnote. ### Footnotes ``` Footnotes( notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart ) ``` Bases: `_Notes` Collection containing the footnotes of this document. Only the footnotes an author wrote are in the collection. Word keeps two more in the same part, at ids -1 and 0, which hold the rule it draws above the footnote area and its continuation on the next page; those are structural and never appear here. Source code in `src/docx/footnotes.py` ``` def __init__(self, notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart): self._notes_elm = notes_elm self._notes_part = notes_part ``` #### add_footnote ``` add_footnote(text: str = '') -> Footnote ``` Add a new footnote to the document and return it. The footnote is added to the end of the footnotes collection and is assigned an id unique within it. Adding it does not place a reference to it in the body text; use `Run.add_footnote_reference()` for that. A footnote no run references does not appear in the rendered document. If `text` is provided it is added to the footnote, after the reference mark that Word renders as the footnote number. Multiple paragraphs can be added by separating their text with newlines (`"\\n"`); between newlines, text is interpreted as it is in `Document.add_paragraph(text=...)`. The default is a footnote holding only the reference mark, to which runs can be added with `footnote.paragraphs[0].add_run()` and further paragraphs with `.add_paragraph()`. Source code in `src/docx/footnotes.py` ``` def add_footnote(self, text: str = "") -> Footnote: """Add a new footnote to the document and return it. The footnote is added to the end of the footnotes collection and is assigned an id unique within it. Adding it does not place a reference to it in the body text; use `Run.add_footnote_reference()` for that. A footnote no run references does not appear in the rendered document. If `text` is provided it is added to the footnote, after the reference mark that Word renders as the footnote number. Multiple paragraphs can be added by separating their text with newlines (`"\\\\n"`); between newlines, text is interpreted as it is in `Document.add_paragraph(text=...)`. The default is a footnote holding only the reference mark, to which runs can be added with `footnote.paragraphs[0].add_run()` and further paragraphs with `.add_paragraph()`. """ return self._add_note(text) # pyright: ignore[reportReturnType] ``` #### get ``` get(footnote_id: int) -> Footnote | None ``` The footnote identified by `footnote_id`, or `None` if there is none. `None` is also returned for the ids of Word's structural separator footnotes, which are not footnotes of this document in any sense an author would mean. Source code in `src/docx/footnotes.py` ``` def get(self, footnote_id: int) -> Footnote | None: """The footnote identified by `footnote_id`, or |None| if there is none. |None| is also returned for the ids of Word's structural separator footnotes, which are not footnotes of this document in any sense an author would mean. """ return self._get(footnote_id) # pyright: ignore[reportReturnType] ``` ### Endnotes ``` Endnotes( notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart ) ``` Bases: `_Notes` Collection containing the endnotes of this document. The endnote counterpart of Footnotes, and identical to it in behavior. Where an endnote appears — end of section or end of document — is a document setting, in `w:sectPr/w:endnotePr` and `w:settings/w:endnotePr`, and is not exposed here. Source code in `src/docx/footnotes.py` ``` def __init__(self, notes_elm: _CT_FtnEdnCollection, notes_part: StoryPart): self._notes_elm = notes_elm self._notes_part = notes_part ``` #### add_endnote ``` add_endnote(text: str = '') -> Endnote ``` Add a new endnote to the document and return it. As for `Footnotes.add_footnote()`: the endnote is given an id unique within the collection, and adding it does not place a reference to it in the body text — use `Run.add_endnote_reference()` for that. An endnote no run references does not appear in the rendered document. Source code in `src/docx/footnotes.py` ``` def add_endnote(self, text: str = "") -> Endnote: """Add a new endnote to the document and return it. As for `Footnotes.add_footnote()`: the endnote is given an id unique within the collection, and adding it does not place a reference to it in the body text — use `Run.add_endnote_reference()` for that. An endnote no run references does not appear in the rendered document. """ return self._add_note(text) # pyright: ignore[reportReturnType] ``` #### get ``` get(endnote_id: int) -> Endnote | None ``` The endnote identified by `endnote_id`, or `None` if there is none. Source code in `src/docx/footnotes.py` ``` def get(self, endnote_id: int) -> Endnote | None: """The endnote identified by `endnote_id`, or |None| if there is none.""" return self._get(endnote_id) # pyright: ignore[reportReturnType] ``` ## formfield The FormField object, a legacy Word form field. ### FormField ``` FormField(fldChar: CT_FldChar, parent: ProvidesStoryPart) ``` Bases: `StoryChild` A legacy form field — a text input, check box or drop-down. Word writes a form field as a complex field: a "begin" field-character carrying the field properties in `w:ffData`, the field instruction, a "separate" field-character, the current value, and an "end" field-character, each in its own run. This object proxies the "begin" field-character and reaches the rest through it. Legacy form fields are what Word's Developer ribbon calls "Legacy Forms". They are distinct from content controls (`w:sdt`), which ContentControl covers. The whole field is expected to sit in one paragraph, which is how Word writes a legacy form field — it does not let a paragraph break be typed into one. Reading or writing value on a field whose "end" field-character is in a later paragraph raises InvalidXmlError rather than returning a partial value. Source code in `src/docx/formfield.py` ``` def __init__(self, fldChar: CT_FldChar, parent: t.ProvidesStoryPart): super().__init__(parent) self._element = self._fldChar = fldChar ``` #### calc_on_exit ``` calc_on_exit: bool | None ``` Whether Word recalculates its fields when this one is left. `None` when the document does not say, which Word treats as `False`. #### default ``` default: str | bool | None ``` The value this field starts out holding, `None` when it has no default. A `bool` for a check box, the text for a text input, and the selected entry for a drop-down, matching value. Assigning an entry a drop-down does not offer raises `ValueError`, as it does for value; assigning `None` removes the default. #### enabled ``` enabled: bool | None ``` Whether the field can be edited. `None` when the document does not say, which Word treats as enabled. #### help_text ``` help_text: str | None ``` The text Word shows when F1 is pressed in this field, or `None`. #### items ``` items: tuple[str, ...] ``` The entries of a drop-down field, in the order Word lists them. Empty for a field that is not a drop-down. #### max_length ``` max_length: int | None ``` The most characters a text field accepts, `None` when unlimited. `None` for a field that is not a text input. #### name ``` name: str | None ``` The bookmark name Word knows this field by, or `None` when it has none. This is the name shown in the "Bookmark" box of the form-field properties dialog and the one a `REF` field or a macro would use. #### status_text ``` status_text: str | None ``` The text Word shows in the status bar for this field, or `None`. #### text_type ``` text_type: WD_TEXT_FORM_FIELD_TYPE | None ``` Member of WdTextFormFieldType a text field accepts, or `None`. `None` both for a field that is not a text input and for a text input that does not say, which Word treats as `REGULAR_TEXT`. #### type ``` type: WD_FORM_FIELD_TYPE ``` Member of `WdFormFieldType` telling what kind of field this is. #### value ``` value: str | bool ``` The value this field currently holds. A `bool` for a check box; for a drop-down the selected entry, the empty string when nothing is selected; for a text input the result text Word last rendered, which is the empty string for an empty field. Note that Word renders an empty text field as five spaces or similar filler text; that filler is what this returns, because it is what the document contains. Compare against default to tell an untouched field apart. ### iter_form_fields ``` iter_form_fields( element: _Element, parent: ProvidesStoryPart ) -> Iterator[FormField] ``` Generate a FormField for each legacy form field in the subtree of `element`. Source code in `src/docx/formfield.py` ``` def iter_form_fields(element: _Element, parent: t.ProvidesStoryPart) -> Iterator[FormField]: """Generate a |FormField| for each legacy form field in the subtree of `element`.""" for fldChar in cast("List[CT_FldChar]", element.xpath(_FORM_FIELD_XPATH)): yield FormField(fldChar, parent) ``` ## math The Math proxy, giving access to the equations in a document. ### Math ``` Math(oMath: CT_OMath, parent: ProvidesStoryPart) ``` Bases: `StoryChild` Proxy for an `m:oMath` element, one equation. Reached through Paragraph.math or Document.math. Word stores equations in OMML, a notation of its own with no overlap with the wordprocessing run content. This exposes the equation's XML and the characters in it; it deliberately does not model the notation, which would be a substantially larger piece of work with nothing to render the result. Source code in `src/docx/math.py` ``` def __init__(self, oMath: CT_OMath, parent: t.ProvidesStoryPart): super().__init__(parent) self._element = oMath self._oMath = oMath ``` #### text ``` text: str ``` The characters of this equation, with none of its structure. A fraction reads as its numerator followed by its denominator, a superscript as its base followed by its exponent. This is what a plain-text extraction can offer; it is not a rendering of the equation and will not round-trip. #### xml ``` xml: str ``` The OMML source of this equation. The reliable representation, and what to hand to anything that understands OMML — an XSLT to MathML, say. #### is_display ``` is_display: bool ``` `True` when this equation is displayed on a line of its own. Word wraps a display equation in an `m:oMathPara`; an inline one sits directly among the runs of its paragraph. ### iter_math ``` iter_math( element: BaseOxmlElement, parent: ProvidesStoryPart ) -> Iterator[Math] ``` Generate a Math for each equation under `element`, in document order. Both the inline `m:oMath` and the `m:oMath` children of a display `m:oMathPara` are found, and each is yielded once. Source code in `src/docx/math.py` ``` def iter_math(element: BaseOxmlElement, parent: t.ProvidesStoryPart) -> Iterator[Math]: """Generate a |Math| for each equation under `element`, in document order. Both the inline `m:oMath` and the `m:oMath` children of a display `m:oMathPara` are found, and each is yielded once. """ for oMath in element.iter(qn("m:oMath")): yield Math(oMath, parent) # pyright: ignore[reportArgumentType] ``` ### math_list ``` math_list( element: BaseOxmlElement, parent: ProvidesStoryPart ) -> List[Math] ``` The equations under `element`, in document order. Source code in `src/docx/math.py` ``` def math_list(element: BaseOxmlElement, parent: t.ProvidesStoryPart) -> List[Math]: """The equations under `element`, in document order.""" return list(iter_math(element, parent)) ``` ## numbering The list-numbering API — reading the number a list paragraph displays, and restarting. The number a reader sees against a list paragraph — "1.", "a)", "iii." — is nowhere in the document body. Word computes it from `numbering.xml` at display time, so anything converting a document to text, Markdown or HTML has to compute it too. That computation is compute_list_numbers, and it is the part of this module worth being careful about; the rest is a straightforward model over the numbering part. The model mirrors the two-level indirection described in `docx.oxml.numbering`: a NumberingDefinition is a `w:num`, a concrete list, and it resolves each of its nine NumberingLevel objects from the `w:abstractNum` it points at, with any `w:lvlOverride` of its own applied on top. Restarting a list means creating a second `w:num` on the same `w:abstractNum` carrying a `w:startOverride`, not resetting a counter; see Paragraph.restart_numbering. What is not computed here: a level whose format is one of the locale-specific ones — Japanese counting, Korean chosung and the rest — falls back to decimal, because rendering those correctly is a localisation problem rather than a document-model one. NumberingLevel.is_renderable says which is which. ### NumberingLevel ``` NumberingLevel( ilvl: int, lvl: CT_Lvl | None, start_override: int | None = None, ) ``` One of the nine levels of a list, with any instance overrides applied. Reached through NumberingDefinition.levels or NumberingDefinition.level. Source code in `src/docx/numbering.py` ``` def __init__(self, ilvl: int, lvl: CT_Lvl | None, start_override: int | None = None): self._ilvl = ilvl self._lvl = lvl self._start_override = start_override ``` #### ilvl ``` ilvl: int ``` The zero-based level number, 0 being the outermost. #### is_bullet ``` is_bullet: bool ``` True when this level shows a bullet rather than a number. #### is_renderable ``` is_renderable: bool ``` True when format_number can render this level's format faithfully. `False` for the locale-specific formats, where `format_number` falls back to decimal. Worth checking before presenting a computed number as authoritative. #### level_text ``` level_text: str ``` The pattern this level displays, e.g. `"%1."`. A `%n` is the counter of one-based level `n`. Defaults to `"%{ilvl+1}."`, which is what Word shows for a level that does not say. #### number_format ``` number_format: WD_NUMBER_FORMAT ``` Member of `WdNumberFormat` this level renders its counter as. `DECIMAL` when the level does not say, which is Word's default. #### restart_after_level ``` restart_after_level: int | None ``` The one-based level whose increment restarts this one. `None` means Word's default: restart whenever any higher level increments. 0 means never restart. #### start ``` start: int ``` The number this level counts from. A `w:startOverride` on the concrete list wins over the abstract definition's `w:start`, which is how a restarted list begins again at 1. Defaults to 1. #### style_id ``` style_id: str | None ``` The paragraph style linked to this level, or `None`. A paragraph with this style takes this level even with no `w:numPr` of its own. #### is_legal ``` is_legal: bool ``` True when this level renders every placeholder as decimal. Word's "legal numbering" option, which turns "1.a.i" into "1.1.1". #### suffix ``` suffix: str ``` What separates the number from the text: `"tab"`, `"space"` or `"nothing"`. `"tab"` when the level does not say, which is Word's default. #### indent ``` indent: Length | None ``` The left indent this level applies, or `None` when it sets none. #### hanging_indent ``` hanging_indent: Length | None ``` The hanging indent this level applies, or `None` when it sets none. This is what keeps the wrapped text of a list item lined up under the first line rather than under the bullet. #### set ``` set( *, start: int | None = None, number_format: WD_NUMBER_FORMAT | str | None = None, level_text: str | None = None, suffix: str | None = None, alignment: str | None = None, indent: Length | None = None, hanging_indent: Length | None = None, restart_after_level: int | None = None, style_id: str | None = None, is_legal: bool | None = None, ) -> NumberingLevel ``` Change this level's definition; return self for chaining. Only the arguments given are written, so a call sets what it names and leaves the rest of the level alone: ``` level.set(number_format=WD_NUMBER_FORMAT.LOWER_LETTER, level_text="%2)") ``` The change is made to the *abstract* definition, which is shared: every list pointing at it changes with it. Use Numbering.add_definition for a list of your own rather than editing a definition the document already had. Raises `ValueError` for a level that has no definition to write to — one of the nine levels an abstract definition does not define. Source code in `src/docx/numbering.py` ``` def set( self, *, start: int | None = None, number_format: WD_NUMBER_FORMAT | str | None = None, level_text: str | None = None, suffix: str | None = None, alignment: str | None = None, indent: Length | None = None, hanging_indent: Length | None = None, restart_after_level: int | None = None, style_id: str | None = None, is_legal: bool | None = None, ) -> NumberingLevel: """Change this level's definition; return self for chaining. Only the arguments given are written, so a call sets what it names and leaves the rest of the level alone:: level.set(number_format=WD_NUMBER_FORMAT.LOWER_LETTER, level_text="%2)") The change is made to the *abstract* definition, which is shared: every list pointing at it changes with it. Use :meth:`.Numbering.add_definition` for a list of your own rather than editing a definition the document already had. Raises |ValueError| for a level that has no definition to write to — one of the nine levels an abstract definition does not define. """ if self._lvl is None: raise ValueError( "level %d has no definition in this list; only levels the abstract" " definition defines can be changed" % self._ilvl ) lvl = self._lvl if start is not None: lvl.start = start if number_format is not None: lvl.num_fmt = number_format if level_text is not None: lvl.lvl_text = level_text if suffix is not None: lvl.suffix = suffix if alignment is not None: lvl.jc = alignment if restart_after_level is not None: lvl.lvl_restart = restart_after_level if style_id is not None: lvl.p_style = style_id if is_legal is not None: lvl.is_lgl = is_legal if indent is not None or hanging_indent is not None: pPr = lvl.get_or_add_pPr() if indent is not None: pPr.ind_left = indent # pyright: ignore[reportAttributeAccessIssue] if hanging_indent is not None: # -- a hanging indent is a negative first-line indent, which is how # -- `CT_PPr.first_line_indent` already spells it -- pPr.first_line_indent = Length( # pyright: ignore[reportAttributeAccessIssue] -hanging_indent ) return self ``` #### format_number ``` format_number(value: int) -> str ``` `value` rendered in this level's number format. Falls back to decimal for a format this library does not render; see is_renderable. Source code in `src/docx/numbering.py` ``` def format_number(self, value: int) -> str: """`value` rendered in this level's number format. Falls back to decimal for a format this library does not render; see :attr:`is_renderable`. """ fmt = self.number_format if fmt == WD_NUMBER_FORMAT.UPPER_ROMAN: return _to_roman(value) if fmt == WD_NUMBER_FORMAT.LOWER_ROMAN: return _to_roman(value).lower() if fmt == WD_NUMBER_FORMAT.UPPER_LETTER: return _to_letter(value) if fmt == WD_NUMBER_FORMAT.LOWER_LETTER: return _to_letter(value).lower() if fmt == WD_NUMBER_FORMAT.ORDINAL: return _to_ordinal(value) if fmt == WD_NUMBER_FORMAT.HEX: return format(value, "X") if fmt == WD_NUMBER_FORMAT.DECIMAL_ZERO: return f"{value:02d}" if fmt == WD_NUMBER_FORMAT.CHICAGO: return _CHICAGO[(value - 1) % len(_CHICAGO)] if value > 0 else str(value) if fmt == WD_NUMBER_FORMAT.NONE: return "" return str(value) ``` ### NumberingDefinition ``` NumberingDefinition(num: CT_Num, numbering: Numbering) ``` A concrete list — a `w:num` — and the levels it resolves to. Two definitions pointing at the same abstract definition are two independent sequences that happen to look alike; that is how a restarted list is represented. Source code in `src/docx/numbering.py` ``` def __init__(self, num: CT_Num, numbering: Numbering): self._num = num self._numbering = numbering ``` #### abstract_num_id ``` abstract_num_id: int | None ``` The id of the abstract definition this list takes its formatting from. #### num_id ``` num_id: int ``` The id a paragraph's `w:numPr/w:numId` refers to this list by. #### levels ``` levels: List[NumberingLevel] ``` The nine levels of this list, outermost first. #### level ``` level(ilvl: int) -> NumberingLevel ``` The level `ilvl` of this list, with any instance override applied. A level the definition says nothing about is still returned, carrying Word's defaults, because a paragraph can legitimately refer to it. Source code in `src/docx/numbering.py` ``` def level(self, ilvl: int) -> NumberingLevel: """The level `ilvl` of this list, with any instance override applied. A level the definition says nothing about is still returned, carrying Word's defaults, because a paragraph can legitimately refer to it. """ abstract = self._abstract_num lvl = None if abstract is None else abstract.lvl_having_ilvl(ilvl) start_override = None lvlOverride = self._num.lvlOverride_having_ilvl(ilvl) if lvlOverride is not None: start_override = lvlOverride.start_override if lvlOverride.lvl is not None: lvl = lvlOverride.lvl return NumberingLevel(ilvl, lvl, start_override) ``` ### Numbering ``` Numbering(numbering: CT_Numbering, part: DocumentPart) ``` Bases: `ElementProxy` The numbering definitions of a document. Reached through Document.numbering. Supports `len()`, iteration over the concrete list definitions, and lookup by `num_id`. Source code in `src/docx/numbering.py` ``` def __init__(self, numbering: CT_Numbering, part: DocumentPart): super().__init__(numbering) self._element = numbering self._part = part ``` #### get ``` get(num_id: int) -> NumberingDefinition | None ``` The list definition with `num_id`, or `None` when there is none. Source code in `src/docx/numbering.py` ``` def get(self, num_id: int) -> NumberingDefinition | None: """The list definition with `num_id`, or |None| when there is none.""" try: num = self._element.num_having_numId(num_id) except KeyError: return None return NumberingDefinition(num, self) ``` #### add_definition ``` add_definition( levels: Sequence[Mapping[str, object]] | None = None, *, multi_level_type: str | None = None, ) -> NumberingDefinition ``` Define a new list and return it. Until now a list could be *applied* and *restarted* but not defined, so a format the template did not already contain — `1)` where the template has `1.`, a custom bullet character, a particular per-level indent — meant hand-building `w:abstractNum` XML: ``` definition = document.numbering.add_definition([ {"number_format": WD_NUMBER_FORMAT.DECIMAL, "level_text": "%1)"}, {"number_format": WD_NUMBER_FORMAT.LOWER_LETTER, "level_text": "%2)"}, ]) paragraph.set_numbering(definition.num_id, level=0) ``` `levels` is one mapping per level, outermost first, of the keyword arguments NumberingLevel.set takes. A level given as an empty mapping takes the defaults. With `levels` of `None` the definition gets nine decimal levels, which is what Word's plain numbered list is; add_bulleted_definition and add_numbered_definition are the shorthands for the two common cases. `multi_level_type` is written to `w:multiLevelType` when given; Word uses it to decide how to present the list in its gallery and is content without it. A fresh `w:abstractNum` and a `w:num` pointing at it are created, both with ids free in this document. `w:nsid` and `w:tmpl` are deliberately not written — they are what Word uses to recognise a definition as one of its own gallery entries, and inventing values would claim a provenance this definition does not have. Raises `ValueError` for more than nine levels, which is all OOXML admits. Source code in `src/docx/numbering.py` ``` def add_definition( self, levels: Sequence[Mapping[str, object]] | None = None, *, multi_level_type: str | None = None, ) -> NumberingDefinition: """Define a new list and return it. Until now a list could be *applied* and *restarted* but not defined, so a format the template did not already contain — `1)` where the template has `1.`, a custom bullet character, a particular per-level indent — meant hand-building `w:abstractNum` XML:: definition = document.numbering.add_definition([ {"number_format": WD_NUMBER_FORMAT.DECIMAL, "level_text": "%1)"}, {"number_format": WD_NUMBER_FORMAT.LOWER_LETTER, "level_text": "%2)"}, ]) paragraph.set_numbering(definition.num_id, level=0) `levels` is one mapping per level, outermost first, of the keyword arguments :meth:`.NumberingLevel.set` takes. A level given as an empty mapping takes the defaults. With `levels` of |None| the definition gets nine decimal levels, which is what Word's plain numbered list is; :meth:`add_bulleted_definition` and :meth:`add_numbered_definition` are the shorthands for the two common cases. `multi_level_type` is written to `w:multiLevelType` when given; Word uses it to decide how to present the list in its gallery and is content without it. A fresh `w:abstractNum` and a `w:num` pointing at it are created, both with ids free in this document. `w:nsid` and `w:tmpl` are deliberately not written — they are what Word uses to recognise a definition as one of its own gallery entries, and inventing values would claim a provenance this definition does not have. Raises |ValueError| for more than nine levels, which is all OOXML admits. """ from docx.oxml.numbering import CT_AbstractNum if levels is None: levels = [{} for _ in range(_MAX_LEVELS)] elif len(levels) > _MAX_LEVELS: raise ValueError( "a list definition has at most %d levels, got %d" % (_MAX_LEVELS, len(levels)) ) abstract = CT_AbstractNum.new(self._next_abstract_num_id()) self._insert_abstract_num(abstract) if multi_level_type is not None: abstract.multi_level_type = multi_level_type for ilvl, spec in enumerate(levels): lvl = abstract.add_level(ilvl) NumberingLevel(ilvl, lvl).set( **{ "number_format": WD_NUMBER_FORMAT.DECIMAL, "level_text": "%%%d." % (ilvl + 1), "start": 1, **spec, # pyright: ignore[reportArgumentType] } ) num = self._element.add_num(abstract.abstractNumId) return NumberingDefinition(num, self) ``` #### add_numbered_definition ``` add_numbered_definition( depth: int = 9, *, formats: Sequence[WD_NUMBER_FORMAT] | None = None, indent_step: Length | None = None, ) -> NumberingDefinition ``` Define a decimal-with-sublevels list and return it. The tedious half of add_definition is assembling nine levels by hand, so this does it: each level shows its own counter followed by a period, in the format `formats` gives it, indented `indent_step` further than the level above. `formats` cycles when it is shorter than `depth`; the default of decimal, lower letter and lower roman is Word's familiar 1. / a. / i. alternation. For the cumulative "1.1.1" style, pass `levels` to add_definition with `level_text` of `"%1.%2."` and so on. `indent_step` defaults to a quarter inch, which is what Word uses. Raises `ValueError` for a `depth` above nine, as add_definition does. Source code in `src/docx/numbering.py` ``` def add_numbered_definition( self, depth: int = 9, *, formats: Sequence[WD_NUMBER_FORMAT] | None = None, indent_step: Length | None = None, ) -> NumberingDefinition: """Define a decimal-with-sublevels list and return it. The tedious half of :meth:`add_definition` is assembling nine levels by hand, so this does it: each level shows its own counter followed by a period, in the format `formats` gives it, indented `indent_step` further than the level above. `formats` cycles when it is shorter than `depth`; the default of decimal, lower letter and lower roman is Word's familiar 1. / a. / i. alternation. For the cumulative "1.1.1" style, pass `levels` to :meth:`add_definition` with `level_text` of ``"%1.%2."`` and so on. `indent_step` defaults to a quarter inch, which is what Word uses. Raises |ValueError| for a `depth` above nine, as :meth:`add_definition` does. """ from docx.shared import Inches if formats is None: formats = ( WD_NUMBER_FORMAT.DECIMAL, WD_NUMBER_FORMAT.LOWER_LETTER, WD_NUMBER_FORMAT.LOWER_ROMAN, ) step = Inches(0.25) if indent_step is None else indent_step levels: List[Dict[str, object]] = [] for ilvl in range(depth): levels.append( { "number_format": formats[ilvl % len(formats)], "level_text": "%%%d." % (ilvl + 1), "start": 1, "indent": Length(step * (ilvl + 2)), "hanging_indent": step, } ) return self.add_definition(levels, multi_level_type="multilevel") ``` #### add_bulleted_definition ``` add_bulleted_definition( depth: int = 9, *, bullets: Sequence[str] = ("•", "o", "§"), indent_step: Length | None = None, ) -> NumberingDefinition ``` Define a bulleted list and return it. `bullets` cycles when it is shorter than `depth`; the default is Word's own bullet, circle and square sequence. `indent_step` defaults to a quarter inch. Raises `ValueError` for a `depth` above nine. Note the bullet characters Word writes are glyphs of the Symbol and Wingdings fonts rather than the Unicode characters they resemble. The defaults here are the Unicode ones, which render in whatever font the paragraph uses and so do not depend on a font being installed. Source code in `src/docx/numbering.py` ``` def add_bulleted_definition( self, depth: int = 9, *, bullets: Sequence[str] = ("•", "o", "§"), indent_step: Length | None = None, ) -> NumberingDefinition: """Define a bulleted list and return it. `bullets` cycles when it is shorter than `depth`; the default is Word's own bullet, circle and square sequence. `indent_step` defaults to a quarter inch. Raises |ValueError| for a `depth` above nine. Note the bullet characters Word writes are glyphs of the Symbol and Wingdings fonts rather than the Unicode characters they resemble. The defaults here are the Unicode ones, which render in whatever font the paragraph uses and so do not depend on a font being installed. """ from docx.shared import Inches step = Inches(0.25) if indent_step is None else indent_step levels: List[Dict[str, object]] = [] for ilvl in range(depth): levels.append( { "number_format": WD_NUMBER_FORMAT.BULLET, "level_text": bullets[ilvl % len(bullets)], "indent": Length(step * (ilvl + 2)), "hanging_indent": step, } ) return self.add_definition(levels, multi_level_type="hybridMultilevel") ``` #### restart ``` restart( num_id: int, ilvl: int = 0, start: int = 1 ) -> NumberingDefinition ``` Return a new list definition restarting the list `num_id` at `start`. The new definition points at the same abstract definition, so it looks identical, and carries a `w:startOverride` for level `ilvl`. Assign its num_id to a paragraph to make the list begin again there; Paragraph.restart_numbering does that in one step. Raises `KeyError` if `num_id` names no list. Source code in `src/docx/numbering.py` ``` def restart(self, num_id: int, ilvl: int = 0, start: int = 1) -> NumberingDefinition: """Return a new list definition restarting the list `num_id` at `start`. The new definition points at the same abstract definition, so it looks identical, and carries a `w:startOverride` for level `ilvl`. Assign its :attr:`num_id` to a paragraph to make the list begin again there; :meth:`.Paragraph.restart_numbering` does that in one step. Raises |KeyError| if `num_id` names no list. """ source = self.get(num_id) if source is None: raise KeyError(f"no numbering definition with num_id {num_id}") abstract_num_id = source.abstract_num_id if abstract_num_id is None: raise KeyError(f"numbering definition {num_id} names no abstract definition") num = self._element.add_num(abstract_num_id) num.add_lvlOverride(ilvl).add_startOverride(start) return NumberingDefinition(num, self) ``` ### ParagraphNumbering ``` ParagraphNumbering( num_id: int, level: int, numbering: Numbering, from_style: bool, ) ``` The list membership of a paragraph — which list it is in, and at what level. Reached through Paragraph.numbering, which is `None` for a paragraph that is not in a list at all. Source code in `src/docx/numbering.py` ``` def __init__(self, num_id: int, level: int, numbering: Numbering, from_style: bool): self._num_id = num_id self._level = level self._numbering = numbering self._from_style = from_style ``` #### definition ``` definition: NumberingDefinition | None ``` The list this paragraph belongs to, `None` if `num_id` names none. #### from_style ``` from_style: bool ``` True when this numbering comes from the paragraph's style, not the paragraph. A paragraph numbered through its style has no `w:numPr` of its own, so changing its level means giving it one. #### level ``` level: int ``` The zero-based list level of this paragraph, 0 being the outermost. #### level_definition ``` level_definition: NumberingLevel | None ``` The NumberingLevel governing this paragraph, `None` if unresolvable. #### num_id ``` num_id: int ``` The id of the list this paragraph belongs to. ### get_paragraph_numbering ``` get_paragraph_numbering( p: CT_P, part: DocumentPart ) -> Tuple[int, int, bool] | None ``` `(num_id, ilvl, from_style)` for `p`, or `None` when it is not in a list. A direct `w:pPr/w:numPr` on the paragraph wins. Failing that the paragraph's style hierarchy is walked — a style's own `w:numPr`, then the style it is based on — which is how the built-in "List Number" and "List Bullet" styles number a paragraph that carries no numbering markup at all. `w:numId` of 0 means "explicitly not numbered" and is honoured as such: Word uses it to switch numbering off for a paragraph whose style would otherwise apply it. Source code in `src/docx/numbering.py` ``` def get_paragraph_numbering(p: CT_P, part: DocumentPart) -> Tuple[int, int, bool] | None: """`(num_id, ilvl, from_style)` for `p`, or |None| when it is not in a list. A direct `w:pPr/w:numPr` on the paragraph wins. Failing that the paragraph's style hierarchy is walked — a style's own `w:numPr`, then the style it is based on — which is how the built-in "List Number" and "List Bullet" styles number a paragraph that carries no numbering markup at all. `w:numId` of 0 means "explicitly not numbered" and is honoured as such: Word uses it to switch numbering off for a paragraph whose style would otherwise apply it. """ pPr = p.pPr if pPr is not None: numPr = pPr.numPr if numPr is not None: num_id = numPr.numId_val if num_id is not None: if num_id == 0: return None return num_id, numPr.ilvl_val or 0, False style_id = p.style if style_id is None: return None resolved = _numbering_from_style(style_id, part) if resolved is None: return None num_id, ilvl = resolved if num_id == 0: return None return num_id, ilvl, True ``` ### compute_list_numbers ``` compute_list_numbers( paragraphs: Iterator[CT_P], part: DocumentPart ) -> List[Tuple[CT_P, str]] ``` `(paragraph_element, number)` for each numbered paragraph, in document order. `paragraphs` must be every paragraph of the story in document order, since a list number depends on everything before it. Paragraphs that are not in a list contribute no entry. The elements are returned rather than used as dictionary keys because an lxml element proxy is created on demand and may be collected and its `id()` reused; holding the element in the result is what keeps the association valid. The counters follow ISO/IEC 29500 §17.9. For each numbered paragraph the counter of its own level increments, and every deeper level is restarted — unless its `w:lvlRestart` says otherwise, where 0 means never restart and *n* means restart only when the one-based level *n* increments. Counters are kept per `w:num` rather than per abstract definition, which is what makes two lists sharing a definition count independently and what makes a `w:startOverride` restart work. Source code in `src/docx/numbering.py` ``` def compute_list_numbers(paragraphs: Iterator[CT_P], part: DocumentPart) -> List[Tuple[CT_P, str]]: """`(paragraph_element, number)` for each numbered paragraph, in document order. `paragraphs` must be every paragraph of the story in document order, since a list number depends on everything before it. Paragraphs that are not in a list contribute no entry. The elements are returned rather than used as dictionary keys because an lxml element proxy is created on demand and may be collected and its `id()` reused; holding the element in the result is what keeps the association valid. The counters follow ISO/IEC 29500 §17.9. For each numbered paragraph the counter of its own level increments, and every deeper level is restarted — unless its `w:lvlRestart` says otherwise, where 0 means never restart and *n* means restart only when the one-based level *n* increments. Counters are kept per `w:num` rather than per abstract definition, which is what makes two lists sharing a definition count independently and what makes a `w:startOverride` restart work. """ if not part.has_numbering_part: # -- no numbering part means no list can resolve; say so without adding one -- return [] numbering = Numbering(part.numbering_part.element, part) # -- counters[num_id][ilvl] is the last number shown at that level. A level absent # -- from the dict has not started yet and takes its `start` value next. -- counters: Dict[int, Dict[int, int]] = {} numbers: List[Tuple[CT_P, str]] = [] for p in paragraphs: resolved = get_paragraph_numbering(p, part) if resolved is None: continue num_id, ilvl, _ = resolved definition = numbering.get(num_id) if definition is None: continue level = definition.level(ilvl) list_counters = counters.setdefault(num_id, {}) if ilvl in list_counters: list_counters[ilvl] += 1 else: list_counters[ilvl] = level.start _restart_deeper_levels(definition, list_counters, ilvl) numbers.append((p, _render(definition, level, list_counters, ilvl))) return numbers ``` ### iter_story_paragraphs ``` iter_story_paragraphs(element) -> Iterator[CT_P] ``` Generate every `w:p` in `element` in document order, tables included. List numbering counts paragraphs in the order Word lays them out, and a numbered paragraph inside a table cell counts towards the same list as one outside it. Source code in `src/docx/numbering.py` ``` def iter_story_paragraphs(element) -> Iterator[CT_P]: """Generate every `w:p` in `element` in document order, tables included. List numbering counts paragraphs in the order Word lays them out, and a numbered paragraph inside a table cell counts towards the same list as one outside it. """ for p in element.iter(qn("w:p")): yield p ``` ## object The EmbeddedObject proxy — a file embedded in a document as an OLE object. Word can embed a whole file inside a document and show it as an icon or a preview image that opens the original application on double-click. The read side matters on its own: for a document containing embedded attachments there was previously no way to discover that they exist, let alone extract them. This is a different thing from the two neighbouring features. `Document.add_alt_chunk()` imports content and dissolves it into the document when Word opens the file; an OLE object stays a distinct embedded file. `Run.add_picture()` embeds an image with no underlying document. ### EmbeddedObject ``` EmbeddedObject( object_elm: CT_Object, parent: ProvidesStoryPart ) ``` Bases: `StoryChild` An OLE object embedded in a run — a spreadsheet, a PDF, another document. Reached through Run.embedded_objects or Document.embedded_objects. Source code in `src/docx/object.py` ``` def __init__(self, object_elm: CT_Object, parent: t.ProvidesStoryPart): super().__init__(parent) self._element = object_elm self._object = object_elm ``` #### prog_id ``` prog_id: str | None ``` The application Word launches for this object, e.g. `"Excel.Sheet.12"`. `None` when the object names none. This is what tells Word which application to open; an object whose `ProgID` names nothing installed is one Word shows but cannot open. #### is_linked ``` is_linked: bool ``` `True` when the object links to an external file rather than embedding it. A linked object's bytes are not in the package, so blob is `None`. #### shows_icon ``` shows_icon: bool ``` `True` when Word shows this object as an icon rather than a preview. #### embedded_part ``` embedded_part: Part | None ``` The package part holding the embedded file, or `None`. `None` for a linked object, and for an embedded one whose relationship the document does not resolve — which is a broken document rather than an error here. #### blob ``` blob: bytes | None ``` The bytes of the embedded file, or `None` when there are none to give. This is the useful half — extracting an attachment from a document: ``` for obj in document.embedded_objects: if obj.blob is not None: Path(obj.filename or "attachment").write_bytes(obj.blob) ``` #### content_type ``` content_type: str | None ``` The content type of the embedded part, or `None` when there is no part. #### filename ``` filename: str | None ``` The partname's basename, e.g. `"oleObject1.bin"`, or `None`. OOXML does not record the original file name of an embedded object; this is the name of the part it landed in, which is what a caller extracting it has to work with. #### image ``` image: Image | None ``` The icon or preview image Word displays for this object, or `None`. Every OLE object has one — Word cannot render the embedded file itself — so `None` means the document is missing it rather than that the object has none. ### add_embedded_object ``` add_embedded_object( run: object, path_or_stream: str | PathLike[str] | IO[bytes], *, icon: str | PathLike[str] | IO[bytes], prog_id: str | None = None, width: Length | None = None, height: Length | None = None, ) -> EmbeddedObject ``` Embed `path_or_stream` in `run` as an OLE object; see Run.add_embedded_object. Source code in `src/docx/object.py` ``` def add_embedded_object( run: object, path_or_stream: str | os.PathLike[str] | IO[bytes], *, icon: str | os.PathLike[str] | IO[bytes], prog_id: str | None = None, width: Length | None = None, height: Length | None = None, ) -> EmbeddedObject: """Embed `path_or_stream` in `run` as an OLE object; see :meth:`.Run.add_embedded_object`.""" part = run.part # pyright: ignore[reportAttributeAccessIssue] package = part.package assert package is not None blob = _read_blob(path_or_stream) partname = package.next_partname("/word/embeddings/oleObject%d.bin") object_part = Part(partname, CT.OFC_OLE_OBJECT, blob, package) object_rId = part.relate_to(object_part, RT.OLE_OBJECT) icon_rId, icon_image = part.get_or_add_image(icon) cx = width if width is not None else icon_image.width cy = height if height is not None else icon_image.height ordinal = _next_shape_ordinal(part) # -- the shape id must be unique in the document, and `o:OLEObject/@ShapeID` names # -- it, so the two are generated together -- shape_id = "_x0000_i%04d" % ordinal object_elm = parse_xml( "\n" ' \n' ' \n' " \n" ' \n' "" % ( nsdecls("w", "v", "o", "r"), shape_id, Emu(cx).pt, Emu(cy).pt, icon_rId, prog_id or _DEFAULT_PROG_ID, shape_id, ordinal, object_rId, ) ) run._r.append(object_elm) # pyright: ignore[reportPrivateUsage] return EmbeddedObject(object_elm, run) # pyright: ignore[reportArgumentType] ``` ### iter_embedded_objects ``` iter_embedded_objects( element: object, parent: ProvidesStoryPart ) -> list[EmbeddedObject] ``` The EmbeddedObject instances under `element`, in document order. Source code in `src/docx/object.py` ``` def iter_embedded_objects( element: object, parent: t.ProvidesStoryPart ) -> list[EmbeddedObject]: """The |EmbeddedObject| instances under `element`, in document order.""" return [ EmbeddedObject(obj, parent) for obj in element.xpath(".//w:object") # pyright: ignore[reportAttributeAccessIssue] ] ``` ## package WordprocessingML Package class and related objects. ### Package Bases: `OpcPackage` Customizations specific to a WordprocessingML package. #### after_unmarshal ``` after_unmarshal() ``` Called by loading code after all parts and relationships have been loaded. This method affords the opportunity for any required post-processing. Source code in `src/docx/package.py` ``` def after_unmarshal(self): """Called by loading code after all parts and relationships have been loaded. This method affords the opportunity for any required post-processing. """ self._gather_image_parts() ``` #### get_or_add_image_part ``` get_or_add_image_part( image_descriptor: str | PathLike[str] | IO[bytes], ) -> ImagePart ``` Return ImagePart containing image specified by `image_descriptor`. The image-part is newly created if a matching one is not already present in the collection. Source code in `src/docx/package.py` ``` def get_or_add_image_part( self, image_descriptor: str | os.PathLike[str] | IO[bytes] ) -> ImagePart: """Return |ImagePart| containing image specified by `image_descriptor`. The image-part is newly created if a matching one is not already present in the collection. """ return self.image_parts.get_or_add_image_part(image_descriptor) ``` #### image_parts ``` image_parts() -> ImageParts ``` ImageParts collection object for this package. Source code in `src/docx/package.py` ``` @lazyproperty def image_parts(self) -> ImageParts: """|ImageParts| collection object for this package.""" return ImageParts() ``` ### ImageParts ``` ImageParts() ``` Collection of ImagePart objects corresponding to images in the package. Source code in `src/docx/package.py` ``` def __init__(self): self._image_parts: list[ImagePart] = [] ``` #### get_or_add_image_part ``` get_or_add_image_part( image_descriptor: str | PathLike[str] | IO[bytes], ) -> ImagePart ``` Return ImagePart object containing image identified by `image_descriptor`. The image-part is newly created if a matching one is not present in the collection. Source code in `src/docx/package.py` ``` def get_or_add_image_part( self, image_descriptor: str | os.PathLike[str] | IO[bytes] ) -> ImagePart: """Return |ImagePart| object containing image identified by `image_descriptor`. The image-part is newly created if a matching one is not present in the collection. """ image = Image.from_file(image_descriptor) matching_image_part = self._get_by_sha1(image.sha1) if matching_image_part is not None: return matching_image_part return self._add_image_part(image) ``` ## revisions The tracked-changes API — reading, accepting and rejecting revisions. Any document that has been through review carries revision markup, and before this existed the library's handling of it was silently wrong rather than loudly broken: inserted text was dropped, deleted text was dropped, and `Paragraph.text` returned something that matched neither the original nor the final version of the document. The text model is now defined: - Paragraph.text is the document **as it now reads** — every revision accepted. Insertions are included; deletions are not. This is what almost every caller wants and what makes `.text` consistent with what a reader sees with markup hidden. - Paragraph.original_text is the document **as it read before** the revisions. Deletions are included; insertions are not. Accepting a revision makes the first reading permanent; rejecting it makes the second. **In scope:** `w:ins`, `w:del`, `w:moveFrom` and `w:moveTo`, whether they wrap content, mark a paragraph mark as inserted or deleted (which is how a paragraph split or merge is tracked), or mark a table row; and the `*Change` elements recording a formatting change. **Not in scope:** `w:numberingChange`, and the cell-level merge revisions (`w:cellMerge`). Both are rare and neither has a well-defined accept that this library could perform without guessing. ### Revision ``` Revision( element: CT_TrackChange, parent: ProvidesStoryPart ) ``` Bases: `StoryChild` One tracked change — an insertion, a deletion, a move or a formatting change. Not constructed directly; reached through Document.revisions or Paragraph.revisions. Source code in `src/docx/revisions.py` ``` def __init__(self, element: CT_TrackChange, parent: t.ProvidesStoryPart): super().__init__(parent) self._element = element ``` #### author ``` author: str ``` The name of whoever made this change. The empty string when the document does not say, which is the case for a `w:tblGridChange` and for a document stripped of personal information. #### date ``` date: datetime | None ``` When the change was made, `None` when the document does not say. `None` too for an unparseable timestamp, which an anonymised document has. #### id ``` id: int | None ``` The `w:id` of this revision, `None` when it carries none. Unique among the revisions of a document when present. #### is_paragraph_mark ``` is_paragraph_mark: bool ``` True when this revision is of a paragraph mark rather than of content. An inserted paragraph mark is a paragraph split; a deleted one is a merge with the paragraph that follows. Accepting or rejecting one therefore joins or splits paragraphs rather than adding or removing text. #### is_row ``` is_row: bool ``` True when this revision marks a whole table row as inserted or deleted. #### text ``` text: str ``` The text this revision covers, the empty string when it covers none. Empty for a paragraph-mark revision, a row revision and a formatting change, none of which cover text of their own. #### type ``` type: WD_REVISION_TYPE ``` Member of WdRevisionType saying what kind of change this is. #### accept ``` accept() -> None ``` Keep this change, and remove the record of it. An insertion's content stays and stops being marked as new; a deletion's content goes; a formatting change's record goes, leaving the current formatting in place. Accepting a deleted paragraph mark merges the paragraph with the one after it, which is what the deletion recorded. Source code in `src/docx/revisions.py` ``` def accept(self) -> None: """Keep this change, and remove the record of it. An insertion's content stays and stops being marked as new; a deletion's content goes; a formatting change's record goes, leaving the current formatting in place. Accepting a deleted paragraph mark merges the paragraph with the one after it, which is what the deletion recorded. """ self._apply(accept=True) ``` #### reject ``` reject() -> None ``` Undo this change, and remove the record of it. An insertion's content goes; a deletion's content comes back, its `w:delText` turned back into `w:t`; a formatting change puts the recorded previous properties back. Rejecting an inserted paragraph mark merges the paragraph with the one after it, undoing the split. Source code in `src/docx/revisions.py` ``` def reject(self) -> None: """Undo this change, and remove the record of it. An insertion's content goes; a deletion's content comes back, its `w:delText` turned back into `w:t`; a formatting change puts the recorded previous properties back. Rejecting an inserted paragraph mark merges the paragraph with the one after it, undoing the split. """ self._apply(accept=False) ``` ### iter_revisions ``` iter_revisions( element: BaseOxmlElement, parent: ProvidesStoryPart ) -> Iterator[Revision] ``` Generate a Revision for each tracked change in the subtree of `element`. Source code in `src/docx/revisions.py` ``` def iter_revisions(element: BaseOxmlElement, parent: t.ProvidesStoryPart) -> Iterator[Revision]: """Generate a |Revision| for each tracked change in the subtree of `element`.""" for revision_elm in iter_revision_elements(element): yield Revision(revision_elm, parent) ``` ### apply_all ``` apply_all( element: BaseOxmlElement, parent: ProvidesStoryPart, accept: bool, ) -> int ``` Accept or reject every revision in `element`, returning how many were applied. Applied innermost-last and in reverse document order, so that unwrapping or removing one revision cannot invalidate another that has not been reached yet — a nested revision is dealt with before the one containing it. Source code in `src/docx/revisions.py` ``` def apply_all(element: BaseOxmlElement, parent: t.ProvidesStoryPart, accept: bool) -> int: """Accept or reject every revision in `element`, returning how many were applied. Applied innermost-last and in reverse document order, so that unwrapping or removing one revision cannot invalidate another that has not been reached yet — a nested revision is dealt with before the one containing it. """ revisions = list(iter_revisions(element, parent)) for revision in reversed(revisions): # -- a revision whose container was already removed is no longer in the tree -- if revision._element.getparent() is None: # pyright: ignore[reportPrivateUsage] continue if accept: revision.accept() else: revision.reject() return len(revisions) ``` ### collect_authors ``` collect_authors(revisions: List[Revision]) -> List[str] ``` The distinct authors of `revisions`, in the order they first appear. Source code in `src/docx/revisions.py` ``` def collect_authors(revisions: List[Revision]) -> List[str]: """The distinct authors of `revisions`, in the order they first appear.""" authors: List[str] = [] for revision in revisions: if revision.author not in authors: authors.append(revision.author) return authors ``` ## sdt The ContentControl proxy object for a structured document tag (`w:sdt`). ### ContentControl ``` ContentControl(sdt: CT_Sdt, parent: ProvidesStoryPart) ``` Bases: `Parented` Proxy for a `w:sdt` element, a structured document tag or "content control". Content controls are what Word uses for form fields in modern documents, for template placeholders, and for regions bound to a data source. Their content appears in `.paragraphs`, `.iter_inner_content()` and the rest of the read API as though the wrapper were not there; this object is how the wrapper itself is inspected. Source code in `src/docx/sdt.py` ``` def __init__(self, sdt: CT_Sdt, parent: t.ProvidesStoryPart): super(ContentControl, self).__init__(parent) self._element = self._sdt = sdt ``` #### alias ``` alias: str | None ``` The friendly name Word shows on this control, or `None` if not set. #### id ``` id: int | None ``` The numeric id of this control, or `None` if not set. #### is_block_level ``` is_block_level: bool ``` `True` when this control wraps block-level content. A block-level control contains paragraphs or tables; a run-level control sits inside a paragraph and contains runs. #### paragraphs ``` paragraphs: List[Paragraph] ``` The paragraphs directly inside this control, in document order. #### runs ``` runs: List[Run] ``` The runs directly inside this control, for a run-level control. Empty for a block-level control; the runs of such a control are reached through its paragraphs. #### showing_placeholder ``` showing_placeholder: bool ``` `True` when this control is currently displaying its placeholder text. The text of such a control is the prompt shown to the user, not a value they entered, which is worth distinguishing when harvesting values from a form. #### tables ``` tables: List[Table] ``` The tables directly inside this control, in document order. #### tag ``` tag: str | None ``` The programmatic identifier of this control, or `None` if not set. Unlike `.alias`, the tag is not shown to the user; it is what code binding to a template matches on. #### text ``` text: str ``` All the text inside this control. Paragraphs are separated by newlines, as for a table cell. #### type ``` type: WD_CONTENT_CONTROL_TYPE | None ``` Member of WdContentControlType, or `None` when the control names no kind. Word treats a control with no declared kind as rich text, but that is a default rather than a statement, so it is reported as `None` here. #### iter_inner_content ``` iter_inner_content() -> Iterator[Paragraph | Table] ``` Generate each Paragraph or Table in this control, in document order. Yields nothing for a run-level control; use `.runs` for one. Source code in `src/docx/sdt.py` ``` def iter_inner_content(self) -> Iterator[Paragraph | Table]: """Generate each |Paragraph| or |Table| in this control, in document order. Yields nothing for a run-level control; use `.runs` for one. """ from docx.table import Table from docx.text.paragraph import Paragraph sdtContent = self._sdt.sdtContent if sdtContent is None: return for element in iter_block_content(sdtContent): yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self)) ``` ## section The Section object and related proxy classes. ### \_PageBorders ``` _PageBorders(sectPr: CT_SectPr) ``` Bases: `_Borders` The border edges drawn around the pages of a section, `section.page_borders`. Beyond the four edges, `w:pgBorders` carries three of its own settings — see offset_from, display and z_order. Source code in `src/docx/section.py` ``` def __init__(self, sectPr: CT_SectPr): super().__init__(CT_PageBorders.edges) self._sectPr = sectPr ``` #### display ``` display: str | None ``` Which pages the border is drawn on, or `None` when not set. One of `"allPages"`, `"firstPage"` or `"notFirstPage"`. Word's default, when the attribute is absent, is all pages. #### offset_from ``` offset_from: str | None ``` What the border is measured from, `"page"` or `"text"`, or `None`. A certificate frame is measured from the page edge; a border that should track the text block is measured from the text. #### z_order ``` z_order: str | None ``` Whether the border is drawn `"front"` of or `"back"` of the page content. `None` when not set. ### Section ``` Section(sectPr: CT_SectPr, document_part: DocumentPart) ``` Document section, providing access to section and page setup settings. Also provides access to headers and footers. Source code in `src/docx/section.py` ``` def __init__(self, sectPr: CT_SectPr, document_part: DocumentPart): super(Section, self).__init__() self._sectPr = sectPr self._document_part = document_part ``` #### bidi ``` bidi: bool | None ``` `True` when the default base direction for this section is right-to-left. This is the section-level default; a paragraph's own `paragraph_format.bidi` overrides it. `None` means inherited, not `False`. #### text_direction ``` text_direction: WD_TEXT_DIRECTION | None ``` Default flow direction of the text in this section, or `None` when inherited. Distinct from bidi: this says which way the lines run and whether the glyphs are rotated, not which direction the text reads in. #### bottom_margin ``` bottom_margin: Length | None ``` Read/write. Bottom margin for pages in this section, in EMU. `None` when no bottom margin has been specified. Assigning `None` removes any bottom-margin setting. #### column_count ``` column_count: int ``` Read/write. The number of text columns in this section. 1 for an ordinary single-column section, which is also what an absent `w:cols` element means. Assigning a count leaves the columns of equal width; use set_column_widths for unequal ones. #### column_separator ``` column_separator: bool ``` Read/write. `True` if a vertical rule is drawn between the columns. #### column_spacing ``` column_spacing: Length | None ``` Read/write. The space between columns, in EMU, or `None` if not specified. For columns of unequal width this is the fallback; each column can carry its own spacing, given through set_column_widths. #### column_widths ``` column_widths: tuple[Length | None, ...] ``` The width of each column, when the columns are of unequal width. An empty tuple for equal-width columns, whose width Word derives from the page width, the margins and the column spacing rather than stating. #### different_first_page_header_footer ``` different_first_page_header_footer: bool ``` True if this section displays a distinct first-page header and footer. Read/write. The definition of the first-page header and footer are accessed using first_page_header and first_page_footer respectively. #### even_page_footer ``` even_page_footer: _Footer ``` \_Footer object defining footer content for even pages. The content of this footer definition is ignored unless the document setting odd_and_even_pages_header_footer is set True. #### even_page_header ``` even_page_header: _Header ``` \_Header object defining header content for even pages. The content of this header definition is ignored unless the document setting odd_and_even_pages_header_footer is set True. #### first_page_footer ``` first_page_footer: _Footer ``` \_Footer object defining footer content for the first page of this section. The content of this footer definition is ignored unless the property different_first_page_header_footer is set True. #### first_page_header ``` first_page_header: _Header ``` \_Header object defining header content for the first page of this section. The content of this header definition is ignored unless the property different_first_page_header_footer is set True. #### footer_distance ``` footer_distance: Length | None ``` Distance from bottom edge of page to bottom edge of the footer. Read/write. `None` if no setting is present in the XML. #### watermarks ``` watermarks: List[Watermark] ``` The watermarks in force for this section, in header order. Empty when the section has none. Ordinarily one per header type, all saying the same thing; they are separate objects because they are separate shapes in separate headers. #### gutter ``` gutter: Length | None ``` Length object representing page gutter size in English Metric Units. Read/write. The page gutter is extra spacing added to the `inner` margin to ensure even margins after page binding. Generally only used in book-bound documents with double-sided and facing pages. This setting applies to all pages in this section. #### header_distance ``` header_distance: Length | None ``` Distance from top edge of page to top edge of header. Read/write. `None` if no setting is present in the XML. Assigning `None` causes default value to be used. #### left_margin ``` left_margin: Length | None ``` Length object representing the left margin for all pages in this section in English Metric Units. #### orientation ``` orientation: WD_ORIENTATION ``` WdOrientation member specifying page orientation for this section. One of `WD_ORIENT.PORTRAIT` or `WD_ORIENT.LANDSCAPE`. Assigning a different orientation also exchanges page_width and page_height, so the page is actually rotated: ``` section.page_width, section.page_height # -- (8.5in, 11in) -- section.orientation = WD_ORIENT.LANDSCAPE section.page_width, section.page_height # -- (11in, 8.5in) -- ``` Underneath, `w:orient` and the `w:w`/`w:h` page dimensions are independent attributes, and setting only the first leaves a section declared landscape at portrait dimensions — which Word renders as portrait. Set page_width and page_height explicitly afterwards for a page size that is not simply the rotation of the current one. Margins (left_margin and friends) are not moved. #### page_height ``` page_height: Length | None ``` Total page height used for this section. This value is inclusive of all edge spacing values such as margins. Page orientation is taken into account, so for example, its expected value would be `Inches(8.5)` for letter-sized paper when orientation is landscape. #### page_width ``` page_width: Length | None ``` Total page width used for this section. This value is like "paper size" and includes all edge spacing values such as margins. Page orientation is taken into account, so for example, its expected value would be `Inches(11)` for letter-sized paper when orientation is landscape. #### right_margin ``` right_margin: Length | None ``` Length object representing the right margin for all pages in this section in English Metric Units. #### start_type ``` start_type: WD_SECTION_START ``` Type of page-break (if any) inserted at the start of this section. For exmple, `WD_SECTION_START.ODD_PAGE` if the section should begin on the next odd page, possibly inserting two page-breaks instead of one. #### top_margin ``` top_margin: Length | None ``` Length object representing the top margin for all pages in this section in English Metric Units. #### page_borders ``` page_borders() -> _PageBorders ``` The border edges drawn around the pages of this section: ``` section.page_borders["top"].line = WD_LINE_STYLE.DOUBLE section.page_borders.offset_from = "page" ``` This is how a certificate or a title page gets its frame. Only the four sides are admitted, unlike a paragraph's borders. Source code in `src/docx/section.py` ``` @lazyproperty def page_borders(self) -> _PageBorders: """The border edges drawn around the pages of this section:: section.page_borders["top"].line = WD_LINE_STYLE.DOUBLE section.page_borders.offset_from = "page" This is how a certificate or a title page gets its frame. Only the four sides are admitted, unlike a paragraph's borders. """ return _PageBorders(self._sectPr) ``` #### footer ``` footer() -> _Footer ``` \_Footer object representing default page footer for this section. The default footer is used for odd-numbered pages when separate odd/even footers are enabled. It is used for both odd and even-numbered pages otherwise. Source code in `src/docx/section.py` ``` @lazyproperty def footer(self) -> _Footer: """|_Footer| object representing default page footer for this section. The default footer is used for odd-numbered pages when separate odd/even footers are enabled. It is used for both odd and even-numbered pages otherwise. """ return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.PRIMARY) ``` #### add_text_watermark ``` add_text_watermark( text: str, *, font: str = "Calibri", font_size: Length | int | None = None, color: str = "C0C0C0", opacity: float | None = None, angle: float = 315, width: Length | int = Pt(468), height: Length | int = Pt(234), bold: bool = False, italic: bool = False, ) -> List[Watermark] ``` Add a text watermark to this section, returning the watermarks added. The watermark goes into all three header types — default, first-page and even-page — so it does not vanish on a page that uses a different header: ``` section.add_text_watermark("DRAFT") section.add_text_watermark("CONFIDENTIAL", color="FF0000", angle=0) ``` `color` is an RGB hex string; Word's own "Semitransparent" watermark is simply a light grey, which is the default here. `opacity` additionally sets true VML transparency, between 0 and 1. `angle` is the rotation in degrees, 315 giving Word's diagonal watermark and 0 a horizontal one. The text is stretched to fill a box of `width` by `height`, which is how Word sizes a watermark; the defaults are Word's own. Passing `font_size` renders the text at that size instead of stretching it. Where this section's headers are inherited from an earlier section, the watermark is written into the header actually in force, which that earlier section shares. An inherited header is the same header, so there is no way to mark up one section's copy of it alone without first setting `is_linked_to_previous = False`. Source code in `src/docx/section.py` ``` def add_text_watermark( self, text: str, *, font: str = "Calibri", font_size: Length | int | None = None, color: str = "C0C0C0", opacity: float | None = None, angle: float = 315, width: Length | int = Pt(468), height: Length | int = Pt(234), bold: bool = False, italic: bool = False, ) -> List[Watermark]: """Add a text watermark to this section, returning the watermarks added. The watermark goes into all three header types — default, first-page and even-page — so it does not vanish on a page that uses a different header:: section.add_text_watermark("DRAFT") section.add_text_watermark("CONFIDENTIAL", color="FF0000", angle=0) `color` is an RGB hex string; Word's own "Semitransparent" watermark is simply a light grey, which is the default here. `opacity` additionally sets true VML transparency, between 0 and 1. `angle` is the rotation in degrees, 315 giving Word's diagonal watermark and 0 a horizontal one. The text is stretched to fill a box of `width` by `height`, which is how Word sizes a watermark; the defaults are Word's own. Passing `font_size` renders the text at that size instead of stretching it. Where this section's headers are inherited from an earlier section, the watermark is written into the header actually in force, which that earlier section shares. An inherited header is the same header, so there is no way to mark up one section's copy of it alone without first setting `is_linked_to_previous = False`. """ from docx.watermark import add_text_watermark, iter_watermark_headers return add_text_watermark( iter_watermark_headers([self]), text, font=font, font_size=font_size, color=color, opacity=opacity, angle=angle, width=width, height=height, bold=bold, italic=italic, ) ``` #### add_image_watermark ``` add_image_watermark( image_path_or_stream: str | PathLike[str] | IO[bytes], *, width: Length | int | None = None, height: Length | int | None = None, washout: bool = True, scale: float = 1.0, ) -> List[Watermark] ``` Add an image watermark to this section, returning the watermarks added. As with add_text_watermark, all three header types get the watermark. `width` and `height` scale the image the same way Run.add_picture does, defaulting to its native size; `scale` multiplies whatever that works out to. `washout` applies Word's brightness-and-contrast correction, which is what turns a logo into a pale background image rather than an opaque one over the text. Source code in `src/docx/section.py` ``` def add_image_watermark( self, image_path_or_stream: str | os.PathLike[str] | IO[bytes], *, width: Length | int | None = None, height: Length | int | None = None, washout: bool = True, scale: float = 1.0, ) -> List[Watermark]: """Add an image watermark to this section, returning the watermarks added. As with :meth:`add_text_watermark`, all three header types get the watermark. `width` and `height` scale the image the same way :meth:`.Run.add_picture` does, defaulting to its native size; `scale` multiplies whatever that works out to. `washout` applies Word's brightness-and-contrast correction, which is what turns a logo into a pale background image rather than an opaque one over the text. """ from docx.watermark import add_image_watermark, iter_watermark_headers return add_image_watermark( iter_watermark_headers([self]), image_path_or_stream, width=width, height=height, washout=washout, scale=scale, ) ``` #### remove_watermark ``` remove_watermark() -> int ``` Remove every watermark from this section, returning how many were removed. Source code in `src/docx/section.py` ``` def remove_watermark(self) -> int: """Remove every watermark from this section, returning how many were removed.""" from docx.watermark import iter_watermark_headers, remove_watermarks return remove_watermarks(iter_watermark_headers([self])) ``` #### iter_headers_footers ``` iter_headers_footers() -> Iterator[_Header | _Footer] ``` Generate all six header and footer objects of this section. The default, first-page and even-page header come first, then the three footers. All six are generated whether or not they are in use: whether a first-page header is shown depends on different_first_page_header_footer, and whether it is defined here or inherited from the prior section is is_linked_to_previous. This is for code that needs to visit each of them, such as a document-wide search. Source code in `src/docx/section.py` ``` def iter_headers_footers(self) -> Iterator[_Header | _Footer]: """Generate all six header and footer objects of this section. The default, first-page and even-page header come first, then the three footers. All six are generated whether or not they are in use: whether a first-page header is shown depends on :attr:`different_first_page_header_footer`, and whether it is defined here or inherited from the prior section is :attr:`~._BaseHeaderFooter.is_linked_to_previous`. This is for code that needs to visit each of them, such as a document-wide search. """ yield self.header yield self.first_page_header yield self.even_page_header yield self.footer yield self.first_page_footer yield self.even_page_footer ``` #### header ``` header() -> _Header ``` \_Header object representing default page header for this section. The default header is used for odd-numbered pages when separate odd/even headers are enabled. It is used for both odd and even-numbered pages otherwise. Source code in `src/docx/section.py` ``` @lazyproperty def header(self) -> _Header: """|_Header| object representing default page header for this section. The default header is used for odd-numbered pages when separate odd/even headers are enabled. It is used for both odd and even-numbered pages otherwise. """ return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.PRIMARY) ``` #### iter_inner_content ``` iter_inner_content() -> Iterator[Paragraph | Table] ``` Generate each Paragraph or Table object in this `section`. Items appear in document order. Source code in `src/docx/section.py` ``` def iter_inner_content(self) -> Iterator[Paragraph | Table]: """Generate each Paragraph or Table object in this `section`. Items appear in document order. """ for element in self._sectPr.iter_inner_content(): yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self)) ``` #### set_column_widths ``` set_column_widths( widths: Sequence[Length], spacings: Sequence[Length] | None = None, ) -> None ``` Lay this section out in columns of the given `widths`. `spacings` gives the space following each column and defaults to the section's `.column_spacing` for every column. It must be the same length as `widths` when given; the value for the last column is written but has no visible effect. The equal-width case is the common one and is better expressed by assigning `.column_count`, which this replaces. Pass a single width to go back to one column. Source code in `src/docx/section.py` ``` def set_column_widths( self, widths: Sequence[Length], spacings: Sequence[Length] | None = None ) -> None: """Lay this section out in columns of the given `widths`. `spacings` gives the space following each column and defaults to the section's `.column_spacing` for every column. It must be the same length as `widths` when given; the value for the last column is written but has no visible effect. The equal-width case is the common one and is better expressed by assigning `.column_count`, which this replaces. Pass a single width to go back to one column. """ if not widths: raise ValueError("at least one column width is required") if spacings is not None and len(spacings) != len(widths): raise ValueError( "spacings must have one value per column, got %d for %d columns" % (len(spacings), len(widths)) ) cols = self._sectPr.get_or_add_cols() cols.clear_cols() cols.num = len(widths) cols.equalWidth = False for idx, width in enumerate(widths): col = cols.add_col() col.w = width if spacings is not None: col.space = spacings[idx] ``` ### Sections ``` Sections( document_elm: CT_Document, document_part: DocumentPart ) ``` Bases: `Sequence[Section]` Sequence of Section objects corresponding to the sections in the document. Supports `len()`, iteration, and indexed access. Source code in `src/docx/section.py` ``` def __init__(self, document_elm: CT_Document, document_part: DocumentPart): super(Sections, self).__init__() self._document_elm = document_elm self._document_part = document_part ``` ### \_BaseHeaderFooter ``` _BaseHeaderFooter( sectPr: CT_SectPr, document_part: DocumentPart, header_footer_index: WD_HEADER_FOOTER, ) ``` Bases: `BlockItemContainer` Base class for header and footer classes. Source code in `src/docx/section.py` ``` def __init__( self, sectPr: CT_SectPr, document_part: DocumentPart, header_footer_index: WD_HEADER_FOOTER, ): self._sectPr = sectPr self._document_part = document_part self._hdrftr_index = header_footer_index ``` #### is_linked_to_previous ``` is_linked_to_previous: bool ``` `True` if this header/footer uses the definition from the prior section. `False` if this header/footer has an explicit definition. Assigning `True` to this property removes the header/footer definition for this section, causing it to "inherit" the corresponding definition of the prior section. Assigning `False` causes a new, empty definition to be added for this section, but only if no definition is already present. #### part ``` part: HeaderPart | FooterPart ``` The HeaderPart or FooterPart for this header/footer. This overrides `BlockItemContainer.part` and is required to support image insertion and perhaps other content like hyperlinks. ### \_Footer ``` _Footer( sectPr: CT_SectPr, document_part: DocumentPart, header_footer_index: WD_HEADER_FOOTER, ) ``` Bases: `_BaseHeaderFooter` Page footer, used for all three types (default, even-page, and first-page). Note that, like a document or table cell, a footer must contain a minimum of one paragraph and a new or otherwise "empty" footer contains a single empty paragraph. This first paragraph can be accessed as `footer.paragraphs[0]` for purposes of adding content to it. Using `add_paragraph()` by itself to add content will leave an empty paragraph above the newly added one. Source code in `src/docx/section.py` ``` def __init__( self, sectPr: CT_SectPr, document_part: DocumentPart, header_footer_index: WD_HEADER_FOOTER, ): self._sectPr = sectPr self._document_part = document_part self._hdrftr_index = header_footer_index ``` ### \_Header ``` _Header( sectPr: CT_SectPr, document_part: DocumentPart, header_footer_index: WD_HEADER_FOOTER, ) ``` Bases: `_BaseHeaderFooter` Page header, used for all three types (default, even-page, and first-page). Note that, like a document or table cell, a header must contain a minimum of one paragraph and a new or otherwise "empty" header contains a single empty paragraph. This first paragraph can be accessed as `header.paragraphs[0]` for purposes of adding content to it. Using `add_paragraph()` by itself to add content will leave an empty paragraph above the newly added one. Source code in `src/docx/section.py` ``` def __init__( self, sectPr: CT_SectPr, document_part: DocumentPart, header_footer_index: WD_HEADER_FOOTER, ): self._sectPr = sectPr self._document_part = document_part self._hdrftr_index = header_footer_index ``` ## settings Settings object, providing access to document-level settings. ### Settings ``` Settings( element: BaseOxmlElement, parent: ProvidesXmlPart | None = None, ) ``` Bases: `ElementProxy` Provides access to document-level settings for a document. Accessed using the Document.settings property. Source code in `src/docx/settings.py` ``` def __init__(self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None): super().__init__(element, parent) self._settings = cast("CT_Settings", element) ``` #### odd_and_even_pages_header_footer ``` odd_and_even_pages_header_footer: bool ``` True if this document has distinct odd and even page headers and footers. Read/write. #### track_revisions ``` track_revisions: bool ``` True when Word records changes made to this document as tracked changes. Read/write. This is the "Track Changes" toggle. Setting it does not mark anything already in the document as a revision; it asks Word to record what happens from now on. #### update_fields_on_open ``` update_fields_on_open: bool ``` True when Word should recalculate every field when it opens this document. Read/write. This library cannot compute a field result — a table of contents added here is empty, and a `PAGE` field has no page number, because both depend on how Word lays the document out. Setting this asks Word to fill them in as soon as the document is opened, which is the only way to get a populated table of contents out of a generated document. Word prompts the reader before updating when the document has a table of contents, so a document saved with this set may show that prompt once. ## shape Objects related to shapes. A shape is a visual object that appears on the drawing layer of a document. ### \_PictureShape The picture-extraction half of an inline or floating shape. Both `wp:inline` and `wp:anchor` wrap the same `a:graphic` subtree, so finding the image behind them is one implementation rather than two. #### image ``` image: Image | None ``` The Image this shape displays, or `None` when there is no image to return. This is the counterpart of Run.add_picture — extracting the pictures from a document without walking the relationships by hand: ``` for shape in document.inline_shapes: if shape.image is not None: Path(f"{shape.image.sha1}.{shape.image.ext}").write_bytes( shape.image.blob ) ``` `None` in three cases, each of which is a real document rather than an error: - the shape is not a picture at all — a chart, a SmartArt diagram or an embedded object; - the picture is *linked* rather than embedded, so the bytes are not in the package and there is nothing to hand back; - the shape was constructed without a parent, so there is no part to resolve the relationship against. For an SVG picture this returns the raster fallback, which is what every consumer can decode; svg_image returns the vector source. Several shapes can share one image part, so the Image returned for two shapes may be the same object. #### svg_image ``` svg_image: Image | None ``` The SVG source of this picture, or `None` when it has none. Word records an SVG picture as an `asvg:svgBlip` extension *alongside* a raster rendering of it, rather than in place of one. image returns the raster fallback; this returns the vector original. ### InlineShapes ``` InlineShapes(body_elm: CT_Body, parent: StoryPart) ``` Bases: `Parented` Sequence of InlineShape instances, supporting len(), iteration, and indexed access. Source code in `src/docx/shape.py` ``` def __init__(self, body_elm: CT_Body, parent: StoryPart): super(InlineShapes, self).__init__(parent) self._body = body_elm ``` ### FloatingShapes ``` FloatingShapes(body_elm: CT_Body, parent: StoryPart) ``` Bases: `Parented` Sequence of FloatingShape instances, supporting len(), iteration and indexing. A floating shape is anchored rather than inline: it is positioned against the page, the margin, the column or the paragraph, and text wraps around it. These are a distinct collection from InlineShapes rather than part of it, because almost nothing that is true of an inline shape's position is true of a floating one's, and silently mixing the two is how code that walks `inline_shapes` starts reporting nonsense positions. Source code in `src/docx/shape.py` ``` def __init__(self, body_elm: CT_Body, parent: StoryPart): super().__init__(parent) self._body = body_elm ``` ### FloatingShape ``` FloatingShape( anchor: CT_Anchor, parent: ProvidesStoryPart | None = None, ) ``` Bases: `_PictureShape` Proxy for a `` element, a shape that text flows around. Reached through Document.floating_shapes or returned by Run.add_float_picture. Source code in `src/docx/shape.py` ``` def __init__(self, anchor: CT_Anchor, parent: t.ProvidesStoryPart | None = None): self._anchor = anchor self._parent = parent ``` #### allow_overlap ``` allow_overlap: bool ``` Whether this shape may overlap another floating shape. Read/write. #### behind_text ``` behind_text: bool ``` Whether this shape is drawn behind the document text rather than over it. Read/write. This is what "put the watermark behind the text" means; it takes effect only with wrap_type of `WD_WRAP_TYPE.NONE`, since any other wrap setting keeps text out of the shape's way in the first place. #### description ``` description: str | None ``` The alternative text of this shape, `None` if not set. Read/write. #### height ``` height: Length ``` The display height of this shape as an Emu instance. Read/write. #### horizontal_align ``` horizontal_align: WD_ANCHOR_ALIGN_H | None ``` Named horizontal alignment of this shape, `None` when an offset is used. Read/write. Assigning an alignment replaces any left offset, and vice versa: the schema allows only one of the two, and Word ignores a shape that has both. Assigning `None` leaves the shape with neither, which Word treats as an offset of zero. #### left ``` left: Length | None ``` Horizontal offset from relative_from_h, `None` when aligned instead. Read/write. See horizontal_align for how the two interact. #### relative_from_h ``` relative_from_h: WD_ANCHOR_RELATIVE_FROM_H ``` What left and horizontal_align are measured from. Read/write. #### relative_from_v ``` relative_from_v: WD_ANCHOR_RELATIVE_FROM_V ``` What top and vertical_align are measured from. Read/write. #### title ``` title: str | None ``` The title of this shape, `None` if not set. Read/write. #### top ``` top: Length | None ``` Vertical offset from relative_from_v, `None` when aligned instead. Read/write. #### vertical_align ``` vertical_align: WD_ANCHOR_ALIGN_V | None ``` Named vertical alignment of this shape, `None` when an offset is used. Read/write. See horizontal_align. #### width ``` width: Length ``` The display width of this shape as an Emu instance. Read/write. #### wrap_distance ``` wrap_distance: tuple[Length, Length, Length, Length] ``` Space held clear of this shape as `(top, right, bottom, left)`. Read-only. Set the individual distances with set_wrap_distance. #### wrap_type ``` wrap_type: WD_WRAP_TYPE ``` Member of WdWrapType describing how text wraps around this shape. Read/write. #### z_order ``` z_order: int ``` Position of this shape in the stack of floating shapes. Read/write. A higher value is drawn on top of a lower one. Independent of behind_text, which decides whether the whole floating layer this shape is in sits in front of the text or behind it. #### set_wrap_distance ``` set_wrap_distance( top: Length | int | None = None, right: Length | int | None = None, bottom: Length | int | None = None, left: Length | int | None = None, ) -> None ``` Set the space held clear of this shape when text wraps around it. Each argument left as `None` is unchanged. Word's own default is no clearance above and below and 0.13cm to each side, which is what a new floating picture gets here. Source code in `src/docx/shape.py` ``` def set_wrap_distance( self, top: Length | int | None = None, right: Length | int | None = None, bottom: Length | int | None = None, left: Length | int | None = None, ) -> None: """Set the space held clear of this shape when text wraps around it. Each argument left as |None| is unchanged. Word's own default is no clearance above and below and 0.13cm to each side, which is what a new floating picture gets here. """ anchor = self._anchor if top is not None: anchor.distT = int(top) if right is not None: anchor.distR = int(right) if bottom is not None: anchor.distB = int(bottom) if left is not None: anchor.distL = int(left) ``` ### InlineShape ``` InlineShape( inline: CT_Inline, parent: ProvidesStoryPart | None = None, ) ``` Bases: `_PictureShape` Proxy for an `` element, representing the container for an inline graphical object. Source code in `src/docx/shape.py` ``` def __init__(self, inline: CT_Inline, parent: t.ProvidesStoryPart | None = None): super(InlineShape, self).__init__() self._inline = inline self._parent = parent ``` #### description ``` description: str | None ``` Read/write. The alternative text of this shape, `None` if not set. This is what a screen reader announces in place of the picture, and what an automated accessibility check looks for. Word's "Alt Text" pane writes this field. Assigning `None` removes it. #### height ``` height: Length ``` Read/write. The display height of this inline shape as an Emu instance. #### type ``` type ``` The type of this inline shape as a member of `docx.enum.shape.WD_INLINE_SHAPE`, e.g. `LINKED_PICTURE`. Read-only. #### title ``` title: str | None ``` Read/write. The title of this shape, `None` if not set. Word presents this separately from the alternative text and screen readers do not generally announce it; `.description` is the one accessibility depends on. Assigning `None` removes it. #### width ``` width ``` Read/write. The display width of this inline shape as an Emu instance. ## shared Objects shared by docx modules. ### Length Bases: `int` Base class for length constructor classes Inches, Cm, Mm, Px, and Emu. Behaves as an int count of English Metric Units, 914,400 to the inch, 36,000 to the mm. Provides convenience unit conversion methods in the form of read-only properties. Immutable. #### cm ``` cm ``` The equivalent length expressed in centimeters (float). #### emu ``` emu ``` The equivalent length expressed in English Metric Units (int). #### inches ``` inches ``` The equivalent length expressed in inches (float). #### mm ``` mm ``` The equivalent length expressed in millimeters (float). #### pt ``` pt ``` Floating point length in points. #### twips ``` twips ``` The equivalent length expressed in twips (int). ### Inches Bases: `Length` Convenience constructor for length in inches, e.g. `width = Inches(0.5)`. ### Cm Bases: `Length` Convenience constructor for length in centimeters, e.g. `height = Cm(12)`. ### Emu Bases: `Length` Convenience constructor for length in English Metric Units, e.g. `width = Emu(457200)`. ### Mm Bases: `Length` Convenience constructor for length in millimeters, e.g. `width = Mm(240.5)`. ### Pt Bases: `Length` Convenience value class for specifying a length in points. ### Twips Bases: `Length` Convenience constructor for length in twips, e.g. `width = Twips(42)`. A twip is a twentieth of a point, 635 EMU. ### Pct Bases: `float` A percentage, e.g. `Pct(100)` is one hundred percent. A percentage is deliberately *not* a Length. Every unit on Length is absolute and reducible to EMU; a percentage is relative to something else and has no size of its own, so the two do not belong to the same family and mixing them silently produces nonsense. Word stores these in fiftieths of a percent, which is what `.fiftieths` returns and what `from_fiftieths()` reads. #### fiftieths ``` fiftieths: int ``` This percentage in fiftieths of a percent, e.g. `Pct(100).fiftieths` is 5000. #### from_fiftieths ``` from_fiftieths(value: int) -> Pct ``` A Pct from `value` fiftieths of a percent, the form Word writes. Source code in `src/docx/shared.py` ``` @classmethod def from_fiftieths(cls, value: int) -> Pct: """A |Pct| from `value` fiftieths of a percent, the form Word writes.""" return cls(value / 50.0) ``` ### RGBColor Bases: `Tuple[int, int, int]` Immutable value object defining a particular RGB color. #### from_string ``` from_string(rgb_hex_str: str) -> RGBColor ``` Return a new instance from an RGB color hex string like `'3C2F80'`. A leading "#" is accepted, so CSS-style values like `'#3C2F80'` also work. Source code in `src/docx/shared.py` ``` @classmethod def from_string(cls, rgb_hex_str: str) -> RGBColor: """Return a new instance from an RGB color hex string like ``'3C2F80'``. A leading "#" is accepted, so CSS-style values like ``'#3C2F80'`` also work. """ rgb_hex_str = rgb_hex_str.lstrip("#") r = int(rgb_hex_str[:2], 16) g = int(rgb_hex_str[2:4], 16) b = int(rgb_hex_str[4:], 16) return cls(r, g, b) ``` ### lazyproperty ``` lazyproperty(fget: Callable[..., T]) ``` Bases: `Generic[T]` Decorator like @property, but evaluated only on first access. Like @property, this can only be used to decorate methods having only a `self` parameter, and is accessed like an attribute on an instance, i.e. trailing parentheses are not used. Unlike @property, the decorated method is only evaluated on first access; the resulting value is cached and that same value returned on second and later access without re-evaluation of the method. Like @property, this class produces a *data descriptor* object, which is stored in the **dict** of the *class* under the name of the decorated method ('fget' nominally). The cached value is stored in the **dict** of the *instance* under that same name. Because it is a data descriptor (as opposed to a *non-data descriptor*), its `__get__()` method is executed on each access of the decorated attribute; the **dict** item of the same name is "shadowed" by the descriptor. While this may represent a performance improvement over a property, its greater benefit may be its other characteristics. One common use is to construct collaborator objects, removing that "real work" from the constructor, while still only executing once. It also de-couples client code from any sequencing considerations; if it's accessed from more than one location, it's assured it will be ready whenever needed. Loosely based on: https://stackoverflow.com/a/6849299/1902513. A lazyproperty is read-only. There is no counterpart to the optional "setter" (or deleter) behavior of an @property. This is critically important to maintaining its immutability and idempotence guarantees. Attempting to assign to a lazyproperty raises AttributeError unconditionally. The parameter names in the methods below correspond to this usage example: ``` class Obj(object) @lazyproperty def fget(self): return 'some result' obj = Obj() ``` Not suitable for wrapping a function (as opposed to a method) because it is not callable. *fget* is the decorated method (a "getter" function). A lazyproperty is read-only, so there is only an *fget* function (a regular @property can also have an fset and fdel function). This name was chosen for consistency with Python's `property` class which uses this name for the corresponding parameter. Source code in `src/docx/shared.py` ``` def __init__(self, fget: Callable[..., T]) -> None: """*fget* is the decorated method (a "getter" function). A lazyproperty is read-only, so there is only an *fget* function (a regular @property can also have an fset and fdel function). This name was chosen for consistency with Python's `property` class which uses this name for the corresponding parameter. """ # --- maintain a reference to the wrapped getter method self._fget = fget # --- and store the name of that decorated method self._name = fget.__name__ # --- adopt fget's __name__, __doc__, and other attributes functools.update_wrapper(self, fget) # pyright: ignore ``` ### ElementProxy ``` ElementProxy( element: BaseOxmlElement, parent: ProvidesXmlPart | None = None, ) ``` Base class for lxml element proxy classes. An element proxy class is one whose primary responsibilities are fulfilled by manipulating the attributes and child elements of an XML element. They are the most common type of class in python-docx other than custom element (oxml) classes. Source code in `src/docx/shared.py` ``` def __init__(self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None): self._element = element self._parent = parent ``` #### element ``` element ``` The lxml element proxied by this object. #### part ``` part: XmlPart ``` The package part containing this object. ### Parented ``` Parented(parent: ProvidesXmlPart) ``` Provides common services for document elements that occur below a part but may occasionally require an ancestor object to provide a service, such as add or drop a relationship. Provides `self._parent` attribute to subclasses. Source code in `src/docx/shared.py` ``` def __init__(self, parent: t.ProvidesXmlPart): self._parent = parent ``` #### part ``` part: XmlPart ``` The package part containing this object. ### StoryChild ``` StoryChild(parent: ProvidesStoryPart) ``` A document element within a story part. Story parts include DocumentPart and Header/FooterPart and can contain block items (paragraphs and tables). Items from the block-item subtree occasionally require an ancestor object to provide access to part-level or package-level items like styles or images or to add or drop a relationship. Provides `self._parent` attribute to subclasses. Source code in `src/docx/shared.py` ``` def __init__(self, parent: t.ProvidesStoryPart): self._parent = parent ``` #### part ``` part: StoryPart ``` The package part containing this object. ### TextAccumulator ``` TextAccumulator(separator: str = '') ``` Accepts `str` fragments and joins them together, in order, on \`.pop(). Handy when text in a stream is broken up arbitrarily and you want to join it back together within certain bounds. The optional `separator` argument determines how the text fragments are punctuated, defaulting to the empty string. Source code in `src/docx/shared.py` ``` def __init__(self, separator: str = ""): self._separator = separator self._texts: List[str] = [] ``` #### push ``` push(text: str) -> None ``` Add a text fragment to the accumulator. Source code in `src/docx/shared.py` ``` def push(self, text: str) -> None: """Add a text fragment to the accumulator.""" self._texts.append(text) ``` #### pop ``` pop() -> Iterator[str] ``` Generate sero-or-one str from those accumulated. Using `yield from accum.pop()` in a generator setting avoids producing an empty string when no text is in the accumulator. Source code in `src/docx/shared.py` ``` def pop(self) -> Iterator[str]: """Generate sero-or-one str from those accumulated. Using `yield from accum.pop()` in a generator setting avoids producing an empty string when no text is in the accumulator. """ if not self._texts: return text = self._separator.join(self._texts) self._texts.clear() yield text ``` ### write_only_property ``` write_only_property(f: Callable[[Any, Any], None]) ``` @write_only_property decorator. Creates a property (descriptor attribute) that accepts assignment, but not getattr (use in an expression). Source code in `src/docx/shared.py` ``` def write_only_property(f: Callable[[Any, Any], None]): """@write_only_property decorator. Creates a property (descriptor attribute) that accepts assignment, but not getattr (use in an expression). """ docstring = f.__doc__ return property(fset=f, doc=docstring) ``` ## table The Table object and related proxy classes. ### \_TableBorders ``` _TableBorders(tbl: CT_Tbl) ``` Bases: `_Borders` The border edges of a table, `table.borders`. Source code in `src/docx/table.py` ``` def __init__(self, tbl: CT_Tbl): # -- the edges come from the element class rather than being repeated here, so # -- the mapping cannot drift from the schema sequence the element declares -- super().__init__(CT_TblBorders.edges) self._tbl = tbl ``` ### \_CellBorders ``` _CellBorders(tc: CT_Tc) ``` Bases: `_Borders` The border edges of a table cell, `cell.borders`. Source code in `src/docx/table.py` ``` def __init__(self, tc: CT_Tc): super().__init__(CT_TcBorders.edges) self._tc = tc ``` ### \_TableLook ``` _TableLook(tbl: CT_Tbl) ``` Which parts of the table style apply to a table, `table.look`. `w:tblLook` is what tells Word whether the first row is a header row, whether the first or last column is emphasised, and whether row or column banding is on. Without it a styled table looks nothing like the style preview in Word. Each flag is tri-state: `None` means the attribute is absent and Word applies its own default (off for every flag). The two banding flags are stored inverted in the XML, as `w:noHBand` and `w:noVBand`; that inversion lives here so the oxml layer stays faithful to the attribute names. Word writes the six named attributes *and* the equivalent legacy bitmask in `@w:val`, and keeps them in step. Setting any flag through this proxy rewrites `@w:val` to match, because some older consumers read only the bitmask. Source code in `src/docx/table.py` ``` def __init__(self, tbl: CT_Tbl): self._tbl = tbl ``` #### first_row ``` first_row: bool | None ``` `True` when the table style's first-row (header) formatting applies. #### last_row ``` last_row: bool | None ``` `True` when the table style's last-row (total) formatting applies. #### first_column ``` first_column: bool | None ``` `True` when the table style's first-column formatting applies. #### last_column ``` last_column: bool | None ``` `True` when the table style's last-column formatting applies. #### horizontal_banding ``` horizontal_banding: bool | None ``` `True` when the table style's row banding applies. Stored inverted, as `w:noHBand`. #### vertical_banding ``` vertical_banding: bool | None ``` `True` when the table style's column banding applies. Stored inverted, as `w:noVBand`. ### \_TableCellMargins ``` _TableCellMargins(tbl: CT_Tbl) ``` The default cell margins of a table, `table.cell_margins`. These are the padding Word applies inside every cell of the table that does not override them. An edge reads `None` when the table sets no value for it, in which case the table style's value applies. The `start` and `end` edges are the logical (writing-direction) synonyms of `left` and `right`. Word writes `left` and `right`; both are exposed because documents from other producers use the newer pair. Source code in `src/docx/table.py` ``` def __init__(self, tbl: CT_Tbl): self._tbl = tbl ``` #### clear ``` clear() -> None ``` Remove the `w:tblCellMar` element, restoring the table style's margins. Source code in `src/docx/table.py` ``` def clear(self) -> None: """Remove the `w:tblCellMar` element, restoring the table style's margins.""" self._tbl.tblPr._remove_tblCellMar() # pyright: ignore[reportPrivateUsage] ``` ### Table ``` Table(tbl: CT_Tbl, parent: ProvidesStoryPart) ``` Bases: `StoryChild` Proxy class for a WordprocessingML `` element. Source code in `src/docx/table.py` ``` def __init__(self, tbl: CT_Tbl, parent: t.ProvidesStoryPart): super(Table, self).__init__(parent) self._element = tbl self._tbl = tbl ``` #### alignment ``` alignment: WD_TABLE_ALIGNMENT | None ``` Read/write. A member of WdRowAlignment or None, specifying the positioning of this table between the page margins. `None` if no setting is specified, causing the effective value to be inherited from the style hierarchy. #### autofit ``` autofit: bool ``` `True` if column widths can be automatically adjusted to improve the fit of cell contents. `False` if table layout is fixed. Column widths are adjusted in either case if total column width exceeds page width. Read/write boolean. #### description ``` description: str | None ``` Alternative-text description for this table, or `None` if not set. Assigning `None` removes the description. This value is stored in the `w:tblDescription` table-property element and is used by assistive technologies. #### indent ``` indent: Length | None ``` Indentation of this table from the margin, or `None` if not set. This is `w:tblInd`. Assigning `None` removes it. #### width ``` width: Length | Pct | None ``` The preferred width of this table. A Length for an absolute width, a Pct for a percentage of the text column, and `None` when the width is `auto` — Word sizing the table to its content — or no `w:tblW` is present at all. A percentage table reflows with the page margins where one built from absolute column widths does not, so `table.width = Pct(100)` is not the same as setting the column widths to add up: ``` table.width = Pct(100) table.width = Inches(6) table.width = None # auto ``` Note this is the *preferred* width: Word may widen a table whose content does not fit, and a table with `autofit` on will do so routinely. #### style ``` style: _TableStyle | None ``` \_TableStyle object representing the style applied to this table. Read/write. The default table style for the document (often `Normal Table`) is returned if the table has no directly-applied style. Assigning `None` to this property removes any directly-applied table style causing it to inherit the default table style of the document. Note that the style name of a table style differs slightly from that displayed in the user interface; a hyphen, if it appears, must be removed. For example, `Light Shading - Accent 1` becomes `Light Shading Accent 1`. #### table ``` table ``` Provide child objects with reference to the Table object they belong to, without them having to know their direct parent is a Table object. This is the terminus of a series of `parent._table` calls from an arbitrary child through its ancestors. #### table_direction ``` table_direction: WD_TABLE_DIRECTION | None ``` Member of WdTableDirection indicating cell-ordering direction. For example: `WD_TABLE_DIRECTION.LTR`. `None` indicates the value is inherited from the style hierarchy. #### title ``` title: str | None ``` Alternative-text title for this table, or `None` if not set. Assigning `None` removes the title. This value is stored in the `w:tblCaption` table-property element and is used by assistive technologies. #### add_column ``` add_column(width: Length) ``` Return a \_Column object of `width`, newly added rightmost to the table. Source code in `src/docx/table.py` ``` def add_column(self, width: Length): """Return a |_Column| object of `width`, newly added rightmost to the table.""" tblGrid = self._tbl.tblGrid gridCol = tblGrid.add_gridCol() gridCol.w = width for tr in self._tbl.tr_lst: tc = tr.add_tc() tc.width = width return _Column(gridCol, self) ``` #### add_row ``` add_row() ``` Return a \_Row instance, newly added bottom-most to the table. Source code in `src/docx/table.py` ``` def add_row(self): """Return a |_Row| instance, newly added bottom-most to the table.""" tbl = self._tbl tr = tbl.add_tr() for gridCol in tbl.tblGrid.gridCol_lst: tc = tr.add_tc() if gridCol.w is not None: tc.width = gridCol.w return _Row(tr, self) ``` #### borders ``` borders() -> _TableBorders ``` The border edges of this table, as a mapping keyed by edge name: ``` table.borders["top"].line = WD_LINE_STYLE.SINGLE table.borders["top"].size = Pt(1) ``` These are the borders applied to the table as a whole; `insideH` and `insideV` set the horizontal and vertical borders between its cells. A border set on an individual cell through `cell.borders` takes precedence over the table border at that edge. Source code in `src/docx/table.py` ``` @lazyproperty def borders(self) -> _TableBorders: """The border edges of this table, as a mapping keyed by edge name:: table.borders["top"].line = WD_LINE_STYLE.SINGLE table.borders["top"].size = Pt(1) These are the borders applied to the table as a whole; `insideH` and `insideV` set the horizontal and vertical borders between its cells. A border set on an individual cell through `cell.borders` takes precedence over the table border at that edge. """ return _TableBorders(self._tbl) ``` #### cell ``` cell(row_idx: int, col_idx: int) -> _Cell ``` \_Cell at `row_idx`, `col_idx` intersection. (0, 0) is the top, left-most cell. Negative indices count back from the end, as for a sequence. Raises `IndexError` if `row_idx` is out of range, or if the row does not occupy layout-grid column `col_idx` — Word allows a row to start late or end early. The target cell is located directly, without materializing the whole layout grid, so reading a table cell-by-cell costs time proportional to the number of cells rather than to its square. Source code in `src/docx/table.py` ``` def cell(self, row_idx: int, col_idx: int) -> _Cell: """|_Cell| at `row_idx`, `col_idx` intersection. (0, 0) is the top, left-most cell. Negative indices count back from the end, as for a sequence. Raises |IndexError| if `row_idx` is out of range, or if the row does not occupy layout-grid column `col_idx` — Word allows a row to start late or end early. The target cell is located directly, without materializing the whole layout grid, so reading a table cell-by-cell costs time proportional to the number of cells rather than to its square. """ tr = self._tbl.tr_at_idx(row_idx) if col_idx < 0: col_idx += self._column_count try: tc = tr.tc_covering_grid_offset(col_idx) except ValueError: raise IndexError("table column index [%d] is out of range" % col_idx) from None # -- a continuation cell of a vertical span holds no content; the cell the span # -- starts at does -- return _Cell(tc.top_tc, self) ``` #### column_cells ``` column_cells(column_idx: int) -> list[_Cell] ``` Sequence of cells in the column at `column_idx` in this table. A row that does not occupy `column_idx`, because it starts late or ends early, contributes no cell. Source code in `src/docx/table.py` ``` def column_cells(self, column_idx: int) -> list[_Cell]: """Sequence of cells in the column at `column_idx` in this table. A row that does not occupy `column_idx`, because it starts late or ends early, contributes no cell. """ def iter_column_cells() -> Iterator[_Cell]: for tr in self._tbl.tr_lst: try: tc = tr.tc_covering_grid_offset(column_idx) except ValueError: continue yield _Cell(tc.top_tc, self) return list(iter_column_cells()) ``` #### cell_margins ``` cell_margins() -> _TableCellMargins ``` The default cell margins for every cell of this table: ``` table.cell_margins.left = Pt(6) ``` An edge reads `None` when the table sets no value for it, in which case the table style's margin applies. Assigning `None` removes the override. Source code in `src/docx/table.py` ``` @lazyproperty def cell_margins(self) -> _TableCellMargins: """The default cell margins for every cell of this table:: table.cell_margins.left = Pt(6) An edge reads |None| when the table sets no value for it, in which case the table style's margin applies. Assigning |None| removes the override. """ return _TableCellMargins(self._tbl) ``` #### look ``` look() -> _TableLook ``` Which parts of the table style apply to this table: ``` table.look.first_row = True table.look.horizontal_banding = True ``` Applying a table style without setting these produces a table that looks nothing like the style preview in Word. Source code in `src/docx/table.py` ``` @lazyproperty def look(self) -> _TableLook: """Which parts of the table style apply to this table:: table.look.first_row = True table.look.horizontal_banding = True Applying a table style without setting these produces a table that looks nothing like the style preview in Word. """ return _TableLook(self._tbl) ``` #### copy_to ``` copy_to( container: BlockItemContainer | Document, *, before: Paragraph | Table | None = None, after: Paragraph | Table | None = None, missing_style: str = "copy", ) -> Table ``` Return a copy of this table, newly placed in `container`: ``` new_table = table.copy_to(document) ``` See Paragraph.copy_to for what is repaired on the way — relationships, drawing ids, bookmarks, and, for a copy into another document, styles and numbering. Source code in `src/docx/table.py` ``` def copy_to( self, container: BlockItemContainer | Document, *, before: Paragraph | Table | None = None, after: Paragraph | Table | None = None, missing_style: str = "copy", ) -> Table: """Return a copy of this table, newly placed in `container`:: new_table = table.copy_to(document) See :meth:`.Paragraph.copy_to` for what is repaired on the way — relationships, drawing ids, bookmarks, and, for a copy into another document, styles and numbering. """ from docx.copy import copy_content, destination_for, place dest_part, dest_element = destination_for(container) new_tbl = copy_content(self._tbl, self.part, dest_part, missing_style=missing_style) place(new_tbl, dest_element, before, after) return Table(new_tbl, container) # pyright: ignore[reportArgumentType] ``` #### delete ``` delete() -> None ``` Remove this table from the document. Relationships referenced only from inside the table are dropped, and any range marker left unmatched is removed, as for `Paragraph.delete()`. Source code in `src/docx/table.py` ``` def delete(self) -> None: """Remove this table from the document. Relationships referenced only from inside the table are dropped, and any range marker left unmatched is removed, as for `Paragraph.delete()`. """ delete_element(self._tbl, self.part) ``` #### columns ``` columns() ``` \_Columns instance representing the sequence of columns in this table. Source code in `src/docx/table.py` ``` @lazyproperty def columns(self): """|_Columns| instance representing the sequence of columns in this table.""" return _Columns(self._tbl, self) ``` #### row_cells ``` row_cells(row_idx: int) -> list[_Cell] ``` DEPRECATED: Use `table.rows[row_idx].cells` instead. Sequence of cells in the row at `row_idx` in this table. Source code in `src/docx/table.py` ``` def row_cells(self, row_idx: int) -> list[_Cell]: """DEPRECATED: Use `table.rows[row_idx].cells` instead. Sequence of cells in the row at `row_idx` in this table. """ column_count = self._column_count start = row_idx * column_count end = start + column_count return self._cells[start:end] ``` #### rows ``` rows() -> _Rows ``` \_Rows instance containing the sequence of rows in this table. Source code in `src/docx/table.py` ``` @lazyproperty def rows(self) -> _Rows: """|_Rows| instance containing the sequence of rows in this table.""" return _Rows(self._tbl, self) ``` ### \_Cell ``` _Cell(tc: CT_Tc, parent: TableParent) ``` Bases: `BlockItemContainer` Table cell. Source code in `src/docx/table.py` ``` def __init__(self, tc: CT_Tc, parent: TableParent): super(_Cell, self).__init__(tc, cast("t.ProvidesStoryPart", parent)) self._parent = parent self._tc = self._element = tc ``` #### column_index ``` column_index: int ``` Index of the left-most layout-grid column this cell occupies. Together with `.row_index` this gives the origin of the cell, which is what tells a repeat of a merged cell apart from a cell in its own right: ``` for row_idx, row in enumerate(table.rows): for col_idx, cell in enumerate(row.cells): if (cell.row_index, cell.column_index) != (row_idx, col_idx): continue # -- already emitted, this is part of a merged cell -- emit(cell.text, rowspan=cell.span_height, colspan=cell.grid_span) ``` Note this is a layout-grid column index, so it accounts for the grid positions a row leaves unpopulated at its start; see `_Row.grid_cols_before`. #### grid_span ``` grid_span: int ``` Number of layout-grid cells this cell spans horizontally. A "normal" cell has a grid-span of 1. A horizontally merged cell has a grid-span of 2 or more. #### is_merged ``` is_merged: bool ``` `True` when this cell spans more than one layout-grid cell. Horizontally, vertically, or both. #### paragraphs ``` paragraphs ``` List of paragraphs in the cell. A table cell is required to contain at least one block-level element and end with a paragraph. By default, a new cell contains a single paragraph. Read-only #### row_index ``` row_index: int ``` Index of the top-most row this cell occupies. For a vertically merged cell this is the row the merge starts at, not the row the cell was reached through. See `.column_index` for how the pair is used. #### span ``` span: tuple[int, int] ``` The extent of this cell as `(rows, columns)`. `(1, 1)` for an unmerged cell. #### span_height ``` span_height: int ``` Number of rows this cell spans vertically. An unmerged cell has a span-height of 1; a vertically merged cell has 2 or more. This is the read-side counterpart of `.grid_span`, and the two together describe a merge completely, including the combined case of a cell that is merged in both directions. A merge is measured by following its continuation cells, so a document whose origin cell omits `w:vMerge` — legal in practice and common from generators other than Word — reports the same extent Word renders. #### tables ``` tables ``` List of tables in the cell, in the order they appear. Read-only. #### text ``` text: str ``` The entire contents of this cell as a string of text. Assigning a string to this property replaces all existing content with a single paragraph containing the assigned text in a single run. #### text_direction ``` text_direction: WD_TEXT_DIRECTION | None ``` Flow direction of the text in this cell, or `None` when inherited. This is what a rotated header cell needs: ``` cell.text_direction = WD_TEXT_DIRECTION.BT_LR ``` Assigning `None` removes the setting, restoring inheritance. #### vertical_alignment ``` vertical_alignment ``` Member of WdCellVerticalAlignment or None. A value of `None` indicates vertical alignment for this cell is inherited. Assigning `None` causes any explicitly defined vertical alignment to be removed, restoring inheritance. #### width ``` width ``` The width of this cell in EMU, or `None` if no explicit width is set. #### add_paragraph ``` add_paragraph( text: str = "", style: str | ParagraphStyle | None = None, ) ``` Return a paragraph newly added to the end of the content in this cell. If present, `text` is added to the paragraph in a single run. If specified, the paragraph style `style` is applied. If `style` is not specified or is `None`, the result is as though the 'Normal' style was applied. Note that the formatting of text in a cell can be influenced by the table style. `text` can contain tab (`\t`) characters, which are converted to the appropriate XML form for a tab. `text` can also include newline (`\n`) or carriage return (`\r`) characters, each of which is converted to a line break. Source code in `src/docx/table.py` ``` def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None): """Return a paragraph newly added to the end of the content in this cell. If present, `text` is added to the paragraph in a single run. If specified, the paragraph style `style` is applied. If `style` is not specified or is |None|, the result is as though the 'Normal' style was applied. Note that the formatting of text in a cell can be influenced by the table style. `text` can contain tab (``\\t``) characters, which are converted to the appropriate XML form for a tab. `text` can also include newline (``\\n``) or carriage return (``\\r``) characters, each of which is converted to a line break. """ return super(_Cell, self).add_paragraph(text, style) ``` #### add_table ``` add_table( rows: int, cols: int, *, title: str | None = None, description: str | None = None, ) -> Table ``` Return a table newly added to this cell after any existing cell content. The new table will have `rows` rows and `cols` columns. An empty paragraph is added after the table because Word requires a paragraph element as the last element in every cell. `description` is the table's alternative text and `title` the separate, caption-like field Word writes alongside it. Both are omitted from the XML when `None`. Source code in `src/docx/table.py` ``` def add_table( # pyright: ignore[reportIncompatibleMethodOverride] self, rows: int, cols: int, *, title: str | None = None, description: str | None = None, ) -> Table: """Return a table newly added to this cell after any existing cell content. The new table will have `rows` rows and `cols` columns. An empty paragraph is added after the table because Word requires a paragraph element as the last element in every cell. `description` is the table's alternative text and `title` the separate, caption-like field Word writes alongside it. Both are omitted from the XML when |None|. """ width = self.width if self.width is not None else Inches(1) table = super(_Cell, self).add_table( rows, cols, width, title=title, description=description ) self.add_paragraph() return table ``` #### add_caption ``` add_caption( label: str, text: str = "", *, style: str | None = "Caption", separator: str = " ", restart_at_heading_level: int | None = None, before: Paragraph | None = None, ) -> Caption ``` Add a numbered, cross-referenceable caption to this cell and return it. See Document.add_caption. Source code in `src/docx/table.py` ``` def add_caption( self, label: str, text: str = "", *, style: str | None = "Caption", separator: str = " ", restart_at_heading_level: int | None = None, before: Paragraph | None = None, ) -> Caption: """Add a numbered, cross-referenceable caption to this cell and return it. See :meth:`.Document.add_caption`. """ from docx.caption import add_caption return add_caption( self, label, text, style=style, separator=separator, restart_at_heading_level=restart_at_heading_level, before=before, ) ``` #### borders ``` borders() -> _CellBorders ``` The border edges of this cell, as a mapping keyed by edge name: ``` cell.borders["bottom"].line = WD_LINE_STYLE.DOUBLE ``` A cell adds the two diagonal edges `tl2br` and `tr2bl` to the edges a table admits. A border set here takes precedence over the table border at the same edge. Source code in `src/docx/table.py` ``` @lazyproperty def borders(self) -> _CellBorders: """The border edges of this cell, as a mapping keyed by edge name:: cell.borders["bottom"].line = WD_LINE_STYLE.DOUBLE A cell adds the two diagonal edges `tl2br` and `tr2bl` to the edges a table admits. A border set here takes precedence over the table border at the same edge. """ return _CellBorders(self._tc) ``` #### merge ``` merge(other_cell: _Cell) ``` Return a merged cell created by spanning the rectangular region having this cell and `other_cell` as diagonal corners. Raises InvalidSpanError if the cells do not define a rectangular region. Source code in `src/docx/table.py` ``` def merge(self, other_cell: _Cell): """Return a merged cell created by spanning the rectangular region having this cell and `other_cell` as diagonal corners. Raises |InvalidSpanError| if the cells do not define a rectangular region. """ tc, tc_2 = self._tc, other_cell._tc merged_tc = tc.merge(tc_2) return _Cell(merged_tc, self._parent) ``` ### \_Column ``` _Column(gridCol: CT_TblGridCol, parent: TableParent) ``` Bases: `Parented` Table column. Source code in `src/docx/table.py` ``` def __init__(self, gridCol: CT_TblGridCol, parent: TableParent): super(_Column, self).__init__(parent) self._parent = parent self._gridCol = gridCol ``` #### cells ``` cells: tuple[_Cell, ...] ``` Sequence of \_Cell instances corresponding to cells in this column. #### table ``` table: Table ``` Reference to the Table object this column belongs to. #### width ``` width: Length | None ``` The width of this column in EMU, or `None` if no explicit width is set. #### delete ``` delete() -> None ``` Remove this column from its table. Removes the `w:gridCol` and the cell occupying this layout-grid column in every row. A cell that spans this column and others is narrowed by one rather than removed, so the rest of its span survives. Source code in `src/docx/table.py` ``` def delete(self) -> None: """Remove this column from its table. Removes the `w:gridCol` and the cell occupying this layout-grid column in every row. A cell that spans this column and others is narrowed by one rather than removed, so the rest of its span survives. """ table = self.table column_idx = self._index for tr in table._tbl.tr_lst: # pyright: ignore[reportPrivateUsage] tr.delete_grid_column(column_idx, table.part) delete_element(self._gridCol, table.part) ``` ### \_Columns ``` _Columns(tbl: CT_Tbl, parent: TableParent) ``` Bases: `Parented` Sequence of \_Column instances corresponding to the columns in a table. Supports `len()`, iteration and indexed access. Source code in `src/docx/table.py` ``` def __init__(self, tbl: CT_Tbl, parent: TableParent): super(_Columns, self).__init__(parent) self._parent = parent self._tbl = tbl ``` #### table ``` table: Table ``` Reference to the Table object this column collection belongs to. ### \_Row ``` _Row(tr: CT_Row, parent: TableParent) ``` Bases: `Parented` Table row. Source code in `src/docx/table.py` ``` def __init__(self, tr: CT_Row, parent: TableParent): super(_Row, self).__init__(parent) self._parent = parent self._tr = self._element = tr ``` #### cells ``` cells: tuple[_Cell, ...] ``` Sequence of \_Cell instances corresponding to cells in this row. Note that Word allows table rows to start later than the first column and end before the last column. - Only cells actually present are included in the return value. - This implies the length of this cell sequence may differ between rows of the same table. - If you are reading the cells from each row to form a rectangular "matrix" data structure of the table cell values, you will need to account for empty leading and/or trailing layout-grid positions using `.grid_cols_before` and `.grid_cols_after`. #### grid_cols_after ``` grid_cols_after: int ``` Count of unpopulated grid-columns after the last cell in this row. Word allows a row to "end early", meaning that one or more cells are not present at the end of that row. Note these are not simply "empty" cells. The renderer reads this value and "skips" this many columns after drawing the last cell. Note this also implies that not all rows are guaranteed to have the same number of cells, e.g. `_Row.cells` could have length `n` for one row and `n - m` for the next row in the same table. Visually this appears as a column (at the beginning or end, not in the middle) with one or more cells missing. #### grid_cols_before ``` grid_cols_before: int ``` Count of unpopulated grid-columns before the first cell in this row. Word allows a row to "start late", meaning that one or more cells are not present at the beginning of that row. Note these are not simply "empty" cells. The renderer reads this value and skips forward to the table layout-grid position of the first cell in this row; the renderer "skips" this many columns before drawing the first cell. Note this also implies that not all rows are guaranteed to have the same number of cells, e.g. `_Row.cells` could have length `n` for one row and `n - m` for the next row in the same table. #### height ``` height: Length | None ``` Return a Length object representing the height of this cell, or `None` if no explicit height is set. #### dont_split ``` dont_split: bool | None ``` `True` if this row is kept on a single page rather than broken across pages. Corresponds to unchecking "Allow row to break across pages" in Word. `None` indicates no explicit setting, which Word treats as allowing the break. #### repeat_as_header ``` repeat_as_header: bool | None ``` `True` when this row repeats at the top of each page the table spans. Corresponds to "Repeat Header Rows" in Word. `None` indicates no explicit setting, which Word treats as off. Word only honours this on a contiguous run of rows starting at the first row of the table. Setting it on row 3 alone is legal XML that has no visible effect. #### hidden ``` hidden: bool | None ``` `True` when this row is not displayed. `None` indicates no explicit setting, which Word treats as visible. #### alignment ``` alignment: WD_TABLE_ALIGNMENT | None ``` Horizontal alignment of this row within the table, or `None` if not set. This overrides the table's own alignment for this row alone. #### cell_spacing ``` cell_spacing: Length | None ``` Spacing between the cells of this row, or `None` if not set. #### width_before ``` width_before: Length | None ``` Width of the grid positions this row leaves unpopulated at its start. Pairs with `.grid_cols_before`, which counts them. `None` if not set. #### width_after ``` width_after: Length | None ``` Width of the grid positions this row leaves unpopulated at its end. Pairs with `.grid_cols_after`, which counts them. `None` if not set. #### height_rule ``` height_rule: WD_ROW_HEIGHT_RULE | None ``` Return the height rule of this cell as a member of the WdRowHeightRule. This value is `None` if no explicit height_rule is set. #### table ``` table: Table ``` Reference to the Table object this row belongs to. #### copy_to ``` copy_to( table: Table, *, before: _Row | None = None, after: _Row | None = None, missing_style: str = "copy", ) -> _Row ``` Return a copy of this row, newly placed in `table`. "Duplicate this table row N times" is the other most-written-by-hand operation: ``` for _ in range(9): template_row.copy_to(table) ``` `before` and `after` place the copy relative to an existing row; with neither it is appended. The copy keeps this row's own cell widths and spans. It is not adjusted to `table`'s grid, so copying a row into a table of a different column count produces a row that does not line up — which is what the XML says and what Word will render. See Paragraph.copy_to for what is repaired on the way — relationships, drawing ids, bookmarks, and, for a copy into another document, styles and numbering. Source code in `src/docx/table.py` ``` def copy_to( self, table: Table, *, before: _Row | None = None, after: _Row | None = None, missing_style: str = "copy", ) -> _Row: """Return a copy of this row, newly placed in `table`. "Duplicate this table row N times" is the other most-written-by-hand operation:: for _ in range(9): template_row.copy_to(table) `before` and `after` place the copy relative to an existing row; with neither it is appended. The copy keeps this row's own cell widths and spans. It is not adjusted to `table`'s grid, so copying a row into a table of a different column count produces a row that does not line up — which is what the XML says and what Word will render. See :meth:`.Paragraph.copy_to` for what is repaired on the way — relationships, drawing ids, bookmarks, and, for a copy into another document, styles and numbering. """ from docx.copy import copy_content dest_part = table.part new_tr = copy_content(self._tr, self.part, dest_part, missing_style=missing_style) if before is not None and after is not None: raise ValueError("pass at most one of `before` and `after`") if before is not None: before._tr.addprevious(new_tr) elif after is not None: after._tr.addnext(new_tr) else: table._tbl.append(new_tr) return _Row(new_tr, table) # pyright: ignore[reportArgumentType] ``` #### delete ``` delete() -> None ``` Remove this row from its table. A vertically merged cell whose span started in this row is not dropped: the row below inherits it, so the merge continues to render, which is what Word does when a row is deleted. Source code in `src/docx/table.py` ``` def delete(self) -> None: """Remove this row from its table. A vertically merged cell whose span started in this row is not dropped: the row below inherits it, so the merge continues to render, which is what Word does when a row is deleted. """ self._tr.transfer_vertical_spans_to_row_below() delete_element(self._tr, self.table.part) ``` ### \_Rows ``` _Rows(tbl: CT_Tbl, parent: TableParent) ``` Bases: `Parented` Sequence of \_Row objects corresponding to the rows in a table. Supports `len()`, iteration, indexed access, and slicing. Source code in `src/docx/table.py` ``` def __init__(self, tbl: CT_Tbl, parent: TableParent): super(_Rows, self).__init__(parent) self._parent = parent self._tbl = tbl ``` #### table ``` table: Table ``` Reference to the Table object this row collection belongs to. ## theme The Theme object, the document's theme fonts and colours. ### \_ThemeFont ``` _ThemeFont(fontCollection: object) ``` One font collection of a theme — its major or minor fonts. Source code in `src/docx/theme.py` ``` def __init__(self, fontCollection: object): self._fontCollection = fontCollection ``` #### latin ``` latin: str | None ``` The Latin typeface of this collection, e.g. `"Calibri"`. This is what a `minorHAnsi` or `majorHAnsi` theme token resolves to, and the one that matters for a Western document. #### east_asian ``` east_asian: str | None ``` The East Asian typeface of this collection, or `None` when it sets none. The default Office theme leaves this empty and relies on the `a:font` script entries instead, so `None` here is ordinary rather than exceptional. #### complex_script ``` complex_script: str | None ``` The complex-script typeface of this collection, or `None` when it sets none. ### Theme ``` Theme( theme: CT_OfficeStyleSheet, part: XmlPart | None = None ) ``` Bases: `ElementProxy` The document's theme: its major and minor fonts and its twelve theme colours. Reached through Document.theme, which is `None` for a document carrying no theme part. The point of exposing it is Font.theme_typeface: a run whose font is set only by a theme token reports `None` for Font.name, and this is where the concrete typeface behind that token lives. Source code in `src/docx/theme.py` ``` def __init__(self, theme: CT_OfficeStyleSheet, part: XmlPart | None = None): super().__init__(theme, part) # pyright: ignore[reportArgumentType] self._element = theme ``` #### name ``` name: str | None ``` The theme's name, e.g. `"Office Theme"`, or `None` when it has none. #### major_font ``` major_font: _ThemeFont ``` The theme's major fonts, which Word applies to headings. #### minor_font ``` minor_font: _ThemeFont ``` The theme's minor fonts, which Word applies to body text. #### colors ``` colors: dict[str, RGBColor | str | None] ``` The twelve theme colours, keyed by slot name, in schema order. #### typeface ``` typeface(theme_token: str) -> str | None ``` The concrete typeface `theme_token` names, or `None` when there is none. `theme_token` is a `w:rFonts/@w:asciiTheme`-style value such as `"minorHAnsi"`. An unrecognised token, and a token whose slot the theme leaves empty, both give `None`. Source code in `src/docx/theme.py` ``` def typeface(self, theme_token: str) -> str | None: """The concrete typeface `theme_token` names, or |None| when there is none. `theme_token` is a `w:rFonts/@w:asciiTheme`-style value such as ``"minorHAnsi"``. An unrecognised token, and a token whose slot the theme leaves empty, both give |None|. """ try: collection, script = _THEME_TOKENS[theme_token] except KeyError: return None font = self.major_font if collection == "major" else self.minor_font return {"latin": font.latin, "ea": font.east_asian, "cs": font.complex_script}[script] ``` #### color ``` color(name: str) -> RGBColor | str | None ``` The RGB value of theme colour `name`, or `None` when the theme has none. `name` is one of `dk1`, `lt1`, `dk2`, `lt2`, `accent1` through `accent6`, `hlink` and `folHlink` — the slot names as they appear in the XML. A MSO_THEME_COLOR member's own spelling differs; this takes the XML one because that is what the theme part is keyed on. A system colour such as `dk1` reports the RGB value the producing application last resolved it to, which is the only concrete value available outside that operating system. Source code in `src/docx/theme.py` ``` def color(self, name: str) -> RGBColor | str | None: """The RGB value of theme colour `name`, or |None| when the theme has none. `name` is one of ``dk1``, ``lt1``, ``dk2``, ``lt2``, ``accent1`` through ``accent6``, ``hlink`` and ``folHlink`` — the slot names as they appear in the XML. A |MSO_THEME_COLOR| member's own spelling differs; this takes the XML one because that is what the theme part is keyed on. A system colour such as ``dk1`` reports the RGB value the producing application last resolved it to, which is the only concrete value available outside that operating system. """ clrScheme = self._element.themeElements.clrScheme if name not in clrScheme.slots: raise ValueError( "no theme color %r; must be one of %s" % (name, ", ".join(clrScheme.slots)) ) color = clrScheme.color(name) return None if color is None else color.rgb ``` ## types Abstract types used by `python-docx`. ### ProvidesStoryPart Bases: `Protocol` An object that provides access to the StoryPart. This type is for objects that have a story part like document or header as their root part. ### ProvidesXmlPart Bases: `Protocol` An object that provides access to its XmlPart. This type is for objects that need access to their part but it either isn't a StoryPart or they don't care, possibly because they just need access to the package or related parts. ## watermark Watermark support — the faint "DRAFT" or "CONFIDENTIAL" behind a document's content. Word implements a watermark as a VML shape inside a header, not as DrawingML. VML is deprecated in the specification, but this is what current versions of Word write and what they render correctly; a DrawingML equivalent does not display the same way, and a watermark that looks wrong is worse than none. So VML it is, and `docx.oxml.ns` carries the `v:`, `o:` and `w10:` namespaces for it. A watermark is a header artefact, so which pages show one follows from which header applies to them. Both API entry points therefore write into all three header types — default, first-page and even-page — because a watermark that vanishes on page 1 of a document with a distinct first-page header reads as a bug rather than as a setting. Scope: - Document.add_text_watermark applies the watermark to the whole document. - Section.add_text_watermark applies it to one section. Where that section's headers are inherited from an earlier one, the watermark goes into the header actually in force, which the earlier section shares — a header that is inherited is the same header, and there is no way to mark up one section's copy of it alone without first breaking the link. ### Watermark ``` Watermark(shape: _Element) ``` A watermark in a header — the faint text or image behind the document content. Not constructed directly; reached through `Section.watermark` or returned by Section.add_text_watermark and Section.add_image_watermark. Source code in `src/docx/watermark.py` ``` def __init__(self, shape: _Element): self._shape = shape ``` #### is_image ``` is_image: bool ``` True when this is an image watermark rather than a text one. #### text ``` text: str | None ``` The watermark text, or `None` for an image watermark. #### remove ``` remove() -> None ``` Remove this watermark from the document. The whole `w:pict` is removed, and the run holding it too when that leaves the run empty, so nothing is left behind that Word would render as a stray space. Source code in `src/docx/watermark.py` ``` def remove(self) -> None: """Remove this watermark from the document. The whole `w:pict` is removed, and the run holding it too when that leaves the run empty, so nothing is left behind that Word would render as a stray space. """ pict = self._shape.getparent() if pict is None: return r = pict.getparent() if r is None: return r.remove(pict) if r.tag == qn("w:r") and len(r.xpath("./*[not(self::w:rPr)]")) == 0: parent = r.getparent() if parent is not None: parent.remove(r) ``` ### iter_watermarks ``` iter_watermarks( hdrftr: _BaseHeaderFooter, ) -> Iterator[Watermark] ``` Generate a Watermark for each watermark shape in `hdrftr`. Source code in `src/docx/watermark.py` ``` def iter_watermarks(hdrftr: _BaseHeaderFooter) -> Iterator[Watermark]: """Generate a |Watermark| for each watermark shape in `hdrftr`.""" for shape in hdrftr.part.element.xpath( f'.//w:pict/v:shape[starts-with(@id, "{_TEXT_SHAPE_ID}")]' f' | .//w:pict/v:shape[starts-with(@id, "{_IMAGE_SHAPE_ID}")]' ): yield Watermark(shape) ``` ### iter_watermark_headers ``` iter_watermark_headers( sections: Iterable[Section], ) -> Iterator[_BaseHeaderFooter] ``` Generate the header objects a watermark should be written into for `sections`. All three header types of each section, skipping any whose definition has already been generated. A header inherited from an earlier section *is* that earlier section's header, so writing to both would give it two watermarks. Source code in `src/docx/watermark.py` ``` def iter_watermark_headers(sections: Iterable[Section]) -> Iterator[_BaseHeaderFooter]: """Generate the header objects a watermark should be written into for `sections`. All three header types of each section, skipping any whose definition has already been generated. A header inherited from an earlier section *is* that earlier section's header, so writing to both would give it two watermarks. """ seen: List[int] = [] for section in sections: for header in (section.header, section.first_page_header, section.even_page_header): part_id = id(header.part) if part_id in seen: continue seen.append(part_id) yield header ``` ### add_text_watermark ``` add_text_watermark( headers: Iterable[_BaseHeaderFooter], text: str, font: str = "Calibri", font_size: Length | int | None = None, color: str = "C0C0C0", opacity: float | None = None, angle: float = 315, width: Length | int = _DEFAULT_WIDTH, height: Length | int = _DEFAULT_HEIGHT, bold: bool = False, italic: bool = False, ) -> List[Watermark] ``` Add a text watermark to each of `headers`, returning the watermarks added. Source code in `src/docx/watermark.py` ``` def add_text_watermark( headers: Iterable[_BaseHeaderFooter], text: str, font: str = "Calibri", font_size: Length | int | None = None, color: str = "C0C0C0", opacity: float | None = None, angle: float = 315, width: Length | int = _DEFAULT_WIDTH, height: Length | int = _DEFAULT_HEIGHT, bold: bool = False, italic: bool = False, ) -> List[Watermark]: """Add a text watermark to each of `headers`, returning the watermarks added.""" return [ Watermark( _add_shape( header, _TEXT_SHAPETYPE_XML, _text_shape_xml( text, font, font_size, color, opacity, angle, width, height, bold, italic ), ) ) for header in headers ] ``` ### add_image_watermark ``` add_image_watermark( headers: Iterable[_BaseHeaderFooter], image_descriptor: str | PathLike[str] | IO[bytes], width: Length | int | None = None, height: Length | int | None = None, washout: bool = True, scale: float = 1.0, ) -> List[Watermark] ``` Add an image watermark to each of `headers`, returning the watermarks added. The image is related to each header part separately, since a relationship belongs to the part that refers to it. Source code in `src/docx/watermark.py` ``` def add_image_watermark( headers: Iterable[_BaseHeaderFooter], image_descriptor: str | os.PathLike[str] | IO[bytes], width: Length | int | None = None, height: Length | int | None = None, washout: bool = True, scale: float = 1.0, ) -> List[Watermark]: """Add an image watermark to each of `headers`, returning the watermarks added. The image is related to each header part separately, since a relationship belongs to the part that refers to it. """ watermarks: List[Watermark] = [] for header in headers: rId, image = header.part.get_or_add_image(image_descriptor) cx, cy = image.scaled_dimensions(width, height) watermarks.append( Watermark( _add_shape( header, _IMAGE_SHAPETYPE_XML, _image_shape_xml( rId, image.filename, Emu(int(cx * scale)), Emu(int(cy * scale)), washout ), ) ) ) return watermarks ``` ### remove_watermarks ``` remove_watermarks( headers: Iterable[_BaseHeaderFooter], ) -> int ``` Remove every watermark from each of `headers`, returning how many were removed. Source code in `src/docx/watermark.py` ``` def remove_watermarks(headers: Iterable[_BaseHeaderFooter]) -> int: """Remove every watermark from each of `headers`, returning how many were removed.""" removed = 0 for header in headers: for watermark in list(iter_watermarks(header)): watermark.remove() removed += 1 return removed ``` ## dml ## color DrawingML objects related to color, ColorFormat being the most prominent. ### ColorFormat ``` ColorFormat(rPr_parent: RPrParent) ``` Bases: `ElementProxy` Provides access to color settings like RGB color, theme color, and luminance adjustments. Source code in `src/docx/dml/color.py` ``` def __init__(self, rPr_parent: RPrParent): super(ColorFormat, self).__init__(rPr_parent) self._element = rPr_parent ``` #### rgb ``` rgb: RGBColor | None ``` An RGBColor value or `None` if no RGB color is specified. When type is `MSO_COLOR_TYPE.RGB`, the value of this property will always be an RGBColor value. It may also be an RGBColor value if type is `MSO_COLOR_TYPE.THEME`, as Word writes the current value of a theme color when one is assigned. In that case, the RGB value should be interpreted as no more than a good guess however, as the theme color takes precedence at rendering time. Its value is `None` whenever type is either `None` or `MSO_COLOR_TYPE.AUTO`. Assigning an RGBColor value causes type to become `MSO_COLOR_TYPE.RGB` and any theme color is removed. Assigning `None` causes any color to be removed such that the effective color is inherited from the style hierarchy. #### theme_color ``` theme_color: MSO_THEME_COLOR | None ``` Member of MsoThemeColorIndex or `None` if no theme color is specified. When type is `MSO_COLOR_TYPE.THEME`, the value of this property will always be a member of MsoThemeColorIndex. When type has any other value, the value of this property is `None`. Assigning a member of MsoThemeColorIndex causes type to become `MSO_COLOR_TYPE.THEME`. Any existing RGB value is retained but ignored by Word. Assigning `None` causes any color specification to be removed such that the effective color is inherited from the style hierarchy. #### type ``` type: MSO_COLOR_TYPE | None ``` Read-only. A member of MsoColorType, one of RGB, THEME, or AUTO, corresponding to the way this color is defined. Its value is `None` if no color is applied at this level, which causes the effective color to be inherited from the style hierarchy. ## drawing DrawingML-related objects are in this subpackage. ### Drawing ``` Drawing(drawing: CT_Drawing, parent: ProvidesStoryPart) ``` Bases: `Parented` Container for a DrawingML object. Source code in `src/docx/drawing/__init__.py` ``` def __init__(self, drawing: CT_Drawing, parent: t.ProvidesStoryPart): super().__init__(parent) self._parent = parent self._drawing = self._element = drawing ``` #### has_picture ``` has_picture: bool ``` True when `drawing` contains an embedded picture. A drawing can contain a picture, but it can also contain a chart, SmartArt, or a drawing canvas. Methods related to a picture, like `.image`, will raise when the drawing does not contain a picture. Use this value to determine whether image methods will succeed. This value is `False` when a linked picture is present. This should be relatively rare and the image would only be retrievable from the filesystem. Note this does not distinguish between inline and floating images. The presence of either one will cause this value to be `True`. #### image ``` image: Image ``` An `Image` proxy object for the image in this (picture) drawing. Raises `ValueError` when this drawing does contains something other than a picture. Use `.has_picture` to qualify drawing objects before using this property. ## enum ## base Base classes and other objects used by enumerations. ### BaseEnum Bases: `int`, `Enum` Base class for Enums that do not map XML attr values. The enum's value will be an integer, corresponding to the integer assigned the corresponding member in the MS API enum of the same name. ### BaseXmlEnum Bases: `int`, `Enum` Base class for Enums that also map XML attr values. The enum's value will be an integer, corresponding to the integer assigned the corresponding member in the MS API enum of the same name. #### from_xml ``` from_xml(xml_value: str | None) -> Self ``` Enumeration member corresponding to XML attribute value `xml_value`. Example: ``` >>> WD_PARAGRAPH_ALIGNMENT.from_xml("center") WD_PARAGRAPH_ALIGNMENT.CENTER ``` Source code in `src/docx/enum/base.py` ``` @classmethod def from_xml(cls, xml_value: str | None) -> Self: """Enumeration member corresponding to XML attribute value `xml_value`. Example:: >>> WD_PARAGRAPH_ALIGNMENT.from_xml("center") WD_PARAGRAPH_ALIGNMENT.CENTER """ member = next((member for member in cls if member.xml_value == xml_value), None) if member is None: raise ValueError(f"{cls.__name__} has no XML mapping for '{xml_value}'") return member ``` #### to_xml ``` to_xml(value: int | _T | None) -> str | None ``` XML value of this enum member, generally an XML attribute value. Source code in `src/docx/enum/base.py` ``` @classmethod def to_xml(cls: Type[_T], value: int | _T | None) -> str | None: """XML value of this enum member, generally an XML attribute value.""" # -- presence of multi-arg `__new__()` method fools type-checker, but getting a # -- member by its value using EnumCls(val) works as usual. member = cls(value) xml_value = member.xml_value if not xml_value: raise ValueError(f"{cls.__name__}.{member.name} has no XML representation") return xml_value ``` ### DocsPageFormatter ``` DocsPageFormatter(clsname: str, clsdict: Dict[str, Any]) ``` Generate an .rst doc page for an enumeration. Formats a RestructuredText documention page (string) for the enumeration class parts passed to the constructor. An immutable one-shot service object. Source code in `src/docx/enum/base.py` ``` def __init__(self, clsname: str, clsdict: Dict[str, Any]): self._clsname = clsname self._clsdict = clsdict ``` #### page_str ``` page_str ``` The RestructuredText documentation page for the enumeration. This is the only API member for the class. ## dml Enumerations used by DrawingML objects. ### MSO_COLOR_TYPE Bases: `BaseEnum` Specifies the color specification scheme. Example: ``` from docx.enum.dml import MSO_COLOR_TYPE assert font.color.type == MSO_COLOR_TYPE.SCHEME ``` MS API name: `MsoColorType` http://msdn.microsoft.com/en-us/library/office/ff864912(v=office.15).aspx #### RGB ``` RGB = (1, 'Color is specified by an |RGBColor| value.') ``` Color is specified by an RGBColor value. #### THEME ``` THEME = (2, 'Color is one of the preset theme colors.') ``` Color is one of the preset theme colors. #### AUTO ``` AUTO = ( 101, "Color is determined automatically by the application.", ) ``` Color is determined automatically by the application. ### MSO_THEME_COLOR_INDEX Bases: `BaseXmlEnum` Indicates the Office theme color, one of those shown in the color gallery on the formatting ribbon. Alias: `MSO_THEME_COLOR` Example: ``` from docx.enum.dml import MSO_THEME_COLOR font.color.theme_color = MSO_THEME_COLOR.ACCENT_1 ``` MS API name: `MsoThemeColorIndex` http://msdn.microsoft.com/en-us/library/office/ff860782(v=office.15).aspx #### NOT_THEME_COLOR ``` NOT_THEME_COLOR = ( 0, "UNMAPPED", "Indicates the color is not a theme color.", ) ``` Indicates the color is not a theme color. #### ACCENT_1 ``` ACCENT_1 = ( 5, "accent1", "Specifies the Accent 1 theme color.", ) ``` Specifies the Accent 1 theme color. #### ACCENT_2 ``` ACCENT_2 = ( 6, "accent2", "Specifies the Accent 2 theme color.", ) ``` Specifies the Accent 2 theme color. #### ACCENT_3 ``` ACCENT_3 = ( 7, "accent3", "Specifies the Accent 3 theme color.", ) ``` Specifies the Accent 3 theme color. #### ACCENT_4 ``` ACCENT_4 = ( 8, "accent4", "Specifies the Accent 4 theme color.", ) ``` Specifies the Accent 4 theme color. #### ACCENT_5 ``` ACCENT_5 = ( 9, "accent5", "Specifies the Accent 5 theme color.", ) ``` Specifies the Accent 5 theme color. #### ACCENT_6 ``` ACCENT_6 = ( 10, "accent6", "Specifies the Accent 6 theme color.", ) ``` Specifies the Accent 6 theme color. #### BACKGROUND_1 ``` BACKGROUND_1 = ( 14, "background1", "Specifies the Background 1 theme color.", ) ``` Specifies the Background 1 theme color. #### BACKGROUND_2 ``` BACKGROUND_2 = ( 16, "background2", "Specifies the Background 2 theme color.", ) ``` Specifies the Background 2 theme color. #### DARK_1 ``` DARK_1 = (1, 'dark1', 'Specifies the Dark 1 theme color.') ``` Specifies the Dark 1 theme color. #### DARK_2 ``` DARK_2 = (3, 'dark2', 'Specifies the Dark 2 theme color.') ``` Specifies the Dark 2 theme color. #### FOLLOWED_HYPERLINK ``` FOLLOWED_HYPERLINK = ( 12, "followedHyperlink", "Specifies the theme color for a clicked hyperlink.", ) ``` Specifies the theme color for a clicked hyperlink. #### HYPERLINK ``` HYPERLINK = ( 11, "hyperlink", "Specifies the theme color for a hyperlink.", ) ``` Specifies the theme color for a hyperlink. #### LIGHT_1 ``` LIGHT_1 = ( 2, "light1", "Specifies the Light 1 theme color.", ) ``` Specifies the Light 1 theme color. #### LIGHT_2 ``` LIGHT_2 = ( 4, "light2", "Specifies the Light 2 theme color.", ) ``` Specifies the Light 2 theme color. #### TEXT_1 ``` TEXT_1 = (13, 'text1', 'Specifies the Text 1 theme color.') ``` Specifies the Text 1 theme color. #### TEXT_2 ``` TEXT_2 = (15, 'text2', 'Specifies the Text 2 theme color.') ``` Specifies the Text 2 theme color. ## numbering Enumerations related to list numbering in WordprocessingML files. ### WD_NUMBER_FORMAT Bases: `BaseXmlEnum` Specifies how a numbering level renders its counter. Corresponds to `ST_NumberFormat` in ISO/IEC 29500 §17.18.59. The specification defines a long tail of locale-specific formats; the ones this library can actually render a number for are marked below. For the rest, and for any value not listed here at all, NumberingLevel.format_number falls back to decimal — a wrong number is less useful than an obviously plain one, but the alternative of raising would make a document using Japanese counting unreadable rather than imperfect. MS API name: `WdListNumberStyle` (the closest equivalent; the mapping is not exact) #### DECIMAL ``` DECIMAL = ( 0, "decimal", "Arabic numerals: 1, 2, 3. Rendered.", ) ``` Arabic numerals: 1, 2, 3. #### UPPER_ROMAN ``` UPPER_ROMAN = ( 1, "upperRoman", "Upper-case Roman numerals: I, II, III. Rendered.", ) ``` Upper-case Roman numerals: I, II, III. #### LOWER_ROMAN ``` LOWER_ROMAN = ( 2, "lowerRoman", "Lower-case Roman numerals: i, ii, iii. Rendered.", ) ``` Lower-case Roman numerals: i, ii, iii. #### UPPER_LETTER ``` UPPER_LETTER = ( 3, "upperLetter", "Upper-case letters: A, B, C, continuing AA, BB, CC after Z. Rendered.", ) ``` Upper-case letters: A, B, C, continuing AA, BB, CC after Z. #### LOWER_LETTER ``` LOWER_LETTER = ( 4, "lowerLetter", "Lower-case letters: a, b, c, continuing aa, bb, cc after z. Rendered.", ) ``` Lower-case letters: a, b, c, continuing aa, bb, cc after z. #### ORDINAL ``` ORDINAL = ( 5, "ordinal", "Ordinal numerals: 1st, 2nd, 3rd. Rendered.", ) ``` Ordinal numerals: 1st, 2nd, 3rd. #### CARDINAL_TEXT ``` CARDINAL_TEXT = ( 6, "cardinalText", "Cardinal text: One, Two, Three. Not rendered.", ) ``` Cardinal text: One, Two, Three. #### ORDINAL_TEXT ``` ORDINAL_TEXT = ( 7, "ordinalText", "Ordinal text: First, Second, Third. Not rendered.", ) ``` Ordinal text: First, Second, Third. #### HEX ``` HEX = ( 8, "hex", "Hexadecimal numerals: 8, 9, A, B. Rendered.", ) ``` Hexadecimal numerals: 8, 9, A, B. #### CHICAGO ``` CHICAGO = ( 9, "chicago", "The Chicago Manual of Style sequence of footnote marks: *, †, ‡, §. Rendered.", ) ``` The Chicago Manual of Style footnote marks. #### DECIMAL_ZERO ``` DECIMAL_ZERO = ( 10, "decimalZero", "Arabic numerals with a leading zero below ten: 01, 02, 03. Rendered.", ) ``` Arabic numerals with a leading zero below ten: 01, 02, 03. #### BULLET ``` BULLET = ( 11, "bullet", "A bullet rather than a number. The character shown is the literal level text, so the counter is not rendered at all.", ) ``` A bullet rather than a number; the level text is shown literally. #### NONE ``` NONE = (12, "none", "No numbering is shown for this level.") ``` No numbering is shown for this level. #### RUSSIAN_LOWER ``` RUSSIAN_LOWER = ( 13, "russianLower", "Lower-case Cyrillic letters. Not rendered.", ) ``` Lower-case Cyrillic letters. #### RUSSIAN_UPPER ``` RUSSIAN_UPPER = ( 14, "russianUpper", "Upper-case Cyrillic letters. Not rendered.", ) ``` Upper-case Cyrillic letters. #### IDEOGRAPH_DIGITAL ``` IDEOGRAPH_DIGITAL = ( 15, "ideographDigital", "Chinese numerals. Not rendered.", ) ``` Chinese numerals. #### JAPANESE_COUNTING ``` JAPANESE_COUNTING = ( 16, "japaneseCounting", "Japanese counting. Not rendered.", ) ``` Japanese counting. #### AIUEO ``` AIUEO = ( 17, "aiueo", "Japanese aiueo ordering. Not rendered.", ) ``` Japanese aiueo ordering. #### IROHA ``` IROHA = ( 18, "iroha", "Japanese iroha ordering. Not rendered.", ) ``` Japanese iroha ordering. #### DECIMAL_FULL_WIDTH ``` DECIMAL_FULL_WIDTH = ( 19, "decimalFullWidth", "Full-width Arabic numerals. Not rendered.", ) ``` Full-width Arabic numerals. #### DECIMAL_HALF_WIDTH ``` DECIMAL_HALF_WIDTH = ( 20, "decimalHalfWidth", "Half-width Arabic numerals. Rendered.", ) ``` Half-width Arabic numerals. #### GANADA ``` GANADA = ( 21, "ganada", "Korean ganada ordering. Not rendered.", ) ``` Korean ganada ordering. #### CHOSUNG ``` CHOSUNG = ( 22, "chosung", "Korean chosung ordering. Not rendered.", ) ``` Korean chosung ordering. ## revision Enumerations related to tracked changes in WordprocessingML files. ### WD_REVISION_TYPE Bases: `BaseXmlEnum` Specifies what kind of change a tracked revision records. Example: ``` from docx.enum.revision import WD_REVISION_TYPE for revision in document.revisions: if revision.type == WD_REVISION_TYPE.DELETION: revision.reject() ``` MS API name: `WdRevisionType` https://learn.microsoft.com/en-us/office/vba/api/word.wdrevisiontype #### INSERTION ``` INSERTION = (1, 'ins', 'Content was added.') ``` Content was added. #### DELETION ``` DELETION = (2, 'del', 'Content was removed.') ``` Content was removed. #### MOVE_FROM ``` MOVE_FROM = ( 5, "moveFrom", "Content was moved away from here. The deletion half of a move; the matching MOVE_TO holds the same content where it now is.", ) ``` Content was moved away from here — the deletion half of a move. #### MOVE_TO ``` MOVE_TO = ( 6, "moveTo", "Content was moved to here. The insertion half of a move.", ) ``` Content was moved to here — the insertion half of a move. #### FORMATTING ``` FORMATTING = ( 3, None, "Formatting was changed. The revision records the properties as they were before, which is what rejecting it puts back. Covers `w:rPrChange`, `w:pPrChange` and the other `*Change` elements, which differ only in which properties they record.", ) ``` Formatting was changed; the revision records the previous properties. ## section Enumerations related to the main document in WordprocessingML files. ### WD_HEADER_FOOTER_INDEX Bases: `BaseXmlEnum` Alias: **WD_HEADER_FOOTER** Specifies one of the three possible header/footer definitions for a section. For internal use only; not part of the python-docx API. MS API name: `WdHeaderFooterIndex` URL: https://docs.microsoft.com/en-us/office/vba/api/word.wdheaderfooterindex #### PRIMARY ``` PRIMARY = ( 1, "default", "Header for odd pages or all if no even header.", ) ``` Header for odd pages or all if no even header. #### FIRST_PAGE ``` FIRST_PAGE = ( 2, "first", "Header for first page of section.", ) ``` Header for first page of section. #### EVEN_PAGE ``` EVEN_PAGE = ( 3, "even", "Header for even pages of recto/verso section.", ) ``` Header for even pages of recto/verso section. ### WD_ORIENTATION Bases: `BaseXmlEnum` Alias: **WD_ORIENT** Specifies the page layout orientation. Example: ``` from docx.enum.section import WD_ORIENT section = document.sections[-1] section.orientation = WD_ORIENT.LANDSCAPE ``` MS API name: `WdOrientation` MS API URL: http://msdn.microsoft.com/en-us/library/office/ff837902.aspx #### PORTRAIT ``` PORTRAIT = (0, 'portrait', 'Portrait orientation.') ``` Portrait orientation. #### LANDSCAPE ``` LANDSCAPE = (1, 'landscape', 'Landscape orientation.') ``` Landscape orientation. ### WD_SECTION_START Bases: `BaseXmlEnum` Alias: **WD_SECTION** Specifies the start type of a section break. Example: ``` from docx.enum.section import WD_SECTION section = document.sections[0] section.start_type = WD_SECTION.NEW_PAGE ``` MS API name: `WdSectionStart` MS API URL: http://msdn.microsoft.com/en-us/library/office/ff840975.aspx #### CONTINUOUS ``` CONTINUOUS = (0, 'continuous', 'Continuous section break.') ``` Continuous section break. #### NEW_COLUMN ``` NEW_COLUMN = (1, 'nextColumn', 'New column section break.') ``` New column section break. #### NEW_PAGE ``` NEW_PAGE = (2, 'nextPage', 'New page section break.') ``` New page section break. #### EVEN_PAGE ``` EVEN_PAGE = (3, 'evenPage', 'Even pages section break.') ``` Even pages section break. #### ODD_PAGE ``` ODD_PAGE = ( 4, "oddPage", "Section begins on next odd page.", ) ``` Section begins on next odd page. ## shape Enumerations related to DrawingML shapes in WordprocessingML files. ### WD_INLINE_SHAPE_TYPE Bases: `Enum` Corresponds to WdInlineShapeType enumeration. http://msdn.microsoft.com/en-us/library/office/ff192587.aspx. ### WD_WRAP_TYPE Bases: `BaseXmlEnum` Specifies how text wraps around a floating (anchored) shape. Example: ``` from docx.enum.shape import WD_WRAP_TYPE shape = paragraph.add_float_picture("logo.png") shape.wrap_type = WD_WRAP_TYPE.SQUARE ``` MS API name: `WdWrapType` https://learn.microsoft.com/en-us/office/vba/api/word.wdwraptype #### NONE ``` NONE = ( 3, "wrapNone", "Text does not wrap. The shape floats over the text, or behind it when `behind_text` is set, which is how a watermark is placed.", ) ``` Text does not wrap; the shape floats over or behind it. #### SQUARE ``` SQUARE = ( 0, "wrapSquare", "Text wraps around the shape's bounding rectangle.", ) ``` Text wraps around the shape's bounding rectangle. #### TIGHT ``` TIGHT = ( 1, "wrapTight", "Text wraps around the shape's outline rather than its bounding rectangle, following the wrap polygon Word derives from the image.", ) ``` Text wraps around the shape's outline rather than its bounding rectangle. #### THROUGH ``` THROUGH = ( 2, "wrapThrough", "As TIGHT, but text also flows into any open region within the outline.", ) ``` As TIGHT, but text also flows into open regions within the outline. #### TOP_BOTTOM ``` TOP_BOTTOM = ( 4, "wrapTopAndBottom", "Text stops above the shape and resumes below it, leaving the sides clear.", ) ``` Text stops above the shape and resumes below it. ### WD_ANCHOR_RELATIVE_FROM_H Bases: `BaseXmlEnum` What the horizontal position of a floating shape is measured from. MS API name: `WdRelativeHorizontalPosition` #### MARGIN ``` MARGIN = (0, 'margin', 'Relative to the text margin.') ``` Relative to the text margin. #### PAGE ``` PAGE = (1, 'page', 'Relative to the edge of the page.') ``` Relative to the edge of the page. #### COLUMN ``` COLUMN = (2, 'column', 'Relative to the text column.') ``` Relative to the text column. #### CHARACTER ``` CHARACTER = ( 3, "character", "Relative to the character the anchor sits at.", ) ``` Relative to the character the anchor sits at. #### LEFT_MARGIN ``` LEFT_MARGIN = ( 4, "leftMargin", "Relative to the left margin.", ) ``` Relative to the left margin. #### RIGHT_MARGIN ``` RIGHT_MARGIN = ( 5, "rightMargin", "Relative to the right margin.", ) ``` Relative to the right margin. #### INSIDE_MARGIN ``` INSIDE_MARGIN = ( 6, "insideMargin", "Relative to the inside margin — the left margin on an odd page and the right margin on an even one, in a document laid out for double-sided printing.", ) ``` Relative to the inside margin of a double-sided layout. #### OUTSIDE_MARGIN ``` OUTSIDE_MARGIN = ( 7, "outsideMargin", "Relative to the outside margin.", ) ``` Relative to the outside margin of a double-sided layout. ### WD_ANCHOR_RELATIVE_FROM_V Bases: `BaseXmlEnum` What the vertical position of a floating shape is measured from. MS API name: `WdRelativeVerticalPosition` #### MARGIN ``` MARGIN = (0, 'margin', 'Relative to the text margin.') ``` Relative to the text margin. #### PAGE ``` PAGE = (1, 'page', 'Relative to the edge of the page.') ``` Relative to the edge of the page. #### PARAGRAPH ``` PARAGRAPH = ( 2, "paragraph", "Relative to the paragraph the anchor sits in.", ) ``` Relative to the paragraph the anchor sits in. #### LINE ``` LINE = ( 3, "line", "Relative to the line the anchor sits on.", ) ``` Relative to the line the anchor sits on. #### TOP_MARGIN ``` TOP_MARGIN = (4, "topMargin", "Relative to the top margin.") ``` Relative to the top margin. #### BOTTOM_MARGIN ``` BOTTOM_MARGIN = ( 5, "bottomMargin", "Relative to the bottom margin.", ) ``` Relative to the bottom margin. #### INSIDE_MARGIN ``` INSIDE_MARGIN = ( 6, "insideMargin", "Relative to the inside margin.", ) ``` Relative to the inside margin of a double-sided layout. #### OUTSIDE_MARGIN ``` OUTSIDE_MARGIN = ( 7, "outsideMargin", "Relative to the outside margin.", ) ``` Relative to the outside margin of a double-sided layout. ### WD_ANCHOR_ALIGN_H Bases: `BaseXmlEnum` Horizontal alignment of a floating shape within what it is positioned against. An alternative to an absolute offset: `LEFT` against `WD_ANCHOR_RELATIVE_FROM_H.PAGE` puts the shape at the left edge of the page whatever the page size turns out to be. #### LEFT ``` LEFT = (0, 'left', 'Aligned to the left edge.') ``` Aligned to the left edge. #### CENTER ``` CENTER = (1, 'center', 'Centred.') ``` Centred. #### RIGHT ``` RIGHT = (2, 'right', 'Aligned to the right edge.') ``` Aligned to the right edge. #### INSIDE ``` INSIDE = ( 3, "inside", "Aligned to the inside edge of a double-sided layout.", ) ``` Aligned to the inside edge of a double-sided layout. #### OUTSIDE ``` OUTSIDE = ( 4, "outside", "Aligned to the outside edge of a double-sided layout.", ) ``` Aligned to the outside edge of a double-sided layout. ### WD_ANCHOR_ALIGN_V Bases: `BaseXmlEnum` Vertical alignment of a floating shape within what it is positioned against. #### TOP ``` TOP = (0, 'top', 'Aligned to the top edge.') ``` Aligned to the top edge. #### CENTER ``` CENTER = (1, 'center', 'Centred.') ``` Centred. #### BOTTOM ``` BOTTOM = (2, 'bottom', 'Aligned to the bottom edge.') ``` Aligned to the bottom edge. #### INSIDE ``` INSIDE = ( 3, "inside", "Aligned to the inside edge of a double-sided layout.", ) ``` Aligned to the inside edge of a double-sided layout. #### OUTSIDE ``` OUTSIDE = ( 4, "outside", "Aligned to the outside edge of a double-sided layout.", ) ``` Aligned to the outside edge of a double-sided layout. ## style Enumerations related to styles. ### WD_BUILTIN_STYLE Bases: `BaseEnum` Alias: **WD_STYLE** Specifies a built-in Microsoft Word style. Example: ``` from docx import Document from docx.enum.style import WD_STYLE document = Document() styles = document.styles style = styles[WD_STYLE.BODY_TEXT] ``` MS API name: `WdBuiltinStyle` http://msdn.microsoft.com/en-us/library/office/ff835210.aspx #### BLOCK_QUOTATION ``` BLOCK_QUOTATION = (-85, 'Block Text.') ``` Block Text. #### BODY_TEXT ``` BODY_TEXT = (-67, 'Body Text.') ``` Body Text. #### BODY_TEXT_2 ``` BODY_TEXT_2 = (-81, 'Body Text 2.') ``` Body Text 2. #### BODY_TEXT_3 ``` BODY_TEXT_3 = (-82, 'Body Text 3.') ``` Body Text 3. #### BODY_TEXT_FIRST_INDENT ``` BODY_TEXT_FIRST_INDENT = (-78, 'Body Text First Indent.') ``` Body Text First Indent. #### BODY_TEXT_FIRST_INDENT_2 ``` BODY_TEXT_FIRST_INDENT_2 = ( -79, "Body Text First Indent 2.", ) ``` Body Text First Indent 2. #### BODY_TEXT_INDENT ``` BODY_TEXT_INDENT = (-68, 'Body Text Indent.') ``` Body Text Indent. #### BODY_TEXT_INDENT_2 ``` BODY_TEXT_INDENT_2 = (-83, 'Body Text Indent 2.') ``` Body Text Indent 2. #### BODY_TEXT_INDENT_3 ``` BODY_TEXT_INDENT_3 = (-84, 'Body Text Indent 3.') ``` Body Text Indent 3. #### BOOK_TITLE ``` BOOK_TITLE = (-265, 'Book Title.') ``` Book Title. #### CAPTION ``` CAPTION = (-35, 'Caption.') ``` Caption. #### CLOSING ``` CLOSING = (-64, 'Closing.') ``` Closing. #### COMMENT_REFERENCE ``` COMMENT_REFERENCE = (-40, 'Comment Reference.') ``` Comment Reference. #### COMMENT_TEXT ``` COMMENT_TEXT = (-31, 'Comment Text.') ``` Comment Text. #### DATE ``` DATE = (-77, 'Date.') ``` Date. #### DEFAULT_PARAGRAPH_FONT ``` DEFAULT_PARAGRAPH_FONT = (-66, 'Default Paragraph Font.') ``` Default Paragraph Font. #### EMPHASIS ``` EMPHASIS = (-89, 'Emphasis.') ``` Emphasis. #### ENDNOTE_REFERENCE ``` ENDNOTE_REFERENCE = (-43, 'Endnote Reference.') ``` Endnote Reference. #### ENDNOTE_TEXT ``` ENDNOTE_TEXT = (-44, 'Endnote Text.') ``` Endnote Text. #### ENVELOPE_ADDRESS ``` ENVELOPE_ADDRESS = (-37, 'Envelope Address.') ``` Envelope Address. #### ENVELOPE_RETURN ``` ENVELOPE_RETURN = (-38, 'Envelope Return.') ``` Envelope Return. #### FOOTER ``` FOOTER = (-33, 'Footer.') ``` Footer. #### FOOTNOTE_REFERENCE ``` FOOTNOTE_REFERENCE = (-39, 'Footnote Reference.') ``` Footnote Reference. #### FOOTNOTE_TEXT ``` FOOTNOTE_TEXT = (-30, 'Footnote Text.') ``` Footnote Text. #### HEADER ``` HEADER = (-32, 'Header.') ``` Header. #### HEADING_1 ``` HEADING_1 = (-2, 'Heading 1.') ``` Heading 1. #### HEADING_2 ``` HEADING_2 = (-3, 'Heading 2.') ``` Heading 2. #### HEADING_3 ``` HEADING_3 = (-4, 'Heading 3.') ``` Heading 3. #### HEADING_4 ``` HEADING_4 = (-5, 'Heading 4.') ``` Heading 4. #### HEADING_5 ``` HEADING_5 = (-6, 'Heading 5.') ``` Heading 5. #### HEADING_6 ``` HEADING_6 = (-7, 'Heading 6.') ``` Heading 6. #### HEADING_7 ``` HEADING_7 = (-8, 'Heading 7.') ``` Heading 7. #### HEADING_8 ``` HEADING_8 = (-9, 'Heading 8.') ``` Heading 8. #### HEADING_9 ``` HEADING_9 = (-10, 'Heading 9.') ``` Heading 9. #### HTML_ACRONYM ``` HTML_ACRONYM = (-96, 'HTML Acronym.') ``` HTML Acronym. #### HTML_ADDRESS ``` HTML_ADDRESS = (-97, 'HTML Address.') ``` HTML Address. #### HTML_CITE ``` HTML_CITE = (-98, 'HTML Cite.') ``` HTML Cite. #### HTML_CODE ``` HTML_CODE = (-99, 'HTML Code.') ``` HTML Code. #### HTML_DFN ``` HTML_DFN = (-100, 'HTML Definition.') ``` HTML Definition. #### HTML_KBD ``` HTML_KBD = (-101, 'HTML Keyboard.') ``` HTML Keyboard. #### HTML_NORMAL ``` HTML_NORMAL = (-95, 'Normal (Web).') ``` Normal (Web). #### HTML_PRE ``` HTML_PRE = (-102, 'HTML Preformatted.') ``` HTML Preformatted. #### HTML_SAMP ``` HTML_SAMP = (-103, 'HTML Sample.') ``` HTML Sample. #### HTML_TT ``` HTML_TT = (-104, 'HTML Typewriter.') ``` HTML Typewriter. #### HTML_VAR ``` HTML_VAR = (-105, 'HTML Variable.') ``` HTML Variable. #### HYPERLINK ``` HYPERLINK = (-86, 'Hyperlink.') ``` Hyperlink. #### HYPERLINK_FOLLOWED ``` HYPERLINK_FOLLOWED = (-87, 'Followed Hyperlink.') ``` Followed Hyperlink. #### INDEX_1 ``` INDEX_1 = (-11, 'Index 1.') ``` Index 1. #### INDEX_2 ``` INDEX_2 = (-12, 'Index 2.') ``` Index 2. #### INDEX_3 ``` INDEX_3 = (-13, 'Index 3.') ``` Index 3. #### INDEX_4 ``` INDEX_4 = (-14, 'Index 4.') ``` Index 4. #### INDEX_5 ``` INDEX_5 = (-15, 'Index 5.') ``` Index 5. #### INDEX_6 ``` INDEX_6 = (-16, 'Index 6.') ``` Index 6. #### INDEX_7 ``` INDEX_7 = (-17, 'Index 7.') ``` Index 7. #### INDEX_8 ``` INDEX_8 = (-18, 'Index 8.') ``` Index 8. #### INDEX_9 ``` INDEX_9 = (-19, 'Index 9.') ``` Index 9. #### INDEX_HEADING ``` INDEX_HEADING = (-34, 'Index Heading') ``` Index Heading #### INTENSE_EMPHASIS ``` INTENSE_EMPHASIS = (-262, 'Intense Emphasis.') ``` Intense Emphasis. #### INTENSE_QUOTE ``` INTENSE_QUOTE = (-182, 'Intense Quote.') ``` Intense Quote. #### INTENSE_REFERENCE ``` INTENSE_REFERENCE = (-264, 'Intense Reference.') ``` Intense Reference. #### LINE_NUMBER ``` LINE_NUMBER = (-41, 'Line Number.') ``` Line Number. #### LIST ``` LIST = (-48, 'List.') ``` List. #### LIST_2 ``` LIST_2 = (-51, 'List 2.') ``` List 2. #### LIST_3 ``` LIST_3 = (-52, 'List 3.') ``` List 3. #### LIST_4 ``` LIST_4 = (-53, 'List 4.') ``` List 4. #### LIST_5 ``` LIST_5 = (-54, 'List 5.') ``` List 5. #### LIST_BULLET ``` LIST_BULLET = (-49, 'List Bullet.') ``` List Bullet. #### LIST_BULLET_2 ``` LIST_BULLET_2 = (-55, 'List Bullet 2.') ``` List Bullet 2. #### LIST_BULLET_3 ``` LIST_BULLET_3 = (-56, 'List Bullet 3.') ``` List Bullet 3. #### LIST_BULLET_4 ``` LIST_BULLET_4 = (-57, 'List Bullet 4.') ``` List Bullet 4. #### LIST_BULLET_5 ``` LIST_BULLET_5 = (-58, 'List Bullet 5.') ``` List Bullet 5. #### LIST_CONTINUE ``` LIST_CONTINUE = (-69, 'List Continue.') ``` List Continue. #### LIST_CONTINUE_2 ``` LIST_CONTINUE_2 = (-70, 'List Continue 2.') ``` List Continue 2. #### LIST_CONTINUE_3 ``` LIST_CONTINUE_3 = (-71, 'List Continue 3.') ``` List Continue 3. #### LIST_CONTINUE_4 ``` LIST_CONTINUE_4 = (-72, 'List Continue 4.') ``` List Continue 4. #### LIST_CONTINUE_5 ``` LIST_CONTINUE_5 = (-73, 'List Continue 5.') ``` List Continue 5. #### LIST_NUMBER ``` LIST_NUMBER = (-50, 'List Number.') ``` List Number. #### LIST_NUMBER_2 ``` LIST_NUMBER_2 = (-59, 'List Number 2.') ``` List Number 2. #### LIST_NUMBER_3 ``` LIST_NUMBER_3 = (-60, 'List Number 3.') ``` List Number 3. #### LIST_NUMBER_4 ``` LIST_NUMBER_4 = (-61, 'List Number 4.') ``` List Number 4. #### LIST_NUMBER_5 ``` LIST_NUMBER_5 = (-62, 'List Number 5.') ``` List Number 5. #### LIST_PARAGRAPH ``` LIST_PARAGRAPH = (-180, 'List Paragraph.') ``` List Paragraph. #### MACRO_TEXT ``` MACRO_TEXT = (-46, 'Macro Text.') ``` Macro Text. #### MESSAGE_HEADER ``` MESSAGE_HEADER = (-74, 'Message Header.') ``` Message Header. #### NAV_PANE ``` NAV_PANE = (-90, 'Document Map.') ``` Document Map. #### NORMAL ``` NORMAL = (-1, 'Normal.') ``` Normal. #### NORMAL_INDENT ``` NORMAL_INDENT = (-29, 'Normal Indent.') ``` Normal Indent. #### NORMAL_OBJECT ``` NORMAL_OBJECT = (-158, 'Normal (applied to an object).') ``` Normal (applied to an object). #### NORMAL_TABLE ``` NORMAL_TABLE = (-106, 'Normal (applied within a table).') ``` Normal (applied within a table). #### NOTE_HEADING ``` NOTE_HEADING = (-80, 'Note Heading.') ``` Note Heading. #### PAGE_NUMBER ``` PAGE_NUMBER = (-42, 'Page Number.') ``` Page Number. #### PLAIN_TEXT ``` PLAIN_TEXT = (-91, 'Plain Text.') ``` Plain Text. #### QUOTE ``` QUOTE = (-181, 'Quote.') ``` Quote. #### SALUTATION ``` SALUTATION = (-76, 'Salutation.') ``` Salutation. #### SIGNATURE ``` SIGNATURE = (-65, 'Signature.') ``` Signature. #### STRONG ``` STRONG = (-88, 'Strong.') ``` Strong. #### SUBTITLE ``` SUBTITLE = (-75, 'Subtitle.') ``` Subtitle. #### SUBTLE_EMPHASIS ``` SUBTLE_EMPHASIS = (-261, 'Subtle Emphasis.') ``` Subtle Emphasis. #### SUBTLE_REFERENCE ``` SUBTLE_REFERENCE = (-263, 'Subtle Reference.') ``` Subtle Reference. #### TABLE_COLORFUL_GRID ``` TABLE_COLORFUL_GRID = (-172, 'Colorful Grid.') ``` Colorful Grid. #### TABLE_COLORFUL_LIST ``` TABLE_COLORFUL_LIST = (-171, 'Colorful List.') ``` Colorful List. #### TABLE_COLORFUL_SHADING ``` TABLE_COLORFUL_SHADING = (-170, 'Colorful Shading.') ``` Colorful Shading. #### TABLE_DARK_LIST ``` TABLE_DARK_LIST = (-169, 'Dark List.') ``` Dark List. #### TABLE_LIGHT_GRID ``` TABLE_LIGHT_GRID = (-161, 'Light Grid.') ``` Light Grid. #### TABLE_LIGHT_GRID_ACCENT_1 ``` TABLE_LIGHT_GRID_ACCENT_1 = (-175, 'Light Grid Accent 1.') ``` Light Grid Accent 1. #### TABLE_LIGHT_LIST ``` TABLE_LIGHT_LIST = (-160, 'Light List.') ``` Light List. #### TABLE_LIGHT_LIST_ACCENT_1 ``` TABLE_LIGHT_LIST_ACCENT_1 = (-174, 'Light List Accent 1.') ``` Light List Accent 1. #### TABLE_LIGHT_SHADING ``` TABLE_LIGHT_SHADING = (-159, 'Light Shading.') ``` Light Shading. #### TABLE_LIGHT_SHADING_ACCENT_1 ``` TABLE_LIGHT_SHADING_ACCENT_1 = ( -173, "Light Shading Accent 1.", ) ``` Light Shading Accent 1. #### TABLE_MEDIUM_GRID_1 ``` TABLE_MEDIUM_GRID_1 = (-166, 'Medium Grid 1.') ``` Medium Grid 1. #### TABLE_MEDIUM_GRID_2 ``` TABLE_MEDIUM_GRID_2 = (-167, 'Medium Grid 2.') ``` Medium Grid 2. #### TABLE_MEDIUM_GRID_3 ``` TABLE_MEDIUM_GRID_3 = (-168, 'Medium Grid 3.') ``` Medium Grid 3. #### TABLE_MEDIUM_LIST_1 ``` TABLE_MEDIUM_LIST_1 = (-164, 'Medium List 1.') ``` Medium List 1. #### TABLE_MEDIUM_LIST_1_ACCENT_1 ``` TABLE_MEDIUM_LIST_1_ACCENT_1 = ( -178, "Medium List 1 Accent 1.", ) ``` Medium List 1 Accent 1. #### TABLE_MEDIUM_LIST_2 ``` TABLE_MEDIUM_LIST_2 = (-165, 'Medium List 2.') ``` Medium List 2. #### TABLE_MEDIUM_SHADING_1 ``` TABLE_MEDIUM_SHADING_1 = (-162, 'Medium Shading 1.') ``` Medium Shading 1. #### TABLE_MEDIUM_SHADING_1_ACCENT_1 ``` TABLE_MEDIUM_SHADING_1_ACCENT_1 = ( -176, "Medium Shading 1 Accent 1.", ) ``` Medium Shading 1 Accent 1. #### TABLE_MEDIUM_SHADING_2 ``` TABLE_MEDIUM_SHADING_2 = (-163, 'Medium Shading 2.') ``` Medium Shading 2. #### TABLE_MEDIUM_SHADING_2_ACCENT_1 ``` TABLE_MEDIUM_SHADING_2_ACCENT_1 = ( -177, "Medium Shading 2 Accent 1.", ) ``` Medium Shading 2 Accent 1. #### TABLE_OF_AUTHORITIES ``` TABLE_OF_AUTHORITIES = (-45, 'Table of Authorities.') ``` Table of Authorities. #### TABLE_OF_FIGURES ``` TABLE_OF_FIGURES = (-36, 'Table of Figures.') ``` Table of Figures. #### TITLE ``` TITLE = (-63, 'Title.') ``` Title. #### TOAHEADING ``` TOAHEADING = (-47, 'TOA Heading.') ``` TOA Heading. #### TOC_1 ``` TOC_1 = (-20, 'TOC 1.') ``` TOC 1. #### TOC_2 ``` TOC_2 = (-21, 'TOC 2.') ``` TOC 2. #### TOC_3 ``` TOC_3 = (-22, 'TOC 3.') ``` TOC 3. #### TOC_4 ``` TOC_4 = (-23, 'TOC 4.') ``` TOC 4. #### TOC_5 ``` TOC_5 = (-24, 'TOC 5.') ``` TOC 5. #### TOC_6 ``` TOC_6 = (-25, 'TOC 6.') ``` TOC 6. #### TOC_7 ``` TOC_7 = (-26, 'TOC 7.') ``` TOC 7. #### TOC_8 ``` TOC_8 = (-27, 'TOC 8.') ``` TOC 8. #### TOC_9 ``` TOC_9 = (-28, 'TOC 9.') ``` TOC 9. ### WD_STYLE_TYPE Bases: `BaseXmlEnum` Specifies one of the four style types: paragraph, character, list, or table. Example: ``` from docx import Document from docx.enum.style import WD_STYLE_TYPE styles = Document().styles assert styles[0].type == WD_STYLE_TYPE.PARAGRAPH ``` MS API name: `WdStyleType` http://msdn.microsoft.com/en-us/library/office/ff196870.aspx #### CHARACTER ``` CHARACTER = (2, 'character', 'Character style.') ``` Character style. #### LIST ``` LIST = (4, 'numbering', 'List style.') ``` List style. #### PARAGRAPH ``` PARAGRAPH = (1, 'paragraph', 'Paragraph style.') ``` Paragraph style. #### TABLE ``` TABLE = (3, 'table', 'Table style.') ``` Table style. ## table Enumerations related to tables in WordprocessingML files. ### WD_CELL_VERTICAL_ALIGNMENT Bases: `BaseXmlEnum` Alias: **WD_ALIGN_VERTICAL** Specifies the vertical alignment of text in one or more cells of a table. Example: ``` from docx.enum.table import WD_ALIGN_VERTICAL table = document.add_table(3, 3) table.cell(0, 0).vertical_alignment = WD_ALIGN_VERTICAL.BOTTOM ``` MS API name: `WdCellVerticalAlignment` https://msdn.microsoft.com/en-us/library/office/ff193345.aspx #### TOP ``` TOP = ( 0, "top", "Text is aligned to the top border of the cell.", ) ``` Text is aligned to the top border of the cell. #### CENTER ``` CENTER = ( 1, "center", "Text is aligned to the center of the cell.", ) ``` Text is aligned to the center of the cell. #### BOTTOM ``` BOTTOM = ( 3, "bottom", "Text is aligned to the bottom border of the cell.", ) ``` Text is aligned to the bottom border of the cell. #### BOTH ``` BOTH = ( 101, "both", "This is an option in the OpenXml spec, but not in Word itself. It's not clear what Word behavior this setting produces. If you find out please let us know and we'll update this documentation. Otherwise, probably best to avoid this option.", ) ``` This is an option in the OpenXml spec, but not in Word itself. It's not clear what Word behavior this setting produces. If you find out please let us know and we'll update this documentation. Otherwise, probably best to avoid this option. ### WD_ROW_HEIGHT_RULE Bases: `BaseXmlEnum` Alias: **WD_ROW_HEIGHT** Specifies the rule for determining the height of a table row Example: ``` from docx.enum.table import WD_ROW_HEIGHT_RULE table = document.add_table(3, 3) table.rows[0].height_rule = WD_ROW_HEIGHT_RULE.EXACTLY ``` MS API name: `WdRowHeightRule` https://msdn.microsoft.com/en-us/library/office/ff193620.aspx #### AUTO ``` AUTO = ( 0, "auto", "The row height is adjusted to accommodate the tallest value in the row.", ) ``` The row height is adjusted to accommodate the tallest value in the row. #### AT_LEAST ``` AT_LEAST = ( 1, "atLeast", "The row height is at least a minimum specified value.", ) ``` The row height is at least a minimum specified value. #### EXACTLY ``` EXACTLY = (2, 'exact', 'The row height is an exact value.') ``` The row height is an exact value. ### WD_LINE_STYLE Bases: `BaseXmlEnum` Specifies the line style of a table or cell border. Example: ``` from docx.enum.table import WD_LINE_STYLE from docx.shared import Pt table = document.add_table(3, 3) table.borders["top"].line = WD_LINE_STYLE.SINGLE table.borders["top"].size = Pt(1) ``` Only the structural line styles are members here. The `ST_Border` schema type also admits some 170 decorative "page border art" values such as `"apples"` and `"zigZagStitch"`; those are meaningful only on a page border, and Word does not offer or write them for a table or cell border. MS API name: `WdLineStyle` https://learn.microsoft.com/en-us/office/vba/api/word.wdlinestyle #### NONE ``` NONE = (0, 'none', 'No border.') ``` No border. #### SINGLE ``` SINGLE = (1, 'single', 'A single solid line.') ``` A single solid line. #### DOT ``` DOT = (2, 'dotted', 'A dotted line.') ``` A dotted line. #### DASH_SMALL_GAP ``` DASH_SMALL_GAP = ( 3, "dashSmallGap", "A dashed line with small gaps.", ) ``` A dashed line with small gaps. #### DASH_LARGE_GAP ``` DASH_LARGE_GAP = ( 4, "dashed", "A dashed line with large gaps.", ) ``` A dashed line with large gaps. #### DASH_DOT ``` DASH_DOT = ( 5, "dotDash", "A line of alternating dashes and dots.", ) ``` A line of alternating dashes and dots. #### DASH_DOT_DOT ``` DASH_DOT_DOT = ( 6, "dotDotDash", "A line of dashes each followed by two dots.", ) ``` A line of dashes each followed by two dots. #### DOUBLE ``` DOUBLE = (7, 'double', 'Two parallel solid lines.') ``` Two parallel solid lines. #### TRIPLE ``` TRIPLE = (8, 'triple', 'Three parallel solid lines.') ``` Three parallel solid lines. #### THIN_THICK_SMALL_GAP ``` THIN_THICK_SMALL_GAP = ( 9, "thinThickSmallGap", "A thin line and a thick line separated by a small gap.", ) ``` A thin line and a thick line separated by a small gap. #### THICK_THIN_SMALL_GAP ``` THICK_THIN_SMALL_GAP = ( 10, "thickThinSmallGap", "A thick line and a thin line separated by a small gap.", ) ``` A thick line and a thin line separated by a small gap. #### THIN_THICK_THIN_SMALL_GAP ``` THIN_THICK_THIN_SMALL_GAP = ( 11, "thinThickThinSmallGap", "A thin, a thick and a thin line separated by small gaps.", ) ``` A thin, a thick and a thin line separated by small gaps. #### THIN_THICK_MED_GAP ``` THIN_THICK_MED_GAP = ( 12, "thinThickMediumGap", "A thin line and a thick line separated by a medium gap.", ) ``` A thin line and a thick line separated by a medium gap. #### THICK_THIN_MED_GAP ``` THICK_THIN_MED_GAP = ( 13, "thickThinMediumGap", "A thick line and a thin line separated by a medium gap.", ) ``` A thick line and a thin line separated by a medium gap. #### THIN_THICK_THIN_MED_GAP ``` THIN_THICK_THIN_MED_GAP = ( 14, "thinThickThinMediumGap", "A thin, a thick and a thin line separated by medium gaps.", ) ``` A thin, a thick and a thin line separated by medium gaps. #### THIN_THICK_LARGE_GAP ``` THIN_THICK_LARGE_GAP = ( 15, "thinThickLargeGap", "A thin line and a thick line separated by a large gap.", ) ``` A thin line and a thick line separated by a large gap. #### THICK_THIN_LARGE_GAP ``` THICK_THIN_LARGE_GAP = ( 16, "thickThinLargeGap", "A thick line and a thin line separated by a large gap.", ) ``` A thick line and a thin line separated by a large gap. #### THIN_THICK_THIN_LARGE_GAP ``` THIN_THICK_THIN_LARGE_GAP = ( 17, "thinThickThinLargeGap", "A thin, a thick and a thin line separated by large gaps.", ) ``` A thin, a thick and a thin line separated by large gaps. #### SINGLE_WAVY ``` SINGLE_WAVY = (18, 'wave', 'A single wavy line.') ``` A single wavy line. #### DOUBLE_WAVY ``` DOUBLE_WAVY = (19, "doubleWave", "Two parallel wavy lines.") ``` Two parallel wavy lines. #### DASH_DOT_STROKED ``` DASH_DOT_STROKED = ( 20, "dashDotStroked", "A line of slanting dashes and dots.", ) ``` A line of slanting dashes and dots. #### EMBOSS_3D ``` EMBOSS_3D = ( 21, "threeDEmboss", "A line that appears embossed.", ) ``` A line that appears embossed. #### ENGRAVE_3D ``` ENGRAVE_3D = ( 22, "threeDEngrave", "A line that appears engraved.", ) ``` A line that appears engraved. #### OUTSET ``` OUTSET = ( 23, "outset", "A line that makes the enclosed area appear raised.", ) ``` A line that makes the enclosed area appear raised. #### INSET ``` INSET = ( 24, "inset", "A line that makes the enclosed area appear sunken.", ) ``` A line that makes the enclosed area appear sunken. #### THICK ``` THICK = ( 100, "thick", "A single thick solid line. This is an OpenXml value with no `WdLineStyle` counterpart; Word renders it as a heavier `SINGLE`.", ) ``` A single thick solid line. This is an OpenXml value with no `WdLineStyle` counterpart; Word renders it as a heavier `SINGLE`. #### NIL ``` NIL = ( 101, "nil", "No border, and no space reserved for one. Distinct from `NONE` only in that `NONE` is the value Word writes when a border is explicitly turned off, whereas `nil` also appears as the default in a table style.", ) ``` No border, and no space reserved for one. Distinct from `NONE` only in that `NONE` is the value Word writes when a border is explicitly turned off, whereas `nil` also appears as the default in a table style. ### WD_TABLE_ALIGNMENT Bases: `BaseXmlEnum` Specifies table justification type. Example: ``` from docx.enum.table import WD_TABLE_ALIGNMENT table = document.add_table(3, 3) table.alignment = WD_TABLE_ALIGNMENT.CENTER ``` MS API name: `WdRowAlignment` http://office.microsoft.com/en-us/word-help/HV080607259.aspx #### LEFT ``` LEFT = (0, 'left', 'Left-aligned') ``` Left-aligned #### CENTER ``` CENTER = (1, 'center', 'Center-aligned.') ``` Center-aligned. #### RIGHT ``` RIGHT = (2, 'right', 'Right-aligned.') ``` Right-aligned. ### WD_TABLE_DIRECTION Bases: `BaseEnum` Specifies the direction in which an application orders cells in the specified table or row. Example: ``` from docx.enum.table import WD_TABLE_DIRECTION table = document.add_table(3, 3) table.direction = WD_TABLE_DIRECTION.RTL ``` MS API name: `WdTableDirection` http://msdn.microsoft.com/en-us/library/ff835141.aspx #### LTR ``` LTR = ( 0, "The table or row is arranged with the first column in the leftmost position.", ) ``` The table or row is arranged with the first column in the leftmost position. #### RTL ``` RTL = ( 1, "The table or row is arranged with the first column in the rightmost position.", ) ``` The table or row is arranged with the first column in the rightmost position. ## text Enumerations related to text in WordprocessingML files. ### WD_PARAGRAPH_ALIGNMENT Bases: `BaseXmlEnum` Alias: **WD_ALIGN_PARAGRAPH** Specifies paragraph justification type. Example: ``` from docx.enum.text import WD_ALIGN_PARAGRAPH paragraph = document.add_paragraph() paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER ``` #### LEFT ``` LEFT = (0, 'left', 'Left-aligned') ``` Left-aligned #### CENTER ``` CENTER = (1, 'center', 'Center-aligned.') ``` Center-aligned. #### RIGHT ``` RIGHT = (2, 'right', 'Right-aligned.') ``` Right-aligned. #### JUSTIFY ``` JUSTIFY = (3, 'both', 'Fully justified.') ``` Fully justified. #### DISTRIBUTE ``` DISTRIBUTE = ( 4, "distribute", "Paragraph characters are distributed to fill entire width of paragraph.", ) ``` Paragraph characters are distributed to fill entire width of paragraph. #### JUSTIFY_MED ``` JUSTIFY_MED = ( 5, "mediumKashida", "Justified with a medium character compression ratio.", ) ``` Justified with a medium character compression ratio. #### JUSTIFY_HI ``` JUSTIFY_HI = ( 7, "highKashida", "Justified with a high character compression ratio.", ) ``` Justified with a high character compression ratio. #### JUSTIFY_LOW ``` JUSTIFY_LOW = ( 8, "lowKashida", "Justified with a low character compression ratio.", ) ``` Justified with a low character compression ratio. #### THAI_JUSTIFY ``` THAI_JUSTIFY = ( 9, "thaiDistribute", "Justified according to Thai formatting layout.", ) ``` Justified according to Thai formatting layout. ### WD_BREAK_TYPE Bases: `Enum` Corresponds to WdBreakType enumeration. http://msdn.microsoft.com/en-us/library/office/ff195905.aspx. ### WD_COLOR_INDEX Bases: `BaseXmlEnum` Specifies a standard preset color to apply. Used for font highlighting and perhaps other applications. - MS API name: `WdColorIndex` - URL: https://msdn.microsoft.com/EN-US/library/office/ff195343.aspx #### INHERITED ``` INHERITED = ( -1, None, "Color is inherited from the style hierarchy.", ) ``` Color is inherited from the style hierarchy. #### NO_HIGHLIGHT ``` NO_HIGHLIGHT = (-2, 'none', 'Explicitly not highlighted.') ``` Explicitly not highlighted. Distinct from `None`, which means no `w:highlight` element is present and the highlight is therefore inherited. `w:highlight` with `w:val="none"` overrides an inherited highlight and is written by Word when highlighting is cleared on a run that sits under a style supplying one. The MS API assigns `wdNoHighlight` the value 0, the same value as `wdAuto`. Two members cannot share a value here, so this member takes a distinct negative value in the manner of `INHERITED`. #### AUTO ``` AUTO = ( 0, "default", "Automatic color. Default; usually black.", ) ``` Automatic color. Default; usually black. #### BLACK ``` BLACK = (1, 'black', 'Black color.') ``` Black color. #### BLUE ``` BLUE = (2, 'blue', 'Blue color') ``` Blue color #### BRIGHT_GREEN ``` BRIGHT_GREEN = (4, 'green', 'Bright green color.') ``` Bright green color. #### DARK_BLUE ``` DARK_BLUE = (9, 'darkBlue', 'Dark blue color.') ``` Dark blue color. #### DARK_RED ``` DARK_RED = (13, 'darkRed', 'Dark red color.') ``` Dark red color. #### DARK_YELLOW ``` DARK_YELLOW = (14, 'darkYellow', 'Dark yellow color.') ``` Dark yellow color. #### GRAY_25 ``` GRAY_25 = (16, 'lightGray', '25% shade of gray color.') ``` 25% shade of gray color. #### GRAY_50 ``` GRAY_50 = (15, 'darkGray', '50% shade of gray color.') ``` 50% shade of gray color. #### GREEN ``` GREEN = (11, 'darkGreen', 'Green color.') ``` Green color. #### PINK ``` PINK = (5, 'magenta', 'Pink color.') ``` Pink color. #### RED ``` RED = (6, 'red', 'Red color.') ``` Red color. #### TEAL ``` TEAL = (10, 'darkCyan', 'Teal color.') ``` Teal color. #### TURQUOISE ``` TURQUOISE = (3, 'cyan', 'Turquoise color.') ``` Turquoise color. #### VIOLET ``` VIOLET = (12, 'darkMagenta', 'Violet color.') ``` Violet color. #### WHITE ``` WHITE = (8, 'white', 'White color.') ``` White color. #### YELLOW ``` YELLOW = (7, 'yellow', 'Yellow color.') ``` Yellow color. ### WD_CONTENT_CONTROL_TYPE Bases: `BaseEnum` Specifies the kind of a structured document tag (`w:sdt`), aka content control. The kind is determined by which child of `w:sdtPr` is present, not by an attribute value, so these members have no XML value mapping. - MS API name: `WdContentControlType` - URL: https://learn.microsoft.com/en-us/office/vba/api/word.wdcontentcontroltype #### RICH_TEXT ``` RICH_TEXT = ( 0, "Formatted text, which may contain multiple paragraphs.", ) ``` Formatted text, which may contain multiple paragraphs. #### TEXT ``` TEXT = ( 1, "Plain text, a single run without formatting of its own.", ) ``` Plain text, a single run without formatting of its own. #### PICTURE ``` PICTURE = (2, 'A single picture.') ``` A single picture. #### COMBO_BOX ``` COMBO_BOX = ( 3, "A list of choices that also accepts typed text.", ) ``` A list of choices that also accepts typed text. #### DROPDOWN_LIST ``` DROPDOWN_LIST = ( 4, "A list of choices, one of which must be selected.", ) ``` A list of choices, one of which must be selected. #### BUILDING_BLOCK_GALLERY ``` BUILDING_BLOCK_GALLERY = ( 5, "A gallery of building blocks, e.g. a cover page.", ) ``` A gallery of building blocks, e.g. a cover page. #### DATE ``` DATE = (6, 'A date, entered through a calendar picker.') ``` A date, entered through a calendar picker. #### GROUP ``` GROUP = ( 7, "A grouping of content that is edited as a unit.", ) ``` A grouping of content that is edited as a unit. #### CHECKBOX ``` CHECKBOX = (8, 'A check box, checked or unchecked.') ``` A check box, checked or unchecked. Written by Word as the `w14:checkbox` extension element, not as part of the ISO schema. #### REPEATING_SECTION ``` REPEATING_SECTION = ( 9, "A section repeated once per item in a bound collection.", ) ``` A section repeated once per item in a bound collection. ### WD_FONT_HINT Bases: `BaseXmlEnum` Specifies which `w:rFonts` typeface slot Word prefers for ambiguous characters. A character that belongs to no particular script — a space, a digit, punctuation — could be rendered from more than one slot, and the hint settles it. Getting this wrong is a common cause of East Asian text rendering in the wrong typeface. There is no MS API enumeration for this; it corresponds to the `ST_Hint` schema type and the member values are this library's own. #### DEFAULT ``` DEFAULT = ( 0, "default", "Use the ASCII typeface for ambiguous characters.", ) ``` Use the ASCII typeface for ambiguous characters. #### EAST_ASIA ``` EAST_ASIA = ( 1, "eastAsia", "Use the East Asian typeface for ambiguous characters.", ) ``` Use the East Asian typeface for ambiguous characters. #### COMPLEX_SCRIPT ``` COMPLEX_SCRIPT = ( 2, "cs", "Use the complex-script typeface for ambiguous characters.", ) ``` Use the complex-script typeface for ambiguous characters. ### WD_FORM_FIELD_TYPE Bases: `BaseEnum` Specifies the kind of a legacy form field. The kind is determined by which child of `w:ffData` is present, not by an attribute value, so these members have no XML value mapping. - MS API name: `WdFieldType` (the form-field subset) - URL: https://learn.microsoft.com/en-us/office/vba/api/word.wdfieldtype #### TEXT ``` TEXT = (70, 'A text input, which Word calls FORMTEXT.') ``` A text input, which Word calls FORMTEXT. #### CHECK_BOX ``` CHECK_BOX = ( 71, "A check box, which Word calls FORMCHECKBOX.", ) ``` A check box, which Word calls FORMCHECKBOX. #### DROP_DOWN ``` DROP_DOWN = ( 83, "A drop-down list, which Word calls FORMDROPDOWN.", ) ``` A drop-down list, which Word calls FORMDROPDOWN. ### WD_LINE_SPACING Bases: `BaseXmlEnum` Specifies a line spacing format to be applied to a paragraph. Example: ``` from docx.enum.text import WD_LINE_SPACING paragraph = document.add_paragraph() paragraph.line_spacing_rule = WD_LINE_SPACING.EXACTLY ``` MS API name: `WdLineSpacing` URL: http://msdn.microsoft.com/en-us/library/office/ff844910.aspx #### SINGLE ``` SINGLE = (0, 'UNMAPPED', 'Single spaced (default).') ``` Single spaced (default). #### ONE_POINT_FIVE ``` ONE_POINT_FIVE = ( 1, "UNMAPPED", "Space-and-a-half line spacing.", ) ``` Space-and-a-half line spacing. #### DOUBLE ``` DOUBLE = (2, 'UNMAPPED', 'Double spaced.') ``` Double spaced. #### AT_LEAST ``` AT_LEAST = ( 3, "atLeast", "Minimum line spacing is specified amount. Amount is specified separately.", ) ``` Minimum line spacing is specified amount. Amount is specified separately. #### EXACTLY ``` EXACTLY = ( 4, "exact", "Line spacing is exactly specified amount. Amount is specified separately.", ) ``` Line spacing is exactly specified amount. Amount is specified separately. #### MULTIPLE ``` MULTIPLE = ( 5, "auto", "Line spacing is specified as multiple of line heights. Changing font size will change line spacing proportionately.", ) ``` Line spacing is specified as multiple of line heights. Changing font size will change the line spacing proportionately. ### WD_SHADING_PATTERN Bases: `BaseXmlEnum` Specifies the pattern drawn over the background of shaded content. The pattern is drawn in the shading *color* over the shading *fill*. The common case is CLEAR, which draws no pattern and leaves the fill as a solid background. - ISO/IEC 29500-1 §17.18.78 (`ST_Shd`) #### NIL ``` NIL = ( 0, "nil", "No shading. Equivalent to no `w:shd` element at all.", ) ``` No shading. Equivalent to no `w:shd` element at all. #### CLEAR ``` CLEAR = ( 1, "clear", "No pattern; the fill color forms a solid background.", ) ``` No pattern; the fill color forms a solid background. This is what Word writes for an ordinary background color, and what this library writes when shading is applied without naming a pattern. #### SOLID ``` SOLID = ( 2, "solid", "The pattern color entirely covers the fill color.", ) ``` The pattern color entirely covers the fill color. Note the reversal: with SOLID the visible background is the shading *color*, not the fill. #### HORZ_STRIPE ``` HORZ_STRIPE = (3, 'horzStripe', 'Horizontal stripes.') ``` Horizontal stripes. #### VERT_STRIPE ``` VERT_STRIPE = (4, 'vertStripe', 'Vertical stripes.') ``` Vertical stripes. #### REVERSE_DIAG_STRIPE ``` REVERSE_DIAG_STRIPE = ( 5, "reverseDiagStripe", "Diagonal stripes, upward to right.", ) ``` Diagonal stripes running upward to the right. #### DIAG_STRIPE ``` DIAG_STRIPE = ( 6, "diagStripe", "Diagonal stripes, downward to right.", ) ``` Diagonal stripes running downward to the right. #### HORZ_CROSS ``` HORZ_CROSS = ( 7, "horzCross", "A horizontal and vertical crosshatch.", ) ``` A horizontal and vertical crosshatch. #### DIAG_CROSS ``` DIAG_CROSS = (8, 'diagCross', 'A diagonal crosshatch.') ``` A diagonal crosshatch. #### THIN_HORZ_STRIPE ``` THIN_HORZ_STRIPE = ( 9, "thinHorzStripe", "Narrow horizontal stripes.", ) ``` Narrow horizontal stripes. #### THIN_VERT_STRIPE ``` THIN_VERT_STRIPE = ( 10, "thinVertStripe", "Narrow vertical stripes.", ) ``` Narrow vertical stripes. #### THIN_REVERSE_DIAG_STRIPE ``` THIN_REVERSE_DIAG_STRIPE = ( 11, "thinReverseDiagStripe", "Narrow diagonal stripes, upward to right.", ) ``` Narrow diagonal stripes running upward to the right. #### THIN_DIAG_STRIPE ``` THIN_DIAG_STRIPE = ( 12, "thinDiagStripe", "Narrow diagonal stripes, downward to right.", ) ``` Narrow diagonal stripes running downward to the right. #### THIN_HORZ_CROSS ``` THIN_HORZ_CROSS = ( 13, "thinHorzCross", "A narrow horizontal and vertical crosshatch.", ) ``` A narrow horizontal and vertical crosshatch. #### THIN_DIAG_CROSS ``` THIN_DIAG_CROSS = ( 14, "thinDiagCross", "A narrow diagonal crosshatch.", ) ``` A narrow diagonal crosshatch. #### PCT_5 ``` PCT_5 = ( 15, "pct5", "5% of the pattern color over the fill color.", ) ``` 5% of the pattern color over the fill color. #### PCT_10 ``` PCT_10 = ( 16, "pct10", "10% of the pattern color over the fill color.", ) ``` 10% of the pattern color over the fill color. #### PCT_12 ``` PCT_12 = ( 17, "pct12", "12.5% of the pattern color over the fill color.", ) ``` 12.5% of the pattern color over the fill color. #### PCT_15 ``` PCT_15 = ( 18, "pct15", "15% of the pattern color over the fill color.", ) ``` 15% of the pattern color over the fill color. #### PCT_20 ``` PCT_20 = ( 19, "pct20", "20% of the pattern color over the fill color.", ) ``` 20% of the pattern color over the fill color. #### PCT_25 ``` PCT_25 = ( 20, "pct25", "25% of the pattern color over the fill color.", ) ``` 25% of the pattern color over the fill color. #### PCT_30 ``` PCT_30 = ( 21, "pct30", "30% of the pattern color over the fill color.", ) ``` 30% of the pattern color over the fill color. #### PCT_35 ``` PCT_35 = ( 22, "pct35", "35% of the pattern color over the fill color.", ) ``` 35% of the pattern color over the fill color. #### PCT_37 ``` PCT_37 = ( 23, "pct37", "37.5% of the pattern color over the fill color.", ) ``` 37.5% of the pattern color over the fill color. #### PCT_40 ``` PCT_40 = ( 24, "pct40", "40% of the pattern color over the fill color.", ) ``` 40% of the pattern color over the fill color. #### PCT_45 ``` PCT_45 = ( 25, "pct45", "45% of the pattern color over the fill color.", ) ``` 45% of the pattern color over the fill color. #### PCT_50 ``` PCT_50 = ( 26, "pct50", "50% of the pattern color over the fill color.", ) ``` 50% of the pattern color over the fill color. #### PCT_55 ``` PCT_55 = ( 27, "pct55", "55% of the pattern color over the fill color.", ) ``` 55% of the pattern color over the fill color. #### PCT_60 ``` PCT_60 = ( 28, "pct60", "60% of the pattern color over the fill color.", ) ``` 60% of the pattern color over the fill color. #### PCT_62 ``` PCT_62 = ( 29, "pct62", "62.5% of the pattern color over the fill color.", ) ``` 62.5% of the pattern color over the fill color. #### PCT_65 ``` PCT_65 = ( 30, "pct65", "65% of the pattern color over the fill color.", ) ``` 65% of the pattern color over the fill color. #### PCT_70 ``` PCT_70 = ( 31, "pct70", "70% of the pattern color over the fill color.", ) ``` 70% of the pattern color over the fill color. #### PCT_75 ``` PCT_75 = ( 32, "pct75", "75% of the pattern color over the fill color.", ) ``` 75% of the pattern color over the fill color. #### PCT_80 ``` PCT_80 = ( 33, "pct80", "80% of the pattern color over the fill color.", ) ``` 80% of the pattern color over the fill color. #### PCT_85 ``` PCT_85 = ( 34, "pct85", "85% of the pattern color over the fill color.", ) ``` 85% of the pattern color over the fill color. #### PCT_87 ``` PCT_87 = ( 35, "pct87", "87.5% of the pattern color over the fill color.", ) ``` 87.5% of the pattern color over the fill color. #### PCT_90 ``` PCT_90 = ( 36, "pct90", "90% of the pattern color over the fill color.", ) ``` 90% of the pattern color over the fill color. #### PCT_95 ``` PCT_95 = ( 37, "pct95", "95% of the pattern color over the fill color.", ) ``` 95% of the pattern color over the fill color. ### WD_TAB_ALIGNMENT Bases: `BaseXmlEnum` Specifies the tab stop alignment to apply. MS API name: `WdTabAlignment` URL: https://msdn.microsoft.com/EN-US/library/office/ff195609.aspx #### LEFT ``` LEFT = (0, 'left', 'Left-aligned.') ``` Left-aligned. #### CENTER ``` CENTER = (1, 'center', 'Center-aligned.') ``` Center-aligned. #### RIGHT ``` RIGHT = (2, 'right', 'Right-aligned.') ``` Right-aligned. #### DECIMAL ``` DECIMAL = (3, 'decimal', 'Decimal-aligned.') ``` Decimal-aligned. #### BAR ``` BAR = (4, 'bar', 'Bar-aligned.') ``` Bar-aligned. #### LIST ``` LIST = (6, 'list', 'List-aligned. (deprecated)') ``` List-aligned. (deprecated) #### CLEAR ``` CLEAR = (101, 'clear', 'Clear an inherited tab stop.') ``` Clear an inherited tab stop. #### END ``` END = (102, 'end', 'Right-aligned. (deprecated)') ``` Right-aligned. (deprecated) #### NUM ``` NUM = (103, 'num', 'Left-aligned. (deprecated)') ``` Left-aligned. (deprecated) #### START ``` START = (104, 'start', 'Left-aligned. (deprecated)') ``` Left-aligned. (deprecated) ### WD_TAB_LEADER Bases: `BaseXmlEnum` Specifies the character to use as the leader with formatted tabs. MS API name: `WdTabLeader` URL: https://msdn.microsoft.com/en-us/library/office/ff845050.aspx #### SPACES ``` SPACES = (0, 'none', 'Spaces. Default.') ``` Spaces. Default. #### DOTS ``` DOTS = (1, 'dot', 'Dots.') ``` Dots. #### DASHES ``` DASHES = (2, 'hyphen', 'Dashes.') ``` Dashes. #### LINES ``` LINES = (3, 'underscore', 'Double lines.') ``` Double lines. #### HEAVY ``` HEAVY = (4, 'heavy', 'A heavy line.') ``` A heavy line. #### MIDDLE_DOT ``` MIDDLE_DOT = (5, 'middleDot', 'A vertically-centered dot.') ``` A vertically-centered dot. ### WD_TEXT_DIRECTION Bases: `BaseXmlEnum` Specifies the flow direction of text within a paragraph, section or table cell. This is the writing direction — which way the lines run and whether the glyphs are rotated — and is a different thing from `bidi`, which is the base *reading* direction of a right-to-left paragraph. Example: ``` from docx.enum.text import WD_TEXT_DIRECTION cell.text_direction = WD_TEXT_DIRECTION.BT_LR # rotated header cell ``` The names spell out the two axes in the order Word writes them: `LR_TB` is left-to-right within a line, top-to-bottom between lines, which is ordinary horizontal Western layout. #### LR_TB ``` LR_TB = ( 0, "lrTb", "Horizontal, left to right. Ordinary Western layout.", ) ``` Horizontal, left to right. Ordinary Western layout. #### TB_RL ``` TB_RL = ( 1, "tbRl", "Vertical, right to left. Ordinary East Asian vertical layout.", ) ``` Vertical, right to left. Ordinary East Asian vertical layout. #### BT_LR ``` BT_LR = ( 2, "btLr", "Rotated 90 degrees counter-clockwise. A rotated table header.", ) ``` Rotated 90 degrees counter-clockwise. A rotated table header. #### LR_TB_V ``` LR_TB_V = ( 3, "lrTbV", "Horizontal, with each glyph rotated 90 degrees clockwise.", ) ``` Horizontal, with each glyph rotated 90 degrees clockwise. #### TB_RL_V ``` TB_RL_V = ( 4, "tbRlV", "Vertical, with each glyph rotated 90 degrees clockwise.", ) ``` Vertical, with each glyph rotated 90 degrees clockwise. #### TB_LR_V ``` TB_LR_V = ( 5, "tbLrV", "Vertical, left to right, with glyphs rotated.", ) ``` Vertical, left to right, with glyphs rotated. ### WD_TEXT_FORM_FIELD_TYPE Bases: `BaseXmlEnum` Specifies what a text form field accepts. Example: ``` from docx.enum.text import WD_TEXT_FORM_FIELD_TYPE form_field.text_type = WD_TEXT_FORM_FIELD_TYPE.NUMBER_TEXT ``` - MS API name: `WdTextFormFieldType` - URL: https://learn.microsoft.com/en-us/office/vba/api/word.wdtextformfieldtype #### REGULAR_TEXT ``` REGULAR_TEXT = (0, 'regular', 'Any text.') ``` Any text. #### NUMBER_TEXT ``` NUMBER_TEXT = (1, 'number', 'A number.') ``` A number. #### DATE_TEXT ``` DATE_TEXT = (2, 'date', 'A date.') ``` A date. #### CURRENT_DATE_TEXT ``` CURRENT_DATE_TEXT = ( 3, "currentDate", "The current date, filled in by Word.", ) ``` The current date, filled in by Word. #### CURRENT_TIME_TEXT ``` CURRENT_TIME_TEXT = ( 4, "currentTime", "The current time, filled in by Word.", ) ``` The current time, filled in by Word. #### CALCULATION_TEXT ``` CALCULATION_TEXT = ( 5, "calculated", "The result of an expression, computed by Word.", ) ``` The result of an expression, computed by Word. ### WD_UNDERLINE Bases: `BaseXmlEnum` Specifies the style of underline applied to a run of characters. MS API name: `WdUnderline` URL: http://msdn.microsoft.com/en-us/library/office/ff822388.aspx #### INHERITED ``` INHERITED = ( -1, None, "Inherit underline setting from containing paragraph.", ) ``` Inherit underline setting from containing paragraph. #### NONE ``` NONE = ( 0, "none", "No underline.\n\nThis setting overrides any inherited underline value, so can be used to remove underline from a run that inherits underlining from its containing paragraph. Note this is not the same as assigning |None| to Run.underline. |None| is a valid assignment value, but causes the run to inherit its underline value. Assigning `WD_UNDERLINE.NONE` causes underlining to be unconditionally turned off.", ) ``` No underline. This setting overrides any inherited underline value, so can be used to remove underline from a run that inherits underlining from its containing paragraph. Note this is not the same as assigning `None` to Run.underline. `None` is a valid assignment value, but causes the run to inherit its underline value. Assigning `WD_UNDERLINE.NONE` causes underlining to be unconditionally turned off. #### SINGLE ``` SINGLE = ( 1, "single", "A single line.\n\nNote that this setting is write-only in the sense that |True| (rather than `WD_UNDERLINE.SINGLE`) is returned for a run having this setting.", ) ``` A single line. Note that this setting is write-only in the sense that `True` (rather than `WD_UNDERLINE.SINGLE`) is returned for a run having this setting. #### WORDS ``` WORDS = (2, 'words', 'Underline individual words only.') ``` Underline individual words only. #### DOUBLE ``` DOUBLE = (3, 'double', 'A double line.') ``` A double line. #### DOTTED ``` DOTTED = (4, 'dotted', 'Dots.') ``` Dots. #### THICK ``` THICK = (6, 'thick', 'A single thick line.') ``` A single thick line. #### DASH ``` DASH = (7, 'dash', 'Dashes.') ``` Dashes. #### DOT_DASH ``` DOT_DASH = (9, 'dotDash', 'Alternating dots and dashes.') ``` Alternating dots and dashes. #### DOT_DOT_DASH ``` DOT_DOT_DASH = ( 10, "dotDotDash", "An alternating dot-dot-dash pattern.", ) ``` An alternating dot-dot-dash pattern. #### WAVY ``` WAVY = (11, 'wave', 'A single wavy line.') ``` A single wavy line. #### DOTTED_HEAVY ``` DOTTED_HEAVY = (20, 'dottedHeavy', 'Heavy dots.') ``` Heavy dots. #### DASH_HEAVY ``` DASH_HEAVY = (23, 'dashedHeavy', 'Heavy dashes.') ``` Heavy dashes. #### DOT_DASH_HEAVY ``` DOT_DASH_HEAVY = ( 25, "dashDotHeavy", "Alternating heavy dots and heavy dashes.", ) ``` Alternating heavy dots and heavy dashes. #### DOT_DOT_DASH_HEAVY ``` DOT_DOT_DASH_HEAVY = ( 26, "dashDotDotHeavy", "An alternating heavy dot-dot-dash pattern.", ) ``` An alternating heavy dot-dot-dash pattern. #### WAVY_HEAVY ``` WAVY_HEAVY = (27, 'wavyHeavy', 'A heavy wavy line.') ``` A heavy wavy line. #### DASH_LONG ``` DASH_LONG = (39, 'dashLong', 'Long dashes.') ``` Long dashes. #### WAVY_DOUBLE ``` WAVY_DOUBLE = (43, 'wavyDouble', 'A double wavy line.') ``` A double wavy line. #### DASH_LONG_HEAVY ``` DASH_LONG_HEAVY = ( 55, "dashLongHeavy", "Long heavy dashes.", ) ``` Long heavy dashes. ## image Provides objects that can characterize image streams. That characterization is as to content type and size, as a required step in including them in a document. ## bmp ### Bmp ``` Bmp( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `BaseImageHeader` Image header parser for BMP images. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### content_type ``` content_type ``` MIME content type for this image, unconditionally `image/bmp` for BMP images. #### default_ext ``` default_ext ``` Default filename extension, always 'bmp' for BMP images. #### from_stream ``` from_stream(stream) ``` Return Bmp instance having header properties parsed from the BMP image in `stream`. Source code in `src/docx/image/bmp.py` ``` @classmethod def from_stream(cls, stream): """Return |Bmp| instance having header properties parsed from the BMP image in `stream`.""" stream_rdr = StreamReader(stream, LITTLE_ENDIAN) px_width = stream_rdr.read_long(0x12) px_height = stream_rdr.read_long(0x16) horz_px_per_meter = stream_rdr.read_long(0x26) vert_px_per_meter = stream_rdr.read_long(0x2A) horz_dpi = cls._dpi(horz_px_per_meter) vert_dpi = cls._dpi(vert_px_per_meter) return cls(px_width, px_height, horz_dpi, vert_dpi) ``` ## constants Constants specific the the image sub-package. ### JPEG_MARKER_CODE JPEG marker codes. ### MIME_TYPE Image content types. ### PNG_CHUNK_TYPE PNG chunk type names. ### TIFF_FLD_TYPE Tag codes for TIFF Image File Directory (IFD) entries. ### TIFF_TAG Tag codes for TIFF Image File Directory (IFD) entries. ## emf Image header parser for EMF (Enhanced Metafile) images. An EMF file opens with an `EMR_HEADER` record whose `ENHMETAHEADER` structure carries both the physical extent of the picture and the resolution of the device it was recorded against. Both are needed: the extent alone says how large the picture is, and the device resolution is what turns that into a pixel count that agrees with what Word shows. ### Emf ``` Emf( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `BaseImageHeader` Image header parser for EMF images. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### content_type ``` content_type ``` MIME content type for this image, unconditionally `image/x-emf` for EMF images. #### default_ext ``` default_ext ``` Default filename extension, always 'emf' for EMF images. #### from_stream ``` from_stream(stream) ``` Return an Emf instance with header properties parsed from `stream`. Source code in `src/docx/image/emf.py` ``` @classmethod def from_stream(cls, stream): """Return an |Emf| instance with header properties parsed from `stream`.""" header = cls._read_header(stream) horz_dpi, vert_dpi = cls._dpi_from_header(header) inch_width, inch_height = cls._extents_in_inches(header) px_width = int(round(inch_width * horz_dpi)) px_height = int(round(inch_height * vert_dpi)) return cls(px_width, px_height, horz_dpi, vert_dpi) ``` ## exceptions Exceptions specific the the image sub-package. ### InvalidImageStreamError Bases: `Exception` The recognized image stream appears to be corrupted. ### UnexpectedEndOfFileError Bases: `Exception` EOF was unexpectedly encountered while reading an image stream. ### UnrecognizedImageError Bases: `Exception` The provided image stream could not be recognized. ## gif ### Gif ``` Gif( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `BaseImageHeader` Image header parser for GIF images. Note that the GIF format does not support resolution (DPI) information. Both horizontal and vertical DPI default to 72. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### content_type ``` content_type ``` MIME content type for this image, unconditionally `image/gif` for GIF images. #### default_ext ``` default_ext ``` Default filename extension, always 'gif' for GIF images. #### from_stream ``` from_stream(stream) ``` Return Gif instance having header properties parsed from GIF image in `stream`. Source code in `src/docx/image/gif.py` ``` @classmethod def from_stream(cls, stream): """Return |Gif| instance having header properties parsed from GIF image in `stream`.""" px_width, px_height = cls._dimensions_from_stream(stream) return cls(px_width, px_height, 72, 72) ``` ## helpers ### StreamReader ``` StreamReader(stream, byte_order, base_offset=0) ``` Wraps a file-like object to provide access to structured data from a binary file. Byte-order is configurable. `base_offset` is added to any base value provided to calculate actual location for reads. Source code in `src/docx/image/helpers.py` ``` def __init__(self, stream, byte_order, base_offset=0): super(StreamReader, self).__init__() self._stream = stream self._byte_order = LITTLE_ENDIAN if byte_order == LITTLE_ENDIAN else BIG_ENDIAN self._base_offset = base_offset ``` #### read ``` read(count) ``` Allow pass-through read() call. Source code in `src/docx/image/helpers.py` ``` def read(self, count): """Allow pass-through read() call.""" return self._stream.read(count) ``` #### read_byte ``` read_byte(base, offset=0) ``` Return the int value of the byte at the file position defined by self.\_base_offset + `base` + `offset`. If `base` is None, the byte is read from the current position in the stream. Source code in `src/docx/image/helpers.py` ``` def read_byte(self, base, offset=0): """Return the int value of the byte at the file position defined by self._base_offset + `base` + `offset`. If `base` is None, the byte is read from the current position in the stream. """ fmt = "B" return self._read_int(fmt, base, offset) ``` #### read_long ``` read_long(base, offset=0) ``` Return the int value of the four bytes at the file position defined by self.\_base_offset + `base` + `offset`. If `base` is None, the long is read from the current position in the stream. The endian setting of this instance is used to interpret the byte layout of the long. Source code in `src/docx/image/helpers.py` ``` def read_long(self, base, offset=0): """Return the int value of the four bytes at the file position defined by self._base_offset + `base` + `offset`. If `base` is None, the long is read from the current position in the stream. The endian setting of this instance is used to interpret the byte layout of the long. """ fmt = "L" return self._read_int(fmt, base, offset) ``` #### read_short ``` read_short(base, offset=0) ``` Return the int value of the two bytes at the file position determined by `base` and `offset`, similarly to `read_long()` above. Source code in `src/docx/image/helpers.py` ``` def read_short(self, base, offset=0): """Return the int value of the two bytes at the file position determined by `base` and `offset`, similarly to ``read_long()`` above.""" fmt = b"H" return self._read_int(fmt, base, offset) ``` #### read_str ``` read_str(char_count, base, offset=0) ``` Return a string containing the `char_count` bytes at the file position determined by self.\_base_offset + `base` + `offset`. Source code in `src/docx/image/helpers.py` ``` def read_str(self, char_count, base, offset=0): """Return a string containing the `char_count` bytes at the file position determined by self._base_offset + `base` + `offset`.""" def str_struct(char_count): format_ = "%ds" % char_count return Struct(format_) struct = str_struct(char_count) chars = self._unpack_item(struct, base, offset) unicode_str = chars.decode("UTF-8") return unicode_str ``` #### tell ``` tell() ``` Allow pass-through tell() call. Source code in `src/docx/image/helpers.py` ``` def tell(self): """Allow pass-through tell() call.""" return self._stream.tell() ``` ## image Provides objects that can characterize image streams. That characterization is as to content type and size, as a required step in including them in a document. ### Image ``` Image( blob: bytes, filename: str, image_header: BaseImageHeader, ) ``` Graphical image stream such as JPEG, PNG, or GIF with properties and methods required by ImagePart. Source code in `src/docx/image/image.py` ``` def __init__(self, blob: bytes, filename: str, image_header: BaseImageHeader): super(Image, self).__init__() self._blob = blob self._filename = filename self._image_header = image_header ``` #### blob ``` blob ``` The bytes of the image 'file'. #### content_type ``` content_type: str ``` MIME content type for this image, e.g. `'image/jpeg'` for a JPEG image. #### filename ``` filename ``` Original image file name, if loaded from disk, or a generic filename if loaded from an anonymous stream. #### px_width ``` px_width: int ``` The horizontal pixel dimension of the image. #### px_height ``` px_height: int ``` The vertical pixel dimension of the image. #### horz_dpi ``` horz_dpi: int ``` Integer dots per inch for the width of this image. Defaults to 72 when not present in the file, as is often the case. #### vert_dpi ``` vert_dpi: int ``` Integer dots per inch for the height of this image. Defaults to 72 when not present in the file, as is often the case. #### orientation ``` orientation: int ``` The EXIF `Orientation` of this image, 1 through 8. 1 when the image declares none, which is every format but JPEG and TIFF and most files even of those. The eight values are: 1 normal, 2 mirrored, 3 rotated 180°, 4 mirrored and 180°, 5 mirrored and 90° counter-clockwise, 6 rotated 90° clockwise, 7 mirrored and 90° clockwise, 8 rotated 90° counter-clockwise. 6 and 8 are the common ones, and the two that exchange width and height. #### is_rotated ``` is_rotated: bool ``` `True` when this image's EXIF orientation exchanges its width and height. #### px_display_width ``` px_display_width: int ``` Width in pixels *as displayed*, honouring the EXIF orientation. The same as px_width unless the orientation is a quarter turn, in which case the two are exchanged. This is the one to scale from: a portrait photo off a phone is stored landscape with an `Orientation` of 6, and computing a height from px_width gives an aspect ratio nothing will render at. #### px_display_height ``` px_display_height: int ``` Height in pixels *as displayed*, honouring the EXIF orientation. See px_display_width. #### width ``` width: Inches ``` A Length value representing the native width of the image, calculated from the values of `px_width` and `horz_dpi`. The *stored* width; see display_width for the width after the EXIF orientation is applied. #### height ``` height: Inches ``` A Length value representing the native height of the image, calculated from the values of `px_height` and `vert_dpi`. The *stored* height; see display_height. #### display_width ``` display_width: Inches ``` The native width of the image as displayed, honouring the EXIF orientation. The dpi values are exchanged along with the pixel counts, since a quarter turn takes the stored rows to the displayed columns. #### display_height ``` display_height: Inches ``` The native height of the image as displayed, honouring the EXIF orientation. #### drawingml_transform ``` drawingml_transform: Tuple[int, bool] ``` The (rotation, flip_h) pair expressing this image's EXIF orientation. Rotation is in 60000ths of a degree, the unit `a:xfrm/@rot` uses; `flip_h` maps to `@flipH`. `(0, False)` for an image that needs no transform. The rotation goes in the DrawingML rather than into the pixels: rotating the bytes would mean a JPEG decode/encode dependency this library does not have, would lose quality, and would break the sha1-based part deduplication in `package.py` that keeps one copy of an image used twice. #### from_blob ``` from_blob(blob: bytes) -> Image ``` Return a new Image subclass instance parsed from the image binary contained in `blob`. Source code in `src/docx/image/image.py` ``` @classmethod def from_blob(cls, blob: bytes) -> Image: """Return a new |Image| subclass instance parsed from the image binary contained in `blob`.""" stream = io.BytesIO(blob) return cls._from_stream(stream, blob) ``` #### from_file ``` from_file( image_descriptor: str | PathLike[str] | IO[bytes], ) ``` Return a new Image subclass instance loaded from the image file identified by `image_descriptor`, a path (a string or `os.PathLike`) or file-like object. Source code in `src/docx/image/image.py` ``` @classmethod def from_file(cls, image_descriptor: str | os.PathLike[str] | IO[bytes]): """Return a new |Image| subclass instance loaded from the image file identified by `image_descriptor`, a path (a string or ``os.PathLike``) or file-like object.""" if isinstance(image_descriptor, (str, os.PathLike)): path = os.fspath(image_descriptor) with open(path, "rb") as f: blob = f.read() stream = io.BytesIO(blob) filename = os.path.basename(path) else: stream = image_descriptor stream.seek(0) blob = stream.read() filename = None return cls._from_stream(stream, blob, filename) ``` #### ext ``` ext() ``` The file extension for the image. If an actual one is available from a load filename it is used. Otherwise a canonical extension is assigned based on the content type. Does not contain the leading period, e.g. 'jpg', not '.jpg'. Source code in `src/docx/image/image.py` ``` @lazyproperty def ext(self): """The file extension for the image. If an actual one is available from a load filename it is used. Otherwise a canonical extension is assigned based on the content type. Does not contain the leading period, e.g. 'jpg', not '.jpg'. """ return os.path.splitext(self._filename)[1][1:] ``` #### scaled_dimensions ``` scaled_dimensions( width: int | Length | None = None, height: int | Length | None = None, *, honor_exif_orientation: bool = True, ) -> Tuple[Length, Length] ``` (cx, cy) pair representing scaled dimensions of this image. The native dimensions of the image are scaled by applying the following rules to the `width` and `height` arguments. - If both `width` and `height` are specified, the return value is (`width`, `height`); no scaling is performed. - If only one is specified, it is used to compute a scaling factor that is then applied to the unspecified dimension, preserving the aspect ratio of the image. - If both `width` and `height` are `None`, the native dimensions are returned. The native dimensions are calculated using the dots-per-inch (dpi) value embedded in the image, defaulting to 72 dpi if no value is specified, as is often the case. The returned values are both Length objects. The *display* dimensions are used, so a photo carrying an EXIF orientation that turns it a quarter is scaled to the aspect ratio it will be rendered at rather than the one its stored pixels have. Pass `honor_exif_orientation=False` for the stored dimensions — for an image whose pixels are already rotated *and* which carries the tag anyway, which some encoders produce and nothing can detect. Source code in `src/docx/image/image.py` ``` def scaled_dimensions( self, width: int | Length | None = None, height: int | Length | None = None, *, honor_exif_orientation: bool = True, ) -> Tuple[Length, Length]: """(cx, cy) pair representing scaled dimensions of this image. The native dimensions of the image are scaled by applying the following rules to the `width` and `height` arguments. * If both `width` and `height` are specified, the return value is (`width`, `height`); no scaling is performed. * If only one is specified, it is used to compute a scaling factor that is then applied to the unspecified dimension, preserving the aspect ratio of the image. * If both `width` and `height` are |None|, the native dimensions are returned. The native dimensions are calculated using the dots-per-inch (dpi) value embedded in the image, defaulting to 72 dpi if no value is specified, as is often the case. The returned values are both |Length| objects. The *display* dimensions are used, so a photo carrying an EXIF orientation that turns it a quarter is scaled to the aspect ratio it will be rendered at rather than the one its stored pixels have. Pass `honor_exif_orientation=False` for the stored dimensions — for an image whose pixels are already rotated *and* which carries the tag anyway, which some encoders produce and nothing can detect. """ native_width = self.display_width if honor_exif_orientation else self.width native_height = self.display_height if honor_exif_orientation else self.height if width is None and height is None: return native_width, native_height if width is None: assert height is not None scaling_factor = float(height) / float(native_height) width = round(native_width * scaling_factor) if height is None: scaling_factor = float(width) / float(native_width) height = round(native_height * scaling_factor) return Emu(width), Emu(height) ``` #### sha1 ``` sha1() ``` SHA1 hash digest of the image blob. Source code in `src/docx/image/image.py` ``` @lazyproperty def sha1(self): """SHA1 hash digest of the image blob.""" return hashlib.sha1(self._blob).hexdigest() ``` ### BaseImageHeader ``` BaseImageHeader( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Base class for image header subclasses like Jpeg and Tiff. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### orientation ``` orientation: int ``` The EXIF `Orientation` of this image, 1 through 8; 1 when it declares none. Only JPEG and TIFF carry the tag; every other format reports 1. #### content_type ``` content_type: str ``` Abstract property definition, must be implemented by all subclasses. #### default_ext ``` default_ext: str ``` Default filename extension for images of this type. An abstract property definition, must be implemented by all subclasses. #### px_width ``` px_width ``` The horizontal pixel dimension of the image. #### px_height ``` px_height ``` The vertical pixel dimension of the image. #### horz_dpi ``` horz_dpi ``` Integer dots per inch for the width of this image. Defaults to 72 when not present in the file, as is often the case. #### vert_dpi ``` vert_dpi ``` Integer dots per inch for the height of this image. Defaults to 72 when not present in the file, as is often the case. ### \_ImageHeaderFactory ``` _ImageHeaderFactory(stream: IO[bytes]) ``` A BaseImageHeader subclass instance that can parse headers of image in `stream`. Most formats are identified by a magic number at a fixed offset. A format that has no such number — SVG, being XML — is identified by a sniffer function instead, and only after every signature has failed to match, so sniffing can never shadow an exact identification. Source code in `src/docx/image/image.py` ``` def _ImageHeaderFactory(stream: IO[bytes]): """A |BaseImageHeader| subclass instance that can parse headers of image in `stream`. Most formats are identified by a magic number at a fixed offset. A format that has no such number — SVG, being XML — is identified by a sniffer function instead, and only after every signature has failed to match, so sniffing can never shadow an exact identification. """ from docx.image import SIGNATURES, SNIFFERS stream.seek(0) header = stream.read(_HEADER_SAMPLE_LENGTH) for cls, offset, signature_bytes in SIGNATURES: end = offset + len(signature_bytes) if header[offset:end] == signature_bytes: return cls.from_stream(stream) for cls, sniff in SNIFFERS: if sniff(header): return cls.from_stream(stream) raise UnrecognizedImageError ``` ## jpeg Objects related to parsing headers of JPEG image streams. Includes both JFIF and Exif sub-formats. ### Jpeg ``` Jpeg( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `BaseImageHeader` Base class for JFIF and EXIF subclasses. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### content_type ``` content_type ``` MIME content type for this image, unconditionally `image/jpeg` for JPEG images. #### default_ext ``` default_ext ``` Default filename extension, always 'jpg' for JPG images. ### Exif ``` Exif( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `Jpeg` Image header parser for Exif image format. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### from_stream ``` from_stream(stream) ``` Return Exif instance having header properties parsed from Exif image in `stream`. Source code in `src/docx/image/jpeg.py` ``` @classmethod def from_stream(cls, stream): """Return |Exif| instance having header properties parsed from Exif image in `stream`.""" markers = _JfifMarkers.from_stream(stream) # print('\n%s' % markers) px_width = markers.sof.px_width px_height = markers.sof.px_height horz_dpi = markers.app1.horz_dpi vert_dpi = markers.app1.vert_dpi orientation = markers.app1.orientation return cls(px_width, px_height, horz_dpi, vert_dpi, orientation) ``` ### Jfif ``` Jfif( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `Jpeg` Image header parser for JFIF image format. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### from_stream ``` from_stream(stream) ``` Return a Jfif instance having header properties parsed from image in `stream`. Source code in `src/docx/image/jpeg.py` ``` @classmethod def from_stream(cls, stream): """Return a |Jfif| instance having header properties parsed from image in `stream`.""" markers = _JfifMarkers.from_stream(stream) px_width = markers.sof.px_width px_height = markers.sof.px_height horz_dpi = markers.app0.horz_dpi vert_dpi = markers.app0.vert_dpi return cls(px_width, px_height, horz_dpi, vert_dpi) ``` ### \_JfifMarkers ``` _JfifMarkers(markers) ``` Sequence of markers in a JPEG file, perhaps truncated at first SOS marker for performance reasons. Source code in `src/docx/image/jpeg.py` ``` def __init__(self, markers): super(_JfifMarkers, self).__init__() self._markers = list(markers) ``` #### app0 ``` app0 ``` First APP0 marker in image markers. #### app1 ``` app1 ``` First APP1 marker in image markers. #### sof ``` sof ``` First start of frame (SOFn) marker in this sequence. #### from_stream ``` from_stream(stream) ``` Return a \_JfifMarkers instance containing a `_JfifMarker` subclass instance for each marker in `stream`. Source code in `src/docx/image/jpeg.py` ``` @classmethod def from_stream(cls, stream): """Return a |_JfifMarkers| instance containing a |_JfifMarker| subclass instance for each marker in `stream`.""" marker_parser = _MarkerParser.from_stream(stream) markers = [] for marker in marker_parser.iter_markers(): markers.append(marker) if marker.marker_code == JPEG_MARKER_CODE.SOS: break return cls(markers) ``` ### \_MarkerParser ``` _MarkerParser(stream_reader) ``` Service class that knows how to parse a JFIF stream and iterate over its markers. Source code in `src/docx/image/jpeg.py` ``` def __init__(self, stream_reader): super(_MarkerParser, self).__init__() self._stream = stream_reader ``` #### from_stream ``` from_stream(stream) ``` Return a \_MarkerParser instance to parse JFIF markers from `stream`. Source code in `src/docx/image/jpeg.py` ``` @classmethod def from_stream(cls, stream): """Return a |_MarkerParser| instance to parse JFIF markers from `stream`.""" stream_reader = StreamReader(stream, BIG_ENDIAN) return cls(stream_reader) ``` #### iter_markers ``` iter_markers() ``` Generate a (marker_code, segment_offset) 2-tuple for each marker in the JPEG `stream`, in the order they occur in the stream. Source code in `src/docx/image/jpeg.py` ``` def iter_markers(self): """Generate a (marker_code, segment_offset) 2-tuple for each marker in the JPEG `stream`, in the order they occur in the stream.""" marker_finder = _MarkerFinder.from_stream(self._stream) start = 0 marker_code = None while marker_code != JPEG_MARKER_CODE.EOI: marker_code, segment_offset = marker_finder.next(start) marker = _MarkerFactory(marker_code, self._stream, segment_offset) yield marker start = segment_offset + marker.segment_length ``` ### \_MarkerFinder ``` _MarkerFinder(stream) ``` Service class that knows how to find the next JFIF marker in a stream. Source code in `src/docx/image/jpeg.py` ``` def __init__(self, stream): super(_MarkerFinder, self).__init__() self._stream = stream ``` #### from_stream ``` from_stream(stream) ``` Return a \_MarkerFinder instance to find JFIF markers in `stream`. Source code in `src/docx/image/jpeg.py` ``` @classmethod def from_stream(cls, stream): """Return a |_MarkerFinder| instance to find JFIF markers in `stream`.""" return cls(stream) ``` #### next ``` next(start) ``` Return a (marker_code, segment_offset) 2-tuple identifying and locating the first marker in `stream` occuring after offset `start`. The returned `segment_offset` points to the position immediately following the 2-byte marker code, the start of the marker segment, for those markers that have a segment. Source code in `src/docx/image/jpeg.py` ``` def next(self, start): """Return a (marker_code, segment_offset) 2-tuple identifying and locating the first marker in `stream` occuring after offset `start`. The returned `segment_offset` points to the position immediately following the 2-byte marker code, the start of the marker segment, for those markers that have a segment. """ position = start while True: # skip over any non-\xFF bytes position = self._offset_of_next_ff_byte(start=position) # skip over any \xFF padding bytes position, byte_ = self._next_non_ff_byte(start=position + 1) # 'FF 00' sequence is not a marker, start over if found if byte_ == b"\x00": continue # this is a marker, gather return values and break out of scan marker_code, segment_offset = byte_, position + 1 break return marker_code, segment_offset ``` ### \_Marker ``` _Marker(marker_code, offset, segment_length) ``` Base class for JFIF marker classes. Represents a marker and its segment occuring in a JPEG byte stream. Source code in `src/docx/image/jpeg.py` ``` def __init__(self, marker_code, offset, segment_length): super(_Marker, self).__init__() self._marker_code = marker_code self._offset = offset self._segment_length = segment_length ``` #### marker_code ``` marker_code ``` The single-byte code that identifies the type of this marker, e.g. `'à'` for start of image (SOI). #### segment_length ``` segment_length ``` The length in bytes of this marker's segment. #### from_stream ``` from_stream(stream, marker_code, offset) ``` Return a generic \_Marker instance for the marker at `offset` in `stream` having `marker_code`. Source code in `src/docx/image/jpeg.py` ``` @classmethod def from_stream(cls, stream, marker_code, offset): """Return a generic |_Marker| instance for the marker at `offset` in `stream` having `marker_code`.""" if JPEG_MARKER_CODE.is_standalone(marker_code): segment_length = 0 else: segment_length = stream.read_short(offset) return cls(marker_code, offset, segment_length) ``` ### \_App0Marker ``` _App0Marker( marker_code, offset, length, density_units, x_density, y_density, ) ``` Bases: `_Marker` Represents a JFIF APP0 marker segment. Source code in `src/docx/image/jpeg.py` ``` def __init__(self, marker_code, offset, length, density_units, x_density, y_density): super(_App0Marker, self).__init__(marker_code, offset, length) self._density_units = density_units self._x_density = x_density self._y_density = y_density ``` #### horz_dpi ``` horz_dpi ``` Horizontal dots per inch specified in this marker, defaults to 72 if not specified. #### vert_dpi ``` vert_dpi ``` Vertical dots per inch specified in this marker, defaults to 72 if not specified. #### from_stream ``` from_stream(stream, marker_code, offset) ``` Return an \_App0Marker instance for the APP0 marker at `offset` in `stream`. Source code in `src/docx/image/jpeg.py` ``` @classmethod def from_stream(cls, stream, marker_code, offset): """Return an |_App0Marker| instance for the APP0 marker at `offset` in `stream`.""" # field off type notes # ------------------ --- ----- ------------------- # segment length 0 short # JFIF identifier 2 5 chr 'JFIF\x00' # major JPEG version 7 byte typically 1 # minor JPEG version 8 byte typically 1 or 2 # density units 9 byte 1=inches, 2=cm # horz dots per unit 10 short # vert dots per unit 12 short # ------------------ --- ----- ------------------- segment_length = stream.read_short(offset) density_units = stream.read_byte(offset, 9) x_density = stream.read_short(offset, 10) y_density = stream.read_short(offset, 12) return cls(marker_code, offset, segment_length, density_units, x_density, y_density) ``` ### \_App1Marker ``` _App1Marker( marker_code, offset, length, horz_dpi, vert_dpi, orientation=1, ) ``` Bases: `_Marker` Represents a JFIF APP1 (Exif) marker segment. Source code in `src/docx/image/jpeg.py` ``` def __init__(self, marker_code, offset, length, horz_dpi, vert_dpi, orientation=1): super(_App1Marker, self).__init__(marker_code, offset, length) self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### horz_dpi ``` horz_dpi ``` Horizontal dots per inch specified in this marker, defaults to 72 if not specified. #### vert_dpi ``` vert_dpi ``` Vertical dots per inch specified in this marker, defaults to 72 if not specified. #### orientation ``` orientation ``` EXIF `Orientation` specified in this marker, 1 if it specifies none. #### from_stream ``` from_stream(stream, marker_code, offset) ``` Extract the horizontal and vertical dots-per-inch value from the APP1 header at `offset` in `stream`. Source code in `src/docx/image/jpeg.py` ``` @classmethod def from_stream(cls, stream, marker_code, offset): """Extract the horizontal and vertical dots-per-inch value from the APP1 header at `offset` in `stream`.""" # field off len type notes # -------------------- --- --- ----- ---------------------------- # segment length 0 2 short # Exif identifier 2 6 6 chr 'Exif\x00\x00' # TIFF byte order 8 2 2 chr 'II'=little 'MM'=big endian # meaning of universe 10 2 2 chr '*\x00' or '\x00*' depending # IFD0 off fr/II or MM 10 16 long relative to ...? # -------------------- --- --- ----- ---------------------------- segment_length = stream.read_short(offset) if cls._is_non_Exif_APP1_segment(stream, offset): return cls(marker_code, offset, segment_length, 72, 72) tiff = cls._tiff_from_exif_segment(stream, offset, segment_length) return cls( marker_code, offset, segment_length, tiff.horz_dpi, tiff.vert_dpi, tiff.orientation, ) ``` ### \_SofMarker ``` _SofMarker( marker_code, offset, segment_length, px_width, px_height ) ``` Bases: `_Marker` Represents a JFIF start of frame (SOFx) marker segment. Source code in `src/docx/image/jpeg.py` ``` def __init__(self, marker_code, offset, segment_length, px_width, px_height): super(_SofMarker, self).__init__(marker_code, offset, segment_length) self._px_width = px_width self._px_height = px_height ``` #### px_height ``` px_height ``` Image height in pixels. #### px_width ``` px_width ``` Image width in pixels. #### from_stream ``` from_stream(stream, marker_code, offset) ``` Return an \_SofMarker instance for the SOFn marker at `offset` in stream. Source code in `src/docx/image/jpeg.py` ``` @classmethod def from_stream(cls, stream, marker_code, offset): """Return an |_SofMarker| instance for the SOFn marker at `offset` in stream.""" # field off type notes # ------------------ --- ----- ---------------------------- # segment length 0 short # Data precision 2 byte # Vertical lines 3 short px_height # Horizontal lines 5 short px_width # ------------------ --- ----- ---------------------------- segment_length = stream.read_short(offset) px_height = stream.read_short(offset, 3) px_width = stream.read_short(offset, 5) return cls(marker_code, offset, segment_length, px_width, px_height) ``` ### \_MarkerFactory ``` _MarkerFactory(marker_code, stream, offset) ``` Return \_Marker or subclass instance appropriate for marker at `offset` in `stream` having `marker_code`. Source code in `src/docx/image/jpeg.py` ``` def _MarkerFactory(marker_code, stream, offset): """Return |_Marker| or subclass instance appropriate for marker at `offset` in `stream` having `marker_code`.""" if marker_code == JPEG_MARKER_CODE.APP0: marker_cls = _App0Marker elif marker_code == JPEG_MARKER_CODE.APP1: marker_cls = _App1Marker elif marker_code in JPEG_MARKER_CODE.SOF_MARKER_CODES: marker_cls = _SofMarker else: marker_cls = _Marker return marker_cls.from_stream(stream, marker_code, offset) ``` ## png ### Png ``` Png( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `BaseImageHeader` Image header parser for PNG images. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### content_type ``` content_type ``` MIME content type for this image, unconditionally `image/png` for PNG images. #### default_ext ``` default_ext ``` Default filename extension, always 'png' for PNG images. #### from_stream ``` from_stream(stream) ``` Return a Png instance having header properties parsed from image in `stream`. Source code in `src/docx/image/png.py` ``` @classmethod def from_stream(cls, stream): """Return a |Png| instance having header properties parsed from image in `stream`.""" parser = _PngParser.parse(stream) px_width = parser.px_width px_height = parser.px_height horz_dpi = parser.horz_dpi vert_dpi = parser.vert_dpi return cls(px_width, px_height, horz_dpi, vert_dpi) ``` ### \_PngParser ``` _PngParser(chunks) ``` Parses a PNG image stream to extract the image properties found in its chunks. Source code in `src/docx/image/png.py` ``` def __init__(self, chunks): super(_PngParser, self).__init__() self._chunks = chunks ``` #### px_width ``` px_width ``` The number of pixels in each row of the image. #### px_height ``` px_height ``` The number of stacked rows of pixels in the image. #### horz_dpi ``` horz_dpi ``` Integer dots per inch for the width of this image. Defaults to 72 when not present in the file, as is often the case. #### vert_dpi ``` vert_dpi ``` Integer dots per inch for the height of this image. Defaults to 72 when not present in the file, as is often the case. #### parse ``` parse(stream) ``` Return a \_PngParser instance containing the header properties parsed from the PNG image in `stream`. Source code in `src/docx/image/png.py` ``` @classmethod def parse(cls, stream): """Return a |_PngParser| instance containing the header properties parsed from the PNG image in `stream`.""" chunks = _Chunks.from_stream(stream) return cls(chunks) ``` ### \_Chunks ``` _Chunks(chunk_iterable) ``` Collection of the chunks parsed from a PNG image stream. Source code in `src/docx/image/png.py` ``` def __init__(self, chunk_iterable): super(_Chunks, self).__init__() self._chunks = list(chunk_iterable) ``` #### IHDR ``` IHDR ``` IHDR chunk in PNG image. #### pHYs ``` pHYs ``` PHYs chunk in PNG image, or `None` if not present. #### from_stream ``` from_stream(stream) ``` Return a \_Chunks instance containing the PNG chunks in `stream`. Source code in `src/docx/image/png.py` ``` @classmethod def from_stream(cls, stream): """Return a |_Chunks| instance containing the PNG chunks in `stream`.""" chunk_parser = _ChunkParser.from_stream(stream) chunks = list(chunk_parser.iter_chunks()) return cls(chunks) ``` ### \_ChunkParser ``` _ChunkParser(stream_rdr) ``` Extracts chunks from a PNG image stream. Source code in `src/docx/image/png.py` ``` def __init__(self, stream_rdr): super(_ChunkParser, self).__init__() self._stream_rdr = stream_rdr ``` #### from_stream ``` from_stream(stream) ``` Return a \_ChunkParser instance that can extract the chunks from the PNG image in `stream`. Source code in `src/docx/image/png.py` ``` @classmethod def from_stream(cls, stream): """Return a |_ChunkParser| instance that can extract the chunks from the PNG image in `stream`.""" stream_rdr = StreamReader(stream, BIG_ENDIAN) return cls(stream_rdr) ``` #### iter_chunks ``` iter_chunks() ``` Generate a \_Chunk subclass instance for each chunk in this parser's PNG stream, in the order encountered in the stream. Source code in `src/docx/image/png.py` ``` def iter_chunks(self): """Generate a |_Chunk| subclass instance for each chunk in this parser's PNG stream, in the order encountered in the stream.""" for chunk_type, offset in self._iter_chunk_offsets(): chunk = _ChunkFactory(chunk_type, self._stream_rdr, offset) yield chunk ``` ### \_Chunk ``` _Chunk(chunk_type) ``` Base class for specific chunk types. Also serves as the default chunk type. Source code in `src/docx/image/png.py` ``` def __init__(self, chunk_type): super(_Chunk, self).__init__() self._chunk_type = chunk_type ``` #### type_name ``` type_name ``` The chunk type name, e.g. 'IHDR', 'pHYs', etc. #### from_offset ``` from_offset(chunk_type, stream_rdr, offset) ``` Return a default \_Chunk instance that only knows its chunk type. Source code in `src/docx/image/png.py` ``` @classmethod def from_offset(cls, chunk_type, stream_rdr, offset): """Return a default _Chunk instance that only knows its chunk type.""" return cls(chunk_type) ``` ### \_IHDRChunk ``` _IHDRChunk(chunk_type, px_width, px_height) ``` Bases: `_Chunk` IHDR chunk, contains the image dimensions. Source code in `src/docx/image/png.py` ``` def __init__(self, chunk_type, px_width, px_height): super(_IHDRChunk, self).__init__(chunk_type) self._px_width = px_width self._px_height = px_height ``` #### from_offset ``` from_offset(chunk_type, stream_rdr, offset) ``` Return an \_IHDRChunk instance containing the image dimensions extracted from the IHDR chunk in `stream` at `offset`. Source code in `src/docx/image/png.py` ``` @classmethod def from_offset(cls, chunk_type, stream_rdr, offset): """Return an _IHDRChunk instance containing the image dimensions extracted from the IHDR chunk in `stream` at `offset`.""" px_width = stream_rdr.read_long(offset) px_height = stream_rdr.read_long(offset, 4) return cls(chunk_type, px_width, px_height) ``` ### \_ChunkFactory ``` _ChunkFactory(chunk_type, stream_rdr, offset) ``` Return a \_Chunk subclass instance appropriate to `chunk_type` parsed from `stream_rdr` at `offset`. Source code in `src/docx/image/png.py` ``` def _ChunkFactory(chunk_type, stream_rdr, offset): """Return a |_Chunk| subclass instance appropriate to `chunk_type` parsed from `stream_rdr` at `offset`.""" chunk_cls_map = { PNG_CHUNK_TYPE.IHDR: _IHDRChunk, PNG_CHUNK_TYPE.pHYs: _pHYsChunk, } chunk_cls = chunk_cls_map.get(chunk_type, _Chunk) return chunk_cls.from_offset(chunk_type, stream_rdr, offset) ``` ## svg Image header parser for SVG images. SVG is XML, so there is no fixed-offset magic number to match and no binary header to unpack. Size comes from the `width` and `height` attributes of the root `` element, which are CSS lengths and may carry any of the usual units, or — when those are absent or given as percentages, which is common for icons meant to scale — from the `viewBox`. One SVG user unit is one CSS pixel, 1/96 inch, which is why the resolution reported here is 96 rather than the 72 the resolution-free raster formats assume. ### Svg ``` Svg( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `BaseImageHeader` Image header parser for SVG images. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### content_type ``` content_type ``` MIME content type for this image, unconditionally `image/svg+xml` for SVG images. #### default_ext ``` default_ext ``` Default filename extension, always 'svg' for SVG images. #### sniff ``` sniff(header: bytes) -> bool ``` True when `header` looks like the start of an SVG document. SVG has no magic number, so detection is by finding an ` bool: """True when `header` looks like the start of an SVG document. SVG has no magic number, so detection is by finding an ``, such as XHTML -- first_element = re.search(rb"<[A-Za-z]", prefix) return first_element is not None and first_element.start() == match.start() ``` #### from_stream ``` from_stream(stream) ``` Return an Svg instance with header properties parsed from `stream`. Source code in `src/docx/image/svg.py` ``` @classmethod def from_stream(cls, stream): """Return an |Svg| instance with header properties parsed from `stream`.""" root = cls._parse_root(stream) inch_width, inch_height = cls._extents_in_inches(root) px_width = int(round(inch_width * _SVG_DPI)) px_height = int(round(inch_height * _SVG_DPI)) return cls(px_width, px_height, _SVG_DPI, _SVG_DPI) ``` ## tiff ### Tiff ``` Tiff( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `BaseImageHeader` Image header parser for TIFF images. Handles both big and little endian byte ordering. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### content_type ``` content_type ``` Return the MIME type of this TIFF image, unconditionally the string `image/tiff`. #### default_ext ``` default_ext ``` Default filename extension, always 'tiff' for TIFF images. #### from_stream ``` from_stream(stream) ``` Return a Tiff instance containing the properties of the TIFF image in `stream`. Source code in `src/docx/image/tiff.py` ``` @classmethod def from_stream(cls, stream): """Return a |Tiff| instance containing the properties of the TIFF image in `stream`.""" parser = _TiffParser.parse(stream) px_width = parser.px_width px_height = parser.px_height horz_dpi = parser.horz_dpi vert_dpi = parser.vert_dpi orientation = parser.orientation return cls(px_width, px_height, horz_dpi, vert_dpi, orientation) ``` ### \_TiffParser ``` _TiffParser(ifd_entries) ``` Parses a TIFF image stream to extract the image properties found in its main image file directory (IFD) Source code in `src/docx/image/tiff.py` ``` def __init__(self, ifd_entries): super(_TiffParser, self).__init__() self._ifd_entries = ifd_entries ``` #### horz_dpi ``` horz_dpi ``` The horizontal dots per inch value calculated from the XResolution and ResolutionUnit tags of the IFD; defaults to 72 if those tags are not present. #### vert_dpi ``` vert_dpi ``` The vertical dots per inch value calculated from the XResolution and ResolutionUnit tags of the IFD; defaults to 72 if those tags are not present. #### orientation ``` orientation: int ``` The `Orientation` tag value, or 1 when the tag is absent or unparseable. 1 means the stored pixels are already in display order, which is what every value outside the documented 1..8 range is treated as: an orientation nobody can act on is better ignored than guessed at. #### px_height ``` px_height ``` The number of stacked rows of pixels in the image, `None` if the IFD contains no `ImageLength` tag, the expected case when the TIFF is embeded in an Exif image. #### px_width ``` px_width ``` The number of pixels in each row in the image, `None` if the IFD contains no `ImageWidth` tag, the expected case when the TIFF is embeded in an Exif image. #### parse ``` parse(stream) ``` Return an instance of \_TiffParser containing the properties parsed from the TIFF image in `stream`. Source code in `src/docx/image/tiff.py` ``` @classmethod def parse(cls, stream): """Return an instance of |_TiffParser| containing the properties parsed from the TIFF image in `stream`.""" stream_rdr = cls._make_stream_reader(stream) ifd0_offset = stream_rdr.read_long(4) ifd_entries = _IfdEntries.from_stream(stream_rdr, ifd0_offset) return cls(ifd_entries) ``` ### \_IfdEntries ``` _IfdEntries(entries) ``` Image File Directory for a TIFF image, having mapping (dict) semantics allowing "tag" values to be retrieved by tag code. Source code in `src/docx/image/tiff.py` ``` def __init__(self, entries): super(_IfdEntries, self).__init__() self._entries = entries ``` #### from_stream ``` from_stream(stream, offset) ``` Return a new \_IfdEntries instance parsed from `stream` starting at `offset`. Source code in `src/docx/image/tiff.py` ``` @classmethod def from_stream(cls, stream, offset): """Return a new |_IfdEntries| instance parsed from `stream` starting at `offset`.""" ifd_parser = _IfdParser(stream, offset) entries = {e.tag: e.value for e in ifd_parser.iter_entries()} return cls(entries) ``` #### get ``` get(tag_code, default=None) ``` Return value of IFD entry having tag matching `tag_code`, or `default` if no matching tag found. Source code in `src/docx/image/tiff.py` ``` def get(self, tag_code, default=None): """Return value of IFD entry having tag matching `tag_code`, or `default` if no matching tag found.""" return self._entries.get(tag_code, default) ``` ### \_IfdParser ``` _IfdParser(stream_rdr, offset) ``` Service object that knows how to extract directory entries from an Image File Directory (IFD) Source code in `src/docx/image/tiff.py` ``` def __init__(self, stream_rdr, offset): super(_IfdParser, self).__init__() self._stream_rdr = stream_rdr self._offset = offset ``` #### iter_entries ``` iter_entries() ``` Generate an \_IfdEntry instance corresponding to each entry in the directory. Source code in `src/docx/image/tiff.py` ``` def iter_entries(self): """Generate an |_IfdEntry| instance corresponding to each entry in the directory.""" for idx in range(self._entry_count): dir_entry_offset = self._offset + 2 + (idx * 12) ifd_entry = _IfdEntryFactory(self._stream_rdr, dir_entry_offset) yield ifd_entry ``` ### \_IfdEntry ``` _IfdEntry(tag_code, value) ``` Base class for IFD entry classes. Subclasses are differentiated by value type, e.g. ASCII, long int, etc. Source code in `src/docx/image/tiff.py` ``` def __init__(self, tag_code, value): super(_IfdEntry, self).__init__() self._tag_code = tag_code self._value = value ``` #### tag ``` tag ``` Short int code that identifies this IFD entry. #### value ``` value ``` Value of this tag, its type being dependent on the tag. #### from_stream ``` from_stream(stream_rdr, offset) ``` Return an \_IfdEntry subclass instance containing the tag and value of the tag parsed from `stream_rdr` at `offset`. Note this method is common to all subclasses. Override the `_parse_value()` method to provide distinctive behavior based on field type. Source code in `src/docx/image/tiff.py` ``` @classmethod def from_stream(cls, stream_rdr, offset): """Return an |_IfdEntry| subclass instance containing the tag and value of the tag parsed from `stream_rdr` at `offset`. Note this method is common to all subclasses. Override the ``_parse_value()`` method to provide distinctive behavior based on field type. """ tag_code = stream_rdr.read_short(offset, 0) value_count = stream_rdr.read_long(offset, 4) value_offset = stream_rdr.read_long(offset, 8) value = cls._parse_value(stream_rdr, offset, value_count, value_offset) return cls(tag_code, value) ``` ### \_AsciiIfdEntry ``` _AsciiIfdEntry(tag_code, value) ``` Bases: `_IfdEntry` IFD entry having the form of a NULL-terminated ASCII string. Source code in `src/docx/image/tiff.py` ``` def __init__(self, tag_code, value): super(_IfdEntry, self).__init__() self._tag_code = tag_code self._value = value ``` ### \_ShortIfdEntry ``` _ShortIfdEntry(tag_code, value) ``` Bases: `_IfdEntry` IFD entry expressed as a short (2-byte) integer. Source code in `src/docx/image/tiff.py` ``` def __init__(self, tag_code, value): super(_IfdEntry, self).__init__() self._tag_code = tag_code self._value = value ``` ### \_LongIfdEntry ``` _LongIfdEntry(tag_code, value) ``` Bases: `_IfdEntry` IFD entry expressed as a long (4-byte) integer. Source code in `src/docx/image/tiff.py` ``` def __init__(self, tag_code, value): super(_IfdEntry, self).__init__() self._tag_code = tag_code self._value = value ``` ### \_RationalIfdEntry ``` _RationalIfdEntry(tag_code, value) ``` Bases: `_IfdEntry` IFD entry expressed as a numerator, denominator pair. Source code in `src/docx/image/tiff.py` ``` def __init__(self, tag_code, value): super(_IfdEntry, self).__init__() self._tag_code = tag_code self._value = value ``` ### \_IfdEntryFactory ``` _IfdEntryFactory(stream_rdr, offset) ``` Return an \_IfdEntry subclass instance containing the value of the directory entry at `offset` in `stream_rdr`. Source code in `src/docx/image/tiff.py` ``` def _IfdEntryFactory(stream_rdr, offset): """Return an |_IfdEntry| subclass instance containing the value of the directory entry at `offset` in `stream_rdr`.""" ifd_entry_classes = { TIFF_FLD.ASCII: _AsciiIfdEntry, TIFF_FLD.SHORT: _ShortIfdEntry, TIFF_FLD.LONG: _LongIfdEntry, TIFF_FLD.RATIONAL: _RationalIfdEntry, } field_type = stream_rdr.read_short(offset, 2) EntryCls = ifd_entry_classes.get(field_type, _IfdEntry) return EntryCls.from_stream(stream_rdr, offset) ``` ## webp Image header parser for WebP images. A WebP file is a RIFF container: `"RIFF"`, a four-byte file size, `"WEBP"`, and then a chunk whose FourCC says which of the three bitstream variants follows. The image dimensions live in a different place in each, which is the whole of the work here. Word renders WebP natively from Microsoft 365 / Word 2021 onward. Earlier versions show a placeholder instead, so a document that has to open in Word 2019 or earlier should carry PNG or JPEG. ### Webp ``` Webp( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `BaseImageHeader` Image header parser for WebP images. Note that the WebP format carries no resolution (DPI) information. Both horizontal and vertical DPI default to 72. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### content_type ``` content_type ``` MIME content type for this image, unconditionally `image/webp` for WebP images. #### default_ext ``` default_ext ``` Default filename extension, always 'webp' for WebP images. #### from_stream ``` from_stream(stream) ``` Return Webp instance having header properties parsed from the WebP image in `stream`. Source code in `src/docx/image/webp.py` ``` @classmethod def from_stream(cls, stream): """Return |Webp| instance having header properties parsed from the WebP image in `stream`.""" px_width, px_height = cls._dimensions_from_stream(stream) return cls(px_width, px_height, _WEBP_DPI, _WEBP_DPI) ``` ## wmf Image header parser for WMF (Windows Metafile) images. A bare WMF records only drawing commands in metafile units and says nothing about how large the result should be. The physical size comes from the Aldus Placeable Metafile header, a 22-byte prefix carrying a bounding box and the number of metafile units per inch. Only a placeable WMF is recognized here, because a bare one gives us nothing to size the picture with. ### Wmf ``` Wmf( px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ) ``` Bases: `BaseImageHeader` Image header parser for WMF images having an Aldus Placeable Metafile header. Source code in `src/docx/image/image.py` ``` def __init__( self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int, orientation: int = 1, ): self._px_width = px_width self._px_height = px_height self._horz_dpi = horz_dpi self._vert_dpi = vert_dpi self._orientation = orientation ``` #### content_type ``` content_type ``` MIME content type for this image, unconditionally `image/x-wmf` for WMF images. #### default_ext ``` default_ext ``` Default filename extension, always 'wmf' for WMF images. #### from_stream ``` from_stream(stream) ``` Return a Wmf instance with header properties parsed from `stream`. Source code in `src/docx/image/wmf.py` ``` @classmethod def from_stream(cls, stream): """Return a |Wmf| instance with header properties parsed from `stream`.""" stream.seek(0) header = stream.read(_APM_HEADER_LENGTH) if len(header) < _APM_HEADER_LENGTH: raise InvalidImageStreamError("unexpected end of WMF image stream") key, _hwmf, left, top, right, bottom, inch, _reserved, _checksum = _APM_HEADER.unpack( header ) if key != _APM_KEY: raise InvalidImageStreamError( "WMF image has no Aldus Placeable Metafile header, so its display size is unknown" ) if inch == 0: raise InvalidImageStreamError("WMF image declares zero metafile units per inch") # -- the bounding box is in metafile units, `inch` of them to the inch -- inch_width = abs(right - left) / inch inch_height = abs(bottom - top) / inch if inch_width == 0 or inch_height == 0: raise InvalidImageStreamError("WMF image has a zero-size bounding box") px_width = int(round(inch_width * _WMF_DPI)) px_height = int(round(inch_height * _WMF_DPI)) return cls(px_width, px_height, _WMF_DPI, _WMF_DPI) ``` ## opc ## constants Constant values related to the Open Packaging Convention. In particular it includes content types and relationship types. ### CONTENT_TYPE Content type URIs (like MIME-types) that specify a part's format. ### NAMESPACE Constant values for OPC XML namespaces. ### RELATIONSHIP_TARGET_MODE Open XML relationship target modes. ## coreprops Provides CoreProperties, Dublin-Core attributes of the document. These are broadly-standardized attributes like author, last-modified, etc. ### CoreProperties ``` CoreProperties(element: CT_CoreProperties) ``` Corresponds to part named `/docProps/core.xml`, containing the core document properties for this document package. Source code in `src/docx/opc/coreprops.py` ``` def __init__(self, element: CT_CoreProperties): self._element = element ``` ## customprops Provides CustomProperties, the arbitrary named values a document can carry. These are the properties stored in `/docProps/custom.xml`, the third document-properties part alongside the Dublin-Core properties in `core.xml` and the application properties in `app.xml`. Word shows them under File > Info > Properties > Advanced, and a `DOCPROPERTY` field in the document body refers to one by name. ### CustomProperties ``` CustomProperties(element: CT_CustomProperties) ``` Bases: `MutableMapping[str, 'str | int | float | bool | dt.datetime | None']` A mapping of custom document property name to value. Behaves as a `dict` of `str` to value: ``` document.custom_properties["Matter number"] = 4242 document.custom_properties["Reviewed"] = True del document.custom_properties["Draft"] ``` A value may be a `str`, `int`, `float`, `bool` or `datetime`, which cover the variant types Word writes and read back as the same Python type. Assigning any other type raises `ValueError` rather than writing a file Word would refuse to open. A property whose value uses a variant this library does not model — a vector, array or blob — reads as the raw text of its element, so a document that carries one can still be read and re-saved without losing it. Property names are case-sensitive and must be unique; assigning to an existing name replaces its value and leaves its property id alone. Source code in `src/docx/opc/customprops.py` ``` def __init__(self, element: CT_CustomProperties): self._element = element ``` #### lookup_by_pid ``` lookup_by_pid( pid: int, ) -> str | int | float | bool | datetime | None ``` The value of the property having property id `pid`. Raises `KeyError` when no property has that id. Property ids matter only for documents that reference a property by id rather than name; the mapping interface is the ordinary way in. Source code in `src/docx/opc/customprops.py` ``` def lookup_by_pid(self, pid: int) -> str | int | float | bool | dt.datetime | None: """The value of the property having property id `pid`. Raises |KeyError| when no property has that id. Property ids matter only for documents that reference a property by id rather than name; the mapping interface is the ordinary way in. """ for property in self._element.property_lst: if property.pid == pid: return property.value raise KeyError(pid) ``` ## exceptions Exceptions specific to python-opc. The base exception class is OpcError. ### OpcError Bases: `Exception` Base error class for python-opc. ### PackageNotFoundError Bases: `OpcError` Raised when a package cannot be found at the specified path. Also raised when the file is present but is not a readable OPC package, for example a truncated download or a file that is not a zip archive at all. ### DanglingRelationshipWarning Bases: `UserWarning` Issued when a relationship targets a part that is not present in the package. The relationship is dropped on load, which is what Word does with one. Filter this category to silence the warning, or turn it into an error with `warnings.simplefilter`. ### EncryptedPackageError Bases: `PackageNotFoundError` Raised when the file is a password-protected (encrypted) Office document. An encrypted document is an OLE compound file wrapping the encrypted package, not a zip archive, so it cannot be read without the password. Subclasses PackageNotFoundError so that callers already handling an unreadable file keep working; catch this class specifically to tell the user their file is encrypted. ## extendedprops Provides ExtendedProperties, the application-specific document properties. These are the properties stored in `/docProps/app.xml`, such as the word count, the editing time and the application that produced the document. They complement the Dublin-Core properties in `/docProps/core.xml` exposed by CoreProperties. ### ExtendedProperties ``` ExtendedProperties(element: CT_ExtendedProperties) ``` Corresponds to part named `/docProps/app.xml`. Every property reads `None` when the corresponding element is absent from the XML, which is the normal state for most of them. Assigning `None` removes the element. Word maintains the statistics properties (`pages`, `words`, `characters`, `characters_with_spaces`, `lines`, `paragraphs`) itself and recalculates them when the document is next opened and repaginated. Values written here are what a reader sees before that happens; this library does not compute them. Source code in `src/docx/opc/extendedprops.py` ``` def __init__(self, element: CT_ExtendedProperties): self._element = element ``` #### template ``` template: str | None ``` Name of the template attached to the document, e.g. "Normal.dotm". #### manager ``` manager: str | None ``` Name of the manager recorded for the document. #### company ``` company: str | None ``` Name of the company recorded for the document. #### application ``` application: str | None ``` Name of the application that produced the document. #### app_version ``` app_version: str | None ``` Version of the producing application, formatted "MM.mmmm". #### presentation_format ``` presentation_format: str | None ``` Intended presentation format. Rarely set by Word. #### hyperlink_base ``` hyperlink_base: str | None ``` Base path that relative hyperlinks in the document resolve against. #### pages ``` pages: int | None ``` Page count last recorded by the producing application. #### words ``` words: int | None ``` Word count last recorded by the producing application. #### characters ``` characters: int | None ``` Character count excluding spaces. #### characters_with_spaces ``` characters_with_spaces: int | None ``` Character count including spaces. #### lines ``` lines: int | None ``` Line count last recorded by the producing application. #### paragraphs ``` paragraphs: int | None ``` Paragraph count last recorded by the producing application. #### total_time ``` total_time: int | None ``` Cumulative editing time in minutes. #### doc_security ``` doc_security: int | None ``` Document security flags, e.g. 1 for password-protected, 2 for read-only. This records the intent of the producing application. It is not enforced by this library and provides no security guarantee. #### scale_crop ``` scale_crop: bool | None ``` `True` when the document thumbnail is scaled, `False` when cropped. #### links_up_to_date ``` links_up_to_date: bool | None ``` `True` when hyperlinks in the document are known to be current. #### shared_doc ``` shared_doc: bool | None ``` `True` when the document is flagged as shared between multiple authors. #### hyperlinks_changed ``` hyperlinks_changed: bool | None ``` `True` when hyperlinks changed and the producing application should update. ## oxml Temporary stand-in for main oxml module. This module came across with the PackageReader transplant. Probably much will get replaced with objects from the pptx.oxml.core and then this module will either get deleted or only hold the package related custom element classes. ### BaseOxmlElement Bases: `ElementBase` Base class for all custom element classes, to add standardized behavior to all classes in one place. #### xml ``` xml: str ``` Return XML string for this element, suitable for testing purposes. Pretty printed for readability and without an XML declaration at the top. ### CT_Default Bases: `BaseOxmlElement` `` element that appears in `[Content_Types].xml` part. Used to specify a default content type to be applied to any part with the specified extension. #### content_type ``` content_type ``` String held in the `ContentType` attribute of this `` element. #### extension ``` extension ``` String held in the `Extension` attribute of this `` element. #### new ``` new(ext: str, content_type: str) ``` Return a new `` element with attributes set to parameter values. Source code in `src/docx/opc/oxml.py` ``` @staticmethod def new(ext: str, content_type: str): """Return a new ```` element with attributes set to parameter values.""" xml = '' % nsmap["ct"] default = parse_xml(xml) default.set("Extension", ext) default.set("ContentType", content_type) return default ``` ### CT_Override Bases: `BaseOxmlElement` `` element, specifying the content type to be applied for a part with the specified partname. #### content_type ``` content_type ``` String held in the `ContentType` attribute of this `` element. #### partname ``` partname ``` String held in the `PartName` attribute of this `` element. #### new ``` new(partname, content_type) ``` Return a new `` element with attributes set to parameter values. Source code in `src/docx/opc/oxml.py` ``` @staticmethod def new(partname, content_type): """Return a new ```` element with attributes set to parameter values.""" xml = '' % nsmap["ct"] override = parse_xml(xml) override.set("PartName", partname) override.set("ContentType", content_type) return override ``` ### CT_Relationship Bases: `BaseOxmlElement` `` element, representing a single relationship from source to target part. #### rId ``` rId ``` String held in the `Id` attribute of this `` element. #### reltype ``` reltype ``` String held in the `Type` attribute of this `` element. #### target_ref ``` target_ref ``` String held in the `Target` attribute of this `` element. #### target_mode ``` target_mode ``` String held in the `TargetMode` attribute of this `` element, either `Internal` or `External`. Defaults to `Internal`. #### new ``` new( rId: str, reltype: str, target: str, target_mode: str = INTERNAL, ) ``` Return a new `` element. Source code in `src/docx/opc/oxml.py` ``` @staticmethod def new(rId: str, reltype: str, target: str, target_mode: str = RTM.INTERNAL): """Return a new ```` element.""" xml = '' % nsmap["pr"] relationship = parse_xml(xml) relationship.set("Id", rId) relationship.set("Type", reltype) relationship.set("Target", target) if target_mode == RTM.EXTERNAL: relationship.set("TargetMode", RTM.EXTERNAL) return relationship ``` ### CT_Relationships Bases: `BaseOxmlElement` `` element, the root element in a .rels file. #### Relationship_lst ``` Relationship_lst ``` Return a list containing all the `` child elements. #### xml ``` xml ``` Return XML string for this element, suitable for saving in a .rels stream, not pretty printed and with an XML declaration at the top. #### add_rel ``` add_rel( rId: str, reltype: str, target: str, is_external: bool = False, ) ``` Add a child `` element with attributes set according to parameter values. Source code in `src/docx/opc/oxml.py` ``` def add_rel(self, rId: str, reltype: str, target: str, is_external: bool = False): """Add a child ```` element with attributes set according to parameter values.""" target_mode = RTM.EXTERNAL if is_external else RTM.INTERNAL relationship = CT_Relationship.new(rId, reltype, target, target_mode) self.append(relationship) ``` #### new ``` new() -> CT_Relationships ``` Return a new `` element. Source code in `src/docx/opc/oxml.py` ``` @staticmethod def new() -> CT_Relationships: """Return a new ```` element.""" xml = '' % nsmap["pr"] return cast(CT_Relationships, parse_xml(xml)) ``` ### CT_Types Bases: `BaseOxmlElement` `` element, the container element for Default and Override elements in [Content_Types].xml. #### add_default ``` add_default(ext, content_type) ``` Add a child `` element with attributes set to parameter values. Source code in `src/docx/opc/oxml.py` ``` def add_default(self, ext, content_type): """Add a child ```` element with attributes set to parameter values.""" default = CT_Default.new(ext, content_type) self.append(default) ``` #### add_override ``` add_override(partname, content_type) ``` Add a child `` element with attributes set to parameter values. Source code in `src/docx/opc/oxml.py` ``` def add_override(self, partname, content_type): """Add a child ```` element with attributes set to parameter values.""" override = CT_Override.new(partname, content_type) self.append(override) ``` #### new ``` new() ``` Return a new `` element. Source code in `src/docx/opc/oxml.py` ``` @staticmethod def new(): """Return a new ```` element.""" xml = '' % nsmap["ct"] types = parse_xml(xml) return types ``` ### parse_xml ``` parse_xml(text: str) -> _Element ``` `etree.fromstring()` replacement that uses oxml parser. Source code in `src/docx/opc/oxml.py` ``` def parse_xml(text: str) -> etree._Element: """`etree.fromstring()` replacement that uses oxml parser.""" return etree.fromstring(text, oxml_parser) ``` ### qn ``` qn(tag: str) -> str ``` Stands for "qualified name", a utility function to turn a namespace prefixed tag name into a Clark-notation qualified tag name for lxml. For example, `qn('p:cSld')` returns `'{http://schemas.../main}cSld'`. Source code in `src/docx/opc/oxml.py` ``` def qn(tag: str) -> str: """Stands for "qualified name", a utility function to turn a namespace prefixed tag name into a Clark-notation qualified tag name for lxml. For example, ``qn('p:cSld')`` returns ``'{http://schemas.../main}cSld'``. """ prefix, tagroot = tag.split(":") uri = nsmap[prefix] return "{%s}%s" % (uri, tagroot) ``` ### serialize_part_xml ``` serialize_part_xml(part_elm: _Element) -> bytes ``` Serialize `part_elm` etree element to XML suitable for storage as an XML part. That is to say, no insignificant whitespace added for readability, and an appropriate XML declaration added with UTF-8 encoding specified. Source code in `src/docx/opc/oxml.py` ``` def serialize_part_xml(part_elm: etree._Element) -> bytes: """Serialize `part_elm` etree element to XML suitable for storage as an XML part. That is to say, no insignificant whitespace added for readability, and an appropriate XML declaration added with UTF-8 encoding specified. """ return etree.tostring(part_elm, encoding="UTF-8", standalone=True) ``` ### serialize_for_reading ``` serialize_for_reading(element: _Element) -> str ``` Serialize `element` to human-readable XML suitable for tests. No XML declaration. Source code in `src/docx/opc/oxml.py` ``` def serialize_for_reading(element: etree._Element) -> str: """Serialize `element` to human-readable XML suitable for tests. No XML declaration. """ return etree.tostring(element, encoding="unicode", pretty_print=True) ``` ## package Objects that implement reading and writing OPC packages. ### OpcPackage Main API class for |python-opc|. A new instance is constructed by calling the open class method with a path to a package file or file-like object containing one. #### core_properties ``` core_properties: CoreProperties ``` CoreProperties object providing read/write access to the Dublin Core properties for this document. #### custom_properties ``` custom_properties: CustomProperties ``` CustomProperties object providing read/write access to the arbitrary named values attached to this document. #### extended_properties ``` extended_properties: ExtendedProperties ``` ExtendedProperties object providing read/write access to the application-specific properties for this document. #### main_document_part ``` main_document_part ``` Return a reference to the main document part for this package. Examples include a document part for a WordprocessingML package, a presentation part for a PresentationML package, or a workbook part for a SpreadsheetML package. #### parts ``` parts: list[Part] ``` Return a list containing a reference to each of the parts in this package. #### after_unmarshal ``` after_unmarshal() ``` Entry point for any post-unmarshaling processing. May be overridden by subclasses without forwarding call to super. Source code in `src/docx/opc/package.py` ``` def after_unmarshal(self): """Entry point for any post-unmarshaling processing. May be overridden by subclasses without forwarding call to super. """ # don't place any code here, just catch call if not overridden by # subclass pass ``` #### iter_rels ``` iter_rels() -> Iterator[_Relationship] ``` Generate exactly one reference to each relationship in the package by performing a depth-first traversal of the rels graph. Source code in `src/docx/opc/package.py` ``` def iter_rels(self) -> Iterator[_Relationship]: """Generate exactly one reference to each relationship in the package by performing a depth-first traversal of the rels graph.""" def walk_rels( source: OpcPackage | Part, visited: list[Part] | None = None ) -> Iterator[_Relationship]: visited = [] if visited is None else visited for rel in source.rels.values(): yield rel if rel.is_external: continue part = rel.target_part if part in visited: continue visited.append(part) new_source = part for rel in walk_rels(new_source, visited): yield rel for rel in walk_rels(self): yield rel ``` #### iter_parts ``` iter_parts() -> Iterator[Part] ``` Generate exactly one reference to each of the parts in the package by performing a depth-first traversal of the rels graph. Source code in `src/docx/opc/package.py` ``` def iter_parts(self) -> Iterator[Part]: """Generate exactly one reference to each of the parts in the package by performing a depth-first traversal of the rels graph.""" def walk_parts(source, visited=[]): for rel in source.rels.values(): if rel.is_external: continue part = rel.target_part if part in visited: continue visited.append(part) yield part new_source = part for part in walk_parts(new_source, visited): yield part for part in walk_parts(self): yield part ``` #### load_rel ``` load_rel( reltype: str, target: Part | str, rId: str, is_external: bool = False, ) ``` Return newly added \_Relationship instance of `reltype` between this part and `target` with key `rId`. Target mode is set to `RTM.EXTERNAL` if `is_external` is `True`. Intended for use during load from a serialized package, where the rId is well known. Other methods exist for adding a new relationship to the package during processing. Source code in `src/docx/opc/package.py` ``` def load_rel(self, reltype: str, target: Part | str, rId: str, is_external: bool = False): """Return newly added |_Relationship| instance of `reltype` between this part and `target` with key `rId`. Target mode is set to ``RTM.EXTERNAL`` if `is_external` is |True|. Intended for use during load from a serialized package, where the rId is well known. Other methods exist for adding a new relationship to the package during processing. """ return self.rels.add_relationship(reltype, target, rId, is_external) ``` #### next_partname ``` next_partname(template: str) -> PackURI ``` Return a PackURI instance representing partname matching `template`. The returned part-name has the next available numeric suffix to distinguish it from other parts of its type. `template` is a printf (%)-style template string containing a single replacement item, a '%d' to be used to insert the integer portion of the partname. Example: "/word/header%d.xml" Source code in `src/docx/opc/package.py` ``` def next_partname(self, template: str) -> PackURI: """Return a |PackURI| instance representing partname matching `template`. The returned part-name has the next available numeric suffix to distinguish it from other parts of its type. `template` is a printf (%)-style template string containing a single replacement item, a '%d' to be used to insert the integer portion of the partname. Example: "/word/header%d.xml" """ partnames = {part.partname for part in self.iter_parts()} for n in range(1, len(partnames) + 2): candidate_partname = template % n if candidate_partname not in partnames: return PackURI(candidate_partname) ``` #### open ``` open(pkg_file: str | PathLike[str] | IO[bytes]) -> Self ``` Return an OpcPackage instance loaded with the contents of `pkg_file`. Source code in `src/docx/opc/package.py` ``` @classmethod def open(cls, pkg_file: str | os.PathLike[str] | IO[bytes]) -> Self: """Return an |OpcPackage| instance loaded with the contents of `pkg_file`.""" if isinstance(pkg_file, os.PathLike): pkg_file = os.fspath(pkg_file) pkg_reader = PackageReader.from_file(pkg_file) package = cls() Unmarshaller.unmarshal(pkg_reader, package, PartFactory) return package ``` #### part_related_by ``` part_related_by(reltype: str) -> Part ``` Return part to which this package has a relationship of `reltype`. Raises `KeyError` if no such relationship is found and `ValueError` if more than one such relationship is found. Source code in `src/docx/opc/package.py` ``` def part_related_by(self, reltype: str) -> Part: """Return part to which this package has a relationship of `reltype`. Raises |KeyError| if no such relationship is found and |ValueError| if more than one such relationship is found. """ return self.rels.part_with_reltype(reltype) ``` #### relate_to ``` relate_to(part: Part, reltype: str) ``` Return rId key of new or existing relationship to `part`. If a relationship of `reltype` to `part` already exists, its rId is returned. Otherwise a new relationship is created and that rId is returned. Source code in `src/docx/opc/package.py` ``` def relate_to(self, part: Part, reltype: str): """Return rId key of new or existing relationship to `part`. If a relationship of `reltype` to `part` already exists, its rId is returned. Otherwise a new relationship is created and that rId is returned. """ rel = self.rels.get_or_add(reltype, part) return rel.rId ``` #### rels ``` rels() ``` Return a reference to the Relationships instance holding the collection of relationships for this package. Source code in `src/docx/opc/package.py` ``` @lazyproperty def rels(self): """Return a reference to the |Relationships| instance holding the collection of relationships for this package.""" return Relationships(PACKAGE_URI.baseURI) ``` #### save ``` save(pkg_file: str | PathLike[str] | IO[bytes]) ``` Save this package to `pkg_file`. `pkg_file` can be either a file-path or a file-like object. Source code in `src/docx/opc/package.py` ``` def save(self, pkg_file: str | os.PathLike[str] | IO[bytes]): """Save this package to `pkg_file`. `pkg_file` can be either a file-path or a file-like object. """ if isinstance(pkg_file, os.PathLike): pkg_file = os.fspath(pkg_file) for part in self.parts: part.before_marshal() PackageWriter.write(pkg_file, self.rels, self.parts) ``` ### Unmarshaller Hosts static methods for unmarshalling a package from a PackageReader. #### unmarshal ``` unmarshal(pkg_reader, package, part_factory) ``` Construct graph of parts and realized relationships based on the contents of `pkg_reader`, delegating construction of each part to `part_factory`. Package relationships are added to `pkg`. Source code in `src/docx/opc/package.py` ``` @staticmethod def unmarshal(pkg_reader, package, part_factory): """Construct graph of parts and realized relationships based on the contents of `pkg_reader`, delegating construction of each part to `part_factory`. Package relationships are added to `pkg`. """ parts = Unmarshaller._unmarshal_parts(pkg_reader, package, part_factory) Unmarshaller._unmarshal_relationships(pkg_reader, package, parts) for part in parts.values(): part.after_unmarshal() package.after_unmarshal() ``` ## packuri Provides the PackURI value type. Also some useful known pack URI strings such as PACKAGE_URI. ### PackURI Bases: `str` Provides access to pack URI components such as the baseURI and the filename slice. Behaves as `str` otherwise. #### baseURI ``` baseURI: str ``` The base URI of this pack URI, the directory portion, roughly speaking. E.g. `'/ppt/slides'` for `'/ppt/slides/slide1.xml'`. For the package pseudo- partname '/', baseURI is '/'. #### ext ``` ext: str ``` The extension portion of this pack URI, e.g. `'xml'` for `'/word/document.xml'`. Note the period is not included. #### filename ``` filename ``` The "filename" portion of this pack URI, e.g. `'slide1.xml'` for `'/ppt/slides/slide1.xml'`. For the package pseudo-partname '/', filename is ''. #### idx ``` idx ``` Return partname index as integer for tuple partname or None for singleton partname, e.g. `21` for `'/ppt/slides/slide21.xml'` and `None` for `'/ppt/presentation.xml'`. #### membername ``` membername ``` The pack URI with the leading slash stripped off, the form used as the Zip file membername for the package item. Returns '' for the package pseudo-partname '/'. #### rels_uri ``` rels_uri ``` The pack URI of the .rels part corresponding to the current pack URI. Only produces sensible output if the pack URI is a partname or the package pseudo-partname '/'. #### from_rel_ref ``` from_rel_ref(baseURI: str, relative_ref: str) -> PackURI ``` The absolute PackURI formed by translating `relative_ref` onto `baseURI`. Source code in `src/docx/opc/packuri.py` ``` @staticmethod def from_rel_ref(baseURI: str, relative_ref: str) -> PackURI: """The absolute PackURI formed by translating `relative_ref` onto `baseURI`.""" joined_uri = posixpath.join(baseURI, relative_ref) abs_uri = posixpath.abspath(joined_uri) return PackURI(abs_uri) ``` #### relative_ref ``` relative_ref(baseURI: str) ``` Return string containing relative reference to package item from `baseURI`. E.g. PackURI('/ppt/slideLayouts/slideLayout1.xml') would return '../slideLayouts/slideLayout1.xml' for baseURI '/ppt/slides'. Source code in `src/docx/opc/packuri.py` ``` def relative_ref(self, baseURI: str): """Return string containing relative reference to package item from `baseURI`. E.g. PackURI('/ppt/slideLayouts/slideLayout1.xml') would return '../slideLayouts/slideLayout1.xml' for baseURI '/ppt/slides'. """ # workaround for posixpath bug in 2.6, doesn't generate correct # relative path when `start` (second) parameter is root ('/') return self[1:] if baseURI == "/" else posixpath.relpath(self, baseURI) ``` ## part Open Packaging Convention (OPC) objects related to package parts. ### Part ``` Part( partname: PackURI, content_type: str, blob: bytes | None = None, package: Package | None = None, ) ``` Base class for package parts. Provides common properties and methods, but intended to be subclassed in client code to implement specific part behaviors. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, blob: bytes | None = None, package: Package | None = None, ): super(Part, self).__init__() self._partname = partname self._content_type = content_type self._blob = blob self._package = package ``` #### blob ``` blob: bytes ``` Contents of this package part as a sequence of bytes. May be text or binary. Intended to be overridden by subclasses. Default behavior is to return load blob. #### content_type ``` content_type ``` Content type of this part. Writable, because a part can legitimately change what it is without its bytes changing — a Word document and a Word template hold identical markup and differ only here. #### package ``` package ``` OpcPackage instance this part belongs to. #### partname ``` partname ``` PackURI instance holding partname of this part, e.g. '/ppt/slides/slide1.xml'. #### related_parts ``` related_parts ``` Dictionary mapping related parts by rId, so child objects can resolve explicit relationships present in the part XML, e.g. sldIdLst to a specific `Slide` instance. #### after_unmarshal ``` after_unmarshal() ``` Entry point for post-unmarshaling processing, for example to parse the part XML. May be overridden by subclasses without forwarding call to super. Source code in `src/docx/opc/part.py` ``` def after_unmarshal(self): """Entry point for post-unmarshaling processing, for example to parse the part XML. May be overridden by subclasses without forwarding call to super. """ # don't place any code here, just catch call if not overridden by # subclass pass ``` #### before_marshal ``` before_marshal() ``` Entry point for pre-serialization processing, for example to finalize part naming if necessary. May be overridden by subclasses without forwarding call to super. Source code in `src/docx/opc/part.py` ``` def before_marshal(self): """Entry point for pre-serialization processing, for example to finalize part naming if necessary. May be overridden by subclasses without forwarding call to super. """ # don't place any code here, just catch call if not overridden by # subclass pass ``` #### drop_rel ``` drop_rel(rId: str) ``` Remove the relationship identified by `rId` if its reference count is less than 2. Relationships with a reference count of 0 are implicit relationships. Source code in `src/docx/opc/part.py` ``` def drop_rel(self, rId: str): """Remove the relationship identified by `rId` if its reference count is less than 2. Relationships with a reference count of 0 are implicit relationships. """ if self._rel_ref_count(rId) < 2: del self.rels[rId] ``` #### load_rel ``` load_rel( reltype: str, target: Part | str, rId: str, is_external: bool = False, ) ``` Return newly added \_Relationship instance of `reltype`. The new relationship relates the `target` part to this part with key `rId`. Target mode is set to `RTM.EXTERNAL` if `is_external` is `True`. Intended for use during load from a serialized package, where the rId is well-known. Other methods exist for adding a new relationship to a part when manipulating a part. Source code in `src/docx/opc/part.py` ``` def load_rel(self, reltype: str, target: Part | str, rId: str, is_external: bool = False): """Return newly added |_Relationship| instance of `reltype`. The new relationship relates the `target` part to this part with key `rId`. Target mode is set to ``RTM.EXTERNAL`` if `is_external` is |True|. Intended for use during load from a serialized package, where the rId is well-known. Other methods exist for adding a new relationship to a part when manipulating a part. """ return self.rels.add_relationship(reltype, target, rId, is_external) ``` #### part_related_by ``` part_related_by(reltype: str) -> Part ``` Return part to which this part has a relationship of `reltype`. Raises `KeyError` if no such relationship is found and `ValueError` if more than one such relationship is found. Provides ability to resolve implicitly related part, such as Slide -> SlideLayout. Source code in `src/docx/opc/part.py` ``` def part_related_by(self, reltype: str) -> Part: """Return part to which this part has a relationship of `reltype`. Raises |KeyError| if no such relationship is found and |ValueError| if more than one such relationship is found. Provides ability to resolve implicitly related part, such as Slide -> SlideLayout. """ return self.rels.part_with_reltype(reltype) ``` #### relate_to ``` relate_to( target: Part | str, reltype: str, is_external: bool = False, ) -> str ``` Return rId key of relationship of `reltype` to `target`. The returned `rId` is from an existing relationship if there is one, otherwise a new relationship is created. Source code in `src/docx/opc/part.py` ``` def relate_to(self, target: Part | str, reltype: str, is_external: bool = False) -> str: """Return rId key of relationship of `reltype` to `target`. The returned `rId` is from an existing relationship if there is one, otherwise a new relationship is created. """ if is_external: return self.rels.get_or_add_ext_rel(reltype, cast(str, target)) else: rel = self.rels.get_or_add(reltype, cast(Part, target)) return rel.rId ``` #### rels ``` rels() ``` Relationships instance holding the relationships for this part. Source code in `src/docx/opc/part.py` ``` @lazyproperty def rels(self): """|Relationships| instance holding the relationships for this part.""" # -- prevent breakage in `python-docx-template` by retaining legacy `._rels` attribute -- self._rels = Relationships(self._partname.baseURI) return self._rels ``` #### target_ref ``` target_ref(rId: str) -> str ``` Return URL contained in target ref of relationship identified by `rId`. Source code in `src/docx/opc/part.py` ``` def target_ref(self, rId: str) -> str: """Return URL contained in target ref of relationship identified by `rId`.""" rel = self.rels[rId] return rel.target_ref ``` ### PartFactory Provides a way for client code to specify a subclass of Part to be constructed by Unmarshaller based on its content type and/or a custom callable. Setting `PartFactory.part_class_selector` to a callable object will cause that object to be called with the parameters `content_type, reltype`, once for each part in the package. If the callable returns an object, it is used as the class for that part. If it returns `None`, part class selection falls back to the content type map defined in `PartFactory.part_type_for`. If no class is returned from either of these, the class contained in `PartFactory.default_part_type` is used to construct the part, which is by default `opc.package.Part`. ### XmlPart ``` XmlPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `Part` Base class for package parts containing an XML payload, which is most of them. Provides additional methods to the Part base class that take care of parsing and reserializing the XML payload and managing relationships to other parts. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### element ``` element ``` The root XML element of this XML part. #### part ``` part ``` Part of the parent protocol, "children" of the document will not know the part that contains them so must ask their parent object. That chain of delegation ends here for child objects. ## phys_pkg Provides a general interface to a `physical` OPC package, such as a zip file. ### PhysPkgReader Factory for physical package reader objects. ### PhysPkgWriter Factory for physical package writer objects. ### \_DirPkgReader ``` _DirPkgReader(path) ``` Bases: `PhysPkgReader` Implements PhysPkgReader interface for an OPC package extracted into a directory. `path` is the path to a directory containing an expanded package. Source code in `src/docx/opc/phys_pkg.py` ``` def __init__(self, path): """`path` is the path to a directory containing an expanded package.""" super(_DirPkgReader, self).__init__() self._path = os.path.abspath(path) ``` #### content_types_xml ``` content_types_xml ``` Return the `[Content_Types].xml` blob from the package. #### blob_for ``` blob_for(pack_uri) ``` Return contents of file corresponding to `pack_uri` in package directory. Source code in `src/docx/opc/phys_pkg.py` ``` def blob_for(self, pack_uri): """Return contents of file corresponding to `pack_uri` in package directory.""" path = os.path.join(self._path, pack_uri.membername) with open(path, "rb") as f: blob = f.read() return blob ``` #### contains ``` contains(pack_uri) ``` True if a member corresponding to `pack_uri` is present in the package. Source code in `src/docx/opc/phys_pkg.py` ``` def contains(self, pack_uri): """True if a member corresponding to `pack_uri` is present in the package.""" return os.path.isfile(os.path.join(self._path, pack_uri.membername)) ``` #### close ``` close() ``` Provides interface consistency with `ZipFileSystem`, but does nothing, a directory file system doesn't need closing. Source code in `src/docx/opc/phys_pkg.py` ``` def close(self): """Provides interface consistency with |ZipFileSystem|, but does nothing, a directory file system doesn't need closing.""" pass ``` #### rels_xml_for ``` rels_xml_for(source_uri) ``` Return rels item XML for source with `source_uri`, or None if the item has no rels item. Source code in `src/docx/opc/phys_pkg.py` ``` def rels_xml_for(self, source_uri): """Return rels item XML for source with `source_uri`, or None if the item has no rels item.""" try: rels_xml = self.blob_for(source_uri.rels_uri) except IOError: rels_xml = None return rels_xml ``` ### \_ZipPkgReader ``` _ZipPkgReader(pkg_file) ``` Bases: `PhysPkgReader` Implements PhysPkgReader interface for a zip file OPC package. Source code in `src/docx/opc/phys_pkg.py` ``` def __init__(self, pkg_file): super(_ZipPkgReader, self).__init__() # -- ZipFile() reads from the stream before it decides the file is not a zip, # -- so note where the caller left it and put it back on the failure path -- try: origin = pkg_file.tell() if hasattr(pkg_file, "tell") else None except (OSError, ValueError): origin = None try: self._zipf = ZipFile(pkg_file, "r") except BadZipFile as err: # -- a truncated, garbage or encrypted file lands here; BadZipFile leaks an # -- implementation detail of this layer, so translate it -- error = _not_a_package_error(pkg_file) if origin is not None: pkg_file.seek(origin) raise error from err ``` #### content_types_xml ``` content_types_xml ``` Return the `[Content_Types].xml` blob from the zip package. #### blob_for ``` blob_for(pack_uri) ``` Return blob corresponding to `pack_uri`. Raises `ValueError` if no matching member is present in zip archive. Source code in `src/docx/opc/phys_pkg.py` ``` def blob_for(self, pack_uri): """Return blob corresponding to `pack_uri`. Raises |ValueError| if no matching member is present in zip archive. """ return self._zipf.read(pack_uri.membername) ``` #### close ``` close() ``` Close the zip archive, releasing any resources it is using. Source code in `src/docx/opc/phys_pkg.py` ``` def close(self): """Close the zip archive, releasing any resources it is using.""" self._zipf.close() ``` #### contains ``` contains(pack_uri) ``` True if a member corresponding to `pack_uri` is present in the archive. Source code in `src/docx/opc/phys_pkg.py` ``` def contains(self, pack_uri): """True if a member corresponding to `pack_uri` is present in the archive.""" return pack_uri.membername in self._membernames ``` #### rels_xml_for ``` rels_xml_for(source_uri) ``` Return rels item XML for source with `source_uri` or None if no rels item is present. Source code in `src/docx/opc/phys_pkg.py` ``` def rels_xml_for(self, source_uri): """Return rels item XML for source with `source_uri` or None if no rels item is present.""" try: rels_xml = self.blob_for(source_uri.rels_uri) except KeyError: rels_xml = None return rels_xml ``` ### \_ZipPkgWriter ``` _ZipPkgWriter(pkg_file) ``` Bases: `PhysPkgWriter` Implements PhysPkgWriter interface for a zip file OPC package. Source code in `src/docx/opc/phys_pkg.py` ``` def __init__(self, pkg_file): super(_ZipPkgWriter, self).__init__() self._zipf = ZipFile(pkg_file, "w", compression=ZIP_DEFLATED) ``` #### close ``` close() ``` Close the zip archive, flushing any pending physical writes and releasing any resources it's using. Source code in `src/docx/opc/phys_pkg.py` ``` def close(self): """Close the zip archive, flushing any pending physical writes and releasing any resources it's using.""" self._zipf.close() ``` #### write ``` write(pack_uri, blob) ``` Write `blob` to this zip package with the membername corresponding to `pack_uri`. Source code in `src/docx/opc/phys_pkg.py` ``` def write(self, pack_uri, blob): """Write `blob` to this zip package with the membername corresponding to `pack_uri`.""" # -- a plain writestr() stamps each member with the current time, so saving # -- the same document twice produces different bytes. Use the zip epoch # -- instead; Word does not read these timestamps. -- zinfo = ZipInfo(filename=pack_uri.membername, date_time=_ZIP_EPOCH) zinfo.compress_type = ZIP_DEFLATED self._zipf.writestr(zinfo, blob) ``` ## pkgreader Low-level, read-only API to a serialized Open Packaging Convention (OPC) package. ### PackageReader ``` PackageReader(content_types, pkg_srels, sparts) ``` Provides access to the contents of a zip-format OPC package via its `serialized_parts` and `pkg_srels` attributes. Source code in `src/docx/opc/pkgreader.py` ``` def __init__(self, content_types, pkg_srels, sparts): super(PackageReader, self).__init__() self._pkg_srels = pkg_srels self._sparts = sparts ``` #### from_file ``` from_file(pkg_file) ``` Return a PackageReader instance loaded with contents of `pkg_file`. Source code in `src/docx/opc/pkgreader.py` ``` @staticmethod def from_file(pkg_file): """Return a |PackageReader| instance loaded with contents of `pkg_file`.""" phys_reader = PhysPkgReader(pkg_file) content_types = _ContentTypeMap.from_xml(phys_reader.content_types_xml) pkg_srels = PackageReader._srels_for(phys_reader, PACKAGE_URI) sparts = PackageReader._load_serialized_parts(phys_reader, pkg_srels, content_types) phys_reader.close() return PackageReader(content_types, pkg_srels, sparts) ``` #### iter_sparts ``` iter_sparts() ``` Generate a 4-tuple `(partname, content_type, reltype, blob)` for each of the serialized parts in the package. Source code in `src/docx/opc/pkgreader.py` ``` def iter_sparts(self): """Generate a 4-tuple `(partname, content_type, reltype, blob)` for each of the serialized parts in the package.""" for s in self._sparts: yield (s.partname, s.content_type, s.reltype, s.blob) ``` #### iter_srels ``` iter_srels() ``` Generate a 2-tuple `(source_uri, srel)` for each of the relationships in the package. Source code in `src/docx/opc/pkgreader.py` ``` def iter_srels(self): """Generate a 2-tuple `(source_uri, srel)` for each of the relationships in the package.""" for srel in self._pkg_srels: yield (PACKAGE_URI, srel) for spart in self._sparts: for srel in spart.srels: yield (spart.partname, srel) ``` ### \_ContentTypeMap ``` _ContentTypeMap() ``` Value type providing dictionary semantics for looking up content type by part name, e.g. `content_type = cti['/ppt/presentation.xml']`. Source code in `src/docx/opc/pkgreader.py` ``` def __init__(self): super(_ContentTypeMap, self).__init__() self._overrides = CaseInsensitiveDict() self._defaults = CaseInsensitiveDict() ``` #### from_xml ``` from_xml(content_types_xml) ``` Return a new \_ContentTypeMap instance populated with the contents of `content_types_xml`. Source code in `src/docx/opc/pkgreader.py` ``` @staticmethod def from_xml(content_types_xml): """Return a new |_ContentTypeMap| instance populated with the contents of `content_types_xml`.""" types_elm = parse_xml(content_types_xml) ct_map = _ContentTypeMap() for o in types_elm.overrides: ct_map._add_override(o.partname, o.content_type) for d in types_elm.defaults: ct_map._add_default(d.extension, d.content_type) return ct_map ``` ### \_SerializedPart ``` _SerializedPart( partname, content_type, reltype, blob, srels ) ``` Value object for an OPC package part. Provides access to the partname, content type, blob, and serialized relationships for the part. Source code in `src/docx/opc/pkgreader.py` ``` def __init__(self, partname, content_type, reltype, blob, srels): super(_SerializedPart, self).__init__() self._partname = partname self._content_type = content_type self._reltype = reltype self._blob = blob self._srels = srels ``` #### reltype ``` reltype ``` The referring relationship type of this part. ### \_SerializedRelationship ``` _SerializedRelationship(baseURI, rel_elm) ``` Value object representing a serialized relationship in an OPC package. Serialized, in this case, means any target part is referred to via its partname rather than a direct link to an in-memory Part object. Source code in `src/docx/opc/pkgreader.py` ``` def __init__(self, baseURI, rel_elm): super(_SerializedRelationship, self).__init__() self._baseURI = baseURI self._rId = rel_elm.rId self._reltype = rel_elm.reltype self._target_mode = rel_elm.target_mode self._target_ref = rel_elm.target_ref ``` #### is_external ``` is_external ``` True if target_mode is `RTM.EXTERNAL` #### reltype ``` reltype ``` Relationship type, like `RT.OFFICE_DOCUMENT` #### rId ``` rId ``` Relationship id, like 'rId9', corresponds to the `Id` attribute on the `CT_Relationship` element. #### target_mode ``` target_mode ``` String in `TargetMode` attribute of `CT_Relationship` element, one of `RTM.INTERNAL` or `RTM.EXTERNAL`. #### target_ref ``` target_ref ``` String in `Target` attribute of `CT_Relationship` element, a relative part reference for internal target mode or an arbitrary URI, e.g. an HTTP URL, for external target mode. #### target_partname ``` target_partname ``` PackURI instance containing partname targeted by this relationship. Raises `ValueError` on reference if target_mode is `'External'`. Use target_mode to check before referencing. ### \_SerializedRelationships ``` _SerializedRelationships() ``` Read-only sequence of \_SerializedRelationship instances corresponding to the relationships item XML passed to constructor. Source code in `src/docx/opc/pkgreader.py` ``` def __init__(self): super(_SerializedRelationships, self).__init__() self._srels = [] ``` #### drop ``` drop(srel) ``` Remove `srel` from this collection. Used to discard a relationship whose target part is missing from the package, so the collection never hands out an rId that cannot be resolved to a part. Source code in `src/docx/opc/pkgreader.py` ``` def drop(self, srel): """Remove `srel` from this collection. Used to discard a relationship whose target part is missing from the package, so the collection never hands out an rId that cannot be resolved to a part. """ self._srels.remove(srel) ``` #### load_from_xml ``` load_from_xml(baseURI, rels_item_xml) ``` Return \_SerializedRelationships instance loaded with the relationships contained in `rels_item_xml`. Returns an empty collection if `rels_item_xml` is `None`. Source code in `src/docx/opc/pkgreader.py` ``` @staticmethod def load_from_xml(baseURI, rels_item_xml): """Return |_SerializedRelationships| instance loaded with the relationships contained in `rels_item_xml`. Returns an empty collection if `rels_item_xml` is |None|. """ srels = _SerializedRelationships() if rels_item_xml is not None: rels_elm = parse_xml(rels_item_xml) for rel_elm in rels_elm.Relationship_lst: srels._srels.append(_SerializedRelationship(baseURI, rel_elm)) return srels ``` ## pkgwriter Provides low-level, write-only API to serialized (OPC) package. OPC stands for Open Packaging Convention. This is e, essentially an implementation of OpcPackage.save(). ### PackageWriter Writes a zip-format OPC package to `pkg_file`, where `pkg_file` can be either a path to a zip file (a string) or a file-like object. Its single API method, write, is static, so this class is not intended to be instantiated. #### write ``` write(pkg_file, pkg_rels, parts) ``` Write a physical package (.pptx file) to `pkg_file` containing `pkg_rels` and `parts` and a content types stream based on the content types of the parts. Source code in `src/docx/opc/pkgwriter.py` ``` @staticmethod def write(pkg_file, pkg_rels, parts): """Write a physical package (.pptx file) to `pkg_file` containing `pkg_rels` and `parts` and a content types stream based on the content types of the parts.""" phys_writer = PhysPkgWriter(pkg_file) # -- parts arrive in graph-traversal order, which varies between runs. Sort # -- them so the same document always serializes to the same bytes. -- sorted_parts = sorted(parts, key=lambda part: part.partname) PackageWriter._write_content_types_stream(phys_writer, sorted_parts) PackageWriter._write_pkg_rels(phys_writer, pkg_rels) PackageWriter._write_parts(phys_writer, sorted_parts) phys_writer.close() ``` ### \_ContentTypesItem ``` _ContentTypesItem() ``` Service class that composes a content types item ([Content_Types].xml) based on a list of parts. Not meant to be instantiated directly, its single interface method is xml_for(), e.g. `_ContentTypesItem.xml_for(parts)`. Source code in `src/docx/opc/pkgwriter.py` ``` def __init__(self): self._defaults = CaseInsensitiveDict() self._overrides = {} ``` #### blob ``` blob ``` Return XML form of this content types item, suitable for storage as `[Content_Types].xml` in an OPC package. #### from_parts ``` from_parts(parts) ``` Return content types XML mapping each part in `parts` to the appropriate content type and suitable for storage as `[Content_Types].xml` in an OPC package. Source code in `src/docx/opc/pkgwriter.py` ``` @classmethod def from_parts(cls, parts): """Return content types XML mapping each part in `parts` to the appropriate content type and suitable for storage as ``[Content_Types].xml`` in an OPC package.""" cti = cls() cti._defaults["rels"] = CT.OPC_RELATIONSHIPS cti._defaults["xml"] = CT.XML for part in parts: cti._add_content_type(part.partname, part.content_type) return cti ``` ## rel Relationship-related objects. ### \_RelatedParts Bases: `Dict[str, 'Part']` Mapping of rId to target part, reporting an unresolvable rId usefully. A reference to an rId that has no target part is what a document looks like after a relationship to a missing part has been dropped on load: the `w:drawing` (or other referring element) is still there, but its rId no longer resolves. Subclasses `dict` and raises `KeyError`, so existing handling is unaffected; only the message improves. ### Relationships ``` Relationships(baseURI: str) ``` Bases: `Dict[str, '_Relationship']` Collection object for \_Relationship instances, having list semantics. Source code in `src/docx/opc/rel.py` ``` def __init__(self, baseURI: str): super(Relationships, self).__init__() self._baseURI = baseURI self._target_parts_by_rId: dict[str, Any] = _RelatedParts() ``` #### related_parts ``` related_parts ``` Dict mapping rIds to target parts for all the internal relationships in the collection. #### xml ``` xml: str ``` Serialize this relationship collection into XML suitable for storage as a .rels file in an OPC package. #### add_relationship ``` add_relationship( reltype: str, target: Part | str, rId: str, is_external: bool = False, ) -> "_Relationship" ``` Return a newly added \_Relationship instance. Source code in `src/docx/opc/rel.py` ``` def add_relationship( self, reltype: str, target: Part | str, rId: str, is_external: bool = False ) -> "_Relationship": """Return a newly added |_Relationship| instance.""" rel = _Relationship(rId, reltype, target, self._baseURI, is_external) self[rId] = rel if not is_external: self._target_parts_by_rId[rId] = target return rel ``` #### get_or_add ``` get_or_add( reltype: str, target_part: Part ) -> _Relationship ``` Return relationship of `reltype` to `target_part`, newly added if not already present in collection. Source code in `src/docx/opc/rel.py` ``` def get_or_add(self, reltype: str, target_part: Part) -> _Relationship: """Return relationship of `reltype` to `target_part`, newly added if not already present in collection.""" rel = self._get_matching(reltype, target_part) if rel is None: rId = self._next_rId rel = self.add_relationship(reltype, target_part, rId) return rel ``` #### get_or_add_ext_rel ``` get_or_add_ext_rel(reltype: str, target_ref: str) -> str ``` Return rId of external relationship of `reltype` to `target_ref`, newly added if not already present in collection. Source code in `src/docx/opc/rel.py` ``` def get_or_add_ext_rel(self, reltype: str, target_ref: str) -> str: """Return rId of external relationship of `reltype` to `target_ref`, newly added if not already present in collection.""" rel = self._get_matching(reltype, target_ref, is_external=True) if rel is None: rId = self._next_rId rel = self.add_relationship(reltype, target_ref, rId, is_external=True) return rel.rId ``` #### part_with_reltype ``` part_with_reltype(reltype: str) -> Part ``` Return target part of rel with matching `reltype`, raising `KeyError` if not found and `ValueError` if more than one matching relationship is found. Source code in `src/docx/opc/rel.py` ``` def part_with_reltype(self, reltype: str) -> Part: """Return target part of rel with matching `reltype`, raising |KeyError| if not found and |ValueError| if more than one matching relationship is found.""" rel = self._get_rel_of_type(reltype) return rel.target_part ``` ### \_Relationship ``` _Relationship( rId: str, reltype: str, target: Part | str, baseURI: str, external: bool = False, ) ``` Value object for relationship to part. Source code in `src/docx/opc/rel.py` ``` def __init__( self, rId: str, reltype: str, target: Part | str, baseURI: str, external: bool = False ): super(_Relationship, self).__init__() self._rId = rId self._reltype = reltype self._target = target self._baseURI = baseURI self._is_external = bool(external) ``` ## shared Objects shared by opc modules. ### CaseInsensitiveDict Bases: `Dict[str, Any]` Mapping type that behaves like dict except that it matches without respect to the case of the key. E.g. cid['A'] == cid['a']. Note this is not general-purpose, just complete enough to satisfy opc package needs. It assumes str keys, and that it is created empty; keys passed in constructor are not accounted for ### cls_method_fn ``` cls_method_fn(cls: type, method_name: str) ``` Return method of `cls` having `method_name`. Source code in `src/docx/opc/shared.py` ``` def cls_method_fn(cls: type, method_name: str): """Return method of `cls` having `method_name`.""" return getattr(cls, method_name) ``` ## spec Provides mappings that embody aspects of the Open XML spec ISO/IEC 29500. ## parts ## coreprops Core properties part, corresponds to `/docProps/core.xml` part in package. ### CorePropertiesPart ``` CorePropertiesPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `XmlPart` Corresponds to part named `/docProps/core.xml`. The "core" is short for "Dublin Core" and contains document metadata relatively common across documents of all types, not just DOCX. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### core_properties ``` core_properties ``` A CoreProperties object providing read/write access to the core properties contained in this core properties part. #### default ``` default(package: OpcPackage) ``` Return a new CorePropertiesPart object initialized with default values for its base properties. Source code in `src/docx/opc/parts/coreprops.py` ``` @classmethod def default(cls, package: OpcPackage): """Return a new |CorePropertiesPart| object initialized with default values for its base properties.""" core_properties_part = cls._new(package) core_properties = core_properties_part.core_properties core_properties.title = "Word Document" core_properties.last_modified_by = "python-docx" core_properties.revision = 1 core_properties.modified = dt.datetime.now(dt.timezone.utc) return core_properties_part ``` ## custom_xml The custom XML data store parts, `customXml/itemN.xml` and its properties sidecar. A `.docx` can carry arbitrary XML in the custom XML data store, and bind document content to it through `w:dataBinding` inside a `w:sdt`. This is how most document-generation pipelines that are not string substitution actually work: the data lives in the store, the content controls display it, and Word keeps the two in sync. This is a different thing from the custom *document properties* in `docProps/custom.xml` (`docx.opc.customprops`), which are a flat list of named scalars. ### CustomXmlPropertiesPart ``` CustomXmlPropertiesPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `XmlPart` A `customXml/itemPropsN.xml` part, the sidecar of a data store item. Carries the GUID Word identifies the item by and the namespaces of the schemas it claims to conform to. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### item_id ``` item_id: str ``` The GUID Word identifies the store item by, e.g. `"{EF278816-...}"`. #### schema_refs ``` schema_refs: tuple[str, ...] ``` The schema namespaces the store item claims, in document order. #### new ``` new( package: OpcPackage, partname: PackURI, item_id: str, schema_refs: tuple[str, ...], ) -> CustomXmlPropertiesPart ``` A newly created properties part for a store item. Source code in `src/docx/opc/parts/custom_xml.py` ``` @classmethod def new( cls, package: OpcPackage, partname: PackURI, item_id: str, schema_refs: tuple[str, ...] ) -> CustomXmlPropertiesPart: """A newly created properties part for a store item.""" return cls( partname, CT.OFC_CUSTOM_XML_PROPERTIES, CT_DatastoreItem.new(item_id, schema_refs), package, ) ``` ### CustomXmlPart ``` CustomXmlPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `XmlPart` A `customXml/itemN.xml` part, one item of the custom XML data store. The content is arbitrary caller-supplied XML, so it has no element classes of its own; element is a plain parsed tree and xml its serialization. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### item_id ``` item_id: str | None ``` The GUID this item is identified by, or `None` when it has no props part. A store item without a properties part is out of spec but does occur; Word ignores such an item rather than repairing the document. #### schema_refs ``` schema_refs: tuple[str, ...] ``` The schema namespaces this item claims, empty when it has no props part. #### xml ``` xml: str ``` The item's XML as a string. Serialized with lxml directly rather than through `BaseOxmlElement.xml`: the content is arbitrary caller-supplied XML, so its root arrives as a plain `lxml.etree._Element` with none of this library's element classes behind it. #### new ``` new( package: OpcPackage, partname: PackURI, element: BaseOxmlElement, ) -> CustomXmlPart ``` A newly created store item part holding `element`. Source code in `src/docx/opc/parts/custom_xml.py` ``` @classmethod def new(cls, package: OpcPackage, partname: PackURI, element: BaseOxmlElement) -> CustomXmlPart: """A newly created store item part holding `element`.""" return cls(partname, CT.XML, element, package) ``` ## customprops Custom properties part, corresponds to `/docProps/custom.xml` part in package. ### CustomPropertiesPart ``` CustomPropertiesPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `XmlPart` Corresponds to part named `/docProps/custom.xml`. Holds the arbitrary named values an application attaches to a document, as opposed to the Dublin-Core properties in `/docProps/core.xml` and the application properties in `/docProps/app.xml`. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### custom_properties ``` custom_properties: CustomProperties ``` A CustomProperties object providing read/write access to the custom properties contained in this part. #### default ``` default(package: OpcPackage) -> CustomPropertiesPart ``` Return a new CustomPropertiesPart holding no properties. Most documents have no custom properties part at all, so one is created only when the collection is first reached. Source code in `src/docx/opc/parts/customprops.py` ``` @classmethod def default(cls, package: OpcPackage) -> CustomPropertiesPart: """Return a new |CustomPropertiesPart| holding no properties. Most documents have no custom properties part at all, so one is created only when the collection is first reached. """ return cls._new(package) ``` ## extendedprops Extended properties part, corresponds to `/docProps/app.xml` part in package. ### ExtendedPropertiesPart ``` ExtendedPropertiesPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `XmlPart` Corresponds to part named `/docProps/app.xml`. Holds the application-specific document properties, such as the word count and the producing application, as opposed to the Dublin-Core properties in `/docProps/core.xml`. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### extended_properties ``` extended_properties: ExtendedProperties ``` An ExtendedProperties object providing read/write access to the extended properties contained in this part. #### default ``` default(package: OpcPackage) -> ExtendedPropertiesPart ``` Return a new ExtendedPropertiesPart with default values. Source code in `src/docx/opc/parts/extendedprops.py` ``` @classmethod def default(cls, package: OpcPackage) -> ExtendedPropertiesPart: """Return a new |ExtendedPropertiesPart| with default values.""" extended_properties_part = cls._new(package) extended_properties = extended_properties_part.extended_properties extended_properties.application = "python-docx-ng" return extended_properties_part ``` ## oxml Initializes oxml sub-package. This including registering custom element classes corresponding to Open XML elements. ### OxmlElement ``` OxmlElement( nsptag_str: str, attrs: Dict[str, str] | None = None, nsdecls: Dict[str, str] | None = None, ) -> BaseOxmlElement | _Element ``` Return a 'loose' lxml element having the tag specified by `nsptag_str`. The tag in `nsptag_str` must contain the standard namespace prefix, e.g. `a:tbl`. The resulting element is an instance of the custom element class for this tag name if one is defined. A dictionary of attribute values may be provided as `attrs`; they are set if present. All namespaces defined in the dict `nsdecls` are declared in the element using the key as the prefix and the value as the namespace name. If `nsdecls` is not provided, a single namespace declaration is added based on the prefix on `nsptag_str`. Source code in `src/docx/oxml/parser.py` ``` def OxmlElement( nsptag_str: str, attrs: Dict[str, str] | None = None, nsdecls: Dict[str, str] | None = None, ) -> BaseOxmlElement | etree._Element: # pyright: ignore[reportPrivateUsage] """Return a 'loose' lxml element having the tag specified by `nsptag_str`. The tag in `nsptag_str` must contain the standard namespace prefix, e.g. `a:tbl`. The resulting element is an instance of the custom element class for this tag name if one is defined. A dictionary of attribute values may be provided as `attrs`; they are set if present. All namespaces defined in the dict `nsdecls` are declared in the element using the key as the prefix and the value as the namespace name. If `nsdecls` is not provided, a single namespace declaration is added based on the prefix on `nsptag_str`. """ nsptag = NamespacePrefixedTag(nsptag_str) if nsdecls is None: nsdecls = nsptag.nsmap return oxml_parser.makeelement(nsptag.clark_name, attrib=attrs, nsmap=nsdecls) ``` ### parse_xml ``` parse_xml(xml: str | bytes) -> 'BaseOxmlElement' ``` Root lxml element obtained by parsing XML character string `xml`. The custom parser is used, so custom element classes are produced for elements in `xml` that have them. Source code in `src/docx/oxml/parser.py` ``` def parse_xml(xml: str | bytes) -> "BaseOxmlElement": """Root lxml element obtained by parsing XML character string `xml`. The custom parser is used, so custom element classes are produced for elements in `xml` that have them. """ return cast("BaseOxmlElement", etree.fromstring(xml, oxml_parser)) ``` ## bookmark Custom element classes for bookmarks. A bookmark is a named range delimited by a `w:bookmarkStart` and a matching `w:bookmarkEnd`, paired by their `w:id`. The pair is not nested in anything: the two elements sit as siblings of whatever content they surround, which is what lets a bookmark span paragraphs, table cells and block containers without regard to the structure in between. It also means an unmatched start or end is structurally possible, and real documents contain them, so nothing here treats a missing counterpart as an error. ### CT_BookmarkStart Bases: `BaseOxmlElement` `w:bookmarkStart` element, the opening delimiter of a named range. #### bookmarkEnd ``` bookmarkEnd: CT_BookmarkEnd | None ``` The `w:bookmarkEnd` matching this start, or `None` when there is none. An unmatched start is common enough in documents produced by other tools that it is reported rather than raised on. ### CT_BookmarkEnd Bases: `BaseOxmlElement` `w:bookmarkEnd` element, the closing delimiter of a named range. ### is_hidden_bookmark_name ``` is_hidden_bookmark_name(name: str) -> bool ``` True for a bookmark Word maintains for itself rather than one a user made. Source code in `src/docx/oxml/bookmark.py` ``` def is_hidden_bookmark_name(name: str) -> bool: """True for a bookmark Word maintains for itself rather than one a user made.""" return name.startswith(_HIDDEN_BOOKMARK_PREFIXES) ``` ## comments Custom element classes related to document comments. ### CT_Comments Bases: `BaseOxmlElement` `w:comments` element, the root element for the comments part. Simply contains a collection of `w:comment` elements, each representing a single comment. Each contained comment is identified by a unique `w:id` attribute, used to reference the comment from the document text. The offset of the comment in this collection is arbitrary; it is essentially a *set* implemented as a list. #### add_comment ``` add_comment() -> CT_Comment ``` Return newly added `w:comment` child of this `w:comments`. The returned `w:comment` element is the minimum valid value, having a `w:id` value unique within the existing comments and the required `w:author` attribute present but set to the empty string. It's content is limited to a single run containing the necessary annotation reference but no text. Content is added by adding runs to this first paragraph and by adding additional paragraphs as needed. Source code in `src/docx/oxml/comments.py` ``` def add_comment(self) -> CT_Comment: """Return newly added `w:comment` child of this `w:comments`. The returned `w:comment` element is the minimum valid value, having a `w:id` value unique within the existing comments and the required `w:author` attribute present but set to the empty string. It's content is limited to a single run containing the necessary annotation reference but no text. Content is added by adding runs to this first paragraph and by adding additional paragraphs as needed. """ next_id = self._next_available_comment_id() comment = cast( CT_Comment, parse_xml( f'' f" " f" " f' ' f" " f" " f" " f' ' f" " f" " f" " f" " f"" ), ) self.append(comment) return comment ``` #### get_comment_by_id ``` get_comment_by_id(comment_id: int) -> CT_Comment | None ``` Return the `w:comment` element identified by `comment_id`, or `None` if not found. Source code in `src/docx/oxml/comments.py` ``` def get_comment_by_id(self, comment_id: int) -> CT_Comment | None: """Return the `w:comment` element identified by `comment_id`, or |None| if not found.""" comment_elms = self.xpath(f"(./w:comment[@w:id='{comment_id}'])[1]") return comment_elms[0] if comment_elms else None ``` ### CT_Comment Bases: `BaseOxmlElement` `w:comment` element, representing a single comment. A comment is a so-called "story" and can contain paragraphs and tables much like a table-cell. While probably most often used for a single sentence or phrase, a comment can contain rich content, including multiple rich-text paragraphs, hyperlinks, images, and tables. #### inner_content_elements ``` inner_content_elements: list[CT_P | CT_Tbl] ``` Generate all `w:p` and `w:tbl` elements in this comment. Content inside a `w:sdt` (content control) wrapper is included. ## coreprops Custom element classes for core properties-related XML elements. ### CT_CoreProperties Bases: `BaseOxmlElement` `` element, the root element of the Core Properties part. Stored as `/docProps/core.xml`. Implements many of the Dublin Core document metadata elements. String elements resolve to an empty string ("") if the element is not present in the XML. String elements are limited in length to 255 unicode characters. #### author_text ``` author_text: str ``` The text in the `dc:creator` child element. #### revision_number ``` revision_number: int ``` Integer value of revision property. #### new ``` new() -> CT_CoreProperties ``` Return a new `` element. Source code in `src/docx/oxml/coreprops.py` ``` @classmethod def new(cls) -> CT_CoreProperties: """Return a new `` element.""" xml = cls._coreProperties_tmpl coreProperties = cast(CT_CoreProperties, parse_xml(xml)) return coreProperties ``` ## customprops Custom element classes for the custom document properties part. Stored as `/docProps/custom.xml`, this is the third and last of the document-properties parts, after the Dublin-Core properties in `core.xml` and the application properties in `app.xml`. It holds arbitrary named values, which is what document-management systems, contract tooling and mail-merge pipelines use to carry their own keys, and what a `DOCPROPERTY` field in the document body refers to. ### CT_Property Bases: `BaseOxmlElement` `` element, one custom document property. The value is carried by a single child element from the `vt:` variant namespace, whose tag names the type. Writing the wrong variant for a value produces a file Word refuses to open, so the mapping is deliberate and narrow. #### value ``` value: str | int | float | bool | datetime | None ``` The Python value of this property, or `None` for an empty or null variant. A variant this library does not model — a vector, array or blob — reads as the raw text of the element, so nothing in a document is silently dropped, but such a property cannot be written back through a Python value. ### CT_CustomProperties Bases: `BaseOxmlElement` `` element, the root of the custom document properties part. #### new ``` new() -> CT_CustomProperties ``` Return a new, empty `` element. Source code in `src/docx/oxml/customprops.py` ``` @classmethod def new(cls) -> CT_CustomProperties: """Return a new, empty `` element.""" return cast(CT_CustomProperties, parse_xml(cls._Properties_tmpl)) ``` #### add_named_property ``` add_named_property(name: str, value: object) -> CT_Property ``` Return a new `` element for `name`, appended to this part. Source code in `src/docx/oxml/customprops.py` ``` def add_named_property(self, name: str, value: object) -> CT_Property: """Return a new `` element for `name`, appended to this part.""" # -- compute the pid before adding; the new element has none yet and the scan # -- would trip over it -- pid = self.next_pid() property = self.add_property() property.fmtid = FMTID_USER_DEFINED property.pid = pid property.name = name property.value = cast("str | int | float | bool | dt.datetime | None", value) return property ``` #### get_by_name ``` get_by_name(name: str) -> CT_Property | None ``` The `` element named `name`, or `None` if there is none. Source code in `src/docx/oxml/customprops.py` ``` def get_by_name(self, name: str) -> CT_Property | None: """The `` element named `name`, or |None| if there is none.""" for property in self.property_lst: if property.name == name: return property return None ``` #### next_pid ``` next_pid() -> int ``` The property id to give the next property added. One greater than the highest in use. Gaps left by deleted properties are not reused and existing properties are never renumbered: a `pid` need only be unique, and rewriting them would churn the file for no gain. Source code in `src/docx/oxml/customprops.py` ``` def next_pid(self) -> int: """The property id to give the next property added. One greater than the highest in use. Gaps left by deleted properties are not reused and existing properties are never renumbered: a `pid` need only be unique, and rewriting them would churn the file for no gain. """ pids = [property.pid for property in self.property_lst] return max(pids) + 1 if pids else _FIRST_PID ``` ## customxml Custom element classes for the custom XML data store's properties part. `customXml/itemProps1.xml` is the sidecar of `customXml/item1.xml`: it carries the GUID Word identifies the store item by, and the namespaces of the schemas the item claims to conform to. The item itself is arbitrary caller-supplied XML with no element classes of its own. ### CT_DatastoreSchemaRef Bases: `BaseOxmlElement` `ds:schemaRef` element, naming one schema namespace the store item uses. ### CT_DatastoreSchemaRefs Bases: `BaseOxmlElement` `ds:schemaRefs` element, the set of schemas a store item claims to conform to. ### CT_DatastoreItem Bases: `BaseOxmlElement` `ds:datastoreItem`, the root element of a `customXml/itemPropsN.xml` part. #### schema_ref_uris ``` schema_ref_uris: tuple[str, ...] ``` The schema namespaces this item claims, in document order. #### new ``` new( item_id: str, schema_refs: tuple[str, ...] = () ) -> CT_DatastoreItem ``` A newly created `ds:datastoreItem` for `item_id` naming `schema_refs`. Source code in `src/docx/oxml/customxml.py` ``` @classmethod def new(cls, item_id: str, schema_refs: tuple[str, ...] = ()) -> CT_DatastoreItem: """A newly created `ds:datastoreItem` for `item_id` naming `schema_refs`.""" datastoreItem = parse_xml( '' % (nsdecls("ds"), item_id) ) if schema_refs: schemaRefs = datastoreItem.get_or_add_schemaRefs() for uri in schema_refs: schemaRefs.add_schemaRef().uri = uri return datastoreItem ``` ### is_datastore_item ``` is_datastore_item(element: object) -> bool ``` `True` when `element` is a `ds:datastoreItem` root element. Source code in `src/docx/oxml/customxml.py` ``` def is_datastore_item(element: object) -> bool: """|True| when `element` is a `ds:datastoreItem` root element.""" return getattr(element, "tag", None) == qn("ds:datastoreItem") ``` ## deletion Shared bookkeeping for removing content from a document. Removing an element is one line of lxml. Removing it *safely* is not: the content may have carried the only reference to a hyperlink relationship, or one half of a comment range or bookmark, and dropping it without tidying those leaves a document Word either repairs on open or refuses outright. ### delete_element ``` delete_element( element: BaseOxmlElement, part: Part | None ) -> None ``` Remove `element` from its tree, tidying what it referred to. Any relationship referenced only from inside `element` is dropped, and the surviving half of any range marker whose partner is inside `element` is removed too, so no dangling `w:bookmarkStart` or `w:commentRangeStart` is left behind. `part` may be `None` for an element not attached to a package, in which case the relationship cleanup is skipped. Source code in `src/docx/oxml/deletion.py` ``` def delete_element(element: BaseOxmlElement, part: Part | None) -> None: """Remove `element` from its tree, tidying what it referred to. Any relationship referenced only from inside `element` is dropped, and the surviving half of any range marker whose partner is inside `element` is removed too, so no dangling `w:bookmarkStart` or `w:commentRangeStart` is left behind. `part` may be |None| for an element not attached to a package, in which case the relationship cleanup is skipped. """ _remove_orphaned_range_markers(element) rIds = _rIds_within(element) if part is not None else [] parent = element.getparent() if parent is not None: parent.remove(element) if part is not None: for rId in rIds: # -- the element is gone, so a remaining reference is a real one. Note this # -- is not `Part.drop_rel()`, whose threshold assumes the caller's own # -- reference is still in the XML. -- if rId in part.rels and _rel_ref_count(part, rId) == 0: del part.rels[rId] ``` ## document Custom element classes that correspond to the document part, e.g. . ### CT_AltChunk Bases: `BaseOxmlElement` `w:altChunk` element, an embedded document Word imports when it opens the file. The `r:id` attribute is optional in the schema; an alt-chunk without one names no content and Word ignores it. ### CT_Document Bases: `BaseOxmlElement` `` element, the root element of a document.xml file. #### sectPr_lst ``` sectPr_lst: List[CT_SectPr] ``` All `w:sectPr` elements directly accessible from document element. Note this does not include a `sectPr` child in a paragraphs wrapped in revision marks or other intervening layer, perhaps `w:sdt` or customXml elements. `w:sectPr` elements appear in document order. The last one is always `w:body/w:sectPr`, all preceding are `w:p/w:pPr/w:sectPr`. ### CT_Body Bases: `BaseOxmlElement` `w:body`, the container element for the main document story in `document.xml`. #### inner_content_elements ``` inner_content_elements: List[CT_P | CT_Tbl] ``` Generate all `w:p` and `w:tbl` elements in this document-body. Elements appear in document order. Content inside a `w:sdt` (content control) wrapper is included; content shaded by nesting in a `w:ins` or other wrapper is not. #### add_section_break ``` add_section_break() -> CT_SectPr ``` Return `w:sectPr` element for new section added at end of document. The last `w:sectPr` becomes the second-to-last, with the new `w:sectPr` being an exact clone of the previous one, except that all header and footer references are removed (and are therefore now "inherited" from the prior section). A copy of the previously-last `w:sectPr` will now appear in a new `w:p` at the end of the document. The returned `w:sectPr` is the sentinel `w:sectPr` for the document (and as implemented, `is` the prior sentinel `w:sectPr` with headers and footers removed). Source code in `src/docx/oxml/document.py` ``` def add_section_break(self) -> CT_SectPr: """Return `w:sectPr` element for new section added at end of document. The last `w:sectPr` becomes the second-to-last, with the new `w:sectPr` being an exact clone of the previous one, except that all header and footer references are removed (and are therefore now "inherited" from the prior section). A copy of the previously-last `w:sectPr` will now appear in a new `w:p` at the end of the document. The returned `w:sectPr` is the sentinel `w:sectPr` for the document (and as implemented, `is` the prior sentinel `w:sectPr` with headers and footers removed). """ # ---get the sectPr at file-end, which controls last section (sections[-1])--- sentinel_sectPr = self.get_or_add_sectPr() # ---add exact copy to new `w:p` element; that is now second-to last section--- self.add_p().set_sectPr(sentinel_sectPr.clone()) # ---remove any header or footer references from "new" last section--- for hdrftr_ref in sentinel_sectPr.xpath("w:headerReference|w:footerReference"): sentinel_sectPr.remove(hdrftr_ref) # ---the sentinel `w:sectPr` now controls the new last section--- return sentinel_sectPr ``` #### clear_content ``` clear_content() ``` Remove all content child elements from this element. Leave the element if it is present. Source code in `src/docx/oxml/document.py` ``` def clear_content(self): """Remove all content child elements from this element. Leave the element if it is present. """ for content_elm in self.xpath("./*[not(self::w:sectPr)]"): self.remove(content_elm) ``` ## drawing Custom element-classes for DrawingML-related elements like ``. For legacy reasons, many DrawingML-related elements are in `docx.oxml.shape`. Expect those to move over here as we have reason to touch them. ### CT_Drawing Bases: `BaseOxmlElement` `` element, containing a DrawingML object like a picture or chart. ## exceptions Exceptions for oxml sub-package. ### XmlchemyError Bases: `Exception` Generic error class. ### InvalidXmlError Bases: `XmlchemyError` Raised when invalid XML is encountered, such as on attempt to access a missing required child element. ## extendedprops Custom element classes for extended (application) properties XML elements. ### CT_ExtendedProperties Bases: `BaseOxmlElement` `` element, the root element of the Extended Properties part. Stored as `/docProps/app.xml`. These are the properties Word shows under File > Info > Properties that are not Dublin Core, such as the word count and the application that wrote the file. `CT_Properties` is declared `xsd:all` rather than `xsd:sequence`, so child order carries no meaning and every element is optional. Word writes them in its own order, which is not the order they appear in the schema. New elements are therefore simply appended, and no `successors` bookkeeping is needed. #### new ``` new() -> CT_ExtendedProperties ``` Return a new `` element. Source code in `src/docx/oxml/extendedprops.py` ``` @classmethod def new(cls) -> CT_ExtendedProperties: """Return a new `` element.""" return cast(CT_ExtendedProperties, parse_xml(cls._Properties_tmpl)) ``` #### text_of ``` text_of(property_name: str) -> str | None ``` Text of the child element named `property_name`. `None` when the element is absent, distinguishing "not recorded" from a value that is genuinely the empty string. Source code in `src/docx/oxml/extendedprops.py` ``` def text_of(self, property_name: str) -> str | None: """Text of the child element named `property_name`. |None| when the element is absent, distinguishing "not recorded" from a value that is genuinely the empty string. """ element = getattr(self, property_name) if element is None: return None return element.text or "" ``` #### set_text_of ``` set_text_of(property_name: str, value: str | None) -> None ``` Set the text of child element `property_name`, adding it if necessary. Assigning `None` removes the element. Source code in `src/docx/oxml/extendedprops.py` ``` def set_text_of(self, property_name: str, value: str | None) -> None: """Set the text of child element `property_name`, adding it if necessary. Assigning |None| removes the element. """ if value is None: getattr(self, "_remove_%s" % property_name)() return element = getattr(self, "get_or_add_%s" % property_name)() element.text = value ``` #### int_of ``` int_of(property_name: str) -> int | None ``` Integer value of child element `property_name`, or `None` if absent. Returns `None` rather than raising when the recorded text is not a valid integer; these values are written by other applications and a malformed count should not make the whole document unreadable. Source code in `src/docx/oxml/extendedprops.py` ``` def int_of(self, property_name: str) -> int | None: """Integer value of child element `property_name`, or |None| if absent. Returns |None| rather than raising when the recorded text is not a valid integer; these values are written by other applications and a malformed count should not make the whole document unreadable. """ text = self.text_of(property_name) if not text: return None try: return int(text) except ValueError: return None ``` #### bool_of ``` bool_of(property_name: str) -> bool | None ``` Boolean value of child element `property_name`, or `None` if absent. Source code in `src/docx/oxml/extendedprops.py` ``` def bool_of(self, property_name: str) -> bool | None: """Boolean value of child element `property_name`, or |None| if absent.""" text = self.text_of(property_name) if not text: return None return text.strip().lower() in ("true", "1") ``` ## footnotes Custom element classes related to document footnotes and endnotes. Footnotes and endnotes are the same feature in two places. The schema gives both `w:footnote` and `w:endnote` the type `CT_FtnEdn`, both parts wrap a sequence of those, and `w:footnoteReference` and `w:endnoteReference` are both `CT_FtnEdnRef`. Only the tag names and the reference-mark element differ, so this module models the pair once and subclasses for the two spellings. ### \_CT_FtnEdnCollection Bases: `BaseOxmlElement` Common behavior of the `w:footnotes` and `w:endnotes` root elements. They differ only in the tag of their children, the reference-mark element that goes in a new note, and the styles Word applies to one; `_tag`, `_ref_tag`, `_para_style` and `_char_style` name those. #### note_lst ``` note_lst: List[CT_FtnEdn] ``` The `w:footnote` or `w:endnote` children, in document order. #### add_note ``` add_note() -> CT_FtnEdn ``` Return a newly added note child of this element. The returned element is the minimum valid value: a `w:id` unique among the existing notes and a single paragraph holding the reference mark that Word renders as the note number. Content is added by adding runs to that paragraph and by adding further paragraphs. Source code in `src/docx/oxml/footnotes.py` ``` def add_note(self) -> CT_FtnEdn: """Return a newly added note child of this element. The returned element is the minimum valid value: a `w:id` unique among the existing notes and a single paragraph holding the reference mark that Word renders as the note number. Content is added by adding runs to that paragraph and by adding further paragraphs. """ next_id = self._next_available_note_id() note = cast( "CT_FtnEdn", parse_xml( f'<{self._tag} {nsdecls("w")} w:id="{next_id}">' f" " f" " f' ' f" " f" " f" " f' ' f" " f" <{self._ref_tag}/>" f" " f" " f"" ), ) self.append(note) return note ``` #### get_note_by_id ``` get_note_by_id(note_id: int) -> CT_FtnEdn | None ``` The note element identified by `note_id`, or `None` if not found. Source code in `src/docx/oxml/footnotes.py` ``` def get_note_by_id(self, note_id: int) -> CT_FtnEdn | None: """The note element identified by `note_id`, or |None| if not found.""" note_elms = self.xpath(f"(./{self._tag}[@w:id='{note_id}'])[1]") return note_elms[0] if note_elms else None ``` #### iter_authored_notes ``` iter_authored_notes() -> List[CT_FtnEdn] ``` The note elements an author wrote, in document order. The structural separator notes Word keeps at ids -1 and 0 are left out; see `STRUCTURAL_FOOTNOTE_TYPES`. Source code in `src/docx/oxml/footnotes.py` ``` def iter_authored_notes(self) -> List[CT_FtnEdn]: """The note elements an author wrote, in document order. The structural separator notes Word keeps at ids -1 and 0 are left out; see `STRUCTURAL_FOOTNOTE_TYPES`. """ return [f for f in self.note_lst if not f.is_structural] ``` ### CT_Footnotes Bases: `_CT_FtnEdnCollection` `w:footnotes` element, the root element for the footnotes part. Contains a `w:footnote` element for each footnote in the document, plus the structural separator footnotes Word keeps at ids -1 and 0. ### CT_Endnotes Bases: `_CT_FtnEdnCollection` `w:endnotes` element, the root element for the endnotes part. The endnote half of `CT_Footnotes`; the two are the same complex type in the schema. ### CT_FtnEdn Bases: `BaseOxmlElement` `w:footnote` or `w:endnote` element, a single note. A footnote is a "story" and can contain paragraphs and tables much like a table cell, so its content can be rich: multiple paragraphs, hyperlinks, images and tables. #### inner_content_elements ``` inner_content_elements: List[CT_P | CT_Tbl] ``` All `w:p` and `w:tbl` elements in this footnote, in document order. Content inside a `w:sdt` (content control) wrapper is included. #### is_structural ``` is_structural: bool ``` `True` when this is one of Word's separator footnotes rather than an author's. These are the footnotes Word keeps at ids -1 and 0 to draw the rule above the footnote area and its continuation. ### CT_FtnEdnRef Bases: `BaseOxmlElement` `w:footnoteReference` or `w:endnoteReference`, the mark that cites a note. ## math Custom element classes for OMML, the Office Math Markup Language. Word stores an equation as `m:oMath`, in the math namespace rather than the wordprocessing one. It sits among the runs of a paragraph, or inside an `m:oMathPara` wrapper when the equation is displayed on a line of its own. Only enough of OMML is modelled to find an equation and read its text and XML. A full object model of the notation — fractions, radicals, matrices, delimiters, accents, some hundreds of elements in `ref/xsd/shared-math.xsd` — is a substantially larger piece of work, and one that would not pay for itself until there were something to render it with. ### CT_OMath Bases: `BaseOxmlElement` `m:oMath` element, one equation. Equation text lives in `m:t` inside `m:r`, not in `w:t` inside `w:r`, so nothing that walks the wordprocessing run content finds it. #### text ``` text: str ``` The concatenated text of this equation's `m:t` descendants. This is the equation's *characters* with none of its structure: a fraction reads as its numerator followed by its denominator, a superscript as the base followed by the exponent. It is what a plain-text extraction can offer, and is not a rendering of the equation. ### CT_OMathPara Bases: `BaseOxmlElement` `m:oMathPara` element, a group of equations displayed on their own line. Holds one or more `m:oMath` children. Word writes this rather than a bare `m:oMath` when the equation is "display" rather than "inline". ## ns Namespace-related objects. ### NamespacePrefixedTag ``` NamespacePrefixedTag(nstag: str) ``` Bases: `str` Value object that knows the semantics of an XML tag having a namespace prefix. Source code in `src/docx/oxml/ns.py` ``` def __init__(self, nstag: str): self._pfx, self._local_part = nstag.split(":") self._ns_uri = nsmap[self._pfx] ``` #### local_part ``` local_part: str ``` The local part of this tag. E.g. "foobar" is returned for tag "f:foobar". #### nsmap ``` nsmap: Dict[str, str] ``` Single-member dict mapping prefix of this tag to it's namespace name. Example: `{"f": "http://foo/bar"}`. This is handy for passing to xpath calls and other uses. #### nspfx ``` nspfx: str ``` The namespace-prefix for this tag. For example, "f" is returned for tag "f:foobar". #### nsuri ``` nsuri: str ``` The namespace URI for this tag. For example, "http://foo/bar" would be returned for tag "f:foobar" if the "f" prefix maps to "http://foo/bar" in nsmap. ### is_strict_ooxml_tag ``` is_strict_ooxml_tag(tag: object) -> bool ``` True if `tag` is a Clark-notation tag name in an ISO Strict namespace. False for anything that is not a string tag, which covers lxml's comment and processing-instruction elements. Source code in `src/docx/oxml/ns.py` ``` def is_strict_ooxml_tag(tag: object) -> bool: """True if `tag` is a Clark-notation tag name in an ISO Strict namespace. False for anything that is not a string tag, which covers lxml's comment and processing-instruction elements. """ return isinstance(tag, str) and tag.startswith("{" + STRICT_NS_PREFIX) ``` ### nsdecls ``` nsdecls(*prefixes: str) -> str ``` Namespace declaration including each namespace-prefix in `prefixes`. Handy for adding required namespace declarations to a tree root element. Source code in `src/docx/oxml/ns.py` ``` def nsdecls(*prefixes: str) -> str: """Namespace declaration including each namespace-prefix in `prefixes`. Handy for adding required namespace declarations to a tree root element. """ return " ".join(['xmlns:%s="%s"' % (pfx, nsmap[pfx]) for pfx in prefixes]) ``` ### nspfxmap ``` nspfxmap(*nspfxs: str) -> Dict[str, str] ``` Subset namespace-prefix mappings specified by *nspfxs*. Any number of namespace prefixes can be supplied, e.g. namespaces("a", "r", "p"). Source code in `src/docx/oxml/ns.py` ``` def nspfxmap(*nspfxs: str) -> Dict[str, str]: """Subset namespace-prefix mappings specified by *nspfxs*. Any number of namespace prefixes can be supplied, e.g. namespaces("a", "r", "p"). """ return {pfx: nsmap[pfx] for pfx in nspfxs} ``` ### qn ``` qn(tag: str) -> str ``` Stands for "qualified name". This utility function converts a familiar namespace-prefixed tag name like "w:p" into a Clark-notation qualified tag name for lxml. For example, `qn("w:p")` returns "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p". Memoized; `nsmap` is a fixed table and this is called for every element access. Source code in `src/docx/oxml/ns.py` ``` @functools.lru_cache(maxsize=None) def qn(tag: str) -> str: """Stands for "qualified name". This utility function converts a familiar namespace-prefixed tag name like "w:p" into a Clark-notation qualified tag name for lxml. For example, `qn("w:p")` returns "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p". Memoized; `nsmap` is a fixed table and this is called for every element access. """ prefix, tagroot = tag.split(":") uri = nsmap[prefix] return "{%s}%s" % (uri, tagroot) ``` ## numbering Custom element classes related to the numbering part. The numbering model has two levels of indirection, and getting them the wrong way round is what most reimplementations of it do: - A paragraph's `w:numPr/w:numId` names a `w:num`, a *concrete* list instance. - The `w:num` names a `w:abstractNum` through `w:abstractNumId`. The abstract definition holds the formatting of each of the nine levels: the start value, the number format and the level text. - The `w:num` may carry `w:lvlOverride` children that override parts of the abstract definition for this instance, `w:startOverride` in particular. Two `w:num` elements pointing at the same `w:abstractNum` are two independent sequences that happen to look alike. That is precisely how Word restarts a list: it does not reset a counter, it creates a second `w:num` with a `w:startOverride`. Leaf values here are read from their `w:val` attribute rather than through registered element classes, because the tag names are reused elsewhere in the schema with other types — `w:start` is a table-cell border, and lxml resolves an element class by tag name alone, so registering it would silently change the type of every table border in the document. ### CT_Lvl Bases: `BaseOxmlElement` `w:lvl` element, the definition of one of the nine levels of a list. #### start ``` start: int | None ``` The number this level counts from, or `None` when it does not say. Word treats an unspecified start as 1. #### num_fmt ``` num_fmt: WD_NUMBER_FORMAT | None ``` Member of `WdNumberFormat` this level renders its counter as. `None` when the level does not say, or when it names a format outside the enumeration — Word accepts vendor extensions here and a document using one should still be readable. #### lvl_restart ``` lvl_restart: int | None ``` The one-based level whose increment restarts this one, or `None`. `None` means the default: this level restarts whenever any higher level increments. A value of 0 means it never restarts. #### lvl_text ``` lvl_text: str | None ``` The pattern this level displays, e.g. `"%1."` or `"%1.%2"`, or `None`. A `%n` placeholder is replaced by the counter of the one-based level `n`. For a bullet level the text is the bullet character itself and holds no placeholder. #### is_lgl ``` is_lgl: bool ``` True when this level renders all its placeholders as decimal. The "legal numbering" option, which turns "1.a.i" into "1.1.1" without changing the underlying formats. #### p_style ``` p_style: str | None ``` The style id this level is linked to, or `None`. A paragraph with this style takes this numbering level even without its own `w:numPr`, which is how the built-in "List Number" styles work. #### suffix ``` suffix: str | None ``` What separates the number from the text: `"tab"`, `"space"` or `"nothing"`. `None` when the level does not say, which Word treats as `"tab"`. This is the gap between the bullet or number and the paragraph text, and setting it to `"space"` is the usual way to tighten up a compact list. #### jc ``` jc: str | None ``` Alignment of the number within its indent: `"left"`, `"center"`, `"right"`. `None` when the level does not say, which Word treats as left. #### pPr ``` pPr: BaseOxmlElement | None ``` The `w:pPr` of this level, or `None` when it has none. This is where a level's indent lives, as an ordinary `w:ind`. #### get_or_add_pPr ``` get_or_add_pPr() -> BaseOxmlElement ``` The `w:pPr` of this level, added in schema order if not already there. Source code in `src/docx/oxml/numbering.py` ``` def get_or_add_pPr(self) -> BaseOxmlElement: """The `w:pPr` of this level, added in schema order if not already there.""" pPr = self.pPr if pPr is None: pPr = OxmlElement("w:pPr") _insert_in_order(self, pPr, "w:pPr", self._tag_seq) return pPr ``` #### new ``` new(ilvl: int) -> CT_Lvl ``` A new empty `w:lvl` for level `ilvl`. Source code in `src/docx/oxml/numbering.py` ``` @classmethod def new(cls, ilvl: int) -> CT_Lvl: """A new empty `w:lvl` for level `ilvl`.""" lvl = OxmlElement("w:lvl") lvl.set(qn("w:ilvl"), str(ilvl)) return lvl # pyright: ignore[reportReturnType] ``` ### CT_AbstractNum Bases: `BaseOxmlElement` `w:abstractNum` element, the shared definition behind one or more `w:num`. #### multi_level_type ``` multi_level_type: str | None ``` `"singleLevel"`, `"multilevel"` or `"hybridMultilevel"`, or `None`. #### num_style_link ``` num_style_link: str | None ``` The style id this definition defers to, or `None`. An abstract definition carrying this holds no levels of its own; the numbering actually comes from the definition the named style points at. Word writes this for a list style shared between several lists. #### style_link ``` style_link: str | None ``` The style id this definition is the numbering for, or `None`. #### new ``` new(abstract_num_id: int) -> CT_AbstractNum ``` A new empty `w:abstractNum` with `abstract_num_id`. `w:nsid` and `w:tmpl` are deliberately not written. They are what Word uses to recognise a definition as one of its own list-gallery entries; inventing values for them would claim a provenance this definition does not have, and Word opens a document without them perfectly well. Source code in `src/docx/oxml/numbering.py` ``` @classmethod def new(cls, abstract_num_id: int) -> CT_AbstractNum: """A new empty `w:abstractNum` with `abstract_num_id`. `w:nsid` and `w:tmpl` are deliberately not written. They are what Word uses to recognise a definition as one of its own list-gallery entries; inventing values for them would claim a provenance this definition does not have, and Word opens a document without them perfectly well. """ abstractNum = OxmlElement("w:abstractNum") abstractNum.set(qn("w:abstractNumId"), str(abstract_num_id)) return abstractNum # pyright: ignore[reportReturnType] ``` #### add_level ``` add_level(ilvl: int) -> CT_Lvl ``` A `w:lvl` for level `ilvl`, newly added in ascending `w:ilvl` order. Word rejects an abstract definition whose levels are out of order. Source code in `src/docx/oxml/numbering.py` ``` def add_level(self, ilvl: int) -> CT_Lvl: """A `w:lvl` for level `ilvl`, newly added in ascending `w:ilvl` order. Word rejects an abstract definition whose levels are out of order. """ existing = self.lvl_having_ilvl(ilvl) if existing is not None: return existing lvl = CT_Lvl.new(ilvl) for sibling in self.lvl_lst: if sibling.ilvl > ilvl: sibling.addprevious(lvl) return lvl _insert_in_order(self, lvl, "w:lvl", self._tag_seq) return lvl ``` #### lvl_having_ilvl ``` lvl_having_ilvl(ilvl: int) -> CT_Lvl | None ``` The `w:lvl` child for level `ilvl`, or `None` when it has none. Source code in `src/docx/oxml/numbering.py` ``` def lvl_having_ilvl(self, ilvl: int) -> CT_Lvl | None: """The `w:lvl` child for level `ilvl`, or |None| when it has none.""" return next(iter(self.xpath('./w:lvl[@w:ilvl="%d"]' % ilvl)), None) ``` ### CT_Num Bases: `BaseOxmlElement` `` element, which represents a concrete list definition instance, having a required child that references an abstract numbering definition that defines most of the formatting details. #### add_lvlOverride ``` add_lvlOverride(ilvl) ``` Return a newly added CT_NumLvl () element having its `ilvl` attribute set to `ilvl`. Source code in `src/docx/oxml/numbering.py` ``` def add_lvlOverride(self, ilvl): """Return a newly added CT_NumLvl () element having its ``ilvl`` attribute set to `ilvl`.""" return self._add_lvlOverride(ilvl=ilvl) ``` #### lvlOverride_having_ilvl ``` lvlOverride_having_ilvl(ilvl: int) -> CT_NumLvl | None ``` The `w:lvlOverride` child for level `ilvl`, or `None` when there is none. Source code in `src/docx/oxml/numbering.py` ``` def lvlOverride_having_ilvl(self, ilvl: int) -> CT_NumLvl | None: """The `w:lvlOverride` child for level `ilvl`, or |None| when there is none.""" return next(iter(self.xpath('./w:lvlOverride[@w:ilvl="%d"]' % ilvl)), None) ``` #### new ``` new(num_id, abstractNum_id) ``` Return a new `` element having numId of `num_id` and having a `` child with val attribute set to `abstractNum_id`. Source code in `src/docx/oxml/numbering.py` ``` @classmethod def new(cls, num_id, abstractNum_id): """Return a new ```` element having numId of `num_id` and having a ```` child with val attribute set to `abstractNum_id`.""" num = OxmlElement("w:num") num.numId = num_id abstractNumId = CT_DecimalNumber.new("w:abstractNumId", abstractNum_id) num.append(abstractNumId) return num ``` ### CT_NumLvl Bases: `BaseOxmlElement` `` element, which identifies a level in a list definition to override with settings it contains. #### lvl ``` lvl: CT_Lvl | None ``` The `w:lvl` override of this level, or `None` when it overrides only start. #### start_override ``` start_override: int | None ``` The number this level counts from in this instance, or `None`. #### add_startOverride ``` add_startOverride(val) ``` Return a newly added CT_DecimalNumber element having tagname `w:startOverride` and `val` attribute set to `val`. Source code in `src/docx/oxml/numbering.py` ``` def add_startOverride(self, val): """Return a newly added CT_DecimalNumber element having tagname ``w:startOverride`` and ``val`` attribute set to `val`.""" return self._add_startOverride(val=val) ``` ### CT_NumPr Bases: `BaseOxmlElement` A `` element, a container for numbering properties applied to a paragraph. #### ilvl_val ``` ilvl_val: int | None ``` Value of `w:ilvl/@w:val`, or `None` when absent. #### numId_val ``` numId_val: int | None ``` Value of `w:numId/@w:val`, or `None` when absent. ### CT_Numbering Bases: `BaseOxmlElement` `` element, the root element of a numbering part, i.e. numbering.xml. #### add_num ``` add_num(abstractNum_id) ``` Return a newly added CT_Num () element referencing the abstract numbering definition identified by `abstractNum_id`. Source code in `src/docx/oxml/numbering.py` ``` def add_num(self, abstractNum_id): """Return a newly added CT_Num () element referencing the abstract numbering definition identified by `abstractNum_id`.""" next_num_id = self._next_numId num = CT_Num.new(next_num_id, abstractNum_id) return self._insert_num(num) ``` #### abstractNum_having_abstractNumId ``` abstractNum_having_abstractNumId( abstractNumId: int, ) -> CT_AbstractNum | None ``` The `w:abstractNum` child with `abstractNumId`, or `None` if not found. Source code in `src/docx/oxml/numbering.py` ``` def abstractNum_having_abstractNumId(self, abstractNumId: int) -> CT_AbstractNum | None: """The `w:abstractNum` child with `abstractNumId`, or |None| if not found.""" xpath = './w:abstractNum[@w:abstractNumId="%d"]' % abstractNumId return next(iter(self.xpath(xpath)), None) ``` #### num_having_numId ``` num_having_numId(numId) ``` Return the `` child element having `numId` attribute matching `numId`. Source code in `src/docx/oxml/numbering.py` ``` def num_having_numId(self, numId): """Return the ```` child element having ``numId`` attribute matching `numId`.""" xpath = './w:num[@w:numId="%d"]' % numId try: return self.xpath(xpath)[0] except IndexError: raise KeyError("no element with numId %d" % numId) ``` ## object Custom element classes for embedded OLE objects — `w:object`. Word can embed a whole file inside a document — a spreadsheet, a PDF, another document — displayed as an icon or a preview image that opens the original application on double-click. The markup is a `w:object` in a run, holding a VML `v:shape` for the visual and an `o:OLEObject` naming the relationship to the embedded part under `word/embeddings/`. The visual is VML, not DrawingML, so none of the `Run.add_picture()` machinery applies. VML attribute names are not namespace-qualified the way the `w:` ones are, and their case is inconsistent — `ProgID`, `ShapeID`, `DrawAspect` — so they are taken from `ref/xsd/vml-officeDrawing.xsd` rather than from a sample document. ### CT_OLEObject Bases: `BaseOxmlElement` `o:OLEObject`, the element identifying the embedded file and its application. ### CT_Object Bases: `BaseOxmlElement` `w:object`, the run-level container for an embedded or linked OLE object. #### oleObject ``` oleObject: CT_OLEObject | None ``` The `o:OLEObject` child, or `None` for a `w:object` that has none. Reached by `find` rather than a declared child because `w:object` admits any `v:` or `o:` element in any order, so there is no schema sequence to place a `ZeroOrOne` against. #### shape ``` shape: BaseOxmlElement | None ``` The `v:shape` holding the object's visual, or `None` when there is none. #### image_rId ``` image_rId: str | None ``` The relationship id of the icon or preview image, or `None`. The image is a `v:imagedata` inside the shape, related in its own right — an embedded object is at minimum two relationships plus the run XML. ### iter_objects ``` iter_objects(element: BaseOxmlElement) -> List[CT_Object] ``` The `w:object` elements under `element`, in document order. Source code in `src/docx/oxml/object.py` ``` def iter_objects(element: BaseOxmlElement) -> List[CT_Object]: """The `w:object` elements under `element`, in document order.""" return element.xpath(".//w:object") ``` ## parser XML parser for python-docx. ### parse_xml ``` parse_xml(xml: str | bytes) -> 'BaseOxmlElement' ``` Root lxml element obtained by parsing XML character string `xml`. The custom parser is used, so custom element classes are produced for elements in `xml` that have them. Source code in `src/docx/oxml/parser.py` ``` def parse_xml(xml: str | bytes) -> "BaseOxmlElement": """Root lxml element obtained by parsing XML character string `xml`. The custom parser is used, so custom element classes are produced for elements in `xml` that have them. """ return cast("BaseOxmlElement", etree.fromstring(xml, oxml_parser)) ``` ### register_element_cls ``` register_element_cls( tag: str, cls: Type["BaseOxmlElement"] ) ``` Register an lxml custom element-class to use for `tag`. A instance of `cls` to be constructed when the oxml parser encounters an element with matching `tag`. `tag` is a string of the form `nspfx:tagroot`, e.g. `'w:document'`. Source code in `src/docx/oxml/parser.py` ``` def register_element_cls(tag: str, cls: Type["BaseOxmlElement"]): """Register an lxml custom element-class to use for `tag`. A instance of `cls` to be constructed when the oxml parser encounters an element with matching `tag`. `tag` is a string of the form `nspfx:tagroot`, e.g. `'w:document'`. """ nspfx, tagroot = tag.split(":") namespace = element_class_lookup.get_namespace(nsmap[nspfx]) namespace[tagroot] = cls ``` ### OxmlElement ``` OxmlElement( nsptag_str: str, attrs: Dict[str, str] | None = None, nsdecls: Dict[str, str] | None = None, ) -> BaseOxmlElement | _Element ``` Return a 'loose' lxml element having the tag specified by `nsptag_str`. The tag in `nsptag_str` must contain the standard namespace prefix, e.g. `a:tbl`. The resulting element is an instance of the custom element class for this tag name if one is defined. A dictionary of attribute values may be provided as `attrs`; they are set if present. All namespaces defined in the dict `nsdecls` are declared in the element using the key as the prefix and the value as the namespace name. If `nsdecls` is not provided, a single namespace declaration is added based on the prefix on `nsptag_str`. Source code in `src/docx/oxml/parser.py` ``` def OxmlElement( nsptag_str: str, attrs: Dict[str, str] | None = None, nsdecls: Dict[str, str] | None = None, ) -> BaseOxmlElement | etree._Element: # pyright: ignore[reportPrivateUsage] """Return a 'loose' lxml element having the tag specified by `nsptag_str`. The tag in `nsptag_str` must contain the standard namespace prefix, e.g. `a:tbl`. The resulting element is an instance of the custom element class for this tag name if one is defined. A dictionary of attribute values may be provided as `attrs`; they are set if present. All namespaces defined in the dict `nsdecls` are declared in the element using the key as the prefix and the value as the namespace name. If `nsdecls` is not provided, a single namespace declaration is added based on the prefix on `nsptag_str`. """ nsptag = NamespacePrefixedTag(nsptag_str) if nsdecls is None: nsdecls = nsptag.nsmap return oxml_parser.makeelement(nsptag.clark_name, attrib=attrs, nsmap=nsdecls) ``` ## revision Custom element classes for tracked changes (revisions). A revision is recorded in one of two shapes: **Content revisions** wrap the content they affect. `w:ins` holds runs that were added, `w:del` runs that were removed — with their text in `w:delText` rather than `w:t`, so a consumer that does not understand deletions does not show it. `w:moveTo` and `w:moveFrom` are the two halves of a move, and behave as an insertion and a deletion respectively: ``` added removed ``` **Property revisions** record what the formatting *used to be*: `w:rPrChange` holds the previous `w:rPr`, `w:pPrChange` the previous `w:pPr`, and so on. Accepting one means dropping the record; rejecting it means putting the recorded properties back. The same `w:ins` and `w:del` tag names are also used as empty markers — in `w:pPr/w:rPr` they say the paragraph mark itself was inserted or deleted, which is how a paragraph split or merge is tracked, and in `w:trPr` they say a table row was. lxml resolves an element class by tag name alone, so one class serves every position; whether a given `w:ins` wraps content is discovered from its children rather than declared. ### CT_TrackChange Bases: `BaseOxmlElement` A revision element — `w:ins`, `w:del`, `w:moveFrom`, `w:moveTo` or a `*Change`. Serves every position these tag names appear in, since lxml dispatches on tag name alone. A content revision has run children; a paragraph-mark or table-row marker has none; a property revision has the previous properties element. #### date ``` date: datetime | None ``` When the revision was made, or `None` when the document does not say. `None` too when the timestamp is not a valid ISO 8601 datetime. Word writes `w:date` in that form, but an anonymised document has it stripped or blanked and that is not a reason to refuse to read the revision. #### is_content_revision ``` is_content_revision: bool ``` True when this revision wraps content rather than marking a position. #### text ``` text: str ``` The text this revision covers, the empty string when it covers none. Both `w:t` and `w:delText` count: the point of a deletion is the text it removed, and reporting nothing for it would make the revision useless to read. ### run_original_text ``` run_original_text(r: CT_R) -> str ``` The text of `r` as the document read before its revisions. Differs from `CT_R.text` only for a run inside a deletion, whose text is in `w:delText` and so does not appear in the document as it now reads. Source code in `src/docx/oxml/revision.py` ``` def run_original_text(r: CT_R) -> str: """The text of `r` as the document read before its revisions. Differs from `CT_R.text` only for a run inside a deletion, whose text is in `w:delText` and so does not appear in the document as it now reads. """ return "".join(str(e) for e in r.xpath(_ORIGINAL_TEXT_XPATH)) ``` ### iter_original_run_content ``` iter_original_run_content( element: BaseOxmlElement, ) -> Iterator[CT_R | CT_Hyperlink] ``` Generate the runs of `element` as the document read before its revisions. Deletions are descended into and insertions skipped — the opposite of `docx.oxml.sdt.iter_run_content`, which generates the document as it now reads. Source code in `src/docx/oxml/revision.py` ``` def iter_original_run_content(element: BaseOxmlElement) -> Iterator[CT_R | CT_Hyperlink]: """Generate the runs of `element` as the document read before its revisions. Deletions are descended into and insertions skipped — the opposite of :func:`docx.oxml.sdt.iter_run_content`, which generates the document as it now reads. """ deleted = tuple(qn(t) for t in DELETED_TAGS) inserted = tuple(qn(t) for t in INSERTED_TAGS) for child in element.iterchildren(): tag = child.tag if tag in (qn("w:r"), qn("w:hyperlink")): yield cast("CT_R | CT_Hyperlink", child) elif tag in inserted: continue # -- not there before the revision -- elif tag in deleted or tag == qn("w:fldSimple") or tag in TRANSPARENT_WRAPPER_TAGS: yield from iter_original_run_content(cast(BaseOxmlElement, child)) elif tag == qn("w:sdt"): sdtContent = child.find(qn("w:sdtContent")) if sdtContent is not None: yield from iter_original_run_content(cast(BaseOxmlElement, sdtContent)) ``` ### iter_revision_elements ``` iter_revision_elements( element: BaseOxmlElement, ) -> List[CT_TrackChange] ``` Every revision element in the subtree of `element`, in document order. Source code in `src/docx/oxml/revision.py` ``` def iter_revision_elements(element: BaseOxmlElement) -> List[CT_TrackChange]: """Every revision element in the subtree of `element`, in document order.""" xpath = " | ".join(f".//{tag}" for tag in ALL_REVISION_TAGS) return cast("List[CT_TrackChange]", element.xpath(xpath)) ``` ## sdt Custom element classes for structured document tags, aka "content controls". A `w:sdt` element wraps a region of a document, marking it as a form field, a template placeholder, a building block, or a region bound to a data source. Its content lives inside a `w:sdtContent` child, so anything that walks a container's children without looking through that wrapper simply does not see it. The schema defines four `w:sdt` variants — block, run, row and cell — differing only in the content model of `w:sdtContent`. lxml dispatches on tag name alone, so one element class serves all four; what a particular `w:sdt` contains is discovered from its children rather than declared. ### CT_Sdt Bases: `BaseOxmlElement` `w:sdt` element, a structured document tag ("content control"). #### alias_val ``` alias_val: str | None ``` The friendly name shown on the control in Word, or `None` if not set. #### content_control_type ``` content_control_type: WD_CONTENT_CONTROL_TYPE | None ``` Member of WdContentControlType this control is, or `None`. `None` when `w:sdtPr` is absent or names no type, which Word treats as a rich-text control but is not the same as saying so explicitly. #### id_val ``` id_val: int | None ``` Value of `./w:sdtPr/w:id/@w:val`, or `None` if not present. Read from the attribute directly rather than through a registered element class; `w:id` appears in several unrelated places in the schema and is not this library's to claim globally. #### showing_placeholder ``` showing_placeholder: bool ``` True when the control currently displays its placeholder text. The text inside such a control is the prompt ("Click here to enter text."), not a value the user supplied. #### tag_val ``` tag_val: str | None ``` Value of `./w:sdtPr/w:tag/@w:val`, or `None` if not present. The tag is the programmatic identifier of the control; unlike the alias it is not shown to the user and is what code binding to a template matches on. Named `tag_val` rather than `tag` because `.tag` is lxml's element tag name. #### text ``` text: str ``` The text of everything inside this content control. ### CT_SdtContent Bases: `BaseOxmlElement` `w:sdtContent` element, the content region of a `w:sdt`. #### text ``` text: str ``` The text of this content region. Block-level content contributes one line per paragraph, including paragraphs inside a table; run-level content is concatenated as it would be in a paragraph. ### CT_SdtPr Bases: `BaseOxmlElement` `w:sdtPr` element, the properties of a `w:sdt`. ### iter_block_content ``` iter_block_content( element: BaseOxmlElement, ) -> Iterator[CT_P | CT_Tbl] ``` Generate each `w:p` and `w:tbl` child of `element`, in document order. A `w:sdt` child is looked through rather than skipped: the block-level content of its `w:sdtContent` is generated in its place, recursively, so a content control nested in another content control is seen as well. So is a `w:customXml`, which the schema defines in a block-level flavour (`CT_CustomXmlBlock`) as well as the run-level one — it wraps whole paragraphs and tables, and skipping it drops them from the document entirely. Source code in `src/docx/oxml/sdt.py` ``` def iter_block_content(element: BaseOxmlElement) -> Iterator[CT_P | CT_Tbl]: """Generate each `w:p` and `w:tbl` child of `element`, in document order. A `w:sdt` child is looked through rather than skipped: the block-level content of its `w:sdtContent` is generated in its place, recursively, so a content control nested in another content control is seen as well. So is a `w:customXml`, which the schema defines in a block-level flavour (`CT_CustomXmlBlock`) as well as the run-level one — it wraps whole paragraphs and tables, and skipping it drops them from the document entirely. """ for child in element.iterchildren(): if child.tag in (qn("w:p"), qn("w:tbl")): yield cast("CT_P | CT_Tbl", child) elif child.tag in TRANSPARENT_WRAPPER_TAGS: yield from iter_block_content(cast("BaseOxmlElement", child)) elif child.tag == qn("w:sdt"): sdtContent = child.find(qn("w:sdtContent")) if sdtContent is not None: yield from iter_block_content(sdtContent) ``` ### iter_run_content ``` iter_run_content( element: BaseOxmlElement, ) -> Iterator[CT_R | CT_Hyperlink] ``` Generate each `w:r` and `w:hyperlink` child of `element`, in document order. This is the document as it now reads, which for a document carrying tracked changes means with every revision accepted. As with iter_block_content, a run-level `w:sdt` is looked through. So is a `w:fldSimple`, whose runs hold the result text the field displays; skipping it would drop a page number or a cross-reference from the paragraph's text. So is an insertion (`w:ins`, `w:moveTo`), whose runs are part of the text. A deletion (`w:del`, `w:moveFrom`) is skipped: its text is no longer part of the document, and it is held in `w:delText` rather than `w:t` for exactly that reason. Use `docx.oxml.revision.iter_original_run_content` for the other reading. Source code in `src/docx/oxml/sdt.py` ``` def iter_run_content(element: BaseOxmlElement) -> Iterator[CT_R | CT_Hyperlink]: """Generate each `w:r` and `w:hyperlink` child of `element`, in document order. This is the document as it now reads, which for a document carrying tracked changes means with every revision accepted. As with :func:`iter_block_content`, a run-level `w:sdt` is looked through. So is a `w:fldSimple`, whose runs hold the result text the field displays; skipping it would drop a page number or a cross-reference from the paragraph's text. So is an insertion (`w:ins`, `w:moveTo`), whose runs are part of the text. A deletion (`w:del`, `w:moveFrom`) is skipped: its text is no longer part of the document, and it is held in `w:delText` rather than `w:t` for exactly that reason. Use :func:`docx.oxml.revision.iter_original_run_content` for the other reading. """ for child in element.iterchildren(): if child.tag in (qn("w:r"), qn("w:hyperlink")): yield cast("CT_R | CT_Hyperlink", child) elif child.tag in _LOOK_THROUGH_TAGS: yield from iter_run_content(cast("BaseOxmlElement", child)) elif child.tag in _SKIP_TAGS: continue elif child.tag == qn("w:sdt"): sdtContent = child.find(qn("w:sdtContent")) if sdtContent is not None: yield from iter_run_content(sdtContent) ``` ## section Section-related custom element classes. ### CT_PageBorders Bases: `_CT_BordersBase` `w:pgBorders` element, the border drawn around the pages of a section. Only four edges, unlike `w:pBdr`. The three attributes have no `CT_Border` counterpart and belong to the container: where the border is measured from, which pages it appears on, and whether it is drawn in front of or behind the page content. The schema gives the top and bottom edges the `CT_TopPageBorder` and `CT_BottomPageBorder` types, which add attributes naming a decorative border image. Those are not modelled; the edges arrive as `CT_Border` like every other edge tag, which covers the line style, colour, width and spacing that make up an ordinary page border. ### CT_HdrFtr Bases: `BaseOxmlElement` `w:hdr` and `w:ftr`, the root element for header and footer part respectively. #### inner_content_elements ``` inner_content_elements: List[CT_P | CT_Tbl] ``` Generate all `w:p` and `w:tbl` elements in this header or footer. Elements appear in document order. Content inside a `w:sdt` (content control) wrapper is included; content shaded by nesting in a `w:ins` or other wrapper is not. ### CT_HdrFtrRef Bases: `BaseOxmlElement` `w:headerReference` and `w:footerReference` elements. ### CT_Column Bases: `BaseOxmlElement` `w:col` element, one column of an unequal-width multi-column layout. ### CT_Columns Bases: `BaseOxmlElement` `w:cols` element, the multi-column layout of a section. `w:col` children appear only when the columns are of unequal width; the common equal-width case is described entirely by the attributes here. #### clear_cols ``` clear_cols() -> None ``` Remove all `w:col` children, restoring equal-width columns. Source code in `src/docx/oxml/section.py` ``` def clear_cols(self) -> None: """Remove all `w:col` children, restoring equal-width columns.""" for col in self.col_lst: self.remove(col) ``` ### CT_PageMar Bases: `BaseOxmlElement` `` element, defining page margins. ### CT_PageSz Bases: `BaseOxmlElement` `` element, defining page dimensions and orientation. ### CT_SectPr Bases: `BaseOxmlElement` `w:sectPr` element, the container element for section properties. #### bidi_val ``` bidi_val: bool | None ``` Value of `./w:bidi/@w:val`, or `None` if the element is absent. #### textDirection_val ``` textDirection_val: WD_TEXT_DIRECTION | None ``` Value of `./w:textDirection/@w:val`, or `None` if the element is absent. #### bottom_margin ``` bottom_margin: Length | None ``` Value of the `w:bottom` attr of `` child element, as Length. `None` when either the element or the attribute is not present. #### footer ``` footer: Length | None ``` Distance from bottom edge of page to bottom edge of the footer. This is the value of the `w:footer` attribute in the `w:pgMar` child element, as a Length object, or `None` if either the element or the attribute is not present. #### gutter ``` gutter: Length | None ``` The value of the `w:gutter` attribute in the `` child element, as a Length object, or `None` if either the element or the attribute is not present. #### header ``` header: Length | None ``` Distance from top edge of page to top edge of header. This value comes from the `w:header` attribute on the `w:pgMar` child element. `None` if either the element or the attribute is not present. #### left_margin ``` left_margin: Length | None ``` The value of the `w:left` attribute in the `` child element, as a Length object, or `None` if either the element or the attribute is not present. #### orientation ``` orientation: WD_ORIENTATION ``` `WD_ORIENTATION` member indicating page-orientation for this section. This is the value of the `orient` attribute on the `w:pgSz` child, or `WD_ORIENTATION.PORTRAIT` if not present. #### page_height ``` page_height: Length | None ``` Value in EMU of the `h` attribute of the `w:pgSz` child element. `None` if not present. #### page_width ``` page_width: Length | None ``` Value in EMU of the `w` attribute of the `` child element. `None` if not present. #### preceding_sectPr ``` preceding_sectPr: CT_SectPr | None ``` SectPr immediately preceding this one or None if this is the first. #### right_margin ``` right_margin: Length | None ``` The value of the `w:right` attribute in the `` child element, as a Length object, or `None` if either the element or the attribute is not present. #### start_type ``` start_type: WD_SECTION_START ``` The member of the `WD_SECTION_START` enumeration corresponding to the value of the `val` attribute of the `` child element, or `WD_SECTION_START.NEW_PAGE` if not present. #### titlePg_val ``` titlePg_val: bool ``` Value of `w:titlePg/@val` or `False` if `./w:titlePg` is not present. #### top_margin ``` top_margin: Length | None ``` The value of the `w:top` attribute in the `` child element, as a Length object, or `None` if either the element or the attribute is not present. #### add_footerReference ``` add_footerReference( type_: WD_HEADER_FOOTER, rId: str ) -> CT_HdrFtrRef ``` Return newly added CT_HdrFtrRef element of `type_` with `rId`. The element tag is `w:footerReference`. Source code in `src/docx/oxml/section.py` ``` def add_footerReference(self, type_: WD_HEADER_FOOTER, rId: str) -> CT_HdrFtrRef: """Return newly added CT_HdrFtrRef element of `type_` with `rId`. The element tag is `w:footerReference`. """ footerReference = self._add_footerReference() footerReference.type_ = type_ footerReference.rId = rId return footerReference ``` #### add_headerReference ``` add_headerReference( type_: WD_HEADER_FOOTER, rId: str ) -> CT_HdrFtrRef ``` Return newly added CT_HdrFtrRef element of `type_` with `rId`. The element tag is `w:headerReference`. Source code in `src/docx/oxml/section.py` ``` def add_headerReference(self, type_: WD_HEADER_FOOTER, rId: str) -> CT_HdrFtrRef: """Return newly added CT_HdrFtrRef element of `type_` with `rId`. The element tag is `w:headerReference`. """ headerReference = self._add_headerReference() headerReference.type_ = type_ headerReference.rId = rId return headerReference ``` #### clone ``` clone() -> CT_SectPr ``` Return an exact duplicate of this `` element tree suitable for use in adding a section break. All rsid\* attributes are removed from the root `` element. Source code in `src/docx/oxml/section.py` ``` def clone(self) -> CT_SectPr: """Return an exact duplicate of this ```` element tree suitable for use in adding a section break. All rsid* attributes are removed from the root ```` element. """ cloned_sectPr = deepcopy(self) cloned_sectPr.attrib.clear() return cloned_sectPr ``` #### get_footerReference ``` get_footerReference( type_: WD_HEADER_FOOTER, ) -> CT_HdrFtrRef | None ``` Return footerReference element of `type_` or None if not present. Source code in `src/docx/oxml/section.py` ``` def get_footerReference(self, type_: WD_HEADER_FOOTER) -> CT_HdrFtrRef | None: """Return footerReference element of `type_` or None if not present.""" path = "./w:footerReference[@w:type='%s']" % WD_HEADER_FOOTER.to_xml(type_) footerReferences = self.xpath(path) if not footerReferences: return None return footerReferences[0] ``` #### get_headerReference ``` get_headerReference( type_: WD_HEADER_FOOTER, ) -> CT_HdrFtrRef | None ``` Return headerReference element of `type_` or None if not present. Source code in `src/docx/oxml/section.py` ``` def get_headerReference(self, type_: WD_HEADER_FOOTER) -> CT_HdrFtrRef | None: """Return headerReference element of `type_` or None if not present.""" matching_headerReferences = self.xpath( "./w:headerReference[@w:type='%s']" % WD_HEADER_FOOTER.to_xml(type_) ) if len(matching_headerReferences) == 0: return None return matching_headerReferences[0] ``` #### iter_inner_content ``` iter_inner_content() -> Iterator[CT_P | CT_Tbl] ``` Generate all `w:p` and `w:tbl` elements in this section. Elements appear in document order. Elements shaded by nesting in a `w:ins` or other "wrapper" element will not be included. Source code in `src/docx/oxml/section.py` ``` def iter_inner_content(self) -> Iterator[CT_P | CT_Tbl]: """Generate all `w:p` and `w:tbl` elements in this section. Elements appear in document order. Elements shaded by nesting in a `w:ins` or other "wrapper" element will not be included. """ return _SectBlockElementIterator.iter_sect_block_elements(self) ``` #### remove_footerReference ``` remove_footerReference(type_: WD_HEADER_FOOTER) -> str ``` Return rId of w:footerReference child of `type_` after removing it. Source code in `src/docx/oxml/section.py` ``` def remove_footerReference(self, type_: WD_HEADER_FOOTER) -> str: """Return rId of w:footerReference child of `type_` after removing it.""" footerReference = self.get_footerReference(type_) if footerReference is None: # -- should never happen, but to satisfy type-check and just in case -- raise ValueError("CT_SectPr has no footer reference") rId = footerReference.rId self.remove(footerReference) return rId ``` #### remove_headerReference ``` remove_headerReference(type_: WD_HEADER_FOOTER) ``` Return rId of w:headerReference child of `type_` after removing it. Source code in `src/docx/oxml/section.py` ``` def remove_headerReference(self, type_: WD_HEADER_FOOTER): """Return rId of w:headerReference child of `type_` after removing it.""" headerReference = self.get_headerReference(type_) if headerReference is None: # -- should never happen, but to satisfy type-check and just in case -- raise ValueError("CT_SectPr has no header reference") rId = headerReference.rId self.remove(headerReference) return rId ``` ### CT_SectType Bases: `BaseOxmlElement` `` element, defining the section start type. ### \_SectBlockElementIterator ``` _SectBlockElementIterator(sectPr: CT_SectPr) ``` Generates the block-item XML elements in a section. A block-item element is a `CT_P` (paragraph) or a `CT_Tbl` (table). Source code in `src/docx/oxml/section.py` ``` def __init__(self, sectPr: CT_SectPr): self._sectPr = sectPr ``` #### iter_sect_block_elements ``` iter_sect_block_elements( sectPr: CT_SectPr, ) -> Iterator[BlockElement] ``` Generate each CT_P or CT_Tbl element within extents governed by `sectPr`. Source code in `src/docx/oxml/section.py` ``` @classmethod def iter_sect_block_elements(cls, sectPr: CT_SectPr) -> Iterator[BlockElement]: """Generate each CT_P or CT_Tbl element within extents governed by `sectPr`.""" return cls(sectPr)._iter_sect_block_elements() ``` ## settings Custom element classes related to document settings. ### CT_Settings Bases: `BaseOxmlElement` `w:settings` element, root element for the settings part. #### trackRevisions_val ``` trackRevisions_val: bool ``` Value of `w:trackRevisions/@w:val`, `False` when the element is absent. #### updateFields_val ``` updateFields_val: bool ``` Value of `w:updateFields/@w:val`, `False` when the element is absent. #### evenAndOddHeaders_val ``` evenAndOddHeaders_val: bool ``` Value of `w:evenAndOddHeaders/@w:val` or `None` if not present. ## shape Custom element classes for shape-related elements like ``. ### CT_Anchor Bases: `BaseOxmlElement` `` element, container for a "floating" shape. Where `wp:inline` puts a picture in the text flow like a character, `wp:anchor` detaches it: the picture is positioned against the page, the margin, the column or the paragraph, and text wraps around it. The schema type is an `xsd:sequence` and Word refuses to open a document whose children are out of order, so the `successors` bookkeeping below matters more than usual. The wrap element is one of an `xsd:choice` — exactly one must be present — which is why it is reached through wrap_type rather than as five separate declared children. #### wrap_type ``` wrap_type: WD_WRAP_TYPE ``` Member of WdWrapType describing how text wraps around this shape. #### new_pic_anchor ``` new_pic_anchor( shape_id: int, rId: str, filename: str, cx: Length, cy: Length, pos_x: Length, pos_y: Length, description: str | None = None, title: str | None = None, svg_rId: str | None = None, transform: tuple[int, bool] = (0, False), ) -> CT_Anchor ``` Create a `wp:anchor` element containing a `pic:pic` element. The shape is positioned `pos_x` right of and `pos_y` below the column and paragraph it is anchored to, which is where Word puts a picture converted from inline to floating, and text wraps around its bounding rectangle. Source code in `src/docx/oxml/shape.py` ``` @classmethod def new_pic_anchor( cls, shape_id: int, rId: str, filename: str, cx: Length, cy: Length, pos_x: Length, pos_y: Length, description: str | None = None, title: str | None = None, svg_rId: str | None = None, transform: tuple[int, bool] = (0, False), ) -> CT_Anchor: """Create a `wp:anchor` element containing a `pic:pic` element. The shape is positioned `pos_x` right of and `pos_y` below the column and paragraph it is anchored to, which is where Word puts a picture converted from inline to floating, and text wraps around its bounding rectangle. """ pic_id = 0 # -- as with an inline picture, Word does not appear to use this -- pic = CT_Picture.new(pic_id, filename, rId, cx, cy, svg_rId=svg_rId, transform=transform) anchor = cast(CT_Anchor, parse_xml(cls._anchor_xml())) anchor.extent.cx = cx anchor.extent.cy = cy anchor.positionH.offset = pos_x anchor.positionV.offset = pos_y anchor.docPr.id = shape_id anchor.docPr.name = "Picture %d" % shape_id if description is not None: anchor.docPr.descr = description if title is not None: anchor.docPr.title = title anchor.graphic.graphicData.uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" anchor.graphic.graphicData._insert_pic(pic) # pyright: ignore[reportPrivateUsage] return anchor ``` ### \_CT_PosBase Bases: `BaseOxmlElement` Common behavior of `wp:positionH` and `wp:positionV`. Both hold an `xsd:choice` of `wp:align` or `wp:posOffset` — a named alignment such as "center", or an absolute distance in EMU. Setting one removes the other, since the schema allows only one to be present and Word ignores a document that has both. #### align ``` align ``` The named alignment of this position, or `None` when an offset is used. #### offset ``` offset: Length | None ``` The absolute offset of this position, or `None` when an alignment is used. ### CT_PosH Bases: `_CT_PosBase` `` element, the horizontal position of a floating shape. ### CT_PosV Bases: `_CT_PosBase` `` element, the vertical position of a floating shape. ### CT_Blip Bases: `BaseOxmlElement` `` element, specifies image source and adjustments such as alpha and tint. #### svgBlip ``` svgBlip: CT_SvgBlip | None ``` The `asvg:svgBlip` extension of this blip, or `None` when there is none. Reached by xpath rather than a declared child, because `a:ext` is already registered as the extent element of `a:xfrm` and lxml dispatches on tag name alone; the same tag means two different things in DrawingML. ### CT_SvgBlip Bases: `BaseOxmlElement` `` element, the SVG source of a picture. A Word 2016 extension. It accompanies rather than replaces the raster blip: a consumer that does not understand the extension renders the raster one instead. ### CT_BlipFillProperties Bases: `BaseOxmlElement` `` element, specifies picture properties. ### CT_GraphicalObject Bases: `BaseOxmlElement` `` element, container for a DrawingML object. ### CT_GraphicalObjectData Bases: `BaseOxmlElement` `` element, container for the XML of a DrawingML object. ### CT_Inline Bases: `BaseOxmlElement` `` element, container for an inline shape. #### new ``` new( cx: Length, cy: Length, shape_id: int, pic: CT_Picture ) -> CT_Inline ``` Return a new `` element populated with the values passed as parameters. Source code in `src/docx/oxml/shape.py` ``` @classmethod def new(cls, cx: Length, cy: Length, shape_id: int, pic: CT_Picture) -> CT_Inline: """Return a new ```` element populated with the values passed as parameters.""" inline = cast(CT_Inline, parse_xml(cls._inline_xml())) inline.extent.cx = cx inline.extent.cy = cy inline.docPr.id = shape_id inline.docPr.name = "Picture %d" % shape_id inline.graphic.graphicData.uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" inline.graphic.graphicData._insert_pic(pic) return inline ``` #### new_pic_inline ``` new_pic_inline( shape_id: int, rId: str, filename: str, cx: Length, cy: Length, description: str | None = None, title: str | None = None, svg_rId: str | None = None, transform: tuple[int, bool] = (0, False), ) -> CT_Inline ``` Create `wp:inline` element containing a `pic:pic` element. The contents of the `pic:pic` element is taken from the argument values. `description` and `title` are the alternative text of the picture and are omitted when `None`. `svg_rId`, when given, identifies the SVG source of the picture, making `rId` its raster fallback. `transform` is the rotation and flip expressing the source image's EXIF orientation. `cx` and `cy` are the *display* dimensions, so they become the `wp:extent` and the layout reserves the space the rotated picture actually occupies. Source code in `src/docx/oxml/shape.py` ``` @classmethod def new_pic_inline( cls, shape_id: int, rId: str, filename: str, cx: Length, cy: Length, description: str | None = None, title: str | None = None, svg_rId: str | None = None, transform: tuple[int, bool] = (0, False), ) -> CT_Inline: """Create `wp:inline` element containing a `pic:pic` element. The contents of the `pic:pic` element is taken from the argument values. `description` and `title` are the alternative text of the picture and are omitted when |None|. `svg_rId`, when given, identifies the SVG source of the picture, making `rId` its raster fallback. `transform` is the rotation and flip expressing the source image's EXIF orientation. `cx` and `cy` are the *display* dimensions, so they become the `wp:extent` and the layout reserves the space the rotated picture actually occupies. """ pic_id = 0 # Word doesn't seem to use this, but does not omit it pic = CT_Picture.new(pic_id, filename, rId, cx, cy, svg_rId=svg_rId, transform=transform) inline = cls.new(cx, cy, shape_id, pic) if description is not None: inline.docPr.descr = description if title is not None: inline.docPr.title = title return inline ``` ### CT_NonVisualDrawingProps Bases: `BaseOxmlElement` Used for `` element, and perhaps others. Specifies the id and name of a DrawingML drawing, and its alternative text. ### CT_NonVisualPictureProperties Bases: `BaseOxmlElement` `` element, specifies picture locking and resize behaviors. ### CT_Picture Bases: `BaseOxmlElement` `` element, a DrawingML picture. #### new ``` new( pic_id: int, filename: str, rId: str, cx: Length, cy: Length, svg_rId: str | None = None, transform: tuple[int, bool] = (0, False), ) -> CT_Picture ``` A new minimum viable `` (picture) element. `cx` and `cy` are the *display* dimensions. `transform` is the `(rotation, flip_h)` pair expressing the source image's EXIF orientation, in the units `a:xfrm/@rot` uses; it is omitted when it is the no-op `(0, False)`. `rId` identifies the image the raster blip refers to. When `svg_rId` is given the picture also carries an `asvg:svgBlip` extension referring to that SVG, and `rId` is the raster fallback shown by anything that does not understand the extension. Source code in `src/docx/oxml/shape.py` ``` @classmethod def new( cls, pic_id: int, filename: str, rId: str, cx: Length, cy: Length, svg_rId: str | None = None, transform: tuple[int, bool] = (0, False), ) -> CT_Picture: """A new minimum viable `` (picture) element. `cx` and `cy` are the *display* dimensions. `transform` is the `(rotation, flip_h)` pair expressing the source image's EXIF orientation, in the units `a:xfrm/@rot` uses; it is omitted when it is the no-op `(0, False)`. `rId` identifies the image the raster blip refers to. When `svg_rId` is given the picture also carries an `asvg:svgBlip` extension referring to that SVG, and `rId` is the raster fallback shown by anything that does not understand the extension. """ pic = parse_xml(cls._pic_xml_svg() if svg_rId else cls._pic_xml()) pic.nvPicPr.cNvPr.id = pic_id pic.nvPicPr.cNvPr.name = filename pic.blipFill.blip.embed = rId if svg_rId: svgBlip = pic.blipFill.blip.svgBlip assert svgBlip is not None svgBlip.embed = svg_rId rotation, flip_h = transform if rotation in (90 * 60000, 270 * 60000): # -- `a:ext` is the box the shape occupies *before* rotation, which for a # -- quarter turn is the display box with its sides exchanged. The # -- containing `wp:extent` stays the display box, so the layout reserves # -- the right space. -- pic.spPr.cx, pic.spPr.cy = Emu(cy), Emu(cx) else: pic.spPr.cx = cx pic.spPr.cy = cy pic.spPr.apply_transform(rotation, flip_h) return pic ``` ### CT_PictureNonVisual Bases: `BaseOxmlElement` `` element, non-visual picture properties. ### CT_Point2D Bases: `BaseOxmlElement` Used for `` element, and perhaps others. Specifies an x, y coordinate (point). ### CT_PositiveSize2D Bases: `BaseOxmlElement` Used for `` element, and perhaps others later. Specifies the size of a DrawingML drawing. ### CT_PresetGeometry2D Bases: `BaseOxmlElement` `` element, specifies an preset autoshape geometry, such as `rect`. ### CT_RelativeRect Bases: `BaseOxmlElement` `` element, specifying picture should fill containing rectangle shape. ### CT_ShapeProperties Bases: `BaseOxmlElement` `` element, specifies size and shape of picture container. #### cx ``` cx ``` Shape width as an instance of Emu, or None if not present. #### cy ``` cy ``` Shape height as an instance of Emu, or None if not present. #### apply_transform ``` apply_transform(rotation: int, flip_h: bool) -> None ``` Rotate this shape by `rotation`, in 60000ths of a degree, and mirror it. Both are omitted when they would be the no-op values, so an ordinary picture carries no rotation markup at all. Source code in `src/docx/oxml/shape.py` ``` def apply_transform(self, rotation: int, flip_h: bool) -> None: """Rotate this shape by `rotation`, in 60000ths of a degree, and mirror it. Both are omitted when they would be the no-op values, so an ordinary picture carries no rotation markup at all. """ if rotation == 0 and not flip_h: return xfrm = self.get_or_add_xfrm() if rotation: xfrm.rot = rotation if flip_h: xfrm.flipH = True ``` ### CT_StretchInfoProperties Bases: `BaseOxmlElement` `` element, specifies how picture should fill its containing shape. ### CT_Transform2D Bases: `BaseOxmlElement` `` element, specifies size and shape of picture container. `@rot` is the rotation applied about the shape's centre, in 60000ths of a degree — 5400000 is a quarter turn clockwise. `@flipH` and `@flipV` mirror the shape. This is where an image's EXIF orientation lands: rotating the pixels themselves would mean a decode/encode dependency this library does not have, would lose quality, and would break the sha1 deduplication that keeps one copy of an image used twice. ## shared Objects shared by modules in the docx.oxml subpackage. ### CT_DecimalNumber Bases: `BaseOxmlElement` Used for ``, ``, `` and several others, containing a text representation of a decimal number (e.g. 42) in its `val` attribute. #### new ``` new(nsptagname: str, val: int) ``` Return a new `CT_DecimalNumber` element having tagname `nsptagname` and `val` attribute set to `val`. Source code in `src/docx/oxml/shared.py` ``` @classmethod def new(cls, nsptagname: str, val: int): """Return a new ``CT_DecimalNumber`` element having tagname `nsptagname` and ``val`` attribute set to `val`.""" return OxmlElement(nsptagname, attrs={qn("w:val"): str(val)}) ``` ### CT_OnOff Bases: `BaseOxmlElement` Used for `w:b`, `w:i` elements and others. Contains a bool-ish string in its `val` attribute, xsd:boolean plus "on" and "off". Defaults to `True`, so `` for example means "bold is turned on". ### CT_String Bases: `BaseOxmlElement` Used for `w:pStyle` and `w:tblStyle` elements and others. In those cases, it containing a style name in its `val` attribute. #### new ``` new(nsptagname: str, val: str) ``` A new ``` CT_String`` element with tagname ```nsptagname`and`val`attribute set to`val\`. Source code in `src/docx/oxml/shared.py` ``` @classmethod def new(cls, nsptagname: str, val: str): """A new `CT_String`` element with tagname `nsptagname` and `val` attribute set to `val`.""" elm = cast(CT_String, OxmlElement(nsptagname)) elm.val = val return elm ``` ## simpletypes Simple-type classes, corresponding to ST\_\* schema items. These provide validation and format translation for values stored in XML element attributes. Naming generally corresponds to the simple type in the associated XML schema. ### BaseSimpleType Base class for simple-types. ### XsdAnyUri Bases: `BaseStringType` There's a regex in the spec this is supposed to meet... but current assessment is that spending cycles on validating wouldn't be worth it for the number of programming errors it would catch. ### XsdId Bases: `BaseStringType` String that must begin with a letter or underscore and cannot contain any colons. Not fully validated because not used in external API. ### XsdStringEnumeration Bases: `BaseStringEnumerationType` Set of enumerated xsd:string values. ### XsdToken Bases: `BaseStringType` Xsd:string with whitespace collapsing, e.g. multiple spaces reduced to one, leading and trailing space stripped. ### ST_DateTime Bases: `BaseSimpleType` #### convert_from_xml ``` convert_from_xml(str_value: str) -> datetime ``` Convert an xsd:dateTime string to a datetime object. Source code in `src/docx/oxml/simpletypes.py` ``` @classmethod def convert_from_xml(cls, str_value: str) -> dt.datetime: """Convert an xsd:dateTime string to a datetime object.""" def parse_xsd_datetime(dt_str: str) -> dt.datetime: # -- handle trailing 'Z' (Zulu/UTC), common in Word files -- if dt_str.endswith("Z"): try: # -- optional fractional seconds case -- return dt.datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%S.%fZ").replace( tzinfo=dt.timezone.utc ) except ValueError: return dt.datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%SZ").replace( tzinfo=dt.timezone.utc ) # -- handles explicit offsets like +00:00, -05:00, or naive datetimes -- try: return dt.datetime.fromisoformat(dt_str) except ValueError: # -- fall-back to parsing as naive datetime (with or without fractional seconds) -- try: return dt.datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%S.%f") except ValueError: return dt.datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%S") try: # -- parse anything reasonable, but never raise, just use default epoch time -- return parse_xsd_datetime(str_value) except Exception: return dt.datetime(1970, 1, 1, tzinfo=dt.timezone.utc) ``` ### ST_WrapDistance Bases: `XsdUnsignedInt` Distance in EMU held clear of a floating shape when text wraps around it. The `distT`, `distB`, `distL` and `distR` attributes of `wp:anchor`. Exchanged as Length so it composes with `Pt()`, `Inches()` and the rest. ### ST_EighthPointMeasure Bases: `XsdUnsignedLong` Measure in eighths of a point, e.g. `"4"` is half a point. Used for border widths (`w:sz` on `w:tblBorders/w:top` and friends). Values are exchanged as Length so they compose with `Pt()`, `Inches()` and the rest. ### ST_PointMeasure Bases: `XsdUnsignedLong` Measure in whole points, e.g. `"4"` is four points. Used for the offset of a border from the text it surrounds (`w:space`). Values are exchanged as Length, as for ST_EighthPointMeasure. ### ST_FldCharType Bases: `XsdStringEnumeration` Valid values for the `w:fldChar/@w:fldCharType` attribute. ### ST_FtnEdn Bases: `XsdStringEnumeration` Valid values for the `w:footnote/@w:type` and `w:endnote/@w:type` attributes. ### ST_HexColor Bases: `BaseStringType` `ST_HexColor`, a union of an RGB triple and the literal "auto". `ref/xsd/wml.xsd:159` defines it as ``, so "auto" — meaning "let the consumer choose a colour that contrasts with the background" — is as valid as a hex triple. Both directions accept it; converting one way only would make a value readable and not writable. #### convert_to_xml ``` convert_to_xml(value: RGBColor | str) -> str ``` Keep alpha hex numerals all uppercase just for consistency. Source code in `src/docx/oxml/simpletypes.py` ``` @classmethod def convert_to_xml( # pyright: ignore[reportIncompatibleMethodOverride] cls, value: RGBColor | str ) -> str: """Keep alpha hex numerals all uppercase just for consistency.""" if value == ST_HexColorAuto.AUTO: return ST_HexColorAuto.AUTO # expecting 3-tuple of ints in range 0-255 return "%02X%02X%02X" % cast(RGBColor, value) ``` ### ST_HexColorAuto Bases: `XsdStringEnumeration` Value for \`w:color/[@val="auto"] attribute setting. ### ST_HpsMeasure Bases: `XsdUnsignedLong` Half-point measure, e.g. 24.0 represents 12.0 points. The schema type is a union of an unsigned decimal count of half-points and a universal measure like `"12pt"`. A fractional count of half-points such as `"21.5"` is not strictly valid, but Word reads it and several other generators write it, so it is accepted here and rounded to the nearest EMU. #### convert_to_xml ``` convert_to_xml(value: int | Length) -> str ``` Round to the nearest half-point rather than truncating. A half-point count is always written as an integer, since a fractional value is outside the schema type even though it is accepted on read. Source code in `src/docx/oxml/simpletypes.py` ``` @classmethod def convert_to_xml(cls, value: int | Length) -> str: """Round to the nearest half-point rather than truncating. A half-point count is always written as an integer, since a fractional value is outside the schema type even though it is accepted on read. """ emu = Emu(value) half_points = int(round(emu.pt * 2)) return str(half_points) ``` ### ST_Merge Bases: `XsdStringEnumeration` Valid values for attribute. ### ST_PageBorderDisplay Bases: `XsdStringEnumeration` Valid values for `w:pgBorders/@w:display`. ### ST_PageBorderOffset Bases: `XsdStringEnumeration` Valid values for `w:pgBorders/@w:offsetFrom`. ### ST_PageBorderZOrder Bases: `XsdStringEnumeration` Valid values for `w:pgBorders/@w:zOrder`. ### ST_MeasurementOrPercent Bases: `XsdInt` The `w:w` attribute of `w:tblW`, `w:tcW`, `w:tblInd` and the rest of `CT_TblWidth`. What the number means depends on the sibling `w:type` attribute, which an attribute converter cannot see, so this type stays deliberately literal and hands back a plain `int`: twips for `w:type="dxa"`, fiftieths of a percent for `"pct"`. `CT_TblWidth` is where the two are told apart. The schema also admits `"50%"` and universal measures such as `"1.5in"`. Word writes neither, but documents from other producers do, so both are converted to the plain form on the way in. ### ST_ShortHexNumber Bases: `BaseSimpleType` A two-byte value written as four hexadecimal digits, e.g. `"04A0"`. Used for the legacy bitmask on `w:tblLook/@w:val`. Exchanged as an `int`. ### ST_TextScalePercent Bases: `XsdInt` Horizontal character scaling, as a whole percentage of normal width. ECMA-376 constrains `w:w/@w:val` to 1..600; Word rejects values outside that. ### ST_VerticalAlignRun Bases: `XsdStringEnumeration` Valid values for `w:vertAlign/@val`. ## styles Custom element classes related to the styles part. ### CT_PPrDefault Bases: `BaseOxmlElement` `w:pPrDefault` element, wrapping the document-wide default paragraph formatting. ### CT_RPrDefault Bases: `BaseOxmlElement` `w:rPrDefault` element, wrapping the document-wide default run formatting. ### CT_DocDefaults Bases: `BaseOxmlElement` `w:docDefaults` element, the bottom of the formatting inheritance chain. Whatever is set here applies to the whole document before any style or direct formatting. For many real documents it is the only place the base font and the base paragraph spacing are set. ### CT_LatentStyles Bases: `BaseOxmlElement` `w:latentStyles` element, defining behavior defaults for latent styles and containing `w:lsdException` child elements that each override those defaults for a named latent style. #### bool_prop ``` bool_prop(attr_name) ``` Return the boolean value of the attribute having `attr_name`, or `False` if not present. Source code in `src/docx/oxml/styles.py` ``` def bool_prop(self, attr_name): """Return the boolean value of the attribute having `attr_name`, or |False| if not present.""" value = getattr(self, attr_name) if value is None: return False return value ``` #### get_by_name ``` get_by_name(name) ``` Return the `w:lsdException` child having `name`, or `None` if not found. Source code in `src/docx/oxml/styles.py` ``` def get_by_name(self, name): """Return the `w:lsdException` child having `name`, or |None| if not found.""" found = self.xpath("w:lsdException[@w:name=$name]", name=name) if not found: return None return found[0] ``` #### set_bool_prop ``` set_bool_prop(attr_name, value) ``` Set the on/off attribute having `attr_name` to `value`. Source code in `src/docx/oxml/styles.py` ``` def set_bool_prop(self, attr_name, value): """Set the on/off attribute having `attr_name` to `value`.""" setattr(self, attr_name, bool(value)) ``` ### CT_LsdException Bases: `BaseOxmlElement` `` element, defining override visibility behaviors for a named latent style. #### delete ``` delete() ``` Remove this `w:lsdException` element from the XML document. Source code in `src/docx/oxml/styles.py` ``` def delete(self): """Remove this `w:lsdException` element from the XML document.""" self.getparent().remove(self) ``` #### on_off_prop ``` on_off_prop(attr_name) ``` Return the boolean value of the attribute having `attr_name`, or `None` if not present. Source code in `src/docx/oxml/styles.py` ``` def on_off_prop(self, attr_name): """Return the boolean value of the attribute having `attr_name`, or |None| if not present.""" return getattr(self, attr_name) ``` #### set_on_off_prop ``` set_on_off_prop(attr_name, value) ``` Set the on/off attribute having `attr_name` to `value`. Source code in `src/docx/oxml/styles.py` ``` def set_on_off_prop(self, attr_name, value): """Set the on/off attribute having `attr_name` to `value`.""" setattr(self, attr_name, value) ``` ### CT_Style Bases: `BaseOxmlElement` A `` element, representing a style definition. #### basedOn_val ``` basedOn_val ``` Value of `w:basedOn/@w:val` or `None` if not present. #### base_style ``` base_style ``` Sibling CT_Style element this style is based on or `None` if no base style or base style not found. #### locked_val ``` locked_val ``` Value of `w:locked/@w:val` or `False` if not present. #### name_val ``` name_val ``` Value of `` child or `None` if not present. #### link_val ``` link_val ``` Value of `w:link/@w:val` or `None` if not present. The id of the paired style of the other kind: a paragraph style's `w:link` names the character style holding its run formatting, and vice versa. Copying one without the other leaves a dangling pair. #### next_val ``` next_val ``` Value of `w:next/@w:val` or `None` if not present. #### numId_val ``` numId_val: int | None ``` Value of `./w:pPr/w:numPr/w:numId/@w:val`, or `None` if not present. A numbering reference points into the numbering part, so it means nothing on its own in another document. #### next_style ``` next_style ``` Sibling CT_Style element identified by the value of `w:name/@w:val` or `None` if no value is present or no style with that style id is found. #### qFormat_val ``` qFormat_val ``` Value of `w:qFormat/@w:val` or `False` if not present. #### semiHidden_val ``` semiHidden_val ``` Value of `` child or `False` if not present. #### uiPriority_val ``` uiPriority_val ``` Value of `` child or `None` if not present. #### unhideWhenUsed_val ``` unhideWhenUsed_val ``` Value of `w:unhideWhenUsed/@w:val` or `False` if not present. #### delete ``` delete() ``` Remove this `w:style` element from its parent `w:styles` element. Source code in `src/docx/oxml/styles.py` ``` def delete(self): """Remove this `w:style` element from its parent `w:styles` element.""" self.getparent().remove(self) ``` ### CT_Styles Bases: `BaseOxmlElement` `` element, the root element of a styles part, i.e. styles.xml. #### add_style_of_type ``` add_style_of_type(name, style_type, builtin) ``` Return a newly added `w:style` element having `name` and `style_type`. `w:style/@customStyle` is set based on the value of `builtin`. Source code in `src/docx/oxml/styles.py` ``` def add_style_of_type(self, name, style_type, builtin): """Return a newly added `w:style` element having `name` and `style_type`. `w:style/@customStyle` is set based on the value of `builtin`. """ style = self.add_style() style.type = style_type style.customStyle = None if builtin else True style.styleId = styleId_from_name(name) style.name_val = name return style ``` #### default_for ``` default_for(style_type) ``` Return `w:style[@w:type="*{style_type}*][-1]` or `None` if not found. Source code in `src/docx/oxml/styles.py` ``` def default_for(self, style_type): """Return `w:style[@w:type="*{style_type}*][-1]` or |None| if not found.""" default_styles_for_type = [ s for s in self._iter_styles() if s.type == style_type and s.default ] if not default_styles_for_type: return None # spec calls for last default in document order return default_styles_for_type[-1] ``` #### get_by_id ``` get_by_id(styleId: str) -> CT_Style | None ``` `w:style` child where @styleId = `styleId`. `None` if not found. Source code in `src/docx/oxml/styles.py` ``` def get_by_id(self, styleId: str) -> CT_Style | None: """`w:style` child where @styleId = `styleId`. |None| if not found. """ return next(iter(self.xpath("w:style[@w:styleId=$style_id]", style_id=styleId)), None) ``` #### get_by_name ``` get_by_name(name: str) -> CT_Style | None ``` `w:style` child with `w:name` grandchild having value `name`. Matched exactly first, then case-insensitively. Word treats style names as case-insensitive, and a built-in style has two spellings — the UI name ("Heading 1") and the internal name Word stores ("heading 1"). Documents written by other generators routinely store the UI casing, and without the second pass the style is present but unreachable. The exact pass runs first so that a document containing both spellings resolves to the one asked for rather than to whichever comes first. `None` if not found. Source code in `src/docx/oxml/styles.py` ``` def get_by_name(self, name: str) -> CT_Style | None: """`w:style` child with `w:name` grandchild having value `name`. Matched exactly first, then case-insensitively. Word treats style names as case-insensitive, and a built-in style has two spellings — the UI name ("Heading 1") and the internal name Word stores ("heading 1"). Documents written by other generators routinely store the UI casing, and without the second pass the style is present but unreachable. The exact pass runs first so that a document containing both spellings resolves to the one asked for rather than to whichever comes first. |None| if not found. """ exact = next(iter(self.xpath("w:style[w:name/@w:val=$name]", name=name)), None) if exact is not None: return exact # -- no XPath 1.0 lower-case function, so fold in Python. Only reached when the # -- exact match fails, which for a Word-authored document is never. -- folded = name.lower() for style in self.xpath("w:style"): style_name = style.name_val if style_name is not None and style_name.lower() == folded: return style return None ``` ### styleId_from_name ``` styleId_from_name(name) ``` Return the style id corresponding to `name`, taking into account special-case names such as 'Heading 1'. Source code in `src/docx/oxml/styles.py` ``` def styleId_from_name(name): """Return the style id corresponding to `name`, taking into account special-case names such as 'Heading 1'.""" return { "caption": "Caption", "heading 1": "Heading1", "heading 2": "Heading2", "heading 3": "Heading3", "heading 4": "Heading4", "heading 5": "Heading5", "heading 6": "Heading6", "heading 7": "Heading7", "heading 8": "Heading8", "heading 9": "Heading9", }.get(name, name.replace(" ", "")) ``` ## table Custom element classes for tables. ### CT_Border Bases: `BaseOxmlElement` A single border edge, e.g. `w:tblBorders/w:top`. The same complex type serves every edge of `w:tblBorders`, `w:tcBorders` and `w:pBdr`. ### \_CT_BordersBase Bases: `BaseOxmlElement` Common behavior of the `w:tblBorders` and `w:tcBorders` elements. Each holds an optional `CT_Border` child per edge and they differ only in which edges they admit; `edges` names those, in schema order. #### get_border ``` get_border(edge: str) -> CT_Border | None ``` The `w:{edge}` child element, or `None` when this edge has no border. Source code in `src/docx/oxml/table.py` ``` def get_border(self, edge: str) -> CT_Border | None: """The `w:{edge}` child element, or |None| when this edge has no border.""" return cast("CT_Border | None", getattr(self, edge)) ``` #### get_or_add_border ``` get_or_add_border(edge: str) -> CT_Border ``` The `w:{edge}` child element, newly added in schema order if not present. A newly added border is given `w:val="single"`, since `w:val` is required and a border element without a line style is not valid. Source code in `src/docx/oxml/table.py` ``` def get_or_add_border(self, edge: str) -> CT_Border: """The `w:{edge}` child element, newly added in schema order if not present. A newly added border is given `w:val="single"`, since `w:val` is required and a border element without a line style is not valid. """ border = self.get_border(edge) if border is not None: return border border = cast("CT_Border", getattr(self, "get_or_add_%s" % edge)()) border.val = WD_LINE_STYLE.SINGLE return border ``` #### remove_border ``` remove_border(edge: str) -> None ``` Remove the `w:{edge}` child element; does nothing when it is not present. Source code in `src/docx/oxml/table.py` ``` def remove_border(self, edge: str) -> None: """Remove the `w:{edge}` child element; does nothing when it is not present.""" cast("Callable[[], None]", getattr(self, "_remove_%s" % edge))() ``` ### CT_TblBorders Bases: `_CT_BordersBase` `w:tblBorders` element, the set of border edges of a table. ### CT_TcBorders Bases: `_CT_BordersBase` `w:tcBorders` element, the set of border edges of a table cell. Adds the two diagonals to the edges a table admits. ### CT_Height Bases: `BaseOxmlElement` Used for `w:trHeight` to specify a row height and row height rule. ### CT_Row Bases: `BaseOxmlElement` `` element. #### grid_after ``` grid_after: int ``` The number of unpopulated layout-grid cells at the end of this row. #### grid_before ``` grid_before: int ``` The number of unpopulated layout-grid cells at the start of this row. #### tr_idx ``` tr_idx: int ``` Index of this `w:tr` element within its parent `w:tbl` element. #### trHeight_hRule ``` trHeight_hRule: WD_ROW_HEIGHT_RULE | None ``` The value of `./w:trPr/w:trHeight/@w:hRule`, or `None` if not present. #### trHeight_val ``` trHeight_val ``` Return the value of `w:trPr/w:trHeight@w:val`, or `None` if not present. #### cantSplit_val ``` cantSplit_val: bool | None ``` Value of `w:trPr/w:cantSplit@w:val`, or `None` if not present. #### tblHeader_val ``` tblHeader_val: bool | None ``` Value of `w:trPr/w:tblHeader/@w:val`, or `None` if not present. #### hidden_val ``` hidden_val: bool | None ``` Value of `w:trPr/w:hidden/@w:val`, or `None` if not present. #### alignment ``` alignment: WD_TABLE_ALIGNMENT | None ``` Value of `w:trPr/w:jc/@w:val`, or `None` if not present. #### cell_spacing ``` cell_spacing: Length | None ``` Value of `w:trPr/w:tblCellSpacing`, or `None` if not present. #### width_after ``` width_after: Length | None ``` Value of `w:trPr/w:wAfter`, or `None` if not present. #### width_before ``` width_before: Length | None ``` Value of `w:trPr/w:wBefore`, or `None` if not present. #### grid_width ``` grid_width() -> int ``` The count of layout-grid columns this row occupies. Includes the grid positions this row leaves unpopulated at either end. Source code in `src/docx/oxml/table.py` ``` def grid_width(self) -> int: """The count of layout-grid columns this row occupies. Includes the grid positions this row leaves unpopulated at either end. """ return self.grid_before + sum(tc.grid_span for tc in self.tc_lst) + self.grid_after ``` #### delete_grid_column ``` delete_grid_column(grid_offset: int, part=None) -> None ``` Remove this row's occupancy of layout-grid column `grid_offset`. A cell that starts at `grid_offset` and spans no further is removed; one that spans this column and others is narrowed by one, so the rest of its span survives. A row that does not populate the column is left alone, and its `w:gridBefore` or `w:gridAfter` adjusted when the removed column falls inside the unpopulated run. Source code in `src/docx/oxml/table.py` ``` def delete_grid_column(self, grid_offset: int, part=None) -> None: """Remove this row's occupancy of layout-grid column `grid_offset`. A cell that starts at `grid_offset` and spans no further is removed; one that spans this column and others is narrowed by one, so the rest of its span survives. A row that does not populate the column is left alone, and its `w:gridBefore` or `w:gridAfter` adjusted when the removed column falls inside the unpopulated run. """ from docx.oxml.deletion import delete_element grid_before = self.grid_before if grid_offset < grid_before: self.trPr.grid_before = grid_before - 1 # pyright: ignore[reportOptionalMemberAccess] return try: tc = self.tc_covering_grid_offset(grid_offset) except ValueError: # -- the column falls after this row's last cell, in its `w:gridAfter` run -- trPr = self.trPr if trPr is not None and trPr.grid_after > 0: trPr.grid_after = trPr.grid_after - 1 return if tc.grid_span > 1: tc.grid_span = tc.grid_span - 1 return delete_element(tc, part) ``` #### transfer_vertical_spans_to_row_below ``` transfer_vertical_spans_to_row_below() -> None ``` Make the row below own any vertical span that starts in this row. Called before deleting this row: a continuation cell whose origin disappears would otherwise be left referring to nothing. The cell below becomes the origin, keeping the content and the remainder of the span, which is what Word does. Source code in `src/docx/oxml/table.py` ``` def transfer_vertical_spans_to_row_below(self) -> None: """Make the row below own any vertical span that starts in this row. Called before deleting this row: a continuation cell whose origin disappears would otherwise be left referring to nothing. The cell below becomes the origin, keeping the content and the remainder of the span, which is what Word does. """ tr_below = self._tr_below if tr_below is None: return for tc in self.tc_lst: if tc.vMerge != ST_Merge.RESTART: continue try: tc_below = tr_below.tc_covering_grid_offset(tc.grid_offset) except ValueError: continue if tc_below.vMerge != ST_Merge.CONTINUE: continue tc._move_content_to(tc_below) # -- the cell below is the origin now; it keeps "restart" only if the span # -- continues past it -- tc_below.vMerge = ST_Merge.RESTART if tc_below.bottom > tc_below._tr_idx + 1 else None ``` #### tc_covering_grid_offset ``` tc_covering_grid_offset(grid_offset: int) -> CT_Tc ``` The `w:tc` element in this tr occupying layout-grid column `grid_offset`. Unlike `.tc_at_grid_offset()`, a horizontally merged cell is returned for every grid column it spans, not only for the one it starts at. Raises `ValueError` when this row does not populate `grid_offset`, which happens when the row starts late or ends early. Source code in `src/docx/oxml/table.py` ``` def tc_covering_grid_offset(self, grid_offset: int) -> CT_Tc: """The `w:tc` element in this tr occupying layout-grid column `grid_offset`. Unlike `.tc_at_grid_offset()`, a horizontally merged cell is returned for every grid column it spans, not only for the one it starts at. Raises |ValueError| when this row does not populate `grid_offset`, which happens when the row starts late or ends early. """ remaining_offset = grid_offset - self.grid_before if remaining_offset >= 0: for tc in self.tc_lst: grid_span = tc.grid_span if remaining_offset < grid_span: return tc remaining_offset -= grid_span raise ValueError(f"row does not populate grid_offset={grid_offset}") ``` #### tc_at_grid_offset ``` tc_at_grid_offset(grid_offset: int) -> CT_Tc ``` The `tc` element in this tr at exact `grid offset`. Raises ValueError when this `w:tr` contains no `w:tc` with exact starting `grid_offset`. Source code in `src/docx/oxml/table.py` ``` def tc_at_grid_offset(self, grid_offset: int) -> CT_Tc: """The `tc` element in this tr at exact `grid offset`. Raises ValueError when this `w:tr` contains no `w:tc` with exact starting `grid_offset`. """ # -- account for omitted cells at the start of the row -- remaining_offset = grid_offset - self.grid_before for tc in self.tc_lst: # -- We've gone past grid_offset without finding a tc, no sense searching further. -- if remaining_offset < 0: break # -- We've arrived at grid_offset, this is the `w:tc` we're looking for. -- if remaining_offset == 0: return tc # -- We're not there yet, skip forward the number of layout-grid cells this cell # -- occupies. remaining_offset -= tc.grid_span raise ValueError(f"no `tc` element at grid_offset={grid_offset}") ``` ### CT_Tbl Bases: `BaseOxmlElement` `` element. #### tblGrid ``` tblGrid: CT_TblGrid ``` The `w:tblGrid` child of this table, synthesized when absent. `w:tblGrid` is required by the schema, but Word opens a table without one by reconstructing the grid from the row contents, and enough generators emit such a table that refusing to read one is harsher than the situation warrants. Note the synthesized element is *inserted into the tree*, so saving a document read this way repairs the table. This is deliberate; the alternative is an `add_column()` that silently does nothing and a save that writes the invalid table straight back out. A `w:tblGrid` that is present but has fewer `w:gridCol` children than the widest row is left alone. Nothing in this library depends on the grid to locate a cell, so the short grid affects only `len(table.columns)`, which reports what the document actually says. #### bidiVisual_val ``` bidiVisual_val: bool | None ``` Value of `./w:tblPr/w:bidiVisual/@w:val` or `None` if not present. Controls whether table cells are displayed right-to-left or left-to-right. #### col_count ``` col_count ``` The number of grid columns in this table. #### tblStyle_val ``` tblStyle_val: str | None ``` `w:tblPr/w:tblStyle/@w:val` (a table style id) or `None` if not present. #### tr_at_idx ``` tr_at_idx(idx: int) -> CT_Row ``` The `w:tr` child of this table at `idx`, counting from zero. Raises `IndexError` when `idx` is out of range. Locating the row this way avoids materializing the full row list, which is what makes reading a table row by row cost time proportional to its size rather than to its square. Source code in `src/docx/oxml/table.py` ``` def tr_at_idx(self, idx: int) -> CT_Row: """The `w:tr` child of this table at `idx`, counting from zero. Raises |IndexError| when `idx` is out of range. Locating the row this way avoids materializing the full row list, which is what makes reading a table row by row cost time proportional to its size rather than to its square. """ if idx < 0: return self.tr_lst[idx] tr = next(islice(self.iterchildren(qn("w:tr")), idx, idx + 1), None) if tr is None: raise IndexError("table row index [%d] is out of range" % idx) return cast(CT_Row, tr) ``` #### iter_tcs ``` iter_tcs() ``` Generate each of the `w:tc` elements in this table, left to right and top to bottom. Each cell in the first row is generated, followed by each cell in the second row, etc. Source code in `src/docx/oxml/table.py` ``` def iter_tcs(self): """Generate each of the `w:tc` elements in this table, left to right and top to bottom. Each cell in the first row is generated, followed by each cell in the second row, etc. """ for tr in self.tr_lst: for tc in tr.tc_lst: yield tc ``` #### new_tbl ``` new_tbl(rows: int, cols: int, width: Length) -> CT_Tbl ``` Return a new `w:tbl` element having `rows` rows and `cols` columns. `width` is distributed evenly between the columns. Source code in `src/docx/oxml/table.py` ``` @classmethod def new_tbl(cls, rows: int, cols: int, width: Length) -> CT_Tbl: """Return a new `w:tbl` element having `rows` rows and `cols` columns. `width` is distributed evenly between the columns. """ return cast(CT_Tbl, parse_xml(cls._tbl_xml(rows, cols, width))) ``` ### CT_TblGrid Bases: `BaseOxmlElement` `w:tblGrid` element. Child of `w:tbl`, holds \`w:gridCol> elements that define column count, width, etc. ### CT_TblGridCol Bases: `BaseOxmlElement` `w:gridCol` element, child of `w:tblGrid`, defines a table column. #### gridCol_idx ``` gridCol_idx: int ``` Index of this `w:gridCol` element within its parent `w:tblGrid` element. ### CT_TblLayoutType Bases: `BaseOxmlElement` `w:tblLayout` element. Specifies whether column widths are fixed or can be automatically adjusted based on content. ### CT_TblPr Bases: `BaseOxmlElement` `` element, child of ``, holds child elements that define table properties such as style and borders. #### alignment ``` alignment: WD_TABLE_ALIGNMENT | None ``` Horizontal alignment of table, `None` if `./w:jc` is not present. #### autofit ``` autofit: bool ``` `False` when there is a `w:tblLayout` child with `@w:type="fixed"`. Otherwise `True`. #### style ``` style ``` Return the value of the `val` attribute of the `` child or `None` if not present. ### CT_TblPrEx Bases: `BaseOxmlElement` `w:tblPrEx` element, exceptions to table-properties. Applied at a lower level, like a `w:tr` to modify the appearance. Possibly used when two tables are merged. For more see: http://officeopenxml.com/WPtablePropertyExceptions.php ### CT_TblWidth Bases: `BaseOxmlElement` Used for `w:tblW` and `w:tcW` and others, specifies a table-related width. #### width ``` width: Length | None ``` EMU length indicated by the combined `w:w` and `w:type` attrs. `None` for any `w:type` other than `dxa`, which includes the percentage widths a Length cannot represent. Use `.value` for the reading that covers those. #### value ``` value: Length | Pct | None ``` The width this element expresses, whatever unit it is written in. A Length for `w:type="dxa"`, a Pct for `"pct"`, and `None` for `"auto"` and `"nil"` — neither of those carries a width of its own, the first meaning "size to the content" and the second "no width". ### CT_TblCellMar Bases: `BaseOxmlElement` `w:tblCellMar` element, the default cell margins for a whole table. An absent edge means the value is inherited from the table style. Each edge is a `CT_TblWidth` in the schema, but the edge tag names are shared with `w:tblBorders` and lxml resolves an element class by tag name alone, so these children arrive typed as `CT_Border` — see the note above the registrations in `oxml/__init__.py`. The `w:w` and `w:type` attributes are therefore read and written directly here rather than through element-class attributes. #### get_margin ``` get_margin(edge: str) -> Length | None ``` The width of the `w:{edge}` child, or `None` when that edge is absent. Also `None` when the edge is present but expressed in a unit other than `dxa`, which is the only one Word writes here. Source code in `src/docx/oxml/table.py` ``` def get_margin(self, edge: str) -> Length | None: """The width of the `w:{edge}` child, or |None| when that edge is absent. Also |None| when the edge is present but expressed in a unit other than `dxa`, which is the only one Word writes here. """ child = self.find(qn("w:%s" % edge)) if child is None or child.get(qn("w:type")) != "dxa": return None w = child.get(qn("w:w")) return None if w is None else Twips(int(w)) ``` #### set_margin ``` set_margin(edge: str, value: Length | None) -> None ``` Set the `w:{edge}` child to `value`, removing it when `value` is `None`. Source code in `src/docx/oxml/table.py` ``` def set_margin(self, edge: str, value: Length | None) -> None: """Set the `w:{edge}` child to `value`, removing it when `value` is |None|.""" tag = "w:%s" % edge child = self.find(qn(tag)) if value is None: if child is not None: self.remove(child) return if child is None: child = OxmlElement(tag) self._insert_edge(tag, child) child.set(qn("w:type"), "dxa") child.set(qn("w:w"), str(Emu(value).twips)) ``` ### CT_TblLook Bases: `BaseOxmlElement` `w:tblLook` element, selecting which parts of the table style apply. All attributes, no children. `w:val` is the legacy bitmask carrying the same six flags as the named attributes; Word writes both and keeps them in step, so `docx.table._TableLook` rewrites it whenever a flag changes. #### update_val ``` update_val() -> None ``` Rewrite `@w:val` from the six named attributes. Word reads the named attributes, but some older consumers read only the bitmask, so the two are kept in step rather than letting `@w:val` go stale. Source code in `src/docx/oxml/table.py` ``` def update_val(self) -> None: """Rewrite `@w:val` from the six named attributes. Word reads the named attributes, but some older consumers read only the bitmask, so the two are kept in step rather than letting `@w:val` go stale. """ bits = 0 for name, bit in self._BITS.items(): if getattr(self, name): bits |= bit self.val = bits ``` ### CT_Tc Bases: `BaseOxmlElement` `w:tc` table cell element. #### bottom ``` bottom: int ``` The row index that marks the bottom extent of the vertical span of this cell. This is one greater than the index of the bottom-most row of the span, similar to how a slice of the cell's rows would be specified. The span is measured by following continuation cells downward, without requiring this cell to carry `w:vMerge` of "restart". That is what the schema calls for, but a merge whose origin cell simply omits `w:vMerge` is common from other generators and renders as a merge in Word. #### grid_offset ``` grid_offset: int ``` Starting offset of `tc` in the layout-grid columns of its table. A cell in the leftmost grid-column has offset 0. #### grid_span ``` grid_span: int ``` The integer number of columns this cell spans. Determined by ./w:tcPr/w:gridSpan/@val, it defaults to 1. #### inner_content_elements ``` inner_content_elements: list[CT_P | CT_Tbl] ``` Generate all `w:p` and `w:tbl` elements in this table cell. Elements appear in document order. Content inside a `w:sdt` (content control) wrapper is included; content shaded by nesting in a `w:ins` or other wrapper is not. #### left ``` left: int ``` The grid column index at which this `` element appears. #### right ``` right: int ``` The grid column index that marks the right-side extent of the horizontal span of this cell. This is one greater than the index of the right-most column of the span, similar to how a slice of the cell's columns would be specified. #### top ``` top: int ``` The top-most row index in the vertical span of this cell. #### top_tc ``` top_tc: CT_Tc ``` The `w:tc` element holding the content of this cell's vertical span. This is this element itself unless it is a continuation cell (`w:vMerge` of "continue"), in which case it is the cell the span starts at. #### vMerge ``` vMerge: str | None ``` Value of ./w:tcPr/w:vMerge/@val, `None` if w:vMerge is not present. #### width ``` width: Length | None ``` EMU length represented in `./w:tcPr/w:tcW` or `None` if not present. #### clear_content ``` clear_content() ``` Remove all content elements, preserving `w:tcPr` element if present. Note that this leaves the `w:tc` element in an invalid state because it doesn't contain at least one block-level element. It's up to the caller to add a `w:p`child element as the last content element. Source code in `src/docx/oxml/table.py` ``` def clear_content(self): """Remove all content elements, preserving `w:tcPr` element if present. Note that this leaves the `w:tc` element in an invalid state because it doesn't contain at least one block-level element. It's up to the caller to add a `w:p`child element as the last content element. """ # -- remove all cell inner-content except a `w:tcPr` when present. -- for e in self.xpath("./*[not(self::w:tcPr)]"): self.remove(e) ``` #### iter_block_items ``` iter_block_items() ``` Generate a reference to each of the block-level content elements in this cell, in the order they appear. Source code in `src/docx/oxml/table.py` ``` def iter_block_items(self): """Generate a reference to each of the block-level content elements in this cell, in the order they appear.""" block_item_tags = (qn("w:p"), qn("w:tbl"), qn("w:sdt")) for child in self: if child.tag in block_item_tags: yield child ``` #### merge ``` merge(other_tc: CT_Tc) -> CT_Tc ``` Return top-left `w:tc` element of a new span. Span is formed by merging the rectangular region defined by using this tc element and `other_tc` as diagonal corners. Source code in `src/docx/oxml/table.py` ``` def merge(self, other_tc: CT_Tc) -> CT_Tc: """Return top-left `w:tc` element of a new span. Span is formed by merging the rectangular region defined by using this tc element and `other_tc` as diagonal corners. """ top, left, height, width = self._span_dimensions(other_tc) top_tc = self._tbl.tr_lst[top].tc_at_grid_offset(left) top_tc._grow_to(width, height) return top_tc ``` #### new ``` new() -> CT_Tc ``` A new `w:tc` element, containing an empty paragraph as the required EG_BlockLevelElt. Source code in `src/docx/oxml/table.py` ``` @classmethod def new(cls) -> CT_Tc: """A new `w:tc` element, containing an empty paragraph as the required EG_BlockLevelElt.""" return cast(CT_Tc, parse_xml("" % nsdecls("w"))) ``` ### CT_TcPr Bases: `BaseOxmlElement` `` element, defining table cell properties. #### textDirection_val ``` textDirection_val: WD_TEXT_DIRECTION | None ``` Value of `./w:textDirection/@w:val`, or `None` if the element is absent. #### grid_span ``` grid_span: int ``` The integer number of columns this cell spans. Determined by ./w:gridSpan/@val, it defaults to 1. #### vAlign_val ``` vAlign_val ``` Value of `w:val` attribute on `w:vAlign` child. Value is `None` if `w:vAlign` child is not present. The `w:val` attribute on `w:vAlign` is required. #### vMerge_val ``` vMerge_val ``` The value of the ./w:vMerge/@val attribute, or `None` if the w:vMerge element is not present. #### width ``` width: Length | None ``` EMU length in `./w:tcW` or `None` if not present or its type is not 'dxa'. ### CT_TrPr Bases: `BaseOxmlElement` `` element, defining table row properties. #### alignment ``` alignment: WD_TABLE_ALIGNMENT | None ``` Value of `./w:jc/@w:val`, or `None` if the element is absent. #### cantSplit_val ``` cantSplit_val: bool | None ``` Value of `./w:cantSplit/@w:val`, or `None` if the element is absent. #### hidden_val ``` hidden_val: bool | None ``` Value of `./w:hidden/@w:val`, or `None` if the element is absent. #### tblHeader_val ``` tblHeader_val: bool | None ``` Value of `./w:tblHeader/@w:val`, or `None` if the element is absent. #### cell_spacing ``` cell_spacing: Length | None ``` Value of `./w:tblCellSpacing`, or `None` if the element is absent. #### width_after ``` width_after: Length | None ``` Value of `./w:wAfter`, or `None` if the element is absent. #### width_before ``` width_before: Length | None ``` Value of `./w:wBefore`, or `None` if the element is absent. #### grid_after ``` grid_after: int ``` The number of unpopulated layout-grid cells at the end of this row. #### grid_before ``` grid_before: int ``` The number of unpopulated layout-grid cells at the start of this row. #### trHeight_hRule ``` trHeight_hRule: WD_ROW_HEIGHT_RULE | None ``` Return the value of `w:trHeight@w:hRule`, or `None` if not present. #### trHeight_val ``` trHeight_val ``` Return the value of `w:trHeight@w:val`, or `None` if not present. ### CT_VerticalJc Bases: `BaseOxmlElement` `w:vAlign` element, specifying vertical alignment of cell. ### CT_VMerge Bases: `BaseOxmlElement` `` element, specifying vertical merging behavior of a cell. ## theme Custom element classes for the theme part, `word/theme/theme1.xml`. Only the two subtrees that a word-processing document actually resolves against are modelled: `a:fontScheme`, which is where a theme typeface such as `minorHAnsi` turns into a real font name, and `a:clrScheme`, which is where a theme colour turns into an RGB value. `a:fmtScheme` — the fill, line and effect matrices — is a drawing-formatting model of its own and is left as opaque XML. ### CT_TextFont Bases: `BaseOxmlElement` `a:latin`, `a:ea` and `a:cs`, each naming one typeface of a font collection. ### CT_FontCollection Bases: `BaseOxmlElement` `a:majorFont` or `a:minorFont`, the typefaces of one half of the font scheme. `a:latin` is the one a `w:rFonts/@w:asciiTheme` of `majorHAnsi` or `minorHAnsi` resolves to; `a:ea` and `a:cs` serve the East Asian and complex-script slots. #### typeface_for ``` typeface_for(script: str) -> str | None ``` The typeface for `script`, one of `"latin"`, `"ea"` or `"cs"`. `None` when the slot carries the empty typeface Word writes to mean "no override", which is what `a:ea` and `a:cs` hold in the default Office theme. Source code in `src/docx/oxml/theme.py` ``` def typeface_for(self, script: str) -> str | None: """The typeface for `script`, one of `"latin"`, `"ea"` or `"cs"`. |None| when the slot carries the empty typeface Word writes to mean "no override", which is what `a:ea` and `a:cs` hold in the default Office theme. """ textFont = getattr(self, script) return textFont.typeface or None ``` ### CT_FontScheme Bases: `BaseOxmlElement` `a:fontScheme`, the major and minor font collections of a theme. ### CT_SRgbColor Bases: `BaseOxmlElement` `a:srgbClr`, a colour given as an explicit RGB value. ### CT_SystemColor Bases: `BaseOxmlElement` `a:sysClr`, a colour taken from the operating system's palette. `@lastClr` is the RGB value the producing application last resolved it to, which is the only concrete value available to a consumer that is not the operating system in question. ### CT_ThemeColor Bases: `BaseOxmlElement` One slot of `a:clrScheme`, e.g. `a:accent1`. The colour itself is one of several child element types; only the two Word writes for a theme are modelled. #### rgb ``` rgb: RGBColor | str | None ``` The RGB value of this colour slot, or `None` when there is none to give. A system colour reports the `@lastClr` the producing application resolved it to; that is the closest thing to a concrete value a consumer outside that operating system can have. ### CT_ColorScheme Bases: `BaseOxmlElement` `a:clrScheme`, the twelve theme colours. #### color ``` color(slot: str) -> CT_ThemeColor | None ``` The `a:{slot}` child, or `None` when the scheme does not define it. Source code in `src/docx/oxml/theme.py` ``` def color(self, slot: str) -> CT_ThemeColor | None: """The `a:{slot}` child, or |None| when the scheme does not define it.""" from docx.oxml.ns import qn return self.find(qn("a:%s" % slot)) # pyright: ignore[reportReturnType] ``` ### CT_BaseStyles Bases: `BaseOxmlElement` `a:themeElements`, the part of a theme that documents resolve against. ### CT_OfficeStyleSheet Bases: `BaseOxmlElement` `a:theme`, the root element of a theme part. ## xmlchemy Enabling declarative definition of lxml custom element classes. ### XmlString Bases: `str` Provides string comparison override suitable for serialized XML that is useful for tests. ### MetaOxmlElement ``` MetaOxmlElement( clsname: str, bases: tuple[type, ...], namespace: dict[str, Any], ) ``` Bases: `type` Metaclass for BaseOxmlElement. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__(cls, clsname: str, bases: tuple[type, ...], namespace: dict[str, Any]): dispatchable = ( OneAndOnlyOne, OneOrMore, OptionalAttribute, RequiredAttribute, ZeroOrMore, ZeroOrOne, ZeroOrOneChoice, ) for key, value in namespace.items(): if isinstance(value, dispatchable): value.populate_class_members(cls, key) ``` ### BaseAttribute ``` BaseAttribute( attr_name: str, simple_type: Type[BaseXmlEnum] | Type[BaseSimpleType], ) ``` Base class for OptionalAttribute and RequiredAttribute. Provides common methods. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__(self, attr_name: str, simple_type: Type[BaseXmlEnum] | Type[BaseSimpleType]): super(BaseAttribute, self).__init__() self._attr_name = attr_name self._simple_type = simple_type ``` #### populate_class_members ``` populate_class_members( element_cls: MetaOxmlElement, prop_name: str ) -> None ``` Add the appropriate methods to `element_cls`. Source code in `src/docx/oxml/xmlchemy.py` ``` def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: """Add the appropriate methods to `element_cls`.""" self._element_cls = element_cls self._prop_name = prop_name self._add_attr_property() ``` ### OptionalAttribute ``` OptionalAttribute( attr_name: str, simple_type: Type[BaseXmlEnum] | Type[BaseSimpleType], default: BaseXmlEnum | BaseSimpleType | str | bool | None = None, ) ``` Bases: `BaseAttribute` Defines an optional attribute on a custom element class. An optional attribute returns a default value when not present for reading. When assigned `None`, the attribute is removed, but still returns the default value when one is specified. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__( self, attr_name: str, simple_type: Type[BaseXmlEnum] | Type[BaseSimpleType], default: BaseXmlEnum | BaseSimpleType | str | bool | None = None, ): super(OptionalAttribute, self).__init__(attr_name, simple_type) self._default = default ``` ### RequiredAttribute ``` RequiredAttribute( attr_name: str, simple_type: Type[BaseXmlEnum] | Type[BaseSimpleType], ) ``` Bases: `BaseAttribute` Defines a required attribute on a custom element class. A required attribute is assumed to be present for reading, so does not have a default value; its actual value is always used. If missing on read, an InvalidXmlError is raised. It also does not remove the attribute if `None` is assigned. Assigning `None` raises `TypeError` or `ValueError`, depending on the simple type of the attribute. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__(self, attr_name: str, simple_type: Type[BaseXmlEnum] | Type[BaseSimpleType]): super(BaseAttribute, self).__init__() self._attr_name = attr_name self._simple_type = simple_type ``` ### \_BaseChildElement ``` _BaseChildElement( nsptagname: str, successors: tuple[str, ...] = () ) ``` Base class for the child-element classes. The child-element sub-classes correspond to varying cardinalities, such as ZeroOrOne and ZeroOrMore. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__(self, nsptagname: str, successors: tuple[str, ...] = ()): super(_BaseChildElement, self).__init__() self._nsptagname = nsptagname self._successors = successors ``` #### populate_class_members ``` populate_class_members( element_cls: MetaOxmlElement, prop_name: str ) -> None ``` Baseline behavior for adding the appropriate methods to `element_cls`. Source code in `src/docx/oxml/xmlchemy.py` ``` def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: """Baseline behavior for adding the appropriate methods to `element_cls`.""" self._element_cls = element_cls self._prop_name = prop_name ``` ### Choice ``` Choice(nsptagname: str, successors: tuple[str, ...] = ()) ``` Bases: `_BaseChildElement` Defines a child element belonging to a group, only one of which may appear as a child. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__(self, nsptagname: str, successors: tuple[str, ...] = ()): super(_BaseChildElement, self).__init__() self._nsptagname = nsptagname self._successors = successors ``` #### populate_class_members ``` populate_class_members( element_cls: MetaOxmlElement, group_prop_name: str, successors: tuple[str, ...], ) -> None ``` Add the appropriate methods to `element_cls`. Source code in `src/docx/oxml/xmlchemy.py` ``` def populate_class_members( # pyright: ignore[reportIncompatibleMethodOverride] self, element_cls: MetaOxmlElement, group_prop_name: str, successors: tuple[str, ...], ) -> None: """Add the appropriate methods to `element_cls`.""" self._element_cls = element_cls self._group_prop_name = group_prop_name self._successors = successors self._add_getter() self._add_creator() self._add_inserter() self._add_adder() self._add_get_or_change_to_method() ``` ### OneAndOnlyOne ``` OneAndOnlyOne(nsptagname: str) ``` Bases: `_BaseChildElement` Defines a required child element for MetaOxmlElement. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__(self, nsptagname: str): super(OneAndOnlyOne, self).__init__(nsptagname, ()) ``` #### populate_class_members ``` populate_class_members( element_cls: MetaOxmlElement, prop_name: str ) -> None ``` Add the appropriate methods to `element_cls`. Source code in `src/docx/oxml/xmlchemy.py` ``` def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: """Add the appropriate methods to `element_cls`.""" super(OneAndOnlyOne, self).populate_class_members(element_cls, prop_name) self._add_getter() ``` ### OneOrMore ``` OneOrMore( nsptagname: str, successors: tuple[str, ...] = () ) ``` Bases: `_BaseChildElement` Defines a repeating child element for MetaOxmlElement that must appear at least once. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__(self, nsptagname: str, successors: tuple[str, ...] = ()): super(_BaseChildElement, self).__init__() self._nsptagname = nsptagname self._successors = successors ``` #### populate_class_members ``` populate_class_members( element_cls: MetaOxmlElement, prop_name: str ) -> None ``` Add the appropriate methods to `element_cls`. Source code in `src/docx/oxml/xmlchemy.py` ``` def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: """Add the appropriate methods to `element_cls`.""" super(OneOrMore, self).populate_class_members(element_cls, prop_name) self._add_list_getter() self._add_creator() self._add_inserter() self._add_adder() self._add_public_adder() delattr(element_cls, prop_name) ``` ### ZeroOrMore ``` ZeroOrMore( nsptagname: str, successors: tuple[str, ...] = () ) ``` Bases: `_BaseChildElement` Defines an optional repeating child element for MetaOxmlElement. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__(self, nsptagname: str, successors: tuple[str, ...] = ()): super(_BaseChildElement, self).__init__() self._nsptagname = nsptagname self._successors = successors ``` #### populate_class_members ``` populate_class_members( element_cls: MetaOxmlElement, prop_name: str ) -> None ``` Add the appropriate methods to `element_cls`. Source code in `src/docx/oxml/xmlchemy.py` ``` def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: """Add the appropriate methods to `element_cls`.""" super(ZeroOrMore, self).populate_class_members(element_cls, prop_name) self._add_list_getter() self._add_creator() self._add_inserter() self._add_adder() self._add_public_adder() delattr(element_cls, prop_name) ``` ### ZeroOrOne ``` ZeroOrOne( nsptagname: str, successors: tuple[str, ...] = () ) ``` Bases: `_BaseChildElement` Defines an optional child element for MetaOxmlElement. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__(self, nsptagname: str, successors: tuple[str, ...] = ()): super(_BaseChildElement, self).__init__() self._nsptagname = nsptagname self._successors = successors ``` #### populate_class_members ``` populate_class_members( element_cls: MetaOxmlElement, prop_name: str ) -> None ``` Add the appropriate methods to `element_cls`. Source code in `src/docx/oxml/xmlchemy.py` ``` def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: """Add the appropriate methods to `element_cls`.""" super(ZeroOrOne, self).populate_class_members(element_cls, prop_name) self._add_getter() self._add_creator() self._add_inserter() self._add_adder() self._add_get_or_adder() self._add_remover() ``` ### ZeroOrOneChoice ``` ZeroOrOneChoice( choices: Sequence[Choice], successors: tuple[str, ...] = (), ) ``` Bases: `_BaseChildElement` Correspondes to an `EG_*` element group where at most one of its members may appear as a child. Source code in `src/docx/oxml/xmlchemy.py` ``` def __init__(self, choices: Sequence[Choice], successors: tuple[str, ...] = ()): self._choices = choices self._successors = successors ``` #### populate_class_members ``` populate_class_members( element_cls: MetaOxmlElement, prop_name: str ) -> None ``` Add the appropriate methods to `element_cls`. Source code in `src/docx/oxml/xmlchemy.py` ``` def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: """Add the appropriate methods to `element_cls`.""" super(ZeroOrOneChoice, self).populate_class_members(element_cls, prop_name) self._add_choice_getter() for choice in self._choices: choice.populate_class_members(element_cls, self._prop_name, self._successors) self._add_group_remover() ``` ### BaseOxmlElement Bases: `ElementBase` Effective base class for all custom element classes. Adds standardized behavior to all classes in one place. #### xml ``` xml: str ``` XML string for this element, suitable for testing purposes. Pretty printed for readability and without an XML declaration at the top. #### first_child_found_in ``` first_child_found_in(*tagnames: str) -> _Element | None ``` First child with tag in `tagnames`, or None if not found. Source code in `src/docx/oxml/xmlchemy.py` ``` def first_child_found_in(self, *tagnames: str) -> _Element | None: """First child with tag in `tagnames`, or None if not found.""" for tagname in tagnames: child = self.find(qn(tagname)) if child is not None: return child return None ``` #### remove_all ``` remove_all(*tagnames: str) -> None ``` Remove child elements with tagname (e.g. "a:p") in `tagnames`. Source code in `src/docx/oxml/xmlchemy.py` ``` def remove_all(self, *tagnames: str) -> None: """Remove child elements with tagname (e.g. "a:p") in `tagnames`.""" for tagname in tagnames: matching = self.findall(qn(tagname)) for child in matching: self.remove(child) ``` #### xpath ``` xpath( xpath_str: str, namespaces: Dict[str, str] | None = None, **variables: Any, ) -> Any ``` Override of `lxml` \_Element.xpath() method. Provides standard Open XML namespace mapping (`nsmap`) in centralized location. `namespaces` adds prefixes not in the standard mapping, which is needed to query elements from vendor or custom namespaces. Entries override the standard mapping where the prefixes collide. `variables` binds values to XPath variables, avoiding unsafe string interpolation for user-supplied values. Source code in `src/docx/oxml/xmlchemy.py` ``` def xpath( # pyright: ignore[reportIncompatibleMethodOverride] self, xpath_str: str, namespaces: Dict[str, str] | None = None, **variables: Any, ) -> Any: """Override of `lxml` _Element.xpath() method. Provides standard Open XML namespace mapping (`nsmap`) in centralized location. `namespaces` adds prefixes not in the standard mapping, which is needed to query elements from vendor or custom namespaces. Entries override the standard mapping where the prefixes collide. `variables` binds values to XPath variables, avoiding unsafe string interpolation for user-supplied values. """ namespace_map = nsmap if namespaces is None else {**nsmap, **namespaces} return super().xpath(xpath_str, namespaces=namespace_map, **variables) ``` ### serialize_for_reading ``` serialize_for_reading(element: ElementBase) ``` Serialize `element` to human-readable XML suitable for tests. No XML declaration. Source code in `src/docx/oxml/xmlchemy.py` ``` def serialize_for_reading(element: ElementBase): """Serialize `element` to human-readable XML suitable for tests. No XML declaration. """ xml = etree.tostring(element, encoding="unicode", pretty_print=True) return XmlString(xml) ``` ## text ## font Custom element classes related to run properties (font). ### CT_Color Bases: `BaseOxmlElement` `w:color` element, specifying the color of a font and perhaps other objects. ### CT_Fonts Bases: `BaseOxmlElement` `` element. Specifies typeface name for the various language types. The four independent slots — `w:ascii`, `w:hAnsi`, `w:eastAsia` and `w:cs` — are chosen between per character by Word, according to the script the character belongs to. ### CT_Highlight Bases: `BaseOxmlElement` `w:highlight` element, specifying font highlighting/background color. ### CT_Shd Bases: `BaseOxmlElement` `w:shd` element, specifying the shading (background fill) behind content. One class serves `w:rPr`, `w:pPr`, `w:tcPr` and `w:tblPr`; the element is identical in all four. `w:val` is the required attribute in the schema, naming the pattern drawn over the background. It is modelled as optional so a `w:shd` written without it — which this library itself did before 2.0.0 — reads as `None` rather than raising. Everything written from here carries an explicit `w:val`. It is deliberately not given a descriptor `default`; OptionalAttribute *removes* an attribute assigned its default value, which would drop the `w:val` this class exists to start writing. ### CT_TextScale Bases: `BaseOxmlElement` `w:w` element, specifying horizontal character scaling as a percentage. ### CT_HpsMeasure Bases: `BaseOxmlElement` Used for `` element and others, specifying font size in half-points. ### CT_RPr Bases: `BaseOxmlElement` `` element, containing the properties for a run. #### highlight_val ``` highlight_val: WD_COLOR_INDEX | None ``` Value of `./w:highlight/@val`. Specifies font's highlight color, or `None` if the text is not highlighted. #### rFonts_ascii ``` rFonts_ascii: str | None ``` The value of `w:rFonts/@w:ascii` or `None` if not present. Represents the assigned typeface name. The rFonts element also specifies other special-case typeface names; this method handles the case where just the common name is required. #### rFonts_hAnsi ``` rFonts_hAnsi: str | None ``` The value of `w:rFonts/@w:hAnsi` or `None` if not present. #### rFonts_eastAsia ``` rFonts_eastAsia: str | None ``` The value of `w:rFonts/@w:eastAsia` or `None` if not present. The typeface Word uses for East Asian characters in the run. #### rFonts_cs ``` rFonts_cs: str | None ``` The value of `w:rFonts/@w:cs` or `None` if not present. The typeface Word uses for complex-script characters in the run. #### rFonts_hint ``` rFonts_hint: WD_FONT_HINT | None ``` The value of `w:rFonts/@w:hint` or `None` if not present. #### rFonts_asciiTheme ``` rFonts_asciiTheme: str | None ``` The value of `w:rFonts/@w:asciiTheme` or `None` if not present. Names a theme typeface slot, like "minorHAnsi", resolved against the theme part rather than naming a font directly. #### rFonts_hAnsiTheme ``` rFonts_hAnsiTheme: str | None ``` The value of `w:rFonts/@w:hAnsiTheme` or `None` if not present. #### shd_fill ``` shd_fill: RGBColor | str | None ``` Value of `./w:shd/@w:fill`, or `None` when there is none. `None` both when there is no `w:shd` at all and when it carries a pattern but no fill, which is valid — `` for instance. #### shd_val ``` shd_val: WD_SHADING_PATTERN | None ``` The `w:shd/@w:val` shading pattern, or `None` when no shading is applied. #### shd_color ``` shd_color: RGBColor | str | None ``` Value of `./w:shd/@w:color`, the pattern foreground, or `None`. #### w_val ``` w_val: int | None ``` Value of `./w:w/@w:val`, the character scale percentage. `None` when no explicit scaling is applied and the value is inherited. #### style ``` style: str | None ``` String in `./w:rStyle/@val`, or None if `w:rStyle` is not present. #### subscript ``` subscript: bool | None ``` `True` if `./w:vertAlign/@w:val` is "subscript". `False` if `w:vertAlign/@w:val` contains any other value. `None` if `w:vertAlign` is not present. #### superscript ``` superscript: bool | None ``` `True` if `w:vertAlign/@w:val` is 'superscript'. `False` if `w:vertAlign/@w:val` contains any other value. `None` if `w:vertAlign` is not present. #### sz_val ``` sz_val: Length | None ``` The value of `w:sz/@w:val` or `None` if not present. #### szCs_val ``` szCs_val: Length | None ``` The value of `w:szCs/@w:val` or `None` if not present. This is the font size applied to complex-script text, which Word tracks separately from `w:sz`. #### u_val ``` u_val: WD_UNDERLINE | None ``` Value of `w:u/@val`, or None if not present. Values `WD_UNDERLINE.SINGLE` and `WD_UNDERLINE.NONE` are mapped to `True` and `False` respectively. ### CT_Underline Bases: `BaseOxmlElement` `` element, specifying the underlining style for a run. ### CT_VerticalAlignRun Bases: `BaseOxmlElement` `` element, specifying subscript or superscript. ## form Custom element classes for the legacy form-field elements. A legacy form field — FORMTEXT, FORMCHECKBOX or FORMDROPDOWN — is a complex field spread across sibling runs: ``` FORMTEXT the current value ``` The properties of the field live in `w:ffData` on the "begin" `w:fldChar`; the value of a text field is the run content between "separate" and "end". Only the container elements get element classes here. The leaf children of `w:ffData` and its type-specific children are read and written through their `w:val` attribute instead, because their tag names are reused elsewhere in the schema with other types — `w:name` is a style name, `w:type` is a section-break type, and `w:default` is three different types depending on which of `w:textInput`, `w:checkBox` and `w:ddList` it appears in. lxml resolves an element class by tag name alone, so registering any of them would silently change the type of an unrelated element. ### CT_FFCheckBox Bases: `BaseOxmlElement` `w:checkBox` element, the check-box specifics of a form field. #### checked ``` checked: bool | None ``` Whether the box is currently ticked, or `None` when unspecified. #### default ``` default: bool | None ``` Whether the box starts out ticked, or `None` when unspecified. ### CT_FFDDList Bases: `BaseOxmlElement` `w:ddList` element, the drop-down specifics of a form field. #### default ``` default: int | None ``` Index of the entry selected initially, or `None` when unspecified. #### listEntry_vals ``` listEntry_vals: List[str] ``` The `w:val` of each `w:listEntry` child, in document order. A `w:listEntry` without a `w:val` contributes an empty string, which is what Word shows for it. #### result ``` result: int | None ``` Index of the entry currently selected, or `None` when unspecified. ### CT_FFTextInput Bases: `BaseOxmlElement` `w:textInput` element, the text-input specifics of a form field. #### default ``` default: str | None ``` The text the field starts out holding, or `None` when unspecified. #### format ``` format: str | None ``` Word's formatting string for the value, e.g. `"UPPERCASE"`, or `None`. #### maxLength ``` maxLength: int | None ``` The most characters the field accepts, or `None` when unlimited. #### type ``` type: WD_TEXT_FORM_FIELD_TYPE | None ``` Member of WdTextFormFieldType, or `None` when unspecified. Word treats an unspecified type as `REGULAR`. ### CT_FFData Bases: `BaseOxmlElement` `w:ffData` element, the properties of a legacy form field. Its schema type is an unbounded `xsd:choice`, not a sequence, so its children have no required order and no `successors` bookkeeping applies. #### calcOnExit ``` calcOnExit: bool | None ``` Whether Word recalculates fields when this one is left, or `None`. #### enabled ``` enabled: bool | None ``` Whether the field can be edited, or `None` when unspecified. Word treats an unspecified value as enabled. #### helpText ``` helpText: str | None ``` The text Word shows when F1 is pressed in the field, or `None`. #### name ``` name: str | None ``` The bookmark name of the field, or `None` when it has none. #### statusText ``` statusText: str | None ``` The text Word shows in the status bar for the field, or `None`. ### CT_FldChar Bases: `BaseOxmlElement` `w:fldChar` element, a field-character marking a boundary of a complex field. #### r ``` r: _Element | None ``` The `w:r` element this field-character belongs to, or `None`. A `w:fldChar` is always a child of a run in a valid document, but a caller can detach one. ### CT_SimpleField Bases: `BaseOxmlElement` `w:fldSimple` element, a field whose instruction and result are one element. The instruction is an attribute and the cached result is the element's content, so unlike a complex field this is self-contained. Word writes a legacy form field as a complex field rather than a simple one, and `w:ffData` is not allowed here. #### result_text ``` result_text: str ``` The result text of this field, as Word last rendered it. #### text ``` text: str ``` The text this field displays, which is its cached result. ## hyperlink Custom element classes related to hyperlinks (CT_Hyperlink). ### CT_Hyperlink Bases: `BaseOxmlElement` `` element, containing the text and address for a hyperlink. #### lastRenderedPageBreaks ``` lastRenderedPageBreaks: List[CT_LastRenderedPageBreak] ``` All `w:lastRenderedPageBreak` descendants of this hyperlink. #### text ``` text: str ``` The textual content of this hyperlink. `CT_Hyperlink` stores the hyperlink-text as one or more `w:r` children, which may be wrapped in a content control or a revision mark; those are looked through the same way they are in a paragraph, so link text that Word marked as inserted is not silently dropped. ## isolate Run-splitting primitives and the cross-run text replacement built on them. Word splits a paragraph into runs for reasons that have nothing to do with formatting — spell-check state, language tagging, revision marks, the rsid bookkeeping it uses to track editing sessions. A string a reader sees as one word is routinely three runs, so anything that searches or edits paragraph text one run at a time misses most matches. The primitive here is isolate_range: given a character range measured against `CT_P.text`, split the runs covering it so that the range is covered by whole runs and nothing else, with each original run's `w:rPr` carried onto the pieces it was divided into. Everything else — replacement, and reformatting a range — builds on that. Offsets are measured in the same character space as `CT_P.text`, so `w:tab` counts as one character and a text-wrapping `w:br` as one newline. A `w:instrText` contributes nothing: it holds a field instruction rather than document text, and splitting one corrupts the field. ### \_Atom Bases: `NamedTuple` One text-bearing run child, located in the paragraph's character space. #### is_divisible ``` is_divisible: bool ``` True when this atom's text can be cut at an interior offset. Only `w:t` holds a string of arbitrary length. Every other text-bearing child maps to a fixed one-character string — a tab is one "\\t" — so a boundary can fall on either side of it but never inside it. ### iter_runs ``` iter_runs(element: BaseOxmlElement) -> Iterator[CT_R] ``` Generate each `w:r` contributing text to `element`, in document order. A `w:hyperlink` is descended into, since its runs are part of the paragraph's text, and a `w:sdt` is looked through the same way iter_run_content does. Source code in `src/docx/oxml/text/isolate.py` ``` def iter_runs(element: BaseOxmlElement) -> Iterator[CT_R]: """Generate each `w:r` contributing text to `element`, in document order. A `w:hyperlink` is descended into, since its runs are part of the paragraph's text, and a `w:sdt` is looked through the same way :func:`iter_run_content` does. """ for item in iter_run_content(element): if item.tag == qn("w:r"): yield cast("CT_R", item) else: # -- a `w:hyperlink`, which holds runs of its own -- yield from iter_runs(item) ``` ### isolate_range ``` isolate_range(p: CT_P, start: int, end: int) -> List[CT_R] ``` Split the runs of `p` so `[start, end)` is covered by whole runs, and return them. Each returned run lies entirely within the range, and together they cover it. The formatting of every original run is preserved on each piece it was divided into. An empty list is returned for an empty range, and for a range beyond the end of the paragraph's text. Raises `ValueError` for a reversed or negative range. Source code in `src/docx/oxml/text/isolate.py` ``` def isolate_range(p: CT_P, start: int, end: int) -> List[CT_R]: """Split the runs of `p` so `[start, end)` is covered by whole runs, and return them. Each returned run lies entirely within the range, and together they cover it. The formatting of every original run is preserved on each piece it was divided into. An empty list is returned for an empty range, and for a range beyond the end of the paragraph's text. Raises |ValueError| for a reversed or negative range. """ if start < 0 or end < start: raise ValueError(f"invalid character range ({start}, {end})") if start == end: return [] _split_at(p, start) _split_at(p, end) # -- a run is in range when its atoms are; a run holding no text at all (an image, # -- a field character) is not part of the matched text and is left alone -- runs: List[CT_R] = [] for atom in _iter_atoms(p): if start <= atom.start and atom.end <= end and atom.r not in runs: runs.append(atom.r) return runs ``` ### replace_range ``` replace_range( p: CT_P, start: int, end: int, text: str ) -> None ``` Replace the characters of `p` in `[start, end)` with `text`. The replacement takes the formatting of the run holding the first replaced character, which is what Word's own Find and Replace does and what callers expect. When the range spans several runs the remaining matched text is removed from each of them and their formatting goes with it; the runs themselves are left in place, so a hyperlink, bookmark or field partly covered by the range keeps its structure. Source code in `src/docx/oxml/text/isolate.py` ``` def replace_range(p: CT_P, start: int, end: int, text: str) -> None: """Replace the characters of `p` in `[start, end)` with `text`. The replacement takes the formatting of the run holding the first replaced character, which is what Word's own Find and Replace does and what callers expect. When the range spans several runs the remaining matched text is removed from each of them and their formatting goes with it; the runs themselves are left in place, so a hyperlink, bookmark or field partly covered by the range keeps its structure. """ if start < 0 or end < start: raise ValueError(f"invalid character range ({start}, {end})") _divide_at(p, start) _divide_at(p, end) atoms = [a for a in _iter_atoms(p) if start <= a.start and a.end <= end and a.text] new_elements = _content_elements_for(text) if atoms: anchor = atoms[0].element for element in new_elements: anchor.addprevious(element) for atom in atoms: parent = atom.element.getparent() if parent is not None: parent.remove(atom.element) _clear_placeholder(atoms[0].r) return # -- an empty range: there is nothing to remove, only a position to insert at -- if not new_elements: return _insert_at(p, start, new_elements) ``` ## pagebreak Custom element class for rendered page-break (CT_LastRenderedPageBreak). ### CT_LastRenderedPageBreak Bases: `BaseOxmlElement` `` element, indicating page break inserted by renderer. A rendered page-break is one inserted by the renderer when it runs out of room on a page. It is an empty element (no attrs or children) and is a child of CT_R, peer to CT_Text. NOTE: this complex-type name does not exist in the schema, where `w:lastRenderedPageBreak` maps to `CT_Empty`. This name was added to give it distinguished behavior. CT_Empty is used for many elements. #### following_fragment_p ``` following_fragment_p: CT_P ``` A "loose" `CT_P` containing only the paragraph content before this break. Raises `ValueError` if this `w:lastRenderedPageBreak` is not the first rendered page-break in its paragraph. The returned `CT_P` is a "clone" (deepcopy) of the `w:p` ancestor of this page-break with this `w:lastRenderedPageBreak` element and all content preceding it removed. NOTE: this `w:p` can itself contain one or more `w:renderedPageBreak` elements (when the paragraph contained more than one). While this is rare, the caller should treat this paragraph the same as other paragraphs and split it if necessary in a folloing step or recursion. #### follows_all_content ``` follows_all_content: bool ``` True when this page-break element is the last "content" in the paragraph. This is very uncommon case and may only occur in contrived or cases where the XML is edited by hand, but it is not precluded by the spec. #### precedes_all_content ``` precedes_all_content: bool ``` True when a `w:lastRenderedPageBreak` precedes all paragraph content. This is a common case; it occurs whenever the page breaks on an even paragraph boundary. #### preceding_fragment_p ``` preceding_fragment_p: CT_P ``` A "loose" `CT_P` containing only the paragraph content before this break. Raises `ValueError` if this `w:lastRenderedPageBreak` is not the first rendered paragraph in its paragraph. The returned `CT_P` is a "clone" (deepcopy) of the `w:p` ancestor of this page-break with this `w:lastRenderedPageBreak` element and all its following siblings removed. ## paragraph Custom element classes related to paragraphs (CT_P). ### CT_P Bases: `BaseOxmlElement` `` element, containing the properties and text for a paragraph. #### alignment ``` alignment: WD_PARAGRAPH_ALIGNMENT | None ``` The value of the `` grandchild element or `None` if not present. #### inner_content_elements ``` inner_content_elements: List[CT_R | CT_Hyperlink] ``` Run and hyperlink children of the `w:p` element, in document order. A run-level `w:sdt` (content control) is looked through, so the runs it wraps appear here in its place. #### lastRenderedPageBreaks ``` lastRenderedPageBreaks: List[CT_LastRenderedPageBreak] ``` All `w:lastRenderedPageBreak` descendants of this paragraph. Rendered page-breaks commonly occur in a run but can also occur in a run inside a hyperlink. This returns both. #### style ``` style: str | None ``` String contained in `w:val` attribute of `./w:pPr/w:pStyle` grandchild. `None` if not present. #### text ``` text ``` The textual content of this paragraph. Inner-content child elements like `w:r` and `w:hyperlink` are translated to their text equivalent, including those wrapped in a run-level `w:sdt`. #### assert_deletable ``` assert_deletable() -> None ``` Raise `ValueError` if removing this paragraph would invalidate the document. A `w:tc` must contain at least one block-level element, so the last paragraph of a table cell cannot simply be removed. Source code in `src/docx/oxml/text/paragraph.py` ``` def assert_deletable(self) -> None: """Raise |ValueError| if removing this paragraph would invalidate the document. A `w:tc` must contain at least one block-level element, so the last paragraph of a table cell cannot simply be removed. """ parent = self.getparent() if parent is None or parent.tag != qn("w:tc"): return block_items = parent.xpath("./w:p | ./w:tbl | ./w:sdt") if len(block_items) < 2: raise ValueError( "cannot delete the only block-level element in a table cell; a w:tc" " must contain at least one, and Word will not open a document whose" " cell is empty. Assign `cell.text = ''` to clear the cell instead." ) ``` #### add_bookmark_around_content ``` add_bookmark_around_content( id: int, name: str ) -> CT_BookmarkStart ``` Wrap the inner content of this paragraph in a bookmark named `name`. The `w:bookmarkStart` goes after `w:pPr` and before the first run; the `w:bookmarkEnd` goes at the end of the paragraph. Source code in `src/docx/oxml/text/paragraph.py` ``` def add_bookmark_around_content(self, id: int, name: str) -> CT_BookmarkStart: """Wrap the inner content of this paragraph in a bookmark named `name`. The `w:bookmarkStart` goes after `w:pPr` and before the first run; the `w:bookmarkEnd` goes at the end of the paragraph. """ bookmarkStart = cast("CT_BookmarkStart", OxmlElement("w:bookmarkStart")) bookmarkStart.id = id bookmarkStart.name = name pPr = self.pPr if pPr is None: self.insert(0, bookmarkStart) else: pPr.addnext(bookmarkStart) bookmarkEnd = cast("CT_BookmarkEnd", OxmlElement("w:bookmarkEnd")) bookmarkEnd.id = id self.append(bookmarkEnd) return bookmarkStart ``` #### add_p_before ``` add_p_before() -> CT_P ``` Return a new `` element inserted directly prior to this one. Source code in `src/docx/oxml/text/paragraph.py` ``` def add_p_before(self) -> CT_P: """Return a new `` element inserted directly prior to this one.""" new_p = cast(CT_P, OxmlElement("w:p")) self.addprevious(new_p) return new_p ``` #### clear_content ``` clear_content() ``` Remove all child elements, except the `` element if present. Source code in `src/docx/oxml/text/paragraph.py` ``` def clear_content(self): """Remove all child elements, except the `` element if present.""" for child in self.xpath("./*[not(self::w:pPr)]"): self.remove(child) ``` #### set_sectPr ``` set_sectPr(sectPr: CT_SectPr) ``` Unconditionally replace or add `sectPr` as grandchild in correct sequence. Source code in `src/docx/oxml/text/paragraph.py` ``` def set_sectPr(self, sectPr: CT_SectPr): """Unconditionally replace or add `sectPr` as grandchild in correct sequence.""" pPr = self.get_or_add_pPr() pPr._remove_sectPr() pPr._insert_sectPr(sectPr) ``` ## parfmt Custom element classes related to paragraph properties (CT_PPr). ### CT_Ind Bases: `BaseOxmlElement` `` element, specifying paragraph indentation. Two unit systems live side by side here. The `w:left`, `w:right`, `w:firstLine` and `w:hanging` attributes are absolute twips measures. The `*Chars` attributes beside them are in hundredths of a character — the unit Word's paragraph dialogue offers for a CJK document — and are *not* Length values: a character has no fixed size, so there is nothing to convert them to. `w:start` and `w:end` are the newer writing-direction synonyms of `w:left` and `w:right`. Word writes them in files saved by recent versions; a document using them reads as unindented if only `w:left` is consulted. ### CT_Jc Bases: `BaseOxmlElement` `` element, specifying paragraph justification. ### CT_TextDirection Bases: `BaseOxmlElement` `w:textDirection` element, specifying the flow direction of text. One class serves the `w:pPr`, `w:sectPr` and `w:tcPr` occurrences; the element is identical in all three. ### CT_PBdr Bases: `_CT_BordersBase` `w:pBdr` element, the set of border edges of a paragraph. Two of the six edges have no table counterpart. `w:between` is the border drawn between consecutive paragraphs that share identical border settings, rather than an edge of any one paragraph; `w:bar` is the vertical bar drawn beside the paragraph. ### CT_PPr Bases: `BaseOxmlElement` `` element, containing the properties for a paragraph. #### outlineLvl_val ``` outlineLvl_val: int | None ``` Value of `./w:outlineLvl/@w:val`, or `None` if not present. #### shd_fill ``` shd_fill: RGBColor | str | None ``` Value of `./w:shd/@w:fill`, or `None` when there is none. `None` both when there is no `w:shd` at all and when it carries a pattern but no fill, which is valid — `` for instance. #### shd_val ``` shd_val: WD_SHADING_PATTERN | None ``` The `w:shd/@w:val` shading pattern, or `None` when no shading is applied. #### shd_color ``` shd_color: RGBColor | str | None ``` Value of `./w:shd/@w:color`, the pattern foreground, or `None`. #### first_line_indent ``` first_line_indent: Length | None ``` A Length value calculated from the values of `w:ind/@w:firstLine` and `w:ind/@w:hanging`. Returns `None` if the `w:ind` child is not present. #### first_line_indent_chars ``` first_line_indent_chars: int | None ``` The first-line indent in hundredths of a character, or `None` if not present. Derived from `w:ind/@w:firstLineChars` and `@w:hangingChars` the way `.first_line_indent` is derived from their twips counterparts: a negative value means a hanging indent. #### ind_left ``` ind_left: Length | None ``` The value of `w:ind/@w:left` or `None` if not present. Falls back to `@w:start`, the writing-direction synonym Word writes in files saved by recent versions. #### ind_right ``` ind_right: Length | None ``` The value of `w:ind/@w:right` or `None` if not present. Falls back to `@w:end`, the writing-direction synonym. #### ind_left_chars ``` ind_left_chars: int | None ``` `w:ind/@w:leftChars` in hundredths of a character, or `None` if not present. Falls back to `@w:startChars`, its writing-direction synonym. #### ind_right_chars ``` ind_right_chars: int | None ``` `w:ind/@w:rightChars` in hundredths of a character, or `None` if not present. Falls back to `@w:endChars`, its writing-direction synonym. #### bidi_val ``` bidi_val: bool | None ``` Value of `./w:bidi/@w:val`, or `None` if the element is absent. #### textDirection_val ``` textDirection_val: WD_TEXT_DIRECTION | None ``` Value of `./w:textDirection/@w:val`, or `None` if the element is absent. #### jc_val ``` jc_val: WD_ALIGN_PARAGRAPH | None ``` Value of the `` child element or `None` if not present. #### keepLines_val ``` keepLines_val ``` The value of `keepLines/@val` or `None` if not present. #### keepNext_val ``` keepNext_val ``` The value of `keepNext/@val` or `None` if not present. #### pageBreakBefore_val ``` pageBreakBefore_val ``` The value of `pageBreakBefore/@val` or `None` if not present. #### spacing_after ``` spacing_after ``` The value of `w:spacing/@w:after` or `None` if not present. #### spacing_before ``` spacing_before ``` The value of `w:spacing/@w:before` or `None` if not present. #### spacing_after_lines ``` spacing_after_lines: int | None ``` `w:spacing/@w:afterLines` in hundredths of a line, or `None` if not present. #### spacing_before_lines ``` spacing_before_lines: int | None ``` `w:spacing/@w:beforeLines` in hundredths of a line, or `None` if not present. #### spacing_line ``` spacing_line ``` The value of `w:spacing/@w:line` or `None` if not present. #### spacing_lineRule ``` spacing_lineRule ``` The value of `w:spacing/@w:lineRule` as a member of the WdLineSpacing enumeration. Only the `MULTIPLE`, `EXACTLY`, and `AT_LEAST` members are used. It is the responsibility of the client to calculate the use of `SINGLE`, `DOUBLE`, and `MULTIPLE` based on the value of `w:spacing/@w:line` if that behavior is desired. #### style ``` style: str | None ``` String contained in `./w:pStyle/@val`, or None if child is not present. #### widowControl_val ``` widowControl_val ``` The value of `widowControl/@val` or `None` if not present. ### CT_Spacing Bases: `BaseOxmlElement` `` element, specifying paragraph spacing attributes such as space before and line spacing. `w:beforeLines` and `w:afterLines` are the line-relative counterparts of `w:before` and `w:after`, in hundredths of a line. Like the `*Chars` attributes on `w:ind` they are not Length values — a line has no fixed height. ### CT_TabStop Bases: `BaseOxmlElement` `` element, representing an individual tab stop. Overloaded to use for a tab-character in a run, which also uses the w:tab tag but only needs a **str** method. ### CT_TabStops Bases: `BaseOxmlElement` `` element, container for a sorted sequence of tab stops. #### insert_tab_in_order ``` insert_tab_in_order(pos, align, leader) ``` Insert a newly created `w:tab` child element in `pos` order. Source code in `src/docx/oxml/text/parfmt.py` ``` def insert_tab_in_order(self, pos, align, leader): """Insert a newly created `w:tab` child element in `pos` order.""" new_tab = self._new_tab() new_tab.pos, new_tab.val, new_tab.leader = pos, align, leader for tab in self.tab_lst: if new_tab.pos < tab.pos: tab.addprevious(new_tab) return new_tab self.append(new_tab) return new_tab ``` ## run Custom element classes related to text runs (CT_R). ### CT_R Bases: `BaseOxmlElement` `` element, containing the properties and text for a run. #### inner_content_items ``` inner_content_items: List[ str | CT_Drawing | CT_LastRenderedPageBreak ] ``` Text of run, possibly punctuated by `w:lastRenderedPageBreak` elements. #### lastRenderedPageBreaks ``` lastRenderedPageBreaks: List[CT_LastRenderedPageBreak] ``` All `w:lastRenderedPageBreaks` descendants of this run. #### style ``` style: str | None ``` String contained in `w:val` attribute of `w:rStyle` grandchild. `None` if that element is not present. #### text ``` text: str ``` The textual content of this run. Inner-content child elements like `w:tab` are translated to their text equivalent. #### add_t ``` add_t(text: str) -> CT_Text ``` Return a newly added `` element containing `text`. Source code in `src/docx/oxml/text/run.py` ``` def add_t(self, text: str) -> CT_Text: """Return a newly added `` element containing `text`.""" t = self._add_t(text=text) if len(text.strip()) < len(text): t.set(qn("xml:space"), "preserve") return t ``` #### add_drawing ``` add_drawing( inline_or_anchor: CT_Inline | CT_Anchor, ) -> CT_Drawing ``` Return newly appended `CT_Drawing` (`w:drawing`) child element. The `w:drawing` element has `inline_or_anchor` as its child. Source code in `src/docx/oxml/text/run.py` ``` def add_drawing(self, inline_or_anchor: CT_Inline | CT_Anchor) -> CT_Drawing: """Return newly appended `CT_Drawing` (`w:drawing`) child element. The `w:drawing` element has `inline_or_anchor` as its child. """ drawing = self._add_drawing() drawing.append(inline_or_anchor) return drawing ``` #### clear_content ``` clear_content() -> None ``` Remove all child elements except a `w:rPr` element if present. Source code in `src/docx/oxml/text/run.py` ``` def clear_content(self) -> None: """Remove all child elements except a `w:rPr` element if present.""" # -- remove all run inner-content except a `w:rPr` when present. -- for e in self.xpath("./*[not(self::w:rPr)]"): self.remove(e) ``` #### insert_comment_range_end_and_reference_below ``` insert_comment_range_end_and_reference_below( comment_id: int, ) -> None ``` Insert a `w:commentRangeEnd` and `w:commentReference` element after this run. The `w:commentRangeEnd` element is the immediate sibling of this `w:r` and is followed by a `w:r` containing the `w:commentReference` element. Source code in `src/docx/oxml/text/run.py` ``` def insert_comment_range_end_and_reference_below(self, comment_id: int) -> None: """Insert a `w:commentRangeEnd` and `w:commentReference` element after this run. The `w:commentRangeEnd` element is the immediate sibling of this `w:r` and is followed by a `w:r` containing the `w:commentReference` element. """ self.addnext(self._new_comment_reference_run(comment_id)) self.addnext(OxmlElement("w:commentRangeEnd", attrs={qn("w:id"): str(comment_id)})) ``` #### insert_bookmark_start_above ``` insert_bookmark_start_above( id: int, name: str ) -> CT_BookmarkStart ``` Insert a `w:bookmarkStart` for `name` immediately before this run. Source code in `src/docx/oxml/text/run.py` ``` def insert_bookmark_start_above(self, id: int, name: str) -> CT_BookmarkStart: """Insert a `w:bookmarkStart` for `name` immediately before this run.""" bookmarkStart = cast("CT_BookmarkStart", OxmlElement("w:bookmarkStart")) bookmarkStart.id = id bookmarkStart.name = name self.addprevious(bookmarkStart) return bookmarkStart ``` #### insert_bookmark_end_below ``` insert_bookmark_end_below(id: int) -> CT_BookmarkEnd ``` Insert a `w:bookmarkEnd` for `id` immediately after this run. Source code in `src/docx/oxml/text/run.py` ``` def insert_bookmark_end_below(self, id: int) -> CT_BookmarkEnd: """Insert a `w:bookmarkEnd` for `id` immediately after this run.""" bookmarkEnd = cast("CT_BookmarkEnd", OxmlElement("w:bookmarkEnd")) bookmarkEnd.id = id self.addnext(bookmarkEnd) return bookmarkEnd ``` #### insert_comment_range_start_above ``` insert_comment_range_start_above(comment_id: int) -> None ``` Insert a `w:commentRangeStart` element with `comment_id` before this run. Source code in `src/docx/oxml/text/run.py` ``` def insert_comment_range_start_above(self, comment_id: int) -> None: """Insert a `w:commentRangeStart` element with `comment_id` before this run.""" self.addprevious(OxmlElement("w:commentRangeStart", attrs={qn("w:id"): str(comment_id)})) ``` ### CT_Br Bases: `BaseOxmlElement` `` element, indicating a line, page, or column break in a run. ### CT_Cr Bases: `BaseOxmlElement` `` element, representing a carriage-return (0x0D) character within a run. ``` In Word, this represents a "soft carriage-return" in the sense that it does not end the paragraph the way pressing Enter (aka. Return) on the keyboard does. Here the text equivalent is considered to be newline (" ``` ") since in plain-text that's the closest Python equivalent. ``` NOTE: this complex-type name does not exist in the schema, where `w:tab` maps to `CT_Empty`. This name was added to give it distinguished behavior. CT_Empty is used for many elements. ``` ### CT_NoBreakHyphen Bases: `BaseOxmlElement` `` element, a hyphen ineligible for a line-wrap position. This maps to a plain-text dash ("-"). NOTE: this complex-type name does not exist in the schema, where `w:noBreakHyphen` maps to `CT_Empty`. This name was added to give it behavior distinguished from the many other elements represented in the schema by CT_Empty. ### CT_PTab Bases: `BaseOxmlElement` `` element, representing an absolute-position tab character within a run. This character advances the rendering position to the specified position regardless of any tab-stops, perhaps for layout of a table-of-contents (TOC) or similar. ### CT_Text Bases: `BaseOxmlElement` `` element, containing a sequence of characters within a run. ### \_RunContentAppender ``` _RunContentAppender(r: CT_R) ``` Translates a Python string into run content elements appended in a `w:r` element. ``` Contiguous sequences of regular characters are appended in a single `` element. Each tab character (' ') causes a `` element to be appended. Likewise a newline or carriage return character (' ``` ', ' ') causes a `` element to be appended. Source code in `src/docx/oxml/text/run.py` ``` def __init__(self, r: CT_R): self._r = r self._bfr: List[str] = [] ``` #### append_to_run_from_text ``` append_to_run_from_text(r: CT_R, text: str) ``` Append inner-content elements for `text` to `r` element. Source code in `src/docx/oxml/text/run.py` ``` @classmethod def append_to_run_from_text(cls, r: CT_R, text: str): """Append inner-content elements for `text` to `r` element.""" appender = cls(r) appender.add_text(text) ``` #### add_text ``` add_text(text: str) ``` Append inner-content elements for `text` to the `w:r` element. Source code in `src/docx/oxml/text/run.py` ``` def add_text(self, text: str): """Append inner-content elements for `text` to the `w:r` element.""" for char in text: self.add_char(char) self.flush() ``` #### add_char ``` add_char(char: str) ``` Process next character of input through finite state maching (FSM). There are two possible states, buffer pending and not pending, but those are hidden behind the `.flush()` method which must be called at the end of text to ensure any pending `` element is written. Source code in `src/docx/oxml/text/run.py` ``` def add_char(self, char: str): """Process next character of input through finite state maching (FSM). There are two possible states, buffer pending and not pending, but those are hidden behind the `.flush()` method which must be called at the end of text to ensure any pending `` element is written. """ if char == "\t": self.flush() self._r.add_tab() elif char in "\r\n": self.flush() self._r.add_br() else: self._bfr.append(char) ``` ## parts ## altchunk AltChunkPart and closely related objects. ### AltChunkPart ``` AltChunkPart( partname: PackURI, content_type: str, blob: bytes | None = None, package: Package | None = None, ) ``` Bases: `Part` An "alternative format import" part, the target of a `w:altChunk` reference. Holds an embedded document in some format other than WordprocessingML — HTML, RTF, plain text, MHTML, or another .docx — for Word to convert and splice in when it opens the file. The bytes are stored and written back unchanged; this library has no knowledge of the embedded format. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, blob: bytes | None = None, package: Package | None = None, ): super(Part, self).__init__() self._partname = partname self._content_type = content_type self._blob = blob self._package = package ``` #### new ``` new( package: OpcPackage, blob: bytes, content_type: str ) -> AltChunkPart ``` An AltChunkPart newly created from `blob` and added to `package`. Source code in `src/docx/parts/altchunk.py` ``` @classmethod def new(cls, package: OpcPackage, blob: bytes, content_type: str) -> AltChunkPart: """An |AltChunkPart| newly created from `blob` and added to `package`.""" ext = _EXT_FOR_CONTENT_TYPE.get(content_type, "bin") return cls(cls._next_partname(package, ext), content_type, blob, package) ``` #### new_from_stream ``` new_from_stream( package: OpcPackage, chunk: str | PathLike[str] | IO[bytes], content_type: str, ) -> AltChunkPart ``` An AltChunkPart newly created from `chunk` and added to `package`. `chunk` is either a path to a file (a string or `os.PathLike`) or a file-like object open for binary read. Source code in `src/docx/parts/altchunk.py` ``` @classmethod def new_from_stream( cls, package: OpcPackage, chunk: str | os.PathLike[str] | IO[bytes], content_type: str, ) -> AltChunkPart: """An |AltChunkPart| newly created from `chunk` and added to `package`. `chunk` is either a path to a file (a string or ``os.PathLike``) or a file-like object open for binary read. """ if isinstance(chunk, (str, os.PathLike)): with open(os.fspath(chunk), "rb") as f: blob = f.read() else: blob = chunk.read() return cls.new(package, blob, content_type) ``` ## comments Contains comments added to the document. ### CommentsPart ``` CommentsPart( partname: PackURI, content_type: str, element: CT_Comments, package: Package, ) ``` Bases: `StoryPart` Container part for comments added to the document. Source code in `src/docx/parts/comments.py` ``` def __init__( self, partname: PackURI, content_type: str, element: CT_Comments, package: Package ): super().__init__(partname, content_type, element, package) self._comments = element ``` #### comments ``` comments: Comments ``` A Comments proxy object for the `w:comments` root element of this part. #### default ``` default(package: Package) -> Self ``` A newly created comments part, containing a default empty `w:comments` element. Source code in `src/docx/parts/comments.py` ``` @classmethod def default(cls, package: Package) -> Self: """A newly created comments part, containing a default empty `w:comments` element.""" partname = PackURI("/word/comments.xml") content_type = CT.WML_COMMENTS element = cast("CT_Comments", parse_xml(cls._default_comments_xml())) return cls(partname, content_type, element, package) ``` ## document DocumentPart and closely related objects. ### DocumentPart ``` DocumentPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `StoryPart` Main document part of a WordprocessingML (WML) package, aka a .docx file. Acts as broker to other parts such as image, core properties, and style parts. It also acts as a convenient delegate when a mid-document object needs a service involving a remote ancestor. The `Parented.part` property inherited by many content objects provides access to this part object for that purpose. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### custom_xml_parts ``` custom_xml_parts: tuple[CustomXmlPart, ...] ``` The custom XML data store items related from this document part. In relationship-id order, which is the order Word writes them and the order the `itemN.xml` numbering follows. #### vba_project ``` vba_project: bytes | None ``` The bytes of `word/vbaProject.bin`, or `None` when there is no macro project. #### has_macros ``` has_macros: bool ``` `True` when this document carries a VBA project. #### comments ``` comments: Comments ``` Comments object providing access to the comments added to this document. #### footnotes ``` footnotes: Footnotes ``` Footnotes object providing access to the footnotes of this document. #### endnotes ``` endnotes: Endnotes ``` Endnotes object providing access to the endnotes of this document. #### has_endnotes_part ``` has_endnotes_part: bool ``` `True` when this document already has an endnotes part. The endnote counterpart of has_footnotes_part, and used the same way. #### has_footnotes_part ``` has_footnotes_part: bool ``` `True` when this document already has a footnotes part. Reading footnotes creates the part when it is absent, so code that only wants to look at footnotes that exist — a document-wide search, say — asks this first rather than adding `/word/footnotes.xml` to every document it touches. #### core_properties ``` core_properties: CoreProperties ``` A CoreProperties object providing read/write access to the core properties of this document. #### document ``` document ``` A Document object providing access to the content of this document. Raises StrictOoxmlNotSupportedError if the package is an ISO Strict document. #### has_numbering_part ``` has_numbering_part: bool ``` `True` when this document already has a numbering part. Reading numbering_part creates one when it is absent, so code that only wants to look at numbering that exists asks this first rather than adding an empty `/word/numbering.xml` to every document it touches. #### settings ``` settings: Settings ``` A Settings object providing access to the settings in the settings part of this document. #### theme ``` theme: Theme | None ``` A Theme object for this document, or `None` when it has no theme part. Unlike the styles and settings parts, a theme part is *not* created on demand. A theme is a design a document was authored against; synthesising an empty one would answer "what typeface is this actually in" with a fiction. #### styles ``` styles ``` A Styles object providing access to the styles in the styles part of this document. The collection is told which document part it belongs to, so a style taken out of it can find its own numbering definitions when copied into another document. #### add_alt_chunk_part ``` add_alt_chunk_part(blob: bytes, content_type: str) -> str ``` Return the rId of a newly-created alt-chunk part holding `blob`. Each call adds a new part; alt-chunk content is not deduplicated the way image content is, because two embedded documents with identical bytes are rare and Word rewrites them independently on import. Source code in `src/docx/parts/document.py` ``` def add_alt_chunk_part(self, blob: bytes, content_type: str) -> str: """Return the rId of a newly-created alt-chunk part holding `blob`. Each call adds a new part; alt-chunk content is not deduplicated the way image content is, because two embedded documents with identical bytes are rare and Word rewrites them independently on import. """ alt_chunk_part = AltChunkPart.new(self.package, blob, content_type) return self.relate_to(alt_chunk_part, RT.A_F_CHUNK) ``` #### add_custom_xml_part ``` add_custom_xml_part( xml: str | bytes, schema_refs: tuple[str, ...] = (), *, item_id: str | None = None, ) -> CustomXmlPart ``` Add a custom XML data store item holding `xml` and return its part. Creates the `customXml/itemN.xml` part, its `itemPropsN.xml` sidecar carrying the item GUID, and both relationships. `item_id` is that GUID; one is generated at random when it is omitted. See Document.add_custom_xml_part. Source code in `src/docx/parts/document.py` ``` def add_custom_xml_part( self, xml: str | bytes, schema_refs: tuple[str, ...] = (), *, item_id: str | None = None, ) -> CustomXmlPart: """Add a custom XML data store item holding `xml` and return its part. Creates the `customXml/itemN.xml` part, its `itemPropsN.xml` sidecar carrying the item GUID, and both relationships. `item_id` is that GUID; one is generated at random when it is omitted. See :meth:`.Document.add_custom_xml_part`. """ package = self.package assert package is not None blob = xml.encode("utf-8") if isinstance(xml, str) else xml item_partname = package.next_partname("/customXml/item%d.xml") # -- the props part takes its number from its item rather than being numbered # -- independently; Word pairs the two by number -- props_partname = PackURI(str(item_partname).replace("/item", "/itemProps")) item_part = CustomXmlPart.new(package, item_partname, parse_xml(blob)) props_part = CustomXmlPropertiesPart.new( package, props_partname, item_id if item_id is not None else "{%s}" % str(uuid.uuid4()).upper(), schema_refs, ) item_part.relate_to(props_part, RT.CUSTOM_XML_PROPS) self.relate_to(item_part, RT.CUSTOM_XML) return item_part ``` #### remove_vba_project ``` remove_vba_project() -> int ``` Remove the VBA project and its `vbaData.xml` sibling; return how many parts went. The main part's content type is switched back to the non-macro-enabled form, so the document does not claim to carry macros it no longer has — Word warns the user about those. Source code in `src/docx/parts/document.py` ``` def remove_vba_project(self) -> int: """Remove the VBA project and its `vbaData.xml` sibling; return how many parts went. The main part's content type is switched back to the non-macro-enabled form, so the document does not claim to carry macros it no longer has — Word warns the user about those. """ removed = 0 for reltype in (RT.VBA_PROJECT, RT.VBA_DATA): for rId in [rId for rId, rel in self.rels.items() if rel.reltype == reltype]: self.drop_rel(rId) removed += 1 if removed: self.content_type = _plain_content_type(self.content_type) return removed ``` #### add_footer_part ``` add_footer_part() ``` Return (footer_part, rId) pair for newly-created footer part. Source code in `src/docx/parts/document.py` ``` def add_footer_part(self): """Return (footer_part, rId) pair for newly-created footer part.""" footer_part = FooterPart.new(self.package) rId = self.relate_to(footer_part, RT.FOOTER) return footer_part, rId ``` #### add_header_part ``` add_header_part() ``` Return (header_part, rId) pair for newly-created header part. Source code in `src/docx/parts/document.py` ``` def add_header_part(self): """Return (header_part, rId) pair for newly-created header part.""" header_part = HeaderPart.new(self.package) rId = self.relate_to(header_part, RT.HEADER) return header_part, rId ``` #### drop_header_part ``` drop_header_part(rId: str) -> None ``` Remove related header part identified by `rId`. Source code in `src/docx/parts/document.py` ``` def drop_header_part(self, rId: str) -> None: """Remove related header part identified by `rId`.""" self.drop_rel(rId) ``` #### footer_part ``` footer_part(rId: str) ``` Return FooterPart related by `rId`. Source code in `src/docx/parts/document.py` ``` def footer_part(self, rId: str): """Return |FooterPart| related by `rId`.""" return self.related_parts[rId] ``` #### get_style ``` get_style( style_id: str | None, style_type: WD_STYLE_TYPE ) -> BaseStyle ``` Return the style in this document matching `style_id`. Returns the default style for `style_type` if `style_id` is `None` or does not match a defined style of `style_type`. Source code in `src/docx/parts/document.py` ``` def get_style(self, style_id: str | None, style_type: WD_STYLE_TYPE) -> BaseStyle: """Return the style in this document matching `style_id`. Returns the default style for `style_type` if `style_id` is |None| or does not match a defined style of `style_type`. """ return self.styles.get_by_id(style_id, style_type) ``` #### get_style_id ``` get_style_id(style_or_name, style_type) ``` Return the style_id (`str`) of the style of `style_type` matching `style_or_name`. Returns `None` if the style resolves to the default style for `style_type` or if `style_or_name` is itself `None`. Raises if `style_or_name` is a style of the wrong type or names a style not present in the document. Source code in `src/docx/parts/document.py` ``` def get_style_id(self, style_or_name, style_type): """Return the style_id (|str|) of the style of `style_type` matching `style_or_name`. Returns |None| if the style resolves to the default style for `style_type` or if `style_or_name` is itself |None|. Raises if `style_or_name` is a style of the wrong type or names a style not present in the document. """ return self.styles.get_style_id(style_or_name, style_type) ``` #### header_part ``` header_part(rId: str) ``` Return HeaderPart related by `rId`. Source code in `src/docx/parts/document.py` ``` def header_part(self, rId: str): """Return |HeaderPart| related by `rId`.""" return self.related_parts[rId] ``` #### floating_shapes ``` floating_shapes() ``` The FloatingShapes instance containing the anchored shapes in the document. Source code in `src/docx/parts/document.py` ``` @lazyproperty def floating_shapes(self): """The |FloatingShapes| instance containing the anchored shapes in the document.""" return FloatingShapes(self._element.body, self) ``` #### inline_shapes ``` inline_shapes() ``` The InlineShapes instance containing the inline shapes in the document. Source code in `src/docx/parts/document.py` ``` @lazyproperty def inline_shapes(self): """The |InlineShapes| instance containing the inline shapes in the document.""" return InlineShapes(self._element.body, self) ``` #### numbering_part ``` numbering_part() -> NumberingPart ``` A NumberingPart object providing access to the numbering definitions for this document. Creates an empty numbering part if one is not present. Source code in `src/docx/parts/document.py` ``` @lazyproperty def numbering_part(self) -> NumberingPart: """A |NumberingPart| object providing access to the numbering definitions for this document. Creates an empty numbering part if one is not present. """ try: return cast(NumberingPart, self.part_related_by(RT.NUMBERING)) except KeyError: numbering_part = NumberingPart.new() self.relate_to(numbering_part, RT.NUMBERING) return numbering_part ``` #### save ``` save(path_or_stream: str | PathLike[str] | IO[bytes]) ``` Save this document to `path_or_stream`, which can be either a path to a filesystem location (a string or `os.PathLike`) or a file-like object. Source code in `src/docx/parts/document.py` ``` def save(self, path_or_stream: str | os.PathLike[str] | IO[bytes]): """Save this document to `path_or_stream`, which can be either a path to a filesystem location (a string or ``os.PathLike``) or a file-like object.""" if isinstance(path_or_stream, os.PathLike): path_or_stream = os.fspath(path_or_stream) self.package.save(path_or_stream) ``` ## endnotes Contains the endnotes of the document. ### EndnotesPart ``` EndnotesPart( partname: PackURI, content_type: str, element: CT_Endnotes, package: Package, ) ``` Bases: `StoryPart` Container part for the endnotes of the document. The endnote half of FootnotesPart; `word/endnotes.xml` holds the same shape of content as `word/footnotes.xml`. Source code in `src/docx/parts/endnotes.py` ``` def __init__( self, partname: PackURI, content_type: str, element: CT_Endnotes, package: Package ): super().__init__(partname, content_type, element, package) self._endnotes = element ``` #### endnotes ``` endnotes: Endnotes ``` An Endnotes proxy for the `w:endnotes` root element of this part. #### default ``` default(package: Package) -> Self ``` A newly created endnotes part. It holds the two structural endnotes Word requires — the separator and the continuation separator, at ids -1 and 0 — and no author endnotes. Source code in `src/docx/parts/endnotes.py` ``` @classmethod def default(cls, package: Package) -> Self: """A newly created endnotes part. It holds the two structural endnotes Word requires — the separator and the continuation separator, at ids -1 and 0 — and no author endnotes. """ partname = PackURI("/word/endnotes.xml") content_type = CT.WML_ENDNOTES element = cast("CT_Endnotes", parse_xml(cls._default_endnotes_xml())) return cls(partname, content_type, element, package) ``` ## footnotes Contains the footnotes of the document. ### FootnotesPart ``` FootnotesPart( partname: PackURI, content_type: str, element: CT_Footnotes, package: Package, ) ``` Bases: `StoryPart` Container part for the footnotes of the document. Source code in `src/docx/parts/footnotes.py` ``` def __init__( self, partname: PackURI, content_type: str, element: CT_Footnotes, package: Package ): super().__init__(partname, content_type, element, package) self._footnotes = element ``` #### footnotes ``` footnotes: Footnotes ``` A Footnotes proxy for the `w:footnotes` root element of this part. #### default ``` default(package: Package) -> Self ``` A newly created footnotes part. It holds the two structural footnotes Word requires — the separator and the continuation separator, at ids -1 and 0 — and no author footnotes. Source code in `src/docx/parts/footnotes.py` ``` @classmethod def default(cls, package: Package) -> Self: """A newly created footnotes part. It holds the two structural footnotes Word requires — the separator and the continuation separator, at ids -1 and 0 — and no author footnotes. """ partname = PackURI("/word/footnotes.xml") content_type = CT.WML_FOOTNOTES element = cast("CT_Footnotes", parse_xml(cls._default_footnotes_xml())) return cls(partname, content_type, element, package) ``` ## hdrftr Header and footer part objects. ### FooterPart ``` FooterPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `StoryPart` Definition of a section footer. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### new ``` new(package: Package) ``` Return newly created footer part. Source code in `src/docx/parts/hdrftr.py` ``` @classmethod def new(cls, package: Package): """Return newly created footer part.""" partname = package.next_partname("/word/footer%d.xml") content_type = CT.WML_FOOTER element = parse_xml(cls._default_footer_xml()) return cls(partname, content_type, element, package) ``` ### HeaderPart ``` HeaderPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `StoryPart` Definition of a section header. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### new ``` new(package: Package) ``` Return newly created header part. Source code in `src/docx/parts/hdrftr.py` ``` @classmethod def new(cls, package: Package): """Return newly created header part.""" partname = package.next_partname("/word/header%d.xml") content_type = CT.WML_HEADER element = parse_xml(cls._default_header_xml()) return cls(partname, content_type, element, package) ``` ## image The proxy class for an image part, and related objects. ### ImagePart ``` ImagePart( partname: PackURI, content_type: str, blob: bytes, image: Image | None = None, ) ``` Bases: `Part` An image part. Corresponds to the target part of a relationship with type RELATIONSHIP_TYPE.IMAGE. Source code in `src/docx/parts/image.py` ``` def __init__( self, partname: PackURI, content_type: str, blob: bytes, image: Image | None = None ): super(ImagePart, self).__init__(partname, content_type, blob) self._image = image ``` #### default_cx ``` default_cx ``` Native width of this image, calculated from its width in pixels and horizontal dots per inch (dpi). #### default_cy ``` default_cy ``` Native height of this image, calculated from its height in pixels and vertical dots per inch (dpi). #### filename ``` filename ``` Filename from which this image part was originally created. A generic name, e.g. 'image.png', is substituted if no name is available, for example when the image was loaded from an unnamed stream. In that case a default extension is applied based on the detected MIME type of the image. #### sha1 ``` sha1 ``` SHA1 hash digest of the blob of this image part. #### from_image ``` from_image(image: Image, partname: PackURI) ``` Return an ImagePart instance newly created from `image` and assigned `partname`. Source code in `src/docx/parts/image.py` ``` @classmethod def from_image(cls, image: Image, partname: PackURI): """Return an |ImagePart| instance newly created from `image` and assigned `partname`.""" return ImagePart(partname, image.content_type, image.blob, image) ``` #### load ``` load( partname: PackURI, content_type: str, blob: bytes, package: OpcPackage, ) ``` Called by `docx.opc.package.PartFactory` to load an image part from a package being opened by `Document(...)` call. Source code in `src/docx/parts/image.py` ``` @classmethod def load(cls, partname: PackURI, content_type: str, blob: bytes, package: OpcPackage): """Called by ``docx.opc.package.PartFactory`` to load an image part from a package being opened by ``Document(...)`` call.""" return cls(partname, content_type, blob) ``` ## numbering NumberingPart and closely related objects. ### NumberingPart ``` NumberingPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `XmlPart` Proxy for the numbering.xml part containing numbering definitions for a document or glossary. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### new ``` new() -> NumberingPart ``` Newly created numbering part, containing only the root `` element. Source code in `src/docx/parts/numbering.py` ``` @classmethod def new(cls) -> "NumberingPart": """Newly created numbering part, containing only the root ```` element.""" raise NotImplementedError ``` #### numbering_definitions ``` numbering_definitions() ``` The \_NumberingDefinitions instance containing the numbering definitions ( element proxies) for this numbering part. Source code in `src/docx/parts/numbering.py` ``` @lazyproperty def numbering_definitions(self): """The |_NumberingDefinitions| instance containing the numbering definitions ( element proxies) for this numbering part.""" return _NumberingDefinitions(self._element) ``` ### \_NumberingDefinitions ``` _NumberingDefinitions(numbering_elm) ``` Collection of `_NumberingDefinition` instances corresponding to the `` elements in a numbering part. Source code in `src/docx/parts/numbering.py` ``` def __init__(self, numbering_elm): super(_NumberingDefinitions, self).__init__() self._numbering = numbering_elm ``` ## settings SettingsPart and closely related objects. ### SettingsPart ``` SettingsPart( partname: PackURI, content_type: str, element: CT_Settings, package: Package, ) ``` Bases: `XmlPart` Document-level settings part of a WordprocessingML (WML) package. Source code in `src/docx/parts/settings.py` ``` def __init__( self, partname: PackURI, content_type: str, element: CT_Settings, package: Package ): super().__init__(partname, content_type, element, package) self._settings = element ``` #### settings ``` settings: Settings ``` A Settings proxy object for the `w:settings` element in this part. Contains the document-level settings for this document. #### default ``` default(package: Package) ``` Return a newly created settings part, containing a default `w:settings` element tree. Source code in `src/docx/parts/settings.py` ``` @classmethod def default(cls, package: Package): """Return a newly created settings part, containing a default `w:settings` element tree.""" partname = PackURI("/word/settings.xml") content_type = CT.WML_SETTINGS element = cast("CT_Settings", parse_xml(cls._default_settings_xml())) return cls(partname, content_type, element, package) ``` ## story StoryPart and related objects. ### StoryPart ``` StoryPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `XmlPart` Base class for story parts. A story part is one that can contain textual content, such as the document-part and header or footer parts. These all share content behaviors like `.paragraphs`, `.add_paragraph()`, `.add_table()` etc. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### next_bookmark_id ``` next_bookmark_id: int ``` Next available `w:id` for a bookmark in this story. A bookmark id pairs a `w:bookmarkStart` with its `w:bookmarkEnd`, so it needs to be unique only among the bookmarks of this part; `next_id` does not see these, because it looks at unprefixed `id` attributes and a bookmark's is `w:id`. #### next_id ``` next_id: int ``` Next available positive integer id value in this story XML document. The value is determined by incrementing the maximum existing id value. Gaps in the existing id sequence are not filled. The id attribute value is unique in the document, without regard to the element type it appears on. #### document_part ``` document_part: DocumentPart ``` The DocumentPart of this package. A story part is not always the document part — a header or footnote is a story too — but the parts they share, styles and numbering among them, hang off the document part. This is how a paragraph in any story reaches them. #### get_or_add_image ``` get_or_add_image( image_descriptor: str | PathLike[str] | IO[bytes], ) -> Tuple[str, Image] ``` Return (rId, image) pair for image identified by `image_descriptor`. `rId` is the str key (often like "rId7") for the relationship between this story part and the image part, reused if already present, newly created if not. `image` is an Image instance providing access to the properties of the image, such as dimensions and image type. Source code in `src/docx/parts/story.py` ``` def get_or_add_image( self, image_descriptor: str | os.PathLike[str] | IO[bytes] ) -> Tuple[str, Image]: """Return (rId, image) pair for image identified by `image_descriptor`. `rId` is the str key (often like "rId7") for the relationship between this story part and the image part, reused if already present, newly created if not. `image` is an |Image| instance providing access to the properties of the image, such as dimensions and image type. """ package = self._package assert package is not None image_part = package.get_or_add_image_part(image_descriptor) rId = self.relate_to(image_part, RT.IMAGE) return rId, image_part.image ``` #### get_style ``` get_style( style_id: str | None, style_type: WD_STYLE_TYPE ) -> BaseStyle ``` Return the style in this document matching `style_id`. Returns the default style for `style_type` if `style_id` is `None` or does not match a defined style of `style_type`. Source code in `src/docx/parts/story.py` ``` def get_style(self, style_id: str | None, style_type: WD_STYLE_TYPE) -> BaseStyle: """Return the style in this document matching `style_id`. Returns the default style for `style_type` if `style_id` is |None| or does not match a defined style of `style_type`. """ return self._document_part.get_style(style_id, style_type) ``` #### get_style_id ``` get_style_id( style_or_name: BaseStyle | str | None, style_type: WD_STYLE_TYPE, ) -> str | None ``` Return str style_id for `style_or_name` of `style_type`. Returns `None` if the style resolves to the default style for `style_type` or if `style_or_name` is itself `None`. Raises if `style_or_name` is a style of the wrong type or names a style not present in the document. Source code in `src/docx/parts/story.py` ``` def get_style_id( self, style_or_name: BaseStyle | str | None, style_type: WD_STYLE_TYPE ) -> str | None: """Return str style_id for `style_or_name` of `style_type`. Returns |None| if the style resolves to the default style for `style_type` or if `style_or_name` is itself |None|. Raises if `style_or_name` is a style of the wrong type or names a style not present in the document. """ return self._document_part.get_style_id(style_or_name, style_type) ``` #### new_pic_inline ``` new_pic_inline( image_descriptor: str | PathLike[str] | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, description: str | None = None, title: str | None = None, svg_fallback: str | PathLike[str] | IO[bytes] | None = None, honor_exif_orientation: bool = True, ) -> CT_Inline ``` Return a newly-created `w:inline` element. The element contains the image specified by `image_descriptor` and is scaled based on the values of `width` and `height`. `description` and `title` are the alternative text of the picture. `svg_fallback` is the raster image to show in place of an SVG where the SVG cannot be rendered. `honor_exif_orientation` applies the image's EXIF `Orientation` as a rotation in the DrawingML; see Run.add_picture. Source code in `src/docx/parts/story.py` ``` def new_pic_inline( self, image_descriptor: str | os.PathLike[str] | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, description: str | None = None, title: str | None = None, svg_fallback: str | os.PathLike[str] | IO[bytes] | None = None, honor_exif_orientation: bool = True, ) -> CT_Inline: """Return a newly-created `w:inline` element. The element contains the image specified by `image_descriptor` and is scaled based on the values of `width` and `height`. `description` and `title` are the alternative text of the picture. `svg_fallback` is the raster image to show in place of an SVG where the SVG cannot be rendered. `honor_exif_orientation` applies the image's EXIF `Orientation` as a rotation in the DrawingML; see :meth:`.Run.add_picture`. """ rId, image, svg_rId = self._image_rIds(image_descriptor, svg_fallback) cx, cy = image.scaled_dimensions( width, height, honor_exif_orientation=honor_exif_orientation ) transform = image.drawingml_transform if honor_exif_orientation else (0, False) shape_id, filename = self.next_id, image.filename return CT_Inline.new_pic_inline( shape_id, rId, filename, cx, cy, description=description, title=title, svg_rId=svg_rId, transform=transform, ) ``` #### new_pic_anchor ``` new_pic_anchor( image_descriptor: str | PathLike[str] | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, pos_x: Length | int = 0, pos_y: Length | int = 0, description: str | None = None, title: str | None = None, svg_fallback: str | PathLike[str] | IO[bytes] | None = None, honor_exif_orientation: bool = True, ) -> CT_Anchor ``` Return a newly-created `wp:anchor` element for a floating picture. The arguments match new_pic_inline, with `pos_x` and `pos_y` giving the offset from the column and paragraph the shape is anchored to. Source code in `src/docx/parts/story.py` ``` def new_pic_anchor( self, image_descriptor: str | os.PathLike[str] | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, pos_x: Length | int = 0, pos_y: Length | int = 0, description: str | None = None, title: str | None = None, svg_fallback: str | os.PathLike[str] | IO[bytes] | None = None, honor_exif_orientation: bool = True, ) -> CT_Anchor: """Return a newly-created `wp:anchor` element for a floating picture. The arguments match :meth:`new_pic_inline`, with `pos_x` and `pos_y` giving the offset from the column and paragraph the shape is anchored to. """ rId, image, svg_rId = self._image_rIds(image_descriptor, svg_fallback) cx, cy = image.scaled_dimensions( width, height, honor_exif_orientation=honor_exif_orientation ) transform = image.drawingml_transform if honor_exif_orientation else (0, False) return CT_Anchor.new_pic_anchor( self.next_id, rId, image.filename, cx, cy, Emu(int(pos_x)), Emu(int(pos_y)), description=description, title=title, svg_rId=svg_rId, transform=transform, ) ``` ## styles Provides StylesPart and related objects. ### StylesPart ``` StylesPart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `XmlPart` Proxy for the styles.xml part containing style definitions for a document or glossary. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### styles ``` styles ``` The `_Styles` instance containing the styles ( element proxies) for this styles part. #### default ``` default(package: OpcPackage) -> StylesPart ``` Return a newly created styles part, containing a default set of elements. Source code in `src/docx/parts/styles.py` ``` @classmethod def default(cls, package: OpcPackage) -> StylesPart: """Return a newly created styles part, containing a default set of elements.""" partname = PackURI("/word/styles.xml") content_type = CT.WML_STYLES element = parse_xml(cls._default_styles_xml()) return cls(partname, content_type, element, package) ``` ## theme ThemePart and closely related objects. ### ThemePart ``` ThemePart( partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package, ) ``` Bases: `XmlPart` Proxy for `word/theme/theme1.xml`, the theme a document resolves against. A document that sets no explicit `w:rFonts/@w:ascii` anywhere — which is most documents produced from a Word template — has its actual typefaces here, reached through the `minorHAnsi`-style tokens in `Font.theme`. Source code in `src/docx/opc/part.py` ``` def __init__( self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package ): super(XmlPart, self).__init__(partname, content_type, package=package) self._element = element ``` #### theme ``` theme() -> Theme ``` The Theme object for this part. Source code in `src/docx/parts/theme.py` ``` @lazyproperty def theme(self) -> Theme: """The |Theme| object for this part.""" from docx.theme import Theme return Theme(self.element, self) ``` ## styles Sub-package module for docx.styles sub-package. ### BabelFish Translates special-case style names from UI name (e.g. Heading 1) to internal/styles.xml name (e.g. heading 1) and back. #### ui2internal ``` ui2internal(ui_style_name: str) -> str ``` Return the internal style name corresponding to `ui_style_name`, such as 'heading 1' for 'Heading 1'. Source code in `src/docx/styles/__init__.py` ``` @classmethod def ui2internal(cls, ui_style_name: str) -> str: """Return the internal style name corresponding to `ui_style_name`, such as 'heading 1' for 'Heading 1'.""" return cls.internal_style_names.get(ui_style_name, ui_style_name) ``` #### internal2ui ``` internal2ui(internal_style_name: str) -> str ``` Return the user interface style name corresponding to `internal_style_name`, such as 'Heading 1' for 'heading 1'. Source code in `src/docx/styles/__init__.py` ``` @classmethod def internal2ui(cls, internal_style_name: str) -> str: """Return the user interface style name corresponding to `internal_style_name`, such as 'Heading 1' for 'heading 1'.""" return cls.ui_style_names.get(internal_style_name, internal_style_name) ``` ## copy Copying a style definition from one document into another. The public entry point is Styles.copy_style_from; this module holds the work. The hard part is not moving the `w:style` element — a deep copy does that, and it is what people already do by hand. The hard part is the closure around it. A style names other styles (`w:basedOn`, `w:next`, `w:link`) and may name a numbering definition in a different part altogether, and every one of those references is an id that means something else, or nothing, in the destination document. A copy that carries the element but not the closure produces a style that renders wrongly, or a document Word rejects. So: resolve the graph, copy what is reachable, and rewrite every id that changed. ### copy_style ``` copy_style( styles: Styles, style: BaseStyle, *, name: str | None = None, on_collision: str = "skip", include_dependencies: bool = True, include_numbering: bool = True, ) -> BaseStyle ``` Copy `style` into `styles`; see Styles.copy_style_from. Source code in `src/docx/styles/copy.py` ``` def copy_style( styles: Styles, style: BaseStyle, *, name: str | None = None, on_collision: str = "skip", include_dependencies: bool = True, include_numbering: bool = True, ) -> BaseStyle: """Copy `style` into `styles`; see :meth:`.Styles.copy_style_from`.""" source_styles = style._element.getparent() # pyright: ignore[reportPrivateUsage] if source_styles is None: raise ValueError("the style being copied is not attached to a styles part") dest = styles._element # pyright: ignore[reportPrivateUsage] if source_styles is dest: raise ValueError("the style being copied is already in this document") to_copy = ( _dependency_closure(style._element, source_styles) # pyright: ignore[reportPrivateUsage] if include_dependencies else [style._element] # pyright: ignore[reportPrivateUsage] ) # -- style ids that changed on the way in, so references can be rewritten -- id_map: Dict[str, str] = {} copied: Dict[str, CT_Style] = {} result: CT_Style | None = None for source_style in to_copy: is_target = source_style is style._element # pyright: ignore[reportPrivateUsage] new_name = name if (is_target and name is not None) else source_style.name_val existing = _find_existing(dest, new_name) if existing is not None: # -- `on_collision` is about the style asked for. A dependency that already # -- exists is reused whatever the policy says: renaming or overwriting # -- "Normal" because a copied style happens to be based on it would be a # -- surprising thing to do to the destination document. -- policy = on_collision if is_target else "skip" if policy == "raise": raise ValueError(f"document already contains style '{new_name}'") if policy == "skip": if is_target: result = existing if source_style.styleId and existing.styleId: id_map[source_style.styleId] = existing.styleId continue if policy == "rename": new_name = _free_name(dest, new_name or "Style") else: # -- "overwrite" -- dest.remove(existing) new_style = copymod.deepcopy(source_style) new_style.name_val = new_name new_style.styleId = _free_style_id(dest, new_style.styleId or "Style") if source_style.styleId: id_map[source_style.styleId] = new_style.styleId dest.append(new_style) copied[new_style.styleId] = new_style if is_target: result = new_style _rewrite_style_references(copied.values(), id_map) _copy_latent_style_exceptions(source_styles, dest, to_copy) if include_numbering: _copy_numbering(copied.values(), style.document_part, styles._doc_part) # pyright: ignore[reportPrivateUsage] if result is None: # pragma: no cover -- every branch above assigns it raise ValueError("the style could not be copied") return StyleFactory(result, styles._doc_part) # pyright: ignore[reportPrivateUsage] ``` ## latent Latent style-related objects. ### LatentStyles ``` LatentStyles( element: BaseOxmlElement, parent: ProvidesXmlPart | None = None, ) ``` Bases: `ElementProxy` Provides access to the default behaviors for latent styles in this document and to the collection of \_LatentStyle objects that define overrides of those defaults for a particular named latent style. Source code in `src/docx/shared.py` ``` def __init__(self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None): self._element = element self._parent = parent ``` #### default_priority ``` default_priority ``` Integer between 0 and 99 inclusive specifying the default sort order for latent styles in style lists and the style gallery. `None` if no value is assigned, which causes Word to use the default value 99. #### default_to_hidden ``` default_to_hidden ``` Boolean specifying whether the default behavior for latent styles is to be hidden. A hidden style does not appear in the recommended list or in the style gallery. #### default_to_locked ``` default_to_locked ``` Boolean specifying whether the default behavior for latent styles is to be locked. A locked style does not appear in the styles panel or the style gallery and cannot be applied to document content. This behavior is only active when formatting protection is turned on for the document (via the Developer menu). #### default_to_quick_style ``` default_to_quick_style ``` Boolean specifying whether the default behavior for latent styles is to appear in the style gallery when not hidden. #### default_to_unhide_when_used ``` default_to_unhide_when_used ``` Boolean specifying whether the default behavior for latent styles is to be unhidden when first applied to content. #### load_count ``` load_count ``` Integer specifying the number of built-in styles to initialize to the defaults specified in this LatentStyles object. `None` if there is no setting in the XML (very uncommon). The default Word 2011 template sets this value to 276, accounting for the built-in styles in Word 2010. #### add_latent_style ``` add_latent_style(name) ``` Return a newly added \_LatentStyle object to override the inherited defaults defined in this latent styles object for the built-in style having `name`. Source code in `src/docx/styles/latent.py` ``` def add_latent_style(self, name): """Return a newly added |_LatentStyle| object to override the inherited defaults defined in this latent styles object for the built-in style having `name`.""" lsdException = self._element.add_lsdException() lsdException.name = BabelFish.ui2internal(name) return _LatentStyle(lsdException) ``` #### trim ``` trim() -> int ``` Remove every `w:lsdException` override; return how many went. The bundled template carries 137 of these, one per built-in style Word might offer, and a generated document needs none of them. A latent style is a *behavior* declaration for a style the document does not define: which of Word's built-ins appear in the gallery, in what order, and whether they are hidden until used. Removing one therefore changes what a user sees in Word's style list, not how the document renders — a different risk from removing a style definition, which is why this is a separate operation from Styles.remove_unused. The defaults on the `w:latentStyles` element itself are left in place; they are what the overrides were overriding. Source code in `src/docx/styles/latent.py` ``` def trim(self) -> int: """Remove every `w:lsdException` override; return how many went. The bundled template carries 137 of these, one per built-in style Word might offer, and a generated document needs none of them. A latent style is a *behavior* declaration for a style the document does not define: which of Word's built-ins appear in the gallery, in what order, and whether they are hidden until used. Removing one therefore changes what a user sees in Word's style list, not how the document renders — a different risk from removing a style definition, which is why this is a separate operation from :meth:`.Styles.remove_unused`. The defaults on the `w:latentStyles` element itself are left in place; they are what the overrides were overriding. """ lsdExceptions = self._element.lsdException_lst for lsdException in lsdExceptions: self._element.remove(lsdException) return len(lsdExceptions) ``` ### \_LatentStyle ``` _LatentStyle( element: BaseOxmlElement, parent: ProvidesXmlPart | None = None, ) ``` Bases: `ElementProxy` Proxy for an `w:lsdException` element, which specifies display behaviors for a built-in style when no definition for that style is stored yet in the `styles.xml` part. The values in this element override the defaults specified in the parent `w:latentStyles` element. Source code in `src/docx/shared.py` ``` def __init__(self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None): self._element = element self._parent = parent ``` #### hidden ``` hidden ``` Tri-state value specifying whether this latent style should appear in the recommended list. `None` indicates the effective value is inherited from the parent `` element. #### locked ``` locked ``` Tri-state value specifying whether this latent styles is locked. A locked style does not appear in the styles panel or the style gallery and cannot be applied to document content. This behavior is only active when formatting protection is turned on for the document (via the Developer menu). #### name ``` name ``` The name of the built-in style this exception applies to. #### priority ``` priority ``` The integer sort key for this latent style in the Word UI. #### quick_style ``` quick_style ``` Tri-state value specifying whether this latent style should appear in the Word styles gallery when not hidden. `None` indicates the effective value should be inherited from the default values in its parent LatentStyles object. #### unhide_when_used ``` unhide_when_used ``` Tri-state value specifying whether this style should have its hidden attribute set `False` the next time the style is applied to content. `None` indicates the effective value should be inherited from the default specified by its parent LatentStyles object. #### delete ``` delete() ``` Remove this latent style definition such that the defaults defined in the containing LatentStyles object provide the effective value for each of its attributes. Attempting to access any attributes on this object after calling this method will raise `AttributeError`. Source code in `src/docx/styles/latent.py` ``` def delete(self): """Remove this latent style definition such that the defaults defined in the containing |LatentStyles| object provide the effective value for each of its attributes. Attempting to access any attributes on this object after calling this method will raise |AttributeError|. """ self._element.delete() self._element = None ``` ## style Style object hierarchy. ### BaseStyle ``` BaseStyle( style_elm: CT_Style, part: DocumentPart | None = None ) ``` Bases: `ElementProxy` Base class for the various types of style object, paragraph, character, table, and numbering. These properties and methods are inherited by all style objects. Source code in `src/docx/styles/style.py` ``` def __init__(self, style_elm: CT_Style, part: DocumentPart | None = None): super().__init__(style_elm) self._style_elm = style_elm self._doc_part = part ``` #### document_part ``` document_part: DocumentPart | None ``` The DocumentPart this style belongs to, `None` when it is not known. A style reached through Document.styles knows its document, which is what lets a style copied out of it carry its numbering with it. One constructed directly from an element does not. #### builtin ``` builtin ``` Read-only. `True` if this style is a built-in style. `False` indicates it is a custom (user-defined) style. Note this value is based on the presence of a `customStyle` attribute in the XML, not on specific knowledge of which styles are built into Word. #### in_use ``` in_use: bool ``` `True` when this style is reachable from the document's content. "Reachable" is the closure Styles.usage computes, so a style used only as the `w:basedOn` of a used style counts, as does a `w:default="1"` style that nothing names outright: ``` >>> [s.name for s in document.styles if s.in_use] ['Normal', 'Heading 1', 'Hyperlink'] ``` Reading this on every style of a document recomputes the closure each time; use Styles.usage for more than a handful. Always `False` for a style whose document part is unknown — one constructed directly from an element rather than reached through Document.styles — because there is no content to be reachable from. #### hidden ``` hidden ``` `True` if display of this style in the style gallery and list of recommended styles is suppressed. `False` otherwise. In order to be shown in the style gallery, this value must be `False` and quick_style must be `True`. #### locked ``` locked ``` Read/write Boolean. `True` if this style is locked. A locked style does not appear in the styles panel or the style gallery and cannot be applied to document content. This behavior is only active when formatting protection is turned on for the document (via the Developer menu). #### name ``` name ``` The UI name of this style. #### priority ``` priority ``` The integer sort key governing display sequence of this style in the Word UI. `None` indicates no setting is defined, causing Word to use the default value of 0. Style name is used as a secondary sort key to resolve ordering of styles having the same priority value. #### quick_style ``` quick_style ``` `True` if this style should be displayed in the style gallery when hidden is `False`. Read/write Boolean. #### style_id ``` style_id: str ``` The unique key name (string) for this style. This value is subject to rewriting by Word and should generally not be changed unless you are familiar with the internals involved. #### type ``` type ``` Member of WdStyleType corresponding to the type of this style, e.g. `WD_STYLE_TYPE.PARAGRAPH`. #### unhide_when_used ``` unhide_when_used ``` `True` if an application should make this style visible the next time it is applied to content. False otherwise. Note that `python-docx` does not automatically unhide a style having `True` for this attribute when it is applied to content. #### delete ``` delete() ``` Remove this style definition from the document. Note that calling this method does not remove or change the style applied to any document content. Content items having the deleted style will be rendered using the default style, as is any content with a style not defined in the document. Source code in `src/docx/styles/style.py` ``` def delete(self): """Remove this style definition from the document. Note that calling this method does not remove or change the style applied to any document content. Content items having the deleted style will be rendered using the default style, as is any content with a style not defined in the document. """ self._element.delete() self._element = None ``` ### CharacterStyle ``` CharacterStyle( style_elm: CT_Style, part: DocumentPart | None = None ) ``` Bases: `BaseStyle` A character style. A character style is applied to a Run object and primarily provides character- level formatting via the Font object in its font property. Source code in `src/docx/styles/style.py` ``` def __init__(self, style_elm: CT_Style, part: DocumentPart | None = None): super().__init__(style_elm) self._style_elm = style_elm self._doc_part = part ``` #### base_style ``` base_style ``` Style object this style inherits from or `None` if this style is not based on another style. #### font ``` font ``` The Font object providing access to the character formatting properties for this style, such as font name and size. ### ParagraphStyle ``` ParagraphStyle( style_elm: CT_Style, part: DocumentPart | None = None ) ``` Bases: `CharacterStyle` A paragraph style. A paragraph style provides both character formatting and paragraph formatting such as indentation and line-spacing. Source code in `src/docx/styles/style.py` ``` def __init__(self, style_elm: CT_Style, part: DocumentPart | None = None): super().__init__(style_elm) self._style_elm = style_elm self._doc_part = part ``` #### next_paragraph_style ``` next_paragraph_style ``` \_ParagraphStyle object representing the style to be applied automatically to a new paragraph inserted after a paragraph of this style. Returns self if no next paragraph style is defined. Assigning `None` or `self` removes the setting such that new paragraphs are created using this same style. #### paragraph_format ``` paragraph_format ``` The ParagraphFormat object providing access to the paragraph formatting properties for this style such as indentation. ### \_TableStyle ``` _TableStyle( style_elm: CT_Style, part: DocumentPart | None = None ) ``` Bases: `ParagraphStyle` A table style. A table style provides character and paragraph formatting for its contents as well as special table formatting properties. Source code in `src/docx/styles/style.py` ``` def __init__(self, style_elm: CT_Style, part: DocumentPart | None = None): super().__init__(style_elm) self._style_elm = style_elm self._doc_part = part ``` ### \_NumberingStyle ``` _NumberingStyle( style_elm: CT_Style, part: DocumentPart | None = None ) ``` Bases: `BaseStyle` A numbering style. Not yet implemented. Source code in `src/docx/styles/style.py` ``` def __init__(self, style_elm: CT_Style, part: DocumentPart | None = None): super().__init__(style_elm) self._style_elm = style_elm self._doc_part = part ``` ### StyleFactory ``` StyleFactory( style_elm: CT_Style, part: DocumentPart | None = None ) -> BaseStyle ``` Return `Style` object of appropriate BaseStyle subclass for `style_elm`. Source code in `src/docx/styles/style.py` ``` def StyleFactory(style_elm: CT_Style, part: DocumentPart | None = None) -> BaseStyle: """Return `Style` object of appropriate |BaseStyle| subclass for `style_elm`.""" style_cls: Type[BaseStyle] = { WD_STYLE_TYPE.PARAGRAPH: ParagraphStyle, WD_STYLE_TYPE.CHARACTER: CharacterStyle, WD_STYLE_TYPE.TABLE: _TableStyle, WD_STYLE_TYPE.LIST: _NumberingStyle, }[style_elm.type] return style_cls(style_elm, part) ``` ## styles Styles object, container for all objects in the styles part. ### Styles ``` Styles(styles: CT_Styles, part: DocumentPart | None = None) ``` Bases: `ElementProxy` Provides access to the styles defined in a document. Accessed using the Document.styles property. Supports `len()`, iteration, and dictionary-style access by style name. Source code in `src/docx/styles/styles.py` ``` def __init__(self, styles: CT_Styles, part: DocumentPart | None = None): super().__init__(styles) self._element = styles self._doc_part = part ``` #### default_font ``` default_font: Font ``` The document-wide default run formatting, `w:docDefaults/w:rPrDefault/w:rPr`. This is the bottom of the formatting inheritance chain: it applies to every run in the document that no style and no direct formatting overrides. For a document whose base font is set only here — which is most documents produced from a Word template — this is the only place `Font.name` is not `None`: ``` document.styles.default_font.name = "Calibri" ``` As with Styles.latent_styles, the wrapping elements are created on first access so the returned Font always has somewhere to write. #### default_paragraph_format ``` default_paragraph_format: ParagraphFormat ``` The document-wide default paragraph formatting, `w:docDefaults/w:pPrDefault`. The counterpart of default_font for paragraph properties such as `space_after` and `line_spacing`. #### unused ``` unused: Tuple[BaseStyle, ...] ``` The styles this document defines but does not use, in document order. The complement of usage. This is what remove_unused deletes. #### latent_styles ``` latent_styles ``` A LatentStyles object providing access to the default behaviors for latent styles and the collection of \_LatentStyle objects that define overrides of those defaults for a particular named latent style. #### add_style ``` add_style(name, style_type, builtin=False) ``` Return a newly added style object of `style_type` and identified by `name`. A builtin style can be defined by passing True for the optional `builtin` argument. Source code in `src/docx/styles/styles.py` ``` def add_style(self, name, style_type, builtin=False): """Return a newly added style object of `style_type` and identified by `name`. A builtin style can be defined by passing True for the optional `builtin` argument. """ style_name = BabelFish.ui2internal(name) if style_name in self: raise ValueError("document already contains style '%s'" % name) style = self._element.add_style_of_type(style_name, style_type, builtin) return StyleFactory(style, self._doc_part) ``` #### copy_style_from ``` copy_style_from( style: BaseStyle, *, name: str | None = None, on_collision: str = "skip", include_dependencies: bool = True, include_numbering: bool = True, ) -> BaseStyle ``` Copy `style` from another document into this one and return the copy. Applying a style by name fails with `KeyError: no style with name 'X'` whenever the target document's style part lacks it, which is routine when content is assembled from several sources: ``` template = Document("template.docx") report = Document() report.styles.copy_style_from(template.styles["Callout"]) report.add_paragraph("note", style="Callout") ``` **The dependency closure is the point.** A style is not a self-contained object: `w:basedOn` names the style it inherits from, `w:next` the style for the following paragraph, and `w:link` the paired character or paragraph style. A copied style whose `basedOn` target is missing renders as though it inherited from Normal, which is the failure people hit when they deep-copy one `w:style` element by hand. Those are followed and copied too unless `include_dependencies` is `False`. A list style references `numbering.xml`, so `include_numbering` also copies the `w:num` and `w:abstractNum` behind it and rewrites the reference to the new id. That needs the source document, which a style knows only when it came from Document.styles; a style whose document is unknown has its numbering reference left alone, and it will not resolve here. `name` renames the style as it is copied; dependencies keep their own names. `on_collision` decides what happens when this document already has a style of that name: - `"skip"` (default) leaves the existing style alone and returns it. - `"overwrite"` replaces its definition. - `"rename"` copies it under a free name, "Callout 2" and so on. - `"raise"` raises `ValueError`. **Not carried over:** theme fonts. A style referencing `w:asciiTheme` resolves against *this* document's theme part, so a copied style can legitimately look different here. Latent-style visibility (`w:lsdException`) is copied when the source defines one for the style. Source code in `src/docx/styles/styles.py` ``` def copy_style_from( self, style: BaseStyle, *, name: str | None = None, on_collision: str = "skip", include_dependencies: bool = True, include_numbering: bool = True, ) -> BaseStyle: """Copy `style` from another document into this one and return the copy. Applying a style by name fails with `KeyError: no style with name 'X'` whenever the target document's style part lacks it, which is routine when content is assembled from several sources:: template = Document("template.docx") report = Document() report.styles.copy_style_from(template.styles["Callout"]) report.add_paragraph("note", style="Callout") **The dependency closure is the point.** A style is not a self-contained object: `w:basedOn` names the style it inherits from, `w:next` the style for the following paragraph, and `w:link` the paired character or paragraph style. A copied style whose `basedOn` target is missing renders as though it inherited from Normal, which is the failure people hit when they deep-copy one `w:style` element by hand. Those are followed and copied too unless `include_dependencies` is |False|. A list style references `numbering.xml`, so `include_numbering` also copies the `w:num` and `w:abstractNum` behind it and rewrites the reference to the new id. That needs the source document, which a style knows only when it came from :attr:`.Document.styles`; a style whose document is unknown has its numbering reference left alone, and it will not resolve here. `name` renames the style as it is copied; dependencies keep their own names. `on_collision` decides what happens when this document already has a style of that name: - ``"skip"`` (default) leaves the existing style alone and returns it. - ``"overwrite"`` replaces its definition. - ``"rename"`` copies it under a free name, "Callout 2" and so on. - ``"raise"`` raises |ValueError|. **Not carried over:** theme fonts. A style referencing `w:asciiTheme` resolves against *this* document's theme part, so a copied style can legitimately look different here. Latent-style visibility (`w:lsdException`) is copied when the source defines one for the style. """ from docx.styles.copy import copy_style if on_collision not in ("skip", "overwrite", "rename", "raise"): raise ValueError( f"on_collision must be one of 'skip', 'overwrite', 'rename' or 'raise'," f" got {on_collision!r}" ) return copy_style( self, style, name=name, on_collision=on_collision, include_dependencies=include_dependencies, include_numbering=include_numbering, ) ``` #### default ``` default(style_type: WD_STYLE_TYPE) ``` Return the default style for `style_type` or `None` if no default is defined for that type (not common). Source code in `src/docx/styles/styles.py` ``` def default(self, style_type: WD_STYLE_TYPE): """Return the default style for `style_type` or |None| if no default is defined for that type (not common).""" style = self._element.default_for(style_type) if style is None: return None return StyleFactory(style, self._doc_part) ``` #### get_by_id ``` get_by_id(style_id: str | None, style_type: WD_STYLE_TYPE) ``` Return the style of `style_type` matching `style_id`. Returns the default for `style_type` if `style_id` is not found or is `None`, or if the style having `style_id` is not of `style_type`. Source code in `src/docx/styles/styles.py` ``` def get_by_id(self, style_id: str | None, style_type: WD_STYLE_TYPE): """Return the style of `style_type` matching `style_id`. Returns the default for `style_type` if `style_id` is not found or is |None|, or if the style having `style_id` is not of `style_type`. """ if style_id is None: return self.default(style_type) return self._get_by_id(style_id, style_type) ``` #### get_style_id ``` get_style_id(style_or_name, style_type) ``` Return the id of the style corresponding to `style_or_name`, or `None` if `style_or_name` is `None`. If `style_or_name` is not a style object, the style is looked up using `style_or_name` as a style name, raising `ValueError` if no style with that name is defined. Raises `ValueError` if the target style is not of `style_type`. Source code in `src/docx/styles/styles.py` ``` def get_style_id(self, style_or_name, style_type): """Return the id of the style corresponding to `style_or_name`, or |None| if `style_or_name` is |None|. If `style_or_name` is not a style object, the style is looked up using `style_or_name` as a style name, raising |ValueError| if no style with that name is defined. Raises |ValueError| if the target style is not of `style_type`. """ if style_or_name is None: return None elif isinstance(style_or_name, BaseStyle): return self._get_style_id_from_style(style_or_name, style_type) else: return self._get_style_id_from_name(style_or_name, style_type) ``` #### import_from ``` import_from( source: str | PathLike[str] | IO[bytes] | Document, names: Iterable[str] | None = None, *, overwrite: bool = False, include_latent: bool = False, ) -> Dict[str, str] ``` Copy styles from `source` into this document; return what was done to each. The bulk form of copy_style_from, which is what anyone generating documents from a corporate template ends up writing by hand: ``` document.styles.import_from("house-template.dotx") document.styles.import_from(other_document, names=["Quote", "Caption"]) document.styles.import_from(tmpl, overwrite=True) ``` `source` may be a path, a file-like object or an open Document; a `.dotx` is the common case and opens without special handling. `names` of `None` means every style the source defines. Each style is copied with its `w:basedOn` / `w:next` / `w:link` closure and its numbering, as copy_style_from does. `overwrite` decides what happens to a name this document already has: `False` leaves the existing definition alone, `True` replaces it. The report says which happened for each name — `"added"`, `"replaced"` or `"skipped"` — because "why is my heading still the wrong colour" is otherwise unanswerable. `include_latent` copies the source's whole `w:latentStyles` block. It is `False` by default because that changes which of Word's built-ins appear in *this* document's style gallery, which is rarely what was asked for. **Theme fonts are not resolved.** A style specifying `w:asciiTheme="minorHAnsi"` renders with this document's theme after import, which may not be what you saw in the source; Document.theme is where to check. Source code in `src/docx/styles/styles.py` ``` def import_from( self, source: str | os.PathLike[str] | IO[bytes] | Document, names: Iterable[str] | None = None, *, overwrite: bool = False, include_latent: bool = False, ) -> Dict[str, str]: """Copy styles from `source` into this document; return what was done to each. The bulk form of :meth:`copy_style_from`, which is what anyone generating documents from a corporate template ends up writing by hand:: document.styles.import_from("house-template.dotx") document.styles.import_from(other_document, names=["Quote", "Caption"]) document.styles.import_from(tmpl, overwrite=True) `source` may be a path, a file-like object or an open |Document|; a `.dotx` is the common case and opens without special handling. `names` of |None| means every style the source defines. Each style is copied with its `w:basedOn` / `w:next` / `w:link` closure and its numbering, as :meth:`copy_style_from` does. `overwrite` decides what happens to a name this document already has: |False| leaves the existing definition alone, |True| replaces it. The report says which happened for each name — ``"added"``, ``"replaced"`` or ``"skipped"`` — because "why is my heading still the wrong colour" is otherwise unanswerable. `include_latent` copies the source's whole `w:latentStyles` block. It is |False| by default because that changes which of Word's built-ins appear in *this* document's style gallery, which is rarely what was asked for. **Theme fonts are not resolved.** A style specifying `w:asciiTheme="minorHAnsi"` renders with this document's theme after import, which may not be what you saw in the source; :attr:`.Document.theme` is where to check. """ from docx.styles.transfer import import_styles return import_styles( self, source, names, overwrite=overwrite, include_latent=include_latent ) ``` #### extract ``` extract( path_or_stream: str | PathLike[str] | IO[bytes], names: Iterable[str] | None = None, *, as_template: bool = False, ) -> List[str] ``` Write `names` and their closure to a styles-only document; return what went. The other half of the round trip: pull the styles you like out of a document someone sent you, keep them as a template, and apply them to everything you generate: ``` document.styles.extract("styles-only.docx", names=["Quote", "Caption"]) document.styles.extract("house.dotx", as_template=True) ``` The result is a valid, empty Word document carrying the named styles and everything they depend on, and nothing else — the 164 styles of the bundled template are pruned first, or the extract would be mostly them. `names` of `None` extracts every style this document defines. Source code in `src/docx/styles/styles.py` ``` def extract( self, path_or_stream: str | os.PathLike[str] | IO[bytes], names: Iterable[str] | None = None, *, as_template: bool = False, ) -> List[str]: """Write `names` and their closure to a styles-only document; return what went. The other half of the round trip: pull the styles you like out of a document someone sent you, keep them as a template, and apply them to everything you generate:: document.styles.extract("styles-only.docx", names=["Quote", "Caption"]) document.styles.extract("house.dotx", as_template=True) The result is a valid, empty Word document carrying the named styles and everything they depend on, and nothing else — the 164 styles of the bundled template are pruned first, or the extract would be mostly them. `names` of |None| extracts every style this document defines. """ from docx.styles.transfer import save_extract return save_extract(self, path_or_stream, names, as_template=as_template) ``` #### extract_xml ``` extract_xml(names: Iterable[str] | None = None) -> bytes ``` The `styles.xml` bytes of the extract, without writing a package. For diffing two documents' styles, or putting them under version control. Source code in `src/docx/styles/styles.py` ``` def extract_xml(self, names: Iterable[str] | None = None) -> bytes: """The `styles.xml` bytes of the :meth:`extract`, without writing a package. For diffing two documents' styles, or putting them under version control. """ from docx.styles.transfer import extract_styles_xml return extract_styles_xml(self, names) ``` #### usage ``` usage( *, keep: Iterable[str] = (), seed_defaults: bool = True ) -> StyleUsage ``` A report of which styles this document defines and which it actually uses: ``` >>> print(document.styles.usage()) 164 styles defined, 3 in use, 161 unused; 137 latent style exceptions ``` "Used" is a reachability closure rather than a membership test. It starts from every style applied anywhere in the document — in the body, and in the headers, footers, footnotes, endnotes and comments, which are separate parts with their own references — and follows `w:basedOn`, `w:next`, `w:link` and the numbering and table-style references until it stops growing. A style used only as the `basedOn` of a used style *is* used. The `w:default="1"` styles are used by definition: they apply to content that names no style at all, so they have no direct references to count. `keep` names style ids to treat as used along with their own closure, for a caller who plans to apply a style nothing references yet. `seed_defaults` decides whether the `w:default="1"` styles count as used whether or not anything references them. The default of `True` is the truthful reading; `False` answers the narrower question of what is reachable by reference alone. Style *ids* are what the report holds, since ids are what the XML references; the names this collection is keyed on are the UI spelling of the same thing. Source code in `src/docx/styles/styles.py` ``` def usage(self, *, keep: Iterable[str] = (), seed_defaults: bool = True) -> StyleUsage: """A report of which styles this document defines and which it actually uses:: >>> print(document.styles.usage()) 164 styles defined, 3 in use, 161 unused; 137 latent style exceptions "Used" is a reachability closure rather than a membership test. It starts from every style applied anywhere in the document — in the body, and in the headers, footers, footnotes, endnotes and comments, which are separate parts with their own references — and follows `w:basedOn`, `w:next`, `w:link` and the numbering and table-style references until it stops growing. A style used only as the `basedOn` of a used style *is* used. The `w:default="1"` styles are used by definition: they apply to content that names no style at all, so they have no direct references to count. `keep` names style ids to treat as used along with their own closure, for a caller who plans to apply a style nothing references yet. `seed_defaults` decides whether the `w:default="1"` styles count as used whether or not anything references them. The default of |True| is the truthful reading; |False| answers the narrower question of what is reachable by reference alone. Style *ids* are what the report holds, since ids are what the XML references; the names this collection is keyed on are the UI spelling of the same thing. """ from docx.styles.usage import compute_usage return compute_usage(self._element, self._doc_part, keep, seed_defaults=seed_defaults) ``` #### remove_unused ``` remove_unused( *, keep: Iterable[str] = (), keep_defaults: bool = True ) -> Tuple[str, ...] ``` Delete every style outside the reachability closure; return what was removed. Prunes a document down to the styles it actually uses: ``` document.styles.remove_unused() document.styles.remove_unused(keep=["Quote", "Caption"]) ``` **This is destructive and the closure is the only thing standing between it and a broken document.** It deletes definitions, so a style the closure misses comes back as default formatting. The closure is the same one usage reports, deliberately — there is one implementation and one set of tests behind both. `keep` names styles to preserve along with their own dependencies, given as UI names or style ids. `keep_defaults` keeps the `w:default="1"` style of each type whatever the closure says, which is the safe default: those apply to content that names no style at all, so removing one silently changes how that content renders. Pass `False` to prune a default that nothing references. "Normal" is never removed whatever else is asked: Word repairs a document that lacks it, and the repair dialogue is worse than the bloat. Returns the ids removed, so the caller can report them. Latent styles are left alone; removing an `lsdException` changes whether a style appears in Word's gallery rather than how the document looks, which is a different risk — see LatentStyles.trim. Source code in `src/docx/styles/styles.py` ``` def remove_unused( self, *, keep: Iterable[str] = (), keep_defaults: bool = True ) -> Tuple[str, ...]: """Delete every style outside the reachability closure; return what was removed. Prunes a document down to the styles it actually uses:: document.styles.remove_unused() document.styles.remove_unused(keep=["Quote", "Caption"]) **This is destructive and the closure is the only thing standing between it and a broken document.** It deletes definitions, so a style the closure misses comes back as default formatting. The closure is the same one :meth:`usage` reports, deliberately — there is one implementation and one set of tests behind both. `keep` names styles to preserve along with their own dependencies, given as UI names or style ids. `keep_defaults` keeps the `w:default="1"` style of each type whatever the closure says, which is the safe default: those apply to content that names no style at all, so removing one silently changes how that content renders. Pass |False| to prune a default that nothing references. "Normal" is never removed whatever else is asked: Word repairs a document that lacks it, and the repair dialogue is worse than the bloat. Returns the ids removed, so the caller can report them. Latent styles are left alone; removing an `lsdException` changes whether a style appears in Word's gallery rather than how the document looks, which is a different risk — see :meth:`.LatentStyles.trim`. """ keep_ids = {self._style_id_for(name) for name in keep} keep_ids.discard(None) usage = self.usage(keep=cast("set[str]", keep_ids), seed_defaults=keep_defaults) removable = set(usage.unused) removed: list[str] = [] for style in list(self._element.style_lst): if style.styleId in removable: removed.append(style.styleId) style.delete() return tuple(removed) ``` ## transfer Bulk style import and extract, built on `docx.styles.copy.copy_style`. `copy_style_from()` moves one style and resolves its `w:basedOn` / `w:next` / `w:link` closure and its numbering. That is the hard part and it is done. What people write by hand on top of it is the two bulk operations: pull a whole house template's styles into a generated document, and pull a set of styles out of a document into a template of their own. The naive loop over `copy_style_from()` is O(n) full closure resolutions with repeated work, and re-imports shared numbering once per style that uses it. Both operations here resolve the shared work once. ### import_styles ``` import_styles( styles: Styles, source: str | PathLike[str] | IO[bytes] | Document, names: Iterable[str] | None = None, *, overwrite: bool = False, include_latent: bool = False, ) -> ImportReport ``` Copy styles from `source` into `styles`; see Styles.import_from. Source code in `src/docx/styles/transfer.py` ``` def import_styles( styles: Styles, source: str | os.PathLike[str] | IO[bytes] | Document, names: Iterable[str] | None = None, *, overwrite: bool = False, include_latent: bool = False, ) -> ImportReport: """Copy styles from `source` into `styles`; see :meth:`.Styles.import_from`.""" source_document = _as_document(source) source_styles = source_document.styles wanted = ( list(source_styles) if names is None else [source_styles[name] for name in names] ) report: ImportReport = {} for style in wanted: name = style.name if name is None: continue present = name in styles if present and not overwrite: # -- nothing to do: the destination already defines this name, so a later # -- style based on it already resolves -- report[name] = "skipped" continue styles.copy_style_from(style, on_collision="overwrite" if overwrite else "skip") report[name] = "replaced" if present else "added" if include_latent: _copy_all_latent_exceptions(source_styles, styles) return report ``` ### extract_styles ``` extract_styles( styles: Styles, names: Iterable[str] | None = None ) -> Tuple[Document, List[str]] ``` A new empty document carrying `names` and their closure; see Styles.extract. Returns the document and the UI names of the styles it received, in the order they were added. Source code in `src/docx/styles/transfer.py` ``` def extract_styles( styles: Styles, names: Iterable[str] | None = None ) -> Tuple[Document, List[str]]: """A new empty document carrying `names` and their closure; see :meth:`.Styles.extract`. Returns the document and the UI names of the styles it received, in the order they were added. """ from docx.api import Document as new_document source_names = ( [s.name for s in styles if s.name is not None] if names is None else _closure_names(styles, names) ) destination = new_document() # -- the bundled template defines 164 styles of its own; a styles-only document that # -- carried those as well would not be the extract that was asked for -- destination.styles.remove_unused() added: List[str] = [] for name in source_names: style: BaseStyle = styles[name] destination.styles.copy_style_from(style, on_collision="overwrite") added.append(name) return destination, added ``` ### extract_styles_xml ``` extract_styles_xml( styles: Styles, names: Iterable[str] | None = None ) -> bytes ``` The `styles.xml` bytes of the extract; see Styles.extract_xml. Source code in `src/docx/styles/transfer.py` ``` def extract_styles_xml(styles: Styles, names: Iterable[str] | None = None) -> bytes: """The `styles.xml` bytes of the extract; see :meth:`.Styles.extract_xml`.""" from docx.opc.oxml import serialize_part_xml destination, _ = extract_styles(styles, names) styles_part = destination.part.part_related_by(RT.STYLES) return serialize_part_xml(styles_part.element) # pyright: ignore[reportAttributeAccessIssue] ``` ### save_extract ``` save_extract( styles: Styles, path_or_stream: str | PathLike[str] | IO[bytes], names: Iterable[str] | None = None, *, as_template: bool = False, ) -> List[str] ``` Write the extract to `path_or_stream`; see Styles.extract. Source code in `src/docx/styles/transfer.py` ``` def save_extract( styles: Styles, path_or_stream: str | os.PathLike[str] | IO[bytes], names: Iterable[str] | None = None, *, as_template: bool = False, ) -> List[str]: """Write the extract to `path_or_stream`; see :meth:`.Styles.extract`.""" destination, added = extract_styles(styles, names) destination.save(path_or_stream, as_template=as_template) return added ``` ## usage Which styles a document actually uses. The interesting part is getting "used" right. A naive scan of `w:pStyle` in `word/document.xml` gets the wrong answer in several ways, and each of them is a real document: - **Every story part, not just the body.** Headers, footers, footnotes, endnotes and comments are separate parts with their own content and their own style references. - **Indirect references.** A style can be reachable without ever being applied — as the `w:basedOn` of a used style, as its `w:next`, as its `w:link`, from a numbering level's `w:pStyle`, or from the `w:tblStylePr` conditional formatting inside a table style. - **The default styles.** The style carrying `w:default="1"` applies to every paragraph with no `w:pStyle` at all. It is used by definition and has zero direct references. So "used" is a **reachability closure**, not a membership test: seed from the direct applications, then follow the reference edges until the set stops growing. The closure runs on style *ids*, which is what the XML references; `docx.styles.styles.Styles` keys on *names*, which is what the API exposes, and `BabelFish` translates the built-ins between the two spellings. Mixing the two is a recurring source of bugs, so the translation happens only at the boundary. ### StyleUsage Bases: `NamedTuple` A report of which styles a document defines and which of them it uses. Iterating yields the style ids in use. `str()` gives the one-paragraph summary that `print` shows. #### unused ``` unused: Tuple[str, ...] ``` Style ids defined but not reachable, in document order. ### compute_usage ``` compute_usage( styles_elm: CT_Styles, document_part: DocumentPart | None, keep: Iterable[str] = (), *, seed_defaults: bool = True, ) -> StyleUsage ``` The style-usage report for `styles_elm` as used by `document_part`. `keep` names extra style ids to treat as used along with their own closure — for a caller who plans to apply a style that nothing references yet. `seed_defaults` puts the `w:default="1"` styles into the closure whether or not anything references them, which is the truthful reading: they apply to content that names no style at all. Passing `False` answers the narrower question of what is reachable by reference alone, which is what a caller deliberately pruning the defaults needs. With no `document_part` there is no content to scan, so only the defaults and `keep` seed the closure. That is the honest answer for a styles part reached on its own rather than a claim that nothing is used. Source code in `src/docx/styles/usage.py` ``` def compute_usage( styles_elm: CT_Styles, document_part: DocumentPart | None, keep: Iterable[str] = (), *, seed_defaults: bool = True, ) -> StyleUsage: """The style-usage report for `styles_elm` as used by `document_part`. `keep` names extra style ids to treat as used along with their own closure — for a caller who plans to apply a style that nothing references yet. `seed_defaults` puts the `w:default="1"` styles into the closure whether or not anything references them, which is the truthful reading: they apply to content that names no style at all. Passing |False| answers the narrower question of what is reachable by reference alone, which is what a caller deliberately pruning the defaults needs. With no `document_part` there is no content to scan, so only the defaults and `keep` seed the closure. That is the honest answer for a styles part reached on its own rather than a claim that nothing is used. """ by_id = {s.styleId: s for s in styles_elm.style_lst if s.styleId} defined = tuple(s.styleId for s in styles_elm.style_lst if s.styleId) counts = _direct_reference_counts(document_part) if document_part is not None else {} # -- seed: everything directly applied, every default style, and `keep` -- pending = set(counts) | set(keep) if seed_defaults: pending |= _default_style_ids(styles_elm) # -- Word's "Normal" is repaired into a document that lacks it, and the repair # -- dialogue is worse than the bloat, so it is never dropped -- if "Normal" in by_id: pending.add("Normal") # -- reachability closure. A dangling edge — a `w:basedOn` naming a style that is # -- not defined — is a dead end rather than an error; it is legal and common. The # -- visited set is what makes cycles terminate, and `w:next` pointing at its own # -- style is the normal case rather than a pathology. -- used: Set[str] = set() while pending: style_id = pending.pop() if style_id in used or style_id not in by_id: continue used.add(style_id) pending.update(_style_edges(by_id[style_id])) latent = tuple( name for name in styles_elm.xpath("./w:latentStyles/w:lsdException/@w:name") if name not in by_id ) return StyleUsage( defined=defined, used=tuple(style_id for style_id in defined if style_id in used), reference_counts=counts, latent=latent, ) ``` ### latent_exception_names ``` latent_exception_names( styles_elm: CT_Styles, ) -> Tuple[str, ...] ``` Every `w:lsdException/@w:name` in the latent-styles block, defined or not. Source code in `src/docx/styles/usage.py` ``` def latent_exception_names(styles_elm: CT_Styles) -> Tuple[str, ...]: """Every `w:lsdException/@w:name` in the latent-styles block, defined or not.""" return tuple(styles_elm.xpath("./w:latentStyles/w:lsdException/@w:name")) ``` ### is_default_style ``` is_default_style( styles_elm: CT_Styles, style: CT_Style ) -> bool ``` `True` when `style` is the `w:default="1"` style of its type. Source code in `src/docx/styles/usage.py` ``` def is_default_style(styles_elm: CT_Styles, style: CT_Style) -> bool: """|True| when `style` is the `w:default="1"` style of its type.""" return bool(style.styleId) and style.styleId in _default_style_ids(styles_elm) ``` ## text ## font Font-related proxy objects. ### Font ``` Font(r: CT_R, parent: Any | None = None) ``` Bases: `ElementProxy` Proxy object for parent of a `` element and providing access to character properties such as font name, font size, bold, and subscript. Source code in `src/docx/text/font.py` ``` def __init__(self, r: CT_R, parent: Any | None = None): super().__init__(r, parent) self._element = r self._r = r ``` #### all_caps ``` all_caps: bool | None ``` Read/write. Causes text in this font to appear in capital letters. #### bold ``` bold: bool | None ``` Read/write. Causes text in this font to appear in bold. #### color ``` color ``` A ColorFormat object providing a way to get and set the text color for this font. #### complex_script ``` complex_script: bool | None ``` Read/write tri-state value. When `True`, causes the characters in the run to be treated as complex script regardless of their Unicode values. #### cs_bold ``` cs_bold: bool | None ``` Read/write tri-state value. When `True`, causes the complex script characters in the run to be displayed in bold typeface. #### cs_italic ``` cs_italic: bool | None ``` Read/write tri-state value. When `True`, causes the complex script characters in the run to be displayed in italic typeface. #### cs_name ``` cs_name: str | None ``` The typeface name applied to complex-script characters in this run. `w:rFonts` has four independent typeface slots and Word chooses between them per character, according to the script that character belongs to. This is the slot used for Arabic, Hebrew and other complex scripts. `None` indicates the typeface is inherited from the style hierarchy. #### cs_size ``` cs_size: Length | None ``` The font size applied to complex-script characters in this run. Word tracks this separately from `.size`, in `w:szCs`. `None` indicates the size is inherited from the style hierarchy. #### east_asia_name ``` east_asia_name: str | None ``` The typeface name applied to East Asian characters in this run. This is the slot that carries the meaningful typeface for Chinese, Japanese and Korean text; see `.cs_name` for the four-slot arrangement. `None` indicates the typeface is inherited from the style hierarchy. #### hint ``` hint: WD_FONT_HINT | None ``` Member of `WdFontHint`, or `None` when no hint is specified. Tells Word which typeface slot to prefer for a character that belongs to no particular script, such as a space or a digit. This matters for correct East Asian rendering, where an unhinted run mixes typefaces mid-word. #### double_strike ``` double_strike: bool | None ``` Read/write tri-state value. When `True`, causes the text in the run to appear with double strikethrough. #### emboss ``` emboss: bool | None ``` Read/write tri-state value. When `True`, causes the text in the run to appear as if raised off the page in relief. #### hidden ``` hidden: bool | None ``` Read/write tri-state value. When `True`, causes the text in the run to be hidden from display, unless applications settings force hidden text to be shown. #### highlight_color ``` highlight_color: WD_COLOR_INDEX | None ``` Color of highlighing applied or `None` if not highlighted. #### italic ``` italic: bool | None ``` Read/write tri-state value. When `True`, causes the text of the run to appear in italics. `None` indicates the effective value is inherited from the style hierarchy. #### imprint ``` imprint: bool | None ``` Read/write tri-state value. When `True`, causes the text in the run to appear as if pressed into the page. #### math ``` math: bool | None ``` Read/write tri-state value. When `True`, specifies this run contains WML that should be handled as though it was Office Open XML Math. #### name ``` name: str | None ``` The typeface name for this Font. Causes the text it controls to appear in the named font, if a matching font is found. `None` indicates the typeface is inherited from the style hierarchy. This is the `w:ascii` slot of `w:rFonts`, and assigning to it also sets `w:hAnsi`, which is what Word does. It deliberately does *not* fall back to the other slots: a run can name a different typeface for East Asian (`.east_asia_name`) and complex-script (`.cs_name`) characters, Word picks between them per character, and reporting one of them as "the" font would be an approximation dressed up as an answer. A run with only `w:eastAsia` set therefore reports `None` here and its typeface through `.east_asia_name`. #### theme ``` theme: str | None ``` The theme typeface slot for this Font, e.g. "minorHAnsi". The named slot is resolved against the document theme, so the text follows the theme font rather than a font named outright. `None` indicates no theme typeface is assigned and the typeface is inherited from the style hierarchy. #### theme_typeface ``` theme_typeface: str | None ``` The concrete typeface this font's theme slot resolves to, or `None`. theme gives the token — `"minorHAnsi"` — and this gives the font name it stands for, by looking the token up in the document's theme part: ``` >>> run.font.theme 'minorHAnsi' >>> run.font.theme_typeface 'Calibri' ``` `None` when the run has no theme slot, when the document carries no theme part, or when the theme leaves that slot empty. This resolves the run's *own* theme token only; it does not walk the style hierarchy, so a run whose theme font comes from its style reads `None` here as it does from theme. #### scaling ``` scaling: int | None ``` Horizontal character scaling, as a whole percentage of normal width. 100 is normal width, 200 stretches each glyph to double width and 50 condenses it to half. Valid values run from 1 to 600. `None` indicates the value is inherited from the style hierarchy. Note this scales glyphs horizontally only; use `.size` to change font height. #### shading_fill ``` shading_fill: RGBColor | str | None ``` Background shading color behind the text of this run. An RGBColor value, the string "auto", or `None` when no shading is applied. Assigning a hex string such as "FF0000" or "#FF0000" is also accepted. This is distinct from `.highlight_color`, which takes a WD_COLOR_INDEX member and is limited to Word's fixed highlighter palette. Shading accepts any RGB value. Word renders both, with highlighting drawn over shading. #### shading_pattern ``` shading_pattern: WD_SHADING_PATTERN | None ``` The pattern drawn over the shading behind the text of this run. A WD_SHADING_PATTERN member, or `None` when no shading is applied. Word writes |WD_SHADING_PATTERN.CLEAR| for an ordinary background color, which is what `.shading_fill` produces on its own. Assigning `None` removes the shading entirely, the same as assigning `None` to `.shading_fill`. #### shading_color ``` shading_color: RGBColor | str | None ``` The foreground color of the shading pattern behind the text of this run. An RGBColor value, the string "auto", or `None`. This is the color the `.shading_pattern` is drawn *in*; `.shading_fill` is the color behind it. For the usual |WD_SHADING_PATTERN.CLEAR| pattern nothing is drawn and this has no visible effect. #### no_proof ``` no_proof: bool | None ``` Read/write tri-state value. When `True`, specifies that the contents of this run should not report any errors when the document is scanned for spelling and grammar. #### outline ``` outline: bool | None ``` Read/write tri-state value. When `True` causes the characters in the run to appear as if they have an outline, by drawing a one pixel wide border around the inside and outside borders of each character glyph. #### rtl ``` rtl: bool | None ``` Read/write tri-state value. When `True` causes the text in the run to have right-to-left characteristics. #### shadow ``` shadow: bool | None ``` Read/write tri-state value. When `True` causes the text in the run to appear as if each character has a shadow. #### size ``` size: Length | None ``` Font height in English Metric Units (EMU). `None` indicates the font size should be inherited from the style hierarchy. Length is a subclass of `int` having properties for convenient conversion into points or other length units. The `docx.shared.Pt` class allows convenient specification of point values: ``` >>> font.size = Pt(24) >>> font.size 304800 >>> font.size.pt 24.0 ``` #### small_caps ``` small_caps: bool | None ``` Read/write tri-state value. When `True` causes the lowercase characters in the run to appear as capital letters two points smaller than the font size specified for the run. #### snap_to_grid ``` snap_to_grid: bool | None ``` Read/write tri-state value. When `True` causes the run to use the document grid characters per line settings defined in the docGrid element when laying out the characters in this run. #### spec_vanish ``` spec_vanish: bool | None ``` Read/write tri-state value. When `True`, specifies that the given run shall always behave as if it is hidden, even when hidden text is being displayed in the current document. The property has a very narrow, specialized use related to the table of contents. Consult the spec (§17.3.2.36) for more details. #### strike ``` strike: bool | None ``` Read/write tri-state value. When `True` causes the text in the run to appear with a single horizontal line through the center of the line. #### subscript ``` subscript: bool | None ``` Boolean indicating whether the characters in this Font appear as subscript. `None` indicates the subscript/subscript value is inherited from the style hierarchy. #### superscript ``` superscript: bool | None ``` Boolean indicating whether the characters in this Font appear as superscript. `None` indicates the subscript/superscript value is inherited from the style hierarchy. #### underline ``` underline: bool | WD_UNDERLINE | None ``` The underline style for this Font. The value is one of `None`, `True`, `False`, or a member of WdUnderline. `None` indicates the font inherits its underline value from the style hierarchy. `False` indicates no underline. `True` indicates single underline. The values from WdUnderline are used to specify other outline styles such as double, wavy, and dotted. #### web_hidden ``` web_hidden: bool | None ``` Read/write tri-state value. When `True`, specifies that the contents of this run shall be hidden when the document is displayed in web page view. ## hyperlink Hyperlink-related proxy objects for python-docx, Hyperlink in particular. A hyperlink occurs in a paragraph, at the same level as a Run, and a hyperlink itself contains runs, which is where the visible text of the hyperlink is stored. So it's kind of in-between, less than a paragraph and more than a run. So it gets its own module. ### Hyperlink ``` Hyperlink( hyperlink: CT_Hyperlink, parent: ProvidesStoryPart ) ``` Bases: `Parented` Proxy object wrapping a `` element. A hyperlink occurs as a child of a paragraph, at the same level as a Run. A hyperlink itself contains runs, which is where the visible text of the hyperlink is stored. Source code in `src/docx/text/hyperlink.py` ``` def __init__(self, hyperlink: CT_Hyperlink, parent: t.ProvidesStoryPart): super().__init__(parent) self._parent = parent self._hyperlink = self._element = hyperlink ``` #### address ``` address: str ``` The "URL" of the hyperlink (but not necessarily a web link). While commonly a web link like "https://google.com" the hyperlink address can take a variety of forms including "internal links" to bookmarked locations within the document. When this hyperlink is an internal "jump" to for example a heading from the table-of-contents (TOC), the address is blank. The bookmark reference (like "\_Toc147925734") is stored in the `.fragment` property. #### contains_page_break ``` contains_page_break: bool ``` True when the text of this hyperlink is broken across page boundaries. This is not uncommon and can happen for example when the hyperlink text is multiple words and occurs in the last line of a page. Theoretically, a hyperlink can contain more than one page break but that would be extremely uncommon in practice. Still, this value should be understood to mean that "one-or-more" rendered page breaks are present. #### fragment ``` fragment: str ``` Reference like `#glossary` at end of URL that refers to a sub-resource. Note that this value does not include the fragment-separator character ("#"). This value is known as a "named anchor" in an HTML context and "anchor" in the MS API, but an "anchor" element (``) represents a full hyperlink in HTML so we avoid confusion by using the more precise RFC 3986 naming "URI fragment". These are also used to refer to bookmarks within the same document, in which case the `.address` value with be blank ("") and this property will hold a value like "\_Toc147925734". To reliably get an entire web URL you will need to concatenate this with the `.address` value, separated by "#" when both are present. Consider using the `.url` property for that purpose. Word sometimes stores a fragment in this property (an XML attribute) and sometimes with the address, depending on how the URL is inserted, so don't depend on this field being empty to indicate no fragment is present. #### runs ``` runs: list[Run] ``` List of Run instances in this hyperlink. Together these define the visible text of the hyperlink. The text of a hyperlink is typically contained in a single run will be broken into multiple runs if for example part of the hyperlink is bold or the text was changed after the document was saved. #### text ``` text: str ``` String formed by concatenating the text of each run in the hyperlink. Tabs and line breaks in the XML are mapped to `\t` and `\n` characters respectively. Note that rendered page-breaks can occur within a hyperlink but they are not reflected in this text. #### url ``` url: str ``` Convenience property to get web URLs from hyperlinks that contain them. This value is the empty string ("") when there is no address portion, so its boolean value can also be used to distinguish external URIs from internal "jump" hyperlinks like those found in a table-of-contents. Note that this value may also be a link to a file, so if you only want web-urls you'll need to check for a protocol prefix like `https://`. When both an address and fragment are present, the return value joins the two separated by the fragment-separator hash ("#"). Otherwise this value is the same as that of the `.address` property. ## pagebreak Proxy objects related to rendered page-breaks. ### RenderedPageBreak ``` RenderedPageBreak( lastRenderedPageBreak: CT_LastRenderedPageBreak, parent: ProvidesStoryPart, ) ``` Bases: `Parented` A page-break inserted by Word during page-layout for print or display purposes. This usually does not correspond to a "hard" page-break inserted by the document author, rather just that Word ran out of room on one page and needed to start another. The position of these can change depending on the printer and page-size, as well as margins, etc. They also will change in response to edits, but not until Word loads and saves the document. Note these are never inserted by `python-docx` because it has no rendering function. These are generally only useful for text-extraction of existing documents when `python-docx` is being used solely as a document "reader". NOTE: a rendered page-break can occur within a hyperlink; consider a multi-word hyperlink like "excellent Wikipedia article on LLMs" that happens to fall close to the end of the last line on a page such that the page breaks between "Wikipedia" and "article". In such a "page-breaks-in-hyperlink" case, THESE METHODS WILL "MOVE" THE PAGE-BREAK to occur after the hyperlink, such that the entire hyperlink appears in the paragraph returned by `.preceding_paragraph_fragment`. While this places the "tail" text of the hyperlink on the "wrong" page, it avoids having two hyperlinks each with a fragment of the actual text and pointing to the same address. Source code in `src/docx/text/pagebreak.py` ``` def __init__( self, lastRenderedPageBreak: CT_LastRenderedPageBreak, parent: t.ProvidesStoryPart, ): super().__init__(parent) self._element = lastRenderedPageBreak self._lastRenderedPageBreak = lastRenderedPageBreak ``` #### preceding_paragraph_fragment ``` preceding_paragraph_fragment: Paragraph | None ``` A "loose" paragraph containing the content preceding this page-break. Compare `.following_paragraph_fragment` as these two are intended to be used together. This value is `None` when no content precedes this page-break. This case is common and occurs whenever a page breaks on an even paragraph boundary. Returning `None` for this case avoids "inserting" a non-existent paragraph into the content stream. Note that content can include DrawingML items like images or charts. Note the returned paragraph *is divorced from the document body*. Any changes made to it will not be reflected in the document. It is intended to provide a familiar container (`Paragraph`) to interrogate for the content preceding this page-break in the paragraph in which it occured. Contains the entire hyperlink when this break occurs within a hyperlink. #### following_paragraph_fragment ``` following_paragraph_fragment: Paragraph | None ``` A "loose" paragraph containing the content following this page-break. HAS POTENTIALLY SURPRISING BEHAVIORS so read carefully to be sure this is what you want. This is primarily targeted toward text-extraction use-cases for which precisely associating text with the page it occurs on is important. Compare `.preceding_paragraph_fragment` as these two are intended to be used together. This value is `None` when no content follows this page-break. This case is unlikely to occur in practice because Word places even-paragraph-boundary page-breaks on the paragraph *following* the page-break. Still, it is possible and must be checked for. Returning `None` for this case avoids "inserting" an extra, non-existent paragraph into the content stream. Note that content can include DrawingML items like images or charts, not just text. The returned paragraph *is divorced from the document body*. Any changes made to it will not be reflected in the document. It is intended to provide a container (`Paragraph`) with familiar properties and methods that can be used to characterize the paragraph content following a mid-paragraph page-break. Contains no portion of the hyperlink when this break occurs within a hyperlink. ## paragraph Paragraph-related proxy types. ### Paragraph ``` Paragraph(p: CT_P, parent: ProvidesStoryPart) ``` Bases: `StoryChild` Proxy object wrapping a `` element. Source code in `src/docx/text/paragraph.py` ``` def __init__(self, p: CT_P, parent: t.ProvidesStoryPart): super(Paragraph, self).__init__(parent) self._p = self._element = p ``` #### alignment ``` alignment: WD_PARAGRAPH_ALIGNMENT | None ``` A member of the `WdParagraphAlignment` enumeration specifying the justification setting for this paragraph. A value of `None` indicates the paragraph has no directly-applied alignment value and will inherit its alignment value from its style hierarchy. Assigning `None` to this property removes any directly-applied alignment value. #### contains_page_break ``` contains_page_break: bool ``` `True` when one or more rendered page-breaks occur in this paragraph. #### content_controls ``` content_controls: List[ContentControl] ``` The run-level content controls in this paragraph, in document order. The runs inside them appear in `.runs` as though the wrapper were not there; this is how the wrapper itself is reached. #### fields ``` fields: List[Field] ``` A Field for each field in this paragraph, in document order. Outermost first: a field nested in the result of another — a `PAGEREF` inside a table-of-contents entry — follows the field containing it. A complex field can begin in one paragraph and end in a later one, which is what a table of contents does. Such a field does not appear here, in any of the paragraphs it covers, because its extent cannot be determined from one paragraph alone; use Document.fields, which searches the whole body. The fields wholly inside this paragraph, including those in a table-of-contents entry, do appear. #### form_fields ``` form_fields: List[FormField] ``` A FormField instance for each legacy form field in this paragraph. A form field is a complex field, so it may begin in one paragraph and end in another; it is listed with the paragraph its "begin" field-character is in. #### hyperlinks ``` hyperlinks: List[Hyperlink] ``` A Hyperlink instance for each hyperlink in this paragraph. #### numbering ``` numbering: ParagraphNumbering | None ``` The list membership of this paragraph, `None` when it is not in a list. Exposes the list this paragraph belongs to and its level within it: ``` if paragraph.numbering: print(paragraph.numbering.num_id, paragraph.numbering.level) ``` Numbering applied by the paragraph's style is resolved too — that is how the built-in "List Number" and "List Bullet" styles number a paragraph carrying no numbering markup of its own — and ParagraphNumbering.from_style says which it was. #### list_number ``` list_number: str | None ``` The number this paragraph displays as a list item, e.g. `"2."` or `"a)"`. `None` when the paragraph is not in a list. The number is nowhere in the document body — Word computes it from `numbering.xml` at display time — so it is computed here the same way, honouring the level, the start value, `w:lvlRestart` and any `w:startOverride`. Computing it means walking every paragraph before this one, because a list number depends on all of them. Reading this for every paragraph of a document is therefore quadratic; use Document.list_numbers, which walks once. A level whose format is one of the locale-specific ones falls back to decimal; see NumberingLevel.is_renderable. #### math ``` math: List[Math] ``` The equations in this paragraph, in document order. Word stores an equation as OMML (`m:oMath`), a notation of its own with no overlap with the wordprocessing run content, so an equation appears in neither runs nor text: ``` >>> paragraph.text 'The result is for all n' >>> [m.text for m in paragraph.math] ['x2+y2'] ``` **Equation text is deliberately not part of** text. Including it would be more truthful about what the document says, but replace_text and the run-isolating machinery underneath it measure offsets against text and can only cut at run boundaries — text they cannot reach would silently mis-target every replacement after the first equation in a paragraph. A wrong edit is worse than a missing character. #### paragraph_format ``` paragraph_format ``` The ParagraphFormat object providing access to the formatting properties for this paragraph, such as line spacing and indentation. #### original_text ``` original_text: str ``` This paragraph's text as it read before its tracked changes. Deleted text is included and inserted text is not — the reverse of text, which is the document as it now reads. Identical to text for a paragraph carrying no revisions. Neither is "the text with markup shown": Word displays deletions struck through alongside insertions, which is a rendering rather than a string. These two are the two readings that are actually well defined. #### revisions ``` revisions: List[Revision] ``` A Revision for each tracked change in this paragraph, in document order. Includes a revision of the paragraph mark itself, which records that the paragraph was split off from, or merged with, the one after it. #### rendered_page_breaks ``` rendered_page_breaks: List[RenderedPageBreak] ``` All rendered page-breaks in this paragraph. Most often an empty list, sometimes contains one page-break, but can contain more than one is rare or contrived cases. #### runs ``` runs: List[Run] ``` Sequence of Run instances corresponding to the elements in this paragraph. Includes runs wrapped in a run-level `w:sdt` (content control); the content of such a control would otherwise be invisible. #### style ``` style: ParagraphStyle | None ``` Read/Write. \_ParagraphStyle object representing the style assigned to this paragraph. If no explicit style is assigned to this paragraph, its value is the default paragraph style for the document. A paragraph style name can be assigned in lieu of a paragraph style object. Assigning `None` removes any applied style, making its effective value the default paragraph style for the document. #### text ``` text: str ``` The textual content of this paragraph. The text includes the visible-text portion of any hyperlinks in the paragraph. Tabs and line breaks in the XML are mapped to `\t` and `\n` characters respectively. For a paragraph carrying tracked changes this is the text as the document now reads — with every revision accepted, so inserted text is included and deleted text is not. original_text is the reading from before the changes. Assigning text to this property causes all existing paragraph content to be replaced with a single run containing the assigned text. A `\t` character in the text is mapped to a `` element and each `\n` or `\r` character is mapped to a line break. Paragraph-level formatting, such as style, is preserved. All run-level formatting, such as bold or italic, is removed. #### add_run ``` add_run( text: str | None = None, style: str | CharacterStyle | None = None, ) -> Run ``` Append run containing `text` and having character-style `style`. `text` can contain tab (`\t`) characters, which are converted to the appropriate XML form for a tab. `text` can also include newline (`\n`) or carriage return (`\r`) characters, each of which is converted to a line break. When `text` is `None`, the new run is empty. Source code in `src/docx/text/paragraph.py` ``` def add_run(self, text: str | None = None, style: str | CharacterStyle | None = None) -> Run: """Append run containing `text` and having character-style `style`. `text` can contain tab (``\\t``) characters, which are converted to the appropriate XML form for a tab. `text` can also include newline (``\\n``) or carriage return (``\\r``) characters, each of which is converted to a line break. When `text` is `None`, the new run is empty. """ r = self._p.add_r() run = Run(r, self) if text: run.text = text if style: run.style = style return run ``` #### delete ``` delete() -> None ``` Remove this paragraph from the document. Any hyperlink relationship referenced only from this paragraph is dropped, and the surviving half of any comment range or bookmark that started or ended here is removed, so nothing is left pointing at content that is gone. Raises `ValueError` when this is the only paragraph in a table cell: a `w:tc` must contain at least one block-level element and a cell without one produces a document Word refuses to open. Use `_Cell.text = ""` to empty such a cell. Source code in `src/docx/text/paragraph.py` ``` def delete(self) -> None: """Remove this paragraph from the document. Any hyperlink relationship referenced only from this paragraph is dropped, and the surviving half of any comment range or bookmark that started or ended here is removed, so nothing is left pointing at content that is gone. Raises |ValueError| when this is the only paragraph in a table cell: a `w:tc` must contain at least one block-level element and a cell without one produces a document Word refuses to open. Use `_Cell.text = ""` to empty such a cell. """ self._p.assert_deletable() delete_element(self._p, self.part) ``` #### clear ``` clear() ``` Return this same paragraph after removing all its content. Paragraph-level formatting, such as style, is preserved. Source code in `src/docx/text/paragraph.py` ``` def clear(self): """Return this same paragraph after removing all its content. Paragraph-level formatting, such as style, is preserved. """ self._p.clear_content() return self ``` #### add_hyperlink ``` add_hyperlink( text: str, address: str | None = None, fragment: str | None = None, style: str | CharacterStyle | None = "Hyperlink", ) -> Hyperlink ``` Append a hyperlink displaying `text` and return it. `address` is the target URL. `fragment` is the part of a URL after the "#", and is also how an internal link names its target: pass `fragment` alone, with no `address`, to link to a bookmark elsewhere in this document, which is what a cross-reference or a table-of-contents entry is. `style` is the character style applied to the link text, "Hyperlink" by default, which is the style Word uses and which the bundled template defines. Pass `None` to skip styling deliberately, or the name of another character style to use that instead. A named style the document does not define raises `KeyError`, as assigning a missing style always has. The returned Hyperlink exposes its `.runs`, so the link text can be formatted further: ``` link = paragraph.add_hyperlink("python-docx", "https://example.com/") link.runs[0].font.bold = True ``` Raises `ValueError` when neither `address` nor `fragment` is given, which would produce a link that goes nowhere. Source code in `src/docx/text/paragraph.py` ``` def add_hyperlink( self, text: str, address: str | None = None, fragment: str | None = None, style: str | CharacterStyle | None = "Hyperlink", ) -> Hyperlink: """Append a hyperlink displaying `text` and return it. `address` is the target URL. `fragment` is the part of a URL after the "#", and is also how an internal link names its target: pass `fragment` alone, with no `address`, to link to a bookmark elsewhere in this document, which is what a cross-reference or a table-of-contents entry is. `style` is the character style applied to the link text, "Hyperlink" by default, which is the style Word uses and which the bundled template defines. Pass |None| to skip styling deliberately, or the name of another character style to use that instead. A named style the document does not define raises |KeyError|, as assigning a missing style always has. The returned |Hyperlink| exposes its `.runs`, so the link text can be formatted further:: link = paragraph.add_hyperlink("python-docx", "https://example.com/") link.runs[0].font.bold = True Raises |ValueError| when neither `address` nor `fragment` is given, which would produce a link that goes nowhere. """ from docx.text.hyperlink import Hyperlink if not address and not fragment: raise ValueError("hyperlink requires an address, a fragment, or both") hyperlink = self._p.add_hyperlink() if address: # -- reuses the rId of an existing relationship to the same address -- hyperlink.rId = self.part.relate_to(address, RT.HYPERLINK, is_external=True) if fragment: hyperlink.anchor = fragment run = Run(hyperlink.add_r(), self) run.text = text if style is not None: run.style = style return Hyperlink(hyperlink, self._parent) ``` #### add_bookmark ``` add_bookmark(name: str) -> Bookmark ``` Return a Bookmark named `name` spanning the content of this paragraph. Use `Run.mark_bookmark_range()` to bookmark a narrower range. `name` must be unique in the document; Word treats a duplicate name as a second bookmark and the two then compete for anything referring to the name. Source code in `src/docx/text/paragraph.py` ``` def add_bookmark(self, name: str) -> Bookmark: """Return a |Bookmark| named `name` spanning the content of this paragraph. Use `Run.mark_bookmark_range()` to bookmark a narrower range. `name` must be unique in the document; Word treats a duplicate name as a second bookmark and the two then compete for anything referring to the name. """ from docx.bookmark import Bookmark bookmarkStart = self._p.add_bookmark_around_content(self.part.next_bookmark_id, name) return Bookmark(bookmarkStart, self) ``` #### add_field ``` add_field( instruction: str, *, dirty: bool = True, simple: bool = False, result: str | None = None, ) -> Field ``` Append a field for `instruction` and return it. `instruction` is the field code including its switches, for example `"PAGE"` or `r'TOC \o "1-3" \h'`. The builders in `docx.fields` write the ones people usually want: ``` from docx import fields paragraph.add_field(fields.page_number()) paragraph.add_field(fields.table_of_contents(levels=(1, 2))) paragraph.add_field(fields.cross_reference("intro")) ``` **The result is not computed here and cannot be.** A `PAGE` field has no page number and a `TOC` is empty until Word opens the document and works them out. `dirty` sets `w:dirty`, asking Word to refresh this field on open; setting Settings.update_fields_on_open asks it to refresh every field, which is what a generated table of contents needs. A complex field is written by default, as Word does. Pass `simple` to write a `w:fldSimple` instead, which is more compact and equally valid but which some other consumers handle less well. `result` supplies a cached result to display until Word refreshes the field; it is only meaningful for a simple field, and passing it for a complex one raises `ValueError`. Source code in `src/docx/text/paragraph.py` ``` def add_field( self, instruction: str, *, dirty: bool = True, simple: bool = False, result: str | None = None, ) -> Field: """Append a field for `instruction` and return it. `instruction` is the field code including its switches, for example ``"PAGE"`` or ``r'TOC \\o "1-3" \\h'``. The builders in :mod:`docx.fields` write the ones people usually want:: from docx import fields paragraph.add_field(fields.page_number()) paragraph.add_field(fields.table_of_contents(levels=(1, 2))) paragraph.add_field(fields.cross_reference("intro")) **The result is not computed here and cannot be.** A `PAGE` field has no page number and a `TOC` is empty until Word opens the document and works them out. `dirty` sets `w:dirty`, asking Word to refresh this field on open; setting :attr:`.Settings.update_fields_on_open` asks it to refresh every field, which is what a generated table of contents needs. A complex field is written by default, as Word does. Pass `simple` to write a `w:fldSimple` instead, which is more compact and equally valid but which some other consumers handle less well. `result` supplies a cached result to display until Word refreshes the field; it is only meaningful for a simple field, and passing it for a complex one raises |ValueError|. """ from docx.fields import Field, new_complex_field instruction = f" {instruction.strip()} " if simple: fldSimple = cast("CT_SimpleField", OxmlElement("w:fldSimple")) fldSimple.instr = instruction if dirty: fldSimple.dirty = True if result: Run(fldSimple.add_r(), self).text = result self._p.append(fldSimple) return Field(fldSimple, self, instruction, result or "") if result is not None: raise ValueError( "`result` applies only to a simple field; a complex field's cached" " result is the content between its 'separate' and 'end' field" " characters, which Word writes when it computes the result" ) runs = new_complex_field(instruction, dirty=dirty) for r in runs: self._p.append(r) begin = cast("CT_FldChar", runs[0][0]) return Field(begin, self, instruction, "") ``` #### insert_paragraph_before ``` insert_paragraph_before( text: str | None = None, style: str | ParagraphStyle | None = None, ) -> Paragraph ``` Return a newly created paragraph, inserted directly before this paragraph. If `text` is supplied, the new paragraph contains that text in a single run. If `style` is provided, that style is assigned to the new paragraph. Source code in `src/docx/text/paragraph.py` ``` def insert_paragraph_before( self, text: str | None = None, style: str | ParagraphStyle | None = None ) -> Paragraph: """Return a newly created paragraph, inserted directly before this paragraph. If `text` is supplied, the new paragraph contains that text in a single run. If `style` is provided, that style is assigned to the new paragraph. """ paragraph = self._insert_paragraph_before() if text: paragraph.add_run(text) if style is not None: paragraph.style = style return paragraph ``` #### copy_to ``` copy_to( container: BlockItemContainer | Document, *, before: Paragraph | Table | None = None, after: Paragraph | Table | None = None, missing_style: str = "copy", ) -> Paragraph ``` Return a copy of this paragraph, newly placed in `container`. Duplicating a template paragraph is the most common thing people write by hand against this library, and the hand-written version has the bugs below: ``` new = paragraph.copy_to(document) new = paragraph.copy_to(cell, before=cell.paragraphs[0]) ``` `container` is where the copy goes — a Document, a table \_Cell, a header or any other block-item container. `before` and `after` place the copy relative to an existing paragraph or table in that container; with neither, it is appended. Everything a deep copy would get wrong is repaired: - **Relationships.** A picture's `r:embed` and a hyperlink's `r:id` name relationships of the *source* part, which mean something else or nothing in the destination. They are related in afresh. Relating the same image blob back in gives the sha1 deduplication for free, so a copy within one document does not duplicate the media. - **Drawing ids.** `wp:docPr/@id` must be unique document-wide; each copied drawing is reassigned one that is free in the destination. - **Bookmarks.** These are *dropped* rather than duplicated. A bookmark name is document-wide, and a second bookmark of the same name is not a copy — anything referring to the name resolves to whichever it happens to find first. Use `add_bookmark()` on the copy to bookmark it afresh. Copying into a *different* document also has to resolve what the content refers to there. A style the destination does not define is copied across with its `w:basedOn` / `w:next` / `w:link` closure, and a numbering definition is copied and the reference repointed, so a numbered paragraph does not silently join whatever list happens to hold that id here. `missing_style` chooses what happens instead: `"copy"` (the default) brings the style over, `"drop"` removes the reference so the content takes the destination's default, and `"raise"` raises `ValueError`. Raises `ValueError` when both `before` and `after` are given. Source code in `src/docx/text/paragraph.py` ``` def copy_to( self, container: BlockItemContainer | Document, *, before: Paragraph | Table | None = None, after: Paragraph | Table | None = None, missing_style: str = "copy", ) -> Paragraph: """Return a copy of this paragraph, newly placed in `container`. Duplicating a template paragraph is the most common thing people write by hand against this library, and the hand-written version has the bugs below:: new = paragraph.copy_to(document) new = paragraph.copy_to(cell, before=cell.paragraphs[0]) `container` is where the copy goes — a |Document|, a table |_Cell|, a header or any other block-item container. `before` and `after` place the copy relative to an existing paragraph or table in that container; with neither, it is appended. Everything a deep copy would get wrong is repaired: - **Relationships.** A picture's `r:embed` and a hyperlink's `r:id` name relationships of the *source* part, which mean something else or nothing in the destination. They are related in afresh. Relating the same image blob back in gives the sha1 deduplication for free, so a copy within one document does not duplicate the media. - **Drawing ids.** `wp:docPr/@id` must be unique document-wide; each copied drawing is reassigned one that is free in the destination. - **Bookmarks.** These are *dropped* rather than duplicated. A bookmark name is document-wide, and a second bookmark of the same name is not a copy — anything referring to the name resolves to whichever it happens to find first. Use `add_bookmark()` on the copy to bookmark it afresh. Copying into a *different* document also has to resolve what the content refers to there. A style the destination does not define is copied across with its `w:basedOn` / `w:next` / `w:link` closure, and a numbering definition is copied and the reference repointed, so a numbered paragraph does not silently join whatever list happens to hold that id here. `missing_style` chooses what happens instead: ``"copy"`` (the default) brings the style over, ``"drop"`` removes the reference so the content takes the destination's default, and ``"raise"`` raises |ValueError|. Raises |ValueError| when both `before` and `after` are given. """ from docx.copy import copy_content, destination_for, place dest_part, dest_element = destination_for(container) new_p = copy_content(self._p, self.part, dest_part, missing_style=missing_style) place(new_p, dest_element, before, after) return Paragraph(new_p, container) # pyright: ignore[reportArgumentType] ``` #### iter_inner_content ``` iter_inner_content() -> Iterator[Run | Hyperlink] ``` Generate the runs and hyperlinks in this paragraph, in the order they appear. The content in a paragraph consists of both runs and hyperlinks. This method allows accessing each of those separately, in document order, for when the precise position of the hyperlink within the paragraph text is important. Note that a hyperlink itself contains runs. Source code in `src/docx/text/paragraph.py` ``` def iter_inner_content(self) -> Iterator[Run | Hyperlink]: """Generate the runs and hyperlinks in this paragraph, in the order they appear. The content in a paragraph consists of both runs and hyperlinks. This method allows accessing each of those separately, in document order, for when the precise position of the hyperlink within the paragraph text is important. Note that a hyperlink itself contains runs. """ for r_or_hlink in self._p.inner_content_elements: yield ( Run(r_or_hlink, self) if isinstance(r_or_hlink, CT_R) else Hyperlink(r_or_hlink, self) ) ``` #### isolate_run ``` isolate_run(start: int, end: int) -> Run ``` Return the character range `[start, end)` of this paragraph as a single run. The runs covering the range are split as needed so that the range is exactly one run, which can then be formatted independently of the text around it: ``` paragraph.text = "the important part matters" paragraph.isolate_run(4, 13).bold = True ``` Offsets are measured against text, so a tab counts as one character and a line break as one newline. Word splits a paragraph into runs for reasons unrelated to formatting, so the range being asked for is very often not a run already; that is what this is for. Where the range already lies within one run and covers all of it, that run is returned unchanged. When the range spans runs with different formatting they are merged, and the formatting of the run containing `start` applies to the whole range. Raises `ValueError` if the range spans a hyperlink boundary, where merging would move text into or out of the link: replace or format the parts separately, or use replace_text, which handles such a range without merging. Source code in `src/docx/text/paragraph.py` ``` def isolate_run(self, start: int, end: int) -> Run: """Return the character range `[start, end)` of this paragraph as a single run. The runs covering the range are split as needed so that the range is exactly one run, which can then be formatted independently of the text around it:: paragraph.text = "the important part matters" paragraph.isolate_run(4, 13).bold = True Offsets are measured against :attr:`text`, so a tab counts as one character and a line break as one newline. Word splits a paragraph into runs for reasons unrelated to formatting, so the range being asked for is very often not a run already; that is what this is for. Where the range already lies within one run and covers all of it, that run is returned unchanged. When the range spans runs with different formatting they are merged, and the formatting of the run containing `start` applies to the whole range. Raises |ValueError| if the range spans a hyperlink boundary, where merging would move text into or out of the link: replace or format the parts separately, or use :meth:`replace_text`, which handles such a range without merging. """ from docx.oxml.text.isolate import isolate_range runs = isolate_range(self._p, start, end) if not runs: raise ValueError( f"character range ({start}, {end}) is empty or lies beyond the end of" " the paragraph text" ) first = runs[0] parent = first.getparent() if any(r.getparent() is not parent for r in runs[1:]): raise ValueError( f"character range ({start}, {end}) spans a hyperlink or similar" " boundary and cannot be isolated into a single run" ) for r in runs[1:]: for element in r.xpath("./*[not(self::w:rPr)]"): first.append(element) parent.remove(r) return Run(first, self) ``` #### set_numbering ``` set_numbering(num_id: int, level: int = 0) -> None ``` Put this paragraph in the list `num_id` at `level`. This is how a paragraph joins an existing list, or starts one, without editing the numbering part by hand: ``` first = document.add_paragraph("one", style="List Number") second = document.add_paragraph("two") second.set_numbering(first.numbering.num_id) ``` `num_id` must name a list already defined in the numbering part; use Document.numbering to find one. Applying numbering directly like this overrides whatever the paragraph's style would apply. Source code in `src/docx/text/paragraph.py` ``` def set_numbering(self, num_id: int, level: int = 0) -> None: """Put this paragraph in the list `num_id` at `level`. This is how a paragraph joins an existing list, or starts one, without editing the numbering part by hand:: first = document.add_paragraph("one", style="List Number") second = document.add_paragraph("two") second.set_numbering(first.numbering.num_id) `num_id` must name a list already defined in the numbering part; use :attr:`.Document.numbering` to find one. Applying numbering directly like this overrides whatever the paragraph's style would apply. """ numPr = self._p.get_or_add_pPr().get_or_add_numPr() numPr.numId_val = num_id numPr.ilvl_val = level ``` #### remove_numbering ``` remove_numbering() -> None ``` Take this paragraph out of any list it is in. Where the numbering comes from the paragraph's style rather than the paragraph, a `w:numId` of 0 is written, which is how Word switches numbering off for one paragraph without changing its style. Source code in `src/docx/text/paragraph.py` ``` def remove_numbering(self) -> None: """Take this paragraph out of any list it is in. Where the numbering comes from the paragraph's style rather than the paragraph, a `w:numId` of 0 is written, which is how Word switches numbering off for one paragraph without changing its style. """ pPr = self._p.pPr if pPr is None: return if self.numbering is not None and self.numbering.from_style: numPr = pPr.get_or_add_numPr() numPr.numId_val = 0 numPr.ilvl_val = None return pPr._remove_numPr() # pyright: ignore[reportPrivateUsage] ``` #### restart_numbering ``` restart_numbering(start: int = 1) -> int ``` Restart the list this paragraph is in, so it begins again at `start`. Returns the `num_id` of the new list. Raises `ValueError` when this paragraph is not in a list. In OOXML a list is not restarted by resetting a counter — there is no counter to reset. A second `w:num` is created on the same abstract definition, carrying a `w:startOverride`, and the paragraphs that should begin again are pointed at it. This paragraph and every later one in the same list are repointed, which is what Word's own "Restart at 1" does; paragraphs before it keep the original sequence. Source code in `src/docx/text/paragraph.py` ``` def restart_numbering(self, start: int = 1) -> int: """Restart the list this paragraph is in, so it begins again at `start`. Returns the `num_id` of the new list. Raises |ValueError| when this paragraph is not in a list. In OOXML a list is not restarted by resetting a counter — there is no counter to reset. A second `w:num` is created on the same abstract definition, carrying a `w:startOverride`, and the paragraphs that should begin again are pointed at it. This paragraph and every later one in the same list are repointed, which is what Word's own "Restart at 1" does; paragraphs before it keep the original sequence. """ from docx.numbering import Numbering numbering_info = self.numbering if numbering_info is None: raise ValueError("this paragraph is not in a list, so has no numbering to restart") part = self.part.document_part numbering = Numbering(part.numbering_part.element, part) new_definition = numbering.restart( numbering_info.num_id, ilvl=numbering_info.level, start=start ) # -- each repointed paragraph keeps its own level; a restart changes which list # -- a paragraph is in, not how deeply nested it is -- for p, ilvl in self._following_paragraphs_in_list(numbering_info.num_id): Paragraph(p, self._parent).set_numbering(new_definition.num_id, ilvl) return new_definition.num_id ``` #### replace_text ``` replace_text( old: str, new: str, *, count: int = -1, regex: bool = False, flags: int = 0, ) -> int ``` Replace occurrences of `old` with `new` in this paragraph; return how many. The match is made against text, so it succeeds whether or not Word split the text across runs — which it routinely does, for spell-check state, language tagging and revision marks. This is why assigning to `run.text` so often appears to do nothing. `new` takes the formatting of the run holding the first replaced character. When the match spans runs formatted differently, the rest of the matched text is removed along with its formatting; the runs themselves stay, so a hyperlink, bookmark, comment range or field only partly covered keeps its structure. `count` limits the number of replacements, -1 meaning all of them. Set `regex` to treat `old` as a regular expression, in which case `new` may refer to capture groups as `\1` or `\g`; `flags` is passed to `re.compile`. Without `regex`, `old` is matched literally however many metacharacters it contains. Text inside a content control is replaced too, and a control showing its placeholder is marked as holding a real value, since that is what it now holds. A field instruction (`w:instrText`) is never matched or altered — it is not document text, and editing one breaks the field. Source code in `src/docx/text/paragraph.py` ``` def replace_text( self, old: str, new: str, *, count: int = -1, regex: bool = False, flags: int = 0, ) -> int: """Replace occurrences of `old` with `new` in this paragraph; return how many. The match is made against :attr:`text`, so it succeeds whether or not Word split the text across runs — which it routinely does, for spell-check state, language tagging and revision marks. This is why assigning to `run.text` so often appears to do nothing. `new` takes the formatting of the run holding the first replaced character. When the match spans runs formatted differently, the rest of the matched text is removed along with its formatting; the runs themselves stay, so a hyperlink, bookmark, comment range or field only partly covered keeps its structure. `count` limits the number of replacements, -1 meaning all of them. Set `regex` to treat `old` as a regular expression, in which case `new` may refer to capture groups as ``\\1`` or ``\\g``; `flags` is passed to :func:`re.compile`. Without `regex`, `old` is matched literally however many metacharacters it contains. Text inside a content control is replaced too, and a control showing its placeholder is marked as holding a real value, since that is what it now holds. A field instruction (`w:instrText`) is never matched or altered — it is not document text, and editing one breaks the field. """ from docx.text.search import compile_pattern, replace_in_paragraph pattern = compile_pattern(old, regex, flags) return replace_in_paragraph(self._p, pattern, new, count, regex) ``` #### accept_all_revisions ``` accept_all_revisions() -> int ``` Accept every tracked change in this paragraph; return how many were applied. See Revision.accept. Source code in `src/docx/text/paragraph.py` ``` def accept_all_revisions(self) -> int: """Accept every tracked change in this paragraph; return how many were applied. See :meth:`.Revision.accept`. """ from docx.revisions import apply_all return apply_all(self._p, self, accept=True) ``` #### reject_all_revisions ``` reject_all_revisions() -> int ``` Reject every tracked change in this paragraph; return how many were applied. See Revision.reject. Source code in `src/docx/text/paragraph.py` ``` def reject_all_revisions(self) -> int: """Reject every tracked change in this paragraph; return how many were applied. See :meth:`.Revision.reject`. """ from docx.revisions import apply_all return apply_all(self._p, self, accept=False) ``` ## parfmt Paragraph-related proxy types. ### \_ParagraphBorders ``` _ParagraphBorders(parfmt: ParagraphFormat) ``` Bases: `_Borders` The border edges of a paragraph, `paragraph_format.borders`. Source code in `src/docx/text/parfmt.py` ``` def __init__(self, parfmt: ParagraphFormat): super().__init__(CT_PBdr.edges) self._parfmt = parfmt ``` ### ParagraphFormat ``` ParagraphFormat( element: BaseOxmlElement, parent: ProvidesXmlPart | None = None, ) ``` Bases: `ElementProxy` Provides access to paragraph formatting such as justification, indentation, line spacing, space before and after, and widow/orphan control. Source code in `src/docx/shared.py` ``` def __init__(self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None): self._element = element self._parent = parent ``` #### alignment ``` alignment ``` A member of the `WdParagraphAlignment` enumeration specifying the justification setting for this paragraph. A value of `None` indicates paragraph alignment is inherited from the style hierarchy. #### first_line_indent ``` first_line_indent ``` Length value specifying the relative difference in indentation for the first line of the paragraph. A positive value causes the first line to be indented. A negative value produces a hanging indent. `None` indicates first line indentation is inherited from the style hierarchy. #### bidi ``` bidi: bool | None ``` `True` when this paragraph's base direction is right-to-left. This is the paragraph's *reading* direction — which edge the text starts from, where the punctuation lands, which way the indents and the list bullet face. Setting `Font.rtl` on the runs is not a substitute: the runs render right-to-left inside a paragraph still laid out left-to-right, which is subtly rather than obviously wrong. `None` indicates the value is inherited, from the section's own `bidi` and ultimately from the style hierarchy — it does not mean `False`. #### first_line_indent_chars ``` first_line_indent_chars: int | None ``` First-line indent in hundredths of a character, or `None` when not set. The character-unit counterpart of first_line_indent. Word's paragraph dialogue offers "2 ch" as the first-line indent unit for a CJK document and writes `w:firstLineChars="200"`, often with no twips companion — on such a document first_line_indent is `None` although Word plainly shows an indent. **The value is in hundredths**, matching the XML: 2 characters reads as `200`, not `2.0`. These are deliberately not Length values, since a character has no fixed size and nothing on Length could express one. A negative value means a hanging indent, as for first_line_indent. Assigning clears the twips attributes, because Word prefers the character value where both are present and leaving the two disagreeing changes the layout. #### left_indent_chars ``` left_indent_chars: int | None ``` Left indent in hundredths of a character, or `None` when not set. See first_line_indent_chars for the unit. Assigning clears the twips sibling. #### right_indent_chars ``` right_indent_chars: int | None ``` Right indent in hundredths of a character, or `None` when not set. See first_line_indent_chars for the unit. Assigning clears the twips sibling. #### space_after_lines ``` space_after_lines: int | None ``` Space after the paragraph in hundredths of a line, or `None` when not set. The line-relative counterpart of space_after, in the same hundredths unit as first_line_indent_chars — `50` is half a line. Word writes this for a CJK document alongside, or instead of, the twips value. #### space_before_lines ``` space_before_lines: int | None ``` Space before the paragraph in hundredths of a line, or `None` when not set. See space_after_lines. #### text_direction ``` text_direction: WD_TEXT_DIRECTION | None ``` Flow direction of the text in this paragraph, or `None` when inherited. This is the vertical-writing knob, and a different thing from bidi: it says which way the lines run and whether the glyphs are rotated, not which direction the text reads in. #### keep_together ``` keep_together ``` `True` if the paragraph should be kept "in one piece" and not broken across a page boundary when the document is rendered. `None` indicates its effective value is inherited from the style hierarchy. #### keep_with_next ``` keep_with_next ``` `True` if the paragraph should be kept on the same page as the subsequent paragraph when the document is rendered. For example, this property could be used to keep a section heading on the same page as its first paragraph. `None` indicates its effective value is inherited from the style hierarchy. #### left_indent ``` left_indent ``` Length value specifying the space between the left margin and the left side of the paragraph. `None` indicates the left indent value is inherited from the style hierarchy. Use an Inches value object as a convenient way to apply indentation in units of inches. #### line_spacing ``` line_spacing ``` `float` or Length value specifying the space between baselines in successive lines of the paragraph. A value of `None` indicates line spacing is inherited from the style hierarchy. A float value, e.g. `2.0` or `1.75`, indicates spacing is applied in multiples of line heights. A Length value such as `Pt(12)` indicates spacing is a fixed height. The Pt value class is a convenient way to apply line spacing in units of points. Assigning `None` resets line spacing to inherit from the style hierarchy. #### line_spacing_rule ``` line_spacing_rule ``` A member of the WdLineSpacing enumeration indicating how the value of line_spacing should be interpreted. Assigning any of the WdLineSpacing members SINGLE, DOUBLE, or ONE_POINT_FIVE will cause the value of line_spacing to be updated to produce the corresponding line spacing. #### outline_level ``` outline_level: int | None ``` Outline level of this paragraph, from 0 (top level) to 9. The outline level drives the document map that navigation panes and PDF bookmarks are built from. Level 9 is Word's "Body Text", meaning the paragraph is deliberately excluded from the outline; `None` means no level is set here and the effective value is inherited from the style hierarchy. Setting this does not change how the paragraph is rendered. #### shading_fill ``` shading_fill ``` Background shading color applied behind the whole paragraph. An RGBColor value, the string "auto", or `None` when no shading is applied. Assigning a hex string such as "FF0000" or "#FF0000" is also accepted. Use `Font.shading_fill` to shade individual runs instead. #### shading_pattern ``` shading_pattern: WD_SHADING_PATTERN | None ``` The pattern drawn over the shading behind the whole paragraph. A WD_SHADING_PATTERN member, or `None` when no shading is applied. Word writes |WD_SHADING_PATTERN.CLEAR| for an ordinary background color, which is what `.shading_fill` produces on its own. Assigning `None` removes the shading entirely, the same as assigning `None` to `.shading_fill`. #### shading_color ``` shading_color ``` The foreground color of the shading pattern behind this paragraph. An RGBColor value, the string "auto", or `None`. This is the color the `.shading_pattern` is drawn *in*; `.shading_fill` is the color behind it. For the usual |WD_SHADING_PATTERN.CLEAR| pattern nothing is drawn and this has no visible effect. #### page_break_before ``` page_break_before ``` `True` if the paragraph should appear at the top of the page following the prior paragraph. `None` indicates its effective value is inherited from the style hierarchy. #### right_indent ``` right_indent ``` Length value specifying the space between the right margin and the right side of the paragraph. `None` indicates the right indent value is inherited from the style hierarchy. Use a Cm value object as a convenient way to apply indentation in units of centimeters. #### space_after ``` space_after ``` Length value specifying the spacing to appear between this paragraph and the subsequent paragraph. `None` indicates this value is inherited from the style hierarchy. Length objects provide convenience properties, such as pt and inches, that allow easy conversion to various length units. #### space_before ``` space_before ``` Length value specifying the spacing to appear between this paragraph and the prior paragraph. `None` indicates this value is inherited from the style hierarchy. Length objects provide convenience properties, such as pt and cm, that allow easy conversion to various length units. #### widow_control ``` widow_control ``` `True` if the first and last lines in the paragraph remain on the same page as the rest of the paragraph when Word repaginates the document. `None` indicates its effective value is inherited from the style hierarchy. #### borders ``` borders() -> _ParagraphBorders ``` The border edges of this paragraph, as a mapping keyed by edge name: ``` paragraph.paragraph_format.borders["bottom"].line = WD_LINE_STYLE.SINGLE ``` A paragraph with only a bottom border and no text is how Word draws a horizontal rule; there is no other way to draw one. Beyond the four sides, a paragraph admits two edges a table does not. `between` is the border drawn *between* consecutive paragraphs that share identical border settings, rather than an edge of any one paragraph, and `bar` is the vertical bar drawn beside it. Source code in `src/docx/text/parfmt.py` ``` @lazyproperty def borders(self) -> _ParagraphBorders: """The border edges of this paragraph, as a mapping keyed by edge name:: paragraph.paragraph_format.borders["bottom"].line = WD_LINE_STYLE.SINGLE A paragraph with only a bottom border and no text is how Word draws a horizontal rule; there is no other way to draw one. Beyond the four sides, a paragraph admits two edges a table does not. `between` is the border drawn *between* consecutive paragraphs that share identical border settings, rather than an edge of any one paragraph, and `bar` is the vertical bar drawn beside it. """ return _ParagraphBorders(self) ``` #### mark_font ``` mark_font() -> Font ``` The run properties of the paragraph mark — the ¶ itself. These are neither the properties of any run in the paragraph nor the paragraph's style: they are the formatting of the mark character, stored in `w:pPr/w:rPr`: ``` paragraph.paragraph_format.mark_font.size = Pt(8) ``` It matters more than it sounds. The mark's font size participates in the line height of the paragraph's last line, so a paragraph whose runs are all 8pt but whose mark is 24pt renders with a tall final line. An empty paragraph has no runs at all, so the mark's properties are the only place its formatting lives — the height of a blank spacer paragraph is not expressible any other way. And assigning `Paragraph.text` discards the runs and rebuilds them while the mark's properties survive, which can leave a rewritten paragraph looking wrong. Named `mark_font` rather than `font` because "the paragraph's font" reads as the font of the paragraph's text, which this is not. Source code in `src/docx/text/parfmt.py` ``` @lazyproperty def mark_font(self) -> Font: """The run properties of the paragraph mark — the ¶ itself. These are neither the properties of any run in the paragraph nor the paragraph's style: they are the formatting of the mark character, stored in `w:pPr/w:rPr`:: paragraph.paragraph_format.mark_font.size = Pt(8) It matters more than it sounds. The mark's font size participates in the line height of the paragraph's last line, so a paragraph whose runs are all 8pt but whose mark is 24pt renders with a tall final line. An empty paragraph has no runs at all, so the mark's properties are the only place its formatting lives — the height of a blank spacer paragraph is not expressible any other way. And assigning `Paragraph.text` discards the runs and rebuilds them while the mark's properties survive, which can leave a rewritten paragraph looking wrong. Named `mark_font` rather than `font` because "the paragraph's font" reads as the font of the paragraph's text, which this is not. """ return Font(self._element.get_or_add_pPr()) # pyright: ignore[reportArgumentType] ``` #### tab_stops ``` tab_stops() ``` TabStops object providing access to the tab stops defined for this paragraph format. Source code in `src/docx/text/parfmt.py` ``` @lazyproperty def tab_stops(self): """|TabStops| object providing access to the tab stops defined for this paragraph format.""" pPr = self._element.get_or_add_pPr() return TabStops(pPr) ``` ## run Run-related proxy objects for python-docx, Run in particular. ### Run ``` Run(r: CT_R, parent: ProvidesStoryPart) ``` Bases: `StoryChild` Proxy object wrapping `` element. Several of the properties on Run take a tri-state value, `True`, `False`, or `None`. `True` and `False` correspond to on and off respectively. `None` indicates the property is not specified directly on the run and its effective value is taken from the style hierarchy. Source code in `src/docx/text/run.py` ``` def __init__(self, r: CT_R, parent: t.ProvidesStoryPart): super().__init__(parent) self._r = self._element = self.element = r ``` #### bold ``` bold: bool | None ``` Read/write tri-state value. When `True`, causes the text of the run to appear in bold face. When `False`, the text unconditionally appears non-bold. When `None` the bold setting for this run is inherited from the style hierarchy. #### embedded_objects ``` embedded_objects: List[EmbeddedObject] ``` The OLE objects embedded in this run, in document order. #### contains_page_break ``` contains_page_break: bool ``` `True` when one or more rendered page-breaks occur in this run. Note that "hard" page-breaks inserted by the author are not included. A hard page-break gives rise to a rendered page-break in the right position so if those were included that page-break would be "double-counted". It would be very rare for multiple rendered page-breaks to occur in a single run, but it is possible. #### font ``` font: Font ``` The Font object providing access to the character formatting properties for this run, such as font name and size. #### italic ``` italic: bool | None ``` Read/write tri-state value. When `True`, causes the text of the run to appear in italics. When `False`, the text unconditionally appears non-italic. When `None` the italic setting for this run is inherited from the style hierarchy. #### style ``` style: CharacterStyle ``` Read/write. A CharacterStyle object representing the character style applied to this run. The default character style for the document (often `Default Character Font`) is returned if the run has no directly-applied character style. Setting this property to `None` removes any directly-applied character style. #### text ``` text: str ``` String formed by concatenating the text equivalent of each run. Each `` element adds the text characters it contains. A `` element adds a `\t` character. A `` or `` element each add a `\n` character. Note that a `` element can indicate a page break or column break as well as a line break. Only line-break `` elements translate to a `\n` character. Others are ignored. All other content child elements, such as ``, are ignored. Assigning text to this property has the reverse effect, translating each `\t` character to a `` element and each `\n` or `\r` character to a `` element. Any existing run content is replaced. Run formatting is preserved. #### underline ``` underline: bool | WD_UNDERLINE | None ``` The underline style for this Run. Value is one of `None`, `True`, `False`, or a member of WdUnderline. A value of `None` indicates the run has no directly-applied underline value and so will inherit the underline value of its containing paragraph. Assigning `None` to this property removes any directly-applied underline value. A value of `False` indicates a directly-applied setting of no underline, overriding any inherited value. A value of `True` indicates single underline. The values from WdUnderline are used to specify other outline styles such as double, wavy, and dotted. #### add_break ``` add_break(break_type: WD_BREAK = LINE) ``` Add a break element of `break_type` to this run. `break_type` can take the values `WD_BREAK.LINE`, `WD_BREAK.PAGE`, and `WD_BREAK.COLUMN` where `WD_BREAK` is imported from `docx.enum.text`. `break_type` defaults to `WD_BREAK.LINE`. Source code in `src/docx/text/run.py` ``` def add_break(self, break_type: WD_BREAK = WD_BREAK.LINE): """Add a break element of `break_type` to this run. `break_type` can take the values `WD_BREAK.LINE`, `WD_BREAK.PAGE`, and `WD_BREAK.COLUMN` where `WD_BREAK` is imported from `docx.enum.text`. `break_type` defaults to `WD_BREAK.LINE`. """ type_, clear = { WD_BREAK.LINE: (None, None), WD_BREAK.PAGE: ("page", None), WD_BREAK.COLUMN: ("column", None), WD_BREAK.LINE_CLEAR_LEFT: ("textWrapping", "left"), WD_BREAK.LINE_CLEAR_RIGHT: ("textWrapping", "right"), WD_BREAK.LINE_CLEAR_ALL: ("textWrapping", "all"), }[break_type] br = self._r.add_br() if type_ is not None: br.type = type_ if clear is not None: br.clear = clear ``` #### add_picture ``` add_picture( image_path_or_stream: str | PathLike[str] | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, description: str | None = None, title: str | None = None, svg_fallback: str | PathLike[str] | IO[bytes] | None = None, honor_exif_orientation: bool = True, ) -> InlineShape ``` Return InlineShape containing image identified by `image_path_or_stream`. The picture is added to the end of this run. `image_path_or_stream` can be a path (a string) or a file-like object containing a binary image. If neither width nor height is specified, the picture appears at its native size. If only one is specified, it is used to compute a scaling factor that is then applied to the unspecified dimension, preserving the aspect ratio of the image. The native size of the picture is calculated using the dots- per-inch (dpi) value specified in the image file, defaulting to 72 dpi if no value is specified, as is often the case. `description` is the picture's alternative text, which is what a screen reader announces and what an accessibility check looks for. `title` is the separate, rarely-used caption-like field Word writes alongside it. Both are omitted from the XML when `None`. `svg_fallback` applies only when the picture is an SVG. Word records an SVG alongside a raster rendering of it and shows the raster one wherever the vector source cannot be used, so passing a PNG or JPEG here is what makes the picture appear in an older Word, in a PDF export from some tools, and anywhere else the SVG extension is not understood. Without it the fallback refers to the SVG itself, which Word 2016 and later render but earlier versions do not. `honor_exif_orientation` applies the image's EXIF `Orientation` tag, which a photo off a phone or camera almost always carries: the pixels are stored in the sensor's native orientation and the tag says how to turn them for display. Every image viewer, browser and word processor honours it, and a library that inserts pictures and does not produces a visibly wrong document from a correct input file. The rotation is written into the DrawingML (`a:xfrm/@rot`) rather than into the pixels, so the image part stays byte-identical and the sha1 deduplication keeps working. Pass `False` for an image whose pixels are already rotated *and* which carries the tag anyway — some encoders write both and there is no reliable way to detect it. Source code in `src/docx/text/run.py` ``` def add_picture( self, image_path_or_stream: str | os.PathLike[str] | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, description: str | None = None, title: str | None = None, svg_fallback: str | os.PathLike[str] | IO[bytes] | None = None, honor_exif_orientation: bool = True, ) -> InlineShape: """Return |InlineShape| containing image identified by `image_path_or_stream`. The picture is added to the end of this run. `image_path_or_stream` can be a path (a string) or a file-like object containing a binary image. If neither width nor height is specified, the picture appears at its native size. If only one is specified, it is used to compute a scaling factor that is then applied to the unspecified dimension, preserving the aspect ratio of the image. The native size of the picture is calculated using the dots- per-inch (dpi) value specified in the image file, defaulting to 72 dpi if no value is specified, as is often the case. `description` is the picture's alternative text, which is what a screen reader announces and what an accessibility check looks for. `title` is the separate, rarely-used caption-like field Word writes alongside it. Both are omitted from the XML when |None|. `svg_fallback` applies only when the picture is an SVG. Word records an SVG alongside a raster rendering of it and shows the raster one wherever the vector source cannot be used, so passing a PNG or JPEG here is what makes the picture appear in an older Word, in a PDF export from some tools, and anywhere else the SVG extension is not understood. Without it the fallback refers to the SVG itself, which Word 2016 and later render but earlier versions do not. `honor_exif_orientation` applies the image's EXIF `Orientation` tag, which a photo off a phone or camera almost always carries: the pixels are stored in the sensor's native orientation and the tag says how to turn them for display. Every image viewer, browser and word processor honours it, and a library that inserts pictures and does not produces a visibly wrong document from a correct input file. The rotation is written into the DrawingML (`a:xfrm/@rot`) rather than into the pixels, so the image part stays byte-identical and the sha1 deduplication keeps working. Pass |False| for an image whose pixels are already rotated *and* which carries the tag anyway — some encoders write both and there is no reliable way to detect it. """ inline = self.part.new_pic_inline( image_path_or_stream, width, height, description=description, title=title, svg_fallback=svg_fallback, honor_exif_orientation=honor_exif_orientation, ) self._r.add_drawing(inline) return InlineShape(inline, self) ``` #### add_float_picture ``` add_float_picture( image_path_or_stream: str | PathLike[str] | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, left: Length | int = 0, top: Length | int = 0, wrap_type: WD_WRAP_TYPE = SQUARE, behind_text: bool = False, relative_from_h: WD_ANCHOR_RELATIVE_FROM_H = COLUMN, relative_from_v: WD_ANCHOR_RELATIVE_FROM_V = PARAGRAPH, description: str | None = None, title: str | None = None, svg_fallback: str | PathLike[str] | IO[bytes] | None = None, honor_exif_orientation: bool = True, ) -> FloatingShape ``` Return a FloatingShape for a picture that text flows around. Where add_picture puts the image in the text flow like a character, this detaches it: the image is positioned against something on the page and text wraps around it, which is what a logo in a corner or a figure beside a paragraph needs: ``` from docx.shared import Cm from docx.enum.shape import WD_ANCHOR_RELATIVE_FROM_H, WD_WRAP_TYPE run.add_float_picture( "logo.png", width=Cm(3), left=Cm(1), top=Cm(1), relative_from_h=WD_ANCHOR_RELATIVE_FROM_H.PAGE, wrap_type=WD_WRAP_TYPE.SQUARE, ) ``` `image_path_or_stream`, `width`, `height`, `description`, `title`, `svg_fallback` and `honor_exif_orientation` behave exactly as they do for add_picture. `left` and `top` are the offset from `relative_from_h` and `relative_from_v`, which default to the column and the paragraph — where Word puts a picture converted from inline to floating. Assign FloatingShape.horizontal_align afterwards to align the shape instead of offsetting it. `wrap_type` selects how text flows around the shape. `behind_text` puts the shape behind the text rather than over it, which is only meaningful together with `WD_WRAP_TYPE.NONE`. The shape is anchored to this run's paragraph. A floating shape must be anchored to a paragraph in the text flow: Word positions it relative to where the anchor falls, so an anchor in a paragraph that moves takes the shape with it. Source code in `src/docx/text/run.py` ``` def add_float_picture( self, image_path_or_stream: str | os.PathLike[str] | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, left: Length | int = 0, top: Length | int = 0, wrap_type: WD_WRAP_TYPE = WD_WRAP_TYPE.SQUARE, behind_text: bool = False, relative_from_h: WD_ANCHOR_RELATIVE_FROM_H = WD_ANCHOR_RELATIVE_FROM_H.COLUMN, relative_from_v: WD_ANCHOR_RELATIVE_FROM_V = WD_ANCHOR_RELATIVE_FROM_V.PARAGRAPH, description: str | None = None, title: str | None = None, svg_fallback: str | os.PathLike[str] | IO[bytes] | None = None, honor_exif_orientation: bool = True, ) -> FloatingShape: """Return a |FloatingShape| for a picture that text flows around. Where :meth:`add_picture` puts the image in the text flow like a character, this detaches it: the image is positioned against something on the page and text wraps around it, which is what a logo in a corner or a figure beside a paragraph needs:: from docx.shared import Cm from docx.enum.shape import WD_ANCHOR_RELATIVE_FROM_H, WD_WRAP_TYPE run.add_float_picture( "logo.png", width=Cm(3), left=Cm(1), top=Cm(1), relative_from_h=WD_ANCHOR_RELATIVE_FROM_H.PAGE, wrap_type=WD_WRAP_TYPE.SQUARE, ) `image_path_or_stream`, `width`, `height`, `description`, `title`, `svg_fallback` and `honor_exif_orientation` behave exactly as they do for :meth:`add_picture`. `left` and `top` are the offset from `relative_from_h` and `relative_from_v`, which default to the column and the paragraph — where Word puts a picture converted from inline to floating. Assign :attr:`.FloatingShape.horizontal_align` afterwards to align the shape instead of offsetting it. `wrap_type` selects how text flows around the shape. `behind_text` puts the shape behind the text rather than over it, which is only meaningful together with `WD_WRAP_TYPE.NONE`. The shape is anchored to this run's paragraph. A floating shape must be anchored to a paragraph in the text flow: Word positions it relative to where the anchor falls, so an anchor in a paragraph that moves takes the shape with it. """ anchor = self.part.new_pic_anchor( image_path_or_stream, width, height, pos_x=left, pos_y=top, description=description, title=title, svg_fallback=svg_fallback, honor_exif_orientation=honor_exif_orientation, ) anchor.wrap_type = wrap_type anchor.behindDoc = bool(behind_text) anchor.positionH.relativeFrom = relative_from_h anchor.positionV.relativeFrom = relative_from_v self._r.add_drawing(anchor) return FloatingShape(anchor, self) ``` #### add_tab ``` add_tab() -> None ``` Add a `` element at the end of the run, which Word interprets as a tab character. Source code in `src/docx/text/run.py` ``` def add_tab(self) -> None: """Add a ```` element at the end of the run, which Word interprets as a tab character.""" self._r.add_tab() ``` #### add_text ``` add_text(text: str) ``` Returns a newly appended \_Text object (corresponding to a new `` child element) to the run, containing `text`. Compare with the possibly more friendly approach of assigning text to the Run.text property. Source code in `src/docx/text/run.py` ``` def add_text(self, text: str): """Returns a newly appended |_Text| object (corresponding to a new ```` child element) to the run, containing `text`. Compare with the possibly more friendly approach of assigning text to the :attr:`Run.text` property. """ t = self._r.add_t(text) return _Text(t) ``` #### add_embedded_object ``` add_embedded_object( path_or_stream: str | PathLike[str] | IO[bytes], *, icon: str | PathLike[str] | IO[bytes], prog_id: str | None = None, width: Length | None = None, height: Length | None = None, ) -> EmbeddedObject ``` Embed a file in this run as an OLE object and return it. An embedded object is a whole file carried inside the document — a spreadsheet, a PDF, another document — shown as an icon that opens the original application on double-click: ``` run.add_embedded_object("budget.xlsx", icon="excel-icon.png", prog_id="Excel.Sheet.12") ``` This is a different thing from Document.add_alt_chunk, which imports content and dissolves it into the document when Word opens the file; an embedded object stays a distinct file. `icon` is the image Word displays for the object and is required: Word cannot render the embedded file itself, and an object with no visual is invisible in the document. `width` and `height` size the visual, defaulting to the icon's own size. `prog_id` is what tells Word which application to launch — `"Excel.Sheet.12"`, `"Word.Document.12"`, `"AcroExch.Document"`. Getting it wrong produces an object Word shows but cannot open, so it is worth passing the right one; the default of `"Package"` is Word's generic "some file" entry, which prompts the user to choose an application. The visual is VML rather than DrawingML, so this shares nothing with add_picture beyond relating the icon image in. Source code in `src/docx/text/run.py` ``` def add_embedded_object( self, path_or_stream: str | os.PathLike[str] | IO[bytes], *, icon: str | os.PathLike[str] | IO[bytes], prog_id: str | None = None, width: Length | None = None, height: Length | None = None, ) -> EmbeddedObject: """Embed a file in this run as an OLE object and return it. An embedded object is a whole file carried inside the document — a spreadsheet, a PDF, another document — shown as an icon that opens the original application on double-click:: run.add_embedded_object("budget.xlsx", icon="excel-icon.png", prog_id="Excel.Sheet.12") This is a different thing from :meth:`.Document.add_alt_chunk`, which imports content and dissolves it into the document when Word opens the file; an embedded object stays a distinct file. `icon` is the image Word displays for the object and is required: Word cannot render the embedded file itself, and an object with no visual is invisible in the document. `width` and `height` size the visual, defaulting to the icon's own size. `prog_id` is what tells Word which application to launch — ``"Excel.Sheet.12"``, ``"Word.Document.12"``, ``"AcroExch.Document"``. Getting it wrong produces an object Word shows but cannot open, so it is worth passing the right one; the default of ``"Package"`` is Word's generic "some file" entry, which prompts the user to choose an application. The visual is VML rather than DrawingML, so this shares nothing with :meth:`add_picture` beyond relating the icon image in. """ from docx.object import add_embedded_object return add_embedded_object( self, path_or_stream, icon=icon, prog_id=prog_id, width=width, height=height, ) ``` #### copy_to ``` copy_to( paragraph: Paragraph, *, before: Run | None = None, after: Run | None = None, missing_style: str = "copy", ) -> Run ``` Return a copy of this run, newly placed in `paragraph`. `before` and `after` place the copy relative to an existing run; with neither it is appended. See Paragraph.copy_to for what is repaired on the way — relationships, drawing ids, bookmarks, and, for a copy into another document, styles. Source code in `src/docx/text/run.py` ``` def copy_to( self, paragraph: Paragraph, *, before: Run | None = None, after: Run | None = None, missing_style: str = "copy", ) -> Run: """Return a copy of this run, newly placed in `paragraph`. `before` and `after` place the copy relative to an existing run; with neither it is appended. See :meth:`.Paragraph.copy_to` for what is repaired on the way — relationships, drawing ids, bookmarks, and, for a copy into another document, styles. """ from docx.copy import copy_content new_r = copy_content( self._r, self.part, paragraph.part, missing_style=missing_style ) if before is not None and after is not None: raise ValueError("pass at most one of `before` and `after`") if before is not None: before._r.addprevious(new_r) elif after is not None: after._r.addnext(new_r) else: paragraph._p.append(new_r) # pyright: ignore[reportPrivateUsage] return Run(new_r, paragraph) ``` #### clear ``` clear() ``` Return reference to this run after removing all its content. All run formatting is preserved. Source code in `src/docx/text/run.py` ``` def clear(self): """Return reference to this run after removing all its content. All run formatting is preserved. """ self._r.clear_content() return self ``` #### iter_inner_content ``` iter_inner_content() -> Iterator[ str | Drawing | RenderedPageBreak ] ``` Generate the content-items in this run in the order they appear. NOTE: only content-types currently supported by `python-docx` are generated. In this version, that is text and rendered page-breaks. Drawing is included but currently only provides access to its XML element (CT_Drawing) on its `._drawing` attribute. `Drawing` attributes and methods may be expanded in future releases. There are a number of element-types that can appear inside a run, but most of those (w:br, w:cr, w:noBreakHyphen, w:t, w:tab) have a clear plain-text equivalent. Any contiguous range of such elements is generated as a single `str`. Rendered page-break and drawing elements are generated individually. Any other elements are ignored. Source code in `src/docx/text/run.py` ``` def iter_inner_content(self) -> Iterator[str | Drawing | RenderedPageBreak]: """Generate the content-items in this run in the order they appear. NOTE: only content-types currently supported by `python-docx` are generated. In this version, that is text and rendered page-breaks. Drawing is included but currently only provides access to its XML element (CT_Drawing) on its `._drawing` attribute. `Drawing` attributes and methods may be expanded in future releases. There are a number of element-types that can appear inside a run, but most of those (w:br, w:cr, w:noBreakHyphen, w:t, w:tab) have a clear plain-text equivalent. Any contiguous range of such elements is generated as a single `str`. Rendered page-break and drawing elements are generated individually. Any other elements are ignored. """ for item in self._r.inner_content_items: if isinstance(item, str): yield item elif isinstance(item, CT_LastRenderedPageBreak): yield RenderedPageBreak(item, self) elif isinstance(item, CT_Drawing): # pyright: ignore[reportUnnecessaryIsInstance] yield Drawing(item, self) ``` #### delete ``` delete() -> None ``` Remove this run from its paragraph. As for `Paragraph.delete()`, a relationship referenced only from this run is dropped and any range marker left unmatched is removed. Source code in `src/docx/text/run.py` ``` def delete(self) -> None: """Remove this run from its paragraph. As for `Paragraph.delete()`, a relationship referenced only from this run is dropped and any range marker left unmatched is removed. """ delete_element(self._r, self.part) ``` #### mark_bookmark_range ``` mark_bookmark_range(last_run: Run, name: str) -> Bookmark ``` Return a Bookmark named `name` spanning this run through `last_run`. The two runs need not be in the same paragraph; a bookmark's delimiters are siblings of the content they surround rather than a container for it, which is what lets one span paragraphs and table cells. Source code in `src/docx/text/run.py` ``` def mark_bookmark_range(self, last_run: Run, name: str) -> Bookmark: """Return a |Bookmark| named `name` spanning this run through `last_run`. The two runs need not be in the same paragraph; a bookmark's delimiters are siblings of the content they surround rather than a container for it, which is what lets one span paragraphs and table cells. """ from docx.bookmark import Bookmark id = self.part.next_bookmark_id bookmarkStart = self._r.insert_bookmark_start_above(id, name) last_run._r.insert_bookmark_end_below(id) return Bookmark(bookmarkStart, self) ``` #### add_footnote_reference ``` add_footnote_reference(footnote: Footnote) -> None ``` Add a reference to `footnote` at the end of this run. Word renders the reference as the footnote number, superscripted, and places the footnote itself at the foot of the page it falls on. A footnote no run references does not appear in the rendered document at all. The "FootnoteReference" character style is applied to this run when it has no character style of its own, since that style is what raises the mark to a superscript. Give the run a style beforehand to prevent that, or add the reference to a run of its own to keep it off surrounding text: ``` footnote = document.footnotes.add_footnote("See Smith (2019).") paragraph.add_run().add_footnote_reference(footnote) ``` Source code in `src/docx/text/run.py` ``` def add_footnote_reference(self, footnote: Footnote) -> None: """Add a reference to `footnote` at the end of this run. Word renders the reference as the footnote number, superscripted, and places the footnote itself at the foot of the page it falls on. A footnote no run references does not appear in the rendered document at all. The "FootnoteReference" character style is applied to this run when it has no character style of its own, since that style is what raises the mark to a superscript. Give the run a style beforehand to prevent that, or add the reference to a run of its own to keep it off surrounding text:: footnote = document.footnotes.add_footnote("See Smith (2019).") paragraph.add_run().add_footnote_reference(footnote) """ if self._r.style is None: self._r.style = "FootnoteReference" self._r.add_footnoteReference().id = footnote.footnote_id ``` #### add_endnote_reference ``` add_endnote_reference(endnote: Endnote) -> None ``` Add a reference to `endnote` at the end of this run. The endnote counterpart of add_footnote_reference, and identical to it except that Word places the note at the end of the document or section rather than at the foot of the page: ``` endnote = document.endnotes.add_endnote("See Smith (2019).") paragraph.add_run().add_endnote_reference(endnote) ``` The "EndnoteReference" character style is applied to this run when it has no character style of its own, since that style is what raises the mark to a superscript. Source code in `src/docx/text/run.py` ``` def add_endnote_reference(self, endnote: Endnote) -> None: """Add a reference to `endnote` at the end of this run. The endnote counterpart of :meth:`add_footnote_reference`, and identical to it except that Word places the note at the end of the document or section rather than at the foot of the page:: endnote = document.endnotes.add_endnote("See Smith (2019).") paragraph.add_run().add_endnote_reference(endnote) The "EndnoteReference" character style is applied to this run when it has no character style of its own, since that style is what raises the mark to a superscript. """ if self._r.style is None: self._r.style = "EndnoteReference" self._r.add_endnoteReference().id = endnote.endnote_id ``` #### mark_comment_range ``` mark_comment_range(last_run: Run, comment_id: int) -> None ``` Mark the range of runs from this run to `last_run` (inclusive) as belonging to a comment. `comment_id` identfies the comment that references this range. Source code in `src/docx/text/run.py` ``` def mark_comment_range(self, last_run: Run, comment_id: int) -> None: """Mark the range of runs from this run to `last_run` (inclusive) as belonging to a comment. `comment_id` identfies the comment that references this range. """ # -- insert `w:commentRangeStart` with `comment_id` before this (first) run -- self._r.insert_comment_range_start_above(comment_id) # -- insert `w:commentRangeEnd` and `w:commentReference` run with `comment_id` after # -- `last_run` last_run._r.insert_comment_range_end_and_reference_below(comment_id) ``` ### \_Text ``` _Text(t_elm: CT_Text) ``` Proxy object wrapping `` element. Source code in `src/docx/text/run.py` ``` def __init__(self, t_elm: CT_Text): super(_Text, self).__init__() self._t = t_elm ``` ## search Text search and replace across the runs of a paragraph. The public entry points are Paragraph.replace_text, BlockItemContainer.replace_text and Document.replace_text; this module holds the machinery they share. Matching is done against the paragraph's text as a whole, so a match is found whether or not Word happened to split it across runs. Replacement is performed by `docx.oxml.text.isolate.replace_range`, which preserves the formatting of the surrounding text. ### compile_pattern ``` compile_pattern( old: str, regex: bool, flags: int | RegexFlag = 0 ) -> Pattern[str] ``` Return the compiled pattern matching `old`. A literal `old` is escaped, so text containing regex metacharacters — a "$" in a price, the "." in a file name — matches itself rather than being interpreted. Source code in `src/docx/text/search.py` ``` def compile_pattern(old: str, regex: bool, flags: int | re.RegexFlag = 0) -> Pattern[str]: """Return the compiled pattern matching `old`. A literal `old` is escaped, so text containing regex metacharacters — a "$" in a price, the "." in a file name — matches itself rather than being interpreted. """ return re.compile(old if regex else re.escape(old), flags) ``` ### replace_in_paragraph ``` replace_in_paragraph( p: CT_P, pattern: Pattern[str], new: str, count: int = -1, regex: bool = False, ) -> int ``` Replace up to `count` matches of `pattern` in `p` with `new`, returning how many. `count` of -1 replaces every match. When `regex` is True, `new` may refer to capture groups as `\1` or `\g`; otherwise it is used literally. Each replacement changes the paragraph's text, so the next match is searched for against the updated text, starting past the text just written. That means a replacement containing the pattern is not re-matched, and `replace_text("a", "aa")` terminates. Source code in `src/docx/text/search.py` ``` def replace_in_paragraph( p: CT_P, pattern: Pattern[str], new: str, count: int = -1, regex: bool = False ) -> int: """Replace up to `count` matches of `pattern` in `p` with `new`, returning how many. `count` of -1 replaces every match. When `regex` is True, `new` may refer to capture groups as ``\\1`` or ``\\g``; otherwise it is used literally. Each replacement changes the paragraph's text, so the next match is searched for against the updated text, starting past the text just written. That means a replacement containing the pattern is not re-matched, and ``replace_text("a", "aa")`` terminates. """ if count == 0: return 0 replaced = 0 pos = 0 while count < 0 or replaced < count: text = p.text if pos > len(text): break match = pattern.search(text, pos) if match is None: break replacement = match.expand(new) if regex else new replace_range(p, match.start(), match.end(), replacement) replaced += 1 pos = match.start() + len(replacement) if match.start() == match.end(): # -- a zero-width match consumes nothing; step past it or search forever -- pos += 1 if replaced >= _MAX_REPLACEMENTS_PER_PARAGRAPH: raise RuntimeError( f"replacement did not converge after {replaced} matches in one" " paragraph; check that the replacement text does not re-match the" " pattern" ) return replaced ``` ## tabstops Tabstop-related proxy types. ### TabStops ``` TabStops(element) ``` Bases: `ElementProxy` A sequence of TabStop objects providing access to the tab stops of a paragraph or paragraph style. Supports iteration, indexed access, del, and len(). It is accesed using the tab_stops property of ParagraphFormat; it is not intended to be constructed directly. Source code in `src/docx/text/tabstops.py` ``` def __init__(self, element): super(TabStops, self).__init__(element, None) self._pPr = element ``` #### add_tab_stop ``` add_tab_stop(position, alignment=LEFT, leader=SPACES) ``` Add a new tab stop at `position`, a Length object specifying the location of the tab stop relative to the paragraph edge. A negative `position` value is valid and appears in hanging indentation. Tab alignment defaults to left, but may be specified by passing a member of the WdTabAlignment enumeration as `alignment`. An optional leader character can be specified by passing a member of the WdTabLeader enumeration as `leader`. Source code in `src/docx/text/tabstops.py` ``` def add_tab_stop(self, position, alignment=WD_TAB_ALIGNMENT.LEFT, leader=WD_TAB_LEADER.SPACES): """Add a new tab stop at `position`, a |Length| object specifying the location of the tab stop relative to the paragraph edge. A negative `position` value is valid and appears in hanging indentation. Tab alignment defaults to left, but may be specified by passing a member of the :ref:`WdTabAlignment` enumeration as `alignment`. An optional leader character can be specified by passing a member of the :ref:`WdTabLeader` enumeration as `leader`. """ tabs = self._pPr.get_or_add_tabs() tab = tabs.insert_tab_in_order(position, alignment, leader) return TabStop(tab) ``` #### clear_all ``` clear_all() ``` Remove all custom tab stops. Source code in `src/docx/text/tabstops.py` ``` def clear_all(self): """Remove all custom tab stops.""" self._pPr._remove_tabs() ``` ### TabStop ``` TabStop(element) ``` Bases: `ElementProxy` An individual tab stop applying to a paragraph or style. Accessed using list semantics on its containing TabStops object. Source code in `src/docx/text/tabstops.py` ``` def __init__(self, element): super(TabStop, self).__init__(element, None) self._tab = element ``` #### alignment ``` alignment ``` A member of WdTabAlignment specifying the alignment setting for this tab stop. Read/write. #### leader ``` leader ``` A member of WdTabLeader specifying a repeating character used as a "leader", filling in the space spanned by this tab. Assigning `None` produces the same result as assigning `WD_TAB_LEADER.SPACES`. Read/write. #### position ``` position ``` A Length object representing the distance of this tab stop from the inside edge of the paragraph. May be positive or negative. Read/write.