Glossar-Links: Fachbegriffe im Fließtext automatisch verlinken
Auto-Linking im Build: Glossar-Begriffe werden im Fließtext automatisch
erkannt und als Link auf den Glossar-Eintrag gesetzt — erste Nennung je
Begriff je Datei, dezent gestrichelte Unterstreichung im HTML, hyperref
im PDF. Kein manuelles Markieren bei Textänderungen nötig.
Neue Infrastruktur:
- glossar_eintraege_04.py: glossar_slug() + LINK_TEXTE-Map (automatisch
generiert aus EINTRAEGEN + Abkürzungs-Extraktion)
- erzeuge_glossar_04.py: jeder Eintrag bekommt {#gloss:<slug>}-Anchor
- build_version_04.py: resolve_glossar() — Auto-Linking + {gl:}-Marke
- web_04/site.css: .glossar-link (dezent, nicht wie Navigationslink)
5 neue Glossar-Einträge (waren als Begriff im Text, aber nicht im Glossar):
Solver, Scheduling, Graphen, Backtest, DCP (Disciplined Convex Programming)
Schutzmechanismen:
- Code-Blöcke, Inline-Code, {idx:}/{ref:}-Marken, Markdown-Links und
Überschriften werden nicht verlinkt
- Verschachtelung verhindert: 'CP-SAT-Solver' als Ganzes, nicht 'CP'+[
-'SAT'+[-'Solver'
- 'OR' als 2-Zeichen-Abkürzung nicht auto-verlinkt (False Positive in
'OR-Tools'); {gl:OR} als manuelle Marke
- Glossar selbst nicht auto-verlinkt (sonst Self-Links)
- reflow_markdown(): ::: als Block-Grenze (sonst kollabiert fenced div)
619 Glossar-Links im Gesamtdokument, 239 Anchor im Glossar, 34 im Vorwort.
This commit is contained in:
parent
81ddb02d5c
commit
e8abb66d75
52 changed files with 4316 additions and 1921 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -157,4 +157,4 @@ Details zu den Lernpfaden stehen in [`00_Vorwort_und_Lesehilfe.md`](00_Vorwort_u
|
|||
|
||||
---
|
||||
|
||||
*Autor / Herausgeber: Dieter Schlüter · Stand: 10. September 2026 v16.09*
|
||||
*Autor / Herausgeber: Dieter Schlüter · Stand: 10. September 2026 v17.38*
|
||||
|
|
|
|||
|
|
@ -711,6 +711,17 @@ def reflow_markdown(text: str) -> str:
|
|||
i += 1
|
||||
continue
|
||||
|
||||
# Fenced-Div-Grenzen (::: {#...} / :::): wie Ueberschriften als
|
||||
# Blockgrenze behandeln, sonst wuerde reflow die ::: mit dem
|
||||
# folgenden Absatz zu einer Zeile verbinden — und Pandoc wuerde den
|
||||
# div nicht mehr erkennen. Genutzt im Glossar (Anhang E): jeder
|
||||
# Eintrag steht in einem ::: {#gloss:<slug>} ... :::-Block.
|
||||
if content_stripped.startswith(":::"):
|
||||
flush()
|
||||
out.append(raw)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if _HR_RE.match(content_stripped):
|
||||
flush()
|
||||
out.append(raw)
|
||||
|
|
@ -1070,6 +1081,162 @@ def resolve_index(text: str) -> str:
|
|||
return text
|
||||
|
||||
|
||||
# --- Glossar-Links: Fachbegriffe im Fließtext automatisch verlinken -------
|
||||
#
|
||||
# resolve_glossar() sucht im Fließtext nach Begriffen aus LINK_TEXTE (siehe
|
||||
# glossar_eintraege_04.py) und ersetzt das ERSTE Vorkommen je Begriff im
|
||||
# aktuellen Text durch einen Markdown-Link auf den Glossar-Eintrag. "Erste
|
||||
# Nennung" statt jedes Vorkommen: ein Begriff, der 576-mal vorkommt ('Solver'),
|
||||
# wuerde sonst jeden Absatz mit Links pflastern.
|
||||
#
|
||||
# Ausgeschlossen vom Link sind Code-Bloecke (```...```), Inline-Code (`...`),
|
||||
# Marken ({idx:...}, {ref:...}, {#...}), Markdown-Links [...](...) und
|
||||
# Ueberschriften — dort wuerde der Link stoen oder kaputtgehen.
|
||||
#
|
||||
# fuer_seite=False: Link als [Begriff](#gloss:slug) — fuer das Gesamtdokument.
|
||||
# fuer_seite=True: Link als [Begriff](anhang-glossar.md#gloss:slug) — fuer
|
||||
# Einzelseiten (seitenuebergreifend).
|
||||
#
|
||||
# Die manuelle Marke {gl:Begriff} wird ebenfalls aufgeloest — sie erzwingt
|
||||
# einen Link auch fuer Begriffe, die nicht in LINK_TEXTE stehen (z.B. weil
|
||||
# sie zu mehrdeutig fuer Auto-Linking sind, aber an dieser Stelle als
|
||||
# Fachbegriff gemeint sind).
|
||||
|
||||
_GLOSSAR_TOKEN_RE = re.compile(
|
||||
r'`[^`]*`' # Inline-Code `...`
|
||||
r'|\{[^}]*\}' # Marken {...}
|
||||
r'|\[[^\]]*\]\([^)]*\)' # Markdown-Links [text](url)
|
||||
)
|
||||
|
||||
_GLOSSAR_GL_RE = re.compile(r'\{gl:([^}]+)\}')
|
||||
|
||||
|
||||
def resolve_glossar(text: str, fuer_seite: bool = False) -> str:
|
||||
"""Verlinkt Glossar-Begriffe im Fließtext automatisch (erste Nennung je
|
||||
Begriff im uebergebenen Text) und loest {gl:}-Marken auf."""
|
||||
from glossar_eintraege_04 import LINK_TEXTE
|
||||
|
||||
# {gl:Begriff}-Marken zuerst: erzwingen einen Link auch fuer Begriffe,
|
||||
# die nicht auto-linkbar sind. 'Begriff' ist der Anzeigetext im Link;
|
||||
# das Ziel ist der Glossar-Eintrag mit diesem Namen als Indexmarke (oder
|
||||
# falls vorhanden, der Slug aus LINK_TEXTE).
|
||||
gl_treffer = 0
|
||||
def _gl_repl(m: re.Match) -> str:
|
||||
nonlocal gl_treffer
|
||||
begriff = m.group(1)
|
||||
slug = LINK_TEXTE.get(begriff) or _glossar_slug_fuer(begriff)
|
||||
ziel = (f'anhang-glossar.md#gloss:{slug}' if fuer_seite
|
||||
else f'#gloss:{slug}')
|
||||
gl_treffer += 1
|
||||
return f'[{begriff}]({ziel}){{.glossar-link}}'
|
||||
text = _GLOSSAR_GL_RE.sub(_gl_repl, text)
|
||||
if gl_treffer:
|
||||
print(f"Glossar-Links (manuell): {gl_treffer} Marke(n) aufgeloest.")
|
||||
|
||||
# Auto-Linking: laengste Link-Texte zuerst (damit 'Value at Risk' vor
|
||||
# 'VaR' gematcht wird, falls beide im Text stuenden).
|
||||
texte = sorted(LINK_TEXTE.items(), key=lambda x: -len(x[0]))
|
||||
|
||||
lines = text.split('\n')
|
||||
in_code_block = False
|
||||
code_fence = None
|
||||
seen: set[str] = set()
|
||||
treffer = 0
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.lstrip()
|
||||
# Code-Block: nicht verlinken, nur Fence-Ende erkennen.
|
||||
if in_code_block:
|
||||
if stripped.startswith(code_fence):
|
||||
in_code_block = False
|
||||
continue
|
||||
fence_m = re.match(r'^(```+|~~~+)', stripped)
|
||||
if fence_m:
|
||||
code_fence = fence_m.group(1)
|
||||
in_code_block = True
|
||||
continue
|
||||
# Ueberschriften: nicht verlinken (wuerde Inhaltsverzeichnis stoen).
|
||||
if _HEADING_RE.match(stripped):
|
||||
continue
|
||||
|
||||
# Zeile in Token-Segmente (Code/Marke/Link) und Fließtext splitten;
|
||||
# nur in Fließtext-Segmenten wird gesucht.
|
||||
new_parts: list[str] = []
|
||||
last = 0
|
||||
for m in _GLOSSAR_TOKEN_RE.finditer(line):
|
||||
seg = line[last:m.start()]
|
||||
seg, t = _glossar_in_segment(seg, texte, seen, fuer_seite)
|
||||
treffer += t
|
||||
new_parts.append(seg)
|
||||
new_parts.append(m.group(0)) # Token unverändert
|
||||
last = m.end()
|
||||
rest = line[last:]
|
||||
rest, t = _glossar_in_segment(rest, texte, seen, fuer_seite)
|
||||
treffer += t
|
||||
new_parts.append(rest)
|
||||
lines[i] = ''.join(new_parts)
|
||||
|
||||
if treffer:
|
||||
print(f"Glossar-Links (auto): {treffer} Begriff(e) verlinkt.")
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def _glossar_slug_fuer(begriff: str) -> str:
|
||||
"""Slug fuer einen Begriff, der nicht in LINK_TEXTE steht (fuer {gl:}."""
|
||||
from glossar_eintraege_04 import glossar_slug
|
||||
return glossar_slug(begriff)
|
||||
|
||||
|
||||
def _glossar_in_segment(segment: str, texte: list[tuple[str, str]],
|
||||
seen: set[str], fuer_seite: bool) -> tuple[str, int]:
|
||||
"""Sucht im Fließtext-Segment nach Glossar-Begriffen und ersetzt das
|
||||
erste Vorkommen je Begriff durch einen Link.
|
||||
|
||||
Alle Link-Texte werden im Original-Segment gesucht (mit finditer, also
|
||||
alle Vorkommen), dann nach Position sortiert und nicht-ueberlappend
|
||||
ersetzt — laengste zuerst bei gleicher Position. So wird 'CP-SAT' als
|
||||
Ganzes erkannt, bevor 'CP' (als Teilstring darin) ein eigenes Match
|
||||
erzeugt. Wird ein Match wegen Ueberlappung übersprungen, wird das
|
||||
naechste Vorkommen desselben Begriffs genommen (falls nicht ueberlappend).
|
||||
"""
|
||||
matches: list[tuple[int, int, str, str]] = []
|
||||
for link_text, slug in texte:
|
||||
if link_text in seen:
|
||||
continue
|
||||
pat = re.compile(r'\b' + re.escape(link_text) + r'\b')
|
||||
for m in pat.finditer(segment):
|
||||
matches.append((m.start(), m.end(), link_text, slug))
|
||||
# Alle Vorkommen sammeln — falls das erste wegen Ueberlappung
|
||||
# mit einem laengeren Begriff (z.B. 'CP-SAT-Solver' blockiert
|
||||
# 'Solver') uebersprungen wird, greift das naechste.
|
||||
if not matches:
|
||||
return segment, 0
|
||||
# Nach Position aufsteigend, bei gleicher Position laengste zuerst —
|
||||
# damit 'CP-SAT-Solver' (start=X, len=13) vor 'CP-SAT' (start=X, len=6)
|
||||
# und 'CP' (start=X, len=2) kommt.
|
||||
matches.sort(key=lambda x: (x[0], -(x[1] - x[0])))
|
||||
result: list[str] = []
|
||||
last_end = 0
|
||||
treffer = 0
|
||||
for start, end, link_text, slug in matches:
|
||||
if start < last_end:
|
||||
continue # ueberlappt mit einem bereits gesetzten laengeren Match
|
||||
if link_text in seen:
|
||||
continue # dieser Begriff wurde schon weiter oben im Segment
|
||||
# verlinkt (zweiter nicht-ueberlappender Treffer waere moeglich,
|
||||
# aber 'erste Nennung je Begriff' gilt global pro Datei, nicht pro
|
||||
# Segment)
|
||||
result.append(segment[last_end:start])
|
||||
ziel = (f'anhang-glossar.md#gloss:{slug}' if fuer_seite
|
||||
else f'#gloss:{slug}')
|
||||
result.append(f'[{link_text}]({ziel}){{.glossar-link}}')
|
||||
seen.add(link_text)
|
||||
treffer += 1
|
||||
last_end = end
|
||||
result.append(segment[last_end:])
|
||||
return ''.join(result), treffer
|
||||
|
||||
|
||||
# Wird als letzter Textbaustein angehaengt - landet damit unmittelbar vor dem
|
||||
# von Pandoc automatisch erzeugten \end{document}. \phantomsection sorgt
|
||||
# dafuer, dass hyperref einen Sprunganker fuer den (mit \section* erzeugten,
|
||||
|
|
@ -1150,6 +1317,11 @@ def baue_markdown() -> str:
|
|||
inhalt = inhalt.replace("Stand: {datum}", f"Stand: {datum}")
|
||||
# Vorwort-Signatur 'Köln, im {vorwort_datum}' dynamisch setzen.
|
||||
inhalt = inhalt.replace("{vorwort_datum}", aktuelles_monat_jahr())
|
||||
# Glossar-Links pro Datei (erste Nennung je Begriff je Datei) —
|
||||
# aber nicht im Glossar selbst: dort wuerde jeder Eintrag auf sich
|
||||
# selbst verlinken.
|
||||
if name != "94_Anhang_Glossar.md":
|
||||
inhalt = resolve_glossar(inhalt, fuer_seite=False)
|
||||
|
||||
# Inhaltsverzeichnis vor dem Vorwort einfuegen (LaTeX-Rohblock)
|
||||
if TOC_MARKE in inhalt and i == 0:
|
||||
|
|
@ -2293,6 +2465,9 @@ def baue_kapitel_seiten(html_verz: str, datei_seite: dict, labels: dict,
|
|||
text = text.replace("{vorwort_datum}", aktuelles_monat_jahr())
|
||||
text = reflow_markdown(text)
|
||||
text = resolve_numbering_seite(text, eintrag["seite"], labels, label_seite)
|
||||
# Glossar-Links — aber nicht im Glossar selbst (sonst Self-Links).
|
||||
if name != "94_Anhang_Glossar.md":
|
||||
text = resolve_glossar(text, fuer_seite=True)
|
||||
text = resolve_index(text)
|
||||
text, mit_plotly = resolve_plotly(text, fuer_html=True)
|
||||
text = resolve_karte(text, fuer_html=True)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ HIER = os.path.dirname(os.path.abspath(__file__))
|
|||
BASIS = os.path.dirname(HIER)
|
||||
sys.path.insert(0, HIER)
|
||||
|
||||
from glossar_eintraege_04 import EINTRAEGE # noqa: E402
|
||||
from glossar_eintraege_04 import EINTRAEGE, glossar_slug # noqa: E402
|
||||
|
||||
ZIEL = os.path.join(HIER, "94_Anhang_Glossar.md")
|
||||
|
||||
|
|
@ -70,11 +70,14 @@ BREITE = 96 # Zeilenbreite wie in den uebrigen Kapiteldateien
|
|||
# hier eintragen.
|
||||
NUR_IM_GLOSSAR = frozenset({
|
||||
"Alternativoptima",
|
||||
"Backtest",
|
||||
"Binärvariable",
|
||||
"CP-SAT",
|
||||
"Calmar Ratio",
|
||||
"DCP",
|
||||
"Dualitätstheorie",
|
||||
"Explainable OR",
|
||||
"Graphen",
|
||||
"Kanonische Standardform",
|
||||
"Kohärentes Risikomaß",
|
||||
"Lagrange-Multiplikator",
|
||||
|
|
@ -86,7 +89,9 @@ NUR_IM_GLOSSAR = frozenset({
|
|||
"Rebalancing",
|
||||
"Regime-Shift",
|
||||
"Relaxation",
|
||||
"Scheduling",
|
||||
"Schätzfehler",
|
||||
"Solver",
|
||||
"Solver-Status",
|
||||
"Transaktionskosten",
|
||||
"Unsicherheitsmenge",
|
||||
|
|
@ -184,7 +189,13 @@ def baue() -> str:
|
|||
zeilen += [f"## {buchstabe}", ""]
|
||||
letzter = buchstabe
|
||||
marke = "{idx:" + idx + "}" if idx else ""
|
||||
zeilen += [absatz(f"**{name}**{marke} — {text} → {ziel}"), ""]
|
||||
slug = glossar_slug(name)
|
||||
# ::: {#gloss:<slug>} ... ::: erzeugt im HTML einen <div id="gloss-slug">-
|
||||
# Anker und im PDF eine \hypertarget-Marke; beide sind das Ziel der
|
||||
# Auto-Links aus dem Fließtext.
|
||||
zeilen += [f"::: {{#gloss:{slug}}}",
|
||||
absatz(f"**{name}**{marke} — {text} → {ziel}"),
|
||||
":::", ""]
|
||||
zeilen += ["---", "", SCHLUSS, ""]
|
||||
return "\n".join(zeilen)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,41 @@ ueberschreibt sie und bricht ab, wenn er eine Handaenderung bemerkt.
|
|||
|
||||
EINTRAEGE = [
|
||||
|
||||
("Backtest", "Backtest",
|
||||
"Rückblickender Test einer Strategie auf historischen Daten. Ein Backtest "
|
||||
"ist nur so glaubwürdig wie seine Daten, seine Kostenannahme und seine "
|
||||
"Trennung von Trainings- und Testzeitraum — "
|
||||
"{ref:kap:handelsmaschine} zeigt die fünf Selbsttäuschungen, die ihn wertlos "
|
||||
"machen.",
|
||||
"{ref:kap:handelsmaschine}"),
|
||||
|
||||
("DCP (Disciplined Convex Programming)", "DCP",
|
||||
"Regelwerk von CVXPY, das jede Variable als konkav, konvex oder affin "
|
||||
"ausweist und aus der Kombination der Bausteine die Konkavität/Konvexität "
|
||||
"der Zielfunktion und der Nebenbedingungen erschließt. {ref:kap:qp-nlp} "
|
||||
"erklärt es; ein `DCPError` meldet eine Verletzung.",
|
||||
"{ref:kap:qp-nlp}"),
|
||||
|
||||
("Graphen", "Graphen",
|
||||
"Mathematische Struktur aus Knoten und Kanten; das Modellierungsmittel für "
|
||||
"Netzwerke, Touren und Flüsse. {ref:kap:graphen} behandelt Min-Cost-Flow, "
|
||||
"Matching und das Vehicle Routing Problem als Graphanwendungen.",
|
||||
"{ref:kap:graphen}"),
|
||||
|
||||
("Scheduling", "Scheduling",
|
||||
"Zuweisung von Tätigkeiten an Maschinen oder Personen unter "
|
||||
"Ressourcen- und Reihenfolgebedingungen. {ref:kap:cpsat} behandelt es mit "
|
||||
"CP-SAT (Intervallvariablen), {ref:kap:metaheuristiken} mit Simulated "
|
||||
"Annealing und LNS.",
|
||||
"{ref:kap:cpsat}"),
|
||||
|
||||
("Solver", "Solver",
|
||||
"Softwarekomponente, die ein mathematisches Modell löst — also das "
|
||||
"Optimierungsproblem in eine Lösung übersetzt. {ref:kap:oekosystem} "
|
||||
"vergleicht die in diesem Buch verwendeten Solver (HiGHS, OR-Tools/CP-SAT, "
|
||||
"CVXPY) und ihre Stärken.",
|
||||
"{ref:kap:oekosystem}"),
|
||||
|
||||
("Absolutbetrag", "Absolutbetrag (Modellierungsmuster)",
|
||||
"Modellierungsmuster für $|x-z|$: Der Betrag selbst ist nicht linear, lässt sich aber "
|
||||
"durch eine Hilfsvariable $d$ mit den beiden Bedingungen $x-z \\le d$ und $z-x \\le d$ "
|
||||
|
|
@ -1418,3 +1453,96 @@ EINTRAEGE = [
|
|||
"{ref:anhang:fehlerdiagnose}"),
|
||||
|
||||
]
|
||||
|
||||
|
||||
# --- Auto-Link-Infrastruktur ---------------------------------------------
|
||||
# Der Build verlinkt Glossar-Begriffe im Fließtext automatisch — die
|
||||
# {gl:}-Marke von Hand ist nur Ausnahme, nicht Regelfall. Diese beiden
|
||||
# Werte liefern die dazu noetigen Daten:
|
||||
#
|
||||
# glossar_slug(name) Eine stabile HTML-ID je Eintrag ('cp-sat', 'var'…).
|
||||
# Die Glossar-Datei traegt denselben Slug als
|
||||
# {#gloss:<slug>}-Anchor; der Link im Fließtext
|
||||
# zeigt dorthin.
|
||||
# LINK_TEXTE {Text_im_Buch: slug} — alles, was der Build durch
|
||||
# einen Link ersetzt. Generiert aus den EINTRAEGEN:
|
||||
# der Anzeigename je Eintrag, plus die Abkürzung,
|
||||
# falls der Name eine enthaelt (z.B. 'VaR' neben
|
||||
# 'Value at Risk').
|
||||
#
|
||||
# Einträge, die NICHT auto-verlinkt werden sollen, fehlen hier einfach.
|
||||
# 'Variable', 'Lösung', 'Matrix' etc. sind zu mehrdeutig als Fließtext und
|
||||
# werden nicht auto-verlinkt — wer sie verlinkt haben will, setzt {gl:}.
|
||||
|
||||
import re as _re
|
||||
|
||||
_UMLAUT = str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "Ä": "Ae",
|
||||
"Ö": "Oe", "Ü": "Ue", "ß": "ss"})
|
||||
|
||||
|
||||
def glossar_slug(name: str) -> str:
|
||||
"""ASCII-sicherer Slug fuer den Glossar-Anchor (wie die sec:-Labels)."""
|
||||
s = name.translate(_UMLAUT).lower()
|
||||
s = _re.sub(r"[^a-z0-9]+", "-", s).strip("-")
|
||||
return s or "glossar"
|
||||
|
||||
|
||||
def _abkuerzung_aus_name(name: str) -> str | None:
|
||||
"""Extrahiert die Abkürzung aus einem Glossar-Namen.
|
||||
|
||||
Zwei Fälle:
|
||||
* 'Value at Risk (VaR)' — Abkürzung im Klammerinhalt (kurz, überwiegend
|
||||
Großbuchstaben, z.B. VaR, CVaR, KKT, MILP).
|
||||
* 'EVPI (Expected Value ...)' — Abkürzung VOR der Klammer, wenn der
|
||||
Teil vor der Klammer durchgehend Großbuchstaben ist (z.B. EVPI, IIS,
|
||||
TSP, DTO, GIL, SAT).
|
||||
"""
|
||||
m = _re.search(r"\(([^)]+)\)\s*$", name)
|
||||
if not m:
|
||||
return None
|
||||
k = m.group(1).strip()
|
||||
if len(k) <= 15 and sum(1 for c in k if c.isupper()) >= len(k) * 0.5:
|
||||
return k
|
||||
# Fall 2: Teil vor der Klammer ist die Abkürzung (z.B. 'EVPI (... )').
|
||||
vor = name[: m.start()].strip()
|
||||
if vor and len(vor) <= 8 and vor.isupper() and vor.isalpha():
|
||||
return vor
|
||||
return None
|
||||
|
||||
|
||||
# Begriffe, die zwar im Glossar stehen, aber im Fließtext NICHT
|
||||
# auto-verlinkt werden (zu mehrdeutig — 'Lösung' als Lösung eines
|
||||
# Gleichungssystems vs. Lösung einer Aufgabe vs. Optimallösung).
|
||||
# Wer sie verlinkt haben will, setzt {gl:Lösung} — das ist die Ausnahme.
|
||||
_NICHT_AUTO_LINKEN = frozenset({
|
||||
"Binärvariable", "Entscheidungsvariable", "Intervallvariable",
|
||||
"Schlupfvariable", "Semikontinuierliche Variable",
|
||||
"Lösung", "Lösung (Modellierungsmuster)", "Instabile Lösung",
|
||||
"Unsinniges Ergebnis", "Verdächtig guter Backtest",
|
||||
"Matrix", "Matrixform", "Matrix-Vektor-Produkt",
|
||||
"Nebenbedingung (Constraint)",
|
||||
"Widersprüchliche Solver",
|
||||
# Fehlerbilder: werden im Glossar geführt, aber im Fließtext nicht
|
||||
# jedes Vorkommen verlinkt (sonst wird aus jedem 'Tippfehler' ein Link).
|
||||
})
|
||||
|
||||
|
||||
# Abkürzungen, die nicht auto-verlinkt werden — 'OR' ist 2 Zeichen und
|
||||
# taucht als Teilstring in 'OR-Tools' auf (Produktname, nicht die
|
||||
# Abkürzung für Operations Research). Wer 'OR' als Fachbegriff verlinken
|
||||
# will, setzt {gl:OR}. Die anderen 2-Zeichen-Abkürzungen (LP, QP, DP, CP)
|
||||
# kommen im Text nur als Fachbegriff vor und bleiben auto-verlinkt.
|
||||
_ZU_KURZ_ABK = frozenset({"OR"})
|
||||
|
||||
|
||||
LINK_TEXTE: dict[str, str] = {}
|
||||
for _name, _idx, _text, _ziel in EINTRAEGE:
|
||||
if _name in _NICHT_AUTO_LINKEN:
|
||||
continue
|
||||
_slug = glossar_slug(_name)
|
||||
# Anzeigename immer (z.B. 'Schattenpreis' -> 'schattenpreis').
|
||||
LINK_TEXTE[_name] = _slug
|
||||
# Abkürzung falls vorhanden (z.B. 'VaR' -> 'value-at-risk-var').
|
||||
_abk = _abkuerzung_aus_name(_name)
|
||||
if _abk and _abk not in _ZU_KURZ_ABK:
|
||||
LINK_TEXTE[_abk] = _slug
|
||||
|
|
|
|||
Loading…
Reference in a new issue