[Tustep-Liste] Scripting und Satz, Teil 3
Giorgio Giacomazzi
giorgio at giacomazzi.de
Mo Jul 11 17:31:36 CEST 2005
Liebe Tustep-Runde,
nachdem meine allgemeine Position deutlich geworden ist, will ich
abschließend auf den Auslöser des Beitrags zurückgehen.
Bei der Frage, wie man aus
1. <aABC>text <aDEF><aGHI>text</a> text<aLMN> text</a></a></a>
2. <aABC>text <aDEF><aGHI>text</aGHI> text<aLMN> text</aMNO></aDEF></aABC>
machen kann geht es um die Verarbeitung von Klammerausdrücken beliebiger
Tiefe und mit unterschiedlichen Klammerpaaren. Die Informatik
unterscheidet solche 'Sprachen' von einfacheren Sprachen der Art
"<aABC>text</a> <aDEF>text</a>": Letztere lassen sich mit regulären
Ausdrücken verarbeiten, erstere verlangen spezielle Parser. Denn
reguläre Ausdrücke können nicht alle "Endtags richtig zuordnen". Wer
diese Einsicht nicht teilt, versucht die Grenze mit Tricks zu umgehen.
Zum Beispiel ließe sich das Ziel auch so erreichen ("re" steht für
regular expression in Python):
while True:
doc = re.sub( r"<a(\w+)>([^<]*)</a>", r"{\1}\2{/\1}", doc)
if doc.find("<a") == -1: break
Der Trick besteht darin, Tag-Paare von innen her aufzulösen, mit {} zu
maskieren und am Schluß durch <> zu demaskieren. Einschränkungen können
durch weitere Tricks umgangen werden.
Angesichts der sich überbietenden kopiere-Lösungen und -Tricks habe ich
im ersten Beitrag nicht widerstehen können, eine einfache
parser-basierte Lösung anzubieten. In der Praxis aber begegnen oft
Fälle, für die es keine Parser gibt. Die obigen Quellddaten fallen
zunächst in diese Kategorie, obwohl sie sich, wie gezeigt, leicht nach
xml überführen lassen. In solchen Fällen kommt es darauf an, dass das
eigene Hauptwerkzeug (tustep oder sonstiges) die Möglichkeit bietet,
ohne allzu großen Aufwand und Kopfzerbrechen kleine Parser zu
implementieren.
Parser basieren meistens auf einem Stack; beim Fortschreiten im
Datenstrom wird jede Startkennung auf dem Stack abgelegt, sodass beim
Antreffen einer Endkennung die entsprechende Startkennung direkt vom
Stack entnommen werden kann. Sowohl die ruby-Lösung von Herrn Derkits
als auch die Makrolösung von Herrn Schälkle zeigen, dass solche Parser
sich elegant realisieren lassen. Die Ruby-Lösung springt wie eine
Gazelle von einer a-Kennung zur nächsten, indem die Suche nach a-Tags ab
der zuletzt erreichten Positon wiederholt wird; die erreichte Kennung
wird gemerkt bzw. nach Typ verarbeitet.
Die neue Modi "stream" und "records" zur ACCESS-Anweisung, die nach
meiner Kritik auf der itug-Tagung überraschend veröffentlicht wurden,
bieten zunächst den ersehnten Zugriff auf unstrukturierte Daten in der
Makrossprache. Darüber hinaus kann modus "stream" den kompletten
Datenfluß in beliebige Start- und End-Kennungen gemäß Suchtabellen und
in sonstige Teile splitten. Je nach Treffer wird dann ebenfalls gemerkt
bzw. transformiert. Das ergibt ein interessantes Parser-Werkzeug für
einfache Klammersprachen und xml-Dialekte, das wie die Vorführung des
neue *FUNO in Blauberuen gezeigt hat, auch die effiziente
Reimplementierung von Standard-Satzmakros ermöglicht.
Ein Problem ist, dass die besagten Neuerungen nur in Listenbeiträgen
dokumentiert sind (30.11.2004, 9.3.2005, 9.6.2005). Am meisten lernt man
zur Zeit, wenn man sich anschaut, wie der Modus "stream" bzw.
"stream/records" eigene Daten zerlegt, zum Beispiel durch ein Makro
STREAM_CHECK, dem eine Datei und Suchtabellen übergeben werden:
#=: STREAM_CHECK
$$! quelle, kenn1, kenn2, modus
$$=- []
mode variable
set status = check(quelle, read, tustep)
error/stop status
build s_table atbl = kenn1
build s_table etbl = kenn2
if (modus .eq. "stream" .or. modus .eq. "stream/records" ) then
access quelle: read/[modus] "[quelle]" s.z/u,
anf/atbl+text+end/etbl, typ
else
+ Modus-Fehler! Übergebe "stream" oder "stream/records"
stop
end if
loop/99999
read quelle
if (eof) exit
set pos = concat(s, ".", z)
select [typ]
case 0
set typname = "TEXT"
case 1
set typname = "STRT"
case 2
set typname = "ENDE"
case 3
set typname = "ALLE"
end select
+ [pos] [typname] : '[anf]|[text]|[end]'
end loop
end access quelle
Ein anderes Problem ist die Zunahme an Syntaxregeln durch die
Makrosprache und leider, wie die obigen Schreibweisen für Variablen
zeigen, auch in dieser selbst. - Wäre es nicht hilfreich, wenn die
Einzelteile komplexer regulärer Ausdrucke kommentiert und beim Namen
gerufen werden könnten wie in Python:
import re
doc = "<aUVW>bla bla <aABC><aGHI>bli bli</a> blo blo <aMNO>mah </a></a></a>"
out = file("out.txt", "w")
scanner = re.compile( '''
(?P<START> <a(\w+)> ) | # "(?P" benennt Teile einer regex
(?P<END> </a> ) |
(?P<TEXT> . )
''', re.VERBOSE ) # gestattet inline-Kommentare
stack = []
for token in scanner.finditer(doc):
token_typ = token.lastgroup
if token_typ == 'START':
stack.append(token.group(2)) # "(\w+)"-Teil merken
if token_typ == "END":
out.write( "</" + stack.pop() + ">") # Gemerktes einsetzen
else:
out.write( token.group( typ ))
Der Makrosprache täte ein Gedankenaustausch mit anderer Skriptsprachen
gut. Schließlich spricht sie eher den tustep-Außenseiter als den Insider
an. Es lag mir hier aber vor allem daran, nicht den Eindruck entstehen
zu lassen, dass ich nur die strenge XML-Lösung im ersten Beitrag für
vertretbar halte. Nun kann der Tisch geräumt werden.
Eine erholsame Urlaubszeit!
Giorgio Giacomazzi
--
Giorgio Giacomazzi
Strelitzstr. 18
D-12105 Berlin
Tel.: 030-70176848
Fax: 0721-151440186
Mehr Informationen über die Mailingliste Tustep-Liste