diff --git a/src/lib/inhalte/glossarverweise.ts b/src/lib/inhalte/glossarverweise.ts index 942ff7b..fc95593 100644 --- a/src/lib/inhalte/glossarverweise.ts +++ b/src/lib/inhalte/glossarverweise.ts @@ -87,26 +87,38 @@ export function verlinkeGlossar(html: string, glossar: GlossarEintrag[]): string if (teil.trim() === '') continue; if (offen.some((element) => gemiedeneElemente.has(element))) continue; - let text = teil; + // Ein gesetzter Verweis wird als festes Stück herausgelöst. Die folgenden + // Muster durchsuchen nur noch den freien Text – sonst fände etwa „OU" sein + // Gegenstück in der Erklärung von „Gruppenrichtlinie" im title-Attribut, + // und mitten im Attribut entstünde ein zweiter Verweis. + const stuecke: { text: string; fest: boolean }[] = [{ text: teil, fest: false }]; for (const { muster: regex, eintrag } of muster) { if (bereitsVerlinkt.has(eintrag.begriff)) continue; - const gefunden = regex.exec(text); - if (!gefunden) continue; + for (let s = 0; s < stuecke.length; s += 1) { + const stueck = stuecke[s]!; + if (stueck.fest) continue; - const erklaerung = eintrag.steht_fuer - ? `${eintrag.steht_fuer} – ${eintrag.erklaerung}` - : eintrag.erklaerung; + const gefunden = regex.exec(stueck.text); + if (!gefunden) continue; - text = - text.slice(0, gefunden.index) + - gefunden[1] + - `${gefunden[2]}` + - text.slice(gefunden.index + gefunden[0].length); + const erklaerung = eintrag.steht_fuer + ? `${eintrag.steht_fuer} – ${eintrag.erklaerung}` + : eintrag.erklaerung; + const verweis = `${gefunden[2]}`; - bereitsVerlinkt.add(eintrag.begriff); + stuecke.splice( + s, + 1, + { text: stueck.text.slice(0, gefunden.index) + gefunden[1], fest: false }, + { text: verweis, fest: true }, + { text: stueck.text.slice(gefunden.index + gefunden[0].length), fest: false }, + ); + bereitsVerlinkt.add(eintrag.begriff); + break; + } } - teile[i] = text; + teile[i] = stuecke.map((stueck) => stueck.text).join(''); } return teile.join(''); diff --git a/src/lib/inhalte/suche.test.ts b/src/lib/inhalte/suche.test.ts index a9c309e..f1b20a5 100644 --- a/src/lib/inhalte/suche.test.ts +++ b/src/lib/inhalte/suche.test.ts @@ -122,6 +122,41 @@ describe('Glossarbegriffe verlinken', () => { expect(html).not.toContain('title="Ein "Anführungszeichen"'); }); + it('verlinkt nicht in die Erklärung eines schon gesetzten Verweises hinein', () => { + // Die Erklärung von „Gruppenrichtlinie" enthält „OU". Ein zweiter Verweis + // darf nicht im title-Attribut des ersten landen – sonst bricht das HTML auf. + const eintraege = [ + { begriff: 'Gruppenrichtlinie', formen: [], erklaerung: 'Verknüpft an eine OU – was zuletzt kommt, gewinnt.', siehe_auch: [] }, + { begriff: 'OU', formen: [], erklaerung: 'Ein Ordner im Verzeichnis.', siehe_auch: [] }, + ]; + const html = verlinkeGlossar('
Laufwerke per Gruppenrichtlinie zuordnen.
', eintraege); + expect(html.match(/Gruppenrichtlinie zuordnen.'); + }); + + it('verlinkt beide Begriffe, wenn beide im Text stehen', () => { + const eintraege = [ + { begriff: 'Gruppenrichtlinie', formen: [], erklaerung: 'Verknüpft an eine OU.', siehe_auch: [] }, + { begriff: 'OU', formen: [], erklaerung: 'Ein Ordner im Verzeichnis.', siehe_auch: [] }, + ]; + const html = verlinkeGlossar('Die Gruppenrichtlinie hängt an der OU.
', eintraege); + expect(html.match(/OU.'); + expect(html).toContain('title="Verknüpft an eine OU."'); + }); + + it('erzeugt für alle Handouts wohlgeformte Verweise', () => { + // Gegen das echte Glossar und die echten Handouts: In keinem title-Attribut + // darf ein weiterer Verweis stecken. + for (const handout of alle.handouts) { + if (!handout.wesentlich) continue; + const html = verlinkeGlossar(handout.wesentlich.html, glossar); + expect(html, handout.id).not.toMatch(/title="[^"]*/g) ?? []).length); + } + }); + it('lässt HTML unverändert, wenn das Glossar leer ist', () => { expect(verlinkeGlossar('DNS
', [])).toBe('DNS
'); });