Skip to main content

oxml

Create and edit Word DOCX files from Python, including text, tables, comments and tracked changes. oxml is a Rust document library with thin Python bindings and types derived from Microsoft's Open XML SDK. It requires neither .NET nor Office.

Edit a document

from oxml import Document

doc = Document.open('draft.docx')
doc.story.find('fourteen days').replace('twenty-one days')
doc.save('edited.docx')

Search works across text runs, so a phrase need not have uniform formatting. Replacement preserves surrounding formatting and uses the first affected run's format for the new text. Find the text again after each edit: ranges refer to a particular version of the XML.

doc.story is the main document text. doc.stories() also gives access to headers, footers, notes and comments. Story(element, view='original') reads the text before tracked changes without accepting or rejecting them.

Replacement can split and join adjacent paragraphs using \n, but cannot cross section or table-cell boundaries. Bookmarks and comment anchors survive text edits. A replacement may change a field's cached result or remove a whole field, but cannot cross a field boundary. Content controls and existing revision payloads are protected from ordinary text replacement.

Build and edit XML

Use Document.new() to start a document, or Tree(xml) for standalone XML, from bytes or a detached expression.

from oxml import Document, e, w

doc = Document.new()
body = next(doc.main.xml.elements(w.Body))
paragraph = body(e.p(e.r(e.t('New paragraph'))))
paragraph(e.pPr(e.jc(val='center')))
doc.save('new.docx')

e.p(...) builds a detached XML expression. Calling a live parent, such as body(...), attaches it and returns the new live element. Placement follows the schema: paragraph properties go before runs, and paragraphs go before final section properties. Children already in schema order stay where they are. Siblings out of schema order are first sorted into it, keeping the relative order of children that share a slot, and the attached expression's own descendants are sorted the same way. element.reorder() runs that sort on demand, and reorder(deep=True) sorts a whole subtree. Use parent(expression, index=n) when you need an exact XML child-node position or the schema order is unknown.

The e factory supplies the w namespace for elements and attributes. For example, e.tcW(type='dxa', w=2400) creates a table-cell width. Other attribute prefixes use double underscores, such as r__id and xml__space. Configure another namespace with E('a'), or custom bindings with E(ns=bindings).

You can also work directly with typed elements. For example, next(doc.main.xml.elements(w.Text)).value = 'Replacement' changes one text node. Typed attributes check values against SDK rules and refuse constraints they cannot fully check. Raw XML editing remains available for those cases.

Use tree.count(w.Paragraph) to count matching elements in Rust without constructing a list of Python views. tree.count() counts all elements; both forms include the root when it matches. To require exactly one match, unpack it: body, = tree.elements(w.Body).

The XML editor supports elements, attributes, text, comments and processing instructions, with namespace-aware copying and movement. str(element) shows any live element's XML. It reads UTF-8 and UTF-16. Typed views and raw XML edits share the same live tree.

See Editing and preservation contracts for copying, namespaces and raw XML operations.

Comments and tracked changes

Add a comment to a text range:

doc = Document.open('draft.docx')
comment = doc.comments.add(doc.story.find('fourteen days'),
    'Please extend this period.', 'Reviewer')
comment.reply('Agreed.', 'Drafter')
comment.resolve()
doc.save('commented.docx')

Comments support plain-text bodies, replies, resolution and deletion of individual reply subtrees or whole threads. Comment anchors are currently supported in the main document. Modern reply and resolution metadata is maintained alongside the comment text.

Record a replacement as a tracked change:

doc = Document.open('draft.docx')
doc.revisions.replace(doc.story.find('fourteen days'),
    'twenty-one days', author='Drafter')
doc.save('redlined.docx')

Tracked changes cover text insertions and deletions, paragraph splits and joins, and direct run/paragraph formatting. Iterate over doc.revisions to accept or reject individual changes. accept_all() and reject_all() handle a whole story. Use doc.revisions.format(...) to track formatting changes.

Editing inside an existing revision requires accepting or rejecting it first. Table, move and nested revision histories are not supported. Bulk operations refuse unsupported revision types rather than silently skipping them. See Text/review scope for the detailed rules.

  • doc.styles finds, creates and applies paragraph, character and table styles without replacing direct formatting.
  • doc.numbering creates multilevel lists and controls continuation or restart.
  • Table.add(...) creates rectangular tables. Table(element) provides row and column edits. Structural edits do not support merged, offset or revised grids.
  • doc.bookmarks creates, finds and removes bookmarks and builds REF fields with optional switches.
  • doc.hyperlinks adds and removes internal or external links, and retargets them through link.target while retaining the text's formatting. Linked text is protected from range edits until the link is removed. doc.hyperlinks.link(url, *runs) builds a detached link while constructing content.
  • part.add_image(bytes) embeds a picture and returns detached drawing markup for a run, sized from the image itself unless given EMU extents.
  • doc.footnotes creates footnotes from text or block expressions, marks them after live text or through a detached reference run, and removes them with their references.

