Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2087981
proposal: cross-boundary text operations
pablospe Jan 28, 2026
fb8a9e3
openspec: add cross-boundary text operations proposal
pablospe Jan 28, 2026
91355c2
proposal: add mixed-state editing via atomic decomposition
pablospe Jan 28, 2026
4e5f265
proposal: reorder tasks for tests-first approach
pablospe Jan 28, 2026
cfa6133
proposal: add design constraints for rPr preservation and Word compat
pablospe Jan 28, 2026
2547f91
feat: implement cross-boundary text operations
pablospe Jan 29, 2026
b6f42f8
fix: insert_after/before now splits run at anchor boundary
pablospe Jan 29, 2026
ba10c6a
fix: add cross-boundary fallback for insert_after/before anchor
pablospe Jan 29, 2026
c88507b
style: apply ruff formatting to test_coverage_gaps.py
pablospe Jan 29, 2026
e77dc97
fix: trailing whitespace and missing EOF newline in AGENTS.md
pablospe Jan 29, 2026
bf32a68
fix: add missing EOF newline in root AGENTS.md
pablospe Jan 29, 2026
ba1f4ef
fix: add None guards to satisfy ty type checker
pablospe Jan 29, 2026
93cee33
fix: handle multi-w:t nodes in cross-boundary operations
pablospe Jan 29, 2026
ae01ce2
chore: archive cross-boundary text operations proposal
pablospe Jan 29, 2026
c276861
style: fix lint and EOF issues
pablospe Jan 29, 2026
a6fa169
fix: prevent nested w:ins/w:del inside existing w:ins elements
pablospe Jan 30, 2026
b1a3c54
fix: terminate ancestor walk in mock tests and adjust assertions
pablospe Jan 30, 2026
651aedc
fix: address minor PR review comments (6, 7, 9)
pablospe Jan 30, 2026
d1a2d61
fix: multi-w:t run safety and mid-node insertion ordering
pablospe Jan 30, 2026
30398fe
refactor: address code review style and deduplication issues
pablospe Jan 30, 2026
3f88b4d
fix: handle full ins removal in Site D and add coverage tests
pablospe Jan 30, 2026
574c799
chore: add parallel test execution to Makefile test target
pablospe Jan 30, 2026
8a063ff
style: add return type annotation to _get_text_content helper
pablospe Jan 30, 2026
a3e7921
cleanup: remove empty placeholder test classes
pablospe Jan 30, 2026
31e8b28
fix: improve count_matches accuracy, add xml:space preserve, and boun…
pablospe Jan 30, 2026
4ec86ba
fix: preserve multi-w:t siblings and tracked-change metadata
pablospe Jan 30, 2026
cfb23a9
fix: avoid nesting w:ins inside w:ins in _insert_near_match
pablospe Jan 30, 2026
3afcea4
format
pablospe Jan 30, 2026
8c9db94
style: add return type annotations and fix tautological condition
pablospe Jan 30, 2026
e4e5b13
fix: use comment node for marker and deterministic node-to-part mapping
pablospe Jan 30, 2026
dde60e4
fix: resolve occurrence index drift and Site D attribute injection
pablospe Jan 30, 2026
edc489f
fix: preserve non-text run children when removing empty w:t nodes
pablospe Jan 30, 2026
d68749d
test: add regression tests for xml:space, occurrence fallback, and ma…
pablospe Jan 30, 2026
9a99718
Addressing:
pablospe Jan 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!-- OPENSPEC:START -->
# OpenSpec Instructions

These instructions are for AI assistants working in this project.

Always open `@/openspec/AGENTS.md` when the request:
- Mentions planning or proposals (words like proposal, spec, change, plan)
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
- Sounds ambiguous and you need the authoritative spec before coding

Use `@/openspec/AGENTS.md` to learn:
- How to create and apply change proposals
- Spec format and conventions
- Project structure and guidelines

Keep this managed block so 'openspec update' can refresh the instructions.

<!-- OPENSPEC:END -->
19 changes: 19 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
<!-- OPENSPEC:START -->
# OpenSpec Instructions

These instructions are for AI assistants working in this project.

