Den Parzival, den grabben wir! Einleitung

Wie man Texte aus dem Internet auf den heimischen PC zieht

Es gibt sie durchaus: Mittelhochdeutsche Texte im Internet. Wir wollen an ganz konkreten Aufgabenstellungen erkunden, wie man solche Ressourcen nutzt, die für private oder wissenschaftliche Zwecke zur freien Verfügung stehen. Um sie auch offline weiterverwenden zu können, werden wir uns die Texte aus dem Internet auf den eigenen Rechner zu laden. Manchmal benötigt man nicht mehr als ein paar Mausklicks, um eine geeignete lokale Kopie zu erzeugen. Spätestens bei umfangreicheren Materialien lohnt es sich aber diese Vorgänge zu automatisieren. Dazu verwenden wir dann die Skriptsprache Python.

Als erstes beschäftigen wir uns mit einem der prominentesten Texte der mittelhochdeutschen Epoche, dem Parzival des Wolfram von Eschenbach. Bei einem solchen Werk, das weiterhin in immer neuen Interpretationsansätzen und Nachdichtungen erkundet wird, können wir davon ausgehen, dass wir ohne größere Mühe auch Textfassungen im Internet finden. Tatsächlich werden wir beim ergoogeln des Parzivals sogar  im deutschsprachen Projekt Gutenberg fündig. Dort handelt es sich allerdings um die hübsch gereimte und versifizierte Nachdichtung von Karl Simrock in neuhochdeutscher Sprache. Von dieser für uns uninteressanten (literaturhistorisch durchaus spannenden!) Übersetzung abgesehen, finden wir jedoch eine reiche Auswahl von Ausgaben.

Das Internet verbunden mit der Digitalfotografie ermöglicht inzwischen Jedermann und an jedem Ort den Blick in die Originalhandschriften. Wer durch diese technischen Errungenschaften einen atmosphärischen Verlust befürchtet, darf sich selbstverständlich gerne Stoffhandschuhe anziehen und das Licht im heimischen Wohnzimmer dämpfen, bevor er den Codex Pal. germ. 339, in der Parzival-Forschung unter dem Sigle n bekannt, auf seinem Tablett öffnet, um sich an den Aquarellzeichnungen zu erfreuen, die die Handlung illustrieren:

Gahmuret und Belacane – Entnommen dem digitalen Faksimile des Cod. Pal. germ. 339 der Universität Heidelberg

Eine andere teilweise illustrierte Ausgabe des Parzivals, die Handschrift BSB Cgm 19  (Sigle G) lässt sich wahlweise in der Bayerischen Staatsbibliothek oder auch an jedem anderen Ort der Welt einsehen:

Kampf Artus gegen Gramoflanz – Entnommen dem digitalen Faksimile der Handschrift BSB cgm 19 der Bayrischen Staatsbibliothek

Derzeit wird an den Universitäten Bern und Basel eine komplette digitale Neuedition des Parzivals erstellt, die einen neuen Standard setzen wird. Die Edition der in der Forschung mit dem Sigle D bezeichneten Handschrift (Cod. 857, St. Gallen, Stiftsbibliothek) lässt sich bereits einsehen und nutzen. Diese bietet zusätzlich zur fotografischen Abbildung auch jeweils die kommentierte Transkription des Textes. Gute Abbildungen sind zwar für viele Einsatzzwecke auch der sorgfältigsten und penibelsten Textedition vorzuziehen, bedürfen aber gelegentlich doch der Interpretation, die sich möglicherweise erst aufgrund komplexen Hintergrundwissens oder auch durch den Vergleich mit anderen Textzeugnissen ergibt. Auf die Textfassung lassen sich zudem leichter die im digitalen Zeitalter üblichen und gewohnten Recherchetechniken anwenden, die die Navigation der Editions-Website komfortabel zur Verfügung stellt.

Die Schweizer Neuedition wird als Referenz auch die Edition von Karl Lachmann ersetzen. Lachmann sah die Aufgabe des Philologen darin, aus dem verstreuten Handschriftentexten eine Fassung zu rekonstruieren, die den Autorwillen repräsentierte. Diese gab er in einem sogenannten Normalmittelhochdeutsch wieder, das von Zufälligkeiten der Schreibung abstrahieren sollte und deshalb in einem gewissen Sinne als Lautschrift betrachtet werden kann. Die von ihm rekonstruierte Fassung wurde 1833 zusammen mit anderen unter dem Namen Wolfram von Eschenbach überlieferten Texten erstmals veröffentlicht. Sie ist bis heute die Grundlage aller gedruckten Editionen. Die im Buchhandel erwerblichen Fassungen sind allerdings relativ kostspielig. Wer auf eine Übersetzung und Kommentierung verzichten kann und keine Probleme mit der Frakturschrift hat, der findet als kostenlose Alternative bei Google Books mehrere vollständige Ausgaben dieser Edition als eingescannte PDFs vor.  Der Lachmansche Text liegt übrigens auch der an der Hochschule Augsburg im Rahmen des Projektes bibliotheca Augustana veröffentlichen Internetversion zugrunde.

Mehr als einen funktionierenden Internetanschluss und ein Gerät zur Anzeige von Webinhalten brauchen wir also nicht, um uns mit dem Parzival des Wolfram von Eschenbach auf sehr vielfältige Weise auseinanderzusetzen.

Was aber, wenn wir diese Auseinandersetzung gerne offline durchführen würden? Was, wenn wir uns den Text lieber auf einem E-Book-Reader als auf dem Bildschirm des Laptops ansehen würden? Oder, weil wir doch ziemlich konservativ sind, gar gedruckt, auf Papier? Möglicherweise ziehen wir es auch vor Bücher zu hören, anstatt sie zu lesen, auf langen Autofahrten, im Fitnessstudio. Vielleicht sind wir auch durch eine Sehschwäche auf diese Art der Lektüre angewiesen. Es gibt mehr als einen guten Grund, warum die Zugangsmöglichkeiten im Internet nicht als ausreichend betrachtet werden können. Gerade wenn wir die Texte für wissenschaftliche Untersuchungen nutzen wollen, stellen wir schnell fest, dass die Navigation, die uns eine Website zur Verfügung stellt, für unsere Zwecke nicht genügt. Das liegt natürlich daran, dass die Website die sehr extravaganten Anfragen, die wir haben, nicht antizipieren kann. Denn der Wert einer wissenschaftlichen Untersuchung wird unter anderem in der Originalität ihrer Fragestellung bemessen. Je origineller wir sind, desto weniger können uns die bereitgestellten Instrumente weiterhelfen.