These helpers edit the document's XML. They do not calculate layout, inherited formatting, displayed list numbers or field results. Usage details are in Document helpers.

doc.set_custom_xml(item_id, xml_bytes, schema_uri=...) creates or replaces a custom XML datastore by GUID and returns its Part. It manages the property part and relationships while preserving unrelated stores. Content controls can refer to the GUID through w:storeItemID.

doc.properties is a mapping over the core properties part (title, creator, lastModifiedBy, created and so on), created on first write. doc.settings is a native schema-aware mapping for flat settings: updateFields reads as a boolean, while values such as defaultTabStop read as strings. Use doc.settings.root for complex settings. doc.add_part('HeaderPart') creates another declared part related to the main document, and doc.package.relationship_id(source, target) finds the relationship id that a section or reference needs.

Compare and import documents

Compare two documents to produce a new document with tracked text and direct-formatting changes:

from oxml import Document, compare

redline = compare(Document.open('original.docx'),
    Document.open('revised.docx'), author='Reviewer')
redline.save('comparison.docx')

The originals stay unchanged, and equal tables and other opaque blocks are retained. Comparison supports body-text and direct-formatting changes. Paragraphs align by text, and each changed region is compared by Unicode words across run and paragraph boundaries, retaining whitespace, punctuation and each side's formatting, so paragraph splits, joins, insertions and deletions appear as paragraph-mark changes. Tables with the same grid compare cell by cell. Field results compare as text, and a changed field instruction replaces the whole field. Hyperlinks in changed paragraphs become HYPERLINK fields, as in Word, so a changed target replaces the whole link. Moved bookmarks retain both locations in revision markup; lookup follows the current or original story view, and accepting or rejecting retains the corresponding location. Changed table structure, sections and other dependencies are refused, as are documents with unresolved revisions.

import_content(...) copies selected paragraphs and tables between documents, including their style, numbering, image and hyperlink dependencies. It preserves complete bookmark ranges and remaps conflicting IDs without overwriting destination definitions. Destination themes and document defaults still apply. Content with reviews, fields, sections or unsupported package dependencies is refused. See Import and compare for the detailed rules.

Preservation and limits

Saving an unchanged document returns the original bytes. Edited saves retain untouched package payloads. XML edits preserve namespace meaning and unknown content. Edited XML is serialized as UTF-8 without retaining its original formatting. doc.package gives access to parts, content types and relationships.

doc.validate() checks XML structure, attribute values, supported semantic rules and package relationships, including headers, footers and other reachable parts. Errors and unchecked regions are reported separately. Validation is incomplete: a report without errors does not establish that a document is valid.

Current document support focuses on DOCX. XLSX editing and conversion of Strict XML namespaces to the typed vocabulary are not implemented. Signed documents can pass through unchanged but cannot be edited. Encrypted, macro-enabled and template documents, ZIP64 and multidisk archives are refused. See DEV.md for resource limits, validation gaps and development commands.

License and acknowledgements

oxml's own code is Apache-2.0 licensed. Imported material retains its upstream notices and terms.

  • Open XML SDK — Microsoft, the .NET Foundation and contributors. Its schema metadata, validator behavior, implementation ideas and tests are the foundation for our typed model and validation. The SDK copyright and MIT notice ships with the Python package.
  • Open XML PowerTools — Microsoft, Eric White and contributors, for revision-processing and document-comparison reference behavior, tests and fixtures.
  • Pandoc — John MacFarlane, Jesse Rosenthal and contributors, for DOCX fixtures and independent reader/review expectations.
  • LibreOffice contributors and The Document Foundation, for regression documents and tests covering modern comments and cross-part content.
  • python-docx — Steve Canny and contributors, for the section fixture and API/test examples; Apache POI — the Apache Software Foundation and contributors, for the header-image fixture and relationship tests.

The fixture source table maps borrowed files to their upstream locations and retained licenses. Adapted tests identify their upstream cases in source comments. Thanks also to the developers of our runtime dependencies, especially PyO3 and quick-xml.

Metadata

Release files for oxml 0.1.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for oxml 0.1.7
File Size Uploaded
oxml-0.1.7.tar.gz 922.7 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for oxml 0.1.7
File
oxml-0.1.7-cp313-cp313-manylinux_2_28_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.28+ x86-64 Details
oxml-0.1.7-cp313-cp313-macosx_11_0_arm64.whl CPython 3.13 CPython 3.13 macOS 11.0+ ARM64 Details
oxml-0.1.7-cp312-cp312-manylinux_2_28_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.28+ x86-64 Details
oxml-0.1.7-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details
oxml-0.1.7-cp311-cp311-manylinux_2_28_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.28+ x86-64 Details
oxml-0.1.7-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details
oxml-0.1.7-cp310-cp310-manylinux_2_28_x86_64.whl CPython 3.10 CPython 3.10 Linux glibc 2.28+ x86-64 Details
oxml-0.1.7-cp310-cp310-macosx_11_0_arm64.whl CPython 3.10 CPython 3.10 macOS 11.0+ ARM64 Details

