Den Parzifal, den grabben wir! – Teil 1

Der Blick unter die Haube

Bevor wir die Inhalte aus dem Netz auf unseren Rechner ziehen können, müssen wir uns zunächst damit beschäftigen, wie die Websiten aufgebaut sind, die diese Inhalte zur Verfügung stellen. Es ist ganz einfach in modernen Browsern, sich den Quellcode einer Website anzeigen zu lassen, auf der man sich gerade befindet. Inzwischen ist es nämlich Standard, dass überall die Taste F12 ein zweites Fenster zu dessen Anzeige öffnet. Im zu Windows 10 gehörigen Browser Edge teilt sich das Browserfenster in zwei übereinander geordnete Teile, so dass wir unterhalb der Parzival-Ausgabe der bibliotheca augustana folgenden Code angezeigt bekommen:

Betrachten wir uns diesen Text etwas genauer!

Die Zeile 202  lautet beispielsweise:

<h3 style="text-indent:30px;">daz schuof iedoch ein wîse man,</h3>

So schwer ist es nicht, den mittelhochdeutschen Text in dieser Zeile auszumachen, auch wenn uns möglicherweise die Buchstabenfolge w&icirc;se ein wenig verwirrt. Bei dieser Verszeile wird der Text in <h3>-tags geklammert und offensichtlich wird der Absatz mit einem Einzug von 30 Pixel (text-indent:30px) vom rechten Rand positioniert. Wenn wir im oberen Fenster ein wenig hin und her scrollen, finden wir schnell die vom Browser generierten Ansicht des HTML-Codes dieser Zeile (V. 5.11) und können versuchen, durch den Vergleich beider Fensterinhalte zu einem besseren Verständnis des Codes zu gelangen. So finden wir auf diese Weise unter anderem heraus, dass die Buchstabenfolge &icir; dazu dient, die Graphie |î| wiederzugeben, die das lange mittelhochdeutsche i beschreibt.

Wir werden uns gleich eingehender mit dem Quelltext beschäftigen, aber zuvor wollen wir uns von diesem eine lokale Kopie auf unserem eigenen Rechner beschaffen und setzen dazu erstmals die Programmiersprache Python ein. Unser kleines Skript enthält nur 6 Zeilen Programmiercode:

import urllib.request
import html

FileName = "https://www.hs-augsburg.de/~harsch/germanica/Chronologie/13Jh/Wolfram/wol_pa01.html"        
fInput= urllib.request.urlopen(FileName)

for l in fInput:
    print(html.unescape(l.decode('utf-8')))

Von einem so kurzen Programm können wir kaum etwas Spektakuläreres erwarten, als das, was es tatsächlich tut: Es übernimmt aus der Variablen Filenname eine URL und gibt uns den Quelltext der durch die URL angegebenen Website in das Ausgabefenster der Python-Entwicklungsumgebung aus. Den Inhalt von Filename, die URL, haben wir direkt per copy&paste aus der Kopfzeile unseres Browsers übernommen und deshalb wundert es uns auch nicht, dass im Konsolenfenster erwartungsgemäß derselbe Text durchscrollt, den wir auch beim Betätigen der Taste F12 über der Website in unserem Browserfenster angezeigt bekommen haben. Oder doch beinahe derselbe Text … Doch darauf werden wir gleich eingehen. Zuvor ergänzen wir den Code noch um drei weitere Textzeilen:

import urllib.request
import html

FileName = "https://www.hs-augsburg.de/~harsch/germanica/Chronologie/13Jh/Wolfram/wol_pa01.html"        
fInput= urllib.request.urlopen(FileName)
fOutput = open("D:/ParzivalLachmann.html",mode='w',encoding='UTF-8')

for l in fInput:
    print(html.unescape(l.decode('utf-8')))
    fOutput.write(html.unescape(l.decode('utf-8')))
fOutput.close()

Jetzt wird uns der von der Website heruntergeladene Quelltext nicht nur im Konsolenfenster der Entwicklungsumgebung angezeigt, sondern auch, Zeile für Zeile in eine Textdatei geschrieben. Da wir dieser Textdatei die Dateiendung .html gegeben haben, lässt sich diese Datei jetzt in einem Texteditor öffnen und betrachten und wenn wir es wollen auch auf Papier ausdrucken. Wir können uns die Datei aber auch in unserem Browser anzeigen lassen, wenn wir sie auf die für unser Betriebssystem übliche Art „öffnen“. Dann wird uns wieder im Beispielbrowser Edge von Windows 10 ungefähr diese Ansicht präsentiert:

Unsere Kopie ähnelt dem Original, es gibt aber deutliche optische Unterschiede. Diese lassen sich bei einem Blick in den Quelltext schnell erklären. So finden wir an dessen Beginn folgende Zeilen:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>bibliotheca Augustana</title>
    <link rel="StyleSheet" type="text/css" href="../../../../css/f_germanica.css" />
    <link rel="StyleSheet" type="text/css" href="../../../../css/f_canus.css" />
    <link rel="StyleSheet" type="text/css" href="../../../../css/f_albus.css" />
    <link rel="StyleSheet" type="text/css" href="../../../../css/f_viridis.css" />
    <link rel="StyleSheet" type="text/css" href="../../../../css/f_ruberi.css" />
<script type="text/javascript" src="/145306DD-13EB-B04E-A47E-4375CD4BFE49/main.js" charset="UTF-8"></script></head>

In den <link>-tags werden Stylesheets angegeben, die die Wiedergabe der HTML-Elemente formatieren, insbesondere finden sich dort Angaben zur Schriftgröße und den verwendeten Farben. Wir können uns diese Stylesheets als Teil des Quellcodes von unserem Browser anzeigen lassen. In Firefox finden wir sie im Style-Editor:

In Microsofts Browser Edge benutzen wir die Navigationsansicht, um die Stylesheets zu finden und zu öffnen:

Die Navigationsansicht zeigt uns auch, wo der Browser diese Stylesheets findet: Auf demselben Server, wie die HTML-Datei. Er kann das genaue Verzeichnis dort relativ zum Speicherort dieser Datei identifizieren. Zwei aufeinanderfolgende Punke (..) bedeuten, das ein übergeordnetes „Eltern“verzeichnis gemeint ist. Von der HTML-Datei ausgehend, muss also zuerst das Urururelternverzeichnis (../../../../) gefunden werden, von diesem dann das „Kind“verzeichniss /css, in dem die Stylesheets gespeichert sind. Unsere Kopie allerdings ist auf unserem Privatcomputer gespeichert und damit funktioniert diese Art der Pfadangabe für sie nicht. Da uns rein optische Phänomene derzeit nicht interessieren, verzichten wir darauf, die Pfadangaben so anzupassen, dass unsere Datei auch äußerlich dem Vorbild entspricht.

Einen weiteren, tatsächlichen, Unterschied der beiden Dateien können wir hingegen nicht optisch in der Wiedergabe der Dateien im Browser erkennen. Suchen wir jedoch im Quelltext unserer Kopie nach dem Vers 5.11, dann werden wir dort nicht die ominöse Buchstabenfolge &icirc; finden, sondern direkt die entsprechende Graphie î. Für diese kleine Änderung hat in unserem Programm der Befehl html.unescape() gesorgt. Dieser Befehl setzt HTML-Kodierungen zur Wiedergabe von Sonderzeichen in entsprechende Buchstaben um.

Während es früher schwierig war, zu garantieren, dass Zeichen, die nicht im lateinischen Alphabet enthalten sind (genauer: im ASCII-Zeichensatz) überall richtig angezeigt werden, gehört dieses Problem inzwischen in vielen Bereichen weitgehend der Vergangenheit an. Der Blick in unser Browserfenster lehrt uns jedenfalls, dass moderne Browser keine Probleme mehr damit haben, die meisten Buchstaben korrekt wiederzugeben, ohne dass sie auf besondere Weise kodiert werden müssen.

Die Computersprache Python, besteht in ihrem Kern nur aus wenigen Vokabeln. Eine Vielzahl von Bibliotheken erweitern diesen Kern durch viele nützliche Befehle und Techniken. So praktische Helferlein wie das von uns eingesetzte Kommando unescape sind in Bibliotheken enthalten, die zum „Standardlieferumfang“ von Python gehören und können jederzeit durch Import aufgerufen werden, um die darin enthaltenen Techniken für unsere Zwecke einzusetzen. Den Befehl urlopen haben wir, wie leicht erkenntlich, der Bibliothek urlib.request entnommen. Wir beschränken uns in diesem Tutorial vorerst auf solche Bibliotheken die zum Python-Standard gehören und damit gut dokumentiert und leicht zugänglich sind. Für speziellere Aufgaben lohnt es sich, das Internet und insbesondere die Seiten der python.org nach maßgeschneiderten und optimierten Lösungen zu durchsuchen, die dabei helfen, das Rad kein zweites oder drittes Mal erfinden zu müssen. Wir werden später eine sehr populäre, nicht im Standard enthaltene (aber quasi zum Standard gewordene), Bibliothek verwenden, nachdem wir unser Ziel etwas weniger elegant mit „Bord“mitteln erreicht haben.

Jetzt aber versuchen wir zuerst einmal, unsere bisherige Methode auf die Website der Schweizer Parzival-Edition anwenden. Wenn wir allerdings diesmal die URL http://www.parzival.unibe.ch/cod857/Daten/NL_Transkripte/d_i_frm.html aus der Kopfzeile des Browsers übernehmen, werden wir nicht das erwartete Ergebnis auf unseren Rechner laden, bzw. im Konsolenfenster der Python-Umgebung angezeigt bekommen. Ausgegeben wird uns vielmehr ein Layoutgerüst, das festlegt, in welchem <iframe> die Bilddatei und in welchem die Transkription angezeigt werden soll.

Wir wissen ja, dass links der Transkriptionstext und rechts die Abbildung der Handschrift angezeigt wird. Es ist also keine Hexenkunst, herauszufinden, dass die von uns gesuchte Datei „d_i.html“ heißt und sich im selben Verzeichnis des Servers befindet, in dem das Layoutgerüst untergebracht ist:

<html>
<head>
<title>Transkription Parzival, Verse 1.01-58.26 (Buch I)</title>

<frameset COLS="30%, 70%">

<frame name="links" src="d_i.html" scrolling="yes">
<frame name="rechts" src="" scrolling="yes">
</frameset><noframes></noframes>

<script type="text/javascript" src="/145306DD-13EB-B04E-A47E-4375CD4BFE49/main.js" charset="UTF-8"></script></head>

<body>

</body>
</html>

Wenn wir die URL also entsprechend anpassen (und den Namen der Datei, in die wir unsere Kopie speichern), wird uns die von uns gewünschte HTML-Datei von diesem Skript auf unseren Rechner geladen:

import urllib.request
import html

FileName = "http://www.parzival.unibe.ch/cod857/Daten/NL_Transkripte/d_i.html"        
fInput= urllib.request.urlopen(FileName)
fOutput = open("D:/ParzivalBasel.html",mode='w',encoding='UTF-8')

for l in fInput:
    print(html.unescape(l.decode('utf-8')))
    fOutput.write(html.unescape(l.decode('utf-8')))
fOutput.close()

Diese Datei, die wir gerade erzeugt haben, sehen wir uns jetzt ein wenig genauer an (Im Folgenden wird nur ein verkürzter Ausschnitt zu Beginn der Datei angezeigt):

<html>

<head>
	<title>Transkription Parzival</title>
        <meta charset="UTF-8"/>
           ...

    	<link rel="stylesheet" type="text/css" href="NL_Transkript.css">
           ...

<head>
<h2>Buch I</h2>

<div class="schreiber">- Schreiber I -</div>
<div class="vers"><span class="versnr">1.01-0</span> <span class="marg">Der Parcival.</span></div>
<div class="vers"><span class="versnr">1.01</span> <span class ="si1"></span>ST zwiuel h(er)zen nahgebur</div>
<div class="vers"&amp;gt;&amp;lt;span class="versnr"&amp;gt;1.02&amp;lt;/span&amp;gt; daz muͦz der sele werden sur&amp;lt;/div&amp;gt;

Auch diesmal kommt ein Stylesheet zum Einsatz, das das Aussehen der Ausgabe steuert und von uns ignoriert wird. Wir ignorieren momentan auch verschiedene weitere HTML-Elemente, beispielsweise Überschriften, die den Text gliedern, und versuchen nur diejenigen zu identifizieren, die den eigentlichen Transkriptionstext enthalten. Diese, so stellen wir fest, sehen dann so aus:

<div class="vers"><span class="versnr">.01</span> <span class ="si1">I</span>ST zwiuel h(er)zen nahgebur</div>

oder so:

<div class="vers"><span class="versnr">1.11</span> hat di swarzen <span class="k">va*</span> <span class="ls5">varwe&amp;lt;/span&amp;gt; gár.</div>

Der Transkriptionstext befindet sich also stets zwischen <div>-tags. <div>-tags kommen allerdings auch zu anderen Zwecken zum Einsatz. So wird uns auch die Information in <div>-tags präsentiert, dass dieser Teil der Handschrift von einem Schreiber geschrieben wurde, dem die originelle Bezeichnung Schreiber I zugeordnet wurde. <div> ist in HTML5 ein Allzweck-Element für Textabschnitte, das gerne benutzt wird, besondere Elemente zu bezeichnen, die durch die üblichen HTML-tags nicht adäquat erfasst werden können Genauer differenziert werden diese dann durch die Zuweisung einer Klasse. Elemente der Transkription befinden sich also, wenn wir es exakter erfassen, zwischen <div>-tags, die der Klasse „vers“ angehören und lassen sich somit problemlos von denen unterscheiden, die zur Klasse „schreiber“ gehören. Auf jedes Verszeile lässt sich durch eine ihr zugeordnete Versnummer verweisen und diese, so stellen wir fest, wird uns auch in jedem Vers angegeben, innerhalb eines <span>-Elements der Klasse „versnr“. <span>-tags rahmen keine Textabschnitte ein, sondern Teile eines Textabschnittes. Um sie herum gibt es Text oder kann es Text geben, während nach und vor einem Textabschnitt üblicherweise eine neue Zeile beginnt. Die Versnummern werden also korrekt innerhalb des Verses erfasst.

Zwei weitere Klassen die für <span>-tags möglich sind, können wir ebenfalls, spätestens beim Blick in die Handschrift, recht leicht interpretieren: In V. 1.11. hat der Schreiber (Schreiber I !) ein Wort (va) nicht ausgeschrieben. Abgekürzt hat er so wohl (das kann man schließlich aus anderen Handschriften erschließen), das Wort varwe. Die Klasse „k“ bezeichnet also ein in der Edition korrigiertes Element, die Klasse „ls5“ die von Editoren angesetzte „Lesung“ dieser Stelle. Tatsächlich ist es selbstverständlich ein wenig komplizierter, als wir es hier beschreiben, doch für unsere Zwecke reicht diese Zuordnung, die dem optisch erschließbaren Befund im Browserfenster bei Verwendung des zugeordneten Stylesheets entspricht. Hier werden die tatsächlich in der Handschrift anzutreffenden „Verschreibungen“ ausgegraut, die Korrekuren in einem hellen Blau wiedergegeben.

Die differenzierten Informationen, die uns diese Ansicht gibt und die noch differenzierteren Angaben, die wir dem Quelltext entnehmen können, macht deutlich, dass wir eine moderne, digitale Edition vor uns haben, die uns im Internet als HTML-Datei präsentiert wird. Dass der HTML-Datei wiederum eine für uns nicht zugängliche Fassung im strengeren und informativen TEI-Auszeichnungsschema zugrunde liegt, wird schon alleine durch die Verwendung der Klassenbezeichnungen und der Struktur der HTML-Datei deutlich.

So differenzierte Informationen erhalten wir durch den Quelltext der Website der bibliotheca augustana nicht. Hier handelt es sich nicht um eine digitale sondern um eine digitalisierte Edition. Es ist also nur eine für den Druck erarbeitete Edition die nachträglich in eine HTML-Fassung überführt wurde, die allenfalls einige typographische Signale der Druckedition übernimmt oder nachahmt. Außerdem ist es eine recht alte Datei. alt ist dabei natürlich in den Zeiteinheiten des Internets zu verstehen und ganz sicher nicht in denen der Mediävistik. In grauer Vorzeit diente HTML der Wiedergabe von Inhalten und zugleich der optischen Aufbereitung derselben. Und auch, wenn es auf den ersten Blick so scheinen mag, als habe sich daran nicht viel geändert: Spätestens seit der Einführung von HTML5 soll HTML ausschließlich als Beschreibungssprache der Struktur verstanden werden.

