Bearbeiten von .docx mit Word-Änderungsnachverfolgung (Revisionsmodus)
Übersicht
Bearbeiten Sie .docx-Dateien programmatisch, sodass Änderungen als überarbeitete Versionen in Microsoft Word erscheinen – Löschungen als rote Durchstreichungen, Einfügungen als rote Unterstreichungen, mit Autor-/Datumsmetadaten im Seitenrand. Dies nutzt direkte XML-Manipulation der OOXML-Elemente w:ins / w:del über lxml, NICHT die High-Level-API von python-docx (die keine Unterstützung für Änderungsnachverfolgung bietet).
Abhängigkeiten
pip install python-docx lxml
python-docx>= 1.2.0 – nur für die anfängliche Inspektion (Absatzstile, Laufschriftformatierung). NICHT für die eigentlichen Schreibvorgänge mit Änderungsnachverfolgung verwendet.lxml>= 5.0 – das eigentliche Arbeitstier; parst und modifiziertword/document.xmlinnerhalb des.docx-Zip-Archivs.
Beide sind reine Python-Installationen ohne Systemabhängigkeiten.
Wann verwenden
- Der Benutzer möchte eine
.docx-Datei bearbeiten und die Änderungen in Word SEHEN (Änderungsnachverfolgung) - Der Benutzer muss ein überarbeitetes Manuskript senden, bei dem Gutachter einzelne Änderungen annehmen/ablehnen können
- Jedes Szenario, in dem direkte Textersetzung in
.docxnicht ausreicht, weil der Änderungsverlauf wichtig ist
Wann NICHT verwenden
- Der Benutzer möchte lediglich den endgültigen Text ersetzen (keine Revisionsmarkierungen erforderlich) – dann direkt
python-docxverwenden - Die Datei ist
.doc(altes Binärformat) – nicht unterstützt - Der Benutzer möchte Kopf-/Fußzeilen/Fußnoten mit Änderungsnachverfolgung bearbeiten – diese Technik deckt nur Textkörperabsätze ab
Funktioniert mit
Diese Fähigkeit kann allein oder zusammen mit anderen Fähigkeiten in office-docx-skills verwendet werden.
- Verwenden Sie sie mit
word-default-formatting, wenn der überarbeitete Text auch dem Standard-Word-Formatierungsprofil des Pakets folgen soll. - Verwenden Sie sie mit
word-formula-writing, wenn formelbezogene Bearbeitungen als Word-native bearbeitbare Gleichungen und sichtbare Revisionsmarkierungen dargestellt werden sollen. - Wenn sowohl Formatierung als auch Formeln wichtig sind, verwenden Sie alle drei Fähigkeiten zusammen.
OOXML-Struktur der Änderungsnachverfolgung
Eine .docx-Datei ist ein Zip-Archiv, das word/document.xml enthält. Änderungen in der Nachverfolgung verwenden zwei Wrapperelemente um w:r-Elemente (Laufschrift) innerhalb von w:p (Absatz):
<!-- Löschung: umschließt ursprüngliche Laufschriften, Text-Tag wird zu w:delText -->
<w:del w:id="102" w:author="Claude" w:date="2026-04-17T10:00:00Z">
<w:r>
<w:rPr><!-- ursprüngliche Formatierung bleibt erhalten --></w:rPr>
<w:delText xml:space="preserve">alter Text hier</w:delText>
</w:r>
</w:del>
<!-- Einfügung: umschließt neue Laufschrift mit kopierter Formatierung -->
<w:ins w:id="103" w:author="Claude" w:date="2026-04-17T10:00:00Z">
<w:r>
<w:rPr><!-- von der ursprünglichen Laufschrift kopiert --></w:rPr>
<w:t xml:space="preserve">neuer Text hier</w:t>
</w:r>
</w:ins>
Wichtige Regeln:
w:delundw:inssind direkte Kinder vonw:p, Geschwister vonw:pPr- Jedes benötigt eine eindeutige
w:id(ganze Zahl, darf nicht mit vorhandenen IDs im Dokument kollidieren) w:authorundw:datefüllen die Randannotation in Word- Innerhalb von
w:delMÜSSEN Textelementew:delTextsein (nichtw:t) - Innerhalb von
w:inssind Textelemente normalew:t - Laufschrifteigenschaften (
w:rPr) müssen von den ursprünglichen Laufschriften kopiert werden, um Schriftart/-größe/Fettschrift zu erhalten
Kernimplementierung
Die wiederverwendbare Implementierung befindet sich in tracked_change_editor.py neben dieser Fähigkeit. Kopieren Sie diesen Helfer in Ihr Arbeitsverzeichnis, wenn Sie Code möchten, den Sie direkt importieren können.
from tracked_change_editor import TrackedChangeEditor
editor = TrackedChangeEditor('input.docx', author='Ihr Name')
for i, p in enumerate(editor.body_paras):
if 'Zieltext' in editor._get_para_text(p):
editor.replace_paragraph_text(i, 'Ersatztext')
break
editor.save('output.docx')
tracked_change_editor.py übernimmt die wichtigen Abläufe:
- scannt vorhandene Revisions-IDs, bevor neue
w:ins/w:del-Knoten erstellt werden - bewahrt die Absatzformatierung durch Klonen der Standard-
w:rPrdes Absatzes - schreibt über eine sichere temporäre Datei, bevor zur endgültigen
.docx-Datei gewechselt wird
Verwendungsablauf
editor = TrackedChangeEditor('input.docx', author='Ihr Name')
# Zielabsatz anhand des Textinhalts finden
for i, p in enumerate(editor.body_paras):
if 'Zieltext' in editor._get_para_text(p):
editor.replace_paragraph_text(i, 'Ersatztext')
break
editor.save('output.docx') # Immer zuerst in einer NEUEN Datei speichern
Kritische Fallstricke – VOR DEM BEARBEITEN LESEN
0. ALLE Bearbeitungen in EINEM Skript oder Verkettung ab der letzten gespeicherten Datei
Wenn Sie 7 Bearbeitungen vornehmen müssen, führen Sie sie ALLE in einem einzigen Skriptdurchlauf gegen eine einzige TrackedChangeEditor-Instanz aus und speichern Sie dann einmal. Tun Sie NICHT:
- Skript A ausführen → in
_revised.docxspeichern - Skript B gegen die ORIGINALDATEI ausführen → in
_revised.docxspeichern (überschreibt die Arbeit von Skript A)
Wenn Sie auf mehrere Skriptdurchläufe aufteilen müssen, MUSS jeder nachfolgende Durchlauf von der vorherigen Ausgabe laden:
# Runde 1
editor = TrackedChangeEditor('original.docx')
# ... Bearbeitungen ...
editor.save('revised.docx')
# Runde 2 – von revised laden, NICHT vom Original
editor2 = TrackedChangeEditor('revised.docx')
# ... weitere Bearbeitungen ...
editor2.save('revised.docx')
Warum das wichtig ist: Das Laden von der Originaldatei verwirft alle Änderungsnachverfolgungen aus vorherigen Runden. Dies ist der mit Abstand häufigste Fehler – er verwirft stillschweigend Arbeit ohne Fehlermeldung.
0b. Einfügen neuer Absätze: DEN ABSCHNITT finden, nicht nur den Text
Wenn Sie einen neuen Absatz einfügen (z. B. einen Literaturverweiseintrag), durchsuchen Sie NIEMALS das gesamte Dokument nach einer Textübereinstimmung. Textzitate im Fließtext enthalten dieselben Autorennamen wie Literaturverweiseinträge. Eine naive Suche wie if 'Acharya' in text findet zuerst einen Fließtextabsatz und fügt den Verweis in der Mitte von Abschnitt 2 ein.
Richtiger Ansatz – zuerst die Abschnittsgrenze finden:
# Finden, wo der Literaturverweisabschnitt beginnt
refs_start_idx = None
for i, p in enumerate(editor.body_paras):
t = editor._get_para_text(p)
if t.strip() == 'Literaturverzeichnis':
refs_start_idx = i
break
# Nur INNERHALB des Literaturverzeichnisses suchen
for i in range(refs_start_idx + 1, len(editor.body_paras)):
t = editor._get_para_text(editor.body_paras[i])
if t.startswith('Acharya') and 'Johnson' in t:
# Nach diesem Absatz einfügen
break
Warum das wichtig ist: Autorennamen erscheinen sowohl in Textzitaten als auch in Literaturverweiseinträgen. Ohne abschnittsbewusste Suche landen neue Verweisabsätze im Fließtext – eine Verfälschung, die schwer zu erkennen ist, bis der Benutzer Word öffnet.
0c. Absätze in wissenschaftlichen Arbeiten umschreiben: ALLE Zitate bewahren
Beim Ersetzen des Textes eines Absatzes MÜSSEN Sie jedes (Autor Jahr)-Zitat aus dem Original bewahren. Wissenschaftliche Arbeiten haben ein Literaturverzeichnis, das mit den Textzitaten übereinstimmen muss – das Weglassen eines Zitats verwaist den Verweiseintrag oder schwächt das Argument.
Richtiger Ansatz – vor dem Umschreiben extrahieren:
import re
alter_text = editor._get_para_text(editor.body_paras[idx])
alte_zitate = re.findall(r'\([^)]*\d{4}[a-z]?\)', alter_text)
# Neuen Text schreiben und sicherstellen, dass jedes Element in alte_zitate vorkommt
# Nach dem Schreiben überprüfen:
for z in alte_zitate:
assert z in neuer_text, f'ZITAT VERLOREN: {z}'
Warum das wichtig ist: Eine umgeschriebene Einleitung, die (Malmendier und Nagel 2011; Choi, Gao und Jiang 2020) weglässt, zerstört stillschweigend die Zitatintegrität der Arbeit. Das Literaturverzeichnis enthält diese Einträge noch, aber nichts im Text verweist auf sie. Gutachter und Lektoren werden dies beanstanden.
1. Absätze mit vorhandenen Änderungsnachverfolgungen
Wenn ein Absatz bereits w:ins/w:del aus vorherigen Bearbeitungen enthält, erfasst _collect_direct_runs nur direkte w:r-Kinder – Laufschriften innerhalb vorhandener w:ins/w:del-Wrapper werden übersprungen. Dies vermeidet Verschachtelung von Revisionen, bedeutet aber, dass der Ersatz nur den „akzeptierten“ Textteil abdeckt. Bei Absätzen mit umfangreichen vorherigen Revisionen akzeptieren Sie zuerst alle Änderungen in Word und bearbeiten dann erneut.
2. Immer in einer neuen Datei speichern
Überschreiben Sie niemals das Original. Speichern Sie als _revised.docx, lassen Sie den Benutzer in Word überprüfen und ersetzen Sie dann manuell.
3. Inhaltsverzeichnis wird nicht automatisch aktualisiert
python-docx / lxml kann Words Inhaltsverzeichnis nicht aktualisieren. Nach der Bearbeitung muss der Benutzer in Word öffnen → Rechtsklick auf das Inhaltsverzeichnis → Feld aktualisieren.
4. Eindeutigkeit der Revisions-ID
Die Methode _find_max_id() scannt alle vorhandenen w:ins, w:del, w:rPrChange, w:pPrChange, w:sectPrChange-Elemente. Neue IDs werden ab dem Maximum inkrementiert. Wenn IDs kollidieren, kann Word den Revisionsverlauf beschädigen.
5. Formatierung bewahren
_get_default_rpr klont das w:rPr aus der ersten Laufschrift im Absatz. Dies funktioniert für Absätze mit einheitlicher Formatierung. Bei Absätzen mit gemischter Formatierung (z. B. fett „Schlüsselwörter:" gefolgt von normalem Text) führen Sie einen chirurgischen, laufschriftweisen Ersatz durch, anstatt den gesamten Absatz zu ersetzen.
6. Wiedergabetreue
Das Speichern über lxml erzeugt geringfügige kosmetische XML-Unterschiede (Anführungszeichenstil " vs. ', Zeilenenden \r\n vs. \n). Inhalt und Formatierung bleiben erhalten. Die Dateigröße kann aufgrund der Zip-Komprimierungsstufe abweichen – dies ist harmlos.
Überprüfungscheckliste
Nach dem Speichern in Word öffnen und bestätigen:
- Gelöschter Text wird rot durchgestrichen angezeigt
- Eingefügter Text wird rot unterstrichen angezeigt
- Autorname erscheint in den Randanmerkungen
- Umgebende Absätze behalten die ursprüngliche Formatierung
- Keine doppelten oder verschachtelten Revisionsmarkierungen
- Die Anzahl der Einfügungen für Ihren gewählten Autor entspricht den Erwartungen – führen Sie ein Überprüfungsskript aus, um
w:insmit Ihrem Autornamen zu zählen und die ersten 100 Zeichen jeder Einfügung auszugeben. Wenn die Anzahl geringer als erwartet ist, wurden vorherige Bearbeitungen verloren (siehe Fallstrick 0). - Keine Literaturverweiseinträge im Fließtext – scannen Sie Absätze vor der Überschrift „Literaturverzeichnis“ auf Absätze, die wie ein vollständiger bibliografischer Eintrag aussehen (Autor, Jahr, Zeitschrift, Band, Seiten). Wenn gefunden, wurde ein Verweis an der falschen Stelle eingefügt (siehe Fallstrick 0b).
# Überprüfungsausschnitt nach dem Speichern
ziel_autor = 'Codex'
with zipfile.ZipFile('output.docx') as z:
xml = z.read('word/document.xml')
root = etree.fromstring(xml)
autor_ins = [e for e in root.findall('.//w:ins', nsmap)
if e.get(qn('w:author')) == ziel_autor]
print(f'Einfügungen von {ziel_autor}: {len(autor_ins)}')
for i, ins in enumerate(autor_ins):
texte = ''.join(t.text for t in ins.findall('.//w:t', nsmap) if t.text)
print(f' [{i+1}] {texte[:100]}')