#! /usr/local/bin/python
"""

======================================================
Kleines NUMMERIERE in Python.

Ein Proof-of-Concept, den TUSTEP-Benutzern und -Entwicklern gewidmet

Giorgio Giacomazzi, 31.5.2004
email: giorgio at giacomazzi.de
======================================================
Updates:
01.06.2004  Formulierungen
08.06.2004  Grundmodul noch kleiner
22.06.2004  Praezisierungen zur Arbeitsteilung

** Einleitung **

In einem Beitrag mit dem Titel "Quo vadis?" in der TUSTEP-Liste habe ich gegen immer neue proprietaere Loesungen in TUSTEP protestiert und im Gegenzug fuer die Integration bekannter Software-Komponenten, z.B. einer verbreiteten Skriptsprache und fuer eine allgemeine Oeffnung von TUSTEP zum Rest der Welt plaediert. Nun wird nach Beispielen gefragt, wie andere Skriptsprachen die Probleme loesen, die TUSTEP loest. Die folgenden Ausfuehrungen gehen auf diese Frage ein und zugleich ueber sie hinaus.

Um die Anzahl nicht vertrauter Elemente in Grenzen zu halten, habe ich eine bekannte Fragestellung aus dem NUMERIERE-Bereich gewaehlt. Die ersten beiden Beispiele sind im Detail kommentiert; sie setzen keine Python-Kenntnisse voraus. Die dann nachfolgenden Beispiele vertiefen die Fragestellung derart, dass fuer die TUSTEP-Diskussion wichtige Moeglichkeiten von Python in den Blick kommen. Fuer das naehere Verstaendnis dieser Beispiele waeren Kenntisse der Objektorientierung erforderlich, die Grundargumentation ist aber auch ohne diese nachvollziehbar. Allerdings sind dies keine aussergewoehnlichen Kenntnisse, sondern Konzepte, die der Softwareentwicklung seit etwa 15 Jahren zugrundeliegen. Ungefaehr so alt ist auch Python, vielleicht die "general purpose" Skriptsprache schlechthin.

HINWEIS: Aus Bequemlichkeit ist dieser Entwurf einfach der Quellcode der Beispiele plus Syntaxhervorhebung und viel Kommentar. Speichert man das HTML als Text, liegt ein "ausfuehrbares" Dokument vor und die Beispiele koennen sofort getestet und variiert werden (Python ab 2.0 erforderlich). Der Output der Beispiele ist unten angehaengt.

Ausfuehrliche Dokumentation zu Python ist unter http://www.python.org/doc zu finden; sie wird auch mit der Software installiert. Unter der gleichen Adresse werden fuer jeden Geschmack unzaehlige freie Tutorials und Literatur angegeben. Zu TUSTEP vgl. http://www.uni-tuebingen.de/zdv/tustep und http://www.itug.de.


** NUMERIERE in Python **

#NUMERIERE (mit einem M) ist ein umfassendes und leistungsfaehiges TUSTEP-Modul zum Verwalten und Aktualisieren von Querverweisen. Es kann ausgewaehlte Textstellen durchnummerieren, falsche Nummernfolge korrigieren, Verweise auf Textstellen aktualisieren und die Seiten- und Zeilennummer nummerierter Stellen ermitteln. Es ist ein schnoerkelloses Werkzeug in bester TUSTEP-Machart, fuer das sich bestimmt viele ausserhalb des kleinen TUSTEP-Kreises interessieren wuerden, wenn es nicht nur mit TUSTEP-Dateien funktionieren wuerde und wenn es als ein Modul beziehbar waere. Leider sind im Internet weder Dokumentation zu NUMERIERE noch eine Demoversion der TUSTEP-Software zu haben. Und ohne Registrierung sind weder die Software, noch das Handbuch, noch ein Einblick in die Mailingsliste zu bekommen.


1. Eine vorbereitende Aufgabe

Bevor Nummern aktualisiert oder eingefuegt werden, ist es sinnvoll, sich ueber den Stand der Nummerierung Klarheit zu verschaffen. Durch Bearbeitung eines Textes koennen sowohl Querverweise als auch Identifikationsnummern ungueltig werden und kein Programm kann dann erraten, worauf etwa ein toter Link verweist. Angenommen die ID-Nummern folgen auf ein Tag "<p>" und die Verweise auf ein Tag "<v>", dann koennten die vorhandenen Nummern in Python so ermittelt werden: """