Die Bearbeiter der Editionen für die biblitheca augustana verwendeten, wie wir sahen, beispielsweise <h3>-tags zur Auszeichnung des eigentlichen Textes. Mit <h3> soll jedoch eigentlich eine Überschrift dritter Ordnung erfasst werden. Üblicherweise werden Überschriften der dritten Ordnung von den Browsern bis heute (wenn kein Stylesheet vorliegt) etwas kleiner und unauffälliger als solche der zweiten und erst recht der ersten Ordnung dargestellt, auf jeden Fall aber ein wenig prominenter, größer und möglicherweise dicker als einfacher „Normaltext“. Ganz eindeutig wurde <h3> also alleine aus typographischen Gründen für die Textauszeichnung gewählt, so wie Versnummern in <h6>-tags erfasst wurden. Zusätzlich zu den im Kopf der Datei angeforderten Stylesheets werden in den Verszeilen weitere „style“-Angaben gemacht, um etwa die Einrückung vom rechten Rand anzugeben.

Wir können allenfalls versuchen, strukturelle Informationen und vielleicht editorische Eingriffe aus den so notierten Angaben indirekt zu erschließen. Verlassen können wir uns auf diese Schlüsse nicht und werden deshalb vorerst nur versuchen, aus beiden HTML-Dateien den reinen Text ohne alle weiteren Elemente und zusätzlich erreichbaren Informationen zu entnehmen.

Doch werden wir damit erst im zweiten Teil dieses Tutorials beginnen.

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.

 

 

 

 

 

 

 

 

 

 

 

 

Tabellen und Texte

Zu den immer wiederkehrenden Aufgabe im Umgang mit (alten) Texten gehört es, ein Digitalisat in ein anderes digitales Format zu übertragen. Ein Ausschnitt des Nibelungenliedes in der Fassung der Handschrift A wurde von einem fleißigen Helfer sorgfältig abgetippt und ist in einer Datei mit der Endung .txt abgespeichert worden:

 
Ez wuohs in Burgonden ein schoene magedin
daz in allen landen niht schoeners mohte sin
Chriemhilt was si geheizzen vnde was ein schoene wip
darumbe mvosen degene vil verliesen den lip 

Diese Transkription soll jetzt auf einer Website angezeigt werden. Zusätzlich ist eine spätere Weiternutzung der für diesen Zweck erzeugten HTML-Fassung in einer E-Book-Publikation geplant. Die Abfolge der Verszeilen muss selbstverständlich jeweils und auf jedem gewählten Wiedergabegerät erkennbar bleiben. Es ist deshalb vorgesehen, dass die Verszeilen, die in der .txt-Datei je eine Zeile einnehmen, in der HTML-Fassung separat durch Markup ausgezeichnet werden. Ein (vereinfacht wiedergegebenes) Gerüst der HTML-Datei besteht bereits:

<!DOCTYPE html>
<html>
  <body>
<!-- weiterer Inhalt -->     
      <ol>
<!-- ... hier soll der mittelhochdeutsche Text eingefügt werden -->
     </ol>
<!-- weiterer Inhalt -->
   </body>
<html>

 

Jetzt geht es also nur noch darum, zwischen die beiden tags <ol> und </ol> (ol = ordererd list) den Text der Absätze einzufügen und jeweils ein Starttag <li> voran und ein Endtag </li> hinter sie zu stellen (li = list element). Das klingt allerdings nach einer reichlich monotonen Aufgabe. Der eine oder andere Fan der Serie mag sich bei solchen Aufgabenstellungen an die Stelle eines Bart Simpson zu Beginn jeder Folge versetzt fühlen, ein Stück Kreide in der Hand, die große leere Schultafel vor sich. Es gibt jedoch viele Möglichkeiten, eine nicht ganz so stupide Lösung zu finden, um das gewünschte Ziel zu erreichen. Vielleicht die simpelste ist es, eine Tabellenkalkulation zu diesem Zweck einzusetzen. Eine solche dürfte auf den meisten Arbeitsrechnern als Teil des dort installierten Office-Paketes vorhanden sein. Andernfalls gibt es für Calc, dem zu Libre Office (bzw. Open Office) gehörenden Programm, oder in dem von Google bereitgestellten Dienst Tabellen, die Möglichkeit, eine (kostenlose) Onlinelösung zu verwenden. Die im folgenden vorgestellte Methode funktioniert jedenfalls vollkommen identisch in Excel, Calc oder der Android-Variante.

Als erstes wird der Ausgangstext komplett in die Zwischenablage kopiert. Dann wird in einer leeren Arbeitsmappe der Tabellenkalkulation der eigenen Präferenz die zweite Zelle der ersten Zeile markiert (B1). Hierhin fügen wir den Text ohne weitere Formatierungsangaben ein: :

Die Tabellenkalkulation übernimmt die Absatzformatierung der Vorlage. Der Text wird zeilenweise in die Spalte B der Tabelle eingesetzt. Momentan sieht es so aus, als ob sich die Textzeilen über mehrere Spalten erstrecken würden. Tatsächlich sind sie jedoch auf Zellen der Spalte B beschränkt. Wir können uns davon überzeugen, indem wir die Breite der Spalte verändern. Dazu positionieren wir den Mauscursor in die Überschriftenzeile auf die Grenze zwischen die hinterlegten Kästchen mit den Buchstaben B und C und verschieben dann die Grenze zwischen den Spalten bei gedrückter linker Maustaste.

Vor die erste Zelle mit einem Textinhalt, in die Zelle A1, geben wir als Text <li>> ein. In die Zelle nach dem Text, also C1, schreiben wir </li>:

Nun setzen wir den Cursor in die Zelle D1 und schreiben folgende Anweisung, die wir durch die Eingabetaste abschließen:

=A1 & B1 & C1

Das Gleichheitszeichen zu Beginn der Eingabe signalisiert, dass der von uns eingegeben Text als Formel zu verstehen ist, die Leerzeichen sind nur als Erleichterung für unsere Augen verwendet worden und könnten auch wegbleiben. Nach dem Betätigen der Eingabetaste sollten wir das Ergebnis der Formelanwendung sehen. Es ist die Zeichenkette, die sich aus dem durch das „Kaufmannsund“ & signalisierten Zusammenfügen des Zelleninhalts von A1, B1 und C1 ergibt:

D1 enthält den durch das Markup ausgezeichneten Text der ersten Verszeile, so wie wir ihn in die HTML-Datei einsetzen wollen. Wir können ihn mit Kopieren ebenso leicht aus der Tabelle entnehmen, wie wir ihn zuvor per Einfügen in diese eingetragen haben, und dann umgekehrt den Inhalt der Zwischenablage wieder in die HTML-Datei einsetzen. Es wäre allerdings ein sehr mühseliges Verfahren, wollten wir die beschriebenen Arbeitsschritte für jede einzelne Verszeile des Textes wiederholen. Das müssen wir jedoch gar nicht! Denn jetzt können wir eine der zahlreichen kleinen Hilfestellungen nutzen, die Tabellenkalkulationen den Anwendern bei repetiven Aufgaben anbieten.

Wird eine Zelle der Tabelle angeklickt und damit markiert, dann wird sie von einem Rahmen umgeben, dessen rechte, untere Ecke eine quadratförmige Verdickung aufweist. Wir markieren die Zelle A1 und steuern das kleine Quadrat mit dem Mauscursor an, bis dieser sich in ein kleines (in Calc ist es nicht ganz so klein) schwarzes Kreuz oder Plussymbol verwandelt:

Wir können nun mit gedrückter linker Maustaste den markierten Rahmen in der Spalte herabziehen. Sobald wir den Mausknopf loslassen (äquivalent: den Finger vom Touchpad des Laptops nehmen), wird automatisch der Inhalt der Zelle A1 in alle Zellen übertragen, die nun von dem Rahmen umgeben sind. Auf diese Weise ist es uns ein leichtes, alle Zellen in Spalte A vor einer mit Text gefüllten Zelle in Spalte B mit dem Einleitungstag <li> zu füllen.

Auf gleiche Weise können wir nach den Textzeilen verfahren. Wir markieren diesmal allerdings gleich zwei Zellen der Tabelle, C1 und D1 (linken Mausknopf gedrückt halten und Cursor verschieben):

Wenn wir nun wie zuvor das Quadrat in der unteren rechten Ecke des Rahmens anklicken und den schwarzen Plus-Cursor nach unten ziehen, kopieren wir nicht nur den Textinhalt von C1 in die darunterliegenden Zellen, sondern auch die Formel in D1. Diese wird jedoch nicht buchstabengetreu übernommen. Das wollen wir auch gar nicht, denn es würde dazu führen, dass in jeder Zelle der Spalte D derselbe Text, nämlich die Aneinanderreihung von A1, B1, C1, zu finden wäre (Ez wuohs in Burgonden …). Tatsächlich reiht die Formel in Zeile 2 jedoch A2, B2 und C2, in der dritten Zeile A3, B3 und C3 usw.. Dieses adaptive Kopierverfahren erleichtert viele Arbeitsschritte beim Umgang mit Formeln in Tabellen. Tatsächlich muss man mehr Aufwand betreiben, falls man einmal ausdrücklich möchte, dass eine Formel „buchstäblich“, ohne Anpassung, in kopierte Zellen übernommen wird. Auch wir profitieren vom intelligenten Kopierverfahren und können nun per copy & paste sämtliche von tags umrahmte Verszeilen in die HTML-Datei übernehmen.

Da sich der Text der tags von Zeile zu Zeile nicht ändert, können wir für unser Beispiel sogar darauf verzichten , die tags in die Zellen der Spalten A und C einzuschreiben. Denn es genügt folgende Formel in D1, um zum selben Ergebnis zu gelangen:

="<li>" & B1 & "</li>

Unser zuerst verwendetes Verfahren mag ein wenig umständlicher erscheinen, ist aber vermutlich etwas einfacher für denjenigen nachzuvollziehen und für eigene Zwecke abzuwandeln, der bisher wenige Erfahrungen mit dieser Art von Programmen gesammelt hat.

Wir widmen uns jedoch abschließend einer Erweiterung der Aufgabenstellung, die es tatsächlich nötig macht, den Inhalt zweier Zellen in die Formel einzubeziehen. HTML ordnet einem Listenelement einer geordneten Liste einen Index zu. Da es sich beim Textausschnitt um ein Zitat handelt, wollen wir natürlich, dass stets die korrekte Zeilennummerierung verwendet wird. Eine geordnete Liste in HTML erhöht, von einem Startwert ausgehend, den Index der Folgeelemente automatisch jeweils um eins. Eigentlich bräuchten wir bei einem zusammenhängenden Textausschnitt also nur einmal einen Wert anzugeben. Wir haben uns jedoch entschlossen, die Zeilennummerierung jeder Verszeile einzeln zu erfassen und im <li>-Element festzuschreiben. Möglicherweise merkt man daran, wie sehr wir uns als Wissenschaftler einer gewissen Pedanterie verpflichtet fühlen. Wir denken aber auch an das künftige E-Book und erhalten uns mit dieser Maßnahme die Option, die Zitate aus dem Text für dieses umzustellen, zu portionieren und auf mehrere von anderen HTML-Elementen unterbrochene, geordnete Listen zu verteilen.

Die Zeilennummern werden wir in der inzwischen nicht mehr für Formatierungszwecke benötigten Spalte A notieren. Da die Abfolge der Textzeilen (vorerst) intakt ist, können wir wiederum das adaptive Kopierverfahren verwenden, um die Verse fortlaufend zu nummerieren. Dazu tragen wir in A1 und in A2 jeweils die aufeinander folgenden Versnummerierungen ein. Wenn wir nun die beiden Zellen gemeinsam markieren und danach den Rahmen nach unten ziehen, werden die vom Rahmen erfassten folgenden Zellen mit Zahlenwerten gefüllt, die das Intervall wiederholen, das sich zwischen A1 und A2 feststellen lässt.

Jetzt müssen wir nur noch unsere Formel in D1 anpassen. Der Index eines Listeneintrags in HTML wird im Attribut value gespeichert und wie alle Attribute zwischen Anführungszeichen eingeschlossen. Anführungszeichen haben aber auch in den Formeln der Tabellenkalkulationen eine Aufgabe haben. Wir haben sie bereits genutzt, um Zeichenketten („<li>“) dazwischen zu notieren. Um anzuzeigen, dass wir jetzt ein Anführungszeichen in einer Zeichenkette (und nicht zur äußeren Kennzeichnung der Zeichenkette) meinen, müssen eine besondere Kodierung verwenden. Die nun etwas sperrigere Formel in D1 lautet:

