<< Klicken Sie, um das Inhaltsverzeichnis anzuzeigen >> Navigation: Technisches > EMIL Plattform Scripting (+) |
EMIL-Plus kann an vielen Stellen durch Scripting stark individualisiert und funktionell erweitert werden. Wie man solche Scripte erzeugt, ist im Folgenden beschrieben.
Wichtiger Hinweis: Wenn Sie hier überfordert oder unsicher sind, ist dies keine Schande! Das Erstellen von Scripts erfordert ein deutliches 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 absolut nicht jedermanns Ding. ITC bietet daher Trainings zum Scripting aber auch die komplette Erstellung von Scripten für Ihre Wünsche 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 ausführen. Man kann sie durchaus mit einem Filmscript vergleichen, der 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.
So kann ein EMIL-Script in z.B. die Werte aus verschiedenen Quellen (z.B. Feldern eines Formulars) ermitteln, damit etwas ausrechnen und dieses Ergebnis dann wieder einem Zielwert zuweisen. An anderen Stellen wie z.B. Berichten, kann eine Datenmenge (das ist eine Liste aus der Datenbank nach bestimmten Kriterien abgefragter Zeilen) durchgehen und daraus ein Dokument erzeugen, in dem Inhalte berechnet, zusammengestellt und an bestimmte Stellen im Dokument ausgegeben werden. Bei der Verarbeitung von Schnittstellendaten sucht der Script in den empfangenen Quelldaten nach Informationen an bestimmten Stellen, bereitet diese auf und überträgt diese 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 in der Schreibweise unterschieden, sodass für Pascal Script Hallo und hAlLo identisch sind. Dennoch sollte man zugunsten einer guten Lesbarkeit eine einheitliche Schreibweise verwenden. Umlaute und Leerzeichen dürfen nur innerhalb von Zeichenkettenvariablen vorkommen. Sonderzeichen wie +, -, *, / und Klammern werden als Rechenoperatoren interpretiert und sind natürlich in Variablennamen auch nicht zulässig. Man sollte sich generell bei Identifiern auf Buchstaben und Zahlen beschränken. Jede Variable muss zwingend mit einem Buchstaben beginnen.
Jeder Script besteht grundsätzlich aus zwei Blöcken, die zwingend in der folgenden Struktur abzubilden sind:
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, definiert sind und vermeidet so, dass man z.B. einer Variablen einen Wert zuweist, aber durch einen Schreibfehler mit einer anderen weiter rechnet. Auch wird verhindert, dass man versehentlich einer numerischen Variable einen Textwert zuweist. Zur besseren Lesbarkeit empfiehlt sich das CamelCasing oder zu Deutsch der Binnenmajuskel: HalloErna oder WertVorAenderung. Manche aber bevorzugen eine Schreibwise mit Underscores: hallo_erna oder wert_vor_aenderung. Beides ist gültig und einfach nur Geschmackssache. Die Variablendefinitionen hinter var können verschiedene Datentypen definieren:
var
Anzahl: Integer;
Score: Double;
Name: String;
Als Variablentypen stehen String, Integer, Double zur Verfügung.
•Integer ist ein Ganzzahlentyp, der Werte zwischen -2.147.483.648 und 2.147.483.647 speichern kann.
•Double kann eine Fließkommazahl zwischen etwa +/- 10300 mit einer Genauigkeit von 15-16 Dezimalstellen darstellen. Wichtig bei der Arbeit mit Fließkommazahlen ist, dass diese nicht notwendigerweise exakt gespeichert werden. So kann ein Wert 5.768 intern z.B. als 5.76800000000000001 vorliegen. Das muss man bei der Prüfung auf Gleicheit beachten und man sollte diese immer für die Ausgabe runden. Datumsangaben (mit oder ohne Zeit) werden in Scripten immer als Double verarbeitet und repräsentieren die Anzahl der Tage seit dem 30.12.1899, wobei der Teil hinter dem Komma den Bruchteil der 24h eines Tages und damit den Zeitanteil repräsentiert.
•Strings (das sind hier weder kosmische Phänomene noch spezielle Unterhosen, sondern einfach Zeichenketten) können zwischen 0 und rund zwei Milliarden Zeichen fassen. Grenzen setzt der Hauptspeicher des Systems.
Der eigentliche Script wird innerhalb des begin/end Blocks erwartet. Der Punkt hinter dem letzten end ist erforderlich, da dieses end das Ende des Scripts signalisiert. begin/end-Blöcke können verschachtelt werden. Alle Blöcke innerhalb des Haupt-Blocks müssen wie Kommandos mit ; abgeschlossen werden:
begin
...
begin
...
end;
...
end.
Natürlich ergibt das Verschachteln von Blöcken nur einen Sinn, wenn man z.B. Bedingungen oder Schleifen einführt. Eine der wichtigsten Operation ist die Zuweisung eines Werts an eine Variable und die Berechnung. Zuweisungen geschehen zur Unterscheidung gegen Vergleiche mit dem Operator :=:
var
a,b,c: Integer;
begin
a := 10;
b := -7;
c := a * 12 - b;
end.
Der obige Beispielscript definiert die Variablen a, b und c als Ganzzahlen, weist den Variablen a und b einen Wert zu und berechnet daraus den Wert c. Mit Fließkommazahlen (double) läuft es genauso, hier ist zu beachten, dass man immer den Dezimalpunkt verwenden muss:
var
a,b,c: Double;
begin
a := 10.6;
b := -7.35;
c := a * 12.88 - b;
end.
Zeichenketten aka Strings können auch miteinander verknüpft werden. Hier ist zu beachten, dass nur der + Operator zur Verfügung steht und Zeichenketten immer in einfache Hochkommata (auf der Tastatur Shift-#) eingeschlossen sein müssen. Gänsefüßchen sind hier nicht zulässig.
var
a,b,c: String;
begin
a := 'Hallo';
b := 'EMIL';
c := a + ' ' + b;
end.
Je nach Kontext sind bereits Variablen vordefiniert. So stehen in den Scripten des Fomulargenerators beispielsweise bereits alle numerischen Eingaben und alle Ankreuzfelder für die Berechnung zur Verfügung, ohne, dass diese explizit definiert werden müssen. Das erledigt EMIL vor Aufruf des Scripts. Ebenso sind im Bereich ITC-Connect Schnittstelle und Berichtegenerator bereits eine Vielzahl Dinge vordefiniert, die dann einfach direkt und ohne vorherige Definition genutzt werden können.
Hinweis zur Division: Bei Fließkommawerten ist der Operator / für die Division zu verwenden, während Integer (also Ganzahlwerte) immer mit dem Operator div geteilt werden.
Funktionen verwenden
Neben den standard Operatoren stehen zusätzliche Funktionen zur Verfügung, um Variablen bestimmten Operationen zu unterwerfen. Diese sind aber vom jeweiligen Kontext abhängig und nicht notwendigerweise in jedem Kontext verfügbar. Sie finden sich in den jeweiligen Funktionsreferenzen. So kann man beispielsweise bei der Scoreberechnung im Formulargenerator einen Fließkommawert über eine Funktion Format runden und in einen für die Ausgabe benötigten Text umwandeln:
var
a: double;
b: String;
begin
a := 3.5537 * 12.77 - 0.56473;
b := Format(a,3);
end.
Nachdem der Score in a berechnet wurde, wird er mit der Format-Funktion auf drei Stellen gerundet und als Zeichenkette der Variablen b zugewiesen.
Funktionen können einen Wert zurückliefert (wie Format im Beispiel) oder auch einfach nur eine Funktion ausführen. Letztere nennt man in der Sprache Pascal Prozeduren. Man kann aber auch Funktionen aufrufen, ohne den Rückgabewert zuzuweisen, was aber in den meisten Fällen wenig Sinn ergibt.
Im Umfeld der Berichtegenerators beispielsweise kommt diese Prozedur zum Einsatz, um eine Zeile in das Berichtsdokument 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 eine
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 aber auch Variablen sein. Als drittes Argument ist ein Wahrheitswert (true/false) anzugeben, der hier bestimmt, ob die Ausgabezeilen mit abwechselnder Farbhinterlegung ausgegeben werden sollen.
Funktionen im Script erstellen
In manchen Scripten, insbesondere bei Berichten, müssen im Script bestimmte Funktionen erstellt werden, die dann von EMIL aufgerufen werden. Die Erstellung eines Berichts arbeitet im gegensatz zur Scoreberechnung nämlich in mehreren Schritten. Während die Scoreberechnung bei Klick auf den OK Button des Formulars getriggert wird, sind beim Breicht diese Phasen vorhanden:
1.Maske für die Berichtparameter aufbauen DefineParameters()
2.Eingegebene Berichtparameter prüfen CheckParameters()
3.Eigentlichen Bericht generieren CreateContent()
Eine solche Funktion wird so erstellt und muss innerhalb des äußersten begin end Blocks definiert sein:
procedure defineParameters;
var
x: integer
begin
...
end;
Funktionen bzw. Prozeduren können eigene var Definitionen haben. Die dort definierten Variablen sind nur innerhalb des begin end Blocks der jeweiligen Funktion oder Prozedur gültig und außerhalb nicht verfügbar. Sie können auch gleiche Namen haben, wie außerhalb definierte Variablen, dies kann aber zu Verwirrung führen, daher sollte man die Benennung in Scripts zugunsten der Verständlichkeit besser grundsätzlich eindeutig machen.
Funktionen und Prozeduren können übrigens auch für eigene Zwecke in allen Scripts definiert und verwendet werden. Die Erstellung einer Funktion ist immer dann sinnvoll, wenn man einen identischen Code-Block mehrfach benötigt:
begin
...
function doSomething(x: integer): integer;
begin
result := (x * 15 + 12 - 66) div 2;
end;
...
z := doSomething(564);
v := doSomething(653);
...
end.
Hier sieht man, wie man einer Funktion ein Ergebnis zuweist: Jede Funktion hat die vordefinierte Variable result, die den für die Rückgabe definierten Datentypen hat (hier Integer).
Bedingungen
Die bisherigen Beispiele wurden einfach zeilenweise vom Begin zum End ausgeführt. Ein Script kann aber noch mehr. Man kann zum Beispiel bestimmte Aktionen von Bedingungen abhängig machen. Dazu dient die Dirketive if:
if (a > 6) or (b = 0) then
begin
c := 16;
d := 'Hallo';
end
else
begin
c := -33;
d := 'Otto';
end;
Hier kommen Vergleichsoperatoren wie =, <>, > und < sowie <= und >= zum Einsatz. Diese können über and und or miteinander verknüpft werden. Obiges Beispiel fragt daher ab, ob a größer als sechs oder b gleich Null ist.
Der else-Zweig ist optional, denn man kann auch einfach einen Block nur aus führen oder nicht. Ebenso können if Bedingungen 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';
Man sieht in diesem Beispiel neben der Verkettung mehrerer if Direktiven auch, dass man bei einzeiligen Blöcken das begin end weglassen kann. Eine Besonderheit von Pascal Script ist, dass man das Semikolon von dem else immer weglassen muss. Ist einfach so.
Bei Double Werten ist auf Grund der wenn auch hohen, aber begrenzten Genauigkeit zu beachten, dass eine Abfrage auch Gleicheit besser durch einen solchen Konstrukt ersetzt wird:
if (abs(f - 1) < 1E-13) then ...
Der Ansatz hier ist, die Differenz zwischen den zwei zu vergleichenden Zahlen zu bilden, das Vorzeichen von der Differenz 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 vermeintlich erfahrenere Programmierer sehr oft den fatalen Fehler, Fließkommazahlen direkt mit = zu vergleichen und erzeugen somit sporadisch ebenso fatale Programmfehler. Die im Beispiel verwendete Funktion Abs entfernt das Vorzeichen des numerischen Arguments und gehört zum Standard-Funktionsumfang der Pascal Script Sprache.
Schleifen
In Berichten oder an Schnittstellen ist es erforderlich, Blöcke mehrfach, zum Beispiel für jeden selektierten Datensatz, auszuführen. dies ist bei Berichten erforderlich, um jeden der selektierten Datensätze in das Dokument zu übertragen. Es gibt verschiedene Schleifentypen im Scripting, von denen aber heir die While-Schleife zum Einsatz kommt:
var
ds: TDataset;
begin
ds := getDataset('... Abfrage',[]);
while not ds.eof do
begin
...
ds.next;
end;
Hier wird ein (im Berichtescripting, aber nicht bei der Scoreberechnung) verfügbares Dataset, also eine über eine SQL Abfrage erzeugte Datenmenge ds erzeugt und geöffnet. Dann wird der begin end Block so lange ausgeführt, bis diese Datenmenge am Ende angekommen ist, um alle Datensätze abzuarbeiten. Innerhalb jeder Ausführung des Blocks wird über ds.next jeweils der näcste Datensatz angewählt. ds.eof (EOF steht für End Of File) ist zunächst true und wird bei erreichen des Endes der Datenmenge false, was die Ausführung der while Schleife dann auch beendet.
Fehlerbehandlung
Nicht immer kann man sicher stellen, dass ein Block fehlerfrei ausgeführt wird. Im Fall eines Scripts würde dies dazu führen, dass der Fehler an den Aufrufer und damit an die Funktionalität in EMIL weitergereicht wird, die den Script aufruft. Diese scheitert dann und erzeugt ihrerseits eine Fehlermeldung. Bei der Scoreberechnung beispielsweise führt ein Laufzeitfehler dann zur Anzeige von Error statt des Scores.
Es ist essentiell, dass man zum einen daran denkt, dass Fehler auftreten können und auch dass man diese adäquat behandelt.
Pascal scripting bietet dazu den Try/Except Mechanismus an.
try
...
// Block, der fehlschlagen kann
...
except
...
// Block der nur ausgeführt wird, wenn etwas fehlschlägt
...
end
Wichtige Standardfunktionen im EMIL-Scripting
Folgende Funktionen sind unabhängig vom Kontext im Scripting vorhanden:
IntToStr(integer): string
Wandelt einen Integer in einen String um
StrToInt(string): integer
Wandelt einen String in einen Integer um, kann fehlschlagen!
StrToIntDef(string, integer): intger
Wandelt einen String in einen Integer und liefert den zweiten Parameter als Vorgabe, wenn es fehlschlägt
StrToFloat(double): string
Pendant zu StrToInt für Fließkommazahlen.
FloatToStr(double): string
Pendant zu IntToStr für Fließkommazahlen.
Pos(string,string): integer
Prüft, ob der zweite Parameter im ersten vorkommt und liefert die Position 1..n und 0 wenn nicht gefunden
Copy(string,integer,integer): string
Extrahiert einen teil des ersten Parameters ab Zeichen zweiter Parameter und mit der Länge dritter Parameter
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, beachtet 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 PI für einen Vollkreis
Cos(double): double
Cosinusfunktion auf Basis von 2 PI für einen Vollkreis
Pi: double
Konstante PI
Sqrt(double): double
Quadtartwurzel
Round(double): integer
Rundet einen Fließkommawert zum nächsten Ganzzahlwert
Trunc(double): integer
Schneidet die Nachkommastellen eines Fließkommawerts ab und gibt den Ganzzahlanteil zurück
Abs(double): double
Beseitigt das Vorzeichen einer Zahl