Schleifen und Listen

(Übersetzung von »Loop and Linked Lists« von Rick Quatro)

In der vorherigen Lektion haben Sie eine einfache FrameScript-Schleife kennen gelernt, die alle Absatzformate in einem Dokument verarbeitet hat. In dieser Lektion erstellen Sie eine abgewandelte Version dieser Schleife und werden in das wichtige Konzept der »verknüpften Listen« der FrameMaker-Objekte eingeführt.

Eine schöne Abkürzung

Die allgemeine Syntax der Anweisung Loop ForEach lautet:

Loop ForEach(Objekt) In(Objekt) LoopVar(Variable)
    //...(Anweisungen)...
EndLoop

In manchen Fällen ist diese Form der Schleife aber nicht ausreichend. Sie können z.B. mit der Anweisung Loop ForEach keine Schleife durch alle Grafiken im aktiven Dokument ausführen. (Eine Liste der gültigen Parameter finden Sie in der Scriptwriter’s Reference.) Außerdem gibt es Befehle, die innerhalb der Schleife nicht ausgeführt werden können. Hier ein Beispiel: Öffnen Sie ein neues Dokument im Hochformat, wählen Sie FrameScript > Script Window und geben Sie folgenden Text im Skriptfenster ein:

Loop ForEach(PgfFmt) In(ActiveDoc) LoopVar(vPgfFmt)
    Delete Object(vPgfFmt);
EndLoop

Öffnen Sie den Absatzformat-Katalog des Dokuments und klicken Sie auf Run. Eigentlich müsste das Skript alle Absatzformate des Dokuments löschen. Dies ist aber nicht der Fall. Jedes Mal, wenn Sie auf Run klicken, löscht das Skript ein Format und wird dann beendet. Um dies verstehen zu können, müssen Sie eine andere Form von Schleifen kennen lernen und sich bewusst machen, wie Schleifen allgemein funktionieren.

Analyse der Abkürzung

Hier zunächst eine Schleife aus der vorherigen Lektion, die erwiesenermaßen funktioniert:

Loop ForEach(PgfFmt) In(ActiveDoc) LoopVar(vPgfFmt)
    Display vPgfFmt.Name;
EndLoop

Nun lernen Sie eine andere Schleife kennen, die genau dieselbe Aufgabe ausführt:

Set vPgfFmt = ActiveDoc.FirstPgfFmtInDoc;
Loop While(vPgfFmt)
    Display vPgfFmt.Name;
    Set vPgfFmt = vPgfFmt.NextPgfFmtInDoc;
EndLoop

Die zweite Schleife tut Folgendes:

  1. Der Variablen vPgfFmt wird das erste Absatzformat im Dokument zugewiesen.
  2. Die Schleife wird gestartet und fortgesetzt, solange vPgfFmt den Wert »wahr« besitzt (d.h., solange die Variable vPgfFmt vorhanden ist). Wenn es im Dokument keine Absatzformate gibt, wird die Schleife erst gar nicht aufgerufen, denn in diesem Fall hätte vPgfFmt den Wert »falsch« (nicht vorhanden).
  3. Wenn die Schleife gestartet wird, zeigt das Skript den Namen des aktuellen Absatzformats an.
  4. Das Entscheidende bei dieser Schleife ist die zweite Zeile. Hier wird der Variablen vPgfFmt das Format des nächsten Absatzes im Dokument zugewiesen. Wenn ein weiteres Absatzformat vorhanden ist, bleibt das Skript innerhalb der Schleife, wenn nicht, wird die Schleife beendet.

Es ist wichtig zu verstehen, dass beide Schleifen exakt dasselbe tun, obwohl sich deren Syntax unterscheidet. Die erste Schleife ist lediglich eine Kurzform der zweiten Schleife.

Der Hintergrund

Der grundsätzliche Zweck einer Schleife liegt darin, eine bestimmte Aufgabe für jedes Element in einer Objektliste durchzuführen. Die Schleife wechselt von einem Listenelement zum nächsten und führt die Aufgabe für jedes Element durch, bis das Ende der Liste erreicht wurde. FrameMaker verwaltet für jeden Objekttyp eigene »verknüpfte Listen«, z.B. Absatzformate, Absätze, Grafiken und Tabellen. Jedes Objekt steht hierbei für ein Element in der Liste.

