Scripts

<< Klicken Sie, um das Inhaltsverzeichnis anzuzeigen >>

Navigation:  »Kein übergeordnetes Thema«

Scripts

EMIL kann an vielen Stellen durch Scripting individualisiert und funktionell erweitert werden - bei der Berechnung eigener Formular-Scores, beim Import von Daten über die FELIX-Schnittstelle und bei der Generierung von Berichten. Dieses Tutorial vermittelt die Grundlagen, die in allen drei Bereichen gelten.

Wichtiger Hinweis: Wenn Sie hier überfordert oder unsicher sind, ist dies keine Schande! Das Erstellen von Scripts erfordert ein gewisses Maß so zu denken, wie Computer arbeiten (denn im Gegensatz zu Ihnen können die nicht denken). Das ist zwar keineswegs IT-Spezialisten vorbehalten, aber auch nicht jedermanns Ding. ITC bietet Trainings zum Scripting und auf Wunsch die komplette Erstellung von Scripten als Dienstleistung an. Wenden Sie sich bei diesem Thema bitte an service@itc-ms.de. Da das Thema sehr vielfältig ist, ist Scripting kein Bestandteil des kostenlosen ITC-Service.

Was zur Hölle ist überhaupt ein Script?

Scripte sind kleine Computerprogramme, die von EMIL ausgeführt werden und entsprechende Kommandos abarbeiten. Man kann sie mit einem Filmscript vergleichen, das beschreibt, wer was wann und wie machen muss. So, wie beim Filmscript die Kulisse vorgegeben ist, ist auch der Handlungsspielraum der Scripte in EMIL so eingegrenzt, dass man damit nur erwünschte Funktionen ausführen und so nicht wie ein Computervirus in die Grundfunktionen eingreifen und Schaden anrichten kann.

Ein EMIL-Script kann beispielsweise Werte aus verschiedenen Quellen (z.B. Feldern eines Formulars) ermitteln, damit etwas ausrechnen und das Ergebnis einem Zielwert zuweisen. In Berichten kann ein Script eine Datenmenge (also eine nach SQL-Kriterien aus der Datenbank abgefragte Zeilenliste) durchgehen und daraus ein Dokument erzeugen. Bei der Verarbeitung von Schnittstellendaten sucht das Script in den empfangenen Quelldaten nach Informationen, bereitet diese auf und überträgt sie in die Datenbank.

Grundaufbau eines Scripts

Scripte werden in Pascal Script erstellt. Diese Scriptsprache folgt der Syntax der Programmiersprache Pascal. Hier wird bei Identifiern (also Variablennamen, Kommandos etc.) nicht zwischen Groß- und Kleinschreibung unterschieden, sodass Hallo und hAlLo identisch sind. Dennoch sollte man zugunsten guter Lesbarkeit eine einheitliche Schreibweise verwenden. Umlaute und Leerzeichen dürfen nur innerhalb von Zeichenkettenkonstanten vorkommen. Sonderzeichen wie +, -, *, / und Klammern werden als Rechenoperatoren interpretiert und sind in Variablennamen nicht zulässig. Man sollte sich generell bei Identifiern auf Buchstaben und Zahlen beschränken; jede Variable muss zwingend mit einem Buchstaben beginnen.

Jedes Script besteht grundsätzlich aus zwei Blöcken in der folgenden Struktur:

var

   ... Variablendefinitionen ...

begin

   ... Programmcode ...

end.

Kommandos und Definitionen sind immer mit einem Semikolon abzuschließen.

Variablen

Pascal Script erwartet, dass alle Variablen, die im Verlauf des Scripts benötigt werden, im var-Block definiert sind. Das vermeidet, dass man einer Variable einen Wert zuweist und durch einen Schreibfehler mit einer anderen weiterrechnet, und es verhindert auch versehentliche Typvermischung. Zur besseren Lesbarkeit empfiehlt sich CamelCasing (Binnenmajuskel): HalloErna oder WertVorAenderung. Manche bevorzugen Underscores: hallo_erna oder wert_vor_aenderung. Beides ist gültig - Geschmackssache.