="li value="""&A1&""">"&B1&"&</li>"

Nachdem wir sie in die restlichen Zellen von D übernommen haben, können wir wie gewohnt, mit copy & paste unsere Arbeit (lange vor dem Pausenklingeln) abschließen:

Mein erstes mittelhochdeutsches Hörbuch – Teil 6

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

Bitte nochmal, mit mehr Gefühl!

Es gibt noch ein paar Aufgaben, denen wir uns widmen wollen:

  1. In einigen wenigen Fällen nimmt Hedda unsere Regeln zu genau und spricht deshalb Wörter mit einer falschen Aussprache aus.
  2. Ansonsten ist ihre Aussprache der mittelhochdeutschen Laute weitgehend korrekt, merkwürdig klingen allerdings ihre Akzentuierungen: Oft betont sie Wörter auf der falschen Silbe und manchmal wird es dann sehr schwer, den Sinn eines Verses zu verstehen.
  3. Wir werden Hedda sicher nicht beibringen können, den Text emotional zu interpretieren, aber etwas weniger monoton und seelenlos könnte ihr Vortrag doch sein.
  4. Schließlich die Satzzeichen: Bevor sie zu lesen beginnt, zeigt uns Hedda weiterhin vier Zeichen an, die ihr unbekannt sind. Es handelt sich um die Satzzeichen Punkt, Komma, Doppelpunkt und Semikolon. Wir sollten uns entscheiden, ob sie die weiterhin überlesen soll.

Auf diesen letzten Punkt können wir als erstes eingehen. Selbstverständlich handelt es sich bei den Satzzeichen, die wir im Text vorfinden um Eingriffe „moderner“ Editoren (des 19. Jahrhunderts). Im mittelalterlichen Manuskript waren sie nicht vorhanden. Die neuzeitlichen Bearbeiter geben durch die Interpunktion Fingerzeige, die beim Textverständnis helfen sollen. Wir müssen nicht zu kritisch sein und ihre Maßnahmen in Frage stellen, sondern können uns für unsere Zwecke dem Sachverstand der Experten anvertrauen. Die nun einmal im Text vorhandenen Zeichen können an Hedda weitergeben werden, sobald wir sie in die UPS-Notation überführt haben. Je nach dem für Hedda voreingestellten kulturellen Muster (Hochdeutsch/Deutschland), wird sie ihre Satzbetonung an der Interpunktion ausrichten. Bei einem Fragezeichen am Satzende hebt sie die Stimme, bei einem Punkt wird sie sie senken.

Wir fügen also unserem LexikonDerBuchstabenErsetzung ein paar Einträge hinzu. Auch wenn diese in unserem Textausschnitt gar nicht vorkommen, geben wir auch (für zukünftige Verwendungen?) die UPS-Notation für Frage- und Ausrufezeichen an. Doppelpunkt und Semikolon sind hingegen zu häufig im Textausschnitt verwendet worden, als dass wir sie ignorieren wollen. Da für sie kein UPS-Äquivalent existiert, wählen wir nach Belieben einen Ersatz, mit dem wir für uns die Frage beantworten, wie sich ein Doppelpunkt anhört (Wie ein Punkt):

            // Satzzeichen:
            ["."]="_.",
            [","]="_,",
            [":"]="_.",
            [";"]="_,",
            ["?"]="_?",
            ["!"]="_!",

Nachdem wir uns (ein wenig) um die Satzmelodie gekümmert haben, widmen wir uns als nächstes der Wortbetonung. Daran, dass das Wortformen des Deutschen meist anfangsbetont sind, hat sich seit mittelhochdeutscher Zeit nichts geändert. Wenn wir einfach jedem Wort, das wir Hedda zu lesen geben, gleich zu Beginn von FindeAussprache die Zeichenfolge [S1 ] voranstellen, wirkt Hedda Lesung druckvoller und energischer, ansonsten ändert sich jedoch nur wenig:

public string FindeAussprache(string Wort)
        {
            string Aussprache = "S1 ";

Noch deutlicher sind jetzt allerdings die Wörter auszumachen, für die sie eine völlig falsche Akzentuierung wählt. Sie übertreibt! Uns erinnert sie damit möglicherweise an jemand, der gerade eine Fremdsprache erlernt und sich jetzt ein wenig zu sehr bemüht, in der ihm fremden Rhythmik zu sprechen. Am Auffälligsten hören wir die Fehler bei Wörtern mit einer Vorsilbe, die als Präfix fungiert: ge-, be-, ver-, zer- und so weiter:

Um ihr das auszutreiben, werden wir bei jedem Wort überprüfen, ob eine der als Vorsilben gebräuchlichen Buchstabenfolgen an seinem Anfang zu finden ist. Es bietet sich an, die potentiellen Vorsilben in einer Liste zu sammeln. Wir sollten jedoch überlegen, ob wir nicht wieder ein Lexikon verwenden, in dem Hedda dann gleich die für sie lesbare Lautschrift nachschlagen kann. Die erneute Verwendung eines Lexikons mag auf den ersten Blick überflüssig erscheinen. Haben wir uns  nicht bereits darum gekümmert, alle notwendigen Informationen bereit zu stellen? Welche Laute in den potentiellen Vorsilben enthalten sind, ermitteln wir problemlos, indem wir jeden ihrer Buchstaben im LexikonDerBuchstabenErsetzung nachschlagen. Und mit welcher Betonung die präfigierten Worte zu lesen sind, dürfte sich sehr leicht bestimmen lassen.  Die vor das Wort gesetzten Silben werden nicht betont, der Hauptakzent liegt auf der folgenden (Stamm-) Silbe. Dieses Schema sollte sich auch ohne ein weiteres Lexikon automatisieren lassen.

Dieser Einwand ist berechtigt. Während wir jedoch weitere Tipparbeit durch den Einsatz eines Lexikons haben, ersparen wir Hedda bei jeder Verwendung desselben Arbeit. Arbeit, die sich in Zeit umrechnen lässt! Davon abgesehen, wird tatsächlich das e in einer Nebensilbe anders (murmelnder) ausgesprochen, als in einer, die Bedeutung und Betonung trägt. Dem können wir so Rechnung tragen. Außerdem erleichtert uns ein Lexikon den Umgang mit dem gar nicht so seltenen Sonderfall, der eintritt, wenn das verneinende un- vor ein Wort tritt, das bereits ein Präfix besitzt (un-be-kant). In diesen Fällen wird un- mit einer Betonung gelesen (ún-be-kánt), ansonsten wie die anderen Präfixe (mit Ausnahmen!) unbetont (un-réh-te). Wir begnügen uns mit den Präfixen, die wir in unserem Text finden und erstellen folgendes Lexikon, das wir in die Klasse Hedda.cs einfügen:

 private Dictionary&amp;lt;string, string&amp;gt; LexikonDerPräfixe = new Dictionary&amp;lt;string, string&amp;gt;
        {
            ["be"] = "B EX",
            ["ge"] = "G EX",
            ["ver"] = "F EHX",
            ["zer"] = "T + S EHX",
            ["en"] = "EX N",
            ["unbe"] = "S1 UH N . B EX",
            ["unge"] = "S1 UH N . G EX",
            ["unver"] = "S1 UH N . F EHX",
            ["unzer"] = "S1 UH N . T + S EHX"

        };

Der Punkt [.] markiert in der UPS-Lautschrift eine Silbengrenze. [S1 UH N . B EH] gibt demnach an, dass unbe aus zwei Silben besteht, von denen die eine mit der Lautung [UH N], die zweite mit [B EX] wiedergeben wird. Auf der ersten der beiden liegt eine Betonung [S1]. Falls Hedda in FindeAussprache eine Vorsilbe identifiziert, kann sie folgende Regel anwenden, mit der sie unsere Defaultfüllung von Aussprache (Aussprache=“S1 „) überschreibt, die für alle Wörter im Text eine Anfangsbetonung annimmt:

Aussprache = LexikonDerPräfixe[Präfix] + " . S1 ";

Wir haben somit die Aussprache zu Beginn eines Wortes ermittelt und festgelegt, dass die nächste, folgende Silbe betont auszusprechen ist. Mit dieser können wir jetzt fortfahren und nun die UPS-Kodierung für die Buchstaben im Wort hinter dem Präfix ermitteln:

Wort = Wort.Substring(Präfix.Length);

Vorsicht!!

ez ist mîn bete und ouch mîn rât,

Der Blick in den Text lehrt uns, dass nicht in jedem Wort, das mit be beginnt, be als Präfix verstanden werden darf! bete wird auf der ersten Silbe betont. Andere mittelhochdeutsche Worte beginnen ohne jede Präfigierung mit einem  ge: geste, gern.

Einige sehr häufige Wortformen dieser Art nehmen wir in das Lexikon auf, in welchem Hedda ganze Wörter und die ihnen zugeordnete Aussprache findet. Weiterhin werden wir eine Minimalzahl für die Buchstaben festlegen, die auf die Vorsilbe im Wort folgen. Und wir verlangen, dass dieses mindestens eine weitere Silbe enthält. Mit diesen beiden Methoden wird es uns nicht gelingen, alle Fehllesungen auszumerzen. Durch eine Zählung der Buchstaben und der Silben eines Wortes können wir aber in jedem Fall eine Vorauswahl vornehmen:

Kein Wort kann eine Vorsilbe enthalten, die länger ist, als es selber und einsilbige Wörter enthalten mit Sicherheit keine Vorsilbe!

Um die Silben zählen zu können, greifen wir auf eine weitere Bibliothek von C# zurück, die wir in Hedda.cs einbinden. Wir fügen eine using-Anweisung am Anfang der Datei hinzu:

 using System.Text.RegularExpressions;

Wir können jetzt sogenannte reguläre Ausdrücke benutzen. Diese sind in einer speziellen Syntax verfasst, deren Aufgabe es ist, Muster für Zeichenketten zu beschreiben. Wir können mit ihrer Hilfe in einem Text nach allen Buchstaben oder Buchstabenfolgen suchen, die dem gewünschten Muster entsprechen. Um beispielsweise anzuzeigen, dass aus einer Gruppe von Zeichen jedes gleichermaßen meine Suchkriterien erfüllt, schließe ich die Gruppe in eine eckige [] Klammer ein. In einer geschwungenen {} Klammer kann ich sodann angeben, wie viele dieser Zeichen mindestens und höchstens hintereinander vorkommen sollen. Die Silbenzahl können wir auf folgende Weise schnell mit einem regulären Ausdruck ermitteln:

       private int Silbenzahl(string Text)
        {
            Regex re = new Regex(@"[aâeêiîoôöuûüáéíóúàèìòùæœ]{1,2}");
            return re.Matches(Text).Count; 
        }

Jede Silbe, so unsere Überlegung, enthält mindestens einen Vokal oder eine Abfolge von maximal zwei Vokalzeichen. Wir zählen, wie oft Vokale oder Vokalfolgen in einem Text vorkommen, um zu erfahren, wie viele Silben er enthält. Bei Wörtern wie leier oder meien werden von uns korrekt zwei Silben gezählt.

Mithilfe eines regulären Ausdrucks können wir auch leicht die Präfixe in einem Wort bestimmen. Durch ^ signalisieren wir, dass wir nur Zeichen am Beginn eines Textes in unsere Suche aufnehmen. Um anzuzeigen, dass mehre Abfolgen von Buchstaben gleichwertig unsere Suchkriterien erfüllen, stellen wir zwischen sie ein |, dieses wird als (logisches) oder interpretiert. Mit ^ver|^zer suchen wir also nach ver oder zer am Anfang eines Wortes. Wenn wir den Ausdruck klammern, können wir das einleitende Zeichen auf den gesamten Inhalt der Klammer beziehen: ^(ver|zer). Die Klammerung erfüllt noch einen weiteren Zweck: Wenn wir später wissen wollen, welche Zeichenfolge denn tatsächlich in einem Wort vorkam, auf das wir die Suche angewendet haben, ist diese für uns in einer Liste gespeichert. Um bei mehreren Klammerungen den Überblick nicht zu verlieren, können wir den Elementen der Liste sogar einen Namen geben. Mit ^(? <Präfix>ver|zer) haben wir unserem Suchergebnis den Namen Präfix zugeordnet.

Den regulären Ausdruck müssen wir nicht komplett per Hand eingeben. Mit Join() lassen sich Elemente einer Liste zu einer Zeichenkette zusammenführen, die durch ein Zeichen, das wir frei bestimmen, getrennt sind. Der Suchauftrag wird linear ausgeführt. Wir wollen sicherstellen, dass zuerst auf unbe geprüft wird und nur, wenn dieses nicht am Anfang des Wortes steht, auch auf un oder be. Die Sortierung der Stichwörter unseres Lexikons nach ihrer Länge führen wir nach dem bekannten Schema durch. So erhalten wir den passenden regulären Ausdruck für unsere Suche nach den präfigierenden Vorsilben:

List<string> Präfixe = LexikonDerPräfixe.Keys.ToList();
Präfixe.Sort((Präfix1, Präfix2) => Präfix2.Length.CompareTo(Präfix1.Length));
string Suche = "^(?<Präfix>"+String.Join("|",Präfixe)+")";

Ganz ähnlich können wir auch am anderen Ende der Wörter verfahren: Mehrsilbige mittelhochdeutsche Wörter enden meist in einer unbetonten Silbe, deren Silbenkern als e geschrieben, aber als Murmellaut gesprochen wird. In den ersten fünf Versen unseres Textes finden wir als Beispiele: gemer-ken geden-ke wî-se al-ten zî-ten vol-ge dûh-te.

Jede dieser Silben wird von einem Konsonanten eingeleitet, es ist aber in Worten wie lei-er auch möglich, dass sie einen offenen linken Rand aufweist. Als mögliche Silbenendungen kommen außerdem noch ec, es, ez, er, ers, ern, ent, et, el und els in Frage. Wir wollen uns diese Möglichkeiten veranschaulichen, bevor wir unsere Ersetzungsroutine implementieren:

Endsilbe
Wort Wortbeginn linker Rand Murmellaut rechter Rand
alte al- t e
ziten  zi-  t  e  n
hoerent hoe- r e nt
leier lei- e r

Die Abfrage am Ende des Wortes gestaltet sich etwas schwieriger, als am Anfang. Wir können sie sinnvollerweise erst vornehmen, nachdem wir die Laute der Aussprache, inklusive des zu ersetzenden e, ermittelt haben. Unser regulärer Ausdruck operiert also auf der UPS-Notation. Die Neuformulierung der Aussprache für das Wort werden wir größtenteils aus Elementen, die wir mithilfe des regulären Ausdrucks in der Ausgangszeichenkette ermitteln, zusammen“basteln“. Wir werden wieder benötigte Wortbestandteile deshalb unter einem von uns festgelegten Namen erfassen.  Nicht vergessen dürfen wir schließlich die Möglichkeit, dass am Ende des Wortes ein Satzzeichen steht.  Folgender reguläre Ausdruck erfüllt all unsere Bedingungen und Wünsche:

(?<Anfang>.+[^\.\+] )(?<Ei>E lng \+ IH )?(?<Links>(?(Ei)|(P \+ F |T \+ S |SH |[BCDFGHKLMNRSTVZ] )))EH (?<Rechts>N T |R T |L S |R S |[SZTRNK] )?(?<Satzzeichen>_[\.\,\?\!] )?$

Das ist ein Tatzelwurm, der sich vermutlich selbst ein Rätsel ist. Wir können versuchen, ihn ein wenig zu entwirren, indem wir seine Segmente durch natürliche Sprache wiedergeben und uns dabei an den Namen entlang hangeln, die wir vergeben haben:

Am Anfang eines Wortes, das unseren Bedingungen entspricht gibt es eine beliebige Abfolge von Zeichen, deren vorletztes (vor einem Leerzeichen) aber kein Punkt und kein Plus sein darf. Der Punkt dient in UPS der Silbentrennung.  Das + verbindet UPS-Zeichen zu einem gemeinsam kodierten Lautwert. Diesen wollen wir nicht trennen und damit zerstören und die Silbengrenze wollen wir ja gerade setzen. Wenn wir bei diesem Vorhaben auf einen Punkt treffen, läuft etwas falsch. . und + stellen wir einen Schrägstrich voran. Warum wir das tun, werden wir gleich erklären.

Auf den Anfang kann der Diphthong ei folgen – oder auch nicht. Das Fragezeichen hinter dem Ausdruck ist als Quantifizierer (kein- oder einmal) zu verstehen und könnte durch {0,1} ersetzt werden.

Kommen wir zum linken Rand der gesuchten Endsilbe (Links): Falls soeben ein ei  im Wort gefunden wurde, erfahren wird das durch ?(Ei), das Fragezeichen dient hier der Abfrage. Wenn die Frage bejaht wird, dann gibt es keinen linken Rand. Wenn es kein ei gibt, geht es nach dem | weiter, das für ein logisches oder steht. In diesem Fall wird der linke Rand durch einen Konsonant gebildet, der auch einmal durch mehrere, mit + verbundene, Lautzeichen bezeichnet werden kann. Da das + auch ein Quantifizierer sein kann, müssen wir durch den vorangestellten Schrägstrich signalisieren, dass wir diesmal tatsächlich das Buchstabenzeichen meinen.

Ausnahmsweise ist es nun ganz einfach: Nach dem linken Silbenrand, muss ein EH im Wort aufzufinden sein.

Rechts von diesem kann eine bestimmte Gruppe von Konsonanten oder Konsonantenabfolgen stehen. Auch der rechte Silbenrand kann allerdings leer bleiben, was wir erneut durch den Quantifizierer ? angeben.

Ach ja. Und dann könnte noch ein Satzzeichen folgen … Die Schrägstriche / sind erneut darauf zurückzuführen, dass Satzzeichen eine besondere Bedeutung in regulären Ausdrücken haben können. Dem ? sind wir schließlich oft genug begegnet.

ENDE. Nur, wenn wir jetzt am Wortende $ angekommen sind, entspricht das Wort unseren Bedingungen.

War doch gar nicht so schwer, oder? Jetzt muss nur noch aus diesen Bestandteilen eine neue Zeichenkette für Hedda gebildet werden.

Die mithilfe von regulären Ausdrücken vorgenommenen Änderungen betreffen bisher nur die mehrsilbigen Wörter des Textes. Alle Kurzwörter, also diejenigen, die nur aus einer Silbe bestehen, werden betont ausgesprochen. Nicht zuletzt deshalb wirkte Heddas letzte Lesung so kraftvoll auf uns! Sie liest mit gleichem Verve auch jede Partikel, jedes Pronomen, die Formen des bestimmten Artikel und die kurzen Präpositionen. Damit sie diese Wörter mit weniger Energie von sich gibt, erstellen wir eine Liste, die wir (per copy & paste) in die Klasse Hedda.cs einfügen:

        List<string> Kurzwörter = new List<string>
        { "vor","von","der","diu","die","daz","diz","ditz",
            "den","dem","dan","und","in","im","ir",
            "an","ze","al","ez","für","ich","er","si",
            "wol","bî","hie","wi","wie","ein","niht",
            "mîn","sîn","mich","sich","doch","noch","nâch",
            "dar","zuo","ie","mit","mê", "ouch","nu","unz",
            "als","vil","wan","gar"
        };

Dann können wir für diese Wörter in FindeAussprache den Defaultwert korrigieren:

if (Kurzwörter.Contains(Wort)) Aussprache = ""; 

Nachdem wir jetzt all diese Veränderungen an FindeAussprache vorgenommen haben, sieht die deutlich umfangreichere Methode inzwischen so aus:


        public string FindeAussprache(string Wort)
        {
            string Aussprache = "S1 "; //Default
            if (Kurzwörter.Contains(Wort)) Aussprache = ""; // Wenn das Wort nicht zu den meist unbetonten Kurzwörtern gehört!
            
            /* Wir überprüfen, ob die Möglichkeit einer präfigierenden Vorsilbe besteht.
             * Dazu muss das Wort mindestens vier Buchstaben und zwei Silben aufweisen:
             */
            if (Wort.Length>4 && Silbenzahl(Wort) > 1)
            {
                
                // Wir bauen die Suchanfrage auf:

                List<string> Präfixe = LexikonDerPräfixe.Keys.ToList();
                Präfixe.Sort((Präfix1, Präfix2) => Präfix2.Length.CompareTo(Präfix1.Length));
                string Suche = @"^(?<Präfix>" + String.Join("|", Präfixe) + ")";
                
                // Wir suchen. "Treffer" enthält die Suchergebnisse

                Regex reAnfang = new Regex(Suche);
                Match Treffer =  reAnfang.Match(Wort);

                if (Treffer.Success) // Wenn es einen Treffer gibt ...
                {
                    string Präfix = Treffer.Groups["Präfix"].ToString(); // ist dieser in Präfix gespeichert
                                                                        
                    // Erneute Überprüfung: Ist das gefundene Präfix zu lang?

                    if (Wort.Length-Präfix.Length > 2 && Silbenzahl(Wort.Substring(Präfix.Length))>0)
                    {
                        // Gut. Wir ersetzen.

                        Aussprache = LexikonDerPräfixe[Präfix] + " . S1 ";
                        Wort = Wort.Substring(Präfix.Length);
                    }  
                }
            }
           
            // Präfix oder nicht, jetzt wird die Aussprache des (Rest-)Wortes gefunden
            // Dieser Teil von FindeAussprache hat sich nicht geändert.

            int i = 0; // Index des Buchstabens, an dem wir uns gerade befinden.
            while (i<Wort.Length) // Solange i < als die Wortlänge ist.
            {
                List<string> MöglicheBuchstabenFolgen; 
                MöglicheBuchstabenFolgen = LexionDerBuchstabenErsetzung.Keys // Die mhd. Buchstabenfolgen
                                            .Where(Buchstaben => Buchstaben[0] == Wort[i]) // bei denen der erste Buchstabe und der aktuelle unseres Suchbegriffes identisch sind.
                                            .ToList();                         // als Liste.
                MöglicheBuchstabenFolgen.Sort((Folge1,Folge2)=>Folge2.Length.CompareTo(Folge1.Length)); // Sortiere die Liste absteigend nach ihrer Länge.

                /* Wir gehen die Buchstabenfolgen mit dem gesuchten Anfang,
                 * die der Länge nach, absteigend sortiert sind,
                 * eine nach der anderen durch und überprüfen,
                 * ob sie sich ganz an der Stelle i in unserem Wort finden.
                 * Wenn die erste (längste) Folge identifiziert wird,
                 * brechen wir die Suche ab und ersetzen.
                 * Andernfalls suchen wir weiter.*/
                bool Gefunden = false; // 
              
                    
                foreach (string Buchstabenfolge in MöglicheBuchstabenFolgen)
                {
                   
                    if (Wort.Length-i >= Buchstabenfolge.Length && Wort.Substring(i, Buchstabenfolge.Length) == Buchstabenfolge)
                    {
                        string Ersatz = LexionDerBuchstabenErsetzung[Buchstabenfolge];
                        if (Ersatz[0]=='X')
                        {
                            if (i - 1 >= 0 && "iîeêöüäœæ".Contains(Wort[i - 1])) Ersatz = "C";
                            if (i - 2 >= 0 && Wort.Substring(i-2,2)=="iu") Ersatz = "C";
                        }

                        if (Ersatz=="T + S")
                        {
                            if (i - 1 >= 0 && "aouâôûiîeêöüäœæ".Contains(Wort[i - 1])) Ersatz = "Z"; 
                        }
                        Aussprache += Ersatz; // X += Y ist X = X + Y
                        Aussprache += ' '; //nicht das Leerzeichen vergessen!
                        i +=  Buchstabenfolge.Length;
                        Gefunden = true;
                        break; // Ein Ersatz gefunden, wir suchen nicht mehr weiter!
                    }
                
                }
                if (!Gefunden)
                {
                    // Unbekannter Buchstabe
                    // Falls noch nicht in der Liste der unbekannten Zeichen,
                    // trage ihn ein.
                    if (!UnbekannteZeichen.Contains(Wort[i])) UnbekannteZeichen.Add(Wort[i]);
                    i += 1; // Zähle Weiter
                }
            }

            // Jetzt kümmern wir uns um das Ende des Wortes:

            if (Silbenzahl(Wort) > 1)
            {
               /* Wir fragen ab, ob eine unbetonte Endsilbe existieren kann und speichern das Ergebnis
                  Der ziemlich komplizierten Abfrage in vier Variablen:
                    Anfang - speichert den Beginn des Wortes VOR der Endsilbe
                    Links - speichert den linken Rand der Silbe.
                    Rechts - entsprechend den rechten Rand.
                    Satzzeichen - nimmt ein etwaiges Satzzeichen auf.
                    */

               
                Regex reEnde = new Regex(@"(?<Anfang>.+[^\.] )(?<Ei>E lng \+ IH )?(?<Links>(?(Ei)|(P \+ F |T \+ S |SH [BCDFGHKLMNRSTVZ] )))EH (?<Rechts>N T |R T |L S |R S |[SZTRNK] )?(?<Satzzeichen>_[\.\,\?\!] )?$");

                Match 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"] + " EX " + Suffix.Groups["Rechts"]+Suffix.Groups["Satzzeichen"];

                } 
        
            }
       
            return Aussprache;
        } 

Ob sich unsere Mühen gelohnt haben, können wir uns anhören, während wir den C#-Code studieren und versuchen die regulären Ausdrücke zu verstehen:

Endet ein Vers übrigens mit einem Wort, dessen letzte Silbe unbetont ist, wird er traditionell als klingend, oder (etwas chauvinistisch) als weiblich bezeichnet. Endet er hingegen auf betonter Silbe, so nennt man dies eine stumpfe, beziehungsweise männliche Kadenz. Um zu erfahren wie der Vers endet werden wir wieder einen regulären Ausdruck verwenden. Diesmal können wir wieder mit der vielleicht lesbareren mittelhochdeutschen Buchstabenschrift arbeiten:

        private bool Klingend(string Zeile)
        {
            Regex reVersende =new Regex( @"ei|[bcdfghklmnprstvwz]e[rntlsz\.,;:\?!]{0,3}$");
            if (reVersende.Match(Zeile).Success)
            {
                // Wir akzeptieren das Resultat, wenn das letzte Wort von Zeile mehrsilbig ist:
                if (Silbenzahl(Zeile.Split().Last()) > 1) return true;
            }
            return false; // Alle anderen Fälle.
        }

Wenn wir diese Methode zu Hedda.cs hinzufügen, können wir die dadurch zugängliche Information für zwei weitere kleine Verbesserungen nutzen. Als erstes entfernen wir aus Main in Program.cs die Zeile:

pb.AppendBreak(PromptBreak.Small);

Stattdessen setzen wir ganz am Ende, direkt vor der schließenden Klammer }, in der Methode LeseZeile von Hedda.cs folgende Zeile ein:

pb.AppendBreak(Klingend(Zeile)?PromptBreak.ExtraSmall:PromptBreak.Small);

Wir machen die Länge der Pause nach einem Vers dadurch jetzt abhängig vom Versausgang. Wenn er klingend endet, ist die Pause sehr kurz. Nach einer betonten Silbe ist die Pause ein bisschen länger.

Und schließlich widmen wir uns noch einmal den  Kurzwörtern: Das mittelhochdeutsche Versmaß ist gleichmäßig alternierend. Auf eine betonte Silbe folgt, abgesehen von ein paar sehr seltenen Ausnahmen, immer eine unbetonte. Und umgekehrt.

Für ein Kurzwort, das auf eine betonte Silbe fällt, bedeutet dies, das es in diesem Falle doch betont zu lesen ist. Um zu verstehen, warum dies wichtig sein kann, betrachten wir uns eine Wortform wie der. Wenn wir in  einem Text auf dieses Wort treffen, ist es meist der bestimmte Artikel. Als solcher hat es eine untergeordnete Funktion, der (Haupt-) Akzent liegt selbstverständlich auf dem Súbstantiv, dem das Wórt der vorangeht. Wenn wir unsere Betonung jedoch ändern, DER Artikel sagen, dann geben wir zu verstehen, dass wir einen ganz bestimmten, herausgehobenen Artikel meinen. dér ist dann kein Artikel mehr, sondern ein Demonstrativpronomen (in der Bedeutung: dieser, genau der).  Selbstverständlich wird der auch als  Relativpronomen mit Betonung gesprochen (der Artikel, dér). Auch in einem mittelhochdeutschen Vers kann die Betonung eines Wortes einen Bedeutungsunterschied machen und wir sollten annehmen dürfen, dass die Dichter Akzente bewusst gesetzt haben.

Wir sollten uns deshalb darum bemühen, Hedda dazu zu bringen, dem Versmaß entsprechend zu lesen. Alles, was sie dazu wissen muss, ist wie der Vers jeweils endet.  Denn am Anfang des Verses darf der Dichter ein wenig schummeln. Wenn wir jedoch vom Versende rückwärts zählen, wissen wir, dass im stumpfen Vers die zweite, vierte, sechste, im klingenden die erste, dritte, fünfte und siebte Silbe der Zeile betont sein sollte. Wer sich jetzt wundern sollte: Wir zählen selbstverständlich nach Informatikerart und  beginnen mit dem Index 0! Die Regulierung in LeseZeile lautet also:

                       if (Kurzwörter.Contains(Wort))
                        {
                            if ((SilbenImVers-Silbe) % 2 == 1 && Klingend(Zeile)) Lautung = "S1 " + Lautung;
                            if ((SilbenImVers-Silbe) % 2 == 0 && !Klingend(Zeile)) Lautung = "S1 " + Lautung;                                 
                        }

Silbe ist der Index der Silben, die Hedda bisher bearbeitet hat, inklusive des gerade bestimmten Wortes. In der Variablen SilbenImVers haben wir zuvor die Silbenzahl der Verszeile gespeichert. Mit dem Rechenzeichen % (dem Modulo) teilen wir die Differenz dieser Zahlen durch zwei und erhalten den Rest, erfahren also, ob es sich um eine gerade oder ungerade Silbenzahl vom Versende (Index: 0!) gesehen handelt. Die komplette Methode LeseZeile sieht nun so aus:

        public  void LeseZeile (string Zeile, PromptBuilder pb)
        {

            /* Los geht's Hedda!
             * Lies Wort für Wort.
             * Überprüfe, ob das Wort im Lexikon vermerkt wurde.
             * Wenn ja, dann folge der dort in Lautschrift notierten Aussprache.
             * Wenn nicht ... mach's wie immer, sprich so, wie es da "geschrieben" steht.*/

            int SilbenImVers = Silbenzahl(Zeile);
            int Silbe = 0;

            foreach (string Wort in Zeile.ToLower().Split(' '))
            {
                Silbe += Silbenzahl(Wort);
                if (Lexikon.Keys.Contains(Wort))
                {
                    //Sonderfall, der im Lexikon gefunden wurde:
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" + Lexikon[Wort] + @""">"// Die Lautschriftvariante
                                        + Wort + "</Phoneme>");//  gefolgt von der Graphie  
                }
                else
                {
                    // Unbekanntes Wort, ermittel den Ausspracheregeln folgend die korrekte Lautung:
                    string Lautung = FindeAussprache(Wort);
                    if (Lautung != "")
                    {
                        if (Kurzwörter.Contains(Wort))
                        {
                            if ((SilbenImVers-Silbe) % 2 == 1 && Klingend(Zeile)) Lautung = "S1 " + Lautung;
                            if ((SilbenImVers-Silbe) % 2 == 0 && !Klingend(Zeile)) Lautung = "S1 " + Lautung;
                                       
                        }
                        pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" + Lautung + @""">"// Die Lautschriftvariante
                                            + Wort + "<Phoneme>");//  gefolgt von der Graphie  
                    }
                }
            }
            pb.AppendBreak(Klingend(Zeile)?PromptBreak.ExtraSmall:PromptBreak.Small);
                   
        }