Als unser erstes Ziel werden wir deshalb definieren, die im Internet zugänglichen Fassungen auf unsere heimische Festplatte zu laden. Wir nehmen uns vor, sowohl den bisher als Standard gültigen Text der Edition von Lachmann, als auch das Transkript der in der Schweiz erstellte Edition der Handschrift n zu verwenden. Von beiden wollen wir jeweils folgende Fassungen erzeugen:

  • Eine reine Textdatei
  • Eine Textdatei mit Nummerierungen der Bücher, Zeilen etc.
  • Eine HTML-Datei zur Anzeige im Browser oder beispielsweise in einem E-Book-Reader
  • Das Fragment einer XML-Datei, die dem für Editionen wissenschaftlich üblichen TEI-Standard entspricht und mit einem entsprechenden Header versehen, eine solche Edition bilden würde.
  • Schließlich wollen wir auch die kompletten Abbildungen der Handschrift n aus dem Internet laden und sie mit einer einfachen  Navigation für unsere Zwecke versehen.

Der Blick in eine Handschrift ermöglicht den Zugang zu Informationen, die keine (Text-)Edition wirklich komplett erfassen kann. Doch auch die gedruckte oder im Internet publizierte Edition verwendet Signale zur Informationsvermittlung, die möglicherweise bei einem Übertrag in ein anderes Format verloren gehen könnten. Das könnten beispielsweise die Anordnung der Textbestandteile auf der Seite sein und die dazu korrespondierende Positionierung eines Kommentars oder wissenschaftlichen Apparats, oder die Schriftgröße, der Buchstabenabstand und so weiter.

Falls wir aus solchen Gründen Interesse daran haben, die Druckfassung der ursprünglichen Lachmanschen Edition offline zu verwenden, lässt sich diese selbstverständlich als Pdf bei Google Books beziehen. Uns allerdings interessiert in erster Linie der Text dieser Edition. Es wäre sehr umständlich diesen per OCR den im Auftrag von Google eingescannten Vorlagen (in Frakturschrift!) zu entnehmen, zumal wir das Programm zur Buchstabenerkennung zuvor in der mittelhochdeutschen Orthographie (in der Lachmann’schen Normalschreibung) trainieren müssen. Da wir jedoch auf die Fassung der bibliotheca Augustana zugreifen können, bei der die Umsetzung in maschinenlesbare Buchstaben bereits erfolgt ist, können wir uns diesen Aufwand erspraren. Wir verwenden also ausschließlich diese Website und die der Berner Universität für unser kleines Projekt. Für einen ersten Text reduzieren wir unsere Aufgabe noch ein wenig, indem wir uns auf den Prolog des Textes beschränken, die Verse 1.1 – 4.26.

Im Baseler Codex befindet sich dieser Text auf den Seiten 5 und 6, bzw. Blatt 1r und 1v. Um uns die Abbildungen der Seiten unter einem uns genehmen Namen auf die Festplatte zu laden, müssen wir nichts weiter tun, als auf der Website, die uns die Handschrift anzeigt, die entsprechenden Seiten aufzuschlagen und über dem Bild das Kontextmenü mit der rechten Maustaste aufzurufen:

Ebenso leicht lassen sich die beiden von uns gewünschten Textdateien erzeugen. Wir öffnen den Texteditor unserer Wahl, markieren den gewünschten Textausschnitt auf der entsprechenden Website, kopieren ihn von dort und setzen ihn in den Texteditor ein. Dann überlegen wir uns, welche Zeichenkodierung wir verwenden wollen (UTF-8 ist üblich) und speichern die Datei als .txt.

Fertig.

Zu einfach?

Moment! Das waren ja bisher nur die reinen Textdateien. Wir wollten für jede Edition auch eine HTML-Fassung erzeugen und ein TEI-Fragment …

Nein. Keine Sorge! Dieses Tutorial wird sich nicht darauf beschränken, den Einsatz der Zwischenablage zu erläutern. Es soll jedoch, bevor wir ernsthaft beginnen, noch einmal deutlich ins Bewusstsein gerückt werden, dass es nicht immer notwendig ist, zu verhältnismäßig aufwendigen technischen Lösungen zu greifen, wenn das gewünschte Ergebnis durch ein paar Mausklicks ebenfalls erreicht werden kann. In einem früheren Tutorial haben wir eine Methode erprobt, mit der sich eine Tabellenkalkulation dazu einsetzen lässt, um Zeilen eines Textes nach einem bestimmten Muster mit Textbausteinen zu verknüpfen, um sie in validen HTML-Code zu verpacken. Analog lassen sich für ein kleines Textfragment, wie den Prolog des Parzivals, auch die gewünschten TEI-konformen Auszeichnungen ohne jeden Programmieraufwand komfortabel und schnell erzeugen. Wir werden, sobald wir uns mit dem TEI-Format auseinandersetzen, diese Methode für ein erstes Experiment einsetzen.

An dieser Stelle stellen wir jedoch als erstes kleines Zwischenfazit fest, dass es möglich ist, Ausgaben des Parzival im Internet zu finden. Weiterhin ist es möglich, die dort präsentierten Inhalte zu übernehmen und auf der eigenen Festplatte zu speichern. Dabei lässt sich das Format den eigenen Wünschen anpassen. Für nicht zu anspruchsvolle Aufgabenstellungen reichen uns copy & paste, ein Texteditor und als nützliches Hilfsmittel eine Tabellenkalkulation, um das gewünschte Ergebnis zu erzeugen.

Wenn wir uns die beiden per copy-&-paste übertragenen Texte in unseren Texteditoren ansehen, sind wir möglicherweise nicht ganz zufrieden mit den Ergebnissen:

Transkription Baseler Codex:

-
1.01-0 Der Parcival.
1.01 IST zwiuel h(er)zen nahgebur
1.02 daz muͦz der sele werden sur
1.03 gesmehet unde gezieret.
1.04 ist swa sich parrieret.
1.05 vn verzaget mannes muͦt.
1.06 als agelstern varwe tuͦt.
1.07 der mac dennoc* dennoch wesen geil.

Lachmannsche Edition nach der bibliotheca augustana:

Ist zwîvel herzen nâchgebûr,
daz muoz der sêle werden sûr.
gesmæhet unde gezieret
ist, swâ sich parrieret
5
unverzaget mannes muot,
als agelstern varwe tuot.
der mac dennoch wesen geil:

 

Es gibt ein paar Zeilen, die nicht zum eigentlichen Text gehören, Überschriften oder andere Elemente enthalten. Das Formatschema für die Nummerierung, ist nicht einheitlich und müsste je nach unseren Ansprüchen angepasst werden. Die Baseler Edition vermerkt Emendierungen (Korrekturen der Handschrift) in einem Verfahren, dass in unserer reinen Textfassung zu Doppelungen führt. So folgt auf die in der Handschrift gefundene Schreibung dennoc die angesetzte Lesung dennoch. Der Stern * erlaubt es uns dabei, die Handschriftenfassung zu erkennen. Wir können oder müssen uns entscheiden, wie wir in solchen Fällen verfahren wollen, ob wir beide Fassungen, nur die originale Schreibung, oder nur die korrigierte Lesung aufnehmen wollen. Das sind jedoch kleine Probleme, die wir leicht durch ein paar Eingriffe unseren Wünschen entsprechend „händisch“ lösen können.