Always open `@/openspec/AGENTS.md` when the request:
- Mentions planning or proposals (words like proposal, spec, change, plan)
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
- Sounds ambiguous and you need the authoritative spec before coding

Use `@/openspec/AGENTS.md` to learn:
- How to create and apply change proposals
- Spec format and conventions
- Project structure and guidelines

Keep this managed block so 'openspec update' can refresh the instructions.

<!-- OPENSPEC:END -->

# Claude Instructions for docx-editor

## Quick Reference
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ check: ## Run code quality tools.
.PHONY: test
test: ## Test the code with pytest
@echo "🚀 Testing code: Running pytest"
@uv run python -m pytest --cov --cov-config=pyproject.toml --cov-report=xml
@uv run python -m pytest tests -n auto --cov --cov-config=pyproject.toml --cov-report=xml

.PHONY: build
build: clean-build ## Build wheel file
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Pure Python library for Word document track changes and comments, without requir
## Features

- **Track Changes**: Replace, delete, and insert text with revision tracking
- **Cross-Boundary Editing**: Find and replace text spanning multiple XML elements and revision boundaries
- **Mixed-State Editing**: Atomic decomposition for text spanning `<w:ins>`/`<w:del>` boundaries
- **Comments**: Add, reply, resolve, and delete comments
- **Revision Management**: List, accept, and reject tracked changes
- **Cross-Platform**: Works on Linux, macOS, and Windows
Expand Down Expand Up @@ -48,6 +50,28 @@ with Document.open("contract.docx") as doc:
doc.save()
```

### Cross-Boundary Text Operations

Text in Word documents with tracked changes can span revision boundaries. `docx-editor` handles this transparently:

```python
from docx_editor import Document

with Document.open("reviewed.docx") as doc:
# Get visible text (inserted text included, deleted excluded)
text = doc.get_visible_text()

# Find text across element boundaries
match = doc.find_text("Aim: To")
if match and match.spans_boundary:
print("Text spans a revision boundary")

# Replace works even across revision boundaries
doc.replace("Aim: To", "Goal: To")

doc.save()
```

## Claude Code Skill

This repo includes a skill for [Claude Code](https://claude.ai/claude-code) that enables AI-assisted Word document editing.
Expand Down
7 changes: 7 additions & 0 deletions docx_editor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
XMLError,
)
from .track_changes import Revision
from .xml_editor import TextMap, TextMapMatch, TextPosition, build_text_map, find_in_text_map

__all__ = [
# Main classes
Expand All @@ -64,4 +65,10 @@
"RevisionError",
"CommentError",
"TextNotFoundError",
# Text map
"TextPosition",
"TextMap",
"TextMapMatch",
"build_text_map",
"find_in_text_map",
]
27 changes: 26 additions & 1 deletion docx_editor/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from .exceptions import WorkspaceSyncError
from .track_changes import Revision, RevisionManager
from .workspace import Workspace
from .xml_editor import DocxXMLEditor
from .xml_editor import DocxXMLEditor, build_text_map


class Document:
Expand Down Expand Up @@ -117,6 +117,14 @@ def source_path(self) -> Path:

# ==================== Track Changes API ====================

def find_text(self, text: str, occurrence: int = 0):
"""Find text in the document, including across element boundaries.

Returns match info or None if not found.
"""
self._ensure_open()
return self._revision_manager._find_across_boundaries(text, occurrence)

def count_matches(self, text: str) -> int:
"""Count how many times a text string appears in the document.

Expand All @@ -137,6 +145,23 @@ def count_matches(self, text: str) -> int:
self._ensure_open()
return self._revision_manager.count_matches(text)

def get_visible_text(self) -> str:
"""Get the visible text of the document.

Returns flattened text with paragraphs separated by newlines.
Inserted text is included, deleted text is excluded.

Returns:
The visible text content
"""
self._ensure_open()
paragraphs = self._document_editor.dom.getElementsByTagName("w:p")
parts = []
for p in paragraphs:
tm = build_text_map(p)
parts.append(tm.text)
return "\n".join(parts)

def replace(self, find: str, replace_with: str, occurrence: int = 0) -> int:
"""Replace text with tracked changes.

Expand Down
Loading