Wir sind nun vorläufig am Ende unserer selbst gestellten Aufgabe angelangt. Denn jetzt müssen wir Heddas Lesung nur noch in eine Audiodatei umleiten. Außerdem werden wir noch einige kleine Aufräumarbeiten vornehmen und uns ein paar Gedanken darüber machen, wie eine Weiterführung unseres Projektes jenseits des Tutorials aussehen könnte. Doch vorerst hören wir uns an, was wir geleistet haben:

Und weiter geht es dann im siebten und letzten Teil unseres Projektes.

Mein erstes mittelhochdeutsches Hörbuch – Teil 5

Dies ist der fünfte Teil des Tutorials. Der Beginn findet sich hier.

Die kleine Raupe Hedda 

Wie bringen wir Hedda nun dazu sich Buchstabe für Buchstabe durch den Text zu fressen?

Ganz einfach: Wir verwenden eine Schleife, die einen Buchstabenindex i bei jedem Durchlauf weiterzählt. Wir beginnen mit dem ersten Buchstaben (in C# und anderen Programmiersprachen als 0 gezählt), dann folgt der zweite (C#: 1) und so weiter … bis keine Buchstaben mehr zu zählen sind. Was dann der Fall ist, wenn  unser Index i mit der Text- oder Wortlänge identisch ist:

        public string FindeAussprache(string Wort)
        {
            string Aussprache = "";
            int i = 0; // Index des Buchstabens, an dem wir uns gerade befinden.
            while (i < Wort.Length) // Solange i kleiner als die Wortlänge ist.
            {
                // Zähle den Index i weiter
                // Schreibe die zur Wortstelle[i] passende Lautung an das Ende von Aussprache
            }            
            return Aussprache;
        }            

Der Wert des Index i erhöht sich vermutlich in den meisten Durchläufen genau um 1, um den einen Buchstaben, der ersetzt werden soll. Doch manchmal handelt es sich bei dem Buchstaben an der Stelle i um den Anfang einer Buchstabenfolge, die im Ganzen ersetzt werden muss: ein Diphthong beispielsweise, oder der Trigraph sch für das IPA-Lautzeichen [ʃ]. i wird dann um die Länge der zu ersetzenden Folge weitergezählt (also 3 im letzten Fall). An die Zeichenkette Aussprache wird in diesem Fall die der IPA-Kodierung [ʃ] entsprechende UPS-Variante [SH ] angehängt, die sich im Lexikon unter dem Stichwort [„sch“] befindet, bzw. allgemeiner und in C# formuliert:

            if (Wort.Substring(i, Buchstabenfolge.Length) == Buchstabenfolge)
            // Wenn das Wort ab der Position i bis zur Länge der Buchstabenfolge 
            // identisch mit der Buchstabenfolge ist, dann ...
            {
                Aussprache = Aussprache + LexionDerBuchstabenErsetzung[Buchstabenfolge];
                i = i + Buchstabenfolge.Length;
            }

Was wir jetzt noch brauchen, ist eine Liste aller Lexikoneinträge, bei denen das Stichwort mit dem Buchstaben beginnt, der sich an Position i in unserem Wort befindet. Um an diese zu gelangen, verwenden wir eine für spezielle Syntax (LINQ), die C# für solche Zwecke bereitstellt:

                List<string> MöglicheBuchstabenFolgen; 
                MöglicheBuchstabenFolgen = LexionDerBuchstabenErsetzung.Keys // Die mhd. Buchstabenfolgen
                                            .Where(Buchstaben => Buchstaben[0] == Wort[i])
                                         // bei denen der erste Buchstabe und der aktuelle im Suchbegriff identisch sind.
                                            .ToList();                         // als Liste.

Wir gehen von nun an davon aus, dass Buchstabenfolgen, die sich im Lexikon der Buchstabenersetzungen auffinden lassen, auch immer als diese Folgen zu lesen sind. Ein e gefolgt von einem i ist also immer als ein ei aufzufassen, die hintereinander geschriebenen Buchstaben s,c und h immer als sch. Uns ist durchaus bewusst, dass wir mit dieser Annahme Gefahr laufen, ein paar Blumento-Pferde einzufangen. Das mittelhochdeutsche Beispiel geirren haben wir schon angeführt, das selbstverständlich als ge-irren zu lesen ist und nicht als geir-ren. Doch wenn wir uns recht erinnern, haben wir ja bereits ein anderes Lexikon angelegt, in dem wir ganze mittelhochdeutsche Wörter nachschlagen und ihre Verlautung auffinden können.

Dieses Lexikon kümmert sich um die (hoffentlich!) nicht so häufigen Sonderfälle und ansonsten sollten wir im Normalfall mit unserer Verallgemeinerung zurechtkommen. Deshalb können wir die Liste der möglichen Buchstabenfolgen mit dem gesuchten Anfangsbuchstaben der Länge nach sortieren. Dann zählen wir vom längsten Eintrag zum kürzesten abwärts, solange, bis ein Eintrag in unser Wort an die Stelle i passt. Der Eintrag darf dazu nicht länger sein, als Buchstaben nach i im Wort sind und keiner seiner Buchstaben darf im Wort an entsprechender Stelle fehlen. Stimmt alles, schlagen wir im Lexikon nach, um unserer Ausgabezeichenkette Aussprache die passende Lautung anzuhängen. Die komplette Methode FindeAussprache lautet also:

        public string FindeAussprache(string Wort)
        {
            string Aussprache = "";
            int i = 0; // Index des Buchstabens, an dem wir uns gerade befinden.
            while (i<Wort.Length) // Solange i < als die Wortlänge ist.
            {
                List<string> MöglicheBuchstabenFolgen; 
                MöglicheBuchstabenFolgen = LexionDerBuchstabenErsetzung.Keys // Die mhd. Buchstabenfolgen
                                            .Where(Buchstaben => Buchstaben[0] == Wort[i]) // bei denen der erste Buchstabe und der aktuelle unseres Suchbegriffes identisch sind.
                                            .ToList();                         // als Liste.
                MöglicheBuchstabenFolgen.Sort((Folge1,Folge2)=>Folge2.Length.CompareTo(Folge1.Length)); // Sortiere die Liste absteigend nach ihrer Länge.

                /* Wir gehen die Buchstabenfolgen mit dem gesuchten Anfang,
                 * die der Länge nach, absteigend sortiert sind,
                 * eine nach der anderen durch und überprüfen,
                 * ob sie sich ganz an der Stelle i in unserem Wort finden.
                 * Wenn die erste (längste) Folge identifiziert wird,
                 * brechen wir die Suche ab und ersetzen.
                 * Andernfalls suchen wir weiter.*/
                bool Gefunden = false; // 
                foreach (string Buchstabenfolge in MöglicheBuchstabenFolgen)
                {
                    if (Wort.Length-i >= Buchstabenfolge.Length && Wort.Substring(i, Buchstabenfolge.Length) == Buchstabenfolge)
                    {
                        Aussprache += LexionDerBuchstabenErsetzung[Buchstabenfolge]; // X += Y ist X = X + Y
                        Aussprache += ' '; //nicht das Leerzeichen vergessen!
                        i +=  Buchstabenfolge.Length;
                        Gefunden = true;
                        break; // Ein Ersatz gefunden, wir suchen nicht mehr weiter!
                    }
                
                }
               if (!Gefunden) i += 1; // Unbekannter Buchstabe, zähle weiter und tue so als sei nichts geschehen.

            }
       
            return Aussprache;
        }

Jetzt wollen wir endlich austesten, was unsere Methode FindeAussprache leisten kann und deshalb ändern wir LeseZeile so ab, dass für jedes Wort, welches sich nicht als Lexikoneintrag finden lässt, FindeAussprache aufgerufen wird:

        public  void LeseZeile (string Zeile, PromptBuilder pb)
        {

            /* Los geht's Hedda!
             * Lies Wort für Wort.
             * Überprüfe, ob das Wort im Lexikon vermerkt wurde.
             * Wenn ja, dann folge der dort in Lautschrift notierten Aussprache.
             * Wenn nicht ... mach's wie immer, sprich so, wie es da "geschrieben" steht.*/

            foreach (string Wort in Zeile.Split(' '))
            {
                if (Lexikon.Keys.Contains(Wort))
                {
                    //Sonderfall, der im Lexikon gefunden wurde:
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" + Lexikon[Wort] + @""">"// Die Lautschriftvariante
                                        + Wort + "</phoneme>");//  gefolgt von der Graphie  
                } 
                else
                {
                    // Unbekanntes Wort, ermittel den Ausspracheregeln folgend die korrekte Lautung:
                    string Lautung = FindeAussprache(Wort);
                    if (Lautung !="")
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" + Lautung + @""">"// Die Lautschriftvariante
                                        + Wort + "</phoneme>");//  gefolgt von der Graphie  
                }
            }
        }