#------------------------------------------------------------------------------
#- Fuer 'Benutzer'

print "--- Beispiel 1: Ermitteln von ID-Nummern und Verweisen"

text = "<p>10 ... <p>234 ... <v>234 ... <v>10 ..."

import re

stellen  = re.findall( "<p>(\d+)", text )
verweise = re.findall( "<v>(\d+)", text )

print "Quelle:", text
print "Bezugstellen:", stellen
print "Querverweise:", verweise

#------------------------------------------------------------------------------
"""

Mit "import re" werden dem Skript "regular expressions" verfuegbar gemacht. "re.findall( "<p>(\d+)", text )" wendet dann den regulaeren Ausdruck "<p>(\d+)" auf text an und speichert das Ergebnis in stellen ab. "print stellen" tut, was es sagt und ganz analog werden auch die Verweise ermittelt. Drei Zeilen wuerden fuer diese Aufgabe genuegen:

  import re
  print "Bezugstellen:", re.findall( "<p>(\d+)", text )
  print "Querverweise:", re.findall( "<v>(\d+)", text )

Exkurs. Beim regulaeren Ausdruck "<p>(\d+)" folgt Python der inzwischen allgemein verbreiteten Perl-Syntax; \d = eine Ziffer, \d+ = eine oder mehr Ziffer, (\d+) = zu merkende Zifferngruppe. Zurecht hat ein Listenteilnehmer  darauf hingewiesen, dass die regulaeren Ausdruecke in TUSTEP eigenwillig und, obwohl leistungsfaehig, umstaendlich sind. Die Entsprechung zu "<p>(\d+)" in TUSTEP waere "<<p>><>>/" und dann ">=04" oder "<=01", um auf das 4. Zeichen von links bzw. das erste Zeichen von rechts zuzugreifen statt einfach "\1" fuer die erste gemerkte Gruppe. Bei komplexeren Ausdruecken und in Verbindung mit mehreren Tags in Spitzklammern wird die TUSTEPsche Syntax schnell unuebersichtlich und fehlertraechtig; sie ist fuer viele bereits ein Grund, sich nicht mit TUSTEP zu beschaeftigen. Nun sind die regulaeren Ausdruecke von TUSTEP sicher historisch bedingt -  wobei man gerne mehr ueber diese Geschichte und die Entwicklungsgeschichte von TUSTEP ueberhaupt wuesste. Es waere dringend anzuraten, die heute herrschende Syntax auch in TUSTEP zu integrieren; nicht anstelle der alten, sondern als Option. Dies sollte dank verfuegbarer Bibliotheken ohne grossen Aufwand moeglich sein.


2. Eine NUMERIERE-Aufgabe

Um die Stellen zu nummerieren, die auf ein Tag "<p>" folgen, sind mehrere Loesungen denkbar, z.B.: """

#------------------------------------------------------------------------------
#- Fuer 'Benutzer'

print "\n--- Beispiel 2: Nummerieren von Stellen"

text = "Gefluegelte Worte. <p>Quo vadis? <p>Ciao bella! <p>Kannitverstan"

liste1 = text.split("<p>")
liste2 = []

for item in liste1:
    liste2.append( str( liste1.index(item)) + " " + item )

print "Quelle:", text
print "Ziel:  ", "<p>".join(liste2)[2:]

