Titeltext: eine Quelle fuer PDF-Titelseite und Startseite
Die Startseite zeigte einen anderen Titeltext als das PDF. Der Text stand
VIERMAL im Repository: NEUER_TITEL.md, die Konstanten in
bilder_04/erzeuge_titelseite.py, der Titelblock des Vorworts - und noch einmal
fest einprogrammiert im Hero-Block von baue_landingpage().
Die Ursache ist messbar: Die drei Commits, die den Titel ueberarbeitet haben
(ca5621f, 199dd3b, 1659fb9 im Ursprungs-Repository), fassten jedes Mal
dieselben drei Dateien an. build_version_04.py war in keinem davon, und
OR_HTML_04/index.html taucht in ihren Dateilisten gar nicht auf. Online stand
deshalb:
Unterzeile "Von den mathematischen Grundlagen zur quantitativen
Portfolio-Optimierung an den Finanzmaerkten."
Kompendium "Ein vollstaendiges Kompendium mit Algorithmen ..."
Anwendungen fehlte ganz
Behandlung wie bei den neun Nummern-Funden zuvor: nicht die Kopie korrigieren,
sondern die Kopie abschaffen. Das neue titeltexte_04.py zerlegt NEUER_TITEL.md
in Titel, Untertitel, Kompendium- und Anwendungen-Absatz; PDF-Titelseite und
Startseite lesen dieselbe Funktion. Der Autor bleibt bewusst Konstante im
Generator - die Vorlage beschreibt das Buch, nicht sein Impressum.
Der entscheidende Beleg: Die neu erzeugte titelseite.svg ist BYTE-IDENTISCH
(97fb40d7...) zur vorherigen - der Umbau hat am PDF nachweislich nichts
veraendert. Auch nicht am Umbruch: Die frueher handgesetzten drei Zeilen
entstehen jetzt automatisch aus _umbrechen(), und zwar exakt so. Ein
Textvergleich ueber das SVG war uebrigens nicht moeglich - es enthaelt kein
einziges <text>-Element, matplotlib zeichnet alle 726 Buchstaben als Kurven.
Das Vorwort bleibt von Hand gepflegter Buchtext, wird aber von
pruefe_titeltexte() bei jedem --check gegen die Quelle gestellt. Gleich beim
ersten Lauf fand es einen echten Unterschied: Das Vorwort setzte einen
Geviertstrich, die Vorlage einen Halbgeviertstrich. Im Deutschen ist der
Halbgeviertstrich der richtige - das Vorwort folgt jetzt der Quelle.
Veroeffentlicht: 5 Dateien uebertragen, alle vierzehn HTTP-Proben richtig.
Gegenprobe vom Server: alle drei Absaetze der Startseite sind zeichengenau
identisch mit NEUER_TITEL.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
47d63dfe9c
commit
022bd4eaca
15 changed files with 250 additions and 37 deletions
|
|
@ -23,7 +23,7 @@ und Matrixmethoden sowie HiGHS, SciPy, Google OR-Tools und CVXPY.
|
|||
|
||||
Von **Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und
|
||||
Maschinen- und Netzwerkauslastung** über **Energieverteilung und Ressourcenplanung** bis zur
|
||||
**Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten** — mit Python
|
||||
**Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten** – mit Python
|
||||
modellieren, optimieren und belegbar fundierte Entscheidungen treffen.
|
||||
|
||||
Autor / Herausgeber: Dieter Schlüter
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ import subprocess
|
|||
import sys
|
||||
import zipfile
|
||||
|
||||
from titeltexte_04 import lies_titeltexte, _saeubern as _saeubere_titeltext
|
||||
|
||||
HIER = os.path.dirname(os.path.abspath(__file__))
|
||||
BASIS = os.path.dirname(HIER)
|
||||
|
||||
|
|
@ -389,9 +391,77 @@ def pruefe_dateien() -> list[str]:
|
|||
fehlend.append(name)
|
||||
|
||||
fehlend.extend(pruefe_assets())
|
||||
fehlend.extend(pruefe_titeltexte())
|
||||
return fehlend
|
||||
|
||||
|
||||
# Die Datei, die den Titeltext ein viertes Mal traegt: als Buchtext, den ein
|
||||
# Leser im Vorwort sieht. Erzeugt wird er dort NICHT - er steht mit
|
||||
# Hervorhebungen im Fliesstext und gehoert dem Autor. Aber er wird geprueft.
|
||||
VORWORT_DATEI = "00_Vorwort_und_Lesehilfe.md"
|
||||
|
||||
|
||||
def pruefe_titeltexte() -> list[str]:
|
||||
"""Vergleicht den Titelblock des Vorworts mit NEUER_TITEL.md.
|
||||
|
||||
PDF-Titelseite und Startseite lesen die Quelle direkt und koennen deshalb
|
||||
gar nicht abweichen. Das Vorwort ist die dritte Stelle - und genau eine
|
||||
solche ungeprueft mitgefuehrte Kopie hat dazu gefuehrt, dass die Startseite
|
||||
monatelang einen anderen Untertitel zeigte als das PDF."""
|
||||
quelle = lies_titeltexte()
|
||||
pfad = os.path.join(HIER, VORWORT_DATEI)
|
||||
if not os.path.exists(pfad):
|
||||
return []
|
||||
with open(pfad, encoding="utf-8") as f:
|
||||
zeilen = f.read().split("\n")
|
||||
|
||||
# Gesucht werden die '#'- und '##'-Ueberschrift und die beiden Absaetze
|
||||
# danach. Der '{-}'-Zusatz (Pandoc: nicht nummerieren) gehoert nicht zum
|
||||
# Text.
|
||||
def ohne_marke(text: str) -> str:
|
||||
return re.sub(r"\s*\{-\}\s*$", "", text)
|
||||
|
||||
gefunden: dict[str, tuple[int, str]] = {}
|
||||
absaetze: list[tuple[int, str]] = []
|
||||
block: list[str] = []
|
||||
block_start = 0
|
||||
for nr, zeile in enumerate(zeilen, start=1):
|
||||
if zeile.startswith("# ") and "titel" not in gefunden:
|
||||
gefunden["titel"] = (nr, _saeubere_titeltext(ohne_marke(zeile[2:])))
|
||||
elif zeile.startswith("## ") and "untertitel" not in gefunden:
|
||||
gefunden["untertitel"] = (nr, _saeubere_titeltext(ohne_marke(zeile[3:])))
|
||||
elif zeile.strip() and not zeile.startswith("#") and "untertitel" in gefunden:
|
||||
if not block:
|
||||
block_start = nr
|
||||
block.append(zeile)
|
||||
else:
|
||||
if block:
|
||||
absaetze.append((block_start, _saeubere_titeltext(" ".join(block))))
|
||||
block = []
|
||||
if len(absaetze) == 2:
|
||||
break
|
||||
if block:
|
||||
absaetze.append((block_start, _saeubere_titeltext(" ".join(block))))
|
||||
for name, eintrag in zip(("kompendium", "anwendungen"), absaetze):
|
||||
gefunden[name] = eintrag
|
||||
|
||||
fehler = []
|
||||
for name in ("titel", "untertitel", "kompendium", "anwendungen"):
|
||||
if name not in gefunden:
|
||||
print(f"FEHLER: {VORWORT_DATEI}: Der Titelblock enthaelt kein "
|
||||
f"'{name}' - erwartet werden '#', '##' und zwei Absaetze.")
|
||||
fehler.append(f"{VORWORT_DATEI}:{name}")
|
||||
continue
|
||||
nr, ist = gefunden[name]
|
||||
if ist != quelle[name]:
|
||||
print(f"FEHLER: {VORWORT_DATEI}:{nr}: '{name}' weicht von der "
|
||||
f"Titelquelle ab.")
|
||||
print(f" Vorwort: {ist}")
|
||||
print(f" Quelle: {quelle[name]}")
|
||||
fehler.append(f"{VORWORT_DATEI}:{nr}")
|
||||
return fehler
|
||||
|
||||
|
||||
# Dateien, die der Bau selbst in OR_HTML_04/assets/ erzeugt. Alles andere, was
|
||||
# der Seitenkopf einbindet, muss aus der Quelle web_04/ kommen.
|
||||
ERZEUGTE_ASSETS = {
|
||||
|
|
@ -2172,22 +2242,25 @@ def baue_landingpage(html_verz: str, struktur: list[dict], labels: dict,
|
|||
f'<section class="kapitelgruppe"><h2>{titel}</h2>'
|
||||
f'<div class="kartenraster">{karten}</div></section>')
|
||||
|
||||
# Dieselbe Quelle wie die Titelseite des PDFs (NEUER_TITEL.md). Frueher
|
||||
# standen die drei Absaetze hier fest im Quelltext - und wichen deshalb
|
||||
# vom PDF ab, sobald der Titel ueberarbeitet wurde.
|
||||
titeltexte = lies_titeltexte()
|
||||
|
||||
inhalt = f"""
|
||||
<section class="hero">
|
||||
<div class="hero-text">
|
||||
<p class="hero-kicker">KOMPENDIUM</p>
|
||||
<h1>{SITE_TITEL}</h1>
|
||||
<p class="hero-untertitel">Von den mathematischen Grundlagen zur quantitativen
|
||||
Portfolio-Optimierung an den Finanzmärkten.</p>
|
||||
<p class="hero-untertitel">{titeltexte["untertitel"]}</p>
|
||||
<div class="hero-badges">
|
||||
<span class="badge badge-indigo">HiGHS</span>
|
||||
<span class="badge badge-emerald">SciPy</span>
|
||||
<span class="badge badge-cyan">Google OR-Tools</span>
|
||||
<span class="badge badge-violett">CVXPY</span>
|
||||
</div>
|
||||
<p class="hero-kompendium"><em>Ein vollständiges Kompendium mit Algorithmen, Constraint
|
||||
Programming, Vektor- und Matrixmethoden sowie modernen Python-Solvern (HiGHS, SciPy,
|
||||
Google OR-Tools, CVXPY)</em></p>
|
||||
<p class="hero-kompendium"><em>{titeltexte["kompendium"]}</em></p>
|
||||
<p class="hero-anwendungen">{titeltexte["anwendungen"]}</p>
|
||||
<p class="hero-autor">Autor / Herausgeber: Dieter Schlüter<br>
|
||||
<dieter(dot)schlueter(atsign)linix(dot)de><br>
|
||||
Stand: {aktuelles_datum()}</p>
|
||||
|
|
|
|||
82
Operations_Research_mit_Python_Version_04/titeltexte_04.py
Normal file
82
Operations_Research_mit_Python_Version_04/titeltexte_04.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# titeltexte_04.py
|
||||
#
|
||||
# Liest den Titeltext des Buchs aus seiner EINEN Quelle:
|
||||
# Kritik_und_Verbesserungsvorschlaege/NEUER_TITEL.md
|
||||
#
|
||||
# Warum es dieses Modul gibt: Der Titeltext stand viermal im Repository - in
|
||||
# NEUER_TITEL.md, in den Konstanten von bilder_04/erzeuge_titelseite.py (PDF),
|
||||
# im Vorwort und fest einprogrammiert im Hero-Block von baue_landingpage()
|
||||
# (Website). Drei Ueberarbeitungen des Titels zogen die ersten drei nach und
|
||||
# liessen die vierte stehen: Die Startseite zeigte monatelang einen anderen
|
||||
# Untertitel als das PDF, und der Anwendungen-Absatz fehlte dort ganz.
|
||||
# Aufgefallen ist es niemandem, weil nichts die Fassungen verglichen hat.
|
||||
#
|
||||
# Jetzt lesen PDF-Titelseite und Startseite denselben Text ueber
|
||||
# lies_titeltexte(); das Vorwort bleibt von Hand gepflegter Buchtext, wird aber
|
||||
# von pruefe_dateien() gegen diese Quelle geprueft.
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
HIER = os.path.dirname(os.path.abspath(__file__))
|
||||
BASIS = os.path.dirname(HIER)
|
||||
|
||||
TITEL_QUELLE = os.path.join(BASIS, "Kritik_und_Verbesserungsvorschlaege",
|
||||
"NEUER_TITEL.md")
|
||||
|
||||
|
||||
def _saeubern(text: str) -> str:
|
||||
"""Entfernt Markdown-Hervorhebungen und zieht Zeilenumbrueche zu einfachen
|
||||
Leerzeichen zusammen. Die Vorlage setzt die Fettungen anders als das
|
||||
Vorwort - fuer den Vergleich zaehlt allein der Wortlaut."""
|
||||
return " ".join(text.replace("**", "").replace("*", "").split())
|
||||
|
||||
|
||||
def lies_titeltexte(pfad: str = TITEL_QUELLE) -> dict[str, str]:
|
||||
"""Zerlegt NEUER_TITEL.md in seine vier Bestandteile.
|
||||
|
||||
Aufbau der Vorlage:
|
||||
# <Titel>
|
||||
### <Untertitel>
|
||||
**<Kompendium-Absatz>**
|
||||
<Anwendungen-Absatz>
|
||||
|
||||
Fehlt ein Teil, wird abgebrochen statt ein halb gefuelltes Titelblatt zu
|
||||
erzeugen - ein Titelblatt ohne Untertitel faellt im PDF erst auf, wenn es
|
||||
gedruckt ist."""
|
||||
if not os.path.exists(pfad):
|
||||
raise SystemExit(f"FEHLER: Die Quelle des Titeltexts fehlt: {pfad}")
|
||||
with open(pfad, encoding="utf-8") as f:
|
||||
inhalt = f.read()
|
||||
|
||||
bloecke = [b.strip() for b in re.split(r"\n\s*\n", inhalt) if b.strip()]
|
||||
texte: dict[str, str] = {}
|
||||
absaetze: list[str] = []
|
||||
for block in bloecke:
|
||||
if block.startswith("### "):
|
||||
texte.setdefault("untertitel", _saeubern(block[4:]))
|
||||
elif block.startswith("# "):
|
||||
texte.setdefault("titel", _saeubern(block[2:]))
|
||||
elif not block.startswith("#"):
|
||||
absaetze.append(_saeubern(block))
|
||||
|
||||
if len(absaetze) >= 2:
|
||||
texte["kompendium"], texte["anwendungen"] = absaetze[0], absaetze[1]
|
||||
|
||||
fehlend = [name for name in ("titel", "untertitel", "kompendium", "anwendungen")
|
||||
if not texte.get(name)]
|
||||
if fehlend:
|
||||
raise SystemExit(
|
||||
f"FEHLER: In {os.path.relpath(pfad, BASIS)} fehlt: "
|
||||
f"{', '.join(fehlend)}.\n"
|
||||
f" Erwartet werden eine '#'-Ueberschrift (Titel), eine "
|
||||
f"'###'-Ueberschrift\n"
|
||||
f" (Untertitel) und zwei Absaetze (Kompendium, Anwendungen).")
|
||||
return texte
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for name, text in lies_titeltexte().items():
|
||||
print(f"{name}:\n {text}\n")
|
||||
Loading…
Reference in a new issue