Ab jetzt liest uns Hedda Wort für Wort wirkliches Mittelhochdeutsch vor! Denn was wir zu hören bekommen, ist nicht mehr ein Hochdeutsch, in dem wir nur die in dieser Aussprache zu merkwürdigen Lautungen ersetzt haben:

Der Höreindruck erinnert uns in erschreckender Eindringlichkeit daran, dass das Lexikon der Buchstabenersetzung bisher nur eine Liste von Konsonanten, einen Vokal und drei Diphthonge enthält. Es ist Zeit, es auszubauen! Dabei kann uns Hedda eigentlich helfen, indem sie über die ihr fremden Buchstaben … ähm … Buch führt, anstatt einfach still über sie hinwegzuzählen. Wir richten ihr dazu eine Liste ein, in die sie diese Zeichen einträgt und ändern den Beginn der Klasse Hedda.cs entsprechend ab:

   class Hedda
    {
        // Die Liste der von Hedda in Text vorgefundenen unbekannten Buchstaben:
        public List<char> UnbekannteZeichen = new List<char>();

        // Ein Lexion zur Aufnahme der abweichend auszusprechenden Wortformen:
        private Dictionary<string, string> Lexikon = new Dictionary<string, string>
        {
            ["rehtiu"] = "S1 R EH C . T Y lng",
            ...

Ebenso ändern wir ihr Verhalten in FindeAussprache, wenn sie auf diese Fremdkörper trifft.
Aus:

               if (!Gefunden) i += 1; // Unbekannter Buchstabe, zähle weiter und tue so als sei nichts geschehen.

machen wir:

               if (!Gefunden)
               {
                    // Unbekannter Buchstabe
                    // Falls noch nicht in der Liste der unbekannten Zeichen,
                    // trage ihn ein.
                    if (!UnbekannteZeichen.Contains(Wort[i])) UnbekannteZeichen.Add(Wort[i]);
                    i += 1; // Zähle Weiter
               }

Diese Buchstaben kann uns Hedda selber vorlesen, denn (neuhochdeutsches) Buchstabieren beherrscht sie auch. Da sie aber nicht mit jedem Zeichen zurechtkommt, lassen wir uns die Buchstaben nach einer Anpassung von Main in Program.cs auch in Schriftform im Konsolenfenster anzeigen:

        static void Main(string[] args)
        {
            SpeechSynthesizer synth = new SpeechSynthesizer(); 
            PromptBuilder pb = new PromptBuilder();
            Hedda Hedda = new Hedda();

            //Unsere Textdatei: 
           
            StreamReader Textdatei = new StreamReader("D:/Lanzelet 1 - 49.txt");

            string Zeile;

            /* Schleife:
             * Solange bis das Ende der Textdatei erreicht ist (der Befehl ReadLine() null ergibt)
             * Lies die nächste Zeile ein
             * Und übergib sie an Hedda zum Vorlesen.
             */
          
            while (( Zeile = Textdatei.ReadLine())!=null)
            {
                Hedda.LeseZeile(Zeile, pb); 
            }
           
            synth.Speak("Ich habe folgende mir unbekannten Zeichen gefunden:");
            Console.WriteLine("Ich habe folgende mir unbekannten Zeichen gefunden:");

            foreach (char c in Hedda.UnbekannteZeichen)
            {
                Console.Write(c); // Schreibe die Hedda unbekannten Zeichen ins Konsolenfenster .
                synth.Speak(c.ToString()); // und lass Hedda die Buchstaben sprechen.   
            }
            Console.ReadKey(); //Warte auf eine Zeicheneingabe, bevor Hedda mit dem Lesen des Textes beginnt.

            synth.Speak(pb);  
        }

Hedda kann uns nur helfen, Einzelbuchstaben, die sie nicht kennt, zu identifizieren. Die von ihr angegebene Liste der fehlenden mittelhochdeutschen Schriftzeichen ergänzen wir darum direkt um die Lautungen der mittelhochdeutschen Diphthonge und anderer Mehrbuchstabenkombinationen und tragen die Lautungen in unser LexikonDerBuchstabenErsetzung ein:

Dictionary<string, string> LexionDerBuchstabenErsetzung = new Dictionary<string, string>
{
            ["î"] = "I lng",
            ["iu"] = "Y lng",
            ["ei"] = "E lng + IH",
            ["ie"] = "I lng + EH",
            ["a"]="AA",
            ["â"]="A lng",
            ["ä"]="A",
            ["ae"]="EH lng",
            ["æ"] ="EH lng",
            ["c"]="K",
            ["ch"]="X",
            ["e"]="EH",
            ["ê"]="E lng",
            ["i"]="IH",
            ["o"]="O",
            ["ô"]="O lng",
            ["oe"]="EU",
            ["œ"] ="EU",
            ["ö"]="OE",
            ["ou"]="AO + UH",
            ["öu"]="OI",
            ["ph"]="P + F",
            ["sc"]="SH",
            ["sch"]="SH",
            ["u"] = "UH",
            ["û"]="U lng",
            ["uo"]="U lng + O",
            ["ü"]="YH",
            ["üe"]="Y lng + EH",
            ["w"]="v",
            ["v"]="f",
            ["z"]="T + S",
            ["zz"]="Z"
};

Bevor wir es vergessen: Hedda führt in der Liste der von ihr nicht erkannten Buchstabenzeichen auch alle Großbuchstaben auf, die es im Textausschnitt gibt. Ob ein Buchstabe groß oder klein geschrieben wird, kann man jedoch nicht hören! Wir wandeln deshalb einfach mit ToLower() jede komplette Verszeile, bevor wir sie in Worte aufspalten, komplett in Kleinbuchstaben um. Der Beginn der Leseschleife in der Methode LeseZeile der Klasse Hedda.cs lautet danach:

           foreach (string Wort in Zeile.ToLower().Split(' '))

 

Jetzt können wir die Taste F5 betätigen und gespannt die Ohren spitzen. Diesmal ist es nun wirklich, wirklich wahr: Was aus unseren Lautsprechern ertönt, ist zwar noch nicht perfekt, aber zum ersten Mal klingt es tatsächlich nach Mittelhochdeutsch! Es sollte sogar möglich sein, zu verstehen, was Hedda vorliest:

Wenn sich unsere Euphorie ein wenig gelegt hat, werden wir spätestens beim zweiten Hören noch zahlreiche kleine „Baustellen“ entdecken. Wir könnten uns allerdings deutlich besser auf diese Probleme konzentrieren, wenn Hedda nicht ganz so atemlos durch den Text hasten würde. Wir drosseln deshalb ihr ungestümes Tempo etwas, indem wir nach jeder von ihr gelesenen Verszeile eine kleine Pause einfügen. Die Leseschleife in der Methode Main der Datei Program.cs lautet also nun:

            while (( Zeile = Textdatei.ReadLine())!=null)
            {
                Hedda.LeseZeile(Zeile, pb);
                pb.AppendBreak(PromptBreak.Small);
            }

Die Länge der Pausen haben wir relativ angegeben, es bleibt Hedda überlassen, was sie unter Small im Gegensatz etwa zu ExtraLarge versteht. Etwas mehr als eine halbe Sekunde pro Zeile braucht Hedda auf meinem Computer nun zusätzlich, um ans Ende ihres Vortrags zu gelangen, und für meine Ohren sind das (insgesamt) sehr wohltuende 27 Sekunden:

Es fällt nun leichter, die Stellen auszumachen, an denen ihr Vortrag unseren Ansprüchen noch nicht entspricht. Zwei Phänomene fallen besonders auf:

  • Für ein merkwürdiges Zischeln sorgt Heddas Aussprache eines ch (IPA: [x], UPS: [X]) nach den hellen Vokalen zum Beispiel in ich oder mich.
  • Ein z nach Vokalen, beispielsweise in daz, spricht Hedda wie in Batzen. Die Aussprache als tz (IPA: [t.s], UPS: [T + S])  sollte für den Buchstaben z im Mittelhochdeutschen aber eigentlich nur nach Konsonanten oder am Anfang eines Wortes (ze) verwendet werden. Nach Vokalen wird das z  nicht als tz gesprochen, sondern als stimmloses s (IPA: [z], UPS: [Z]).

Das Zischeln bei Heddas Aussprache eines ch nach den hellen Vokalen, scheint darauf zurückzuführen, dass sie nun mal für die Aussprache des modernen Hochdeutschen vorgesehen ist. Selbst ausgebildete Mediävisten, sofern sie mit der neuhochdeutschen Muttersprache aufgewachsen sind, müssen sich sehr konzentrieren, um den hellen Vokalen den weiter hinten am Gaumen gebildeten Laut folgen zu lassen. Wir können Heddas Akzent akzeptieren, wenn wir an sie nicht den Anspruch stellen, eine bessere Aussprache als die meisten Experten zustande zu bringen. Dann aber können wir für die beiden uns auffällig gewordenen Probleme ihrer Aussprache dieselbe Ursache feststellen: Beide Male müsste sie den Laut vor der problematischen Stelle berücksichtigen, um Fehler zu vermeiden.

Um zu erfahren, ob vor einem z ein Vokal in dem von Hedda gelesenen Wort vorkommt, können wir zum Beispiel fragen, ob dieser Buchstabe in der Zeichenkette „aouâôûiîeêöüäœæ“ enthalten ist. Diese enthält alle Buchstaben, die zur Wiedergabe von Vokalen verwendet werden. Darunter sind natürlich auch die, die in Kombination einen Diphthong ergeben. Streichen wir aus dieser Zeichenkette der Vokale diejenigen, die wir als dunkel einstufen, bleiben selbstverständlich nur die zurück, nach denen Hedda von uns aus ein UPS [C] sprechen darf. Wie ist es jedoch hier mit den Diphthongen?

Wir dürfen nicht vergessen, dass es diese Unterscheidung im Mittelhochdeutschen ja gar nicht gibt und wir nur notgedrungen ein neuhochdeutsches Phänomen dulden. Dort aber gilt, dass Diphthonge, die in einem hellen Vokal enden, auch die „helle“ Aussprache des chs herbeiführen. Wir können also die reduzierte Zeichenkette zur Abfrage prinzipiell verwenden. Mit ihr erfassen wir jedoch nicht die Buchstabenkombination iu, die ja keinen Diphthong, sondern ein langes ü wiedergibt. Und dieses ist ebenfalls als heller Vokal zu betrachten. Eine Änderung in FindeAussprache können wir dann so formulieren:

                   if (Wort.Length-i >= Buchstabenfolge.Length && Wort.Substring(i, Buchstabenfolge.Length) == Buchstabenfolge)
                    {
                        string Ersatz = LexionDerBuchstabenErsetzung[Buchstabenfolge];
                        if (Ersatz=="X")
                        {
                            if (i - 1 >= 0 && "iîeêöüäœæ".Contains(Wort[i - 1])) Ersatz = "C";
                            if (i - 2 >= 0 && Wort.Substring(i-2,2)=="iu") Ersatz = "C";
                        }

                        if (Ersatz=="T + S")
                        {
                            if (i - 1 >= 0 && "aouâôûiîeêöüäœæ".Contains(Wort[i - 1])) Ersatz = "Z"; 
                        }
                        Aussprache += Ersatz; // X += Y ist X = X + Y
                        Aussprache += ' '; //nicht das Leerzeichen vergessen!
                        i +=  Buchstabenfolge.Length;
                        Gefunden = true;
                        break; // Ein Ersatz gefunden, wir suchen nicht mehr weiter!
                    }

Wenn wir eine Buchstabenfolge oder einen Buchstaben in einem Wort identifiziert haben, zu der ein UPS-Code im LexikonDerBuchstabenErsetzung existiert, speichern wir den UPS-Code jetzt in der Zeichenkette Ersatz. Falls diese einen Wert enthält, für den wir den Kontext (= die Buchstaben davor) überprüfen müssen, tun wir das und ersetzen gegebenenfalls den Inhalt von Ersatz, bevor wir Ersatz an die Zeichenkette Aussprache anhängen.

Wir nutzen bei unserem Vorgehen erneut, dass nicht nur UPS oder IPA Lautschriften sind, sondern auch das normierte Verschriftungssystem des Mittelhochdeutschen. So können wir einen Zeichenkette aus mittelhochdeutschen Buchstaben bilden, von denen jeder einzelne ein Vokalzeichen repräsentiert, das wir abfragen können. In UPS werden für die meisten von uns abgefragten Vokale hingegen mehrere Buchstaben verwendet. Wir müssten uns also ein anderes Verfahren einfallen lassen, das vermutlich mehr Zeilen Programmcode benötigen würde. Umgekehrt haben wir in Ersatz ein Zeichen oder eine Zeichenkette in UPS-Kodierung zur Verfügung. Das nutzen wir bei der Abfrage des Lautes, der in UPS durch ein einziges [X], im mittelhochdeutschen jedoch durch zwei Zeichen ch wiedergeben wird.

Doch diese Sicht beinhaltet (noch) einen Fehler! Denn das Mittelhochdeutsche verwendet nicht nur ein ch um den UPS-Wert [X] abzubilden! Gleich im zweiten Wort unseres Textes, rehtiu, wird dieser Laut durch ein einfaches h wiedergegeben. Wir haben dieses Wort bereits früher für unsere Experimente genutzt und seit damals ist es in unserem Lexikon mitsamt der von uns ermittelten Lautung enthalten. Denjenigen, denen zu diesem Zeitpunkt bereits aufgefallen ist, dass diese Lautung eigentlich (das neuhochdeutsche ch!) nicht ganz korrekt ist, darf jetzt gratuliert werden. Nicht nur in rehtiu sondern ganz allgemein gilt, dass die Abfolge ht (Weiteres Beispiel: gevohten) stets als cht zu lesen ist, beziehungsweise je nach vorangehendem Vokal neuhochdeutsch als (in UPS-Kodierung) [X T] oder [C T]. Genauso verhält es sich bei hs (Beispiel: wehsel). Auch die Buchstabenfolgen lh (welhes) und rh (vorhten) sind als lch und rch zu lesen. Für sie gelten im Neuhochdeutschen stets UPS: [R C] und [L C]. Wir können unser LexikonDerBuchstabenErsetzung entsprechend ergänzen (die letzte Zeile einer Lexikondefinition darf nicht mit einem Komma abgeschlossen werden!):

            ["zz"]="Z", // hier ein Komma eintragen!!
            // ht,lh,rh
            ["ht"]="X T",
            ["hs"]="X T",
            ["lh"]="L C",
            ["rh"]= "R C"

Damit auch niht und wehsel ohne ein Zischen zu vernehmen sind, ändern wir eine Zeile in FindeAussprache ab.

Aus:

if (Ersatz == "X")

machen wir:

if (Ersatz[0]=='X')

Wer will kann das Lexikon übrigens noch um folgende Werte ergänzen, die Hedda ein r nach Vokalen „hochdeutscher“ aussprechen lässt:

            // deutsche r-Laute:
            ["ür"]="UYX",
            ["or"]="OWX",
            ["ör"]="OE + AX",
            ["oer"]="EU lng + AX",
            ["œr"] = "EU lng + AX",
            ["er"]="EHX",
            ["ûr"]="UAX",
            ["aer"] = "EH lng + AX",
            ["ær"] = "EH lng + AX",

Und dann hören wir uns Heddas verbesserten Vortrag an:

Wir sind jetzt beinahe zufrieden, aber bevor wir aus Heddas Lesung ein Hörbuch machen, wollen wir noch ein paar  weitere „Schönheitkorrekturen“ vornehmen, die vorrangig die Betonung betreffen.

… im sechsten Teil unseres Projektes.

 

Mein erstes mittelhochdeutsches Hörbuch – Teil 4

Dies ist der vierteTeil des Tutorials. Der Beginn findet sich hier.

Von Lautschrift zu Lautschrift

Zumindest die Mediävisten unter uns sollte es kaum erstaunen (und diese dürfen deshalb auch die nächsten Absätze bis hierhin überspringen), wenn sich die in unserem Text verwendeten Graphien 1 : 1 in eine Lautschrift übertragen lassen. Denn es handelt sich ja bereits um eine solche, ein System zur Transkription gesprochener Sprache!

Die Rechtschreibung moderner Sprachen steht oft in einem verwirrenden Verhältnis zur Lautung. Diese Verwirrung ergibt sich schon beinahe zwangsläufig aus dem Konzept der Rechtschreibung selber: Die Regelung der Schreibweisen soll verbindlich und allgemeingültig sein und sich durch eine innere Logik auszeichnen, die es leicht macht, sie zu erlernen und im Gedächtnis zu behalten.

  • Das Gebot der Verbindlichkeit führt jedoch häufig dazu, dass an Schreibungen auch dann noch festgehalten wird, wenn sich die Aussprache ändert.
  • Das Gebot der inneren Logik hingegen kann in den Widerspruch zur Lautung geraten, wenn beispielsweise an der Schreibung eines deutschen Wortes auch ablesbar sein soll, zu welchem Paradigma es gehört (vgl. Rad [r a: t] – Räder).

Es wäre ein wenig überheblich, würden wir es nun so sehen wollen, als ob die mittelhochdeutsche Schreibung geringere Ansprüche an sich selber gestellt habe. Es waren andere Ansprüche. Als Mönche im Mittelalter begannen, volkssprachliche Texte aufzuzeichnen, sahen sie ihre Aufgabe primär darin, die barbarischen Laute in den lateinischen Buchstabenzeichen so wiederzugeben, das Leser in der Lage waren, sie beim Lesen wieder in gesprochene Sprache zu verwandeln. Manchmal mussten die Schreiber dabei kreativ werden, um Töne zu verschriftlichen, die dem Lateinischen unbekannt gewesen waren. Und schon deshalb entstanden irgendwann, als die volkssprachlichen Texte häufiger wurden, auch im Mittelalter Normierungen und Konventionen, die zu nicht immer unmittelbar auf der Basis des Lateins nachvollziehbaren Schreibungen führten.

Im 19. Jahrhundert versuchten Philologen das Gestrüpp zu entwirren und entwickelten aus den Gebräuchen der mittelalterlichen Schreibstuben eine eigene, künstliche Orthographie, die sie ihren Editionen zugrundelegten. So konnten sie offensichtliche Verschreibungen korrigieren, Abkürzungen auflösen und von individuellen Marotten oder lokalen Gepflogenheiten abstrahieren. Man muss bedenken, dass viele Texte nur in sehr viel späteren Abschriften bekannt sind, und dass die Gelehrten des 19. Jahrhunderts ihre Aufgabe zuallererst darin sahen, den ursprünglichen Urtext zu rekonstruieren. Sie legten also besonderen Wert gerade auf die exakte Ermittlung und Wiedergabe der Lautung, so wie sie vom Autor (ein dem 19. Jahrhundert fast schon heiliger Begriff!) einmal gemeint war.

Der Text, den wir Hedda zum Vorlesen vorsetzen, entstammt einer solchen Edition (der Digitalisierung einer solchen Edition) und selbstverständlich ist es deshalb möglich, die dort anzutreffende Schreibung in eine (andere) Lautschrift umzusetzen. Wir müssen also einfach eine Methode FindeAussprache in Hedda.cs einfügen in der wir nach und nach die mittelhochdeutschen Graphien durch Lautzeichen in der UPS-Notation ersetzen, um ein Wort unseres Textes für Hedda aufzubereiten:

        public string FindeAussprache(string Wort)
        {
            string Aussprache = Wort;
            Aussprache = Aussprache.Replace("î", "I lng");
            Aussprache = Aussprache.Replace("iu", "Y lng");
            Aussprache = Aussprache.Replace("ei", "E lng + IH");
            Aussprache = Aussprache.Replace("ie", "I lng + EH");

            /* ...
            * Weitere Ersetzungen folgen ...
            * ... bis am Ende jede mittelhochdeutsche Graphie
            * durch ein UPS-Lautzeichen ersetzt ist
            * und Aussprache von Hedda verarbeitet werden kann.
            */

            return Aussprache;
        }

Wir erzeugen eine Kopie des Ausgangswort, die wir Aussprache nennen. Dann ersetzen wir in mehreren Anläufen Aussprache immer wieder durch eine Kopie von sich selber, in der wir alle Vorkommen jeweils eines mittelhochdeutschen Buchstaben (einer Buchstabenfolge) durch die entsprechende UPS-Notation ersetzt haben. Falls das mittelhochdeutsche Ausgangswort den zu ersetzenden Buchstaben nicht enthielt, bedeutet dies, dass die neue Kopie von Aussprache identisch mit der alten ist. Wenden wir die bisher kodierten Ersetzungsbefehle nicht auf ein einzelnes Wort, sondern zum Beispiel auf die ganze zweite Zeile des Lanzelet an, dann können wir uns diese Austauschschritte etwas besser vorstellen:

der gedenke wie ein wîse man
der gedenke wie ein w I lngse man
der gedenke wie E lng + IHn w I lngse man
der gedenke wI lng + EH E lng + IHn wI lngse man

… usw. …

Doch halt! Am jeweiligen Ende der ersten vier Ersatzbefehlen haben wir das in der UPS-Notation vorgeschriebene Leerzeichen vergessen, das Hedda braucht, um die Segmente der Lautschrift zu separieren. Das müssen wir per Hand nachtragen und dürfen es in Zukunft nicht vergessen. Gut, dass uns das jetzt aufgefallen ist. Man stelle sich vor, wir hätten bereits mühsam alle unsere Ersetzungsbefehle kodiert!

Tatsächlich müssen wir jedoch damit rechnen, dass sich noch allerlei Änderungen und Korrekturen ergeben, sobald wir erste Tests mit Heddas verbesserter Aussprache des Mittelhochdeutschen anstellen. Niemand ist schließlich perfekt. Es muss aber auch gar nicht unbedingt an unserer schlechten Planung und phonetischen Vorstellungsgabe liegen, wenn Heddas Vortrag der UPS-Zeichen nicht ganz unseren Erwartungen entspricht. Wir machen uns ja erst vertraut mit der Technologie. Auch deshalb werden wir den schon einmal nützlichen Kniff wiederverwenden und uns ein weiteres Lexikon anlegen. Diesmal werden wir darin die zu ersetzenden Buchstaben und die entsprechenden UPS-Zeichen aufnehmen. So verschaffen wir uns einen besseren Überblick, reduzieren den Schreibaufwand und sind für die späteren Korrekturen gerüstet. Dann können wir FindeAussprache entsprechend abändern:

        Dictionary<string, string> LexionDerBuchstabenErsetzung = new Dictionary<string, string>
        {
            ["î"] = "I lng",
            ["iu"] = "Y lng",
            ["ei"] = "E lng + IH",
            ["ie"] = "I lng + EH"
            // Weitere Ersetzungen folgen ...
        };
        public string FindeAussprache(string Wort)
        {
            string Aussprache = Wort;
             foreach(string Grapheme in LexionDerBuchstabenErsetzung.Keys)
            {
                /* Wir gehen das Lexikon durch und
                 * ersetzen die im Ausgangswort vorgefundenen Buchstaben
                 * durch die diesen entsprechenden Einträge.
                 * Am Ende jeder Ersetzung ergänzen wir ein Leerzeichen.
                 */
                Aussprache = Aussprache.Replace(Grapheme, LexionDerBuchstabenErsetzung[Grapheme]+' ');
            }            return Aussprache;
        } 

Durch das neue LexikonDerBuchstabenersetzung haben wir uns wohl zukünftig Tipparbeit gespart. Bis wir es gefüllt haben, werden wir trotzdem noch ganz gut zu tun haben. Und einige Ersetzungen sind ziemlich redundant, unser Lexikon müsste beispielsweise auch diesen Eintrag beinhalten:

         ["t"] = "T",

oder diesen:

         ["f"] = "F",

Jeglicher Buchstabe, „der gesprochen wird, wie man ihn schreibt“, bei dem die UPS-Notation und das System zur Wiedergabe mittelhochdeutscher Laute das gleiche lateinische Buchstabenzeichen verwenden, müsste so aufgeführt werden. Selbstverständlich kann man eine solche Aufgaben automatisieren. Und selbstverständlich werden wir das tun! Eine kleine Programmschleife wird noch vor der ersten Verwendung des Lexikons die entsprechenden Befüllungen vornehmen. Wir ergänzen unsere Klasse Hedda.cs um einen Konstruktor. So wird eine Methode genannt, die bei jeder Erzeugung einer Instanz von Hedda genau einmal aufgerufen wird und Initialisierungen vornimmt. Der Konstruktor hat stets denselben Namen, wie die Klasse selber. Hedda.Hedda füllt also unser Lexikon mit den gewünschten Einträgen:

        public Hedda()
        {
            string EinfachBuchstaben = "bdfghklmnprst";
            foreach(  char c in EinfachBuchstaben)
            {
                LexionDerBuchstabenErsetzung[c.ToString()] = c.ToString().ToUpper();
            }
        }

UPS verwendet Großbuchstaben zur Kodierung. Ersetzt werden müssen nur die Kleinbuchstaben, die UPS nicht kennt. Die haben wir in einer Zeichenkette zusammengestellt und ordnen jetzt einfach jedem kleingeschriebenen Einzelbuchstaben von EinfachBuchstaben einen Eintrag zu, der denselben Buchstaben, jetzt aber großgeschrieben, enthält. Die Liste der Buchstaben ist vielleicht noch nicht ganz vollständig (wo sind beispielsweise die Vokale?), aber wir können Lücken ja nach Bedarf schließen. Und warum übernehmen wir diese praktische Methode nicht gleich ganz zur Füllung unseres Lexikons? Beispielsweise auf diese Weise:

            string KomplexereErsetzungen = "î-I lng,iu-Y lng,ei-E lng + IH,ie-I lng + EH";
            foreach(string Ersatzpaar in KomplexereErsetzungen.Split(','))
            {
                string[] Ersatz = Ersatzpaar.Split('-');
                LexionDerBuchstabenErsetzung[Ersatz[0]] = Ersatz[1];
            }

Diesmal haben wir eine Zeichenkette erzeugt, in der wir die Lexikoneinträge durch Kommata voneinander abtrennen. Jeder Eintrag besteht wiederum aus zwei Teilen: Der mittelhochdeutschen Graphie auf der einen Seite des Bindestrichs und der UPS-Lautschrift auf der anderen. Jetzt müssen wir diese Kodierung nur wieder durch intelligente Verwendung von Split() aufspalten und können unser Lexikon so befüllen. Das erste, vom Rest durch ein Komma getrennte, Paar eines mittelhochdeutschen Buchstaben und der ihm zugeordneten UPS-Zeichen ist zum Beispiel î-I lng. Das Lexikon erhält nach dem Entpacken in der span style="font-family: courier new,courier,monospace;">foreach-Schleife den neuen Eintrag:

    ["î"] = "I lng";

Dieser Eintrag sollte bekannt vorkommen. Wer aufmerksam liest, erkennt, dass es sich bei den Ersatzpaaren in der durch Kommata getrennten Liste bisher um dieselben Einträge handelt, die wir zuvor etwas konventioneller in unser Lexikon eingetragen hatten. Man muss aber wirklich aufmerksam hinsehen, denn was wir mit dieser Methode an Tipparbeit sparen, verlieren wir an Übersichtlichkeit.

Egal auf welche Weise wir unser Lexikon der Buchstabenersetzungen auch befüllen, es gibt drei Schwierigkeiten, mit denen wir uns auseinandersetzen müssen:

  1. Wie garantieren wir die Vollständigkeit der Ersetzungen?
  2. Wie gehen wir mit mehrdeutigen Buchstabenfolgen um?
  3. In welcher Abfolge ersetzen wir?

Das erste Problem ist durch Heddas Verhalten bei unkorrekten UPS-Notationen bedingt: Wenn sie nicht jedes Lautsegment der Abfolge einer im SSML-Format kodierten Zeichenkette identifizieren kann, bricht sie ihren Versuch der Interpretation ab. Wir müssen ihr also einen Text zum Sprechen vorlegen, der nur solche Zeichen enthält, die sie umsetzen kann. Das ist gar nicht so einfach, wie es erscheinen mag. Unser kleiner Textausschnitt ist relativ leicht überschaubar. Aber was ist, wenn wir Hedda das Nibelungenlied zum Lesen präsentieren? In einem Format, möglicherweise, das auch editorische Randbemerkungen enthält, in denen Buchstaben vorkommen, die nicht zum typischen Alphabet des Mittelhochdeutschen gehören? Beispielsweise könnte die Originalschreibweise eine französischen, griechischen oder italienischen Namens angegeben werden, ein linguistischer oder literaturwissenschaftlicher Fachterminus erscheinen … Wenn wir darauf hoffen sollten, Hedda auch einen Text zum Vorlesen vorlegen zu können, den wir nicht Zeile für Zeile für sie präpariert haben (… und könnten wir dann nicht einfacher selber lesen?), wird  sie zwangsläufig irgendwann auf Buchstabenzeichen stoßen, für die wir ihr keine Ausspracheregelung vorgegeben haben.

Mehrdeutige Buchstaben und Buchstabenfolgen ergeben sich aus zwei Gründen. Zum Einen kodiert ein Zeichen manchmal mehrere Aussprachevarianten, von denen aber im jeweiligen Kontext nur eine realisiert wird. Im modernen Hochdeutschen beispielsweise verwenden wir die Buchstabenfolge ch sowohl zur Bezeichnung eines sehr weit hinten am Gaumen, fast schon im Rachen produzierten Lautes ( Beispiel: lachen IPA: [x]), als auch eines viel weiter vorne am Gaumen entstehenden Zischlautes (Beispiel: Kichern IPA: [ç]). Beide Varianten des Bigraphen werden von deutschen Muttersprachlern beim Sprechen nicht durcheinandergebracht, denn [x] tritt nur hinter den „dunklen“ Vokalen a/o/u auf, [ç] hinter den „hellen“ e/i/ä/ö/ü. Das führt dann dazu, dass sich diese Stelle in Wortpaaren wie der Fluch – die Flüche, die Rache – rächen, das Loch – die Löcher bei genauem Hinhören unterscheidet. Einige deutsche Dialekte haben übrigens die Unterscheidung ein wenig weiter getrieben: Bei ihnen rutscht das ch nach dunklen Vokalen noch tiefer in den Rachen und aus dem ch nach hellen Vokalen wird ein sch.

Doch für ein Programm, das Ersetzungen nach den Einträgen eines Lexikons vornimmt, gibt es noch eine zweite Form der Mehrdeutigkeit, sobald ein ch in seinem Ausgangstext erscheint. Schließlich beinhaltet sein Lexikon auch eine Vorschrift, was es tun soll, wenn es auf ein einzelnes c oder ein einzelnes h trifft, die ja ebenfalls in einem hochdeutschen Wort vorkommen können. Welche Regel ist nun anzuwenden?  Unser Lexikon der mittelhochdeutschen Graphien enthält bisher bereits drei Einträge für Buchstabenfolgen, für eine Abfolge mehrerer Buchstaben, die zusammen gelesen und gemeinsam durch eine UPS-Notation ersetzt werden sollen. Bei ei und ie handelt es sich (mittelhochdeutsch) um Diphthonge, also Doppellaute. Die Notation durch zwei Buchstabenzeichen bietet sich durchaus an. Es macht einen wahrnehmbaren Unterschied, ob Hedda an diesen Stellen UPS-konform EH I oder E lng + IH spricht! In einer mittelhochdeutschen Wortform wie ge-irren wäre der Diphthong ei allerdings fehl am Platz. Und iu ist gar kein Doppellaut, sondern wird als langes ü ( IPA: [y:], UPS: [Y lng]) gesprochen!

Spätestens jetzt müssen wir auch darüber nachdenken, in welcher Reihenfolge die Ersetzungen vorgenommen werden sollen. Denn Wörter wie meien, leien, reien etc. sind gar nicht so selten in mittelhochdeutschen Texten. Wird auf diese Wörter als Erstes die Ausspracheregel [„ie“] = „I lng + EX“ angewendet, dann ergibt sich als Ergebnis dieses Austauschs „meI lng + EX n„. Wenn Hedda alle Ersetzungen vorgenommen hat, sollten wir danach Folgendes hören:

Doch selbst dieses Ergebnis dürfen wir nicht unbedingt erwarten, denn die Zeichenfolge lng besteht aus l n und g. Und das sind ebenfalls mittelhochdeutsche Buchstaben, die unser Programm sonst durch die UPS-konformen L, N und G ersetzen soll, so dass das Ergebnis unserer Interpretation von meien auch M E I L N G + EX N oder M E L N G + IH EH N lauten könnte:

Es verschiedene Möglichkeiten mit diesen Problemen umzugehen. Bleiben wir allerdings dabei, unsere Ersetzungen durch eine Abfolge von Replace-Befehlen durchzuführen, wird es sehr schwierig, die Kontrolle über die Ersetzungen zu behalten und eine Abfolge auszuknobeln, die zum gewünschten Ergebnis führt. Wir sollten es nicht als Kapitulation vor diesen Schwierigkeiten auffassen, wenn wir eine andere Lösung wählen, und Hedda sich von nun an brav wie eine Erstklässlerin Buchstabe für Buchstabe durch den Text arbeiten lassen. Trifft sie dabei auf ein e, wird sie solange mit der Entscheidung, wie sie dieses e aussprechen soll warten, bis sie sich auch den nächsten Buchstaben angesehen hat. Handelt es sich bei diesem beispielsweise um ein i, dann gehören die beiden Buchstaben wohl zusammen und müssen als ei ausgesprochen werden. Wenn Hedda irgendwann einmal einen ihr völlig unbekannten Buchstaben vorfindet, überspringt sie den (Wir kümmern uns später darum, versprochen!) und liest einfach weiter. Hedda frisst sich durch den mittelhochdeutschen Text und hinterlässt hinter sich ein Spur „verarbeiteter“ lautsprachlicher Zeichen, die sie uns dann später (lesend) vorsetzt:

S V E lng R R E C T Y lng 

Das ist eine Metapher, über die wir einen Moment nachdenken müssen, bevor wir uns daran machen, sie als Programmroutine zu implementieren.

… im fünften Teil des Projektes

 

Mein erstes mittelhochdeutsches Hörbuch – Teil 3

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

Der Blick ins Lexikon hilft!

Wir wollen, dass Hedda uns einen mittelhochdeutschen Text flüssig vorliest und das in möglichst korrekter Aussprache. Um beiden Anforderungen gerecht zu werden,  blieb uns nichts anderes übrig, als den gesamten Text in Lautschrift zu übersetzen und dann im Ganzen an Hedda zur Aussprache zu übergeben. Denn als wir versuchten, den Text in Einzelpassagen zu zerlegen, die wir Hedda in unterschiedlichem Format übergaben, war die Unterbrechung und der Neuansatz bei jeder Einheit deutlich und störend zu hören. Dabei könnte es doch so einfach sein: Hedda ist für die Ausgabe moderner deutscher Texte optimiert und bei diesen hat sie keine Schwierigkeiten, sie relativ unfallfrei und sogar mit so etwas wie einer angemessenen Betonung in flüssigem Vortrag vorlesen. Trifft sie dabei auf ihr unbekannte Wörter, liest sie ohne Unterbrechung so, wie es der üblichen Aussprache deutscher Buchstaben entspricht. Da sich jedoch das Mittelhochdeutsche nicht in jeder Hinsicht vom modernen Hochdeutsch unterscheidet, könnten wir häufig ihre Voreinstellungen übernehmen und müssten nur in Einzelfällen eingreifen.  Wir bräuchten dafür nur eine Möglichkeit, Hedda einen Text im Ganzen zum Lesen vorzulegen, in dem Passagen in Lautschrift auf solche in Buchstabenschrift folgen und umgekehrt.

Tatsächlich ist das möglich. Als Prompt wird in der englischsprachigen Terminologie der speech.dll eine sprachliche Äußerung verstanden und mit der Klasse Promptbuilder gibt es die Möglichkeit, eine solche Äußerung aus elementaren und heterogenen Bestandteilen zusammenzusetzen. Probieren wir es gleich aus:

        static void Main(string[] args)
        {
            
            SpeechSynthesizer synth = new SpeechSynthesizer();
            
            PromptBuilder pb = new PromptBuilder();
            pb.AppendText("Swêr");
            pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""S1 R EH C . T Y lng"">rehtiu</phoneme>");
            pb.AppendText("wort gemerken kan,");

            synth.Speak(pb);
          
        }

 

 

Heureka! Mit dieser Technik können wir uns viel Arbeit ersparen. Eine zusätzliche Ersparnis ergibt sich durch die Verwendung eines Lexikons. In diesem vermerken wir die Sonderfälle der Aussprache für Hedda. Diese müssen wir dann nur einmal angeben und nicht bei jedem Vorkommen im Text. Wir müssen Hedda nur mitteilen, dass sie von nun an für jedes Wort im Text überprüfen soll, ob es zu diesem einen Eintrag im Lexikon der Sonderfälle gibt:

 

        static void Main(string[] args)
        {

            SpeechSynthesizer synth = new SpeechSynthesizer(); 
            PromptBuilder pb = new PromptBuilder();

            // Ein Lexion zur Aufnahme der abweichend auszusprechenden Wortformen:
            Dictionary<string, string> Lexikon = new Dictionary<string, string>
            {
                ["rehtiu"] = "S1 R EH C . T Y lng"
            };

            // unser Beispieltext:
            string Text = "Swêr rehtiu wort gemerken kan,";

            /* Los geht's Hedda!
             * Lies Wort für Wort.
             * Überprüfe, ob das Wort im Lexikon vermerkt wurde.
             * Wenn ja, dann folge der dort in Lautschrift notierten Aussprache.
             * Wenn nicht ... mach's wie immer, sprich so, wie es da "geschrieben" steht.*/

            foreach (string Wort in Text.Split(' '))
            {
                if (Lexikon.Keys.Contains(Wort))
                {
                    //Sonderfall:
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" +Lexikon[Wort]+@""">"// Die Lautschriftvariante
                                        +Wort+"</phoneme>");//  gefolgt von der Graphie
                }
                else
                {
                    pb.AppendText(Wort); // Normalfall: Hochdeutsche Aussprache.
                }
            }       

            synth.Speak(pb);  
        }
    }

 

