Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Python-Redlines generates `.docx` redline/tracked-changes documents by comparing
Two comparison engines are available:
- **XmlPowerToolsEngine** — wraps Open-XML-PowerTools WmlComparer (original engine).
Deprecated as of 1.0.0: instantiating it emits a `DeprecationWarning`. Still published.
- **DocxodusEngine** — wraps Docxodus, a modernized .NET 10.0 fork with better move detection.
- **DocxodusEngine** — wraps Docxodus v14.1.0, a modernized .NET 10.0 fork with better move detection.
Runs `DocxDiff`, its only algorithm — Docxodus v11.0.0 deleted `WmlComparer`.

## Monorepo structure — three published packages
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ argument raises `ValueError`.
### 1. `DocxodusEngine` — the default

**[Docxodus](https://github.com/JSv4/Docxodus)** is an actively-maintained .NET 10.0 document
toolchain. Its `DocxDiff` algorithm models the document as an intermediate representation rather
than a stream of runs, which lets it attribute a change to the exact paragraph, cell, row, or
section it touched:
toolchain. Python-Redlines 1.1.0 bundles Docxodus v14.1.0. Its `DocxDiff` algorithm
models the document as an intermediate representation rather than a stream of runs,
which lets it attribute a change to the exact paragraph, cell, row, or section it touched:

- **Structure-aware** — tracks table cell and row properties, section properties, and header and
footer content that a run-stream diff reports coarsely or not at all
Expand Down
2 changes: 1 addition & 1 deletion docxodus
Submodule docxodus updated 1278 files
2 changes: 1 addition & 1 deletion packages/core/src/python_redlines/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024-present U.N. Owen <void@some.where>
#
# SPDX-License-Identifier: MIT
__version__ = "1.0.1"
__version__ = "1.1.0"
2 changes: 2 additions & 0 deletions packages/docxodus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Compiled Docxodus redline engine binary for
[`python-redlines`](https://pypi.org/project/python-redlines/).

Version 1.1.0 bundles the engine built from Docxodus v14.1.0.

This package only contains the platform-specific engine binary. Install it via
the `python-redlines` extra rather than directly:

Expand Down
34 changes: 33 additions & 1 deletion tests/test_docxodus_engine.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import io
import re
import zipfile
import xml.etree.ElementTree as ET

import pytest

from python_redlines.engines import DocxodusEngine

# Measured against the Docxodus v12.1.0 binary on tests/fixtures/. Docxodus
# Measured against the Docxodus v14.1.0 binary on tests/fixtures/. Docxodus
# v11.0.0 removed WmlComparer, so DocxDiff is the only algorithm; the count is
# neither the 9 the old default reported nor the 11 the old opt-in reported.
EXPECTED_REVISIONS = 10
Expand Down Expand Up @@ -71,6 +72,37 @@ def test_docxodus_output_is_a_valid_docx_with_tracked_changes(original_docx, mod
assert "<w:del " in document_xml


def test_docxodus_marks_a_single_changed_cjk_character(original_docx):
"""Guard the CJK granularity fix needed by users upgrading from 0.3.0 (#34)."""
namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
ns = {"w": namespace}

def make_document(text):
output = io.BytesIO()
with zipfile.ZipFile(io.BytesIO(original_docx)) as template:
document = ET.Element(f"{{{namespace}}}document")
body = ET.SubElement(document, f"{{{namespace}}}body")
paragraph = ET.SubElement(body, f"{{{namespace}}}p")
run = ET.SubElement(paragraph, f"{{{namespace}}}r")
ET.SubElement(run, f"{{{namespace}}}t").text = text
with zipfile.ZipFile(output, "w") as archive:
for member in template.infolist():
data = (ET.tostring(document, encoding="utf-8", xml_declaration=True)
if member.filename == "word/document.xml"
else template.read(member.filename))
archive.writestr(member, data)
return output.getvalue()

before = make_document("本合同的交付日期为三月十五日,双方应按约履行。")
after = make_document("本合同的交付日期为一月十五日,双方应按约履行。")
redline, _, stderr = DocxodusEngine().run_redline("Reviewer", before, after)
assert stderr is None
with zipfile.ZipFile(io.BytesIO(redline)) as archive:
document = ET.fromstring(archive.read("word/document.xml"))
assert "".join(node.text or "" for node in document.findall(".//w:delText", ns)) == "三"
assert "".join(node.text or "" for node in document.findall(".//w:ins//w:t", ns)) == "一"


# --- Integration tests for comparison settings ---

def test_docxodus_with_detect_moves(original_docx, modified_docx):
Expand Down
Loading