Der Parzival enthält jedoch beinahe 25000 Verse. In gedruckten Fassungen entspricht das etwa 800 Seiten Papier, wenn jede Seite einen der Lachmannschen Absätze enthält. Es braucht nicht viel Vorstellungskraft, um einzusehen, dass unsere bisherige simple Herangehensweise bald an Grenzen stoßen muss, wenn wir unsere Aufgabenstellung ausweiten.

Auch wenn sie inzwischen auf jede Art von Aufgabenstellung angewendet werden können, wurden Skriptsprachen einmal dazu erfunden, den alltäglichen Arbeitsaufwand zu reduzieren, indem die wiederkehrenden Aufgabenstellungen durch ein paar Zeilen Programmiercode automatisiert werden. Wir werden für unsere Zwecke die Skriptsprache Python einsetzen, die sich durch eine besonders leicht zu lernende, einfache Syntax auszeichnet. Wer noch nie mit Python gearbeitet hat, kann sich die aktuelle Python-IDLE (Die Programmierumgebung) für sein Betriebssystem auf der offiziellen Website  herunterladen. Dieses Tutorial wird keinen Kurs in dieser Programmiersprache darstellen. An diesen besteht auch sicherlich kein Mangel. Der verwendete Programmcode und die dazu gehörigen Erklärungen werden aber so einfach gehalten, dass es auch ohne Vorkenntnisse möglich sein sollte, ihnen zu folgen und die präsentierten Techniken für eigene Zwecke einzusetzen und variieren.

Wir beginnen nachdem wir unsere Vorüberlegungen abgeschlossen und gegebenenfalls die Python-Programmierumgebung heruntergeladen und gestartet haben, mit einem Blick in den Quellcode der Websiten im Internet.

im ersten Teil dieses Tutorials.

 

 

 

 

 

 

 

 

 

 

 

 

Zeilenweise Suchen in HTML

Digitalisierte Texte lassen sich durch Algorithmen durchsuchen, die Daten können geordnet, gruppiert und sortiert werden. Das sind Möglichkeiten, die nicht nur von Verfechtern des distant reading genutzt werden und ohne die keine moderne Forschung in den Geisteswissenschaften mehr möglich ist. Sorgfältig präparierte Editionen in von der wissenschaftlichen Gemeinschaft entwickelten standardisierten Datenformaten erlauben es, auch sehr spezifischen Anfragen die für eine Fachrichtung relevant sind, an die Quellen zu stellen. Allerdings ist es nicht für jede Aufgabenstellung nötig, die Daten in einem dieser teilweise sehr komplexen Datenformate zu erfassen, schließlich beherrscht auch eine einfache Textverarbeitung die Möglichkeit differenzierte Suchen durchzuführen. Etwas schwieriger gestaltet es sich allerdings, einen vergleichenden Überblick über die gefundenen Textstellen zu bekommen, die auch jeweils den relevanten Kontext der Textstelle anzeigt.

Die Markup-Sprache HTML wird in erster Linie mit dem World Wide Web in Verbindung gesetzt und sicherlich findet sie als Sprache zur Gestaltung der Internetseiten ihre wichtigste Anwendung. Das Format stellt eine sehr einfach zu handhabende Möglichkeit dar, Texte strukturiert zu erfassen und diese Struktur dann in einem Browser anschaulich zu präsentieren. Mit sehr einfachen Mitteln ist es sehr schnell möglich, einen Text als HTML zu formatieren. Da die Browser auch Programmiersprachen verstehen, lassen sich dann ohne großen Aufwand nützliche Funktionen zur Recherche in die HTML-Dateien integrieren.

Nachfolgend wird eine Möglichkeit demonstriert, die Suche nach Textbelegen zu vereinfachen.Dabei wird angenommen, dass jede Verszeile in einem Listenelement untergebracht ist. Die Suchanfrage wird, sobald der entsprechende Knopf betätigt wird, auf die Listenelemente angewendet und blendet alle diejenigen Listenelemente aus, die den Suchkriterien nicht entsprechen. Damit sind also nur noch jene sichtbar, die das gesuchte Merkmal aufweisen. Wer ein wenig experimentieren will, versucht sich an regulären Ausdrücken. Mit e[,.]$ beispielsweise lassen sich alle Verszeilen finden die auf einer offenen Silbe auf -e enden.

Bitte hier den Suchtext (das Suchmuster) eingeben:

  1. Ich prüeve in mîme sinne
  2. daz lûterlîchiu minne
  3. der werlte ist worden wilde.
  4. dar umb sô sulen bilde
  5. ritter unde frouwen
  6. an diesem mære schouwen,
  7. wand ez von ganzer liebe seit.
  8. des bringet uns gewisheit
  9. von Strâzburc meister Gotfrit:
  10. swer ûf der wâren minne trit
  11. wil eben setzen sînen fuoz,
  12. daz er benamen hœren muoz
  13. sagen unde singen
  14. von herzeclichen dingen,
  15. diu ê wâren den geschehen
  16. die sich dâ hæten undersehen
  17. mit minneclichen ougen.
  18. diu rede ist âne lougen:
  19. er minnet iemer deste baz
  20. swer von minnen etewaz
  21. hœret singen oder lesen.
  22. dar umbe wil ich flîzec wesen
  23. daz ich diz schœne mære
  24. mit rede alsô bewære
  25. daz man dar ane kiesen müge
  26. ein bilde daz der minne tüge,
  27. diu lûter unde reine
  28. sol sîn vor allem meine.

Um diese Funktionalität in einer eigenen HTML-Seite unterzubringen, bedarf es tatsächlich nur ein paar Zeilen Code, die in deren Header eingetragen werden müssen:

<head>
  <script type = "text/javascript" 
         src = "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
  </script>		
  <script type = "text/javascript">
    $(document).ready(function() {
      // Füge am Anfang des Dokuments einen Absatz
      // und in diesemSuchfeld mit der id "suche" ein.

      $("body").prepend("<p>Suche: <input type='text' id='suche' /></p>");
      
      // Wenn sich der Suchtext ändert ...

      $("#suche").change(function() {
        let Suchtext = $("#suche").val();  // Suchtext übernimmt den Text des Suchfeldes
        let reSuch = new RegExp(Suchtext); // reSuch ist ein regulärer Ausdruck, der darauf basiert.
        $("li").each(function()
        {
            // Alle Listenelemente werden überprüft (test),
            // ob sie das Suchmuster beinhalten

            if(reSuch.test($(this).text()))
            {
                $(this).fadeIn(); // gefunden? Einblenden.
            }
            else
            {
                $(this).fadeOut(); // nicht? Ausblenden.
            }
         });
      });
        
   });
  </script>