Total release size: 24.7 MB

Release files / oxml-0.1.7.tar.gz

Download URL oxml-0.1.7.tar.gz
Size 922.7 kB
Tags Source
SHA-256 checksum
How to use checksums
e48e8aeab3eeab66dba8fe74c5227b662c6e438fe60a1d5141463c94e1e68ad7
BLAKE2b-256 checksum
How to use checksums
bd76a2d6a2d70ae5be5f176278e570e9a9f0cc74c5ef5f4da43989760475e34b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / oxml-0.1.7-cp313-cp313-manylinux_2_28_x86_64.whl

Download URL oxml-0.1.7-cp313-cp313-manylinux_2_28_x86_64.whl
Size 3.4 MB
Tags CPython 3.13 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
66d693394f6951664019772fadddbea075761e882ad835fcfe8925c7bbb2aae7
BLAKE2b-256 checksum
How to use checksums
0cb39ad8e18253a0afa844be02b17205938eeb353dc537b18f56fc741b33c68b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / oxml-0.1.7-cp313-cp313-macosx_11_0_arm64.whl

Download URL oxml-0.1.7-cp313-cp313-macosx_11_0_arm64.whl
Size 2.5 MB
Tags CPython 3.13 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
15e3ed7eab8ca06d67ca097db8f14513d63a5f6a35ce618c0336709b24e54269
BLAKE2b-256 checksum
How to use checksums
8caf0e0bc96417e661f6133231a8244a1ed07efcaffd89a7359f3d21e518ee08
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / oxml-0.1.7-cp312-cp312-manylinux_2_28_x86_64.whl

Download URL oxml-0.1.7-cp312-cp312-manylinux_2_28_x86_64.whl
Size 3.4 MB
Tags CPython 3.12 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
31b1e7fa25b309e4122ed325d5c560e07a1c7a002e8950d4689f16e0e3141df1
BLAKE2b-256 checksum
How to use checksums
adf87b93e11d9ad7a4956dd710c3acbf6f5e5bf2d4bbf3086cfd2deec0d998fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / oxml-0.1.7-cp312-cp312-macosx_11_0_arm64.whl

Download URL oxml-0.1.7-cp312-cp312-macosx_11_0_arm64.whl
Size 2.5 MB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
9bcc7299a38c504486a245690b9d3ddd50d23ad780c7a1ff1651f3131b001f3c
BLAKE2b-256 checksum
How to use checksums
4f06b5f47588e531a034acfc04a3155356600de399d9198d192ee2039d3144a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / oxml-0.1.7-cp311-cp311-manylinux_2_28_x86_64.whl

Download URL oxml-0.1.7-cp311-cp311-manylinux_2_28_x86_64.whl
Size 3.4 MB
Tags CPython 3.11 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
44637250811e5a5930736afe1790d30061cc0e0bca7876c170329390daec078b
BLAKE2b-256 checksum
How to use checksums
f1230351299148461be0f3f69237a4aef91fd7d6810929567033fff181d2e0ee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / oxml-0.1.7-cp311-cp311-macosx_11_0_arm64.whl

Download URL oxml-0.1.7-cp311-cp311-macosx_11_0_arm64.whl
Size 2.5 MB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
5c5dd6c5b22d19e0afbb049b45c8078756048298f8cd6403a59aca94cca1ba62
BLAKE2b-256 checksum
How to use checksums
668d894c59527b805bcde96d3848d3bf6397e029ec71cbbcbe11432d0051c72d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / oxml-0.1.7-cp310-cp310-manylinux_2_28_x86_64.whl

Download URL oxml-0.1.7-cp310-cp310-manylinux_2_28_x86_64.whl
Size 3.4 MB
Tags CPython 3.10 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
2710ee78658c2cc02a778c0d4c930f532b5d48485c6511f936f5e51b65e3e96e
BLAKE2b-256 checksum
How to use checksums
da2b8957dc1319d6d58feb3dcde118c76dcd79e8bc1c756bb8f6d21e77c8db35
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / oxml-0.1.7-cp310-cp310-macosx_11_0_arm64.whl

Download URL oxml-0.1.7-cp310-cp310-macosx_11_0_arm64.whl
Size 2.5 MB
Tags CPython 3.10 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
20bd2e4627b08ccbf4eac6e59cfaf129620bf953ca2f5a9a0c3de9a707534279
BLAKE2b-256 checksum
How to use checksums
0137ffe231f23ee405c8703edb84d4cb291a85513080446c3a94b900e01946af
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.7 This release

9 release files

0.1.6

9 release files

0.1.5

9 release files

0.1.3

9 release files

0.1.2

9 release files

0.1.1

9 release files

0.1.0

9 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page