An Heddas Vortrag hat sich nichts geändert, nur an der Art und Weise, wie er zustande kam. Deshalb sollen die Details noch einmal genauer betrachtet werden. Wir haben es diesmal Hedda, bzw. dem von uns geschriebenen Computerprogramm überlassen, den vorzulesenden Text in Wörter zu zerlegen. Wir tun das durch die Anweisung Split(' '), die der alten computerlinguistischen Faustregel folgt, dass als Wort jede Abfolge von Zeichen zwischen zwei Leerzeichen angesehen wird. Das ist selbstverständlich eine grobe Vereinfachung, die aber für unsere Zwecke erst einmal genügt. Wort für Wort schlägt das Programm dann unter den Schlüsselbegriffen mit Lexikon.Keys.Contains(Wort) im Lexikon nach und hängt gegebenenfalls den dort vorgefundenen Eintrag an das Prompt der Aussage an. Gibt es keinen solchen Eintrag, dann wird das Wort selber an Hedda zur späteren Verarbeitung weitergereicht.

Nun ist unser Lexikon mit exakt einem Eintrag nicht gerade umfangreich. Aber wir haben uns ja auch nicht die Aufgabe gestellt, nur eine einzige Verszeile in gesprochene Sprache umzuwandeln, sondern wollten uns ein ganzes Buch vorlesen lassen. Oder zumindest die ersten 50 Verse davon. Doch bevor wir daran gehen, werden wir ein wenig „aufräumen“.