</head>

Das kleine jQuery-Skript fügt einen neuen Absatz am Anfang der Seite ein, in dem sich das Suchfeld findet. Auf einen Button, wie er in diesem Blogbeitrag verwendet wird, wird der Einfachheit verzichtet. Sobald sich der Inhalt des Suchfeldes, dem die id „such“ zugeordnet wurde, ändert, wird die Funktion aufgerufen, die zuerst aus der Texteingabe einen regulären Ausdruck erzeugt. Dann wird Listenelement für Listenelement (Vers für Vers) überprüft, ob das Suchmuster darin wiedergefunden und je nachdem die Zeile ein- oder ausgeblendet.
Selbstverständlich ist es möglich, das Skript in eine nachzuladende Datei zu speichern und dann in die HTML-Seite nachzuladen. Diese muss dann nur noch folgende Zeilen im Header ergänzen (Dateiname und Pfad müssen natürlich auf das Skript verweisen und gegebenenfalls angepasst werden, um es zu laden):

<head>
      <title>The jQuery Example</title>
      <script type = "text/javascript" 
         src = "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
      </script>	
      <script type = "text/javascript"
	  	src="RegexSuche.js">
      </script>
   </head>

Das Skript sieht dann so aus:

// Inhalt der Datei:
// RegexSuche.js


$(document).ready(function() {
    $("body").prepend("<p>Suche: <input type='text' id='suche' /></p>");
    $("#suche").change(function() {
       
        let Suchtext = $("#suche").val();
        let reSuch = new RegExp(Suchtext);

        $("li").each(function()
        {
            if(reSuch.test($(this).text()))
            {
                $(this).fadeIn();
            }
            else
            {
                $(this).fadeOut();
            }
        });
    });      
 });

Der Code wurde bewusst einfach gehalten, da nur das Prinzip demonstriert werden sollte. Im realen Einsatz wird beispielsweise mehr Flexibilität erreicht, wenn statt eines Markup-Typs ein Klassennamen zur Identifizierung der zu durchsuchenden Textblöcke zu verwendet wird. Und sicherlich ist „suche“ kein besonders geeigneter Bezeichner, wenn man vorhat, die Routine in eine komplexe Umgebung einzubinden.In dieser einfachen Form lässt der Codeschnipsel sich jedoch per copy und paste schnell übernehmen, wenn innerhalb von Minuten ein paar einfache Fragen an einen Text gestellt und beantwortet werden sollen.

Nach dem ersten mittelhochdeutschen Hörbuch

Diese Reflektionen beziehen sich auf ein Tutorial, in dem demonstriert wurde, wie sich aus einem mittelhochdeutschen Textausschnitt eine Sprachausgabe erzeugen lässt. Der Beginn des Tutorials findet sich hier.

Tastatur und Bildschirm haben sicher noch lange nicht ausgedient, aber immer seltener stellen sie die einzige Schnittstelle zur Computertechnologie dar. Die Fähigkeit einer Maschine, geschriebenen Text in gesprochene Sprache zu verwandeln, ist in Zeiten der persönlichen Assistentinnen ziemlich alltäglich geworden. Es ist insofern nicht verwunderlich, dass sich die Technologien zur Sprachsynthese mittlerweile mit relativ geringem Aufwand nutzen lassen. An der Sprache einer vergangenen Zeit scheitern die synthetischen Vorleser jedoch. Für diejenigen, die auf diese Technologie angewiesen sind und sich alleine mit ihrer Hilfe im Netz der Informationen bewegen, ergeben sich kaum zu überwindende Barrieren, sobald sie auf mittelhochdeutsche Texte treffen.

Wir haben in unserem Experiment demonstriert, dass es mit ein paar Zeilen Programmcode, allerdings durchaus möglich ist, aus einem geschriebenen mittelhochdeutschen Text eine verständliche und nachvollziehbare Sprachausgabe zu generieren. Die folgende Datei erlaubt es, die Ausgangslage und das Ergebnis bei Abschluss unseres Projektes zeilenweise zu vergleichen:

Der Übungstext, den wir für unseren Test verwendeten, war in der auf Lachmann und andere Pioniere der Mediävistik zurückgehenden normierten Orthographie des Mittelhochdeutschen verfasst. Diese kann im Wesentlichen als Lautschrift verstanden werden. Auf der Basis dieser Überlegung wurde für die Wörter des Textes algorithmisch eine phonetische Realisierung ermittelt, die von der Sprachsynthese wiedergegeben werden kann. Die Metrik mehrsilbiger Wörter ließ sich mit einer kleinen Gruppe von Regeln ziemlich zuverlässig ermitteln. Nur wenige Ausnahmen und Übergeneralisierungen mussten in einem Lexikon händisch eingetragen werden. Ebenfalls in einem Lexikon vermerkt wurden einsilbige Funktionswörter, deren Betonung kontextbedingt wechselt. Die Position im Vers ließ sich für eine verbesserte, sinngemäße Lesung der Akzentuierungen hinzuziehen. Ein weiterer Nutzen konnte aus den in die Edition eingetragenen Satzzeichen gezogen werden. Diese Verbesserungen auf rhythmischen Gebiet und die daraus resultierende größere inhaltliche Verständlichkeit der Lesung lässt sich an den im Verlauf des Tutorial entstandenen Audiodateien leicht nachvollziehen. Durch die doppelte Lesung ist dies an der obigen Datei nicht ganz so gut erkennbar.

Das im Tutorial entstandene Programm ist ohne weitere Änderung und Anpassung in der Lage einen sehr viel größeren Textausschnitt in eine Audiodatei einzulesen. Es wäre beispielsweise kein Problem, den Gesamttext, dem der Ausschnitt entnommen war, zu verwenden. Die Qualität der Lesung wird nur relativ geringfügig durch bisher unbekannte Worte und Konstellationen beeinträchtigt. Die eingangs formulierte Einsatzabsicht, die Erstellung eines Hörbuchs aus einem ausgewählten Text für private Zwecke lässt sich somit erreichen. Einige Modifikationen, etwa die Aufteilung der Audiodatei in Hörkapitel oder die bereits im Programm vorgenommene Umwandlung in transportablere MP3-Dateien würden den Komfort zwar steigern, sind aber nicht zwingend nötig.

