Formatierung von DOCX-Dokumenten für wissenschaftliche Arbeiten
Wann verwenden
- Sie müssen einen Markdown-Analysebericht / eine Tabelle in ein einreicheungsfähiges DOCX konvertieren.
- Sie müssen gemischte chinesisch-westliche Schriften, Dreilinientabellen und mehrstufige Überschriftenabstände einheitlich setzen.
- Dieses Skill wird verwendet, wenn APIs, die Absätze einzeln einfügen, keine Kontrolle über Dreilinientabellen-Rahmen, Run-Level-rFonts oder kursive Aufteilungen bieten.
Chinesische Schriftgrößen → Punktwerte
| Schriftgröße | Punkt |
|---|---|
| Sanhao (三号) | 16 |
| Xiaosan (小三) | 15 |
| Sihao (四号) | 14 |
| Xiaosi (小四) | 12 |
| Wuhao (五号) | 10,5 |
Standardformatierung (kann je nach Zieljournal/Schulvorgaben überschrieben werden): Überschrift Ebene 1: Xiaosan (Fett, Schwarz), Überschrift Ebene 2: Sihao (Fett, Schwarz), Fließtext: Xiaosi (Song, 1,5-facher Zeilenabstand), westliche Schrift: Times New Roman, Tabellen: Wuhao (Song).
Wichtige Implementierungspunkte (Gelernte Fallstricke)
1. Rahmen von Dreilinientabellen
Die Einheit von w:sz in w:tblBorders ist 1/8 Punkt:
- Oben/unten 1,5 pt →
sz=12 - Mittlere Linie (unter Tabellenkopf) 1,0 pt →
sz=8 - Vertikale Linien müssen explizit auf
val="none"gesetzt werden, sonst zeichnet Word standardmäßig vertikale Linien.
Die Linie unter dem Tabellenkopf muss über row.cells[i] mit w:tcBorders/w:bottom pro Zelle gesetzt werden; tblBorders mit insideH ist in manchen Renderern unzuverlässig.
2. cell.text = '' hinterlässt einen leeren Run ohne Format
Um Zellen zu leeren, verwenden Sie:
p = cell.paragraphs[0]
for r in list(p.runs):
r._element.getparent().remove(r._element)
Sonst ist runs[0] ein leerer Run ohne rPr, was spätere Überprüfungen der Schriftart fehlschlagen lässt.
3. Bei gemischten Schriften müssen alle 4 Schriftart-Slots gesetzt werden
rFonts.set(qn('w:ascii'), 'Times New Roman')
rFonts.set(qn('w:hAnsi'), 'Times New Roman')
rFonts.set(qn('w:eastAsia'), 'Songti') # Chinesische Schrift
rFonts.set(qn('w:cs'), 'Times New Roman')
Wenn eastAsia fehlt, fällt die chinesische Schrift auf die Standardschrift zurück.
4. Kursivschrift muss in Runs aufgeteilt werden
Statistische Symbole (P<0,001, OR=2,17, CI, β, P‑Wert) müssen in separate Runs aufgeteilt werden, um sie einzeln kursiv setzen zu können. Die Regex‑Reihenfolge ist wichtig: P值 sollte mit \bP(\s*值)\b als Ganzes abgeglichen werden; zuerst P\s*[<>=]\s*[\d.]+ abgleichen, dann isoliertes P, sonst wird P=0,06 in P + =0,06 zerlegt.
5. „Eine Leerzeile“ = Absatzabstand, kein leerer Absatz
Verwenden Sie auf keinen Fall die Eingabetaste (leere Absätze), um eine Leerzeile zu erzeugen. Leere Absätze verursachen in Word:
- Zusätzliche Leerzeilen bei späterer Bearbeitung, die nicht sauber gelöscht werden können
- Unvorhersehbare Leerzeichen beim Drucken/Konvertieren in PDF
- Überlagerung von Abständen mit vorher/nachher-Abständen
Richtig——verwenden Sie space_before / space_after (Einheit: Punkt):
| Szenario | Implementierung |
|---|---|
| Überschrift Ebene 1: „Eine Leerzeile oben und unten“ | space_before = Pt( Xiaosi × 1,5 ), space_after = gleicher Wert |
| Überschrift Ebene 2: „Eine Leerzeile oben“ | space_before = Pt( Xiaosi × 1,5 ), space_after = Pt(0) |
| Überschrift Ebene 3 und tiefer | Keine Abstände setzen |
| Fließtext | space_before = Pt(0), space_after = Pt(0) |
| Eine Leerzeile zwischen Abbildung/Tabelle und Fließtext | Auf dem Absatz, der die Abbildung/Tabelle enthält space_before/after setzen, keinen neuen leeren Absatz einfügen |
Referenzwert: Xiaosi (12 pt) eine Leerzeile ≈ 18 pt; Wuhao (10,5 pt) eine Leerzeile ≈ 15,75 pt.
Überprüfungsmethode: sum(1 for p in doc.paragraphs if not p.text.strip()) == 0
——Ein korrektes Dokument sollte keine leeren Absätze enthalten.
6. Kein automatisches Fettdrucken
Verwenden Sie auf keinen Fall Fettdruck, es sei denn, der Benutzer hat es ausdrücklich verlangt.
Diese Falle ist leicht zu übersehen: **Text** in Markdown ist ein Quelltext-Hervorhebungszeichen, kein Formatierungsbefehl.
Wenn Sie es in Word als Fettdruck rendern, fügen Sie dem Originaltext eigenmächtig Hervorhebungen hinzu.
Richtiges Vorgehen:
**Text**→ Nur den Textinhalt übernehmen, Nicht fett- Wenn Untertitel benötigt werden, eigenständige Absätze mit Zielschriftart/-größe verwenden, keine Fettung für die Hierarchieebene
- Hierarchieebenen dürfen nur aus folgenden kommen: Schriftart (Fett/Kursiv), Schriftgröße, Abstand vor/nach Absatz, Nummerierung
Ebenso verboten: Kursivschrift (außer für angegebene statistische Symbole/Symbole), Unterstreichungen, Akzentuierungszeichen, Farbige Schrift, Zeichenskalierung/Zeichenabstand – alles, was der Benutzer nicht verlangt hat, bleibt unangetastet.
Nach der Konvertierung sollten Sie folgendes prüfen:
# Es sollten keine nicht vom Benutzer angeforderten Fettungen vorhanden sein
bold = [r.text for p in doc.paragraphs for r in p.runs if r.font.bold]
Ob der Tabellenkopf fett ist, hängt von der Benutzeranforderung ab; Standardmäßig verwendet dieses Skill kein Fett für den Tabellenkopf (Unterscheidung durch Schriftgröße + Dreilinientabelle).
7. Überschriften der Ebene 3 und tiefer: Keine Leerzeile
Nur Überschriften der Ebenen 1 und 2 erhalten Abstände vor/nach; Ebene 3 und tiefer: Keine Abstände, kein Fett.
Skripte
| Skript | Zweck |
|---|---|
scripts/tri_line_table.py | Rahmen für Dreilinientabellen (inkl. verify_table_borders() Selbstprüfung) |
scripts/fmt_lib.py | Hilfsfunktionen für Schriftart/Abstand/Fett-Absatz-Parsing/Zellenformatierung |
scripts/fix_italic.py | Aufteilung von Kursivschrift für statistische Symbole (In-Place, keine Neu-erstellung von Runs) |
scripts/fix_spacing2.py | Korrektur von Abständen bei gemischten chinesisch-westlichen Schriften und Symbolen (In-Place, keine Neu-erstellung von Runs) |
Abhängigkeit: pip install python-docx
fmt_lib.parse_bold_segments(text, allow_bold=False) verwirft standardmäßig die **-Markierung, rendert sie aber nicht;
Nur wenn der Benutzer ausdrücklich Fettdruck verlangt, wird allow_bold=True übergeben.
Ausführungsreihenfolge bei der Formatkorrektur eines vorhandenen DOCX (nicht umkehrbar):
python3 scripts/fix_italic.py zieldokument.docx # Zuerst Kursivschrift aufteilen
python3 scripts/fix_spacing2.py zieldokument.docx # Dann Abstände korrigieren
Bei umgekehrter Reihenfolge könnten die bereits aufgeteilten Symbole durch die Abstandsregeln wieder falsch zusammengesetzt werden.
Prüfcheckliste (nach Erstellung zwingend prüfen)
- Anzahl leerer Absätze == 0 —— Höchste Priorität,
sum(1 for p in doc.paragraphs if not p.text.strip()) - Fettgedruckte Runs == 0 (es sei denn, vom Benutzer angefordert) ——
[r.text for p in doc.paragraphs for r in p.runs if r.font.bold]muss leer sein; auch innerhalb von Tabellen prüfen - Überschrift Ebene 1: sz==15 und eastAsia==Fett, space_before==space_after==18pt
- Überschrift Ebene 2: sz==14, space_before==18pt
- Fließtext: sz==12, eastAsia==Songti, line_spacing==1.5
- Alle Runs: ascii/hAnsi==Times New Roman
- Anzahl kursiver Runs >0 und Inhalt sind statistische Symbole (dies ist das einzige erlaubte Sonderformat)
- Dreilinientabellen: Mit
verify_table_borders(table)jede Tabelle prüfen, alle sollten(True, [])zurückgeben - Tabellenüberschrift: Wuhao (Fett), zentriert, kein Satzzeichen am Ende
- Alle Runs in Tabellen: Songti + 10,5 pt
- Textabsätze: alignment == JUSTIFY, Überschriften und Tabellenüberschriften bleiben unverändert
- Leerzeichen vor Prozentzeichen:
33 %statt33% - Kein Leerzeichen zwischen Symbol und chinesischem Zeichen:
30余次; Leerzeichen zwischen Symbol und Zahl:≥ 10 小时,P < 0.05
Selbstprüfskript (einmalig):
import sys; sys.path.insert(0, '<skill>/scripts')
from tri_line_table import verify_table_borders
from docx import Document
d = Document('out.docx'); ps = d.paragraphs
assert sum(1 for x in ps if not x.text.strip()) == 0, 'Es gibt leere Absätze'
assert not [r for x in ps for r in x.runs if r.font.bold], 'Es gibt Fettdruck'
assert not [r for t in d.tables for row in t.rows for c in row.cells
for r in c.paragraphs[0].runs if r.font.bold], 'Fettdruck in Tabellen'
for i, t in enumerate(d.tables, 1):
good, prob = verify_table_borders(t)
assert good, f'Tabelle {i} entspricht nicht der Dreiliniennorm: {prob}'
print('Alles bestanden')
Wenn keine visuelle Überprüfungsumgebung vorhanden ist
Wenn LibreOffice / Word zum Konvertieren in PDF für eine visuelle Überprüfung der Dreilinientabellen nicht verfügbar ist, verwenden Sie die XML-Ebene-Prüfung (Checklistpunkte 1–9), die Schriftgröße, Schriftart, Rahmen und Abstände zuverlässig abdeckt.
Ergänzung: Genaue Spezifikation für Dreilinientabellen
Es gibt nur 3 Linien:
| Position | Punktwert | w:sz |
|---|---|---|
| Tabellenkopf (oberer Rahmen) | 1,5 | 12 |
| Tabellenkopf (unterer Rahmen) | 1,0 | 8 |
| Letzte Zeile (unterer Rahmen) | 1,5 | 12 |
| Alle anderen Rahmen (inkl. aller Zwischenzeilen, vertikale Linien) | Leer | val="none" |
Wichtig: Es reicht nicht, insideH nur in tblBorders zu setzen. Tabellenebenen-Rahmen können durch Zellenebenen-tcBorders überschrieben werden. Sobald eine Zelle bottom definiert, gilt die Zellenebene. Muss pro Zelle explizit gesetzt werden:
tblBordersauf Tabellenebene: Alle sechs Elemente aufnonesetzentcBordersaller Zeilen: Alle vier Seiten zuerst aufnonesetzen- Zellen der Tabellenkopfzeile:
top=('single',12)+bottom=('single',8) - Zellen der letzten Zeile:
bottom=('single',12)
clear_row_borders() muss für jede Zeile aufgerufen werden, nicht nur für die Kopf- und letzte Zeile – sonst bleiben in den Zwischenzeilen Linienreste erhalten.
Ergänzung: Ersetzung chinesischer Anführungszeichen
In Markdown sind " gerade Anführungszeichen. Nach der Konvertierung in DOCX müssen sie paarweise durch “ / ” ersetzt werden:
dq_index = 0 # Muss über Runs hinweg fortlaufend gezählt werden, nicht pro Run ab 0
for r in p.runs:
for ch in r.text:
if ch == '"':
out.append('“' if dq_index % 2 == 0 else '”')
dq_index += 1
Nur r.text ändern, rPr nicht berühren → Schriftart/Schriftgröße/Kursivschrift bleiben alle erhalten.
Nach der Ersetzung muss überprüft werden: Anzahl " im gesamten Dokument == 0,
‘ Anzahl == ’ Anzahl, absatzweise Paarung
(Das gesamte Dokument ausgeglichen bedeutet nicht absatzweise ausgeglichen).
Ergänzung: Word-Bearbeitung erzeugt fragmentierte Runs
Nach manueller Bearbeitung in Word werden Absätze in mehrere Runs aufgeteilt, von denen einige (die herausgeschnittenen -, Einzelzeichen)
kein rFonts haben und auf die Standardschrift zurückfallen. Nach der Korrektur von Anführungszeichen/Rahmen sollte Folgendes gescannt werden:
rf = r._element.rPr.rFonts if r._element.rPr is not None else None
if rf is None or rf.get(qn('w:eastAsia')) is None: # → muss ergänzt werden
Ergänzungsmethode: Den ersten Run im Absatz mit bereits gesetzter Schriftart als Referenz nehmen, dessen rFonts/sz/b übernehmen.
Wichtiger Workflow-Hinweis
Nachdem der Benutzer ein Dokument manuell in Word geändert hat, darf das gesamte Dokument auf keinen Fall neu generiert werden – dies würde die Benutzeränderungen überschreiben.
Richtiges Vorgehen: Vorhandenes DOCX lesen → XML In-Place punktuell ändern → Speichern.
Vor der Änderung ein Backup erstellen: cp xxx.docx /tmp/backup.docx.
Kursivschrift-Regeln
Einziges Entscheidungskriterium: Ob das Zeichen in Mathematik/Statistik eine Variable oder ein Symbol ist
| Kategorie | Kursiv? | Beispiel |
|---|---|---|
| Statistische Signifikanz P | Kursiv | P = 0,04, P < 0,001 |
| Signifikanzniveau α | Kursiv | α = 0,05 |
| Teststatistiken F, Z, H, χ², t, r | Kursiv | F = 4,52, χ² = 8,31 |
| Sonderbuchstaben (Gensymbole, Methylierungs-β-Werte usw.) | Kursiv | β |
| OR, CI, RR, HR, SD, SE, AUC | Nicht kursiv | OR = 3,14, 95% CI |
| Mehrbuchstaben-Abkürzungen (BMI, OGTT, FPG…) | Nicht kursiv | BMI, OGTT |
| Chinesische Schriftzeichen | Nicht kursiv | 值, 为 |
⚠️ Am fehleranfälligsten: „P值“ – nur P kursiv
✗ Kursiv 'P值' ← "值" ist fälschlicherweise mit kursiv
✓ Kursiv 'P' + Normal '值'
✓ Kursiv 'P' + Normal '值为' + ...
P 值, P值, P 值为 0,07 – immer nur P kursiv, die unmittelbar folgenden chinesischen Zeichen bleiben normal.
Drei eiserne Regeln für die Kursivaufteilung (aus leidvoller Erfahrung)
- Erstellen Sie niemals Runs neu. Das Reparieren der Formatierung durch „Zeichenstrom-Run-Zusammensetzen“ führt zum Verlust von rFonts/sz/b, zum Löschen von Dreilinientabellen-Rahmen und zur Fragmentierung der Kursivmarkierungen. Ändern Sie nur
run.text, löschen/erstellen Sie niemals Run-Elemente. - Die Aufteilung muss
copy.deepcopy(run._element)verwenden, um alle Formatattribute zu erhalten; Einfügeposition:parent.insert(index+1, el)(direkt neben dem ursprünglichen Run). Bei Verwendung vonappendlandet es am Ende des Absatzes und die Reihenfolge ist falsch (PP). Text des Duplikats =lead + rest + trail(ohne stat), Text des ursprünglichen Runs =lead + stat. run.text = xleert nicht die vorhandenen<w:t>-Knoten und wird auf dem Deepcopy-Duplikat angefügt. Vorherel.remove(child)aufrufen, um allew:t/w:br/w:tab/w:crzu entfernen; und zuerstw:i/w:iCsentfernen, dann den Text setzen – bei umgekehrter Reihenfolge kommt es ebenfalls zu Fehlern.
Referenzimplementierung siehe scripts/fix_italic.py.
Abstände bei gemischten Schriften und Symbolen
Grundregeln
| Szenario | Regel | Beispiel |
|---|---|---|
| Chinesisch ↔ Zahl/Buchstabe/Satzzeichen | Kein Leerzeichen | 在30余次未校正检验的背景下, 但Table 1显示 |
| Chinesisch ↔ Chinesisch | Kein Leerzeichen | 研究目的 |
| Zwischen englischen Wörtern | Leerzeichen | Table 1 |
| Innerhalb mathematischer Ausdrücke | Leerzeichen zwischen Buchstabe/Zahl und Operator | P = 0,04, BMI < 18,5 |
| Mathematischer Ausdruck ↔ Chinesisch | Leerzeichen | P < 0.05 为阈值 |
Abstände zwischen Symbolen und Zahlen/Buchstaben/chinesischen Zeichen (fehleranfällig)
| Kombination | Regel | Beispiel |
|---|---|---|
| Symbol ↔ Zahl | Leerzeichen | 33 %, 87,3 %, P = 0,04 |
| Symbol ↔ englisches Wort/Buchstabe | Leerzeichen | Table 1; α = 0,05 |
| Symbol ↔ chinesisches Zeichen | Kein Leerzeichen | 30余次, ≥10小时 (Symbol direkt am chinesischen Zeichen) |
Fehleranfällig: 30 余次 (falsch, zu viel Leerzeichen zwischen Symbol und chinesischem Zeichen) vs 30余次 (richtig).
Operator-Symbole (≥ ≤ = < >) müssen ein Leerzeichen zur Zahl haben, aber zwischen Symbol und direkt folgendem chinesischen Zeichen wird kein Leerzeichen eingefügt.
Prozentzeichen
33 % – Leerzeichen zwischen Zahl und %. Dies ist eine Anforderung wissenschaftlicher Arbeiten; schreiben Sie nicht 33%.
Referenzimplementierung siehe scripts/fix_spacing2.py (enthält fix_spacing_in_run).
Absatzausrichtung des Fließtextes
Fließtextabsätze verwenden Blocksatz – also Word's JUSTIFY (Cmd+J / Ctrl+J),
Achtung, nicht DISTRIBUTE.
from docx.enum.text import WD_ALIGN_PARAGRAPH
p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
Nur auf Fließtextabsätze anwenden, folgende behalten ihre jeweilige Ausrichtung, nicht ändern:
- Überschriften aller Ebenen – normalerweise linksbündig
- Tabellenüberschriften – zentriert
- Zellen in Tabellen – nach Bedarf (linksbündig oder zentriert)
⚠️ Drei eiserne Regeln (Ausführungsreihenfolge und Prüfung)
Ausführungsreihenfolge
python3 scripts/fix_italic.py zieldokument.docx # Zuerst Kursivschrift aufteilen
python3 scripts/fix_spacing2.py zieldokument.docx # Dann Abstände korrigieren
Prüfung (zwingend, keine darf fehlen)
import re
norm = lambda s: re.sub(r'\s', '', s)
# ① Null Textverlust – verhindert verschluckte/doppelte Zeichen
assert all(norm(a.text) == norm(b.text)
for a, b in zip(original.paragraphs, new.paragraphs))
# ② Kursiv-Runs dürfen nur statistische Symbole selbst sein (P/α/β/F/Z/H/χ/t/r), nicht enthalten:
# - Operatoren oder Zahlen: [=<>≤≥0-9]
# - chinesische Zeichen ('P值' '值' muss normal sein)
# - OR/CI/RR usw. Abkürzungen
for p in new.paragraphs:
for r in p.runs:
if r.font.italic and r.text.strip():
t = r.text.strip()
assert not re.search(r'[=<>≤≥0-9\u4e00-\u9fff]', t), f'Kursiv enthält Operator/Zahl/chinesisches Zeichen: {t!r}'
assert t.split(' ')[0] not in ('OR','CI','RR','HR','SD','SE','AUC'), f'Abkürzung sollte nicht kursiv sein: {t!r}'
# ③ Fließtextabsätze müssen Blocksatz haben
from docx.enum.text import WD_ALIGN_PARAGRAPH
assert any(p.alignment == WD_ALIGN_PARAGRAPH.JUSTIFY for p in new.paragraphs)
# ④ Chinesische Anführungszeichen paarig
assert sum(p.text.count('“') for p in new.paragraphs) == \
sum(p.text.count('”') for p in new.paragraphs)
⚠️ Höchste Priorität – eiserne Regeln (Verstoß bedeutet Nacharbeit)
- „Eine Leerzeile“ darf nur durch Absatzabstand (
space_before/space_after) realisiert werden, keinesfalls durch Eingabetaste/leere Absätze. - Verwenden Sie auf keinen Fall Fettdruck, es sei denn, der Benutzer hat es ausdrücklich in der Anforderung verlangt.
**Text**in Markdown ist ein Quelltext-Hervorhebungszeichen, kein Formatierungsbefehl – nur die Markierung entfernen, nicht fett. - Vom Benutzer nicht angeforderte Formate (Unterstreichungen, Akzentuierungszeichen, farbige Schrift, Zeichenabstand, Zeichenskalierung) bleiben alle unverändert.
- Nach manueller Bearbeitung des Dokuments durch den Benutzer darf das gesamte Dokument auf keinen Fall neu generiert werden, es darf nur die vorhandene Datei gelesen und das XML In-Place geändert werden.
Hierarchieebenen dürfen nur aus folgenden kommen: Schriftart (Fett/Kursiv), Schriftgröße, Abstand vor/nach Absatz, Nummerierung – nicht aus Fettdruck.
Hinweise beim Kürzen von Text/ Tabelleninhalten
Beim Kürzen von Text in einem bereits vom Benutzer geänderten DOCX:
Eiserne Regel: In den ersten Run zusammenführen, die restlichen leer setzen
def set_paragraph_text(p, text):
runs = p.runs
if not runs: return False
runs[0].text = text # Format bleibt im ersten Run erhalten
for r in runs[1:]:
r.text = '' # ⚠️ Muss leer gesetzt werden, sonst bleiben alter Textreste und führen zu Dopplungen
return True
Auch hier gilt: „Keine Runs neu erstellen“ – nur run.text ändern, niemals <w:r> löschen/erstellen.
Tabellen lokalisieren: Nach Dokumentenfluss-Reihenfolge, nicht mit doc.paragraphs-Index
Wenn Tabellen in Absätzen eingebettet sind, stimmt die Nummerierung von doc.paragraphs[i] nicht mit der Tabelle überein. Es muss der Body-Fluss verwendet werden:
from docx.table import Table
from docx.text.paragraph import Paragraph
W = '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'
seq = []
for c in doc.element.body.iterchildren():
if c.tag == W+'p': seq.append(('p', Paragraph(c, doc)))
elif c.tag == W+'tbl': seq.append(('t', Table(c, doc)))
Zwei Fallstricke beim Hinzufügen/Löschen von Zeilen
table.rowsparst bei jedem Zugriff das XML neu – Nach dem Löschen einer Zeile führt die Verwendung vonrows[ri]zu einem Indexfehler. Zuerstrows = list(table.rows)als Snapshot erstellen, dann von hinten nach vorne löschen.- Klonte Zeilen verlieren den unteren Rahmen der letzten Zeile – Nach dem Klonen der letzten Zeile wird die untere Linie der Dreilinientabelle auf
nilgesetzt. Abschließend muss der untere Rahmen (bottom) der letzten Zeile wieder ergänzt werden (sz=12).
⚠️ Beim Erkennen von Rahmen nicht nur auf none achten
Eigene Dreilinientabellen von Word schreiben in Zwischenzeilen w:val="nil" statt "none".
Für die Beurteilung „Keine Linie“ muss beides akzeptiert werden:
assert tv in ('nil','none') and bv in ('nil','none')
Nur auf none zu achten, führt zu fälschlichen „Rahmen beschädigt“-Meldungen, obwohl das Problem in der Erkennungslogik liegt.