Lightweight DOCX comment engine based on text view API
Project description
Docxnote
docxnote is a lightweight DOCX comment engine with only an lxml dependency, for automating Word comments.
It works directly on WordprocessingML, treating a DOCX as ZIP + XML, and exposes a text-oriented API.
Unlike typical DOCX libraries, docxnote hides Word’s Run structure entirely; everything is based on paragraph strings.
Installation
pip install docxnote
With uv:
uv add docxnote
Quick start
from docxnote import DocxDocument, Paragraph, Table
# Load document
with open("document.docx", "rb") as f:
# By default existing comments are discarded
doc = DocxDocument.parse(f.read())
# To keep existing comments and add more:
# doc = DocxDocument.parse(f.read(), keep_comments=True)
# Walk block-level content
for block in doc.blocks():
if isinstance(block, Paragraph):
if block.text:
block.comment("Please review wording", end=5, author="reviewer")
elif isinstance(block, Table):
rows, cols = block.shape()
for r in range(rows):
for c in range(cols):
cell = block[r, c]
for inner in cell.blocks():
if isinstance(inner, Paragraph) and inner.text:
inner.comment("Needs review", end=3, author="reviewer")
# Write output
output = doc.render()
with open("output.docx", "wb") as f:
f.write(output)
API
DocxDocument
Represents a DOCX file.
parse
DocxDocument.parse(docx_bytes, *, keep_comments=False)
Parses the DOCX and returns a document object.
- keep_comments: Whether to keep existing comments. Default
False(strips them). UseTrueto preserve existing comments and append new ones.
blocks
doc.blocks()
Returns block-level elements:
(Paragraph | Table, ...)
Order matches the Word document.
render
doc.render()
Returns new DOCX as bytes. Comments are written during this step.
Thread safety
A single DocxDocument instance is safe to use from multiple threads (internally serialized with a reentrant lock). Use separate instances for parallel work across threads. For multiple processes, call parse in each process.
Paragraph
Represents a Word paragraph.
text
text = paragraph.text
Full paragraph text, including \n and \t.
comment
paragraph.comment(
text, # comment body
start=0, # start index (inclusive)
end=None, # end index (exclusive); None means end of paragraph
*,
author="docxnote",
date=None, # datetime (timezone-aware recommended); None = current system time
)
Adds a comment spanning the given character range in the paragraph. The w:date value in comments.xml is stored in UTC (…Z). A naive datetime (no tzinfo) is treated as UTC.
Example:
paragraph.comment("Needs change", start=3, end=8, author="Alice")
docxnote handles run splitting, anchors, comments.xml, and relationship updates.
Table
Represents a Word table.
shape
rows, cols = table.shape()
Returns (row_count, col_count).
Cell access
cell = table[row, col]
Returns a Cell. All coordinates are addressable, including positions covered by merged cells.
Cell
Represents a table cell.
blocks
cell.blocks()
Block-level elements inside the cell:
(Paragraph | Table, ...)
Order matches Word.
bounds
top, left, bottom, right = cell.bounds()
Cell bounds (top, left, bottom, right) with half-open intervals [top, bottom) and [left, right).
For a non-merged cell, returns (r, c, r+1, c+1).
Advanced
Nested tables
for block in doc.blocks():
if isinstance(block, Table):
rows, cols = block.shape()
for r in range(rows):
for c in range(cols):
cell = block[r, c]
for inner_block in cell.blocks():
if isinstance(inner_block, Table):
inner_rows, inner_cols = inner_block.shape()
# ...
Multiple comments
paragraph.comment("Note 1", start=0, end=5, author="Alice")
paragraph.comment("Note 2", start=10, end=15, author="Bob")
paragraph.comment("Note 3", start=20, end=25, author="Carol")
Merged cells
table = [b for b in doc.blocks() if isinstance(b, Table)][0]
cell = table[0, 0]
top, left, bottom, right = cell.bounds()
if bottom - top > 1 or right - left > 1:
print(f"Merged cell spans {bottom - top} rows, {right - left} cols")
Tests
Test documents are generated with python-docx; no checked-in DOCX fixtures. See tests/README.md.
SKILL
This repo includes SKILL.md to help coding agents use docxnote correctly. From the project root, download it into the agent skill folder (replace main if your default branch differs). On Windows PowerShell, use curl.exe if curl is aliased to Invoke-WebRequest.
Cursor
mkdir -p .cursor/docxnote
curl -fsSL -o .cursor/docxnote/SKILL.md https://raw.githubusercontent.com/touken928/docxnote/main/SKILL.md
Claude Code
mkdir -p .claude/docxnote
curl -fsSL -o .claude/docxnote/SKILL.md https://raw.githubusercontent.com/touken928/docxnote/main/SKILL.md
Point your agent at that file for install steps, suggested patterns, and caveats.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file docxnote-0.1.1.tar.gz.
File metadata
- Download URL: docxnote-0.1.1.tar.gz
- Upload date:
- Size: 10.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1738ef0d76cef445918cd3c4680e0a32a3a56a5ff5ef2b225c9404c6f7d236ba
|
|
| MD5 |
58cf5c4fa049bf1ca5860d91185cb46c
|
|
| BLAKE2b-256 |
6d2af32e7ab5ac3af8263970599de880cca99a1c986f5cf35314a6490f22249b
|
Provenance
The following attestation bundles were made for docxnote-0.1.1.tar.gz:
Publisher:
publish.yml on touken928/docxnote
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
docxnote-0.1.1.tar.gz -
Subject digest:
1738ef0d76cef445918cd3c4680e0a32a3a56a5ff5ef2b225c9404c6f7d236ba - Sigstore transparency entry: 1154405904
- Sigstore integration time:
-
Permalink:
touken928/docxnote@781e74aeba06643d381ab82bdb819426b72f1d39 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/touken928
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@781e74aeba06643d381ab82bdb819426b72f1d39 -
Trigger Event:
push
-
Statement type:
File details
Details for the file docxnote-0.1.1-py3-none-any.whl.
File metadata
- Download URL: docxnote-0.1.1-py3-none-any.whl
- Upload date:
- Size: 12.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f8ecc0ef62a434a76550ce81067bf774c701403b8a3508ca27b53e38f692cb0
|
|
| MD5 |
445c054ac81fa7eafe3b39a149fa8737
|
|
| BLAKE2b-256 |
b7ffa653b97e5c7a37b496f9dcaf6c8bee85929bd5542912adcd46232d8ce964
|
Provenance
The following attestation bundles were made for docxnote-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on touken928/docxnote
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
docxnote-0.1.1-py3-none-any.whl -
Subject digest:
3f8ecc0ef62a434a76550ce81067bf774c701403b8a3508ca27b53e38f692cb0 - Sigstore transparency entry: 1154405907
- Sigstore integration time:
-
Permalink:
touken928/docxnote@781e74aeba06643d381ab82bdb819426b72f1d39 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/touken928
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@781e74aeba06643d381ab82bdb819426b72f1d39 -
Trigger Event:
push
-
Statement type: