[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