Texte, die einer anderen Schreibnorm folgen, kann dieses Programm nicht besser lesen, als es die für das Neuhochdeutsche voreingestellten Routinen der Sprachsynthese tun würden. Ebenso wenig kann mit anderen Datenformaten als reinen Textdateien umgegangen werden. Um eine größere Flexibilität zu erreichen wäre es zwingend notwendig, die in das Programm unveränderbar eingeschriebenen Datenstrukturen, insbesonders die lexikalischen Daten, vom Programmcode zu trennen und damit (extern) editierbar zu machen. Das im Tutorial eingeführte aber keinesfalls erschöpfend behandelte Datenformat SSML bietet die benötigten Formalismen für solche Aufgaben. Es war nicht Absicht des Tutorials, eine fertige Lösung für komplexe Aufgaben im großen Maßstab zu erstellen. Viele Möglichkeiten wurden nur angedeutet und ausschnittsweise demonstriert. Selbstverständlich lassen sich sehr viel aufwendigere und treffsicherere Algorithmen zur Ermittlung von Lautstrukturen, Sinneinheiten und Akzentuierungen implementieren und differenziertere Strukturen in SSML kodieren. Mit größerer Ernsthaftigkeit und mehr Professionalität betrieben, als es der Rahmen eines Tutorials gestattet und vorsieht, wäre es möglich, jeden überlieferten und digitalisierten Text der mittelhochdeutschen Zeit wieder zum Klingen zu bringen. In einem gewissen Sinne wäre die Vision der frühen Mediävisten damit tatsächlich umgesetzt. Dieses Tutorial sollte anschaulich und unterhaltsam in die Thematik einführen, erste Kenntnisse der Technik vermitteln und zugleich demonstrieren, wie viel sich bereits mit recht bescheidenen Mitteln erreichen lässt. Es war durchaus Absicht, implizit darauf aufmerksam zu machen, dass angesichts der vorhandenen Möglichkeiten zur Aufhebung der Barrieren, die derzeitig fast ausschließlich den Sehenden vorbehaltene Präsentation der (wissenschaftlichen) Mediävistik im Internet eigentlich nicht akzeptabel ist.

Es wäre jedoch falsch, den Nutzen mit Hilfe der Computerprogramme erzeugter Sprachdateien, ausschließlich darin zu sehen, dass auf diesem Wege die alten Texte breiteren (weniger sehstarken) Schichten zugänglicher wären. Es sind Interpretationen und wenn sie auf dem Stand der wissenschaftlichen Forschung basieren, Editionen, anhand derer sich Theorien ebenso bilden und falsifizieren lassen, wie es Editionen in geschriebener Form erlauben. Die in diesem Tutorial verwendete und vorgestellte Methode erzeugt keine Audiodatei. Sie erstellt eine präzise und bis in phonetische Feinheiten ausgearbeitete Vorgabe, wie ein Text zu lesen ist und übergibt diese an ein Programm, das daraus Klänge erzeugt. Die Vorlage lässt sich speichern, kritisieren, kommentieren, diskutieren, korrigieren – und zur Kontrolle oder auch zu Unterhaltungs- und Informationszwecken lässt sich der dazugehörige Klang anhören.

Mein erstes mittelhochdeutsches Hörbuch – Teil 7

Dies ist der siebte Teil des Tutorials. Der Beginn findet sich hier.

Am Ende – Beinahe!

Ein einfacher Befehl genügt, um Hedas Sprachausgabe in eine Audiodatei umzuleiten. Dazu müssen wir nur irgendwo vor dem Speak-Befehl eine einzige Zeile in Main in Program.cs einfügen:

synth.SetOutputToWaveFile("D:/Lanzelet 1 - 49.wav");

Das war es auch schon!  Wir können natürlich auch ein anderes Verzeichnis unserer Wahl bestimmen oder der Datei einen anderen Namen geben.

Auf jeden Fall haben wir gerade erreicht, was wir uns zu Beginn unseres Projektes vorgenommen haben: Sobald wir die Taste F5 betätigen (und einen Moment warten), liegt im von uns gewählten Verzeichnis, unter dem von uns bestimmten Namen, eine Audiodatei bereit, die einen mittelhochdeutschen Text korrekt, wenn auch etwas synthetisch-blechern vorliest. Das Format der Datei entspricht zwar nicht dem für Hörbücher üblichen, wird aber von nahezu jedem Medienabspielgerät verstanden.

Wer sich damit nicht zufrieden geben will (oder kann): Es gibt jede Menge Möglichkeiten, um eine Wave-Datei komfortabel in eine im MP3-Format zu übertragen. Für die MP3-Dateien dieses Tutorials wurde beispielsweise das handliche kleine Programm fre:ac verwendet. Andere Tools erfüllen, bei abweichendem Funktionsumfang, denselben Zweck genauso gut.

Und ja! Es wäre möglich und sogar relativ einfach, eine Lösung in C# zu programmieren, die es uns erlaubt, die MP3-Datei direkt aus unserem Programm zu erzeugen. Nicht zuletzt aufgrund der leichten Verfügbarkeit der oben erwähnten Tools erscheint es jedoch überflüssig, eine entsprechende Lösung in diesem Tutorial zu behandeln, das sein selbstgesetztes Ziel jetzt erreicht hat.

Wie angekündigt, wollen wir jetzt aber noch ein wenig „aufräumen“. Wir sind nicht immer auf direktem Weg zum Ziel gekommen und können, mit mehr Überblick, vielleicht manche Kurve begradigen. Um die Metaphern wahllos zu mischen: Vielleicht gibt es noch das eine oder andere zurückgebliebene Gerüst zu beseitigen, das wir zwischendurch brauchten, um unsere Gewölbe zu errichten, das nun fest und sicher steht.  Wir kümmern uns um solche Aufräumarbeiten auch mit dem Hintergedanken, damit unser Programm für zukünftige Einsätze vorzubereiten. Denn wollen wir uns nach all der Arbeit wirklich damit zufrieden geben, dass wir nur diesen einen kleinen Textausschnitt in eine Lesung von anderthalb Minuten übertragen haben? Warum sollten wir Hedda nicht irgendwelche weiteren Aufgaben geben … den ganzen Text des Lanzelet zu lesen, vielleicht?

Wir wollen uns gar nicht darauf festlegen, wie es weitergehen soll. Im Gegenteil: Wir wollen uns Optionen schaffen und unser Programm für die Wiederverwendbarkeit öffnen. Bisher haben wir beispielsweise mit einer einzigen Textdatei gearbeitet. Es war die naheliegende und einfachste Lösung,  direkt in unserem Programmcode den Ort anzugeben, an dem diese Datei auf unserer Festplatte zu finden ist. Der hat sich ja während unserer Arbeit nicht geändert. Mehr Flexibilität geben wir dem Benutzer, wenn er zukünftig (das können auch wir sein!) die Möglichkeit bekommt, einen beliebigen Dateinamen für eine Textdatei anzugeben, die Hedda ihm vorlesen soll. Das wird er auf zwei Weisen tun können:

Einmal direkt beim Aufruf: Wenn unser Programm von der Konsole aus, zum Beispiel in der Powershell, geöffnet wird, dann kann der Benutzer durch den ersten Parameter, den er dem Programm übergibt, bestimmen, welche Textdatei gelesen werden soll. Anwender von Windows sind mit dieser Art des Dateiaufrufs meist nicht sehr vertraut. Wer an dieser Stelle über das Wort Parameter stolpert, wird vermutlich sowieso die zweite Variante vorziehen.

Wenn das Programm (Windows-typisch) ohne Angabe eines Parameters aufgerufen wird, fragt es in einer penetranten Endlosschleife nach dem Namen der Textdatei, die gelesen werden soll. Diese Endlosschleife wird nur beendet, wenn eine existierende Textdatei angegeben wird.

Es sei denn … der Benutzer betätigt die Enter-Taste und sendet eine leere Eingabe. Das ist die Hintertür, die wir uns selber lassen, um weiterhin ohne lästigen Aufwand mit „unserer“ Textdatei herumexperimentieren zu können. Die wird nämlich in diesem Fall ganz wie gewohnt geöffnet.

Ähnlich einfach versuchen wir die Steuerung des Ausgabe in eine Wave-Datei zu gestalten. Auch hier gibt es wieder zwei, mit Hintertür: zweieinhalb, Optionen:

  1. Die Übergabe des Dateinamens als zweiten Parameter.
  2. Das Betätigen der Taste j, wenn das Programm danach fragt, ob eine Dateiausgabe erwünscht wird. In diesem Fall wird der Name der Datei von dem der Textdatei abgeleitet: Aus Lanzelet.txt wird Lanzelet.wav im selben Verzeichnis. Und schließlich:
  3. Die Hintertür. Wird an dieser Stelle irgendeine andere Taste als j betätigt, erfolgt die Ausgabe wie bisher über den Lautsprecher des Computers.

Ein Link zur Ansicht d er kompletten Datei Program.cs, an der wir in diesem Tutorial kaum noch Änderungen vornehmen werden, findet sich, wie auch einer zu Hedda.cs im Anschluss an dieses Tutorial. Wer Program.cs jetzt bereits aufmerksam liest, wird allerdings bemerken, dass Main noch ein paar weitere neue Zeilen mit Befehlen an Hedda enthält, auf die bisher nicht eingegangen wurde. Leicht geändert hat sich auch die Art und Weise, wie wir mit unerkannten Zeichen umgehen.

In unserem Textausschnitt gibt es keine blinden Stellen mehr, aber wenn wir weitere Texte lesen wollen, kann uns diese Funktionalität weiter nützlich sein. Hedda kann es noch nicht wissen, aber wir werden sie gleich mit einer weiteren neuen und für zukünftige Vorhaben nützlichen Fähigkeit versehen, die wir jetzt bereits abrufen: Sie kann uns auf das Kommando SchreibeLexion eine Liste ausgeben, die die mittelhochdeutschen Wörter und die von uns zugewiesenen UPS-Kodierungen enthält, so wie sie sich in Heddas Lexikon befinden. Wir müssen SchreibeLexikon nur noch implementieren. Als Neuheit enthält Main inzwischen auch die beiden korrespondieren Befehle einen Satz zu starten und zu beenden, die wir an den PromptBuilder senden:

pb.StartSentence();
pb.StartSentence();

Auch diese beziehen sich auf eine Funktionalität von Hedda, die sie erst noch erlernen muss.

Doch beginnen wir mit dem Lexikon, das sie uns anzeigen soll. Solange sie diese Fähigkeit nicht beherrscht (die Methode nicht in der Datei Hedda.cs zu finden ist), lässt sich unser Programm nämlich nicht kompilieren und starten.

Es ist eine ganz simple Methode, die uns die Einträge des Lexikons alphabetisch im Konsolenfenster ausgibt:

        public void SchreibeLexikon()
        {
            Console.WriteLine("*** Lexikon ***");
            foreach (KeyValuePair<string, string> paar in Lexikon.OrderBy(paar => paar.Key)) Console.WriteLine(paar.Key + "=> [" + paar.Value + "]");
        }

Brauchen wir diese Methode denn? Bisher verstauben in unserem Lexikon ein paar Einträge, die wir vor langer Zeit (Teil 3 des Tutorials) dort abgelegt haben. Damals haben wir das Lexikon genutzt, um die Lautschrift eines Wortes festzulegen, das sich nicht auf neuhochdeutsche Art aussprechen ließ. Doch inzwischen erzeugen wir diese Lautschrift automatisiert für fast alle Wörter im Text – ausgenommen die verstaubten Relikte unserer frühen Experimente. Wozu also weiterhin das Lexikon? Ist das nicht gerade eines dieser verwaisten und überflüssig gewordenen Gerüste früherer Bauarbeiten?

Die entscheidende Antwort ist, dass es immer schneller ist, einen Eintrag nachzuschlagen, als die Lautschrift, inklusive der Betonung der Nebensilben etc. programmgesteuert zu ermitteln. Bei 50 Verszeilen fällt es vielleicht nicht so sehr ins Gewicht, wie viel Zeit Hedda für jedes Wort aufwendet, das sie liest, aber wir wollen uns ja viieele Möglichkeiten offen lassen …

Es gibt sie aber auch durchaus, jene Wörter, deren Lautung sich nicht mit einer Programmroutine ermitteln lässt. Genewîs in unserem Textausschnitt gehört dazu. Dieses Ortsname gehört einer fremden Sprache an, deren Lautmuster wenig mit dem Mittelhochdeutschen zu tun hat. Ehrlich gesagt: Für das konkrete Beispiel Genewîs könnten wir das Betonungsmuster sehr zutreffend aus dem Versmaß erschließen. Doch ist der sonst so gleichmäßige Wechsel der Silbenbetonung ein ausgesprochen unzuverlässiger Indikator, sobald es um die  Bestimmung von Fremdsprachenmaterial geht, das der Dichter auf Biegen und Brechen in seinem Text unterbringen muss. Auf die zu einer Erzählung gehörigen Personen- und Ortsnamen lässt sich nun mal schwer verzichten!