#------------------------------------------------------------------------------
"""
Die Loesung kommt mit einfachsten Mitteln, ohne regulaere Ausdruecken und Berechnungen aus, indem der Text in eine Python-Liste umgewandelt und deren Indizierung genutzt wird. Der Indexwert eines Items wird mit str() in einen String umgewandelt und vor das Item gesetzt. Die so entstehende liste2 wird dann mit "join(liste2)" zu einem String verbunden. Vor der Ausgabe wird noch mit "[2:]" das stoerende "0 " vor dem nullten Item entfernt. "[2:]" bedeutet, greife die Submenge 'von 2 bis Ende' heraus. Dieses sogenannte Slicing erinnert an die TUSTEP-Notation ">=nn", hat in Python aber eine groessere Tragweite: "[pos1:pos2]" kann auf Strings, Listen und Tupel angwendet werden und liefert, was erwartet wird, bei einem String die Zeichen von pos1 bis pos2, bei einer Liste die Items von pos1 bis pos2, ohne dass man die Laenge der Items kennen oder sich um deren Datentypen Gedanken machen muss. - Listen und Slicing sind zwei Staerken im Kern von Python und fuer Python-Benutzer eine Selbstverstaendlichkeit.


3. Ein NUMERIERE-Modul

Beispiel 2 laesst einiges von Python erkennen, nummeriert aber Stellen, die bereits mit einer Nummer versehen sind, nicht korrekt und laesst die Querverweise ganz ausser acht. Es waere leicht, diese Luecken zu fuellen, aber wir nutzen sie als Anlass, um den Schritt von einer Einzelfall- hin zu einer generischen Loesung zu wagen. Skripte wie die obigen reichen im Einzelfall aus, bleiben aber eng mit bestimmten Daten und Anforderungen verbunden. Schon bei kleinen Aenderungen funktionieren sie nicht mehr befriedigend - ein Problem, das TUSTEP-Prozeduren ebenfalls teilen. Eine maechtige Skriptsprache wie Python erlaubt aber die Entwicklung von Modulen und Bibliotheken, vergleichbar dem fuer diesen Aufgabenbereich zustaendigen TUSTEP-Modul NUMERIERE.

Das folgende Nummeriere-Modul in Python ist 30 Zeilen lang, davon sind 6 leer oder Kommentar. Es folgen noch ein Beispieltext und eine Beispielanwendug, die zeigen, wie das Modul verwendet wird. """

#-------------------------------------------------------------------
# Ein simples Nummeriere-Modul
# - Fuer 'Entwickler'

import re

class Nummeriere:

    #--- constructor
    def __init__(self, document, prefixID="<cit>", prefixLink="<see/>"):
        self.doc = document
        self.prefixID = prefixID
        self.prefixLink = prefixLink
        self.counter = 0
        self.map = {}

    def numberIDs(self):
        old = self.prefixID + "(\d+)?\s*"
        # nested callback functions
        def replFuncID(matchobj):
            self.counter += 1
            if matchobj.group(1):
                oldID = matchobj.group(1)
                self.map[oldID] = self.counter
            return self.prefixID + str( self.counter ) + " "
        self.doc = re.sub( old, replFuncID, self.doc )

    def numberLinks(self):
        old = self.prefixLink + "(\d+)"
        def replFuncLink(matchobj):
            oldID = matchobj.group(1)
            if self.map.has_key(oldID):
                return self.prefixLink + str ( self.map[oldID] )
            else:
                return self.prefixLink + oldID + "!broken!"
        self.doc = re.sub( old, replFuncLink, self.doc )

#-------------------------------------------------------------------
# Ein Beispieltext

document = """<cit>100000 Quo vadis?</cit>
<cit>123456 Auch ich in Arkadien</cit>
<cit>Kannitverstan</cit>
Goethe <see/>123456, Hebel <see/>123450, Petrus <see/>100000"""

#-------------------------------------------------------------------
# Eine Beispielanwendung
# - Fuer 'Benutzer'

print "\n--- Beispiel 3: Ein einfaches Nummeriere-Modul"

n = Nummeriere(document)          # "Parametrierung" moeglich
n.numberIDs()   
n.numberLinks()

print "- Quelle:", document
print "- Ziel:", n.doc

#-------------------------------------------------------------------