var

   Anzahl: Integer;

   Score: Double;

   Name: String;

Als Variablentypen stehen String, Integer, Double und Boolean zur Verfügung:

Integer ist ein Ganzzahlentyp, der Werte zwischen -2.147.483.648 und 2.147.483.647 speichert.

Double kann eine Fließkommazahl zwischen etwa +/- 10300 mit einer Genauigkeit von 15-16 Dezimalstellen darstellen. Wichtig: Fließkommazahlen werden nicht notwendigerweise exakt gespeichert. So kann ein Wert 5.768 intern z.B. als 5.76800000000000001 vorliegen. Das muss man bei der Prüfung auf Gleichheit beachten und sollte Werte für die Ausgabe immer runden. Datumsangaben (mit oder ohne Zeit) werden in Scripten immer als Double verarbeitet; der Wert repräsentiert die Anzahl der Tage seit dem 30.12.1899, wobei der Teil hinter dem Komma den Bruchteil der 24 h und damit den Zeitanteil darstellt.

String (Zeichenkette) kann zwischen 0 und rund zwei Milliarden Zeichen fassen. Grenzen setzt allein der Hauptspeicher des Systems.

Boolean ist ein Wahrheitswert und kann nur true oder false annehmen.

Zuweisung und Berechnung

Der eigentliche Script-Code steht im begin/end-Block. Der Punkt hinter dem letzten end ist erforderlich - er signalisiert das Ende des Scripts. Blöcke können verschachtelt werden; innere Blöcke werden wie Kommandos mit Semikolon abgeschlossen, nur der äußerste mit einem Punkt:

begin

   ...

   begin

      ...

   end;

   ...

end.

Zuweisungen erfolgen mit dem Operator := (zur Unterscheidung gegen Vergleiche mit =):

var

   a, b, c: Integer;

begin

   a := 10;

   b := -7;

   c := a * 12 - b;

end.

Mit Fließkommazahlen (Double) ist immer der Dezimalpunkt (nicht das Komma) zu verwenden:

var

   a, b, c: Double;

begin

   a := 10.6;

   b := -7.35;

   c := a * 12.88 - b;

end.