Wir wollen das Argument des reduzierten Arbeitsaufwands besonders ernst nehmen. Wörter, deren Lautung Hedda bereits einmal ermittelt hat, braucht sie nicht bei einem zweiten Auftreten im Text noch einmal bearbeiten, wenn sie sie stets brav im Lexikon ablegt. Wir fügen deshalb unmittelbar nach der Ermittlung einer Lautung einen entsprechenden Auftrag an Hedda in LeseZeile ein:

 string Lautung = FindeAussprache(Wort);
                    if (Lautung != "")
                    {
                        Lexikon[Wort] = Lautung;

SchreibeLexikon listet uns nun alle Wörter des Textes auf. So können wir unsere Höreindrücke beziehungsweise Heddas Lesekünste überprüfen: Hat sie wirklich gelesen, was wir zu hören geglaubt haben? Welche Lautzeichen hat sie den Buchstaben zugewiesen?

Wir berauben uns durch diese Maßnahme jetzt allerdings einer Fähigkeit, die wir Hedda gerade erst beigebracht haben: Beim zweiten Lesen eines Kurzwortes findet sie dieses im Lexikon und übernimmt die dort angesetzte Lautung. Das Wort wird also durchgehend ohne Akzentuierung vorgelesen werden, denn die Aufnahme ins Lexikon findet statt, bevor überprüft wird ob es im Kontext des Verse vielleicht mit einer stärkeren Betonung zu lesen ist. Wollen wir, dass Hedda die Position im alternierenden Vers auch dann berücksichtigt, wenn sie ins Lexikon blickt, müssen wir diese Fähigkeit auch beim Nachschlagen im Lexikon zur Verfügung stellen:So

if (Lexikon.Keys.Contains(Wort))
                {
                    string Sonderbetonung = "";
                    if (Kurzwörter.Contains(Wort))
                   { 
                        if ((SilbenImVers-Silbe) % 2 == 1 && Klingend(Zeile)) Sonderbetonung = "S1 " ;
                        if ((SilbenImVers-Silbe) % 2 == 0 && !Klingend(Zeile)) Sonderbetonung = "S1 "; 
                       
                    }
                    //Sonderfall, der im Lexikon gefunden wurde:
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" +Sonderbetonung + Lexikon[Wort] + @""">"// Die Lautschriftvariante
                                        + Wort + "</Phoneme>");//  gefolgt von der Graphie  
                }

Starten wir einen Testlauf und sehen uns das von ihr ausgegebene Lexikon an,  werden wir unter Umständen bemerken, dass unsere Nebensilbenerkennung in einem bestimmten Fall nicht funktioniert: Sofern wir die Option umgesetzt haben, die Vorkommen eines r nach einem Vokal auf typisch-hochdeutsche Art auszugeben, werden die Nachsilben -er,-ern usw. nicht erkannt da sie ja das UPS-Zeichen EH an der erwarteten Stelle nicht enthalten. Diesen kleinen Fehler zu hören, ist gar nicht so einfach, allenfalls die fehlende Silbentrennung ist bei sehr aufmerksamen Hinhören zu bemerken. Wir erinnern uns noch mit Schrecken an den Tatzelwurm und werden nicht versuchen, auch diese Lautungsvariante in seinen verknoteten Leib einzuarbeiten. Doch so sehr lassen wir von uns dann auch wieder nicht einschüchtern, dass wir kampflos die Waffen streichen! Jetzt, da wir von dem kleinen Fehler wissen, werden wir in für-der nicht mehr ü-berhören können.

Vom bösen Tatzelwurm erstellen wir eine Kopie, die wir etwas umarbeiten (das ist nicht ganz so kompliziert) und lassen dann Hedda in zwei Anläufen nach einem potentiellen Suffix suchen. Unser Lexikon sorgt dafür, dass dieser zweifache Aufwand nur einmal für jedes im Text erscheinende und sich möglicherweise wiederholende Wort notwendig sein wird. Den Klon fügen wir unmittelbar nach dem Einsatz des ersten Tatzelwurms in die FindeAussprache ein:

 // Wir kümmern uns jetzt auch  um Wörter auf -er, bei denen das Ende EHX ist:
               
                reEnde = new Regex(@"(?<Anfang>.+[^\.] )(?<Ei>E lng \+ IH )?(?<Links>(?(Ei)|(P \+ F |T \+ S |SH |[BCDFGHKLMNRSTVZ] )))EHX (?<Rechts>[NTS] )?$");
                Suffix = reEnde.Match(Aussprache);
                if (Suffix.Success)
                {
 
                    // Wenn unsere Suche Erfolg hatte, setzen wir das Ergebnis zusammen:

                      Aussprache = Suffix.Groups["Anfang"] +""+ Suffix.Groups["Ei"]+ ". " + Suffix.Groups["Links"] + "EHX " + Suffix.Groups["Rechts"] + Suffix.Groups["Satzzeichen"];
             
                }

Unsere jetzt sehr viel intensivere Nutzung des Lexikons stößt bisher an eine wahrnehmbare Grenze: Folgt auf ein Wort ein Satzzeichen, wird das Wort nicht im Lexikon gefunden. Das liegt an unserer Methode die Wortgrenzen zu ermitteln: Ein Wort ist in unserer Definition weiterhin der Text zwischen zwei Leerzeichen. Vollkommen logisch, wenn auch sprachlich nicht korrekt, ist es damit, dass Satzzeichen zum Wort gehört, dem sie folgen. In unserem Textausschnitt ist von dieser Regelung beinahe jedes fünfte Wort betroffen. Denn jeder Vers umfasst ungefähr fünf Worte umfasst und beinahe jede Verszeile endet mit einem Satzzeichen (Wer es genau wissen will: Pro Vers sind es durchschnittlich 5,35, pro Satz 7,08 Wörter). Das sind zu viele Wörter, deren Lautung wir wieder und wieder neu ermitteln werden und zu allem Überfluss auch jedes Mal neu im Lexikon speichern!

Die Satzzeichen wollen wir selbstverständlich behalten, die Heddas Lesung abwechslungsreicher machen, aber eben trotzdem das Lexikon für jedes Wort des Verses nutzen können. Und wir möchten unseren Programmcode nicht zu sehr abändern. Deshalb werden wir in zwei Schritten vorgehen: Vor dem Blick ins Lexikon, der uns darüber belehrt, ob wir das Wort darin finden, oder es neu interpretieren müssen, entfernen wir die Satzzeichen. Nachdem wir auf eine der beiden möglichen Arten die Lautung des Wortes ermittelt und gegebenenfalls einen neuen Lexikoneintrag erzeugt haben, setzen wir die Satzzeichen hinter dem Wort ein. Den Programmcode zwischen diesen Schritten werden wir wir nicht ändern. Mit Wort bezeichnen wir jetzt jedoch nur noch den Teil der Zeichenketten zwischen zwei Leerzeichen, der einem Satzzeichen vorausgeht. Für Zeichenketten, die wir durch das Aufspalten der Zeile bei jedem Leerzeichen erzeugen, brauchen wir dann eine neue Bezeichnung. Deshalb ändern wir den Beginn von FindeZeile folgendermaßen ab:


            foreach (string w in Zeile.ToLower().Split(' '))
            {
                
                string Satzzeichen = ".,?!;:".Contains(w.Last()) ? w.Last().ToString():null;
                string Wort = Satzzeichen==null ? w:w.Substring(0, w.Length - 1);

Der Text zwischen zwei Leerzeichen (inklusive eines Satzzeichens) wird von nun an mit w bezeichnet. Diese Bezeichnung ist für genau 2 Zeilen Programmcode von Bedeutung und muss deshalb weder lang noch einprägsam sein. Wort bezeichnet die Buchstaben in w bis zu einem Satzzeichen. Wenn gar kein Satzzeichen am Ende von w steht, übernimmt Wort den gesamten Inhalt von w. In beiden Fällen können wir von nun an Wort genau wie bisher gebrauchen. In Satzzeichen speichern wir das Satzzeichen für den Augenblick, an dem es wieder „gebraucht“ wird (in die Lesung eingeht):

if (Satzzeichen != null)
                {
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" + LexionDerBuchstabenErsetzung[Satzzeichen] + @""">"// Die Lautschriftvariante
                                             + Satzzeichen + "</phoneme>");//  gefolgt von der Graphie
                    if (".?!".Contains(Satzzeichen))
                    { 
                       /* pb.EndSentence();
                        pb.AppendBreak(PromptBreak.ExtraSmall);
                        pb.StartSentence();*/
                    }
                   
                }