"""
Dieses Modul hat bereits einen bemerkenswerten Leistungsumfang:

- Es ist nicht an bestimmte Daten gebunden, auch XML ist keine Bedingung, sondern der Benutzer legt beliebige Praefixe fest (n.prefixID = "..."). Diese "Parametrierung" entfaellt, wenn die Daten den default-Praefixen <cit> und <see/> entsprechen, die das Modul anbietet (im Konstruktor).

- Das Modul erkennt bei jeder Stelle, ob eine Nummer zu ersetzen oder einzufuegen ist.

- Die Querverweise werden aktualisiert.

- Tote Links werden kenntlich gemacht.

- Die seltsame Beschraenkung auf maximal 99999 laufende Nummer in TUSTEP entfaellt.

Um die Funktionalitaet zu verdeutlichen, enthaelt der Beispieltext mit Absicht einen toten Link, ein nicht nummeriertes Item und Nummern groesser als 99999.

Wenn in diesem Modul (was leicht der Fall sein kann) etwas falsch ist, fehlt oder nicht befriedigend ist, kann der Anwender eingreifen. Fuer den Profi andererseits waere es moeglich, einen solchen Prototyp etwa in C++ (oder Fortran) zu transponieren und aus dem Prototyp ein hoch performantes TUSTEP-Modul zu machen. Python ermoeglicht es dem Benutzer, der vielleicht auf eine neue Problematik gestoessen ist, selbst eine provisorische Loesung zu entwickeln. Der Entwurf kann dann, ausgebaut und ueberarbeitet, in eine gemeinsame Bibliothek ueberfuehrt werden. Hier wird eine ganz neue Dynamik zwischen Benutzern und Entwicklern in Gang gesetzt. Der Entwickler wird schnell die Vorteile von Python selbst fuer seine eigene Arbeit entdecken.

Aber auch nachdem ein Skript in kompilierten Code umgegossen worden ist, ist bei Python das Ende laengst nicht erreicht. Denn Python ermoeglicht selbst die Erweiterung von Bibliotheken, die nicht in Python-Code vorliegen, beispielsweise DLLs. Bedingung ist eine dokumentierte Schnittstelle, nicht unbedingt das Vorliegen des Quellcodes.


4. Ein erweitertes Nummeriere-Modul

Wir erweitern exemplarisch das entworfene Modul, ohne es anzutasten und zu veraendern, um einige neue features:

- Protokollfunktion

- Link-Checker, ID-Checker

- Ausgabe der Position von Nummern und Verweisen analog zur Ausgabe der Seiten- und Zeilennummern in TUSTEP.

- einen Schnellaufruf numberAll() sowohl fuer Links und als auch fuer IDs
"""

#-------------------------------------------------------------------
# Erweiterung des Nummeriere-Moduls
# - Fuer 'Power-User' oder 'Entwickler'

class ExtNummeriere( Nummeriere):

    def __init__(self, document):
        Nummeriere.__init__(self, document)
        self.IDs = []
        self.links = []
        self.getLinksAndIDs()

    def getLinksAndIDs(self):
        self.IDs = re.findall( self.prefixID + "(\d+)", self.doc )
        self.links = re.findall( self.prefixLink + "(\d+)", self.doc )

    def checkLinks(self):
        brokenLinks = []
        for link in self.links:
            if link not in self.IDs: brokenLinks.append(link)
        return brokenLinks

    def checkIDs(self):
        doublets = []
        for id in self.IDs:
            if self.IDs.count( id ) > 1: doublets.append(id)
        return doublets

    def printMap(self):
        for id in self.IDs:
            print "%-8s ==> %s" % ( id, self.map[id] )

    def printPageLineNumber(self):
        for id in self.IDs:
            print "%-8s : Pos. %s" % (id, self.doc.index(id) + 1 )

    #--- in a hurry
    def numberAll(self):
        self.numberIDs()
        self.numberLinks()


#-------------------------------------------------------------------
# Beispielanwendung zum erweiterten Nummeriere-Modul
#- Fuer 'Benutzer'

print "\n--- Beispiel 4: Ein erweitertes Nummeriere-Modul"

n2 = ExtNummeriere(document)
                                         # Fehler zuerst korrigieren?
print "Doppelte IDs:", n2.checkIDs()
print "Tote Links  :", n2.checkLinks()

n2.numberAll()
print n2.doc

print "Mapping:"
n2.printMap()                      # Protokollierung

print "Position der neuen IDs:"
n2.getLinksAndIDs()
n2.printPageLineNumber()

#------------------------------------------------------------------------------