Das Verständnis der verknüpften Listen ist der Schlüssel zum Schreiben von Skripts! Dies mag sich übertrieben anhören, aber Sie müssen in der Lage sein, auf diese Listen zuzugreifen und sie zu durchlaufen (also von einem Element zum nächsten wechseln), damit Sie sie mit Hilfe von Skripts manipulieren können.

Eine Veranschaulichung verknüpfter Listen finden Sie in der Scriptwriter’s Reference, wo die Eigenschaften (Properties) des Objekts Document aufgelistet sind. Diese Liste enthält die Dokumenteigenschaften, sie enthält aber auch Verknüpfungen zu anderen Eigenschaftslisten. Damit Sie auf die anderen Listen zugreifen können, stellt Ihnen das Dokument-Objekt eine »Verknüpfung« mit dem ersten Element jeder Liste zur Verfügung. Für Ihr Skript bedeutet dies, dass Sie auf das erste Element in der Liste der Absatzformate über die Punkt-Notation zugreifen können.

Set vPgfFmt = ActiveDoc.FirstPgfFmtInDoc;

Und wenn Sie auf das zweite Absatzformat im Dokument zugreifen möchten? Können Sie

Set vPgfFmt = ActiveDoc.SecondPgfFmtInDoc;

oder

Set vPgfFmt = ActiveDoc.NextPgfFmtInDoc;

verwenden? Keine dieser Anweisungen wird funktionieren, denn das Dokument-Objekt erlaubt lediglich den Zugriff auf das erste Element der Liste. Das Dokument-Objekt »sagt« Ihnen im Prinzip Folgendes: »Ich bringe Sie zum ersten Element der Liste, aber dann sind Sie auf sich selbst gestellt«.

Wie ein Staffellauf

Die Zuständigkeit für das Bewegen von Element zu Element in einer Liste liegt bei jedem Listenelement. Jedes PgfFmt-Objekt hat eine NextPgfFmtInDoc-Eigenschaft. Jedes Pgf-Objekt hat eine PrevPgfInDoc-Eigenschaft und eine NextPgfInDoc-Eigenschaft. Das Dokument-Objekt »übergibt den Stab« an das erste Element der Liste, und das erste Element gibt ihn weiter usw. Genau diese Aufgabe übernimmt in der Schleife das Skript.

Set vPgfFmt = vPgfFmt.NextPgfFmtInDoc;

Wenn das Skript beim letzten Element in der Liste angelangt ist, wird dessen NextPgfFmtInDoc auf Null zurückgesetzt, weil kein NextPgfFmtInDoc-Objekt mehr vorhanden ist. Dadurch nimmt die Variable vPgfFmt den Wert »falsch« an und die Schleife wird beendet.

Die erste Variante der Schleife greift über genau diese Methode auf die Absatzformate zu, aber das »Übergeben des Stabs« ist nicht sichtbar. Verwenden Sie die zweite Version der Schleife (und Ihr Wissen über verknüpfte Listen), um das Problem zu lösen, dass das Skript anhält, bevor alle Formate gelöscht werden. Hier nun eine etwas modifizierte Form des Skripts, das alle Absatzformate im Dokument löschen soll:

Set vPgfFmt = ActiveDoc.FirstPgfFmtInDoc;
Loop While(vPgfFmt)
    Delete Object(vPgfFmt);
    Set vPgfFmt = vPgfFmt.NextPgfFmtInDoc;
EndLoop

Kopieren Sie dieses Skript in Ihr Skriptfenster und klicken Sie auf Run. Wie zuvor wird nur ein Format gelöscht. Und diesmal erhalten Sie eine Fehlermeldung (wenn Sie die Anzeige (Display) der Run Errors nicht unter FrameScript > Options deaktiviert haben; in diesem Fall läuft das Skript endlos und Sie müssen es mit ESC beenden):

Können Sie sich denken, warum nur ein Format gelöscht wird? Denken Sie wieder an die Metapher vom Staffelläufer und dem Stab. Dann ist die Antwort ganz einfach. Die Zeile

Delete Object(vPgfFmt);

löscht das vPgfFmt-Objekt, bevor dieses die Möglichkeit erhält, »den Stab« an das nächste Absatzformat im Dokument zu übergeben. Krass ausgedrückt: Sie haben den Läufer ermordet, bevor er den Stab übergeben konnte! Die nächste Zeile des Skripts verursacht eine Fehlermeldung, weil kein vPgfFmt-Objekt mehr vorhanden ist.

Hier ist die Lösung:

Set vPgfFmt = ActiveDoc.FirstPgfFmtInDoc;
Loop While(vPgfFmt)
    Set vPgfFmtToDelete = vPgfFmt;
    Set vPgfFmt = vPgfFmt.NextPgfFmtInDoc;
    Delete Object(vPgfFmtToDelete);
EndLoop

Mit der folgenden Zeile initialisiert dieses Skript eine weitere Variable für das aktuelle Absatzformat:

Set vPgfFmtToDelete = vPgfFmt;

Die nächste Zeile übergibt den Stab, aber nun haben Sie die Variable vPgfFmtToDelete, in der der vorherige Staffelläufer gespeichert ist. Nun können Sie das vorherige Absatzformat problemlos löschen, denn der Variablen vPgfFmt wurde bereits das nächste Element der verknüpften Liste zugewiesen.

Ein nützliches Skript: Löschen nicht verwendeter Absatzformate

Machen Sie sich keine zu großen Sorgen, wenn Sie diese Lektion noch nicht völlig verstehen. Arbeiten Sie weiter an den Mechanismen der Skripts, und schon bald werden Ihnen die Konzepte geläufiger sein. Als Belohnung für Ihre Geduld beende ich diese Lektion mit einem sehr nützlichen Skript (und werte sie dadurch hoffentlich auf). Dieses Skript löscht alle Absatzformate im Katalog, die nicht im Dokument verwendet werden. Dies stellt einen guten Weg dar, ein Dokument zu bereinigen, bevor Sie Formate aus einer Vorlage importieren. Außerdem enthält das Skript eine neue Form einer Schleife und einige neue Konzepte, wie z.B. Fehlerprüfung und bedingte Anweisungen.

Bevor Sie mit dem Skript beginnen, überlegen Sie sich zunächst, wie Sie das Problem angehen möchten. Zunächst sollen Sie die »Logik« des Skripts entwickeln und sich noch keinerlei Gedanken über dessen Syntax machen. Formulieren Sie den ganzen Lösungsweg im Klartext und erstellen Sie dann eine Liste der einzelnen Aufgaben. Hier ist meine Lösung:

  1. Eine Liste aller Absatzformate im Katalog erstellen
  2. Für jeden Absatz im Dokument das Absatzformat ermitteln
  3. Falls das Absatzformat in der Liste enthalten ist, dieses Format aus der Liste entfernen
  4. Nach Verarbeitung aller Absätze überprüfen, ob noch Absatzformate in der Liste vorhanden sind
  5. Falls ja, diese aus dem Dokumentkatalog löschen

Unten finden Sie das vollständige Skript. Lesen Sie zur Übung das Skript, und versuchen Sie herauszufinden, welche Codezeilen zu welcher Aufgabe in oben stehender Liste gehören. Fügen Sie Anmerkungen zum Skript hinzu. Fallen Ihnen andere nützliche Funktionen ein, die Sie noch hinzufügen könnten?

// Alle nicht-verwendeten Absatzformate löschen.
If (ActiveDoc = 0)
    MsgBox 'Kein aktives Dokument.';
    LeaveSub;
Else
    Set vCurrentDoc = ActiveDoc;
EndIf
// 1. Schritt
New StringList NewVar(vPgfFormatsInCatalog);
Set vPgfFmt = vCurrentDoc.FirstPgfFmtInDoc;
Loop While(vPgfFmt)
    Add Member(vPgfFmt.Name) To(vPgfFormatsInCatalog);
    Set vPgfFmt = vPgfFmt.NextPgfFmtInDoc;
EndLoop
// 2.+3. Schritt
Loop ForEach(Pgf) In(vCurrentDoc) LoopVar(vPgf);
    Find Member(vPgf.Name) InList(vPgfFormatsInCatalog)
        ReturnStatus(vFound);
    If (vFound = 1)
        Remove Member(vPgf.Name) From(vPgfFormatsInCatalog);
    EndIf
EndLoop
// 4.+5. Schritt
If (vPgfFormatsInCatalog.Count > 0)
    Loop While(vCounter <= vPgfFormatsInCatalog.Count)
        LoopVar(vCounter) Init(1) Incr(1)
        Get Member Number(vCounter) From(vPgfFormatsInCatalog)
            NewVar(vPgfFormat);
        Get Object Type(PgfFmt) Name(vPgfFormat) NewVar(vPgfFmt);
        Delete Object(vPgfFmt);
    EndLoop
EndIf

Viel Erfolg!

Februar 2008, Michael Müller-Hillebrand