Für unsere bisherigen Experimente haben wir nur ein paar Zeilen Programmiercode gebraucht, die wir in der statischen Methode Main untergebracht haben. Wir wollen jetzt aber zu komplexeren Arbeitsschritten übergehen, die Hedda erledigen soll. Und deshalb werden wir nun als nächstes eine eigene Klasse für diese Aufgaben anlegen und unserem Projekt hinzufügen. Der geben wir den Namen Hedda.cs. Dann können wir auch weiter, und jetzt mit deutlich mehr Berechtigung davon sprechen, dass wir Hedda einen Befehl geben, oder Hedda etwas im Lexikon nachschlägt:

Die automatisch generierte Klasse Hedda.cs ändern wir ab, so dass sie nun so aussieht:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Speech.Synthesis;

namespace Mittelhochdeutsches_Hörbuch1
{
    class Hedda
    {
        // Ein Lexion zur Aufnahme der abweichend auszusprechenden Wortformen:
        private Dictionary<string, string> Lexikon = new Dictionary<string, string>
        {
            ["rehtiu"] = "S1 R EH C . T Y lng"
        };

        public  void LeseZeile (string Zeile, PromptBuilder pb)
        {

            /* Los geht's Hedda!
             * Lies Wort für Wort.
             * Überprüfe, ob das Wort im Lexikon vermerkt wurde.
             * Wenn ja, dann folge der dort in Lautschrift notierten Aussprache.
             * Wenn nicht ... mach's wie immer, sprich so, wie es da "geschrieben" steht.*/

            foreach (string Wort in Zeile.Split(' '))
            {
                if (Lexikon.Keys.Contains(Wort))
                {
                    //Sonderfall:
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" + Lexikon[Wort] + @""">"// Die Lautschriftvariante
                                        + Wort + "</phoneme>");//  gefolgt von der Graphie
                }
                else
                {
                    pb.AppendText(Wort); // Normalfall: Hochdeutsche Aussprache.
                }
            }
        }
    }
}

 

Problemlos dürften sich die Elemente wiedererkennen lassen, die wir aus der Methode Main in Program.cs übernommen haben: Das Lexikon, die Schleife, in der wir Hedda die Worte der Zeile lesen lassen … Nur, dass wir diese jetzt in eine Methode der Klasse Hedda.cs, einen Befehl an Hedda, verpackt haben. Wichtig ist es übrigens, nicht die Änderung an den using – Anweisungen zu übersehen: Damit Hedda ihren Dienst tun kann, braucht auch sie die Speech.dll. „Unsere“ Methode Main müssen wir auch noch ändern, sie sieht jetzt so aus:

        static void Main(string[] args)
        {


            SpeechSynthesizer synth = new SpeechSynthesizer(); 
            PromptBuilder pb = new PromptBuilder();
            Hedda Hedda = new Hedda();

            //Unsere Textdatei: 
             
            StreamReader Textdatei = new StreamReader("D:/Lanzelet 1 - 49.txt");

            string Zeile;

            /* Schleife:
             * Solange bis das Ende der Textdatei erreicht ist (der Befehl ReadLine() null ergibt)
             * Lies die nächste Zeile ein
             * Und übergib sie an Hedda zum Vorlesen.
             */
            while(( Zeile = Textdatei.ReadLine())!=null)
            {
                Hedda.LeseZeile(Zeile, pb);
            }
            
            synth.Speak(pb);  
        }
[/csharp]

&nbsp;

<em><strong><span style="color: #ff0000;">ACHTUNG!</span></strong> Ich habe die Textdatei, die wir verwenden, auf meinem Rechner im Hauptverzeichnis von Laufwerk D: abgespeichert. Wenn sie auf einem anderen Laufwerk oder Verzeichnis gespeichert wurde, muss die Pfadangabe natürlich entsprechend angepasst werden.</em>

Und auch in <code><span style="font-family: courier new,courier,monospace;">Program.cs</span></code> müssen wir noch eine <code><span style="font-family: courier new,courier,monospace;">using</span></code>-Zeile einfügen, damit <code><span style="font-family: courier new,courier,monospace;">Main</span></code> die Datei einlesen kann:

[code language="csharp"]using System.IO;

Dann können wir uns entspannt zurücklehnen und unser erstes Hörbuch genießen …

Na?

Zufrieden?

Wie …? Wirklich?    … gar nicht?

Es ist wohl nicht zu leugnen: Wir haben noch etwas Arbeit vor uns!!!

Wir mögen damit zurechtkommen, wenn Hedda beim Vortrag einer einzelnen Verszeile, deren Text wir am Bildschirm mitlesen können, ein einziges Wort als völlig unverständliche Lautfolge umsetzt. Wenn wir sie jedoch eine längere Passage lesen lassen, ist es kaum noch möglich ihr zu folgen. Wir sollten uns jedoch vom ziemlich katastrophalen ersten Höreindruck nicht enttäuschen lassen. Schließlich enthält unser Lexikon ja bisher nur einen Eintrag! So kann es kaum eine Hilfe darstellen. Füllen wir es doch mit ein paar Einträgen und ändern die Klasse Hedda.cs entsprechend ab:

        // Ein Lexion zur Aufnahme der abweichend auszusprechenden Wortformen:
        private Dictionary<string, string> Lexikon = new Dictionary<string, string>
        {
            ["rehtiu"] = "S1 R EH C . T Y lng",
            ["gemerken"]="G EX . S1 M EH R . K EX N",
            ["gedenke"]="G EX . S1 D EH N . K EX",
            ["wie"] = "S1 V I lng + EX",
            ["ein"] = "E + I N",
            ["wîze"] = "S1 V I lng . Z EX",
            ["hie"] = "S1 H I lng + EX",
            ["bî"] = "S1 B I lng",
            ["zîten"]= "S1 TS I lng . T EX N",
            ["sît"] = "S1 S I lng T",
            ["diu"] = "D Y lng",
            ["dûhte"] = "S1 D U lng X . T EX",
            ["niht"] = "N IH C T",
            ["gemuot"] = "G EH . S1 M U lng + O T"

        };

 

Die ersten fünf Verszeilen sind nach der kleinen Änderung jetzt deutlich besser zu verstehen:

… und wenn wir uns konzentrieren, begegnen uns auch im weiteren Text einige der neuen Einträge unseres Lexikons wieder und machen diesen damit vereinzelt verständlicher.

Versuchen wir es positiv zu sehen: Wir sind auf dem richtigen Weg!

Ein Weg, der allerdings noch recht lang zu werden verspricht, wie diejenigen sicher bestätigen werden, die sich die Mühe gemacht haben, die Lexikoneinträge per Hand in Hedda.cs einzutragen und sie nicht einfach per copy & paste übernommen haben. Diesen fleißigen Tippern wird dabei jedoch vermutlich auch aufgefallen sein, dass die Umsetzung der Grapheme in Phoneme sehr regelhaft erfolgt. Als UPS-Notation der Aussprache für ein î haben wir zum Beispiel in wîze, bî, zîten und sît jeweils I lng ermittelt (IPA-Notierung: [i:]). Nach unserer bisherigen Erfahrung gilt anscheinend:

Bestimmten Buchstaben (-folgen) entsprechen stets und immer wieder bestimmte (Abfolgen von) Lautzeichen.

Wir werden versuchen aus dieser Beobachtung Nutzen zu ziehen, um uns weitere (unnötige) Tipparbeit zu ersparen. Und damit beginnen wir umgehend …

… im vierten Teil unseres Projektes.

Mein erstes mittelhochdeutsches Hörbuch – Teil 2

Dies ist der zweite Teil des Tutorials (daher heißt es auch Teil 2 im Titel). Der erste Teil findet sich hier.

Hedda spricht (eine Zeile) Mittelhochdeutsch!

Wir legen los und in Visual Studio ein neues Projekt an:

Aus dem Auswahlmenü wählen wir die Konsolen-App (Net-Framework), der wir einen sprechenden Namen geben. Wie wäre es mit „Mittelhochdeutsches Hörbuch1“? Unter welchem Namen auch immer, das Projekt wird erzeugt und uns die Datei Program.cs präsentiert:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Mittelhochdeutsches_Hörbuch1
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