Zeichenketten (Strings) können mit dem +-Operator verknüpft werden. Stringkonstanten müssen in einfache Hochkommata (Shift-#) eingeschlossen sein - Gänsefüßchen sind nicht zulässig:

var

   a, b, c: String;

begin

   a := 'Hallo';

   b := 'EMIL';

   c := a + ' ' + b;

end.

Je nach Kontext sind bereits Variablen vordefiniert. Im Formular-Editor stehen z.B. alle numerischen Eingaben und Ankreuzfelder als Variablen zur Verfügung, ohne dass man sie explizit definieren muss. Auch in FELIX und im Berichtegenerator sind kontextspezifische Variablen und Funktionen vordefiniert. Die jeweils verfügbaren Bezeichner sind in den entsprechenden Referenzkapiteln dokumentiert.

Hinweis zur Division: Bei Fließkommawerten ist der Operator / für die Division zu verwenden; Ganzzahlen werden mit dem Operator div geteilt, der Rest wird mit mod berechnet.

Funktionen verwenden

Neben den Standard-Operatoren stehen kontextspezifische Funktionen zur Verfügung. Diese sind aber vom jeweiligen Kontext abhängig und nicht überall verfügbar; sie sind in den jeweiligen Funktionsreferenzen dokumentiert. Im Formular-Editor kann beispielsweise mit der Funktion Format ein Fließkommawert gerundet und in eine Zeichenkette umgewandelt werden:

var

   a: Double;

   b: String;

begin

   a := 3.5537 * 12.77 - 0.56473;

   b := Format(a, 3);

end.

Funktionen können einen Wert zurückliefern (wie Format) oder einfach eine Aktion ausführen. Letztere werden in Pascal als Prozeduren bezeichnet. Im Berichtegenerator kommt beispielsweise diese Prozedur zum Einsatz, um eine Zeile auszugeben:

   PrintContent('Gewicht (kg)', '99.66', true);

Diese Prozedur hat drei Parameter, die mit Komma getrennt als sogenannte Argumente anzugeben sind. In der jeweiligen Referenz findet sich:

procedure PrintContent(const aLabel, aContent: String; AltColors: Boolean);

Aus der Erklärung in der Referenz ist zu entnehmen, dass man ein Label und einen Inhalt in Form von Strings angibt. Das können feste Strings wie im Beispiel oder auch Variablen sein. Als drittes Argument ist ein Wahrheitswert (true/false) anzugeben, der hier bestimmt, ob die Ausgabezeilen mit abwechselnder Farbhinterlegung erzeugt werden.

Eigene Funktionen und Prozeduren erstellen

In manchen Scripten - insbesondere bei Berichten - müssen im Script bestimmte Funktionen erstellt werden, die dann von EMIL aufgerufen werden. Bei Berichten geschieht das in drei Phasen:

1.Maske für die Berichtparameter aufbauen - DefineParameters

2.Eingegebene Berichtparameter prüfen - CheckParameters

3.Eigentlichen Bericht generieren - CreateContent

Eine eigene Prozedur wird so erstellt und muss innerhalb des äußersten begin/end-Blocks definiert sein:

procedure defineParameters;

var

   x: Integer;

begin

   ...

end;

Funktionen und Prozeduren können eigene var-Blöcke besitzen. Die dort definierten Variablen sind nur innerhalb des begin/end-Blocks der jeweiligen Funktion gültig (lokale Variablen). Sie können denselben Namen haben wie globale Variablen - was aber zu Verwirrung führt und daher vermieden werden sollte.

Eigene Funktionen sind immer dann sinnvoll, wenn ein identischer Code-Block mehrfach benötigt wird:

begin

   ...

   function doSomething(x: Integer): Integer;

   begin

     result := (x * 15 + 12 - 66) div 2;

   end;

   ...

   z := doSomething(564);

   v := doSomething(653);

   ...

end.

Jede Funktion hat die vordefinierte Variable result, der man den Rückgabewert zuweist. Sie hat den im Funktionskopf hinter dem : angegebenen Datentyp (im Beispiel Integer).

Bedingungen

Die bisherigen Beispiele wurden einfach zeilenweise vom begin zum end ausgeführt. Mit der Direktive if kann man Aktionen von Bedingungen abhängig machen:

   if (a > 6) or (b = 0) then

   begin

     c := 16;

     d := 'Hallo';

   end

   else

   begin

     c := -33;

     d := 'Otto';

   end;

Vergleichsoperatoren sind =, <>, >, <, <= und >=. Sie können über and und or miteinander verknüpft werden. Der else-Zweig ist optional. if-Bedingungen können verkettet werden:

if (a > 6) then

   text := 'größer sechs'

else if (a > 2) then

   text := 'größer zwei'

else

   text := 'kleiner oder gleich zwei';

Eine Besonderheit von Pascal Script: vor einem else darf kein Semikolon stehen. Bei einzeiligen Blöcken kann begin/end weggelassen werden.

Bei Double-Werten ist eine Gleichheitsabfrage wegen der begrenzten Genauigkeit besser durch einen Differenzvergleich zu ersetzen:

if (abs(f - 1) < 1E-13) then ...

Der Ansatz hier ist, die Differenz zwischen den zwei zu vergleichenden Zahlen zu bilden, das Vorzeichen mit Abs zu beseitigen und zu prüfen, dass diese Differenz kleiner ist als ein wählbares Epsilon, das z.B. mit 1E-13 (=10-13) sinnvoll gewählt werden kann. Hier machen selbst erfahrene Programmierer oft den fatalen Fehler, Fließkommazahlen direkt mit = zu vergleichen und erzeugen sporadisch auftretende Programmfehler.

Schleifen

In Berichten oder an Schnittstellen ist es erforderlich, Blöcke mehrfach auszuführen - z.B. für jeden Datensatz einer Datenmenge oder für jedes Segment einer Schnittstellennachricht. Es gibt verschiedene Schleifentypen; am häufigsten kommt die while-Schleife zum Einsatz:

var

   ds: TDataset;

begin

   ds := getDataset('... Abfrage', []);

   while not ds.eof do

   begin

     ...

     ds.next;

   end;

end.

Im Beispiel wird ein Dataset (also eine über SQL erzeugte Datenmenge) erzeugt und geöffnet. Dann wird der begin/end-Block so lange ausgeführt, bis das Dataset am Ende angekommen ist (alle Datensätze abgearbeitet). Innerhalb des Blocks wird über ds.next jeweils der nächste Datensatz angewählt. ds.eof (EOF = End Of File) ist zunächst false und wird beim Erreichen des Endes true, was die while-Schleife beendet.

Zählschleife über einen Bereich von Ganzzahlen mit for:

   for i := 0 to 9 do

   begin

     ...

   end;

Fehlerbehandlung

Nicht immer kann sichergestellt werden, dass ein Block fehlerfrei ausgeführt wird. Im Fall eines Scripts würde dies dazu führen, dass der Fehler an den Aufrufer (also EMIL) weitergereicht wird. Bei der Scoreberechnung führt ein Laufzeitfehler dann zur Anzeige von Error statt des Scores.

Pascal Script bietet dazu den try/except-Mechanismus:

try

   ...

   // Block, der fehlschlagen kann

   ...

except

   ...

   // Block, der nur ausgeführt wird, wenn etwas fehlschlägt

   ...

end;

Standardfunktionen im EMIL-Scripting

Folgende Funktionen sind unabhängig vom Kontext (Formular-Editor, FELIX, Berichte) im Scripting vorhanden:

IntToStr(integer): String
Wandelt einen Integer in einen String um. Beispiel: s := IntToStr(42);'42'.

StrToInt(string): Integer
Wandelt einen String in einen Integer um. Achtung: kann fehlschlagen! Bei ungültiger Eingabe wird eine Exception ausgelöst.

StrToIntDef(string, integer): Integer
Wandelt einen String in einen Integer um und liefert bei einem Fehler den zweiten Parameter als Vorgabewert zurück. Beispiel: i := StrToIntDef('abc', -1);-1.

StrToFloat(string): Double
Pendant zu StrToInt für Fließkommazahlen. Erwartet den Dezimalpunkt. Kann fehlschlagen.

FloatToStr(double): String
Pendant zu IntToStr für Fließkommazahlen.

Pos(string, string): Integer
Prüft, ob der erste Parameter im zweiten vorkommt und liefert die Position 1..n bzw. 0, wenn nicht gefunden. Beispiel: Pos('lo', 'Hallo')4.

Copy(string, integer, integer): String
Extrahiert einen Teil des ersten Parameters ab Zeichen start mit der Länge count. Beispiel: Copy('Hallo Welt', 7, 4)'Welt'.

Length(string): Integer
Liefert die Anzahl der Zeichen in einer Zeichenkette.

UpperCase(string): String
Wandelt den Parameter in Großbuchstaben um. Achtung: Umlaute werden nicht beachtet, dafür ist die Funktion sehr schnell.

LowerCase(string): String
Pendant zu UpperCase mit Kleinbuchstaben.

AnsiUpperCase(string): String
Wandelt den Parameter in Großbuchstaben um und beachtet dabei Umlaute.

AnsiLowerCase(string): String
Pendant zu AnsiUpperCase mit Kleinbuchstaben.

Trim(string): String
Entfernt führende und nachfolgende Leerzeichen aus einem String.

Sin(double): Double
Sinusfunktion auf Basis von 2π für einen Vollkreis (Bogenmaß).

Cos(double): Double
Cosinusfunktion auf Basis von 2π für einen Vollkreis (Bogenmaß).

Pi: Double
Konstante π.

Sqrt(double): Double
Quadratwurzel.

Round(double): Integer
Rundet einen Fließkommawert kaufmännisch zum nächsten Ganzzahlwert.

Trunc(double): Integer
Schneidet die Nachkommastellen ab und gibt den Ganzzahlanteil zurück. Negative Werte: Trunc(-3.7)-3.

Abs(double): Double
Beseitigt das Vorzeichen einer Zahl.