Glossarverweise nicht in title-Attribute setzen
Nach dem Setzen eines Verweises liefen die folgenden Suchmuster ueber denselben Text, einschliesslich des frisch eingefuegten title-Attributs. Enthielt die Erklaerung einen weiteren Glossarbegriff, entstand mitten im Attribut ein zweiter Verweis und das HTML brach auf - sichtbar im Handout als `gewinnt.">Gruppenrichtlinie`. Gesetzte Verweise werden jetzt als feste Stuecke herausgeloest; die weiteren Muster durchsuchen nur den freien Text. Ein neuer Test prueft alle Handouts gegen das echte Glossar auf wohlgeformte Verweise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4611974494
commit
1de38b9b7a
@@ -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);
|
||||
for (let s = 0; s < stuecke.length; s += 1) {
|
||||
const stueck = stuecke[s]!;
|
||||
if (stueck.fest) continue;
|
||||
|
||||
const gefunden = regex.exec(stueck.text);
|
||||
if (!gefunden) continue;
|
||||
|
||||
const erklaerung = eintrag.steht_fuer
|
||||
? `${eintrag.steht_fuer} – ${eintrag.erklaerung}`
|
||||
: eintrag.erklaerung;
|
||||
const verweis = `<a class="glossarbegriff" href="/glossar#${glossarAnker(eintrag.begriff)}" title="${maskiere(erklaerung)}">${gefunden[2]}</a>`;
|
||||
|
||||
text =
|
||||
text.slice(0, gefunden.index) +
|
||||
gefunden[1] +
|
||||
`<a class="glossarbegriff" href="/glossar#${glossarAnker(eintrag.begriff)}" title="${maskiere(erklaerung)}">${gefunden[2]}</a>` +
|
||||
text.slice(gefunden.index + gefunden[0].length);
|
||||
|
||||
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('');
|
||||
|
||||
@@ -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('<p>Laufwerke per Gruppenrichtlinie zuordnen.</p>', eintraege);
|
||||
expect(html.match(/<a /g)).toHaveLength(1);
|
||||
expect(html).toContain('title="Verknüpft an eine OU – was zuletzt kommt, gewinnt."');
|
||||
expect(html).toContain('>Gruppenrichtlinie</a> 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('<p>Die Gruppenrichtlinie hängt an der OU.</p>', eintraege);
|
||||
expect(html.match(/<a /g)).toHaveLength(2);
|
||||
expect(html).toContain('>OU</a>.</p>');
|
||||
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="[^"]*<a /);
|
||||
expect((html.match(/<a /g) ?? []).length, handout.id).toBe((html.match(/<\/a>/g) ?? []).length);
|
||||
}
|
||||
});
|
||||
|
||||
it('lässt HTML unverändert, wenn das Glossar leer ist', () => {
|
||||
expect(verlinkeGlossar('<p>DNS</p>', [])).toBe('<p>DNS</p>');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user