Bevor wir unseren Computer zum Sprechen bringen können, müssen wir noch die zur Spracherzeugung benötigte Bibliothek einbinden. Dazu öffnen wir den Reiter „Projekt/Verweis hinzufügen“.

In den Assemblys aktivieren wir „System.Speech“ mit einem Häkchen:

Dann ändern wir die Ausgangsdatei (per copy & paste oder abtippen) folgendermaßen ab und starten mit F5 unser erstes Experiment:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Speech.Synthesis;

namespace Mittelhochdeutsches_Hörbuch1
{
    class Program
    {
        static void Main(string[] args)
        {            
            SpeechSynthesizer synth = new SpeechSynthesizer();
            synth.Speak("Swêr rehtiu wort gemerken kan,");            }
        }
    }
}

 

Jetzt sollten wir eine Stimme hören, die mit der erwarteten falschen Aussprache unseren Text spricht. Es ist gut möglich, dass diese Stimme uns unbekannt ist und nicht derjenigen entspricht, die wir von der Windows-Sprachausgabe gewohnt sind. Auf meinem Rechner ist es jedenfalls eine weibliche Stimme und damit definitiv nicht Stefan, die ich höre. Der Grund hierfür ist, dass Stefan dafür vorgesehen ist, Deutsch mit mir zu sprechen und anscheinend nur dazu in der Lage ist. Ihm fehlt die Möglichkeit, sich an ein anderes Idiom anzupassen. Etwas technischer formuliert: Das Sprachprofil, das mit dem Namen Stefan verbunden wird, bietet nicht alle Möglichkeiten, die durch die Programmierschnittstelle System.Speech.dll vorgesehen sind, und steht uns damit nicht zur Verfügung. Oder vielmehr: mir, zum Zeitpunkt, an dem ich dies schreibe. Denn selbstverständlich kann sich das mit dem nächsten Windows-Update ändern oder mit der nächsten Generation der Speech-Schnittstelle.

Wir können uns, wenn wir wollen, beim nächsten Durchlauf unseres kurzen Programms die uns zur Verfügung stehenden Stimmen vorstellen lassen, indem wir unsere statische Methode Main leicht verändern:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Speech.Synthesis;

namespace Mittelhochdeutsches_Hörbuch1
{
    class Program
    {
        static void Main(string[] args)
        {
            
            SpeechSynthesizer synth = new SpeechSynthesizer();

            foreach (var voice in synth.GetInstalledVoices())
            {
                synth.SelectVoice(voice.VoiceInfo.Name);
                synth.Speak(voice.VoiceInfo.Description);
                synth.Speak("Swêr rehtiu wort gemerken kan,");            }
            }
        }
     }
}

 

Mir stehen eine weibliche Stimme mit dem Namen Hedda zur Verfügung, die deutsch spricht, und eine Amerikanerin namens Sira. Ob es sich bei der um eine Verwandte von Apples ureigener Siri handelt? Und wer hat der „Deutschen“ den so typisch deutschen Namen Hedda verpasst? Die Auswahl der Stimmen ist sehr begrenzt, aber zumindest durch die Namensgebung für einen kleinen nerdigen Lacher gut.  Wer wie ich mit Hedda leben muss (Nein, Sira kann ich mir nicht als Vorleserin der Aventiure vorstellen), mag vielleicht mit der Option käuflicher Stimmenprofile liebäugeln, aber eigentlich reicht ja eine Stimme für unser Vorhaben.

Sofern wir sie dazu bringen können, phonetisch korrektes Mittelhochdeutsch von sich zu geben.

Hedda sollte also eine Lautschrift verstehen, beziehungsweise in der Lage sein, einen in dieser transkribierten Text angemessen wiederzugeben. Genau das verspricht uns die Speech.dll, wenn sie Hedda unter den installierten – und der Norm gerechten –  Stimmen aufführt. Und genau das werden wir jetzt austesten. Wenn wir es (vorerst) nicht ganz so kritisch sehen wollen, gibt es in dieser ersten Zeile unseres Textes ja nur ein Wort, das wirklich so falsch klingt, dass wir handeln müssen: rehtiu. Die anderen Wörter sind in ihrer hochdeutschen Aussprache nicht perfekt, aber, wenn wir ein paar Augen zudrücken, halbwegs akzeptabel. Konzentrieren wir uns auf rehtiu und versuchen Hedda beizubringen, das etwas besser zu artikulieren:

 

        static void Main(string[] args)
        {
            
            SpeechSynthesizer synth = new SpeechSynthesizer();
            string ssmlRehtiu =  @"<speak version=""1.0"" xml:lang=""de-DE"">"
                                + @<phoneme alphabet=""ups"" ph=""S1 R EH C . T YH lng"">"
                                + "Rehtiu</Phoneme></speak>";
            synth.SpeakSsml(ssmlRehtiu);
        }

 

 

Das klingt doch schon besser, oder? Was wir hier benutzen, ist der SSML-Standard, ein XML-Codierungsschema zur Steuerung von Sprachausgaben (Speech Synthesis Markup Language). Wenn wir eine dafür passendere Formatierung wählen sieht das ungefähr so aus:

   <speak version="1.0" xml:lang="de-DE">
       <phoneme Alphabet="ups" ph="S1 R EH C . T YH lng"&>rehtiu</Phoneme>
   </speak>

 

Das <speak>-Tag klammert eine Sprachausgabe ein. Innerhalb des <phoneme>-Tags finden wir unseren Ausgabetext wieder, so wie wir ihn kennen („kennen“ heißt, in der Buchstabendarstellung). Die Lautschriftfassung findet sich im Attribut ph = „S1 R EH C . T YH lng“. Es handelt sich um das Lautschriftsystem UPS, eine Microsoft-Eigenkreation. Dass wir dieses Kodierungssystem verwenden, müssen wir natürlich auch angeben: alphabet = „ups“. Im <speak>-Tag vermerken wir noch (verpflichtend!) die Version des Sprachstandards auf die wir uns beziehen und die Sprache, an der sich unsere Hedda orientieren soll. „de-DE“ steht selbstverständlich für das in Deutschland gesprochene Deutsch (im Gegensatz zum Schweizerischen beispielsweise).

Soweit so gut. Hedda ist in der Lage Text in einer Lautschrift zu verstehen. Tatsächlich sind es sogar drei Lautschriften, allen voran das internationale phonetische Alphabet (IPA). Dieses besteht bekanntlich in seinem Kern aus den lateinischen Buchstaben, die um einige Graphien ergänzt wurden, die besonders Altphilologen gut vertraut sind und normalerweise in der Sprache, der sie entnommen wurden, Laute repräsentieren, die es im Lateinischen nicht gab. Viele dieser Zeichen finden sich nicht auf der Standardtastatur und nicht im Standard-ASCII-Zeichensatz. Beispielsweise der ⁠ʃ⁠-Laut in hübsch: [‚hyp⁠ʃ⁠]). Aus diesem Grund gibt es die beiden anderen Varianten der Lautkodierung, die Hedda versteht. Für Computerprogramme leicht zu verarbeiten ist die SAPI-ID (SAPI = Speech API), ein System, das jedem der IPA-Zeichen einen Zahlenwert zuweist: hübsch wird als [02C8 0068 028F 0283] erfasst. Uns kommt das weniger entgegen und deshalb haben wir uns für UPS entschieden, um mit Hedda zu kommunizieren.

UPS nutzt nur Zeichen, die auf der Tastatur (und im ASCII-Code) vorkommen. Häufig, aber nicht immer, entsprechen auch hier einzelne Buchstaben den lateinischen Lautwerten (wenn auch eher in der englischen Aussprache). Komplexere oder (vom englisch getönten Latein her betrachtet) ungewöhnlichere Laute werden durch Buchstabenkombinationen (EH AX etc.) wiedergegeben, die jeweils eine Einheit der Lautzeichenkette repräsentieren. Um die Segmente deutlich voneinander zu trennen muss ein Leerzeichen zwischen ihnen eingefügt werden.

ACHTUNG! Hedda, bzw. die Speech.dll, ist in dieser Hinsicht ausgesprochen pingelig! Fehlt das Leerzeichen, und lässt sich kein einzelner Lautwert finden, der einer Buchstabenfolge zugeordnet werden kann, bricht die Sprachausgabe ab!

In unserem Beispiel findet sich unter anderem auch die Kodierung lng. Sie gibt keinen Lautwert wieder, sondern zeigt an, dass der vorangehende Laut lang gesprochen werden soll (IPA verwendet den Doppeltpunkt [:] zur Bezeichnung der Länge). Auch solche Signale müssen durch Leerzeichen von ihren Nachbarn getrennt werden. Die weitere Erläuterung der Symbole der Lautschrift erspare ich uns vorerst. Sie findet sich schließlich ausführlich auf den Supportseiten von Microsoft dokumentiert.

Machen wir erst einmal weiter: Wir lassen Hedda den Beginn der Zeile sprechen und akzeptieren ihr modernes Hochdeutsch als Mittelhochdeutsch, fügen dann das problematische rehtiu in Lautschrift ein, und setzten danach den Text fort – jetzt wieder Neuhochdeutsch-Pseudomittelhochdeutsch:

 

        static void Main(string[] args)
        {
            
            SpeechSynthesizer synth = new SpeechSynthesizer();
            string ssmlRehtiu =  @""
                                + @""
                                + "rehtiu";
            synth.Speak("Swêr");
            synth.SpeakSsml(ssmlRehtiu);
            synth.Speak("wort gemerken kan,");
        }

 

Das
hört sich
jetzt
sehr
abgehackt an. Vielleicht sollten wir doch gleich die ganze Zeile in phonetischer Schreibweise erfassen. Dann brauchen wir auch nicht so viele faule Kompromisse einzugehen:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Speech.Synthesis;

namespace Mittelhochdeutsches_Hörbuch1
{
    class Program
    {
        static void Main(string[] args)
        {
            
            SpeechSynthesizer synth = new SpeechSynthesizer();

            foreach (var voice in synth.GetInstalledVoices())
            {
                synth.Speak(voice.VoiceInfo.Description);
                string phoneme = " S1 S V EH lng R . "
                                +"S1 R EH C . T YH lng . "
                                +"S1 V AOX T . "
                                +"G EH . S1 M EX R . K EX N . "
                                +"S1 K A N _,";

                string grapheme = "Swêr rehtiu wort gemerken kan,";
                synth.SpeakSsml(@""
                                +grapheme+"");
            }
        }
    }
}

Wenn wir also unseren Text einfach Zeile für Zeile in Lautschrift umsetzen und diese dann zu einer sehr, sehr, seeeehr lange Zeichenkette zusammensetzen … die wir dann natürlich noch zwischen die SSML-tags quetschen …

Nein. Das ergibt selbst dann kaum entzifferbare Bandwurmkonstruktionen, wenn  wir Hedda eine Verschnaufpause am Ende jeder Zeile zugestehen, wo es nicht so auffällt. Und überhaupt wäre es letztlich deutlich einfacher, den Text direkt selber einzusprechen. Wir müssen und werden einen Weg finden, der mit weniger Aufwand zum Ziel führt.

… im dritten Teil des Projektes.

Mein erstes mittelhochdeutsches Hörbuch – Teil 1

Einleitung: Stefan liest

Nicht jede Lesung, nicht jede musikalische Interpretation eines mittelhochdeutschen Gedichts, die sich auf YouTube finden lässt, genügt wissenschaftlichen germanistischen Ansprüchen, aber wenn es nur darum geht, einmal einen Eindruck vom Klang der mittelhochdeutschen Sprache zu erhalten, dann wird man dort schnell fündig. Wer allerdings auf das Hören als Ersatz für das Sehen angewiesen ist, oder einfach nur den Wunsch verspürt, einen längeren mittelhochdeutschen Text anzuhören, anstatt ihn zu lesen, wird nicht nur auf den einschlägigen Plattformen vergeblich nach „Material“ suchen.

Das im folgenden Artikel vorgestellte Projekt versucht ein wenig Abhilfe zu schaffen. Es soll selbstverständlich nicht darum gehen, Hilfestellung bei der Einrichtung eines eigenen Tonstudios oder bei der Aufnahme eigener Lesungen zu geben. Nein, wir beabsichtigen, unseren Computer dazu zu bringen, uns einen mittelhochdeutschen Text vorzulesen und zwar so, dass wir ihn gut verstehen und seine Aussprache auch Fachleuten für das Mittelhochdeutsche keine Schmerzen verursacht. Die Ausgabe leiten wir dann in eine Audio-Datei, damit wir unser Hörbuch auf jedem uns genehmen Gerät abspielen können.

Das scheint auf den ersten Blick gar nicht so schwer zu bewerkstelligen: Schließlich lässt sich jedes Handy und jeder übliche Browser dazu bewegen, Texte vorzulesen. Dazu muss man allerdings teilweise eine zusätzliche App oder ein Add-On installieren. Das Betriebssystem eines Laptop- oder Desktop-Computer lässt sich natürlich ebenfalls um die Fähigkeit zur Sprachsynthese erweitern, wenn es diese nicht bereits von Haus aus mitbringt. Wer diesen Text beispielsweise auf einem Windows Rechner vor sich sieht, muss nur die  Strg-, die Windows– und die Enter-Taste gemeinsam betätigen, um die automatische Sprachausgabe zu aktivieren. Die auf meinem Rechner zu hörende Stimme von Windows10-Stefan klingt zwar manchmal etwas blechern, aber doch im Ganzen gut verständlich:

Ein ritter sô gelêret was,
daz er an den buochen las,
swaz er dar an geschriben vant:
der was Hartman genant,
dienstman was er zOuwe.

Nun ja, ein paar Worte hören sich etwas exotisch an  – ab und zu scheint Stefan bei fremdartigen Schreibungen zu vermuten, dass sie wohl englisch ausgesprochen werden müssen. Andere Wörter hingegen klingen viel zu vertraut. Kein Wunder! Stefan spricht und liest modernes Hochdeutsch und ahnt ja nicht, dass er in „dienstman“ einen Diphthong von sich geben soll. Probieren wir es gleich mit einem anderen Text als dem Armen Heinrich (Natürlich erkannt, oder?). Wie wäre es beispielsweise mit der Einleitung des Lanzelet des Ulrich von Zatzikhoven, eines ungefähren Zeitgenossen des gelehrten Ritters Hartmann von Aue?

Swer rehtiu wort gemerken kan,
der gedenke wie ein wîse man
hie vor bî alten zîten sprach,
dem sît diu welt der volge jach.

Das … klingt, zumindest von Stefan gelesen, schon weit weniger akzeptabel für mich. Jemand müsste Stefan ein paar Lektionen in der Aussprache mittelhochdeutscher Worte erteilen, bevor ich bereit wäre mir von ihm die fast 9500 Verse der an sich recht spannenden (und actionreichen!) Erzählung vom Artushof vorlesen zu lassen.

Dieser Gedanke soll der Ausgangspunkt für das heutige Projekt sein. Es gibt neben kommerziellen Angeboten zahlreiche Open-Source-Ansätze zur Spracherzeugung und -ausgabe, die Schnittstellen zur individuellen Anpassung und Programmierung bieten. Aber wir bleiben vorerst in der Microsoft-Umgebung und werden unsere Lösung in C# im Visual-Studio umsetzen. Größere Programmierkenntnisse brauchen wir dafür nicht und das ist der Hauptgrund für unsere Wahl. Zudem können wir uns in diesem Fall darauf verlassen, dass die Technologie stabil und fehlerfrei auf allen Rechnern läuft, auf denen Windows stabil und fehlerfr … Naja … auf denen halt …

Wer noch nie mit Visual-Studio oder C# gearbeitet hat, findet bei Verständnisfragen in reichem Maße Hilfe auf den Supportseiten von Microsoft. Dort kann er auch die (für private Zwecke) kostenlose Community-Version der Entwicklungsumgebung herunterladen:

Zum Download von Visual-Studio 2017

Als Übungstext verwenden wir nicht den gesamten Lanzelet-Roman, sondern beschränken uns vorerst bescheiden auf die ersten 49 Zeilen, die wir per copy & paste der Textfassung der Bibliotheca Augustana entnommen haben:

Lanzelet Verse 1 – 49

In Windows 10 per Rechtsklick das Kontextmenü öffnen und „Ziel speichern“ wählen, um den Text auf den eigenen Computer herunter zu laden und dort zu speichern. Jetzt ist alles vorbereitet. Wir können loslegen …

… und zwar im Teil 2.

Wer allerdings jetzt schon wissen will, was auf ihn zukommen wird, kann jetzt auch erst einen Blick auf den Tutorial-Überblick werfen:

Mein erstes mittelhochdeutsches Hörbuch – Teil 1 weiterlesen