"""
Zwei Bemerkungen dazu:

- Anstatt eines ausfuehrlichen Protokolls mit allen vorkommenden laufenden Nummern wie bei NUMERIERE werden nur tote Links und doppelte IDs ausgegeben. Falls gewuenscht, waere es ein Leichtes alle Nummern auszugeben.

- Da TUSTEP-Dateien nur in TUSTEP lesbar sind (was man sich auf der Zunge zergehen lassen sollte), kann kein fremdes Programm die Seiten- und Zeilennummer von TUSTEP-Dateien ermitteln. Dagegen habe ich in "Quo vadis?" besonders protestiert. Wegen des kleinen Beispieltextes wird hier die Zeichenposition nummerierter Verweise und Stellen ausgegeben.


** Schluss **

Aufgrund einer modernen Skriptsprache, besonders wenn sie die Weltklasse von Python hat, wird ein neues produktives Miteinander von Benutzern und Entwicklern moeglich. Der Benutzer wird muendig, der Entwickler agiler. Die bei TUSTEP uebliche Trennung von Modul-Entwicklung in Tuebingen, "Parametrierung" beim Power-User und fast ausschliesslich Editoreinsatz bei den meisten Benutzern wird aufgeweicht und relativiert, weil Python es dem Benutzer moeglich und leicht macht, auf ALLEN Entwicklungsstufen aktiv einzugreifen und mitzureden. Die Moeglichkeiten, die eine moderne Skriptsprache wie Python bietet, gehen damit wohl auch ueber diejenige der neuen proprietaeren Makrosprache von TUSTEP hinaus. Dass auf einmal auch viel neue Hilfe zur Selbsthilfe und beratende Kompetenz von aussen bereitsteht, ist auch erwaehnenswert.

Eine enge Zusammenarbeit von Entwicklern und Benutzern entspricht durchaus dem urspruenglichen Geist von TUSTEP, das sich gerade durch die Auseinandersetzung mit Benutzerproblemen aus den Geisteswissenschaften verdient gemacht hat, fuer die die klassische Informatik lange wenig zu bieten und wenig Verstaendnis hatte. Inzwischen haben sich die Dinge aber tiefgreifend geaendert; TUSTEP, einst Vorreiter, ist ins Hintertreffen geraten.

Die Diskrepanz mag eine Nebensache verdeutlichen, die auch die Form dieses Dokuments betrifft: TUSTEP erlaubt nach wie vor weder mehrzeilige Kommentare noch Kommentare nach Zeilenanfang (innerhalb von Stern-Parametern muessen Kommentare eingerueckt sein). Dafuer bietet es gleich mehrere Konventionen fuer den einzeiligen Kommentar, welche sich kaum untereinander und von denen fuer Kommandos unterscheiden: #-, #=, #, #*, #$. Leider hat die Makrosprache (aus Kompatibilitaetsgruenden?) noch mehr Konventionen fuer den Zeilenanfang hinzugefuegt, $$, $$-, $$*, etc..

Es ging hier nicht nur darum zu zeigen, dass und wie eine Skriptsprache Probleme loest, die TUSTEP loest, sondern auch eine zeitgemaesse Form fuer die Zusammenarbeit von Benutzern und Entwicklern bei TUSTEP selbst zu finden. Dass der ganze Radius der Anforderungen - vom einfachen 2-Zeiler den auch wer KOPIERE meidet beherrscht bis hin zur Modulentwicklung, die nur dem Fortran-Spezialisten vorbehalten war - abgedeckt werden koennte, verdankt sich allerdings dem universellen Charakter der Skriptsprache Python.
 

** Anhang: Output der Beispiele **

--- Beispiel 1: Ermitteln von ID-Nummern und Verweisen
Quelle: <p>10 ... <p>234 ... <v>234 ... <v>10 ...
Bezugstellen: ['10', '234']
Querverweise: ['234', '10']

--- Beispiel 2: Nummerieren von Stellen
Quelle: Gefluegelte Worte. <p>Quo vadis? <p>Ciao bella! <p>Kannitverstan
Ziel:   Gefluegelte Worte. <p>1 Quo vadis? <p>2 Ciao bella! <p>3 Kannitverstan

--- Beispiel 3: einfaches Nummeriere-Modul
- Quelle: <cit>100000 Quo vadis?</cit>
<cit>123456 Auch ich in Arkadien</cit>
<cit>Kannitverstan</cit>
Goethe <see/>123456, Hebel <see/>123450, Petrus <see/>100000
- Ziel: <cit>1 Quo vadis?</cit>
<cit>2 Auch ich in Arkadien</cit>
<cit>3 Kannitverstan</cit>
Goethe <see/>2, Hebel <see/>123450!broken!, Petrus <see/>1

--- Beispiel 4: Ein erweitertes Nummeriere-Modul
Doppelte IDs: []
Tote Links  : ['123450']
<cit>1 Quo vadis?</cit>
<cit>2 Auch ich in Arkadien</cit>
<cit>3 Kannitverstan</cit>
Goethe <see/>2, Hebel <see/>123450!broken!, Petrus <see/>1
Mapping:
100000   ==> 1
123456   ==> 2
Position der neuen IDs:
1        : Pos. 6
2        : Pos. 30
3        : Pos. 64

"""