S08: Teilgebiet 01 Iteration B4 fuer DOCX umgesetzt. Heading 1/2/3 in destengsblue (build/build-reference-docx.py Funktion set_heading_colors mit explizitem color val=0B5394, themeColor accent1 entfernt). Heading-Bottom-Borders direkt am Stil verworfen, weil Word die Border bei hanging-Indent linksbuendig statt zentriert rendert und der right-Indent sowohl Text als auch Border begrenzt. 21 Markdown-HRs aus cv.md entfernt - Quelle der wahrgenommenen Doppellinien war Pandocs DOCX-Konvertierung von --- Zeilen zu VML-rect mit o:hr=t (Embossed-Look). Tabellen-Strich-Zeilen blieben unangetastet. Zwischenfall: NTFS-Mount-Stale-Read der cv.md (20043 statt 20201 Bytes) haette fast die Live-Datei truncated, sofortige Wiederherstellung aus git show HEAD und HR-Removal erneut mit git-Version als Input. H2-Trennlinien via Post-Processing eingefuehrt (build/post-process-docx.py um Logik erweitert): nach jedem H2 wird ein leerer Trenn-Absatz mit linksbuendiger Bottom-Border eingefuegt, schwarz (000000), 8,6 cm Linienlaenge (right-Indent 4196 dxa), 1,25 pt Dicke (sz=10). Sandbox-Verifikation 7 H2 zu 7 Trenner. Visuelle Bestaetigung durch Thomas. teilgebiete/01-lebenslauf.md um Iteration-B4-Block ergaenzt (B4.1 Farben, B4.2 Heading-Border-Sackgasse, B4.3 HR-Removal inkl. Zwischenfall, B4.4 H2-Trennlinien) und Naechste-Schritte-Liste auf C/D verkuerzt.
This commit is contained in:
@@ -1,2 +1,2 @@
|
|||||||
S07
|
S08
|
||||||
Iteration B3 und B3.5 fuer Teilgebiet 01 abgeschlossen. B3 in build/build-reference-docx.py ergaenzt: DocDefault widowControl plus keepNext und keepLines auf Heading 1/2/3 und FirstParagraph (Pandoc-Stil fuer ersten Absatz nach einem Heading, deckt die fett formatierten Kenntnisse-Subsection-Labels KI Software-Design Methodik IT etc ab). Erster Versuch Compact-Stil mit keepNext hat Listen komplett unteilbar gemacht (Job-Stationen begannen jedes Mal auf einer neuen Seite, ungenutzte Seitenenden) und wurde verworfen. Auf Wunsch von Thomas auf 3-3-Regel umgestellt: bei Listen mit mindestens 6 Bullets duerfen Trennungen passieren, aber mindestens 3 Bullets bleiben jeweils zusammen vor und nach dem Umbruch. Bei kuerzeren Listen alles zusammen. Da das stilbasiert nicht abbildbar ist (alle Bullets haben pStyle Compact), neues Post-Processing-Skript build/post-process-docx.py: scannt das fertige DOCX, findet Sequenzen aufeinanderfolgender Bullets mit numPr-Eigenschaft ausserhalb von Tabellen-Zellen, setzt keepNext auf den ersten 2 und den N-3 N-2 Bullets jeder Liste mit n groesser gleich 6 (bei n kleiner 6 alle keepNext). build.ps1 erweitert auf 3 Schritte und ruft das Post-Processing-Skript automatisch nach erfolgreichem DOCX-Build auf, mit Console-Output und Log-Statistiken (Anzahl Listen Bullets keepNext-Markierungen). Sandbox-Verifikation 26 Listen 184 Bullets 93 keepNext, Pattern fuer 11-Bullet-Liste KK......KK.. Auf Thomas System visuell bestaetigt: Listen werden an guten Stellen getrennt, keine ungenutzten Seitenenden, keine einzelnen Bullets allein am Seitenrand. teilgebiete/01-lebenslauf.md um B3- und B3.5-Bloecke ergaenzt sowie Naechste-Schritte-Liste auf B4 C D umstrukturiert. agent-prompt.md Aktueller-Stand-Abschnitt fortgeschrieben mit B3 und B3.5, Hinweis auf 3-stufige DOCX-Pipeline und Edit-Tool-Truncation an build.ps1 ergaenzt. Naechste Session startet mit B4 (Heading-Farben oder Trennlinien analog PDF).
|
Teilgebiet 01 Iteration B4 fuer DOCX umgesetzt. Heading 1/2/3 in destengsblue (build/build-reference-docx.py Funktion set_heading_colors mit explizitem color val=0B5394, themeColor accent1 entfernt). Heading-Bottom-Borders direkt am Stil verworfen, weil Word die Border bei hanging-Indent linksbuendig statt zentriert rendert und der right-Indent sowohl Text als auch Border begrenzt. 21 Markdown-HRs aus cv.md entfernt - Quelle der wahrgenommenen Doppellinien war Pandocs DOCX-Konvertierung von --- Zeilen zu VML-rect mit o:hr=t (Embossed-Look). Tabellen-Strich-Zeilen blieben unangetastet. Zwischenfall: NTFS-Mount-Stale-Read der cv.md (20043 statt 20201 Bytes) haette fast die Live-Datei truncated, sofortige Wiederherstellung aus git show HEAD und HR-Removal erneut mit git-Version als Input. H2-Trennlinien via Post-Processing eingefuehrt (build/post-process-docx.py um Logik erweitert): nach jedem H2 wird ein leerer Trenn-Absatz mit linksbuendiger Bottom-Border eingefuegt, schwarz (000000), 8,6 cm Linienlaenge (right-Indent 4196 dxa), 1,25 pt Dicke (sz=10). Sandbox-Verifikation 7 H2 zu 7 Trenner. Visuelle Bestaetigung durch Thomas. teilgebiete/01-lebenslauf.md um Iteration-B4-Block ergaenzt (B4.1 Farben, B4.2 Heading-Border-Sackgasse, B4.3 HR-Removal inkl. Zwischenfall, B4.4 H2-Trennlinien) und Naechste-Schritte-Liste auf C/D verkuerzt.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,7 @@ build-reference-docx.py
|
|||||||
Baut die templates/reference.docx fuer die Pandoc-DOCX-Pipeline aus der
|
Baut die templates/reference.docx fuer die Pandoc-DOCX-Pipeline aus der
|
||||||
Pandoc-Default-Reference, mit gezielten Anpassungen.
|
Pandoc-Default-Reference, mit gezielten Anpassungen.
|
||||||
|
|
||||||
Iteration B1 + B1.5 + B2 + B3 (aktuell):
|
Iteration B1 + B1.5 + B2 + B3 + B4 (aktuell):
|
||||||
B1 - Theme-Schriften (majorFont und minorFont) beide auf Calibri.
|
B1 - Theme-Schriften (majorFont und minorFont) beide auf Calibri.
|
||||||
B1 - Direkte Schriftnamen-Referenzen in styles.xml auf Calibri
|
B1 - Direkte Schriftnamen-Referenzen in styles.xml auf Calibri
|
||||||
(Code-Schriften wie Consolas bleiben).
|
(Code-Schriften wie Consolas bleiben).
|
||||||
@@ -18,14 +18,20 @@ Iteration B1 + B1.5 + B2 + B3 (aktuell):
|
|||||||
analog PDF (top/bottom 2.2 cm, left/right 2.5 cm).
|
analog PDF (top/bottom 2.2 cm, left/right 2.5 cm).
|
||||||
B3 - DocDefault widowControl. Heading 1/2/3 mit keepNext + keepLines.
|
B3 - DocDefault widowControl. Heading 1/2/3 mit keepNext + keepLines.
|
||||||
Zusaetzlich 'FirstParagraph' (Pandoc-Stil fuer den ersten Absatz
|
Zusaetzlich 'FirstParagraph' (Pandoc-Stil fuer den ersten Absatz
|
||||||
nach einem Heading) — deckt die fett formatierten Kenntnisse-
|
nach einem Heading) - deckt die fett formatierten Kenntnisse-
|
||||||
Subsection-Labels ab. Hinweis: Listen-Bullet-Schutz (3-3-Regel)
|
Subsection-Labels ab. Hinweis: Listen-Bullet-Schutz (3-3-Regel)
|
||||||
passiert nicht hier, sondern im Post-Processing
|
passiert nicht hier, sondern im Post-Processing
|
||||||
(build/post-process-docx.py), das auf das fertige DOCX angewendet
|
(build/post-process-docx.py), das auf das fertige DOCX angewendet
|
||||||
wird — ein Stil kann keine Per-Bullet-Logik abbilden.
|
wird - ein Stil kann keine Per-Bullet-Logik abbilden.
|
||||||
|
B4 - Heading 1/2/3 in destengsblue (0B5394) gefaerbt (themeColor
|
||||||
|
entfernt, damit die Farbe nicht aus dem Word-Theme kommt).
|
||||||
|
Hinweis (S08): die zwischenzeitlich eingebauten Heading-
|
||||||
|
Trennlinien (Bottom-Border + Indent-Trick) wurden zurueck-
|
||||||
|
gerollt, weil sie in Word linksbuendig statt zentriert
|
||||||
|
gerendert wurden (Word-Border folgt bei hanging-Indent der
|
||||||
|
visuellen Absatz-Position, nicht den Indent-Werten).
|
||||||
|
|
||||||
Geplant in Folge-Iterationen:
|
Geplant in Folge-Iterationen:
|
||||||
B4 - optional Heading-Farben auf DesTEngS-Blau analog PDF
|
|
||||||
C - Foto-Einbindung
|
C - Foto-Einbindung
|
||||||
D - Hyphenation-Feintuning fuer PDF
|
D - Hyphenation-Feintuning fuer PDF
|
||||||
"""
|
"""
|
||||||
@@ -70,10 +76,15 @@ SIZE_HEADING3 = 24
|
|||||||
HEADING_SIZES = {"Heading1": SIZE_HEADING1,
|
HEADING_SIZES = {"Heading1": SIZE_HEADING1,
|
||||||
"Heading2": SIZE_HEADING2,
|
"Heading2": SIZE_HEADING2,
|
||||||
"Heading3": SIZE_HEADING3}
|
"Heading3": SIZE_HEADING3}
|
||||||
# Compact NICHT mehr in dieser Liste — Listen-Bullet-Schutz uebernimmt das
|
# Compact NICHT mehr in dieser Liste - Listen-Bullet-Schutz uebernimmt das
|
||||||
# Post-Processing-Skript pro-Bullet.
|
# Post-Processing-Skript pro-Bullet.
|
||||||
KEEP_STYLES = ("Heading1", "Heading2", "Heading3", "FirstParagraph")
|
KEEP_STYLES = ("Heading1", "Heading2", "Heading3", "FirstParagraph")
|
||||||
|
|
||||||
|
# B4 - Heading-Farben (Trennlinien wurden in S08 zurueckgerollt, siehe
|
||||||
|
# Modul-Docstring). Bleibt: Heading 1/2/3 in destengsblue, themeColor entfernt.
|
||||||
|
HEADING_COLOR = "0B5394" # destengsblue (analog template.tex)
|
||||||
|
HEADING_COLOR_STYLES = ("Heading1", "Heading2", "Heading3")
|
||||||
|
|
||||||
PAGE_W = 11906
|
PAGE_W = 11906
|
||||||
PAGE_H = 16838
|
PAGE_H = 16838
|
||||||
MARGIN_TOP = 1247
|
MARGIN_TOP = 1247
|
||||||
@@ -87,19 +98,19 @@ HEADER_RIGHT_TAB = PAGE_W - MARGIN_LEFT - MARGIN_RIGHT
|
|||||||
HEADER_LEFT = "Dr.-Ing. Thomas Langer"
|
HEADER_LEFT = "Dr.-Ing. Thomas Langer"
|
||||||
HEADER_RIGHT = "Lebenslauf"
|
HEADER_RIGHT = "Lebenslauf"
|
||||||
|
|
||||||
def log(msg: str) -> None:
|
def log(msg):
|
||||||
print(f"[build-reference-docx] {msg}", flush=True)
|
print(f"[build-reference-docx] {msg}", flush=True)
|
||||||
|
|
||||||
XML_DECL = b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
XML_DECL = b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
||||||
|
|
||||||
def write_xml(tree: ET.ElementTree, dest: Path) -> None:
|
def write_xml(tree, dest):
|
||||||
body = ET.tostring(tree.getroot(), encoding="utf-8")
|
body = ET.tostring(tree.getroot(), encoding="utf-8")
|
||||||
dest.write_bytes(XML_DECL + body)
|
dest.write_bytes(XML_DECL + body)
|
||||||
|
|
||||||
def write_xml_bytes(content: bytes, dest: Path) -> None:
|
def write_xml_bytes(content, dest):
|
||||||
dest.write_bytes(XML_DECL + content)
|
dest.write_bytes(XML_DECL + content)
|
||||||
|
|
||||||
def fetch_pandoc_default(dest: Path) -> None:
|
def fetch_pandoc_default(dest):
|
||||||
log("Pandoc-Default-Reference extrahieren ...")
|
log("Pandoc-Default-Reference extrahieren ...")
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["pandoc", "--print-default-data-file", "reference.docx"],
|
["pandoc", "--print-default-data-file", "reference.docx"],
|
||||||
@@ -111,11 +122,11 @@ def fetch_pandoc_default(dest: Path) -> None:
|
|||||||
dest.write_bytes(result.stdout)
|
dest.write_bytes(result.stdout)
|
||||||
log(f" -> {dest} ({dest.stat().st_size} Bytes)")
|
log(f" -> {dest} ({dest.stat().st_size} Bytes)")
|
||||||
|
|
||||||
def unpack_docx(src: Path, dest_dir: Path) -> None:
|
def unpack_docx(src, dest_dir):
|
||||||
with zipfile.ZipFile(src, "r") as z:
|
with zipfile.ZipFile(src, "r") as z:
|
||||||
z.extractall(dest_dir)
|
z.extractall(dest_dir)
|
||||||
|
|
||||||
def repack_docx(src_dir: Path, dest: Path) -> None:
|
def repack_docx(src_dir, dest):
|
||||||
files = []
|
files = []
|
||||||
for path in src_dir.rglob("*"):
|
for path in src_dir.rglob("*"):
|
||||||
if path.is_file():
|
if path.is_file():
|
||||||
@@ -126,12 +137,12 @@ def repack_docx(src_dir: Path, dest: Path) -> None:
|
|||||||
for path, arcname in files:
|
for path, arcname in files:
|
||||||
z.write(path, arcname)
|
z.write(path, arcname)
|
||||||
|
|
||||||
def is_code_font(name: str) -> bool:
|
def is_code_font(name):
|
||||||
return (name or "").strip().lower() in CODE_FONTS
|
return (name or "").strip().lower() in CODE_FONTS
|
||||||
|
|
||||||
# --- B1: Schriften ---------------------------------------------------------
|
# --- B1: Schriften ---------------------------------------------------------
|
||||||
|
|
||||||
def set_theme_fonts_to_calibri(theme_xml: Path) -> None:
|
def set_theme_fonts_to_calibri(theme_xml):
|
||||||
tree = ET.parse(theme_xml)
|
tree = ET.parse(theme_xml)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
for kind in ("majorFont", "minorFont"):
|
for kind in ("majorFont", "minorFont"):
|
||||||
@@ -146,7 +157,7 @@ def set_theme_fonts_to_calibri(theme_xml: Path) -> None:
|
|||||||
log(f" Theme {kind}/latin: {old!r} -> {TARGET_FONT!r}")
|
log(f" Theme {kind}/latin: {old!r} -> {TARGET_FONT!r}")
|
||||||
write_xml(tree, theme_xml)
|
write_xml(tree, theme_xml)
|
||||||
|
|
||||||
def replace_direct_fonts_in_styles(styles_xml: Path) -> None:
|
def replace_direct_fonts_in_styles(styles_xml):
|
||||||
tree = ET.parse(styles_xml)
|
tree = ET.parse(styles_xml)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
changed = 0
|
changed = 0
|
||||||
@@ -166,14 +177,16 @@ def replace_direct_fonts_in_styles(styles_xml: Path) -> None:
|
|||||||
f" gesetzt (Code-Fonts unangetastet: {skipped})")
|
f" gesetzt (Code-Fonts unangetastet: {skipped})")
|
||||||
write_xml(tree, styles_xml)
|
write_xml(tree, styles_xml)
|
||||||
|
|
||||||
def set_table_borders_none(styles_xml: Path) -> None:
|
def set_table_borders_none(styles_xml):
|
||||||
tree = ET.parse(styles_xml)
|
tree = ET.parse(styles_xml)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
style = next((s for s in root.findall(f"{W}style")
|
style = next((s for s in root.findall(f"{W}style")
|
||||||
if s.get(f"{W}styleId") == "Table"), None)
|
if s.get(f"{W}styleId") == "Table"), None)
|
||||||
if style is None:
|
if style is None:
|
||||||
raise RuntimeError("Style 'Table' nicht in styles.xml")
|
raise RuntimeError("Style 'Table' nicht in styles.xml")
|
||||||
tbl_pr = style.find(f"{W}tblPr") or ET.SubElement(style, f"{W}tblPr")
|
tbl_pr = style.find(f"{W}tblPr")
|
||||||
|
if tbl_pr is None:
|
||||||
|
tbl_pr = ET.SubElement(style, f"{W}tblPr")
|
||||||
existing = tbl_pr.find(f"{W}tblBorders")
|
existing = tbl_pr.find(f"{W}tblBorders")
|
||||||
if existing is not None:
|
if existing is not None:
|
||||||
tbl_pr.remove(existing)
|
tbl_pr.remove(existing)
|
||||||
@@ -187,19 +200,27 @@ def set_table_borders_none(styles_xml: Path) -> None:
|
|||||||
log(" Style 'Table': tblBorders=none auf allen Sides")
|
log(" Style 'Table': tblBorders=none auf allen Sides")
|
||||||
write_xml(tree, styles_xml)
|
write_xml(tree, styles_xml)
|
||||||
|
|
||||||
def set_default_body_size(styles_xml: Path) -> None:
|
def set_default_body_size(styles_xml):
|
||||||
tree = ET.parse(styles_xml)
|
tree = ET.parse(styles_xml)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
docDefaults = root.find(f"{W}docDefaults") or ET.SubElement(root, f"{W}docDefaults")
|
docDefaults = root.find(f"{W}docDefaults")
|
||||||
rPrDefault = docDefaults.find(f"{W}rPrDefault") or ET.SubElement(docDefaults, f"{W}rPrDefault")
|
if docDefaults is None:
|
||||||
rPr = rPrDefault.find(f"{W}rPr") or ET.SubElement(rPrDefault, f"{W}rPr")
|
docDefaults = ET.SubElement(root, f"{W}docDefaults")
|
||||||
|
rPrDefault = docDefaults.find(f"{W}rPrDefault")
|
||||||
|
if rPrDefault is None:
|
||||||
|
rPrDefault = ET.SubElement(docDefaults, f"{W}rPrDefault")
|
||||||
|
rPr = rPrDefault.find(f"{W}rPr")
|
||||||
|
if rPr is None:
|
||||||
|
rPr = ET.SubElement(rPrDefault, f"{W}rPr")
|
||||||
for tag in (f"{W}sz", f"{W}szCs"):
|
for tag in (f"{W}sz", f"{W}szCs"):
|
||||||
elem = rPr.find(tag) or ET.SubElement(rPr, tag)
|
elem = rPr.find(tag)
|
||||||
|
if elem is None:
|
||||||
|
elem = ET.SubElement(rPr, tag)
|
||||||
elem.set(f"{W}val", str(SIZE_BODY))
|
elem.set(f"{W}val", str(SIZE_BODY))
|
||||||
log(f" DocDefault Body-Schriftgroesse: {SIZE_BODY/2} pt")
|
log(f" DocDefault Body-Schriftgroesse: {SIZE_BODY/2} pt")
|
||||||
write_xml(tree, styles_xml)
|
write_xml(tree, styles_xml)
|
||||||
|
|
||||||
def set_heading_sizes(styles_xml: Path) -> None:
|
def set_heading_sizes(styles_xml):
|
||||||
tree = ET.parse(styles_xml)
|
tree = ET.parse(styles_xml)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
for style in root.findall(f"{W}style"):
|
for style in root.findall(f"{W}style"):
|
||||||
@@ -207,25 +228,35 @@ def set_heading_sizes(styles_xml: Path) -> None:
|
|||||||
if sid not in HEADING_SIZES:
|
if sid not in HEADING_SIZES:
|
||||||
continue
|
continue
|
||||||
target = HEADING_SIZES[sid]
|
target = HEADING_SIZES[sid]
|
||||||
rPr = style.find(f"{W}rPr") or ET.SubElement(style, f"{W}rPr")
|
rPr = style.find(f"{W}rPr")
|
||||||
|
if rPr is None:
|
||||||
|
rPr = ET.SubElement(style, f"{W}rPr")
|
||||||
for tag in (f"{W}sz", f"{W}szCs"):
|
for tag in (f"{W}sz", f"{W}szCs"):
|
||||||
elem = rPr.find(tag) or ET.SubElement(rPr, tag)
|
elem = rPr.find(tag)
|
||||||
|
if elem is None:
|
||||||
|
elem = ET.SubElement(rPr, tag)
|
||||||
elem.set(f"{W}val", str(target))
|
elem.set(f"{W}val", str(target))
|
||||||
log(f" Stil {sid!r}: Schriftgroesse {target/2} pt")
|
log(f" Stil {sid!r}: Schriftgroesse {target/2} pt")
|
||||||
write_xml(tree, styles_xml)
|
write_xml(tree, styles_xml)
|
||||||
|
|
||||||
def set_widow_control_default(styles_xml: Path) -> None:
|
def set_widow_control_default(styles_xml):
|
||||||
tree = ET.parse(styles_xml)
|
tree = ET.parse(styles_xml)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
docDefaults = root.find(f"{W}docDefaults") or ET.SubElement(root, f"{W}docDefaults")
|
docDefaults = root.find(f"{W}docDefaults")
|
||||||
pPrDefault = docDefaults.find(f"{W}pPrDefault") or ET.SubElement(docDefaults, f"{W}pPrDefault")
|
if docDefaults is None:
|
||||||
pPr = pPrDefault.find(f"{W}pPr") or ET.SubElement(pPrDefault, f"{W}pPr")
|
docDefaults = ET.SubElement(root, f"{W}docDefaults")
|
||||||
|
pPrDefault = docDefaults.find(f"{W}pPrDefault")
|
||||||
|
if pPrDefault is None:
|
||||||
|
pPrDefault = ET.SubElement(docDefaults, f"{W}pPrDefault")
|
||||||
|
pPr = pPrDefault.find(f"{W}pPr")
|
||||||
|
if pPr is None:
|
||||||
|
pPr = ET.SubElement(pPrDefault, f"{W}pPr")
|
||||||
if pPr.find(f"{W}widowControl") is None:
|
if pPr.find(f"{W}widowControl") is None:
|
||||||
ET.SubElement(pPr, f"{W}widowControl")
|
ET.SubElement(pPr, f"{W}widowControl")
|
||||||
log(" pPrDefault: widowControl aktiviert")
|
log(" pPrDefault: widowControl aktiviert")
|
||||||
write_xml(tree, styles_xml)
|
write_xml(tree, styles_xml)
|
||||||
|
|
||||||
def set_keep_next_styles(styles_xml: Path) -> None:
|
def set_keep_next_styles(styles_xml):
|
||||||
tree = ET.parse(styles_xml)
|
tree = ET.parse(styles_xml)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
seen = set()
|
seen = set()
|
||||||
@@ -233,7 +264,9 @@ def set_keep_next_styles(styles_xml: Path) -> None:
|
|||||||
sid = style.get(f"{W}styleId")
|
sid = style.get(f"{W}styleId")
|
||||||
if sid not in KEEP_STYLES:
|
if sid not in KEEP_STYLES:
|
||||||
continue
|
continue
|
||||||
pPr = style.find(f"{W}pPr") or ET.SubElement(style, f"{W}pPr")
|
pPr = style.find(f"{W}pPr")
|
||||||
|
if pPr is None:
|
||||||
|
pPr = ET.SubElement(style, f"{W}pPr")
|
||||||
for tag in (f"{W}keepNext", f"{W}keepLines"):
|
for tag in (f"{W}keepNext", f"{W}keepLines"):
|
||||||
if pPr.find(tag) is None:
|
if pPr.find(tag) is None:
|
||||||
ET.SubElement(pPr, tag)
|
ET.SubElement(pPr, tag)
|
||||||
@@ -244,7 +277,31 @@ def set_keep_next_styles(styles_xml: Path) -> None:
|
|||||||
log(f" Hinweis: Stil(e) {sorted(missing)!r} nicht gefunden, uebersprungen")
|
log(f" Hinweis: Stil(e) {sorted(missing)!r} nicht gefunden, uebersprungen")
|
||||||
write_xml(tree, styles_xml)
|
write_xml(tree, styles_xml)
|
||||||
|
|
||||||
def header_default_xml() -> bytes:
|
# --- B4: Heading-Farben ----------------------------------------------------
|
||||||
|
|
||||||
|
def set_heading_colors(styles_xml):
|
||||||
|
tree = ET.parse(styles_xml)
|
||||||
|
root = tree.getroot()
|
||||||
|
for style in root.findall(f"{W}style"):
|
||||||
|
sid = style.get(f"{W}styleId")
|
||||||
|
if sid not in HEADING_COLOR_STYLES:
|
||||||
|
continue
|
||||||
|
rPr = style.find(f"{W}rPr")
|
||||||
|
if rPr is None:
|
||||||
|
rPr = ET.SubElement(style, f"{W}rPr")
|
||||||
|
color = rPr.find(f"{W}color")
|
||||||
|
if color is None:
|
||||||
|
color = ET.SubElement(rPr, f"{W}color")
|
||||||
|
# Theme-Color-Attribute entfernen, damit die Farbe nicht aus dem
|
||||||
|
# Word-Theme abgeleitet wird (Pandoc-Default: themeColor accent1).
|
||||||
|
for attr in (f"{W}themeColor", f"{W}themeTint", f"{W}themeShade"):
|
||||||
|
if attr in color.attrib:
|
||||||
|
del color.attrib[attr]
|
||||||
|
color.set(f"{W}val", HEADING_COLOR)
|
||||||
|
log(f" Stil {sid!r}: color={HEADING_COLOR} (themeColor entfernt)")
|
||||||
|
write_xml(tree, styles_xml)
|
||||||
|
|
||||||
|
def header_default_xml():
|
||||||
return (
|
return (
|
||||||
b'<w:hdr xmlns:w="' + NS["w"].encode() + b'">\n'
|
b'<w:hdr xmlns:w="' + NS["w"].encode() + b'">\n'
|
||||||
b' <w:p>\n'
|
b' <w:p>\n'
|
||||||
@@ -259,14 +316,14 @@ def header_default_xml() -> bytes:
|
|||||||
b'</w:hdr>\n'
|
b'</w:hdr>\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
def header_first_blank_xml() -> bytes:
|
def header_first_blank_xml():
|
||||||
return (
|
return (
|
||||||
b'<w:hdr xmlns:w="' + NS["w"].encode() + b'">\n'
|
b'<w:hdr xmlns:w="' + NS["w"].encode() + b'">\n'
|
||||||
b' <w:p/>\n'
|
b' <w:p/>\n'
|
||||||
b'</w:hdr>\n'
|
b'</w:hdr>\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
def footer_default_xml() -> bytes:
|
def footer_default_xml():
|
||||||
return (
|
return (
|
||||||
b'<w:ftr xmlns:w="' + NS["w"].encode() + b'">\n'
|
b'<w:ftr xmlns:w="' + NS["w"].encode() + b'">\n'
|
||||||
b' <w:p>\n'
|
b' <w:p>\n'
|
||||||
@@ -292,12 +349,12 @@ REL_FOOTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationship
|
|||||||
CT_HEADER = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"
|
CT_HEADER = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"
|
||||||
CT_FOOTER = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"
|
CT_FOOTER = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"
|
||||||
|
|
||||||
def next_free_rel_id(rels_xml: Path) -> int:
|
def next_free_rel_id(rels_xml):
|
||||||
text = rels_xml.read_text(encoding="utf-8")
|
text = rels_xml.read_text(encoding="utf-8")
|
||||||
ids = [int(m.group(1)) for m in re.finditer(r'Id="rId(\d+)"', text)]
|
ids = [int(m.group(1)) for m in re.finditer(r'Id="rId(\d+)"', text)]
|
||||||
return (max(ids) + 1) if ids else 1
|
return (max(ids) + 1) if ids else 1
|
||||||
|
|
||||||
def add_relationship(rels_xml: Path, rid: str, rtype: str, target: str) -> None:
|
def add_relationship(rels_xml, rid, rtype, target):
|
||||||
text = rels_xml.read_text(encoding="utf-8")
|
text = rels_xml.read_text(encoding="utf-8")
|
||||||
new_rel = f'<Relationship Type="{rtype}" Id="{rid}" Target="{target}" />'
|
new_rel = f'<Relationship Type="{rtype}" Id="{rid}" Target="{target}" />'
|
||||||
if new_rel in text:
|
if new_rel in text:
|
||||||
@@ -305,7 +362,7 @@ def add_relationship(rels_xml: Path, rid: str, rtype: str, target: str) -> None:
|
|||||||
text = text.replace("</Relationships>", new_rel + "</Relationships>")
|
text = text.replace("</Relationships>", new_rel + "</Relationships>")
|
||||||
rels_xml.write_text(text, encoding="utf-8")
|
rels_xml.write_text(text, encoding="utf-8")
|
||||||
|
|
||||||
def add_content_type_override(ct_xml: Path, part_name: str, ct: str) -> None:
|
def add_content_type_override(ct_xml, part_name, ct):
|
||||||
text = ct_xml.read_text(encoding="utf-8")
|
text = ct_xml.read_text(encoding="utf-8")
|
||||||
new_override = f'<Override PartName="{part_name}" ContentType="{ct}"/>'
|
new_override = f'<Override PartName="{part_name}" ContentType="{ct}"/>'
|
||||||
if part_name in text:
|
if part_name in text:
|
||||||
@@ -313,10 +370,7 @@ def add_content_type_override(ct_xml: Path, part_name: str, ct: str) -> None:
|
|||||||
text = text.replace("</Types>", new_override + "</Types>")
|
text = text.replace("</Types>", new_override + "</Types>")
|
||||||
ct_xml.write_text(text, encoding="utf-8")
|
ct_xml.write_text(text, encoding="utf-8")
|
||||||
|
|
||||||
def update_sectpr_with_headers(document_xml: Path,
|
def update_sectpr_with_headers(document_xml, header_default_rid, header_first_rid, footer_default_rid):
|
||||||
header_default_rid: str,
|
|
||||||
header_first_rid: str,
|
|
||||||
footer_default_rid: str) -> None:
|
|
||||||
text = document_xml.read_text(encoding="utf-8")
|
text = document_xml.read_text(encoding="utf-8")
|
||||||
new_sectpr = (
|
new_sectpr = (
|
||||||
f'<w:sectPr>'
|
f'<w:sectPr>'
|
||||||
@@ -341,7 +395,7 @@ def update_sectpr_with_headers(document_xml: Path,
|
|||||||
log(f" document.xml sectPr: pgSz/pgMar (A4, 2.2/2.5cm Raender), Header"
|
log(f" document.xml sectPr: pgSz/pgMar (A4, 2.2/2.5cm Raender), Header"
|
||||||
f" default+first, Footer default+first auf gleicher rId, titlePg")
|
f" default+first, Footer default+first auf gleicher rId, titlePg")
|
||||||
|
|
||||||
def add_header_footer(unpacked: Path) -> None:
|
def add_header_footer(unpacked):
|
||||||
word_dir = unpacked / "word"
|
word_dir = unpacked / "word"
|
||||||
rels_xml = word_dir / "_rels" / "document.xml.rels"
|
rels_xml = word_dir / "_rels" / "document.xml.rels"
|
||||||
ct_xml = unpacked / "[Content_Types].xml"
|
ct_xml = unpacked / "[Content_Types].xml"
|
||||||
@@ -368,7 +422,7 @@ def add_header_footer(unpacked: Path) -> None:
|
|||||||
|
|
||||||
update_sectpr_with_headers(doc_xml, rid_h_def, rid_h_first, rid_f_def)
|
update_sectpr_with_headers(doc_xml, rid_h_def, rid_h_first, rid_f_def)
|
||||||
|
|
||||||
def main() -> int:
|
def main():
|
||||||
log(f"Ziel: {OUTPUT_FILE}")
|
log(f"Ziel: {OUTPUT_FILE}")
|
||||||
TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)
|
TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
@@ -398,6 +452,8 @@ def main() -> int:
|
|||||||
set_widow_control_default(styles_xml)
|
set_widow_control_default(styles_xml)
|
||||||
log("Anpassung: keepNext + keepLines auf Heading 1/2/3 + FirstParagraph (B3)")
|
log("Anpassung: keepNext + keepLines auf Heading 1/2/3 + FirstParagraph (B3)")
|
||||||
set_keep_next_styles(styles_xml)
|
set_keep_next_styles(styles_xml)
|
||||||
|
log("Anpassung: Heading 1/2/3 in destengsblue (B4)")
|
||||||
|
set_heading_colors(styles_xml)
|
||||||
log("Anpassung: Header und Footer einbauen (B2)")
|
log("Anpassung: Header und Footer einbauen (B2)")
|
||||||
add_header_footer(unpacked)
|
add_header_footer(unpacked)
|
||||||
|
|
||||||
|
|||||||
@@ -3,23 +3,33 @@
|
|||||||
post-process-docx.py
|
post-process-docx.py
|
||||||
====================
|
====================
|
||||||
|
|
||||||
Wird auf das von Pandoc erzeugte DOCX angewendet, NACH `build.ps1`. Setzt
|
Wird auf das von Pandoc erzeugte DOCX angewendet, NACH `build.ps1`. Macht
|
||||||
Per-Bullet-keepNext-Markierungen, die ein Stil nicht abbilden kann:
|
zwei XML-Modifikationen, die ein Stil oder die `reference.docx` nicht
|
||||||
|
abbilden koennen:
|
||||||
|
|
||||||
3-3-Regel fuer Listen-Bullets:
|
1. 3-3-Regel fuer Listen-Bullets (B3.5):
|
||||||
- Eine Liste ist eine Sequenz aufeinanderfolgender Absaetze mit
|
- Eine Liste ist eine Sequenz aufeinanderfolgender Absaetze mit
|
||||||
<w:numPr>-Eigenschaft im Body (nicht innerhalb von Tabellen-Zellen).
|
<w:numPr>-Eigenschaft im Body (nicht innerhalb von Tabellen-Zellen).
|
||||||
- Bei einer Liste mit weniger als 6 Bullets: alle Bullets bekommen
|
- Bei einer Liste mit weniger als 6 Bullets: alle Bullets bekommen
|
||||||
<w:keepNext/> (Liste bleibt unteilbar — bei <6 ist die 3-3-Regel
|
<w:keepNext/> (Liste bleibt unteilbar - bei <6 ist die 3-3-Regel
|
||||||
sowieso nur durch Zusammenhalten aller erfuellbar).
|
sowieso nur durch Zusammenhalten aller erfuellbar).
|
||||||
- Bei einer Liste mit 6 oder mehr Bullets: die ersten 2 und die
|
- Bei einer Liste mit 6 oder mehr Bullets: die ersten 2 und die
|
||||||
drittletzten und vorletzten Bullets bekommen <w:keepNext/>.
|
drittletzten und vorletzten Bullets bekommen <w:keepNext/>.
|
||||||
Damit gilt: nach Bullet 1 darf nicht getrennt werden (1+2+3 zusammen),
|
Damit gilt: nach Bullet 1 darf nicht getrennt werden (1+2+3 zusammen),
|
||||||
und nach Bullet N-3 darf nicht getrennt werden (N-2+N-1+N zusammen).
|
und nach Bullet N-3 darf nicht getrennt werden (N-2+N-1+N zusammen).
|
||||||
Trennen ist erlaubt zwischen den Bullets in der Mitte.
|
Trennen ist erlaubt zwischen den Bullets in der Mitte.
|
||||||
|
Bullets in Tabellen-Zellen werden uebersprungen.
|
||||||
|
|
||||||
Bullets in Tabellen-Zellen werden uebersprungen — Compact wird auch fuer
|
2. H2-Trennlinie (S08):
|
||||||
Tabellen-Zellen-Inhalte verwendet, dort wollen wir kein keepNext.
|
- Nach jedem H2-Absatz wird ein leerer Trenn-Absatz eingefuegt.
|
||||||
|
- Trenn-Absatz: linksbuendige Bottom-Border, schwarz (000000),
|
||||||
|
1,25 pt (sz=10), 8,6 cm Linienlaenge (right-Indent 4196 dxa bei
|
||||||
|
9072 dxa Textbreite).
|
||||||
|
- Run-Properties auf sz=2 (1 pt), damit der Absatz selbst minimale
|
||||||
|
Hoehe hat.
|
||||||
|
- Ein schmaler-als-Heading-Border ist ueber den Heading-Stil selbst
|
||||||
|
nicht moeglich, weil Words right-Indent sowohl Text als auch
|
||||||
|
Border begrenzt. Deshalb separater Trenn-Absatz.
|
||||||
|
|
||||||
Voraussetzungen: nur Python-Stdlib.
|
Voraussetzungen: nur Python-Stdlib.
|
||||||
"""
|
"""
|
||||||
@@ -37,52 +47,64 @@ DOCX_FILE = BASE_DIR / "output" / "Lebenslauf_Dr-Ing_Thomas_Langer.docx"
|
|||||||
|
|
||||||
W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||||
|
|
||||||
def log(msg: str) -> None:
|
# H2-Trenn-Absatz: linksbuendige Bottom-Border, schwarz, 8,6 cm lang, 1,25 pt dick.
|
||||||
|
# Textbreite = PAGE_W - MARGIN_LEFT - MARGIN_RIGHT = 11906 - 1417 - 1417 = 9072 dxa
|
||||||
|
# 8,6 cm = 8,6 * 567 dxa/cm = 4876 dxa
|
||||||
|
# right-Indent = 9072 - 4876 = 4196 dxa
|
||||||
|
# Border sz ist in 1/8 pt: 1,25 pt * 8 = 10
|
||||||
|
H2_SEP_XML = (
|
||||||
|
'<w:p>'
|
||||||
|
'<w:pPr>'
|
||||||
|
'<w:spacing w:before="0" w:after="80"/>'
|
||||||
|
'<w:ind w:right="4196"/>'
|
||||||
|
'<w:pBdr>'
|
||||||
|
'<w:bottom w:val="single" w:sz="10" w:space="2" w:color="000000"/>'
|
||||||
|
'</w:pBdr>'
|
||||||
|
'<w:rPr><w:sz w:val="2"/><w:szCs w:val="2"/></w:rPr>'
|
||||||
|
'</w:pPr>'
|
||||||
|
'</w:p>'
|
||||||
|
)
|
||||||
|
|
||||||
|
H2_STYLE_RE = re.compile(r'<w:pStyle\s+w:val="Heading2"\s*/?>')
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
print(f"[post-process-docx] {msg}", flush=True)
|
print(f"[post-process-docx] {msg}", flush=True)
|
||||||
|
|
||||||
def is_bullet_paragraph(p_xml: str) -> bool:
|
def is_bullet_paragraph(p_xml):
|
||||||
"""True wenn Absatz-XML eine numPr-Eigenschaft hat (= Listen-Bullet)."""
|
|
||||||
return "<w:numPr" in p_xml
|
return "<w:numPr" in p_xml
|
||||||
|
|
||||||
def has_keep_next(p_xml: str) -> bool:
|
def is_h2_paragraph(p_xml):
|
||||||
|
return bool(H2_STYLE_RE.search(p_xml))
|
||||||
|
|
||||||
|
def has_keep_next(p_xml):
|
||||||
return "<w:keepNext" in p_xml
|
return "<w:keepNext" in p_xml
|
||||||
|
|
||||||
def add_keep_next(p_xml: str) -> str:
|
def add_keep_next(p_xml):
|
||||||
"""Fuegt <w:keepNext/> in das pPr-Element ein. Falls kein pPr existiert,
|
"""Fuegt <w:keepNext/> in das pPr-Element ein. Falls kein pPr existiert,
|
||||||
wird es angelegt. Idempotent (wenn schon vorhanden, unveraendert)."""
|
wird es angelegt. Idempotent."""
|
||||||
if has_keep_next(p_xml):
|
if has_keep_next(p_xml):
|
||||||
return p_xml
|
return p_xml
|
||||||
if "<w:pPr>" in p_xml:
|
if "<w:pPr>" in p_xml:
|
||||||
return p_xml.replace("<w:pPr>", "<w:pPr><w:keepNext/>", 1)
|
return p_xml.replace("<w:pPr>", "<w:pPr><w:keepNext/>", 1)
|
||||||
if "<w:pPr/>" in p_xml:
|
if "<w:pPr/>" in p_xml:
|
||||||
return p_xml.replace("<w:pPr/>", "<w:pPr><w:keepNext/></w:pPr>", 1)
|
return p_xml.replace("<w:pPr/>", "<w:pPr><w:keepNext/></w:pPr>", 1)
|
||||||
# kein pPr: vor <w:r ...> oder vor </w:p>
|
|
||||||
new_ppr = "<w:pPr><w:keepNext/></w:pPr>"
|
new_ppr = "<w:pPr><w:keepNext/></w:pPr>"
|
||||||
if "<w:r" in p_xml:
|
if "<w:r" in p_xml and p_xml.startswith("<w:p>"):
|
||||||
return p_xml.replace("<w:p>", "<w:p>" + new_ppr, 1) \
|
return p_xml.replace("<w:p>", "<w:p>" + new_ppr, 1)
|
||||||
if p_xml.startswith("<w:p>") else p_xml
|
|
||||||
return p_xml.replace("</w:p>", new_ppr + "</w:p>", 1)
|
return p_xml.replace("</w:p>", new_ppr + "</w:p>", 1)
|
||||||
|
|
||||||
# Regex: ein <w:p ...>...</w:p>, optional gefolgt vom oeffnenden Marker fuer
|
|
||||||
# Tabelle (<w:tbl>) oder schliessenden Body (</w:body>). Wir splitten nicht,
|
|
||||||
# sondern iterieren paragraphenweise und tracken Tabellen-Schachtelung.
|
|
||||||
|
|
||||||
P_RE = re.compile(r"<w:p\b[^>]*>.*?</w:p>", re.DOTALL)
|
P_RE = re.compile(r"<w:p\b[^>]*>.*?</w:p>", re.DOTALL)
|
||||||
TBL_OPEN = "<w:tbl>"
|
TBL_OPEN = "<w:tbl>"
|
||||||
TBL_CLOSE = "</w:tbl>"
|
TBL_CLOSE = "</w:tbl>"
|
||||||
|
|
||||||
def process_document_xml(xml: str) -> tuple[str, dict]:
|
def process_document_xml(xml):
|
||||||
"""Findet Listen-Sequenzen ausserhalb von Tabellen, wendet 3-3-Regel an.
|
"""Tokenisiert den Body, wendet 3-3-Regel auf Bullet-Listen an und
|
||||||
Gibt das modifizierte XML und Statistiken zurueck."""
|
fuegt nach jedem H2-Heading einen Trenn-Absatz ein."""
|
||||||
# Tokenize: <w:tbl>...</w:tbl>-Bereiche markieren, damit wir sie ueberspringen.
|
|
||||||
# Ansatz: wir gehen durch das XML und tracken aktuelle Tabellen-Tiefe.
|
|
||||||
# Wenn Tiefe > 0: Bullets in Tabellen-Zellen ueberspringen.
|
|
||||||
out = []
|
out = []
|
||||||
pos = 0
|
bullet_run = []
|
||||||
table_depth = 0
|
table_depth = 0
|
||||||
bullet_run: list[tuple[int, str]] = [] # (out_idx, p_xml) Indizes in out
|
|
||||||
stats = {"lists": 0, "bullets_in_lists": 0, "bullets_keepnext": 0,
|
stats = {"lists": 0, "bullets_in_lists": 0, "bullets_keepnext": 0,
|
||||||
"skipped_in_tables": 0}
|
"skipped_in_tables": 0, "h2_headings": 0, "separators_added": 0}
|
||||||
|
|
||||||
def flush_run():
|
def flush_run():
|
||||||
if not bullet_run:
|
if not bullet_run:
|
||||||
@@ -102,9 +124,6 @@ def process_document_xml(xml: str) -> tuple[str, dict]:
|
|||||||
stats["bullets_keepnext"] += 1
|
stats["bullets_keepnext"] += 1
|
||||||
bullet_run.clear()
|
bullet_run.clear()
|
||||||
|
|
||||||
# Wir scannen das XML linear nach <w:p ...>...</w:p>, <w:tbl>, </w:tbl>
|
|
||||||
# und sammeln Bullet-Sequenzen ausserhalb von Tabellen.
|
|
||||||
# Dafuer iterieren wir mit einem regex der ALLE drei Token findet.
|
|
||||||
token_re = re.compile(
|
token_re = re.compile(
|
||||||
r"(?P<tblopen>" + re.escape(TBL_OPEN) + r")"
|
r"(?P<tblopen>" + re.escape(TBL_OPEN) + r")"
|
||||||
r"|(?P<tblclose>" + re.escape(TBL_CLOSE) + r")"
|
r"|(?P<tblclose>" + re.escape(TBL_CLOSE) + r")"
|
||||||
@@ -113,49 +132,46 @@ def process_document_xml(xml: str) -> tuple[str, dict]:
|
|||||||
)
|
)
|
||||||
last_end = 0
|
last_end = 0
|
||||||
for m in token_re.finditer(xml):
|
for m in token_re.finditer(xml):
|
||||||
# nicht-tokenisierten Text dazwischen anhaengen
|
|
||||||
if m.start() > last_end:
|
if m.start() > last_end:
|
||||||
out.append(xml[last_end:m.start()])
|
out.append(xml[last_end:m.start()])
|
||||||
last_end = m.end()
|
last_end = m.end()
|
||||||
|
|
||||||
if m.group("tblopen"):
|
if m.group("tblopen"):
|
||||||
flush_run() # Listen vor Tabelle abschliessen
|
flush_run()
|
||||||
table_depth += 1
|
table_depth += 1
|
||||||
out.append(m.group())
|
out.append(m.group())
|
||||||
elif m.group("tblclose"):
|
elif m.group("tblclose"):
|
||||||
flush_run() # innerhalb-Tabellen-Listen wir flushen, aber haben
|
flush_run()
|
||||||
# sie eh nicht angesammelt
|
|
||||||
table_depth -= 1
|
table_depth -= 1
|
||||||
out.append(m.group())
|
out.append(m.group())
|
||||||
else:
|
else:
|
||||||
p_xml = m.group("para")
|
p_xml = m.group("para")
|
||||||
out.append(p_xml)
|
out.append(p_xml)
|
||||||
if table_depth > 0:
|
if table_depth > 0:
|
||||||
# Bullets in Tabellen-Zellen ignorieren
|
|
||||||
if is_bullet_paragraph(p_xml):
|
if is_bullet_paragraph(p_xml):
|
||||||
stats["skipped_in_tables"] += 1
|
stats["skipped_in_tables"] += 1
|
||||||
# nicht-bullet-paragraph in tabelle: kein effekt
|
|
||||||
continue
|
continue
|
||||||
if is_bullet_paragraph(p_xml):
|
if is_bullet_paragraph(p_xml):
|
||||||
bullet_run.append((len(out) - 1, p_xml))
|
bullet_run.append((len(out) - 1, p_xml))
|
||||||
else:
|
continue
|
||||||
# Sequenz-Ende: 3-3-Regel anwenden
|
|
||||||
flush_run()
|
flush_run()
|
||||||
|
if is_h2_paragraph(p_xml):
|
||||||
|
out.append(H2_SEP_XML)
|
||||||
|
stats["h2_headings"] += 1
|
||||||
|
stats["separators_added"] += 1
|
||||||
|
|
||||||
# Rest hinten dranhaengen
|
|
||||||
if last_end < len(xml):
|
if last_end < len(xml):
|
||||||
out.append(xml[last_end:])
|
out.append(xml[last_end:])
|
||||||
flush_run() # falls Liste am Body-Ende
|
flush_run()
|
||||||
return "".join(out), stats
|
return "".join(out), stats
|
||||||
|
|
||||||
def main() -> int:
|
def main():
|
||||||
if not DOCX_FILE.exists():
|
if not DOCX_FILE.exists():
|
||||||
sys.stderr.write(f"FEHLER: {DOCX_FILE} existiert nicht. "
|
sys.stderr.write(f"FEHLER: {DOCX_FILE} existiert nicht. "
|
||||||
f"Erst build.ps1 laufen lassen.\n")
|
f"Erst build.ps1 laufen lassen.\n")
|
||||||
return 1
|
return 1
|
||||||
log(f"Verarbeite: {DOCX_FILE}")
|
log(f"Verarbeite: {DOCX_FILE}")
|
||||||
|
|
||||||
# DOCX in memory einlesen
|
|
||||||
with zipfile.ZipFile(DOCX_FILE, "r") as z:
|
with zipfile.ZipFile(DOCX_FILE, "r") as z:
|
||||||
members = {name: z.read(name) for name in z.namelist()}
|
members = {name: z.read(name) for name in z.namelist()}
|
||||||
|
|
||||||
@@ -163,12 +179,10 @@ def main() -> int:
|
|||||||
new_xml, stats = process_document_xml(doc_xml)
|
new_xml, stats = process_document_xml(doc_xml)
|
||||||
|
|
||||||
if new_xml == doc_xml:
|
if new_xml == doc_xml:
|
||||||
log(" keine Aenderung — keine bullet-Listen gefunden oder bereits gesetzt")
|
log(" keine Aenderung")
|
||||||
members["word/document.xml"] = new_xml.encode("utf-8")
|
members["word/document.xml"] = new_xml.encode("utf-8")
|
||||||
|
|
||||||
# DOCX zurueckschreiben (mode='w' truncatet)
|
|
||||||
with zipfile.ZipFile(DOCX_FILE, "w", zipfile.ZIP_DEFLATED) as z:
|
with zipfile.ZipFile(DOCX_FILE, "w", zipfile.ZIP_DEFLATED) as z:
|
||||||
# [Content_Types].xml zuerst
|
|
||||||
order = sorted(members.keys(),
|
order = sorted(members.keys(),
|
||||||
key=lambda n: (0 if n == "[Content_Types].xml" else 1, n))
|
key=lambda n: (0 if n == "[Content_Types].xml" else 1, n))
|
||||||
for name in order:
|
for name in order:
|
||||||
@@ -178,6 +192,8 @@ def main() -> int:
|
|||||||
log(f" Bullets in Listen: {stats['bullets_in_lists']}")
|
log(f" Bullets in Listen: {stats['bullets_in_lists']}")
|
||||||
log(f" keepNext gesetzt: {stats['bullets_keepnext']}")
|
log(f" keepNext gesetzt: {stats['bullets_keepnext']}")
|
||||||
log(f" Bullets in Tabellen uebersprungen: {stats['skipped_in_tables']}")
|
log(f" Bullets in Tabellen uebersprungen: {stats['skipped_in_tables']}")
|
||||||
|
log(f" H2-Headings gefunden: {stats['h2_headings']}")
|
||||||
|
log(f" H2-Trenn-Absaetze eingefuegt: {stats['separators_added']}")
|
||||||
log("Fertig.")
|
log("Fertig.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -179,18 +179,20 @@ endobj
|
|||||||
<< /S /GoTo /D [ 82 0 R /Fit ] >>
|
<< /S /GoTo /D [ 82 0 R /Fit ] >>
|
||||||
endobj
|
endobj
|
||||||
85 0 obj
|
85 0 obj
|
||||||
<< /Filter /FlateDecode /Length 2849 >>
|
<< /Filter /FlateDecode /Length 3132 >>
|
||||||
stream
|
stream
|
||||||
x<EFBFBD><EFBFBD>[͎#<23>
|
x<EFBFBD><EFBFBD>[͎#<23>
|
||||||
<EFBFBD><EFBFBD>S<EFBFBD><05>"R<EFBFBD><EFBFBD>a<EFBFBD>m<EFBFBD><17>[<5B><>-<2D><>A<EFBFBD><EFBFBD>9$<24><>~$Q<>(U<>]=ӝ]<0C>j<EFBFBD>%ş<><1F>u<><75><EFBFBD>?=<3D>v]<5D><>
|
<EFBFBD><EFBFBD>S<EFBFBD><05>"<22>_<EFBFBD>a<EFBFBD>m<EFBFBD><17>[<5B><>-<2D><> {<7B><1C>K^?<3F>HQ<48><51>\v<>Lg<4C><67>Qv<51><76><EFBFBD><EFBFBD><EFBFBD><EFBFBD>#<23>V<EFBFBD>?<0E><>ˋj<CB8B>%ؤ<>a<EFBFBD><61><EFBFBD><EFBFBD>ýG<47><7F><EFBFBD>_/<2F>{<7B><1C>Z<EFBFBD><5A><EFBFBD><EFBFBD><EFBFBD><12>;<3B><><EFBFBD><EFBFBD>o<EFBFBD><6F>ϟ_s<0F><><EFBFBD>?<3F><>׃ufq<66><71><EFBFBD><EFBFBD><<3C><><EFBFBD><EFBFBD>}
|
||||||
<0F><>?<3F><1F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>i<>:<3A><><EFBFBD>%8w<38><77>-^<5E><>oߞ~<7E>5<EFBFBD><35><EFBFBD><EFBFBD><EFBFBD>9<EFBFBD><39>1<1C>[<5B>~;<18>kt<6B><74>_<EFBFBD><5F>?<3F><><EFBFBD><EFBFBD><EFBFBD>(<28><><EFBFBD>YhMLW<1B>$<24><>OI<4F><49><EFBFBD><EFBFBD>_n:<1E>,<2C>8<<3C><>s%<25>sK<73><4B><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Q)<29>'P<><50>\<5C>h<EFBFBD><02>O<EFBFBD>~K<><4B>wHWzF<7A><46><EFBFBD>|ǞB<C79E>e_<65>O:<3A><>u<EFBFBD>
|
<e
|
||||||
<EFBFBD>gW~k3t<33>{N<>bY<62><59>t<EFBFBD>^<=<3D>c<EFBFBD>K<EFBFBD>](=<3D><EFBFBD><D7B7><EFBFBD>*<2A>o<1B>qv<71>3<>w<EFBFBD>ᓌK<><16><><15>Ybtl<74>pB{<7B><1A>z<EFBFBD><7A>O<EFBFBD>J<EFBFBD>**<2A><>vQo<51>S<EFBFBD><53><EFBFBD>ZN<5A>a<EFBFBD>Ҭg<D2AC><67><EFBFBD><EFBFBD>\<5C>Es<45><0F><><EFBFBD>ҿAsf<73><66><07>n<EFBFBD>>4<><34>+m<>.<2E><07>T<EFBFBD><54><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>BAb/︺<><EFB8BA><EFBFBD><EFBFBD><EFBFBD>PM<50><4D>$?<3F>)O<10><><EFBFBD>5d<35><64><EFBFBD>ͽ<EFBFBD><CDBD>.wl<77><19><07><>4<EFBFBD><34>ĭ^I<><49>r]<07>5MH<1F><><1D><>[<5B>M'<1D>+<2B>Iz4<7A><34>n<EFBFBD><6E><EFBFBD><EFBFBD><0E>6<EFBFBD>b\<5C><>}<16><06>>a {<7B><>I<EFBFBD>V<>r42<34><32>\<5C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>))<29>@<13>ڴ<01>NPf<50>lȗ<6C>!<21>̖,<0F>?"G^<5E>#<23>)<29><>s<EFBFBD>L}<7D>Z&<26>]<5D><><0B>?<3F>A<EFBFBD><04><><EFBFBD>n<>1
|
<EFBFBD><EFBFBD>K;<3B><><EFBFBD>Ee"Ne<4E>Q<EFBFBD><51><EFBFBD><EFBFBD>˟n&<1D>.<2E>z}<7D><><EFBFBD><EFBFBD>ܼ_Rp<52>/_<0F><1D><><EFBFBD>*_<><5F><1F>W<EFBFBD><57>)<29>o<EFBFBD>S<EFBFBD><53>|
|
||||||
lxZ*$<24><><EFBFBD><1B>$<19>jSuT<75>TIO<49>!>>j<><13><>Wƛ<C69B>-<2D><><18><>*<2A><><EFBFBD>u<EFBFBD><75><EFBFBD>g8^<5E><>;Oj<4F>7<EFBFBD>NK<4E>!ey<65><79>}ejF<6A><EFBFBD>
|
<EFBFBD>Wm|<7C>Q<EFBFBD><51>S,<2C><>[<5B><><EFBFBD>)<29>5<EFBFBD>T<EFBFBD><54><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ô<EFBFBD><C394><EFBFBD>T<EFBFBD><54>pL<70><4C><EFBFBD>ӫ=<3D>]<5D><>iB<69>5<EFBFBD><EFBFBD><7F><1B><><EFBFBD>eC1<43>-<2D>cj<63>&5<> %<><C292><EFBFBD><EFBFBD>Ek<45><6B>#k1<6B><31>;<3B><><EFBFBD><03>S<EFBFBD><53>v!<21><><EFBFBD>]<5D>]<5D><><EFBFBD>T<EFBFBD><54>h)<29>ɩ+9<><39>>K<>t<EFBFBD><74><EFBFBD><EFBFBD> | ||||||