Wir setzen nach der Ermittlung der Lautung für das Wort, wie bisher. ein Satzzeichen ein, das Einfluss auf die Tonhöhe des Wortes hat, das Hedda spricht. Wir geben zudem aber dem PromptBuilder wenn es sich um ein Satzendezeichen handelt, jetzt auch zu verstehen, dass damit der begonnene Satz endet und zugleich ein neuer beginnt. Dazwischen fügen wir, eine weitere, sehr kleine, Pause ein. Diese wird häufig, aber nicht immer auf eine bereits durch das Versende gegebene Pause folgen und diese geringfügig länger erscheinen lassen.

Ein Vers, der mit einem Komma oder gar keinem Satzzeichen abschließt, geht von nun an, relativ gesehen, etwas übergangsloser in den nächsten Vers über. Anders formuliert: Nach Satzenden und vor einem Neuansatz gibt es eine etwas deutlichere Pause.

Zudem können wir Heddas Voreinstellungen für das Konzept Satz nutzen. Sie weiß jetzt immer, wo die Grenzen eines Satzes anzusetzen sind, welche Elemente zu ihm gehören, mit welchem Wort er terminiert und mit welchem er beginnt. Damit kann sie die für das Neuhochdeutsche vorgegeben Satzkurven nutzen und ihre Lesung noch ein wenig ausdrucksvoller gestalten. Jetzt erklärt sich hoffentlich auch die Verwendung von StartSentence/EndSentence in der Methode Main von Program.cs: So lösen wird das Ei/Henne-Problem, das wir gerade geschaffen haben.: Heda beginnt nach jedem Satzende einen Satz, der irgendwann geschlossen werden muss, sie schließt zuvor einen anderen Satz, der irgendwann begonnenen wurde. Es muss einen ersten Satz und einen letzten geben! (Keine gewollte philosophischen Implikationen!)

Auch für uns gibt es einen letzten Satz. Aber bevor wir zu dem kommen, wollen wir uns eine weitere Aufräumarbeit ansehen, die wir unternehmen können:

Wir haben an einigen Stellen reguläre Ausdrücke eingesetzt. An den Tatzelwurm zur Ermittlung der Suffixe erinnern wir uns noch. Viel leichter war es, den entsprechenden regulären Ausdruck zur Ermittlung der Präfixe zu erstellen. Das haben wir (größtenteils) einer Programmroutine überlassen. Die wird dann allerdings jedes mal zum Einsatz kommen, wenn wir ein Wort auf ein potentielles Präfix prüfen, also bei jedem mehrsilbigen Wort. Wir könnten sie jedoch nur einmal ausführen, wenn wir den regulären Ausdruck dabei einmal erzeugen und dann global in der Klasse Hedda.cs speichern. Bei weiteren Verwendungen steht er uns dann zur Verfügung. Der geeignete Ort, an dem wir das Suchmuster aufbauen, ist natürlich der Konstruktor, die Methode Hedda.Hedda.

Wenn wir schon dabei sind, können wir auch alle anderen von uns benutzten regulären Ausdrücke global für die beliebig häufige Wiederverwendung abspeichern. Keiner dieses Ausdrücke wird sich während eines Programmaufrufes ändern, kein einziges Mal, wenn wir ihn benutzen, müssen wir aktuelle Änderungen vornehmen. Der Effekt dieser Maßnahme auf die Effizienz unseres Programmes ist allerdings relativ gering. Es bleibt Ansichtssache, ob die Lesbarkeit unseres Programms davon beeinträchtigt wird, oder gar profitiert. Einerseits wird der Text in den Programmroutinen kürzer und es wird so leichter, deren Ablauf nachzuvollziehen. Es fehlt aber die Möglichkeit, dabei den regulären Ausdruck im Auge zu haben, um mental nachzuvollziehen, wie der Tatzelwurm entknotet wird. Auf der anderen Seite ist es nun möglich, alle regulären Ausdrücke an einer Stelle im Programmcode zu sammeln, was es erleichtert, sie gezielt zu bearbeiten.

Kurz: Es ist eine Frage der persönlichen Vorliebe. Aus diesem Grund wird es am Ende dieses Teils des Tutorials zwei Fassungen von Hedda.cs geben, um einen Vergleich zu erlauben und eine Auswahl zu bieten. Am Ende dieses Teils des Tutorials? Das geht kürzer: Wir sind am Ende. Des Tutorials.

Unser erstes mittelhochdeutsche Wörterbuch befindet sich auf unserer Festplatte. Wir haben erreicht, was wir erreichen wollten und jetzt unser kleines Programm auch für zukünftige Aufgaben gerüstet. (Letzter Satz) Dieses Tutorial ist damit beendet.

Wer trotzdem noch etwas weiterlesen will, wird sich vor allem den Programmcode ansehen wollen:

Der komplette Inhalt von Program.cs
Der komplette Inhalt von Hedda.cs

Der komplette Inhalt von Hedda.cs, die regulären Ausdrücke sind an einer Stelle gesammelt.
Außerdem gibt es noch eine Nachbetrachtung.

In dieser wollen wir versuchen, noch einmal die Schritte nachzuvollziehen, die wir gegangen sind, um zu einer Beurteilung über den Sinn und Zweck unserer Unternehmung zu gelangen und in Ansätzen darüber nachzudenken, wie man Lehren aus unserem Experiment ziehen oder es auf die große., komplizierte Wirklichkeit übertragen kann. Für uns hat sich der Weg (hoffentlich!) gelohnt und wir haben Spaß an der Bastelei gehabt. Wer es anders sieht, hat bemerkenswert lange durchgehalten und hat sich damit meine Bewunderung und mein Beileid verdient. Ich entschuldige mich!