Wie strukturiert man Texte sinnvoll, und wie findet man gezielt bestimmte Muster in großen Datenmengen? In diesem Workshop lernst du den praktischen Einsatz von XML und regulären Ausdrücken — z.B. zur Analyse von Logfiles oder zur Prüfung von Texteingaben. Am Beispiel von TEI-XML zeigen wir, wie man Textdaten in den Digital Humanities professionell enkodiert. Als konkretes Anwendungsszenario vereinheitlichen wir die Schreibvarianten von Personen- und Ortsnamen, um diese trotz der vorhandenen Variation denselben Personen beziehungsweise Orten zuordnen zu können. Zum Beispiel wird der Name des Komponisten Georg Friedrich Händel aufgrund seines Wirkens in England oft zu „George Frideric Handel“ anglisiert, während die Stadt London in vielen romanischen Sprachen als „Londres“ geschrieben wird.
Konkret lernen wir:
Aufgrund ihrer gemeinsamen Herkunft haben HTML und XML viele Gemeinsamkeiten:
<Elementname>Inhalt</Elementname>,
oder inhaltslos mit einem Leertag vorkommen, wie z.B. <Elementname/>.<Elementname Attribut="Wert">Inhalt</Elementname>.<Element1><Element2>Inhalt</Element2></Element1>.<!-- Kommentar -->.Jedoch gibt es einige wesentliche Unterschiede:
Einfach mal ausprobieren 🎛️
test.xml.Beauty-Tipp 💅
Visual Studio Code kann den Quellcode automatisch formatieren, sodass die Einrückung der Hierarchie der Elemente entspricht. Dieser Vorgang wird im Fachjargon prettify genannt. Unter „Anzeige > Befehlspalette…“ ist der Befehl „Auswahl formatieren“ zu finden, der dies bewirkt. Alternativ kann auch der Tastaturbefehl „Ctrl/Cmd K Ctrl/Cmd F“ verwendet werden.
Wir bedienen uns der Kurzgeschichte „Kleine Fabel“ von Franz Kafka (1920) als konkretes Anwendungsbeispiel. Die Version von Projekt Gutenberg ist wie folgt in HTML erfasst:
<h3 class="title">Kleine Fabel</h3>
<p>»Ach«, sagte die Maus, »die Welt wird enger mit jedem Tag. Zuerst war sie so breit,
daß ich Angst hatte, ich lief weiter und war glücklich, daß ich endlich rechts und links
in der Ferne Mauern sah, aber diese langen Mauern eilen so schnell aufeinander zu,
daß ich schon im letzten Zimmer bin, und dort im Winkel steht die Falle, in die ich laufe.«
– »Du mußt nur die Laufrichtung ändern«, sagte die Katze und fraß sie.</p>Diese Version lässt sich in einem Texteditor wie Visual Studio
Code lokal speichern (z.B. als Kafka_Kleine_Fabel.html)
und verursacht dort keine Fehlermeldung. Auch lässt sich die Datei in
einem Browser öffnen und darstellen.
Denkpause 🤔
Kafka_Kleine_Fabel.xml
umbenennen, um sie dann in Visual Studio Code zu öffnen?Das XML-Format erwartet eine strikte Einhaltung eines einzelnen
Wurzelelements. Deshalb müssen die Elemente <h3> und
<p> in
einem zusätzlichen Element eingebettet werden. Dadurch verschwindet die
Fehlermeldung. Wie das Element heißt, ist im Prinzip unwichtig, da XML
erweiterbar ist. Aus diesem Grund ist es auch kein Hindernis, dass
ursprüngliche HTML-Tags verwendet werden.
Nun können wir den Text mit zusätzlichen Informationen bereichern, wie z.B.:
<xml>
<titel>Kleine Fabel</titel>
<p><rede figur="Maus">»Ach«</rede>, sagte die Maus, <rede figur="Maus">»die Welt
wird enger mit jedem Tag. Zuerst war sie so breit, daß ich Angst hatte, ich lief
weiter und war glücklich, daß ich endlich rechts und links in der Ferne Mauern sah,
aber diese langen Mauern eilen so schnell aufeinander zu, daß ich schon im letzten
Zimmer bin, und dort im Winkel steht die Falle, in die ich laufe.«</rede> –
<rede figur="Katze">»Du mußt nur die Laufrichtung ändern«</rede>, sagte die
Katze und fraß sie.</p>
</xml>Wir haben nun ein valides XML-Dokument, jedoch haben wir bei der Wahl unserer Tags etwas improvisiert. Prinzipiell dürfen wir das auch, doch bei dieser Vorgehensweise wird bei jedem Ansatz das Rad neu erfunden und Projekte sind untereinander nicht kompatibel. Daher gibt es, je nach Anwendungszweck und Fachrichtung, mehrere XML-Schemata, die gewisse Standards vorgeben, wie z.B.:
Von nun an verwenden wir das Schema TEI-XML, indem wir folgende Zeile am Anfang unserer Datei einfügen:
<?xml-model href="https://tei-c.org/release/xml/tei/custom/schema/relaxng/tei_all.rng"
schematypens="http://relaxng.org/ns/structure/1.0"?>Damit können Visual Studio Code und dessen installierte XML-Erweiterung auf die Vorgaben des TEI-XML Schemas zugreifen, wie sie auf der Webseite des TEI-Konsortiums im RelaxNG-Format (REgular LAnguage for XML Next Generation) veröffentlicht sind. Dadurch erscheinen Fehlermeldungen, da unser bisheriges „Freestyle“ XML nicht den Vorgaben von TEI-XML entspricht. Eine detaillierte Dokumentation von TEI-XML befindet auf der Seite https://tei-c.org/release/doc/tei-p5-doc/en/html/index.html. Als Änderungen müssen wir minimal folgendes unternehmen:
<TEI> zu
heißen und muss anhand eines bestimmten Attributs auf die TEI-Webseite
verweisen: <TEI xmlns="http://www.tei-c.org/ns/1.0">.<teiHeader>
zu stehen, welches wiederum bestimmte Pflichtangaben zu enthalten hat.
In Visual Studio Code besteht die Möglichkeit, auf die
Glühbirne zu klicken, um die nötigen Elemente einzufügen, die da wären:
<text> zu
stehen, wobei der Titel innerhalb des Elements <front>
stehen sollte, und der Haupttext innerhalb des Elements <body>.<titel> und
<rede>
sind nicht zulässig und müssen ersetzt werden:
<docTitle>,
in dem wiederum Titelteile als Elemente namens <titlePart>
gelistet werden sollen.<said who="">
markiert werden (wobei es hier mehrere korrekte Methoden gibt).Nach diesen Korrekturen haben wir folgendes TEI-XML Dokument, das keine Fehler mehr vorweist:
<?xml-model href="https://tei-c.org/release/xml/tei/custom/schema/relaxng/tei_all.rng"
schematypens="http://relaxng.org/ns/structure/1.0"?>
<TEI xmlns="http://www.tei-c.org/ns/1.0">
<teiHeader>
<fileDesc>
<titleStmt>
<title>Kleine Fabel</title>
</titleStmt>
<publicationStmt>
<publisher>Projekt Gutenberg-DE</publisher>
</publicationStmt>
<sourceDesc>
<p>Kurzgeschichte von Franz Kafka</p>
</sourceDesc>
</fileDesc>
</teiHeader>
<text>
<front>
<docTitle>
<titlePart>Kleine Fabel</titlePart>
</docTitle>
</front>
<body>
<p><said who="Maus">»Ach«</said>, sagte die Maus, <said who="Maus">»die Welt
wird enger mit jedem Tag. Zuerst war sie so breit, daß ich Angst hatte, ich
lief weiter und war glücklich, daß ich endlich rechts und links in der Ferne
Mauern sah, aber diese langen Mauern eilen so schnell aufeinander zu, daß
ich schon im letzten Zimmer bin, und dort im Winkel steht die Falle, in die
ich laufe.«</said> – <said who="Katze">»Du mußt nur die Laufrichtung
ändern«</said>, sagte die Katze und fraß sie.</p>
</body>
</text>
</TEI>Einige genauere Angaben, die über die reine Fehlerkorrektur hinausgehen, können wir noch machen. Und zwar:
<idno>.<seriesStmt>.<author>
innerhalb eines <bibl>
Tags.<text>
Tag.<titlePart>
deklarieren.Entsprechend dieser zusätzlichen Angaben sieht unser Dokument nun so aus:
<?xml-model href="https://tei-c.org/release/xml/tei/custom/schema/relaxng/tei_all.rng"
schematypens="http://relaxng.org/ns/structure/1.0"?>
<TEI xmlns="http://www.tei-c.org/ns/1.0">
<teiHeader>
<fileDesc>
<titleStmt>
<title>Kleine Fabel</title>
</titleStmt>
<publicationStmt>
<publisher>Projekt Gutenberg-DE</publisher>
<idno type="URI">
https://www.projekt-gutenberg.org/kafka/misc/chap002.html
</idno>
</publicationStmt>
<seriesStmt>
<title>Erzählungen II</title>
</seriesStmt>
<sourceDesc>
<bibl>
<author>Franz Kafka</author>
</bibl>
</sourceDesc>
</fileDesc>
</teiHeader>
<text xml:lang="de">
<front>
<docTitle>
<titlePart type="main">Kleine Fabel</titlePart>
</docTitle>
</front>
<body>
<p><said who="Maus">»Ach«</said>, sagte die Maus, <said who="Maus">»die Welt
wird enger mit jedem Tag. Zuerst war sie so breit, daß ich Angst hatte, ich
lief weiter und war glücklich, daß ich endlich rechts und links in der Ferne
Mauern sah, aber diese langen Mauern eilen so schnell aufeinander zu, daß
ich schon im letzten Zimmer bin, und dort im Winkel steht die Falle, in die
ich laufe.«</said> – <said who="Katze">»Du mußt nur die Laufrichtung
ändern«</said>, sagte die Katze und fraß sie.</p>
</body>
</text>
</TEI>Beauty-Tipp 💅
Der Text kann deutlich lesbarer gemacht werden, in dem wir einen
Darstellungsstil definieren. Dies geschieht am einfachsten mit dem
Format CSS (Cascading Style Sheets), mit dem gewöhnlich
Webseiten gestylt werden. Wir erstellen eine Datei namens style.css und
verweisen am Anfang unseres XML-Dokuments vor dem Wurzelelement <TEI>
darauf:
<?xml-stylesheet href="./style.css" type="text/css"?>Nun können wir in der CSS-Datei den Anzeigestil definieren, in dem wir z.B. eine Schriftart für das gesamte Dokument auswählen, den Titel größer darstellen als den Haupttext, und die Metadaten ausblenden.
text {
font-family: Georgia, 'Times New Roman', Times, serif;
}
docTitle {
font-size: 36pt;
display: block;
}
p {
font-size: 16pt;
display: block;
}
teiHeader {
display: none;
}Die XML-Datei kann mit einem Webbrowser geöffnet werden, um den Inhalt dem CSS-Stil entsprechend anzusehen.
Die konsequente Annotation eines Dokuments in einem gängigen XML-Schema (wie z.B. TEI-XML) erleichtert sowohl qualitative als auch quantitative Analysen und macht die erstellten Dateien zugänglicher für andere Forscherinnen und Forscher.
Für eine qualitative Analyse können wir den letzten Beauty-Tipp zu
etwas Nützlicherem erweitern. Die zwei Figuren der Kurzgeschichte können
farblich hervorgehoben werden, in dem wir in der CSS-Datei den Werten
des Attributs im Tag <said who="WERT">
Hintergrundfarben zuordnen.
said[who="Maus"] {
background-color: orange;
}
said[who="Katze"] {
background-color: lightblue;
}Für eine quantitative Analyse, wie z.B. dem automatischen Zählen von Wörtern der unterschiedlichen Figuren, bedarf es in der Regel fortgeschrittener Methoden, die Programmierkenntnisse voraussetzen. Empfohlene Tools für die Analyse von XML-Dateien sind Beautiful Soup für Python sowie xml2 für R. Diese werden wir aufgrund ihrer Komplexität hier nicht besprechen. Siehe jedoch den Bonusabschnitt 10 als Ausblick. Anstelle dessen befassen wir uns mit einer grundlegenderen Methode, nämlich den regulären Ausdrücken. Mit diesen können wir nicht nur rudimentär XML-Dateien analysieren, sondern auch effektiver bearbeiten.
Reguläre Ausdrücke erlauben flexible und komplexe Suchanfrage dank einer Reihe von Sonderzeichen, die als Wildcards und Quantoren fungieren. Eine kurze Einführung (auf Englisch) befindet sich hier: https://anglistik-toolbox.uni-mannheim.de/app/searchtools/#regular-expressions
Bevor wir reguläre Ausdrücke in unserer XML-Datei anwenden, sollten wir deren Anwendung anhand einfacher Beispiele üben.
Wir können den Quelltext der verlinkten Seiten anhand mehrerer Methoden herunterladen, um darin zu suchen:
curl -o Vornamen_P.html https://de.wikipedia.org/wiki/Liste_von_Vornamen/PNun können wir mit folgenden regulären Ausdrücken unsere Übungsaufgaben lösen:
(?:F|Ph)[ie]l+ip+e?\b: Beginnend
mit F oder Ph, gefolgt von einem i oder
e, mit einem oder mehreren l, mit einem oder mehreren
p, und am Ende optional ein e. Zur Erläuterung: Die
Zeichenkette ?: in den Klammern
erzeugt eine Gruppierung ohne Rückwärtsreferenz (eng. non-capturing
group). Da wir auf den Inhalt der Klammern nicht zurückgreifen
werden, ist es die beste Praxis, eine Rückwärtsreferenz zu
vermeiden.Paul\w+: Dem Text Paul
sollen noch mindestens ein Wortzeichen folgen.In unserem bisherigen Text können wir die alte Rechtschreibung
normalisieren. Die Regeln von TEI-XML sehen dafür das Element <choice>, in
dem <orig> die
Schreibweise aus dem Original beinhaltet und <reg> die
heutige Standardschreibweise beinhaltet. Z.B. können damit Verbindungen
der alten Schreibweisen mußt und daß zu den aktuellen
Varianten musst und dass hergestellt werden. In einem
längeren Text wäre das Einfügen der XML-Elemente mit viel Handarbeit
verbunden. Anhand von regulären Ausdrücken kann dies mit einem Befehl
zum Suchen und Ersetzen von Text erfolgen.
Denkpause 🤔
mußt → <choice><orig>mußt</orig><reg>musst</reg></choicedaß → <choice><orig>daß</orig><reg>dass</reg></choice() Klammern erzeugen und beim Ersetzen
wiedergeben. Dabei ist zu beachten, dass Visual Studio Code
hierfür das Format $1 erwartet,
statt \1 wie in anderen „Dialekten“
regulärer Ausdrücke.(\w+)ß(\w+)?.(mu|da)ß(\w+)?.(mu|da)ß(\w+)? → <choice><orig>$1ß$2</orig><reg>$1ss$2</reg></choice>.Anhand regulärer Ausdrücke können wir die durch Annotation vermerkte
Information konsequent auswerten. In der Regel geschieht dies unter
Verwendung von Programmiersprachen, jedoch können wir auch mit
rudimentären Methoden ähnliche Ergebnisse erzielen. Wenn wir z.B. wissen
möchten, wie viele Wörter jede der Figuren in der Kurzgeschichte
spricht, können wir einfach die Wörter innerhalb der Elemente <said who="WERT">
isolieren, um darin Wörter zu zählen.
Die Funktionen zum Suchen und Ersetzen innerhalb von Visual Studio Code sind dafür zu begrenzt. Daher benutzen wir Kommandozeilenbefehle, um nach Suchkriterien Wörter zu zählen.1 Diese Befehle sind auf bestimmte Aufgaben spezialisiert, weshalb wir für jeden Schritt einen separaten Befehl verwenden:
grep (global
regular expression print) bzw. egrep
(extended grep).sed (stream
editor).wc (word
count).Denkpause 🤔
Für Schritt 1 machen wir uns die Tags <said> zu
Nutze, insbesondere diejenigen mit dem Attribut who="Maus". Nach dem Starttag
und Endtag kann direkt gesucht werden, jedoch müssen wir überlegen, wie
wir den Text dazwischen miteinbeziehen. Eine einfache Wildcard .* ist problematisch, da Quantoren wie
* standardmäßig „gierig“
(greedy) sind. Konkret bedeutet dies, das mit dem Suchbegriff
<said who="Maus">.*</said>
die Suche nicht beim ersten Endtag </said>
endet, sondern beim letztmöglichen Treffer, also dem zweiten Endtag in
der Zeile. Das bedeutet, dass wir die ungewollte Passage , sagte die Maus, mit einbeziehen würden.
Wir können den Quantor *
„genügsam/zurückhaltend“ (non-greedy/lazy) machen, indem wir
ein Fragezeichen anhängen. Somit erhalten wir folgenden Suchbegriff:
<said who="Maus">.*?</said>.
Mit dem Befehl grep können wir
anhand dieses regulären Ausdrucks die relevanten Passagen extrahieren.
In unserem Terminal wechseln wir mit dem Befehl cd zum Ordner, in
dem sich unsere XML-Datei befindet. Standardmäßig gibt grep die
komplette Zeile wieder, in der sich mindestens ein Treffer befindet. Da
wir nur die tatsächlichen Treffer wollen, geben wir die Option -o
(only) an. Da es sich beim Wechsel von einem „gierigen“ zu
einem „genügsamen“ Quantor um einen erweiterten regulären Ausdruck
handelt, geben wir entweder die Option -E
(extended) an oder verwenden die Variante egrep. Unser
Befehl sieht dann wie eine der folgenden Varianten aus:
grep -E -o '<said who="Maus">.*?</said>' Kafka_Kleine_Fabel_TEI.xml
egrep -o '<said who="Maus">.*?</said>' Kafka_Kleine_Fabel_TEI.xmlDadurch sollten uns nur die Passage direkter Rede der Figur
Maus, inklusive der Tags, angezeigt werden. Diesen angezeigten
Text nutzen wir nun als Grundlage für unseren Schritt 2, in dem wir alle
Tags entfernen. Dafür verwenden wir die Funktion zum Suchen und Ersetzen
von sed,
die folgende Syntax erwartet: sed 's/alt/neu/g'.
Das s steht
für substitution und das g für
global. Da sed keine
erweiterten regulären Ausdrücke unterstützt, können wir nicht analog zu
vorhin <.*?>
als Suchbegriff verwenden und müssen uns eine andere Strategie
ausdenken, um die Tags zu entfernen. Anstatt ein beliebiges Zeichen als
Wildcard zu verwenden, definieren wir unsere Wildcard negativ als alle
Zeichen, die nicht > sind. Somit
stellen wir sicher, dass unser Suchbegriff nie die Grenzen eines
einzelnen Tags übertrifft. In Kombination mit dem vorherigen Schritt
sieht unser Befehl nun wie eine der folgenden Varianten aus:
grep -E -o '<said who="Maus">.*?</said>' Kafka_Kleine_Fabel_TEI.xml |
sed 's/<[^>]*>//g'
egrep -o '<said who="Maus">.*?</said>' Kafka_Kleine_Fabel_TEI.xml |
sed 's/<[^>]*>//g'Nun sollte die Passage direkter Rede der Figur Maus ohne
Tags angezeigt werden. Zwar sind alte und neue Schreibweisen beide
präsent und aneinandergeklebt, wie z.B. daßdass, jedoch ist dies für unseren Zweck
des Wörterzählens kein Problem. Diesen neuen Text verwenden wir als
Grundlage für unseren letzten Schritt 3, in dem wir den Befehl wc verwenden.
Standardmäßig liefert dieser drei Werte, nämlich die Anzahl der Zeilen,
Wörter und Zeichen. Da uns nur die Wörter interessieren, geben wir die
Option -w
an. Somit sieht unser endgültiger Befehl wie eine der folgenden
Varianten aus:
grep -E -o '<said who="Maus">.*?</said>' Kafka_Kleine_Fabel_TEI.xml |
sed 's/<[^>]*>//g' |
wc -w
egrep -o '<said who="Maus">.*?</said>' Kafka_Kleine_Fabel_TEI.xml |
sed 's/<[^>]*>//g' |
wc -wAls Übungsbeispiel speichern wir die Wikipedia Artikel zu Georg Friedrich Händel in mehreren Sprachen, z.B. Deutsch, Englisch und Französisch, und bereiten die Texte für TEI-XML vor.
Zum Herunterladen der Texte können wir curl verwenden,
um den Quelltext der Artikel lokal als HTML-Dateien zu speichern:
curl -o Händel_DE.html https://de.wikipedia.org/wiki/Georg_Friedrich_H%C3%A4ndel
curl -o Händel_EN.html https://en.wikipedia.org/wiki/George_Frideric_Handel
curl -o Händel_FR.html https://fr.wikipedia.org/wiki/Georg_Friedrich_HaendelDie Dokumente enthalten viel unerwünschtes Material, vor allem die
Navigation innerhalb von Wikipedia betreffend. Daher behalten wir nur
den tatsächlichen Inhalt der Artikel in Form der <p>-Elemente.
Das ist insofern günstig, als dieser Tag auch in TEI-XML valide ist. Da
reguläre Ausdrücke standardmäßig auf Muster innerhalb einer Zeile
ausgelegt sind und das Suchen über mehrere Zeilen hinweg kompliziert
ist, machen wir uns das Leben leichter, indem wir alle Zeilenumbrüche in
der Datei mit Leerzeichen ersetzen. In Visual Studio Code
erreichen wir dies, indem wir \n (Sonderzeichen
für Zeilenumbrüche) mit
(Leerzeichen) ersetzen.
Nun können wir anhand von grep bzw. egrep die <p>-Elemente
extrahieren und in eine neue Datei speichern. Dadurch erhalten wir eine
Version des Inhalts, in der jedes <p>-Element
auf einer eigenen Zeile steht.
egrep -o '<p>.*?</p>' Händel_DE.html > Händel_DE_p.htmlInnerhalb der <p>-Elemente
haben wir noch weitere HTML-Elemente, die nicht Teil des TEI-XML
Standards sind, wie z.B. <i>
für kursiven Text, <b>
für fettgedruckten Text oder <a>
für Hyperlinks. Hier könnte man überlegen, diese Tags durch ihre
TEI-XML-Entsprechungen zu ersetzen. Um das Beispiel möglichst einfach zu
halten, werden wir sie zunächst löschen. Um alle Tags außer <p>
zu löschen, haben wir zwei Möglichkeiten:
<.*?>.
Anschließend setzen wir an jeden Zeilenanfang ein Starttag: ^ → <p>. Am
Zeilenende setzen wir die entsprechenden Endtags: $ → </p>.<p>. Dazu
verwenden wir ein Konstrukt namens look-ahead assertion
(„vorausschauende Annahme“). Mit einer negativen look-ahead
assertion können wir bestimmen, dass die öffnende Tagklammer
nicht von p oder
/p gefolgt werden darf: <(?!p|/p).*?>.2Nun können wir die Datei im XML-Format speichern, z.B. als Händel_DE.xml.
Zunächst erhalten wir in Visual Studio Code eine Fehlermeldung,
da noch kein XML-Schema wie TEI-XML festgelegt ist und wir kein
Wurzelelement haben. Wir können die Zeilen aus Abschnitt 4 oder aus
unserer vorherigen XML-Datei kopieren.
Denkpause 🤔
Was müssen wir minimal der Datei hinzufügen, um alle Fehlermeldungen zu beseitigen?
Für die Annotation von Namen sehen die TEI-XML-Richtlinien das Tag
<name>
mit dem Attribut type vor, um
zwischen Personen, Orten und Organisationen zu unterscheiden. Im
vorliegenden Text könnten beispielsweise folgende Namen annotiert
werden:
<name type="person">Georg Friedrich Händel</name>
<name type="place">London</name>
<name type="org">Georg-Friedrich-Händel-Gesellschaft</name>Um nicht alle Namen per Hand annotieren zu müssen (z.B. kommt London über 30-mal vor) können wir per Suchen und Ersetzen die Annotation effizienter durchführen. Jedoch müssen wir bei Personennamen darauf achten, dass diese im Text nicht durchgehend gleich genannt werden: anfangs wird Georg Friedrich Händel mit ganzem Namen bezeichnet, später jedoch nur als Händel. Daher können wir uns nicht damit begnügen, den kompletten Namen anhand von Suchen und Ersetzen zu annotieren. Genauso wenig können wir alle Vorkommen des Namens Händel pauschal als Erwähnungen des Komponisten annotieren, da im Text auch dessen Eltern Georg Händel und Dorothea Händel erwähnt werden. Wir können Erwähnungen des Komponisten mit und ohne Vornamen unter Ausschluss seiner Eltern wie folgt effizient annotieren:
((?:George? Frie?de?rich?)?\s?(?<!Georg |Dorothea )H(?:ä|a)ndels?)<name type="person">$1</name>Somit haben wir über 200 Instanzen des Namens annotiert und nur eine verfehlt, in der der zweite Vorname zu „Fr.“ abgekürzt wurde. Hier kann man abwägen, ob dieser Einzelfall einfach per Hand korrigiert wird, oder ob der reguläre Ausdruck umformuliert wird, um auch etwaige weitere Abkürzungen der Vornamen zu erfassen (wie sie z.B. in weiteren Texten vorkommen könnten). Ein aktualisierter regulärer Ausdruck als Suchbegriff könnte wie folgt aussehen:
((?:G[\w\.]+ F[\w\.]+)?\s?(?<!Georg |Dorothea )H(?:ä|a)ndels?)Idealerweise sollten wir vermerken, dass es bei sich diesen Namen um
Referenzen zur selben Person handelt. Zu diesem Zweck können wir in Tags
Referenzen einfügen, die auf ein zentrales Verzeichnis von erwähnten
Personen, Orten und Organisationen verweisen. Dieses Verzeichnis kann im
<teiHeader>
des Dokuments stehen, genauer gesagt unter folgendem Konstrukt:
<teiHeader>
...
<encodingDesc>
<classDecl>
<taxonomy>
<desc>
<listPerson>
<person xml:id="gfhaendel">
<persName xml:lang="de">
Georg Friedrich Händel
</persName>
<persName xml:lang="en">
George Frideric Handel
</persName>
<birth when="1685-03-05" />
<death when="1759-04-14" />
</person>
</listPerson>
</desc>
</taxonomy>
</classDecl>
</encodingDesc>
</teiHeader>In den Tags kann nun ein Verweis auf das Kürzel gfhaendel gesetzt werden. Dadurch wird das
Attribut type="person"
überflüssig, sodass wir folgende Ersetzung durchführen könnten:
<name type="person"><name ref="#gfhaendel">Jedoch ist diese Lösung nur sinnvoll, wenn das ganze Projekt aus nur
einem XML-Dokument besteht. Realistischer ist eine Dokumentensammlung,
sodass ein zentrales Verzeichnis in der Regel sinnvoller ist. Zu diesem
Zweck können wir einen Ordner erstellen, z.B. mit dem Namen ref, in dem wir
in XML-Dateien Verzeichnisse für Personen, Orte und Organisationen
zentral erstellen, z.B. als personen.xml, orte.xml und organisationen.xml. Die Datei personen.xml kann z.B. wie folgt gestaltet
werden:
<?xml-model href="https://tei-c.org/release/xml/tei/custom/schema/relaxng/tei_all.rng"
schematypens="http://relaxng.org/ns/structure/1.0"?>
<TEI xmlns="http://www.tei-c.org/ns/1.0">
<teiHeader>
<fileDesc>
<titleStmt>
<title>Verzeichnis erwähnter Personen</title>
</titleStmt>
<publicationStmt>
<publisher>Name des Projekts</publisher>
</publicationStmt>
<sourceDesc>
<p>Verzeichnis erwähnter Personen</p>
</sourceDesc>
</fileDesc>
</teiHeader>
<text>
<body>
<listPerson>
<person xml:id="gfhaendel">
<persName xml:lang="de">Georg Friedrich Händel</persName>
<persName xml:lang="en">George Frideric Handel</persName>
<birth when="1685-03-05" />
<death when="1759-04-14" />
</person>
<person xml:id="ghaendel">
<persName>Georg Händel</persName>
<birth when="1622-09-24" />
<death when="1697-02-11" />
</person>
<person xml:id="dhaendel">
<persName>Dorothea Händel</persName>
<birth when="1651" />
<death when="1730" />
</person>
<person xml:id="jsbach">
<persName>Johann Sebastian Bach</persName>
<birth when="1685-03-31" />
<death when="1750-07-28" />
</person>
</listPerson>
</body>
</text>
</TEI>Somit verlagern wir das Verzeichnis vom <teiHeader>
des Dokuments in eine zentrale Datei. Somit kann die Referenz wie folgt
ausgedrückt werden:
<name ref="ref/personen.xml#gfhaendel">Georg Friedrich Händel</name>Diese Methode hat jedoch zwei Nachteile:
Stattdessen können abgekürzte Präfixe definiert werden, wie z.B.
psn für Personen, loc für Orte und org für Organisationen, die dann zu den
entsprechenden Dateien verweisen. Innerhalb des <teiHeader>
können die Präfixe wie folgt definiert werden:
<teiHeader>
...
<encodingDesc>
<listPrefixDef>
<prefixDef
ident="psn"
matchPattern="([a-z]+)"
replacementPattern="ref/personen.xml#$1"
/>
<prefixDef
ident="loc"
matchPattern="([a-z]+)"
replacementPattern="ref/orte.xml#$1"
/>
<prefixDef
ident="org"
matchPattern="([a-z]+)"
replacementPattern="ref/organisationen.xml#$1"
/>
</listPrefixDef>
</encodingDesc>
</teiHeader>Sind diese Präfixe definiert und die Details in den zentralen Dateien hinterlegt, können innerhalb mehrerer XML-Dateien Verweise zu denselben Entitäten gemacht werden, wie z.B.:
<!-- im deutschen Text: -->
<name ref="psn:gfhaendel">Georg Friedrich Händel</name>
<name ref="psn:gfhaendel">Georg Fr. Händel</name>
<name ref="psn:gfhaendel">Händel</name>
<name ref="psn:jsbach">Johann Sebastian Bach</name>
<name ref="loc:london">London</name>
<!-- im englischen Text: -->
<name ref="psn:gfhaendel">George Frideric Handel</name>
<name ref="psn:gfhaendel">Handel</name>
<name ref="psn:jsbach">Johann Sebastian Bach</name>
<name ref="loc:london">London</name>
<!-- im französischen Text: -->
<name ref="psn:gfhaendel">Georg Friederich Haendel</name>
<name ref="psn:gfhaendel">Haendel</name>
<name ref="psn:jsbach">Jean-Sébastien Bach</name>
<name ref="loc:london">Londres</name>Wir haben reguläre Ausdrücke sowohl zur effizienten Annotation von XML-Dokumenten als auch zu deren Analyse verwendet. Letzteres entspricht jedoch nicht den bewährten Methoden, da erstens die Verwendung regulärer Ausdrücke für diesen Zweck fehleranfällig sein kann und wir darüber hinaus die Struktur des XML-Dokuments nicht optimal nutzen. Idealerweise sollte ein XML-Dokument geparst werden, d.h. dass dessen Struktur analysiert und die Baumstruktur durchsuchbar gemacht wird. Dies bedarf in der Regel Programmierkenntnisse, weshalb wir diese Methode heute nicht verwendet haben.
Als einer der Standards gilt das Modul Beautiful Soup für die Programmiersprache Python. Als Beispiel wiederholen wir das Zählen von Wörtern in der direkten Rede einer Figur.
from bs4 import BeautifulSoup
with open("Kafka_Kleine_Fabel_TEI.xml", "r") as inp:
content = inp.read()
tree = BeautifulSoup(content, "xml")
character = "Maus"
character_speech = tree.find_all("said", who=character)
word_count = 0
for match in character_speech:
current_speech = match.get_text()
current_word_count = len(current_speech.split())
word_count = word_count + current_word_count
print(
"The direct speech word count for the "
f"character {character} is: {word_count}"
)Alternativ zu Beautiful Soup gibt es für die Programmiersprache R das Modul xml2. Wir bedienen uns des gleichen Beispiels wie vorhin und verwenden xml2 zusammen mit dem Modul tidyverse.
library(xml2)
library(tidyverse)
xml_data <- read_xml("Kafka_Kleine_Fabel_TEI.xml")
xml_ns(xml_data)
# d1 <-> http://www.tei-c.org/ns/1.0
maus_word_count <- xml_data %>%
xml_find_all(".//d1:said[@who='Maus']", xml_ns(xml_data)) %>%
xml_text() %>%
str_flatten(collapse = " ") %>%
str_count(pattern = "\\S+")
paste(
"The direct speech word count for the character Maus is:",
maus_word_count
)curl -o xml_regex.html https://linguistics.percillier.eu/doc/xml_regex.htmlTagesworkshop:
Einführung in XML und reguläre Ausdrücke (MaDaLi2 Zertifikat Data
Literacy) © 2025 von
Michael Percillier ist
lizensiert unter:
CC BY
4.0
Diese Befehle sind standardmäßig auf macOS und Linux Betriebssysteme vorinstalliert. Für Windows empfiehlt sich die Installation des Windows-Subsystems für Linux (WSL).↩︎
Eine entsprechende positive look-ahead
assertion, wonach die öffnende Tagklammer von p oder /p gefolgt werden muss, wäre: <(?=p|/p).*?>.
Es gibt ebenfalls positive und negative look-behind assertions.
Damit bestimmt wann, welcher Ausdruck vorausgehen muss bzw. nicht
vorausgehen darf. Um z.B. gezielt im Text nach Georg Händel, dem Vater
von Georg Friedrich Händel zu suchen, kann folgender regulärer Ausdruck
verwendet werden: (?<=Georg )Händel.
Um genau diesen auszuschließen: (?<!Georg )Händel.
Zusammengefasst werden look-ahead assertions und
look-behind assertions als look-around assertions
bezeichnet.↩︎