<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7017123333978978050</id><updated>2012-02-16T09:44:21.827+01:00</updated><category term='Programmierkunst'/><category term='Betrachtungen'/><category term='Persönliches'/><category term='Astrologie'/><title type='text'>Rüdiger Plantiko</title><subtitle type='html'>Auf diesen Webseiten logge ich meine aktuellen Projekte zu den Themen Astrologie und Softwareentwicklung. Darüberhinaus enthält der Blog in unregelmässiger Folge Gedanken zum aktuellen Zeitgeschehen und Betrachtungen allgemeinerer Natur.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default?start-index=101&amp;max-results=100'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>105</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-2442544689574663385</id><published>2012-02-03T20:27:00.028+01:00</published><updated>2012-02-05T19:35:11.763+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Interesse am Computer wecken und fördern</title><content type='html'>Was ist es eigentlich, was mich an Computern, insbesondere der Programmierkunst interessierte und bis heute begeistert? Diese Frage stellte sich mir, als für meinen Sohn das Thema "Informatik an der Schule" aufkam. Was hat mich damals, als ich als Vierzehnjähriger in die Gedankenwelt der Informatik eintrat, besonders an ihr fasziniert? Was könnte auch heute für Schüler in diesem Alter wirklich interessant sein?&lt;br /&gt;&lt;br /&gt;In den späten siebziger Jahren, lange vor dem Erfolg des PC, waren Computer noch nicht so omnipräsent wie sie es heute sind. Um sie kennenzulernen, musste man sich selbst zu ihnen hinbewegen. Ein heutiger Lehrer, der in mittleren Schulklassen die Informatik unterrichten soll, sitzt dagegen vor Schülern, die fast alle einen Facebook- und Twitter-Account haben, die in den Pausen nicht mehr gummitwisten oder Fussball spielen, sondern, wenn sie es dürfen, auf ihren Smartphones Filmchen anschauen und Mails, Kurznachrichten und Kommentare lesen und schreiben. Schüler, für die &lt;i&gt;die Benutzung eines Computers&lt;/i&gt; ganz selbstverständlich ist. &lt;br /&gt;&lt;br /&gt;Einige dieser Schüler dürften mit mir gemeinsam haben, dass sie die blosse &lt;i&gt;Bedienung&lt;/i&gt; eines Computerprogramms uninteressant finden. Das sind die Schüler, denen ich mich nahe fühle. Sie wollen etwas herausfinden, grübeln, forschen, eine Idee wirklich werden lassen, eine nach Regeln ablaufende Ordnung in Gang setzen - nicht ein Programm bedienen. &lt;br /&gt;&lt;br /&gt;Vielleicht haben oder hatten sie einmal eine Game-Phase, in der sie sich freiwillig in die Regeln einer künstlichen Welt hineinbegeben, die ein Programmierer sich für sie ausgedacht hat. Aber diese Phase wird aufhören, sobald sie die &lt;i&gt;Unfreiheit&lt;/i&gt; erkennen, in der sie sich als Gamer befinden. Ein paar Knöpfchen zu drücken, ein paar ausprogrammierte, vorgedachte Tricks herauszufinden, die ein anderer ersonnen hat: Das stillt nicht auf Dauer die wissenschaftliche Neugier, den Erkenntnisdurst und den gerade in der Pubertät mächtig aufkommenden Wunsch, sich von Fremdbestimmung zu emanzipieren, sich von Grenzen zu befreien, die andere einem auferlegen. Nein - viel mehr Spass macht es doch, sich frei seines eigenen Verstandes zu bedienen und &lt;i&gt;sich den Computer anzueignen&lt;/i&gt; &amp;ndash; ihn nach eigenen, selbst erdachten Vorstellungen für das einzusetzen, was er gut kann: Systematisch Dinge abarbeiten oder durchsuchen, einen Status überwachen, Ereignisse melden, etwas berechnen, Geräte oder mechanische Vorgänge steuern.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/-T_u_XB5icPg/TyxGTV_Hn5I/AAAAAAAAAPI/nlJsgfafri0/s1600/kim_1.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 197px; height: 256px;" src="http://3.bp.blogspot.com/-T_u_XB5icPg/TyxGTV_Hn5I/AAAAAAAAAPI/nlJsgfafri0/s400/kim_1.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5705012126104919954" /&gt;&lt;/a&gt;Was mich damals in diese Richtung bewegte, war der Wunsch, die Arbeitsweise des Computers zu durchdringen und kennenzulernen. Natürlich waren damalige Rechner im Vergleich zu heutigen geradezu niedlich. Der Einplatinenrechner &lt;a href="http://de.wikipedia.org/wiki/KIM-1"&gt;KIM-1&lt;/a&gt; wäre mein erster Computer geworden, wenn meine Eltern ihn mir geschenkt hätten. Die Schaltkreise lagen offen. Er hatte einen sehr guten Mikroprozessor, den &lt;a href="http://de.wikipedia.org/wiki/MOS_Technologies_6502"&gt;6502&lt;/a&gt;[1], 1 KByte Arbeitsspeicher, eine Tastatur mit den Hexziffern &lt;tt&gt;0&lt;/tt&gt; bis &lt;tt&gt;9&lt;/tt&gt; und &lt;tt&gt;A&lt;/tt&gt; bis &lt;tt&gt;F&lt;/tt&gt;, und als Ausgabemedium reichten sechs Siebensegmentanzeigen: Vier für den aktuellen Speicherplatz und zwei für den Inhalt. Unter Bastlern waren auch seine Schnittstellen zur Aussenwelt sehr beliebt, an die man Fotozellen, Thermometer, Lautsprecher, Steuerungen für kleine Modelle mit Elektromotoren, Modelleisenbahnen und anderes anschliessen konnte. &lt;br /&gt;&lt;br /&gt;Bis heute gibt es Fans und Nostalgiker, die für diesem Prozessortyp Assembler und Emulatoren schreiben. Die beeindruckende Seite &lt;a href="http://www.visual6502.org/JSSim/index.html"&gt;visual6502.org&lt;/a&gt; kann sogar für jeden Taktzyklus des zu durchlaufenden Assemblerprogramms die Potentiale der einzelnen Leiterbahnen darstellen (die sich die Autoren der Seite in wochenlanger Detektivarbeit durch Microphotographie des Chips erschlossen haben)!&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/-wrScl1s8Nd0/TyxWR8CZaTI/AAAAAAAAAPU/Xbu1vCyWJ6I/s1600/chip.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 180px; height: 257px;" src="http://3.bp.blogspot.com/-wrScl1s8Nd0/TyxWR8CZaTI/AAAAAAAAAPU/Xbu1vCyWJ6I/s400/chip.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5705029694145521970" /&gt;&lt;/a&gt;Ich war sehr neugierig auf Computer und wollte vor allem wissen, wie sie eigentlich funktionieren. Ich gehörte zu den Käufern der allerersten Nummer der Computerzeitschrift CHIP (Sept. 1978), weil mich der Titel ansprach: &lt;i&gt;Der Computer, das unbekannte Wesen&lt;/i&gt;. Die Artikel gingen ziemlich tief ins Detail und eröffneten mir eine faszinierende Gedankenwelt. Durch die Beschäftigung mit den dort besprochenen Rechnern und Assemblerprogrammen wurde mir langsam die Funktionsweise eines Computers klar: Was ist der Datenbus, was ist der Adressbus, wie wird Speicher angesprochen und ausgelesen, welche (logischen) Bestandteile hat die CPU selbst, wie erfolgt die Kommunikation mit der Aussenwelt, d.h. den Peripheriegeräten? Dadurch, dass ich die Bestandteile des Computers näher kennenlernte, wuchs mein Interesse, den Computer zu betreiben - und das hiess für mich: zu programmieren.&lt;br /&gt;&lt;br /&gt;Ohne dass ich zum damaligen Zeitpunkt etwas von Dingen wie "Turingmaschinen" wusste (den Begriff lernte ich erst sehr viel später kennen), stellte ich mir einen laufenden Computer vor, wie er sich Byte für Byte durch den Speicher durcharbeitet und versucht, die Inhalte, als Befehle zu interpretieren und auszuführen. Das ist ein bedeutendes gedankliches Modell, das auch in der mathematischen Grundlagenforschung eine Rolle spielt - man kann mit einer solchen Maschine als rein gedanklichem Modell schwierige theoretische Fragen wie das &lt;a href="http://de.wikipedia.org/wiki/Entscheidbar"&gt;Entscheidungsproblem&lt;/a&gt; behandeln. Es ist der Kontaktpunkt, bei dem ein "auskristallisierter" Gedanke schliesslich Realität in Form einer Sequenz von einzelnen Schritten wird, die so klar sind, dass sie auch eine Maschine ausführen könnte. &lt;br /&gt;&lt;br /&gt;Die Kernfrage eines Computerkurses könnte ganz schlicht sein:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Wie funktioniert ein Computer?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Natürlich wäre Assembler für einen durchschnittlichen Schüler, der normalerweise nicht so viel Interesse an diesem Thema hat, eine viel zu tiefgehende Angelegenheit. Aber wenn die Schüler nach Absolvieren des Kurses ein Grundverständnis davon haben, wie eigentlich ein Computer arbeitet, dann wäre doch schon viel erreicht! Mehr - viel mehr - als wenn sie nur gelernt hätten, ein bestimmtes heute gängiges Computerprogramm zu bedienen, also Tastenkombinationen oder Menüpunkte zu erlernen, die sich irgendein Programmierer ausgedacht hat. Die Bedienung von Büro-Software erlernt man nebenbei später im Beruf sowieso. Es wäre m.E. zu schade, die wertvolle Zeit eines Schulkurses über Computer damit zu verbringen.&lt;br /&gt;&lt;br /&gt;Aber um die Idee einer Turingmaschine kennenzulernen, reicht schon ein einfaches Simulationsprogramm. Das folgende &lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.turing.org.uk/turing/scrapbook/tmjava.html"&gt;Turing Scrapbook&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;ist ein solches Simulationsprogramm. Man könnte diese Webseite ins Deutsche übersetzen und leicht anpassen, dann könnte sie eventuell etwas für den Unterricht sein. Man kann mit "load" ein Programm in das Textfeld unten links laden und dieses mit "step" schrittweise ausführen. Aber vor allem: Man kann in das Textfenster auch ein eigenes "Programm" für die Turingmaschine eingeben und ausführen.&lt;br /&gt;&lt;br /&gt;Vorstellbar wäre auch eine Mini-Prozessor-Simulation: Ein Prozessor könnte zwei Arbeitsregister haben, sowie Befehle, um Bytes vom Speicher in diese Register zu laden, etwas mit ihnen zu machen (z.B. die beiden Register zu addieren und das Ergebnis in das erste Register zu schreiben), die Registerinhalte in Speicheradressen zu schreiben und bedingte bzw. unbedingte Verzweigungen, die also den Befehlszeiger verändern. Gerade der &lt;a href="http://www.6502.org/"&gt;6502&lt;/a&gt;-Prozessor ist für solche Vorhaben ideal geeignet. &lt;br /&gt;&lt;br /&gt;Sinnvoll in Hinblick auf die Grundfrage &lt;i&gt;Wie funktioniert ein Computer?&lt;/i&gt; könnte dazu auch sein:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Bis zur Elektronik herunter zu verstehen, wieso man eigentlich mit zwei Transistoren einen Speicher bauen kann, der sich 1 bit merkt (&lt;a href="http://de.wikipedia.org/wiki/Flipflop"&gt;Flipflop&lt;/a&gt;). Wie geht das eigentlich?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Man könnte einen einfachen Einplatinencomputer zusammenbauen, dabei seine Bestandteile diskutieren und kennenlernen und ihn schliesslich in irgendeinem Modell einsetzen. Das wäre etwas für eine Klasse, die besonders gern bastelt oder Modelle erstellt.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Mit dem Bit hat man das Kapitel Dualzahlen aufgeschlagen. Man könnte Rechnungen zur Basis 2 und 16 behandeln, dabei insbesondere die Hexadezimalziffern.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Vom Bit ausgehend, kann man das ganze Kapitel der Logik und insbesondere der  Booleschen Algebra anreissen: Operationen mit Bits. UND, ODER, NICHT. Dass man etwa alle Operationen aus einer einzigen Operation herstellen kann, dem NAND (Nicht-Und). Die De-Morganschen-Theoreme. Wahrheitstafeln usw.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Wenn Interesse da ist, eine Programmiersprache zu erlernen, gibt es gute und weniger gute Wahlen. Eine gute Wahl wäre Logo (auch hier gibt es eine Webseite zum Üben: &lt;a href="http://logo.twentygototen.org/"&gt;http://logo.twentygototen.org/&lt;/a&gt; ). Denn Logo wurde genau für diesen Zweck - zum Kennenlernen: wie geht eigentlich Programmieren, wie fühlt es sich an? - konzipiert. Auch eine gute Wahl wäre Forth, da lernt man auch gleich etwas über die Datenstruktur des Stapels und wie diese nutzbringend eingesetzt werden kann. Eine schlechte Wahl wäre sicher Basic - in all seinen Varianten. Sprachen wie Java, C# oder C++ fallen vermutlich aus, da sie nicht schnell genug zu erlernen sind.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Ich bin kein Pädagoge. Daher weiss ich nicht, für welche Altersstufen welche der angerissenen Themen "richtig" sind. Ich bin mir aber sicher, dass die hier beschriebenen Wege vielen Schülern ein Interesse am Computer nahebringen dürften - viel eher als wenn man ihnen beibringt, Softwareprodukte wie Word und Excel zu bedienen. In letzterem Fall lernt man nicht wirklich etwas über Informatik, sondern macht nur einen Ausflug in das Hirn einiger Redmonder Programmierer.&lt;br /&gt;&lt;br /&gt;Völlig abwegig finde ich auch das Argument "wirtschaftlicher Erfordernisse". Die Schule dient nicht wirtschaftlichen Interessen, sondern soll die geistige Entwicklung der Schüler begleiten und fördern. Es steht der von seinen persönlichen Interessen und Zielen motivierte Schüler im Vordergrund, den es bei diesen Interessen anzusprechen, zu fördern und dabei möglichst vielseitig auszubilden gilt. Dass davon auch die Wirtschaft profitiert, ist schön für sie, aber nur ein indirekter Effekt. Eine Einmischung steht ihr trotzdem nicht zu.&lt;br /&gt;&lt;br /&gt;[1] Das Herz des KIM-1, der Mikroprozessor &lt;a href="http://de.wikipedia.org/wiki/MOS_Technologies_6502"&gt;6502&lt;/a&gt; hatte neben dem Akkumulator nur zwei Register, glänzte dafür aber durch eine hohe Zahl von Adressierungsarten. Der Befehl &lt;tt&gt;LDA&lt;/tt&gt;, um ein Byte in den Akkumulator zu laden, kennt z.B. nicht weniger als acht verschiedene Adressierungsarten (siehe &lt;a href="http://www.obelisk.demon.co.uk/6502/reference.html#LDA"&gt;http://www.obelisk.demon.co.uk/6502/reference.html#LDA&lt;/a&gt;).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-2442544689574663385?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/2442544689574663385/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=2442544689574663385' title='4 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/2442544689574663385'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/2442544689574663385'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2012/02/interesse-am-computer-wecken-und.html' title='Interesse am Computer wecken und fördern'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-T_u_XB5icPg/TyxGTV_Hn5I/AAAAAAAAAPI/nlJsgfafri0/s72-c/kim_1.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-8417836179891370251</id><published>2012-01-30T12:27:00.023+01:00</published><updated>2012-01-31T07:33:42.372+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Von der Regel zum semantischen Modell</title><content type='html'>In diesem Blog will ich beschreiben, wie man mit wenig Aufwand einen Parser für eine selbstdefinierte domänenspezifische Sprache schreiben kann, der die vom Benutzer eingegebenen Regeln in passende ABAP-Datenstrukturen konvertiert. Ich verwende ein Beispiel, das ich hier schon öfter herangezogen habe: Die Verpackung von Lieferungen in Paletten.&lt;br /&gt;&lt;br /&gt;Eine Palette kann den Inhalt einer oder mehrerer Lieferungen haben. Paletten können aber auch zu grösseren Verpackungseinheiten zusammengefasst werden, auch gemischt mit Lieferungen, und auch diese zusammengefasste Einheit ist wieder eine Palette. Vom Datenmodell ist eine Palette also ein &lt;a href="http://de.wikipedia.org/wiki/Kompositum_%28Entwurfsmuster%29"&gt;Kompositum&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Wenn wir jeder Palette und jeder Lieferung eine Belegnummer geben und den Inhalt einer Palette darüberhinaus mit einer Positionsnummer durchnumerieren, können wir folgendes beispielhaftes Datenmodell in der Sprache ABAP formulieren:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;types:&lt;br /&gt;&lt;br /&gt;  ty_doc_number(10) type c,&lt;br /&gt;  ty_item_number(6) type c,&lt;br /&gt;  ty_pallet_item_type(2) type c,&lt;br /&gt;  ty_doc_numbers type standard table of ty_doc_number &lt;br /&gt;    with non-unique default key,&lt;br /&gt;&lt;br /&gt;  begin of ty_pallet_item,&lt;br /&gt;      pallet_no   type ty_doc_number,&lt;br /&gt;      item_no     type ty_item_number,&lt;br /&gt;      type        type ty_pallet_item_type,&lt;br /&gt;      content     type ty_doc_number,&lt;br /&gt;    end of ty_pallet_item,&lt;br /&gt;  ty_pallets type standard table of ty_pallet_item&lt;br /&gt;    with key pallet_no item_no,&lt;br /&gt;&lt;br /&gt;   begin of ty_model,&lt;br /&gt;    pallet_numbers    type ty_doc_numbers,&lt;br /&gt;    delivery_numbers  type ty_doc_numbers,&lt;br /&gt;    pallets           type ty_pallets,&lt;br /&gt;  end of ty_model.&lt;/pre&gt;&lt;br /&gt;Als mögliche Typen einer Position kommen nur "Palette" und "Lieferung" in Frage. Dafür prägen wir, wenn wir schon dabei sind, noch eine Konstante aus:&lt;br /&gt;&lt;pre class="sh_abap"&gt;constants: begin of gc_pallet_item_type,&lt;br /&gt;  pallet   type ty_pallet_item_type value 'PA',&lt;br /&gt;  delivery type ty_pallet_item_type value 'DL',&lt;br /&gt;end of gc_pallet_item_type.&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Eine Instanz von &lt;tt&gt;ty_model&lt;/tt&gt; ist demnach ein strukturiertes Datenobjekt, das aus drei Tabellen besteht: Je eine Tabelle enthält die benötigten oder verwendeten Lieferungs- und Palettennummern, und die dritte Tabelle listet positionsweise den Inhalt jeder Palette auf. &lt;br /&gt;&lt;br /&gt;Eine Instanz von &lt;tt&gt;ty_model&lt;/tt&gt; repräsentiert somit eine &lt;i&gt;Packregel&lt;/i&gt;. Sie gibt an, auf welche Weise ein Vorrat von &lt;i&gt;n&lt;/i&gt; Lieferungen auf &lt;i&gt;m&lt;/i&gt; Paletten zu verteilen bzw. in &lt;i&gt;m&lt;/i&gt; Paletten zu organisieren ist.&lt;br /&gt;&lt;br /&gt;Eine nach meiner Ansicht für den Benutzer angenehme Notation einer solchen Packregel würde die Paletten und Lieferungen mit Ordinalzahlen durchnumerieren und Paletteninhalte in Klammern spezifizieren. Ein einfacher Fall: &lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;1. Palette (&lt;br /&gt;  1. Lieferung&lt;br /&gt;  2. Lieferung )&lt;/pre&gt;&lt;br /&gt;würde also vorschreiben, den Inhalt von zwei Lieferungen in einer Palette zu verpacken.&lt;br /&gt;&lt;br /&gt;Ein wenig komplexer wäre schon der hierarchische Fall: &lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;1. Palette (&lt;br /&gt;  1. Lieferung&lt;br /&gt;  2. Palette )&lt;br /&gt;2. Palette (&lt;br /&gt;  2. Lieferung&lt;br /&gt;  3. Lieferung )&lt;/pre&gt;&lt;br /&gt;Hier kommt die 2. Palette in zwei Notationen vor: Einmal als blosse Referenz - indem angegeben ist, dass sie zum Inhalt der 1. Palette gehört - und ein weiteres Mal, um ihren Inhalt zu spezifizieren. Es soll aber auch erlaubt sein, die zweite Notation geschachtelt zu verwenden, etwa so:&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;1. Palette (&lt;br /&gt;  1. Lieferung&lt;br /&gt;  2. Palette (&lt;br /&gt;    2. Lieferung&lt;br /&gt;    3. Lieferung&lt;br /&gt;    )&lt;br /&gt;  )&lt;/pre&gt;&lt;br /&gt;Das ist dieselbe Verpackungsvorschrift wie die vorherige - nur anders notiert.&lt;br /&gt; &lt;br /&gt;Zeilenvorschub soll keine andere Rolle haben als anderer Leerraum auch. Ausserdem wäre es noch schön, wenn man abgekürzte Wörter wie "Lief" und "Pal" verwenden kann, um sich Schreibarbeit zu ersparen:&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;1. Pal ( 1. Lief 2. Lief )&lt;/pre&gt;&lt;br /&gt;Schliesslich soll es möglich sein, nacheinander mehrere Packregeln aufzuführen.&lt;br /&gt;&lt;br /&gt;Wie kann man nun mit möglichst wenig Aufwand einen Übersetzer schreiben, der eine solche, &lt;i&gt;frei erfundene&lt;/i&gt; Notation in das obige ABAP-Datenmodell transformiert? &lt;br /&gt;&lt;br /&gt;In &lt;a href="http://ruediger-plantiko.net/ometa/"&gt;oMeta&lt;/a&gt; würde man nur die folgenden acht Zeilen benötigen, um einen Parser für die oben beschriebene Syntax zu definieren:&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;ometa HandlingUnitDefinition &amp;lt;: Parser {&lt;br /&gt;  expr        = fullPallet+ end,      &lt;br /&gt;  fullPallet  = pallet "(" content ")",&lt;br /&gt;  pallet      = ordnum ( "Palette"  | "Pal" | "SSCC" ),&lt;br /&gt;  content     = (spaces contentPart)+, &lt;br /&gt;  contentPart = fullPallet | delivery | pallet,&lt;br /&gt;  delivery    = ordnum ( "Lieferung" | "Lief" | "LF"  ),&lt;br /&gt;  ordnum      = spaces digit+ "."&lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Auf die Details der Syntax von oMeta will ich hier nicht eingehen &amp;ndash; ich habe die wesentlichen Züge dieser Sprache bereits in meinem Blog &lt;a href="http://ruediger-plantiko.blogspot.com/2010/11/eine-objektorientierte-metasprache.html"&gt;Eine objektorientierte Metasprache&lt;/a&gt; beschrieben. Hier soll es um die Integration von oMeta in ABAP gehen - und darum, um welche semantischen Aktionen man diese Definition noch erweitern muss, um eine Abbildung in ABAP-Daten zu erhalten.&lt;br /&gt;&lt;br /&gt;Da es (noch! siehe [2]) den in ABAP eingebauten JavaScript-Interpreter &lt;tt&gt;CL_JAVA_SCRIPT&lt;/tt&gt; gibt, ist es auch möglich, die oMeta/JS-Implementierung in ABAP zu verwenden. Als Basis dafür habe ich den Subroutinen-Pool &lt;a href="http://pastebin.com/hunE6vsy"&gt;Z_OMETA_BASE&lt;/a&gt; geschrieben. Ich habe extra die Form des Subroutine-Pools gewählt, der leicht mit Copy &amp; Paste in ein (fast) beliebiges SAP-System übernommen werden kann.&lt;br /&gt;&lt;br /&gt;Aus dem Programm &lt;a href="http://pastebin.com/TkugQTHq"&gt;Z_SEMANTIC_MODEL_EXAMPLE&lt;/a&gt;, das diese Übersetzung in ABAP leistet,  schauen wir uns einmal die zentrale Routine &lt;tt&gt;rule_to_model&lt;/tt&gt; an, die eine als String nach obigen Regeln eingegebene Verpackungsregel in eine Instanz von &lt;tt&gt;ty_model&lt;/tt&gt; konvertiert: &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;* --- Apply parser to input, populating the semantic model&lt;br /&gt;form rule_to_model using iv_rule type string&lt;br /&gt;                   changing es_model type ty_model&lt;br /&gt;                            ev_parse_errors type flag.&lt;br /&gt;&lt;br /&gt;  data: lv_error_code type i,&lt;br /&gt;        lv_result type string.&lt;br /&gt;&lt;br /&gt;  clear es_model.&lt;br /&gt;&lt;br /&gt;* Bind the semantic model&lt;br /&gt;  perform bind(z_ometa_base)&lt;br /&gt;    using 'pallets.model' es_model.&lt;br /&gt;&lt;br /&gt;* Execute the parser&lt;br /&gt;  perform match(z_ometa_base)&lt;br /&gt;    using 'HandlingUnitDefinition' 'expr' iv_rule&lt;br /&gt;    changing lv_result lv_error_code.&lt;br /&gt;&lt;br /&gt;  if lv_error_code &lt;&gt; 0.&lt;br /&gt;    ev_parse_errors = abap_true.&lt;br /&gt;    message 'The rule entered is syntactically incorrect' type 'I'.&lt;br /&gt;  else.&lt;br /&gt;    ev_parse_errors = abap_false.&lt;br /&gt;    perform normalize_model changing es_model.&lt;br /&gt;  endif.&lt;br /&gt;&lt;br /&gt;endform.                    "rule_to_model&lt;/pre&gt;&lt;br /&gt;Das aktuelle ABAP-Datenobjekt, das gefüllt werden soll, muss dem Interpreter bekannt gemacht werden. Durch Aufruf der &lt;tt&gt;bind()&lt;/tt&gt;-Methode wird in JavaScript ein Proxy-Objekt erzeugt, auf das man mit JavaScript zugreifen kann. Ist dies gemacht, erfolgt der Aufbau der Daten durch Aufruf der Regel &lt;tt&gt;expr&lt;/tt&gt; (zuvor generierten) Parsers &lt;tt&gt;HandlingUnitDefinition&lt;/tt&gt;. &lt;br /&gt;&lt;br /&gt;Mit der obigen Grammatik &lt;tt&gt;HandlinUnitDefinition&lt;/tt&gt; allein wird das natürlich nicht funktionieren. Denn sie beschreibt ja nur die Syntax. Sie muss noch um die nötige Semantik angereichert werden. Das macht man in &lt;tt&gt;oMeta&lt;/tt&gt; mit den sogenannten &lt;a href="http://www.informatik.htw-dresden.de/~beck/Compiler/doc/Sematikroutinen.html"&gt;semantischen Aktionen&lt;/a&gt;. Wenn eine Regel passt, kann man, von einem &lt;tt&gt;-&gt;&lt;/tt&gt; gefolgt, Angaben im Quellcode der Hostsprache des Parsers (hier JavaScript) machen, die in diesem Fall auszuführen sind. Das Ergebnis der Evaluation einer solchen semantischen Aktion kann darüberhinaus in Form von Symbolen in anderen Regeln abgegriffen und in deren semantischen Aktionen weiterverwendet werden.&lt;br /&gt;&lt;br /&gt;Hier sind nun mit den Regeln für &lt;tt&gt;delivery&lt;/tt&gt;, &lt;tt&gt;pallet&lt;/tt&gt; und &lt;tt&gt;fullPallet&lt;/tt&gt; bestimmte JavaScript-Funktionen &lt;tt&gt;addDelivery()&lt;/tt&gt;, &lt;tt&gt;addPallet()&lt;/tt&gt; und &lt;tt&gt;buildPallet()&lt;/tt&gt; verknüpft, in denen die gelesenen Daten direkt an die gebundenen ABAP-Variablen übertragen werden.&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;    ometa HandlingUnitDefinition &amp;lt;: Parser {&lt;br /&gt;      expr      = fullPallet+ end,      &lt;br /&gt;      fullPallet  = pallet:s "(" content:c ")"&lt;br /&gt;              &lt;span style="color:red;font-weight:bold;"&gt;-&gt; { buildPallet( s, c ) },&lt;/span&gt;&lt;br /&gt;      pallet  = ordnum:n ( "Palette"  | "Pal" | "SSCC" )&lt;br /&gt;              &lt;span style="color:red;font-weight:bold;"&gt;-&gt; {addPallet(n)},&lt;/span&gt;&lt;br /&gt;      content = (spaces contentPart)+, &lt;br /&gt;      contentPart = fullPallet | delivery | pallet,&lt;br /&gt;      delivery  = ordnum:n ( "Lieferung" | "Lief" | "LF"  )&lt;br /&gt;              &lt;span style="color:red;font-weight:bold;"&gt;-&gt; {addDelivery(n)},&lt;/span&gt;&lt;br /&gt;      ordnum  = spaces digit+:ds "." &lt;span style="color:red;font-weight:bold;"&gt;-&gt; parseInt(ds.join(''))&lt;/span&gt;&lt;br /&gt;      }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Die Zugriffsnotation für gebundene ABAP-Variablen &lt;a href="http://help.sap.com/saphelp_470/helpdata/de/ee/a3c79ed59b11d4b2e90050dadfb92b/frameset.htm"&gt;ist in der ABAP-Hilfe dokumentiert&lt;/a&gt;. In dieser Notation müssen die Funktionen &lt;tt&gt;addDelivery()&lt;/tt&gt;, &lt;tt&gt;addPallet()&lt;/tt&gt; und &lt;tt&gt;buildPallet()&lt;/tt&gt; folgendermassen implementiert werden. &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;function addDelivery(n) {&lt;br /&gt;  return addDocNumber(pallets.model.delivery_numbers,"DL"+n);&lt;br /&gt;  }&lt;br /&gt;function addPallet(n) {&lt;br /&gt;  return addDocNumber(pallets.model.pallet_numbers,"PA"+n);&lt;br /&gt;  }&lt;br /&gt;function buildPallet( id, content ) {&lt;br /&gt;  var p = pallets.model.pallets;&lt;br /&gt;  var wa, i;&lt;br /&gt;  for (i=0;i&amp;lt;content.length;i++) {&lt;br /&gt;    p.appendLine();&lt;br /&gt;    wa = p[p.length-1];&lt;br /&gt;    wa.pallet_no = id;&lt;br /&gt;    wa.item_no = i+1;&lt;br /&gt;    wa.type = content[i].substring(0,2);&lt;br /&gt;    wa.content = content[i];&lt;br /&gt;    }&lt;br /&gt; return id;&lt;br /&gt; }&lt;br /&gt;function addDocNumber(table,id) {&lt;br /&gt;  table.appendLine(); &lt;br /&gt;  table[table.length-1] = id;&lt;br /&gt;  return id;&lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Das ist alles. Probieren Sie es in Ihrem SAP-System aus: Sie können den Report &lt;a href="http://pastebin.com/TkugQTHq"&gt;Z_SEMANTIC_MODEL_EXAMPLE&lt;/a&gt; und den von diesem vorausgesetzten Unterprogrammpool &lt;a href="http://pastebin.com/hunE6vsy"&gt;Z_OMETA_BASE&lt;/a&gt; herunterladen und ersteren dann ausführen. Es erscheint ein Popup, in dem Sie eine stringförmige Verpackungsregel nach obiger Syntax eingeben können:[1] &lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-hQ5jzchodt8/Tya9OUZ-wxI/AAAAAAAAAOw/z3ZufOdxFwI/s1600/sterm.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 337px;" src="http://4.bp.blogspot.com/-hQ5jzchodt8/Tya9OUZ-wxI/AAAAAAAAAOw/z3ZufOdxFwI/s400/sterm.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5703454031804810002" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Wenn die Syntax eingehalten wurde, kann die Transformation ausgeführt werden. Das von ihr produzierte semantische Modell wird schliesslich mit dem Baustein &lt;tt&gt;RS_COMPLEX_OBJECT_EDIT&lt;/tt&gt; angezeigt (der auch vom SAP-Standard als Benutzerschnittstelle zur Dateneingabe beim Funktionsbaustein-Einzeltest verwendet wird):&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-nv1v5uWJdtQ/Tya-NVz0ZeI/AAAAAAAAAO8/00c5nKxGwV8/s1600/model.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 304px;" src="http://2.bp.blogspot.com/-nv1v5uWJdtQ/Tya-NVz0ZeI/AAAAAAAAAO8/00c5nKxGwV8/s400/model.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5703455114513376738" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Dieses Modell kann nun mit ABAP-Mitteln weiterverarbeitet werden. Es könnte beispielsweise als Template zum Abfüllen realer Lieferungen in Paletten benutzt werden. &lt;br /&gt;&lt;br /&gt;Ich erwähnte im Blog &lt;a href="http://ruediger-plantiko.blogspot.com/2011/01/domanenspezifische-programmierung-in.html"&gt;Domänenspezifische Programmierung in ABAP&lt;/a&gt;, dass ein ähnliches Tool in meiner Firma im Einsatz ist. Ich hatte allerdings den Parser damals mit Hilfe von regulären Ausdrücken ausprogrammiert. Der Sinn dieses Blogs ist zu zeigen, dass sich das Parsen und Transformieren von Ausdrücken in einer frei definierten Syntax erheblich verkürzt - wenn man eine geeignete Metasprache verwendet. &lt;br /&gt;&lt;br /&gt;Das muss nicht &lt;tt&gt;oMeta&lt;/tt&gt; und nicht &lt;tt&gt;JavaScript&lt;/tt&gt; sein. Nur reizte mich der Titel von Alessandro Warth's Dissertation über &lt;tt&gt;oMeta&lt;/tt&gt; - &lt;i&gt;Experimenting with Programming Languages&lt;/i&gt; [3] - zu einer Machbarkeitsstudie.&lt;br /&gt;&lt;br /&gt;[1] Soweit ich das gesehen habe, gibt es in der Basis genau einen Funktionsbaustein, der ein Popup zur Eingabe eines freien Textes sendet. Er heisst &lt;tt&gt;TERM_CONTROL_EDIT&lt;/tt&gt; und stammt vermutlich noch aus der Zeit, als das TextEditControl entwickelt wurde (das "Enjoy"-Release).&lt;br /&gt;[2] Leider wurde die Klasse &lt;tt&gt;CL_JAVA_SCRIPT&lt;/tt&gt; von SAP für Release 7.02 zum Auslaufmodell erklärt. Die Dokumentation kündigt an, die Klasse zu einem späteren Release aus der Wartung zu nehmen. Leider fällt sogar das Wort von der &lt;i&gt;ersatzlosen&lt;/i&gt; Streichung. Das wird es für Zugänge wie den hier beschriebenen schwer machen. In Java hat man mit dem JSR-223 schon lange erkannt, wie wichtig die Integration dynamischer Sprachen ist. Mit Java SE6 kam dann ein Framework, um beliebige dynamische Sprachen in Java zu integrieren, das zu SE7 sogar noch um einige neue Bytecode-Anweisungen wie &lt;tt&gt;invokeDynamic&lt;/tt&gt; bereichert wurde. Das zeigt, welch hoher Stellenwert in der Java-Welt den dynamischen Sprachen beigemessen wird.&lt;br /&gt;[3] Alessandro Warth, &lt;i&gt;Experimenting with Programming Languages&lt;/i&gt;, PhD dissertation, 2009, &lt;a href="http://www.vpri.org/pdf/tr2008003_experimenting.pdf"&gt;http://www.vpri.org/pdf/tr2008003_experimenting.pdf&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-8417836179891370251?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/8417836179891370251/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=8417836179891370251' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/8417836179891370251'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/8417836179891370251'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2012/01/von-der-regel-zum-semantischen-modell.html' title='Von der Regel zum semantischen Modell'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-hQ5jzchodt8/Tya9OUZ-wxI/AAAAAAAAAOw/z3ZufOdxFwI/s72-c/sterm.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-8098166443137471482</id><published>2012-01-24T20:26:00.023+01:00</published><updated>2012-01-24T23:31:47.539+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>XML Parser zum letzten</title><content type='html'>Da, wie bereits früher erwähnt, meine Blogs mit XML-Themen am meisten gelesen werden und da ich auch hierzu die meisten Fragen erhalte, soll dieser Artikel die grundlegende Bedienungsfragen des XML-Parsers in ABAP ein für allemal klären. In einem &lt;a href="http://ruediger-plantiko.blogspot.com/2009/03/ereignisbasierter-xml-parser-in-abap.html"&gt;früheren Blog&lt;/a&gt; habe ich den ereignisbezogenen, "SAX"-artigen XML-Parser erklärt. Hier soll es um den DOM-Parser gehen, der für kleinere Dokumente häufig die richtige Wahl ist.[1]  &lt;br /&gt;&lt;br /&gt;Es ist "nur" knapp 14 Jahre her, dass die erste Version des &lt;a href="http://www.w3.org/TR/1998/REC-xml-19980210"&gt;XML-Formats&lt;/a&gt; vom World Wide Web Konsortium herausgebracht wurde. Das ist ziemlich viel Zeit, vor allem in der IT. XML wurde in den ersten Jahren euphorisch begrüsst und hat sich in der Softwarewelt schnell verbreitet. Seine universelle Verwendbarkeit als Container für beliebige strukturierte Daten und die Existenz von Parsern in allen möglichen Sprachen und Systemen, sowie die Lesbarkeit und sogar Änderbarkeit mit einem beliebigen Klartext-Editor machten es zum Format der Wahl für alle möglichen Anwendungsdaten. &lt;br /&gt;&lt;br /&gt;Mittlerweile ist man soweit, auch die Schattenseiten von XML wieder klar zu sehen. Die vielen spitzen Klammern, die Notwendigkeit, geöffnete Elemente wieder zu schliessen, die Notation für Attribute - all dies erzeugt ziemlich viel &lt;a href="http://martinfowler.com/bliki/SyntacticNoise.html"&gt;syntaktisches Rauschen&lt;/a&gt;, was die Lesbarkeit erschwert (das Argument vom vermeidbaren Daten-Overhead einmal ausser Acht gelassen). Das Argument, dass man wenigstens über Parser verfügt, die man nicht selbst entwickeln muss, verliert viel an Bedeutung, seit das Wissen um Parsergeneratoren nicht mehr auf die Hörsäle der Universitäten beschränkt ist, sondern sich in weiten Kreisen verbreitet hat, und seit Entwicklungsumgebungen wie Eclipse eine eingebaute Unterstützung für Parsergeneratoren bieten. Damit ist es im Handumdrehen möglich, Parser für selbst erfundene Sprachen zu generieren, deren Syntax genau auf ein spezifisches Problem zugeschnitten ist - die domänenspezifischen Sprachen (DSL). &lt;br /&gt;&lt;br /&gt;Ironischerweise hat das Konzept der DSL, dem viele sich nun zuwenden, noch viel mehr Jahre auf dem Buckel als XML. Als Vision war die "Natürlichsprachlichkeit" bereits bei den ersten für kommerzielle Anwendungen entworfenen Programmiersprachen wie COBOL präsent: COBOL-Programme sollten für "Business People" flüssig lesbar sein, wenn sie natürlich auch &amp;ndash; wie jede Programmiersprache &amp;ndash; festen Syntaxregeln folgen mussten. &lt;br /&gt;&lt;br /&gt;Aber wie dem auch sei. XML wird noch lange Zeit ein verbreitetes Datenformat bleiben. Für die ABAP-Programmierung spielt es immer dann eine Rolle, wenn mit der Aussenwelt kommuniziert werden muss - sei es mit dem Web-Browser in einer Webanwendung, sei es mit Nicht-SAP-Systemen zum Datenaustausch. &lt;br /&gt;&lt;br /&gt;Zurück zum DOM-Parser in ABAP. Hier ist ein Programm, das ein als String gegebenes XHTML-Dokument auf das Vorkommen bestimmter XML-Elemente im DOM untersucht:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pastebin.com/eBGPGjrc"&gt;http://pastebin.com/eBGPGjrc&lt;/a&gt;  &lt;br /&gt;   &lt;br /&gt;Ich werde es nun blockweise dokumentieren. &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;report  zz_dom_parse_xhtml.&lt;/pre&gt;&lt;br /&gt;Die Form eines Reports mit Unterprogrammen als einziger Modularisierungseinheit habe ich gewählt, weil dieser Report self-contained ist und daher ohne weiteres per Copy/Paste in einem SAP-System angelegt, studiert und ausgeführt werden kann. Mit Klassen wäre das nicht so einfach möglich. Ich hätte den Report selbst mit &lt;i&gt;lokalen Klassen&lt;/i&gt; gestalten können. Aber Unterprogramme sind einfacher, ohne dass mir die lokalen Klassen in diesem Fall einen Vorteil verschaffen. Daher Unterprogramme - und keine Methoden lokaler Klassen.&lt;br /&gt;&lt;br /&gt;Ich habe mir angewöhnt, Testprogramme, bereits während ich sie verfasse, durch Unterprogramme zu modularisieren - auch wenn ein kleiner Extraaufwand dazukommt, um für jedes Unterprogramm eine geeignete Schnittstelle zu definieren. Das rentiert sich recht bald: Wenn das Testprogramm erfolgreich war, kann ich den Code der Unterprogramme in Methoden einer passenden Klasse übernehmen, die ich dann für den produktiven Einsatz vorsehe. So auch hier: Unterprogramme von Testprogrammen im XML-Bereich sind als Methoden in meine Klasse &lt;a href="http://bsp.mits.ch/code/clas/zcl_xml_helper"&gt;ZCL_XML_HELPER&lt;/a&gt; gewandert, für die es mittlerweile sehr viele Verwender gibt.  &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;data: go_xml type ref to if_ixml,&lt;br /&gt;      go_stream_factory type ref to if_ixml_stream_factory.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Sowenig &lt;a href="http://ruediger-plantiko.blogspot.com/2009/11/globale-und-lokale-variablen.html"&gt;globale Variablen&lt;/a&gt; wie möglich. Das XML-Basisobjekt und die XML Stream Factory sind Ausnahmen von dieser Regel. Ich benötige nur eine Instanz von ihnen, egal wie oft Code aus diesem Programm durchlaufen wird. Diese beiden Objekte mögen einen internen Zustand haben, der sich jedoch nicht auf die von ihnen erzeugten oder bearbeiteten Objekte auswirkt. Es gibt also kein Reentrance-Problem.&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;parameters: p_elnam type text30 default 'p',&lt;br /&gt;            p_ns    type text128 default 'http://www.w3.org/1999/xhtml',&lt;br /&gt;            p_aware as checkbox default space.&lt;br /&gt;&lt;/pre&gt;Dies sind Testparameter. Das Beispielprogramm soll alle &lt;tt&gt;&amp;lt;p&gt;&lt;/tt&gt;-Elemente in einem XHTML-Dokument herausfinden und deren ID's und Textinhalt ausgeben. &lt;tt&gt;p_elnam&lt;/tt&gt; gibt dabei den Elementnamen an, &lt;tt&gt;p_ns&lt;/tt&gt; den Namensraum. Ich verwende hier die Datentypen &lt;tt&gt;textNNN&lt;/tt&gt;, die im Gegensatz zu &lt;tt&gt;charNNN&lt;/tt&gt; gross/klein-sensitiv arbeiten. Das Flag &lt;tt&gt;p_aware&lt;/tt&gt; steuert, ob im Parser die &lt;i&gt;Namespace Awareness&lt;/i&gt; explizit angeschaltet werden soll. Wir werden jedoch sehen, dass die verwendete Methode &lt;tt&gt;get_elements_by_tag_name_ns()&lt;/tt&gt; unabhängig von diesem Flag arbeitet. &lt;br /&gt;&lt;br /&gt;Wo werden die Daten initialisiert? Beim Laden des Reports. Ich habe mir einen Report-Rumpf als Muster in den Code Editor eingespeist, der mir beim Tippen der Schlüsselwörter &lt;tt&gt;initialization&lt;/tt&gt; oder &lt;tt&gt;start-of-selection&lt;/tt&gt; vorgeschlagen wird. Er verknüpft diese beiden Events mit entsprechenden Unterprogrammen, so dass ich ab dort in sauberen Stack-Einheiten mit lokalen Daten arbeiten kann:&lt;br /&gt;&lt;pre class="sh_abap"&gt;initialization.&lt;br /&gt;  perform init.&lt;br /&gt; &lt;br /&gt;start-of-selection.&lt;br /&gt;  perform start.&lt;br /&gt;&lt;/pre&gt;  &lt;br /&gt;Die Initialisierungsroutine ist nun das Erwartete - sie erzeugt die beiden globalen Objekte mit den dafür vorgesehenen &lt;tt&gt;create&lt;/tt&gt;-Methoden:&lt;br /&gt;&lt;pre class="sh_abap"&gt;*---&lt;br /&gt;form init.&lt;br /&gt;  go_xml = cl_ixml=&gt;create( ).&lt;br /&gt;  go_stream_factory = go_xml-&gt;create_stream_factory( ).&lt;br /&gt;endform.                    "init&lt;/pre&gt;&lt;br /&gt;Kommen wir nun zur Routine &lt;tt&gt;start&lt;/tt&gt;, die beim Druck auf "Ausführen" aufgerufen wird. Hierbei werden nacheinander drei Schritte ausgeführt: Ein Beispieldokument wird in ein Dokument vom Typ &lt;tt&gt;if_ixml_document&lt;/tt&gt; eingelesen. Danach werden bestimmte Elemente in diesem Dokument gesucht und in einer Internen Tabelle vom Typ &lt;tt&gt;dcxmlelems&lt;/tt&gt; zurückgegeben. Diese werden schliesslich in einer ganz altbackenen ABAP-Liste mittels &lt;tt&gt;write&lt;/tt&gt; ausgegeben. &lt;br /&gt;&lt;br /&gt;Genau diese drei Schritte sind in der Routine &lt;tt&gt;start&lt;/tt&gt; dokumentiert:&lt;br /&gt;&lt;pre class="sh_abap"&gt;* ---&lt;br /&gt;form start.&lt;br /&gt; &lt;br /&gt;  data: lo_xhtml type ref to if_ixml_document,&lt;br /&gt;        lt_elements type dcxmlelems,&lt;br /&gt;        lv_element_name type string,&lt;br /&gt;        lv_namespace type string.&lt;br /&gt; &lt;br /&gt;  try.&lt;br /&gt; &lt;br /&gt;      perform build_test_xhtml using p_aware changing lo_xhtml.&lt;br /&gt; &lt;br /&gt;      lv_element_name = p_elnam.&lt;br /&gt;      lv_namespace = p_ns.&lt;br /&gt;      perform search_elements using lo_xhtml lv_element_name lv_namespace&lt;br /&gt;                              changing lt_elements.&lt;br /&gt; &lt;br /&gt;      perform write_result using lt_elements.&lt;br /&gt; &lt;br /&gt;    catch cx_ixml_parse_error.&lt;br /&gt;* Error log has already been written&lt;br /&gt;  endtry.&lt;br /&gt; &lt;br /&gt;endform.                    "start&lt;/pre&gt;&lt;br /&gt;Das XHTML-Dokument wird in zwei Schritten aufgebaut. Zuerst wird es als String erzeugt, den man irgendwie als gegeben betrachten kann. Er könnte z.B. aus einer Datei eingelesen worden sein oder über einen HTTP-Request aus dem Internet stammen. In einem zweiten Schritt wird dieser String dann geparsed, d.h. in das interne DOM-Format gewandelt:&lt;br /&gt;&lt;pre class="sh_abap"&gt;* ---&lt;br /&gt;form build_test_xhtml&lt;br /&gt;  using iv_ns_aware type flag&lt;br /&gt;  changing eo_xhtml type ref to if_ixml_document&lt;br /&gt;  raising cx_ixml_parse_error.&lt;br /&gt; &lt;br /&gt;  data: lv_xhtml type string.&lt;br /&gt; &lt;br /&gt;  perform get_test_html_as_string changing lv_xhtml.&lt;br /&gt; &lt;br /&gt;  perform parse using lv_xhtml iv_ns_aware&lt;br /&gt;                changing eo_xhtml.&lt;br /&gt; &lt;br /&gt;endform.                    "build_test_xhtml&lt;/pre&gt;&lt;br /&gt;Die Routine &lt;tt&gt;get_test_html_as_string&lt;/tt&gt; erzeugt den Beispielstring, den man drt für Testzwecke beliebig umbauen kann:&lt;br /&gt;&lt;pre class="sh_abap"&gt;* ---&lt;br /&gt;form get_test_html_as_string changing ev_xhtml type string.&lt;br /&gt;  data: lv_xhtml type string.&lt;br /&gt;  concatenate&lt;br /&gt;`&amp;lt;?xml version="1.0" encoding="ISO-8859-1" ?&gt;`&lt;br /&gt;`&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;`&lt;br /&gt;`&amp;lt;head&gt;`&lt;br /&gt;`&amp;lt;meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" /&gt;`&lt;br /&gt;`&amp;lt;title&gt;Wenn HTML zu XHTML wird&amp;lt;/title&gt;`&lt;br /&gt;`&amp;lt;/head&gt;`&lt;br /&gt;`&amp;lt;body&gt;`&lt;br /&gt;``&lt;br /&gt;`&amp;lt;h1&gt;&amp;lt;a name="start" id="start"&gt;Wenn HTML zu XHTML wird&amp;lt;/a&gt;&amp;lt;/h1&gt;`&lt;br /&gt;``&lt;br /&gt;`&amp;lt;p id="erster" &gt;Erster Paragraph.&amp;lt;/p&gt;`&lt;br /&gt;`&amp;lt;p id="zweiter"&gt;Zweiter Paragraph.&amp;lt;/p&gt;`&lt;br /&gt;`&amp;lt;p id="dritter"&gt;Na, wievielter Paragraph wohl?&amp;lt;/p&gt;`&lt;br /&gt;`&amp;lt;/body&gt;`&lt;br /&gt;`&amp;lt;/html&gt;`&lt;br /&gt;  into ev_xhtml&lt;br /&gt;  separated by cl_abap_char_utilities=&gt;cr_lf. " Für Zeilenausgabe in Fehlermeldungen&lt;br /&gt;endform.                    "get_test_html_as_string&lt;br /&gt; &lt;/pre&gt;&lt;br /&gt;Ich kann in ABAP nicht nur einfache Anführungszeichen, sondern auch Backticks (`) als Stringbegrenzer verwenden. Das ist nützlich in Fällen wie diesem: Meist kommen in XML- oder HTML-Dokumenten zwar eine Menge einfacher und doppelter Anführungszeichen vor, aber keine Backticks. So muss ich normalerweise nirgends Fluchtsequenzen für das Stringbegrenzungszeichen einführen. Ich konkateniere die Zeilen zu einem String, wobei ich aber Zeilenumbrüche als Trennzeichen verwende. Das hat den Vorteil, dass allfällige vom Parser ausgegebene Fehlermeldungen über die Zeilennummer leicht dem Quelltext zugeordnet werden können.&lt;br /&gt;&lt;br /&gt;Nun zum eigentlichen Parsevorgang. Der DOM-Parser möchte nicht direkt einen String zur Eingabe haben, sondern einen Eingabestrom. Daher muss der übergebene String in einen Stream verwandeln. Ausserdem muss eine leere Instanz des Zieldokuments vom Typ &lt;tt&gt;if_ixml_document&lt;/tt&gt; erzeugen und dem Parser übergeben. Er schreibt sein Ergebnis dann in diese Instanz:&lt;br /&gt;&lt;pre class="sh_abap"&gt;* ---&lt;br /&gt;form parse using iv_xml type string&lt;br /&gt;                 iv_ns_aware type flag&lt;br /&gt;           changing eo_doc type ref to if_ixml_document&lt;br /&gt;           raising cx_ixml_parse_error.&lt;br /&gt; &lt;br /&gt;  data: lo_parser type ref to if_ixml_parser,&lt;br /&gt;        lo_stream type ref to if_ixml_istream.&lt;br /&gt; &lt;br /&gt;  lo_stream   = go_stream_factory-&gt;create_istream_string( iv_xml ).&lt;br /&gt;  eo_doc = go_xml-&gt;create_document( ).&lt;br /&gt; &lt;br /&gt;  lo_parser   = go_xml-&gt;create_parser( &lt;br /&gt;                    document       = eo_doc&lt;br /&gt;                    istream        = lo_stream&lt;br /&gt;                    stream_factory = go_stream_factory ).&lt;br /&gt; &lt;br /&gt;  if iv_ns_aware = 'X'.&lt;br /&gt;    lo_parser-&gt;set_namespace_mode( &lt;br /&gt;                if_ixml_parser=&gt;co_namespace_aware &lt;br /&gt;                ).&lt;br /&gt;  endif.&lt;br /&gt; &lt;br /&gt;  lo_parser-&gt;parse( ).&lt;br /&gt; &lt;br /&gt;  if lo_parser-&gt;num_errors( ) &gt; 0.&lt;br /&gt;    perform do_parser_errors using lo_parser.&lt;br /&gt;  endif.&lt;br /&gt; &lt;br /&gt;endform.                    "parse&lt;/pre&gt;&lt;br /&gt;Wenn das Parsen ohne Fehler funktioniert hat, enthält &lt;tt&gt;eo_doc&lt;/tt&gt; nun ein strukturiertes Abbild des eingelesenen XML-Dokuments. Nun kann man das Interface &lt;tt&gt;if_ixml_document&lt;/tt&gt; verwenden, um dieses Dokument zu durchsuchen. Wenn wir den &lt;tt&gt;try-catch&lt;/tt&gt;-Block in der Routine &lt;tt&gt;start&lt;/tt&gt; noch einmal betrachten,&lt;br /&gt;&lt;pre class="sh_abap"&gt;  try.&lt;br /&gt; &lt;br /&gt;      perform build_test_xhtml using p_aware changing lo_xhtml.&lt;br /&gt; &lt;br /&gt;      lv_element_name = p_elnam.&lt;br /&gt;      lv_namespace = p_ns.&lt;br /&gt;      perform search_elements using lo_xhtml lv_element_name lv_namespace&lt;br /&gt;                              changing lt_elements.&lt;br /&gt; &lt;br /&gt;      perform write_result using lt_elements.&lt;br /&gt; &lt;br /&gt;    catch cx_ixml_parse_error.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;so sind wir nun mit dem Erzeugen des XHTML-Dokuments &lt;tt&gt;lo_xhtml&lt;/tt&gt; fertig. Das Programm kehrt auf diese Stackebene zurück und setzt, wenn es keinen Fehler gab, mit dem Aufruf der Routine &lt;tt&gt;search_elements&lt;/tt&gt; fort. Vor dem Aufruf müssen die Eingabeparameter &lt;tt&gt;p_elnam&lt;/tt&gt; und &lt;tt&gt;p_ns&lt;/tt&gt; leider noch in Strings verwandelt werden, denn diesen Datentyp erwartet die &lt;tt&gt;if_ixml_document&lt;/tt&gt;-Methode &lt;tt&gt;get_elements_by_tagname_ns&lt;/tt&gt;, die wir aufzurufen gedenken. &lt;tt&gt;MOVE&lt;/tt&gt;s von dieser Art sind in ABAP oft unvermeidlich. Zum Beispiel hier: Einerseits können Parameter nicht den Datentyp String haben. Andererseits werden die Schnittstellenparameter oft im Datentyp String erwartet.[2]&lt;br /&gt;&lt;br /&gt;Wie sieht nun die Routine &lt;tt&gt;search_elements&lt;/tt&gt; aus? Sie ruft die Dokumentmethode &lt;tt&gt;get_elements_by_tag_name_ns( )&lt;/tt&gt; auf, die eine &lt;tt&gt;if_ixml_node_collection&lt;/tt&gt; aller gefundenen Elemente zurückliefert - deren Name und Namensraum also mit den angegebenen Werten übereinstimmt. Da der Zugriff auf diese Collection im Code etwas schwerfällig ist, erlaube ich mir den Luxus, sie in eine interne Tabelle von Objekten vom Typ &lt;tt&gt;if_ixml_element&lt;/tt&gt; zu wandeln. Eine interne Tabelle in einer Loop abzuarbeiten, ist in ABAP viel lesbarer als das Iterieren einer Collection. Glücklicherweise gibt es für "eine Standardtabelle von iXML-Elementen" bereits den mit der SAP-Basis ausgelieferten und definierten Datentyp &lt;tt&gt;dcxmlelems&lt;/tt&gt;:&lt;br /&gt;&lt;pre class="sh_abap"&gt;* ---&lt;br /&gt;form search_elements using io_xhtml type ref to if_ixml_document&lt;br /&gt;                           iv_elnam type string&lt;br /&gt;                           iv_ns    type string&lt;br /&gt;                     changing et_elements type dcxmlelems.&lt;br /&gt; &lt;br /&gt;  data: lo_elements type ref to if_ixml_node_collection.&lt;br /&gt; &lt;br /&gt;  lo_elements = io_xhtml-&gt;get_elements_by_tag_name_ns(&lt;br /&gt;                             name      = iv_elnam&lt;br /&gt;                             uri       = iv_ns ).&lt;br /&gt; &lt;br /&gt;  perform element_table_from_collection&lt;br /&gt;    using lo_elements&lt;br /&gt;    changing et_elements.&lt;br /&gt; &lt;br /&gt; &lt;br /&gt;endform.                    "search_elements&lt;/pre&gt;&lt;br /&gt;Was macht &lt;tt&gt;element_table_from_collection&lt;/tt&gt;? Was man erwarten würde: Es durchläuft die Collection, typisiert die Nodes als Elemente und fügt sie in eine interne Tabelle ein:&lt;br /&gt;&lt;pre class="sh_abap"&gt;* ---&lt;br /&gt;form element_table_from_collection&lt;br /&gt;    using io_elements type ref to if_ixml_node_collection&lt;br /&gt;    changing et_elements type dcxmlelems.&lt;br /&gt; &lt;br /&gt;  data: lo_iterator type ref to if_ixml_node_iterator,&lt;br /&gt;        lo_node type ref to if_ixml_node,&lt;br /&gt;        lo_element type ref to if_ixml_element.&lt;br /&gt; &lt;br /&gt;  lo_iterator = io_elements-&gt;create_iterator( ).&lt;br /&gt; &lt;br /&gt;  do.&lt;br /&gt; &lt;br /&gt;    clear lo_node.&lt;br /&gt;    lo_node = lo_iterator-&gt;get_next( ).&lt;br /&gt;    if lo_node is not bound.&lt;br /&gt;      exit.&lt;br /&gt;    endif.&lt;br /&gt; &lt;br /&gt;    clear lo_element.&lt;br /&gt;    lo_element ?= lo_node-&gt;query_interface( ixml_iid_element ).&lt;br /&gt;    if lo_element is bound.&lt;br /&gt;      append lo_element to et_elements.&lt;br /&gt;    endif.&lt;br /&gt; &lt;br /&gt;  enddo.&lt;br /&gt; &lt;br /&gt;endform.             "element_table_from_collection&lt;/pre&gt;&lt;br /&gt;Wenn dies gemacht ist, geht es wieder in der Routine &lt;tt&gt;start&lt;/tt&gt; weiter. Durch den Aufruf &lt;pre class="sh_abap"&gt;      perform write_result using lt_elements.&lt;/pre&gt; &lt;br /&gt;werden die gesammelten Elemente an das Unterprogramm zur Ausgabe übergeben.&lt;br /&gt;&lt;pre class="sh_abap"&gt;* ---&lt;br /&gt;form write_result using it_elements type dcxmlelems.&lt;br /&gt; &lt;br /&gt;  data: lo_element type ref to if_ixml_element,&lt;br /&gt;        lv_id type string,&lt;br /&gt;        lv_text type string.&lt;br /&gt; &lt;br /&gt;  if it_elements is not initial.&lt;br /&gt;    loop at it_elements into lo_element.&lt;br /&gt;      lv_id = lo_element-&gt;get_attribute( 'id' ).&lt;br /&gt;      lv_text = lo_element-&gt;get_content_as_string( ).&lt;br /&gt;      write: / lv_id, at 15 ':', lv_text.&lt;br /&gt;    endloop.&lt;br /&gt;  else.&lt;br /&gt;    write: / &lt;br /&gt;      'Keine Elemente mit diesem Namen und Namespace gefunden'.&lt;br /&gt;  endif.&lt;br /&gt; &lt;br /&gt;endform.                    "write_result&lt;/pre&gt;&lt;br /&gt;Zur Demonstration werden hier pro Element der Wert des Attributs &lt;tt&gt;id&lt;/tt&gt; und der Textinhalt ausgegeben. Das erzeugt mit dem hier verwendeten Beispieldokument die Ausgabe:&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;DOM-Parser sucht nach einem Element in XHTML&lt;br /&gt;&lt;hr/&gt;&lt;br /&gt;erster        : Erster Paragraph.&lt;br /&gt;zweiter       : Zweiter Paragraph.&lt;br /&gt;dritter       : Na, wievielter Paragraph wohl?&lt;/pre&gt;&lt;br /&gt;Was ist, wenn es nicht funktioniert? Der Parser schreibt ein Protokoll aller beim Lesen des Streams aufgetretenen Fehler. Die Gesamtzahl dieser Fehler kann mit der Methode &lt;tt&gt;lo_parser-&gt;num_errors( )&lt;/tt&gt; abgefragt werden. Oben war in der Routine zu sehen, dass bei Fehlern in die Routine &lt;tt&gt;do_parser_errors&lt;/tt&gt; abgesprungen wurde. Diese erzeugt eine Listausgabe aller gesammelten Fehler und löst dann die Ausnahme &lt;tt&gt;cx_ixml_parse_error&lt;/tt&gt; aus. Diese propagiert im Callstack nach oben in die Routine &lt;tt&gt;start&lt;/tt&gt; und wirkt dort als Stopsignal. Da eine Ausgabe bereits geschrieben wurde, ist keine explizite Behandlung der Ausnahme mehr nötig. Es genügt, dass die Ausführung der nachfolgenden Routinen &lt;tt&gt;search_elements&lt;/tt&gt; und &lt;tt&gt;write_result&lt;/tt&gt; verhindert wird, die ja sinnlos sind, wenn kein wohlgeformtes XML-Dokument eingelesen werden konnte.&lt;br /&gt;&lt;pre class="sh_abap"&gt;* ---&lt;br /&gt;form do_parser_errors&lt;br /&gt;       using io_parser type ref to if_ixml_parser&lt;br /&gt;       raising cx_ixml_parse_error.&lt;br /&gt; &lt;br /&gt;  data: lo_error type ref to if_ixml_parse_error,&lt;br /&gt;        lv_maxnum type i,&lt;br /&gt;        lv_num type i,&lt;br /&gt;        lv_text type string,&lt;br /&gt;        lv_line     type i,&lt;br /&gt;        lv_column   type i,&lt;br /&gt;        lv_severity type i.&lt;br /&gt; &lt;br /&gt; &lt;br /&gt;* Fehler im Nachrichtensammler einfügen&lt;br /&gt;  lv_maxnum = io_parser-&gt;num_errors( ).&lt;br /&gt;  while lv_num &lt; lv_maxnum.&lt;br /&gt;    lo_error = io_parser-&gt;get_error( lv_num ).&lt;br /&gt;    lv_text  = lo_error-&gt;get_reason( ).&lt;br /&gt;    lv_line  = lo_error-&gt;get_line( ).&lt;br /&gt;    lv_column = lo_error-&gt;get_column( ).&lt;br /&gt;    lv_severity = lo_error-&gt;get_severity( ).&lt;br /&gt;    write: / lv_line left-justified no-gap, &lt;br /&gt;             '(' no-gap, &lt;br /&gt;             lv_column left-justified no-gap, &lt;br /&gt;             ')', &lt;br /&gt;             lv_text.&lt;br /&gt;    add 1 to lv_num.&lt;br /&gt;  endwhile.&lt;br /&gt; &lt;br /&gt;* Wenn Fehler auftraten, Ausnahme auslösen&lt;br /&gt;  if lv_maxnum &gt; 0.&lt;br /&gt;    raise exception type cx_ixml_parse_error&lt;br /&gt;      exporting&lt;br /&gt;        reason = lv_text&lt;br /&gt;        line   = lv_line&lt;br /&gt;        column = lv_column.&lt;br /&gt;  endif.&lt;br /&gt; &lt;br /&gt;endform.                    "write_parser_errors&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Die ganze Thematik ist auch wunderbar &amp;ndash; und viel umfassender als es ein Blog je sein könnte &amp;ndash; in der SAP-Hilfe dokumentiert. Einsteigern empfehle ich zur Lektüre unbedingt den &lt;a href="http://help.sap.com/saphelp_nw04/helpdata/en/86/8280ba12d511d5991b00508b6b8b11/frameset.htm"&gt;ABAP XML Jumpstart&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Ich habe allerdings die Erfahrung gemacht, dass für kleine XML-Dokumente oft eine XSLT-Transformation die beste Wahl ist, die die gewünschten Inhalte gleich in die Komponenten einer passenden ABAP-Datenstruktur hineintransformiert. Aber trotz der guten Integration in die Workbench und der Möglichkeit, sogar die Transformationen zu debuggen, bedeutet die Sprache XSLT für viele leider eine (rein psychologische) Extrahürde. &lt;br /&gt;[2] ... und nicht in den generischen Typen &lt;tt&gt;csequence&lt;/tt&gt; oder &lt;tt&gt;clike&lt;/tt&gt;, die in Schnittstellen oft die bessere Wahl sind, da sie einen Oberbegriff von &lt;tt&gt;type c&lt;/tt&gt; und &lt;tt&gt;type string&lt;/tt&gt; darstellen, so dass Datenobjekte beider Typen als Aktualparameter akzeptiert werden.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-8098166443137471482?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/8098166443137471482/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=8098166443137471482' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/8098166443137471482'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/8098166443137471482'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2012/01/xml-parser-zum-letzten.html' title='XML Parser zum letzten'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-2630830820186472062</id><published>2012-01-08T12:25:00.039+01:00</published><updated>2012-01-09T10:41:15.671+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Astrologie'/><title type='text'>Savonarola - sein Horoskop und seine Direktionen</title><content type='html'>&lt;a href="http://de.wikipedia.org/wiki/Girolamo_Savonarola"&gt;Girolamo Savonarola&lt;/a&gt; (1452-1498) war ein Dominikanermönch, der inmitten der florentinischen Prachtentfaltung der Renaissance eine Abkehr von allen weltlichen Eitelkeiten predigte - und damit für einige Jahre grossen Erfolg hatte. Bekannt ist sein &lt;i&gt;Feuer der Eitelkeiten&lt;/i&gt; - ein Scheiterhaufen auf der Piazza della Signoria in Florenz, auf dem viele bussfertige Bürger Symbole ihres weltlichen Lebenswandels verbrannten - darunter Frauengemälde, Parfüms, Salben, Puder, Spiegel, Perücken, Spielkarten, Würfel, Liederbücher, Musikinstrumente, Masken und andere Karnevalsutensilien, Bücher mit teilweise anstössigem Inhalt und dergleichen.[1] Bedauerlicherweise warf sogar &lt;a href="http://de.wikipedia.org/wiki/Sandro_Botticelli"&gt;Sandro Botticelli&lt;/a&gt; einige seiner Bilder in die Flammen. Und doch war das Feuer der Eitelkeiten nur ein Vorspiel zu einem anderen Feuer - zu Savonarolas Verbrennung als "Ketzer", die am 23. Mai 1498 am selben Ort durchgeführt wurde. &lt;br /&gt;&lt;br /&gt;In der Monatsschrift &lt;i&gt;Die Astrologie&lt;/i&gt; vom November 1932 befasste sich A. Schoeler mit dem Horoskop dieses Busspredigers, wobei er einige biographische Daten aus Savonarolas Leben mit den Primärdirektionen in Beziehung setzte.[2] &lt;br /&gt;&lt;br /&gt;Die Geburtsdaten hatte Schoeler dem &lt;i&gt;Liber de exemplis centum geniturarum&lt;/i&gt; von &lt;a href="http://wiki.astro.com/astrowiki/de/Hieronymus_Cardanus"&gt;Hieronymus Cardanus&lt;/a&gt; entnommen.[3] Die Gestirnstände hatte er sich von &lt;a href="http://wiki.astro.com/astrowiki/de/Karl_Weidner"&gt;Karl Weidner&lt;/a&gt; auf Basis dieser Daten nachrechnen lassen. Wie in solchen Fällen zu erwarten, musste er natürlich Abweichungen in den Positionsangaben zum Original feststellen. Wenn man mit einer modernen Ephemeride nachrechnet, erweisen sich - das ist ebenso natürlich - auch Weidners Angaben als fehlerhaft, wenn auch nur im Bogenminutenbereich. Beispielsweise steht der Mond richtig auf 16°44' Steinbock und nicht auf 16°50' wie bei Weidner. Immerhin stimmt der Mondstand auf ein Grad, der Aszendent auf zwei Grad mit der Angabe von Cardanus überein, so dass sich wenigstens keine Fehler bei der Datumsangabe eingeschlichen haben. Um Weidners Aszendenten zu reproduzieren, muss man die Weltzeit 16h42m45s am 21.9.1452 für Ferrara (44°50′ N, 11°37′ O) zugrundelegen (auch hier wieder eine beachtliche Abweichung zur Polhöhe 45°16', wie sie Cardanus angab - sie entspräche eher dem ca. 60 km nördlich von Ferrara gelegenen Ort Este, was allen bekannten Dokumenten über seinen Geburtsort widerspricht).&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-F6fb3xEcPkc/TwoETKY2jwI/AAAAAAAAAOY/dSbpQUW7Kgw/s1600/savonarola.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 377px; height: 400px;" src="http://3.bp.blogspot.com/-F6fb3xEcPkc/TwoETKY2jwI/AAAAAAAAAOY/dSbpQUW7Kgw/s400/savonarola.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5695369406016032514" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;In der Figur sticht der Horoskopherrscher &lt;i&gt;Mars&lt;/i&gt; ins Auge, in seinem Zeichen und im ersten Horoskophaus stehend. Er empfängt eine scharfe Opposition zum Saturn und ein Quadrat zum Mond im Steinbock im zehnten Haus. Dieses T-Quadrat von Mars, Saturn und Mond ist die Grundfigur seines Horokops. Die Willenskraft und Energie aus dem ersten Feld heizt hier das Saturn/Waage-Thema an, während der Mond im kühlen Steinbock die Freude an der Begrenzung, am Verzicht, an der Armut lehrt. Der Saturn in der Waage kann zwar, durch seine Stellung im "Du"-Haus, im Haus des öffentlichen Kontrahenten, mit den ihn bekämpfenden Exponenten der Kirche in Verbindung gesehen werden. Andererseits ist er auch eine Kraft in Savonarolas Wesen - das Predigen gegen den Luxus und die Annehmlichkeiten der "herrschenden Klasse" ist sicher verbunden mit einer tiefen Sehnsucht nach Gerechtigkeit, nach Frieden und Harmonie. Die Konjunktion des Saturn mit Merkur bringt eine geistige Vertiefung, die Konzentration auf das Wesentliche. Der Komplex steht - was schon Cardanus anmerkte - bei dem Fixstern &lt;i&gt;Spica&lt;/i&gt;, der einen jungfrauhaft-kühlen, aber zugleich kämpferischen Einschlag geben soll. Baumgartner assoziiert das Bild der "Jungfrau von Orleans". Konjunktionen der Spica mit Merkur werden laut Baumgartner häufig mit einer besonderen Redegabe verbunden, für die Konjunktion Spica / Saturn weiss die Tradition: "Häretiker"! Das passt nicht schlecht, lautete doch das Urteil gegen Savonarola auf Häresie. Im weiteren Sinne kann schliesslich auch als Häretiker bezeichnet werden, wer eine gewohnte Lebensweise oder eingeschliffene soziale Strukturen angreift.[4] &lt;br /&gt;&lt;br /&gt;Hören wir Schoelers Deutung:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Das ist die Figur eines energiegeladenen Herrenmenschen, der unbeugsam seinen Willen durchsetzt! Der Herr des Aszendenten, Mars, im eigenen Zeichen in Widder stehend, sechs Planeten über dem Horizont, fünf Planeten in positiven Zeichen und fünf Planeten in Eckhäusern: diese Konfiguration zeigt ohne weiteres einen Mann, der sich durch niemand und nichts hindern lässt, eine einmal als richtig erkannte Meinung gegen eine Welt von Feinden durchzufechten, und seien diese Feinde auch die vorgesetzte Kirchenbehörde oder sogar der Papst selbst. Dies ist der eifernde Zelot, welcher gegen jeden Übelstand und die Sittenlosigkeit der Zeit mit der Waffe der faszinierenden Rede zu Felde zieht! Gross wurde seine Macht und seine Wirksamkeit, denn der wachsende Mond im Steinbock und in der Himmelsmitte gab ihm eine einflussreiche Position. Die vielen Planeten im 7. Hause zeigen auch klar das Hervortreten seiner Person in der Öffentlichkeit und seine politische Tätigkeit, die weit über die Mauern des Florentiner Klosters San Marco ins Land hinaus reichte.&lt;br /&gt;&lt;br /&gt;Aber auch die Selbstüberschätzung zeigen die Sterne; denn Mars im ersten Hause steht mit Saturn im siebenten Hause in scharfer Opposition. Und zu diesen beiden Planeten bildet der Mond im zehnten Hause noch Quadraturen. So gelang ihm denn auch nicht sein Plan, den Papst Alexander VI. zu stürzen. Im Gegenteil: Das Schicksal erreichte ihn selbst, als er öffentlich im Jahre 1498 auf dem Scheiterhaufen verbrannt wurde.&lt;/blockquote&gt;  &lt;br /&gt;Schoeler listet nun einige Lebensereignisse Savonarolas auf und bringt sie in Verbindung mit den Primärdirektionen. Wie zu jener Zeit allgemein üblich, verwendet er die Halbbogenmethode zum Dirigieren und setzt ein Grad des Direktionsbogens einem Jahr gleich. Seine Direktionen kann man mit der Webanwendung &lt;a href="http://www.astrotexte.ch/sources/primaries.jsp"&gt;primaries.jsp&lt;/a&gt; nachrechnen.&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Eintritt in das Kloster der Dominikaner am &lt;b&gt;25.4.1475&lt;/b&gt;&lt;br /&gt;Für dieses Ereignis bringt Schoeler, durchaus passend, die Direktion &lt;i&gt;Sonne Opposition Jupiter convers&lt;/i&gt;, die allerdings bereits ein Jahr früher fällig wurde (12.4.1474). &lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Savonarola wird Prior des Klosters San Marco im Jahre &lt;b&gt;1490&lt;/b&gt;&lt;br /&gt;&lt;i&gt;MC Trigon Sonne direkt&lt;/i&gt; wurde am 1.12.1490 fällig. Wenig später wurde Savonarola als Kanzelredner berühmt (&lt;i&gt;Mond Konjunktion Jupiter direkt&lt;/i&gt; Anfang 1492).&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Savonarolas politische Tätigkeit beginnt im Jahre &lt;b&gt;1494&lt;/b&gt;&lt;br /&gt;Schoeler führt für dieses, nur mit vager Jahresangabe versehene Datum die Direktion &lt;i&gt;Mond Sextil Sonne convers&lt;/i&gt; auf, fällig am 27.5.1495. Auch &lt;i&gt;Aszendent Trigon Sonne direkt&lt;/i&gt; (18.6.1493) und &lt;i&gt;Mond Sextil MC direkt&lt;/i&gt; (6.12.1494) passen hierher.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Höhepunkt seiner Macht &lt;b&gt;1496/97&lt;/b&gt;&lt;br /&gt;Schoeler nennt in diesem Zusammenhang die Direktion &lt;i&gt;MC Konjunktion Venus convers&lt;/i&gt;, fällig am 18.2.1496.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Bann des Papstes am &lt;b&gt;12.5.1497&lt;/b&gt;&lt;br /&gt;Nach Schoeler in Zusammenhang mit &lt;i&gt;Glückspunkt Opposition Jupiter direkt&lt;/i&gt; zu sehen, eine Direktion, die recht genau zu diesem Zeitpunkt fällig wird. Am Himmel standen aber auch &lt;i&gt;Mond Anderthalbquadrat Saturn direkt&lt;/i&gt; (1.8.1497) und &lt;i&gt;Mond Anderhtalbquadrat Mars&lt;/i&gt; (1.8.1497).&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Trotz des Verbotes betritt Savonarola wieder die Kanzel am &lt;b&gt;11.2.1498&lt;/b&gt;&lt;br /&gt;Auch hier hat Schoeler wieder eine Direktion mit dem Glückspunkt als Signifikator: &lt;i&gt;Glückspunkt Quadrat Jupiter convers&lt;/i&gt;. Man kann aber auch &lt;i&gt;Mond Halbquadrat Mars direkt&lt;/i&gt; (20.4.1498) und &lt;i&gt;Mond Halbquadrat Saturn convers&lt;/i&gt; (8.4.1498) in diesem Zusammenhang sehen.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Tod durch Verbrennen am &lt;b&gt;23.5.1498&lt;/b&gt;&lt;br /&gt;Hier gibt Schoeler &lt;i&gt;Aszendent Quadrat Mars convers&lt;/i&gt; an - eine Direktion, die von der Tradition sehr gut passt und bei ihm eine Abweichung von nur einer Woche zum Ereignis ergibt. (In Wirklichkeit fällt sie auf den 20.8.1498.) Eine Rektifikation der Geburtszeit, um diese Direktion passend zu machen, kann ausgeschlossen werden, da Cardanus den Mars fälschlich auf 13° Steinbock statt im Widder plaziert hat (und auch im Text erwähnt, der Mars stünde in dieser Figur im zehnten Haus).&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;Das Geheimnis liegt hier - wie auch sonst bei Direktionslisten - in der richtigen Auswahl der zu berücksichtigenden Direktionen. Es gibt eine Reihe weiterer Direktionen in diesen zwanzig betrachteten Lebensjahren, auch "starke" Direktionen, die man hätte in Betracht ziehen können. Aber sie sind nicht so gut in der Hauptstruktur des Horoskops, dem T-Quadrat von Mars, Mond und Saturn verankert wie die hier verwendeten. Darüberhinaus zeigt die Nachrechnung, dass es auch hier wieder einen beachtlichen Orbis gibt. Auch wenn sie auf den Tag genau berechnet werden kann, kann eine Primärdirektion durchaus erst ein Jahr vor oder nach ihrer exakten Fälligkeit wirksam werden. Dies weiss auch Schoeler:&lt;br /&gt;&lt;blockquote&gt;Im allgemeinen aber ist zu sagen, dass überhaupt für die Direktion ein Umkreis zugestanden werden muss, der aber 1° nicht überschreiten soll. Dieser Umkreis für primäre Direktionen ist dadurch zu erklären, dass innerhalb eines Jahres Transite und eventuell auch Sekundärdirektionen ein Ereignis verzögern oder beschleunigen können. Immer aber wird ein wichtiges Ereignis durch eine primäre Direktion angezeigt.&lt;br /&gt;Ferner ist eine gewisse Differenz dadurch möglich, dass mit einem gegebenen Datum nicht das eigentliche Ereignis bestimmt wird. Im vorliegenden Fall ist es sehr wohl möglich, dass die Zeit des Noviziats nicht so wichtig war wie der endgültige Eintritt ins Kloster.&lt;/blockquote&gt;&lt;br /&gt;Zu diesen Problemen kommen theoretische Fragen hinzu. Einmal eingestanden, dass die auch von Schoeler favorisierte Halbbogenmethode ohne Berücksichtigung eklitpikaler Breiten und mit dem Ptolemäusschlüssel für die Umrechnung in Lebenszeit das richtige Vorgehen ist, ist auch die Frage nach der &lt;i&gt;Projektionsart auf die Ekliptik&lt;/i&gt; offen: Sollen die Gestirne, wenn sie nicht auf der Ekliptik stehen, vertikal auf diese projiziert werden, wie es die Astronomen und ihnen gleich die meisten Astrologen tun - oder sollte diese Projektion nicht richtiger längs ihrer mundanen Positionslinien erfolgen? Auch dadurch ergeben sich Abweichungen in den Auslösungen, die gut bei einem Jahr liegen können. Im Bewusstsein all dieser Unsicherheiten kommt es zu dem verhältnismässig grossen Umkreis der Primärdirektionen, der durch Hinzunahme anderer Methoden (Transite und wiederkehrende Konstellationen) weiter eingegrenzt werden kann. &lt;br /&gt;&lt;br /&gt;[1] Ernst Piper: &lt;a href="http://books.google.de/books?id=uCNnIBG1U8gC&amp;printsec=frontcover&amp;hl=de&amp;source=gbs_ge_summary_r#v=onepage&amp;q&amp;f=false"&gt;Savonarola, Prophet der Diktatur Gottes&lt;/a&gt;, Norderstedt 2009. &lt;br /&gt;[2] Dr. A. Schoeler: &lt;i&gt;Girolamo Savonarola, sein Horoskop und seine Direktionen&lt;/i&gt;. Die Astrologie, November 1932, S. 226-233.&lt;br /&gt;[3] Hieronymus Cardanus, &lt;i&gt;Liber de exemplis centum geniturarum&lt;/i&gt;, Online unter &lt;a href="http://www.cardano.unimi.it/testi/operaomnia/vol_5_s_7.pdf"&gt;http://www.cardano.unimi.it/testi/operaomnia/vol_5_s_7.pdf&lt;/a&gt;, dort das Savonarola-Horoskop auf Seite 490.&lt;br /&gt;[4] Baumgartner, &lt;i&gt;Fixsterne und ihre Deutung in der Geburts-Astrologie&lt;/i&gt;, Sonderdruck 11 der Astrologischen Universal-Harmonien, Warpke/Billerbeck o.J. Spica ist dort Fixstern Nr. 49.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-2630830820186472062?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/2630830820186472062/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=2630830820186472062' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/2630830820186472062'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/2630830820186472062'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2012/01/savonarola-sein-horoskop-und-seine.html' title='Savonarola - sein Horoskop und seine Direktionen'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-F6fb3xEcPkc/TwoETKY2jwI/AAAAAAAAAOY/dSbpQUW7Kgw/s72-c/savonarola.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-166839106963612412</id><published>2011-12-07T21:01:00.024+01:00</published><updated>2012-02-03T20:40:37.171+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>BIFF - eine Übung im Refaktorisieren</title><content type='html'>Laut Statistik sind die Beiträge meines Blogs, die von Excel und XML handeln, besonders beliebt - neben dem Dauerbrenner &lt;a href="http://ruediger-plantiko.blogspot.com/2008/06/refactoring-in-abap.html"&gt;Refactoring in ABAP&lt;/a&gt;. Das gibt mir die Rechtfertigung, mich hier erneut über ein Excel-Format auszulassen, und obendrein noch über das mittlerweile überholte &lt;a href="http://www.bl.uk/aboutus/stratpolprog/ccare/introduction/digital/formats/excel972007binaryfileformatxlsspecification.pdf"&gt;Binary Interchange File Format (BIFF)&lt;/a&gt;, das den Excel-Versionen bis einschliesslich Excel 2007 zugrundelag. &lt;br /&gt;&lt;br /&gt;Dieses auf binären Records basierende Format, das von der Apache POI Gemeinde als &lt;a href="http://poi.apache.org/spreadsheet/index.html"&gt;Horrible Spreadsheet Format (HSSF)&lt;/a&gt; geführt wird, hat Microsoft selbst mit dem neuen Format Open XML (das den Dateien mit der Endung &lt;tt&gt;.xlsx&lt;/tt&gt; zugrundeliegt) hinter sich gelassen.[1]&lt;br /&gt;&lt;br /&gt;Dennoch gibt es immer noch User, die mit dem alten &lt;tt&gt;xls&lt;/tt&gt;-Format arbeiten. Wenn die Daten aus solchen Excel-Sheets im SAP-System weiterverarbeitet werden sollen, verlangt man üblicherweise, dass der Benutzer sie als &lt;tt&gt;csv&lt;/tt&gt;-Datei abspeichert, um sie danach als Textdatei mit einem CSV-Parser einlesen zu können - wie ich es im Blog &lt;a href="http://ruediger-plantiko.blogspot.com/2009/06/parser-fur-komma-separierte-daten.html"&gt;Parser für komma-separierte Daten&lt;/a&gt; beschrieben habe. &lt;br /&gt;&lt;br /&gt;Wenn dies nicht möglich ist, könnte man eine binäre Excel-Datei in ABAP mit den Objekten der OLE Automation einlesen und bearbeiten. In diesem Fall wäre das Format sogar egal: Ein OLE-Objekt spricht die Daten des darunterliegenden Spreadsheets über Zeilen-, Spalten- und Arbeitsblattnummer an, ohne dass der Aufrufer sich um die zugrundeliegende Datenstruktur kümmern muss.&lt;br /&gt;&lt;br /&gt;Einziges Problem: Die OLE Automation erfordert einen SAP GUI. Damit ist sie für Hintergrundjobs, RFC-Bausteine oder Webanwendungen nicht geeignet. &lt;br /&gt;&lt;br /&gt;Um zu sehen, ob es prinzipiell möglich ist, das native Excel-Format einer &lt;tt&gt;xls&lt;/tt&gt;-Datei in ABAP direkt einzulesen, hatte ich im Jahre 2002 einen Baustein &lt;a href="http://bsp.mits.ch/code/fugr/zutil_excel"&gt;Z_EXCEL_TO_INTERNAL_TABLE&lt;/a&gt; geschrieben, der aber seitdem nie das Licht eines produktiven Einsatzes gesehen hat. Das Experiment war erfolgreich ausgegangen: Der Baustein war tatsächlich in der Lage gewesen, die wesentlichen Zellinhalte eines Excel-Files - Zahlen und Strings - in eine ABAP-Datenstruktur einzulesen. &lt;br /&gt;&lt;br /&gt;Manche Dinge brauchen einfach ihre Zeit: Nach fast zehn Jahren wird dieser Baustein nun in einem produktiven Kontext benötigt: für die Eingangsbearbeitung von Dokumenten in einem Szenario, in dem den Benutzern keine Vorgaben über das verwendete Format gemacht werden können und Datenfiles sowohl im neuen (&lt;tt&gt;xlsx&lt;/tt&gt;) als auch im alten (&lt;tt&gt;xls&lt;/tt&gt;) Excel-Format eingehen. &lt;br /&gt;&lt;br /&gt;Für das neue Format verwenden wir die von Ivan Femia und Gregor Wolf entwickelte Addon-Komponente &lt;a href="http://wiki.sdn.sap.com/wiki/display/ABAP/abap2xlsx"&gt;abap2xlsx&lt;/a&gt;, die keine Wünsche offen lässt. Da auch das alte Format in der im Hintergrund laufenden Eingangsverarbeitung benötigt wird, war dies die Möglichkeit, den Baustein, der nun eine Dekade im Repository gelagert hatte, in kleinen Schritten zu verbessern: Ihn ein bisschen in Schuss zu bringen und zu entwanzen.&lt;br /&gt;&lt;br /&gt;Programmiergewohnheiten ändern sich im Laufe der Jahre, und wenn es auch von Zeit zu Zeit nur wenige Änderungen zu sein scheinen, zeigen sich doch beim Blick auf zehn Jahre alten Code beträchtliche Unterschiede im Stil.&lt;br /&gt;&lt;br /&gt;Die Namenskonventionen des CRM mit ihrem zweibuchstabigen Präfix verwendete ich allerdings schon damals. Und doch stimme ich mit Horst Keller [2] wie auch Robert C. Martin [3] überein, dass es sich bei diesen Präfix-Namenskonventionen nur um Krücken handelt und dass Variablennamen noch besser lesbar sind, wenn sie durch überhaupt kein Präfix verschmutzt werden. Aber präfixfreie Variablennamen können wir in uns in ABAP erst leisten, wenn es in der ABAP-Programmierergemeinde zur Gewohnheit geworden ist, wirklich kurze Methoden zu schreiben, d.h.: Nicht mehr hundert-, sondern zehnzeilige Methoden. Davon sind wir de facto noch entfernt. Solange das so ist, haben die "ungarische" und verwandte Konventionen noch ihre Existenzberechtigung: In einer Methode, die man nicht ganz auf dem Bildschirm zu sehen bekommt (also inclusive ihrer lokalen &lt;tt&gt;data&lt;/tt&gt;-Definitionen), ist es nützlich, wenn man jeder Variablen sofort ihren Gültigkeitsbereich ansieht. &lt;br /&gt;&lt;br /&gt;Natürlich würde ich heute für eine solche Aufgabe eine Klasse schreiben, keine Funktionsgruppe. Auch waren meine Funktionen damals viel länger als ich es mir heute erlauben würde. Insgesamt enthält diese Funktionsgruppe für das, was sie leistet, viel zu wenig Modularisierungseinheiten, d.h. vor allem zu wenig Unterprogramme. &lt;br /&gt;&lt;br /&gt;In diesem speziellen Fall hatte ich mich aus Performancegründen obendrein zu einer intensiven Verwendung von Macros entschlossen: Der Stream eines einzelnen Worksheets ist im BIFF-Format ein langer binärer String, auf den ich immer wieder mit Offset und Länge zugreifen musste, um einzelne Bytes, Doppelbytes oder Vier-Byte-Wörter zu lesen. Um nicht für jedes einzelne Byte, das ich auf diese Weise lese, einen Unterprogrammaufruf machen zu müssen, hatte ich ein Macro &lt;tt&gt;_get_data&lt;/tt&gt; mit zwei Parametern entworfen: Der Anzahl der zu lesenden Bytes und der Zielvariablen (ein Feld vom Typ &lt;tt&gt;x&lt;/tt&gt; oder ein &lt;tt&gt;xstring&lt;/tt&gt;), in die die Bytes hineingestellt werden sollen. &lt;br /&gt;&lt;br /&gt;Nun ist es so, dass die Folge der zu lesenden Bytes im BIFF-Format an beliebiger Stelle durch das Ende des aktuellen Records unterbrochen werden kann. Es folgt dann meistens ein zweibytiger &lt;tt&gt;CONTINUE&lt;/tt&gt;-Code und die Länge dieses &lt;tt&gt;CONTINUE&lt;/tt&gt;-Records, und erst dann geht es weiter mit der Bytefolge. Um diesen Fall abzuhandeln, hatte ich in das Macro schrecklich viel Logik hineingepackt. Ich wundere mich nachträglich, wie ich diesen Code, ohne ihn debuggen zu können, verfasst haben kann [4]:  &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;*---------------------------------------------------------------------*&lt;br /&gt;* Macro zum Lesen von Daten aus dem Stream&lt;br /&gt;*---------------------------------------------------------------------*&lt;br /&gt;  define _get_data.&lt;br /&gt;* Berechnen des Offsets nach erfolgtem Lesen&lt;br /&gt;    lv_pos = gv_pos + &amp;amp;1.&lt;br /&gt;* Zu gross? Fehler&lt;br /&gt;    if lv_pos &gt; gv_length.&lt;br /&gt;      message 'Fehler: Speicherüberschreitung bei get data (1)' type 'X'.&lt;br /&gt;    endif.&lt;br /&gt;&lt;br /&gt;    if lv_next_key ne gc_record-continue or&lt;br /&gt;       lv_pos &amp;lt; lv_next_pos.&lt;br /&gt;*---------------------------------------------------------------------*&lt;br /&gt;* Fall 1: Zu lesendes Datum liegt vollständig im aktuellen Record&lt;br /&gt;*---------------------------------------------------------------------*&lt;br /&gt;* Feld einlesen&lt;br /&gt;      &amp;amp;2 = &amp;lt;gv_data&gt;+gv_pos(&amp;amp;1).&lt;br /&gt;* Globalen Positionszeiger vorsetzen&lt;br /&gt;      add &amp;amp;1 to gv_pos.&lt;br /&gt;    else.&lt;br /&gt;* Abstand bis zum Offset des CONTINUE Key:&lt;br /&gt;      lv_m_dist = lv_next_pos - gv_pos.&lt;br /&gt;*---------------------------------------------------------------------*&lt;br /&gt;* Fall 2: Zu lesendes Datum muss einen Continue-Record überspringen&lt;br /&gt;*---------------------------------------------------------------------*&lt;br /&gt;      if &amp;amp;1 &gt; lv_m_dist.&lt;br /&gt;* Erster Teil des Feldes kann eingelesen werden:&lt;br /&gt;        &amp;amp;2 = &amp;lt;gv_data&gt;+gv_pos(lv_m_dist).&lt;br /&gt;* Jetzt Länge des CONTINUE Records ermitteln (little Endian Format!)&lt;br /&gt;        lv_pos = lv_next_pos + 2.&lt;br /&gt;        lv_m_xlength+1  = &amp;lt;gv_data&gt;+lv_pos(1).&lt;br /&gt;        add 1 to lv_pos.&lt;br /&gt;        lv_m_xlength(1) = &amp;lt;gv_data&gt;+lv_pos(1).&lt;br /&gt;* lv_pos auf erstes Byte des CONTINUE Records setzen:&lt;br /&gt;        add 1 to lv_pos.&lt;br /&gt;* Neue lv_next_pos = Länge des CONTINUE Records + 4      + Offset von CONTINUE&lt;br /&gt;*                  = Länge des CONTINUE Records + lv_pos&lt;br /&gt;        lv_next_pos = lv_m_xlength + lv_pos.&lt;br /&gt;* Der auf CONTINUE folgende nächste Key muss ermittelt werden:&lt;br /&gt;        lv_length = lv_next_pos + 2.&lt;br /&gt;        if lv_length &amp;lt;= gv_length.&lt;br /&gt;          lv_next_key = &amp;lt;gv_data&gt;+lv_next_pos(2).&lt;br /&gt;          concatenate lv_next_key+1(1) lv_next_key(1) into lv_next_key in byte mode.&lt;br /&gt;        else.&lt;br /&gt;* Keine Daten mehr -&gt; Schleife ist beendet&lt;br /&gt;          exit.&lt;br /&gt;        endif.&lt;br /&gt;* Verbleibende, zu lesende Zeichenzahl ermitteln:&lt;br /&gt;        lv_m_length = &amp;amp;1 - lv_m_dist.&lt;br /&gt;* Reststring lesen und an &amp;amp;2 anhängen&lt;br /&gt;        if lv_m_dist &gt; 0.&lt;br /&gt;          concatenate &amp;amp;2 &amp;lt;gv_data&gt;+lv_pos(lv_m_length) into &amp;amp;2 in byte mode.&lt;br /&gt;        else.&lt;br /&gt;          &amp;amp;2 = &amp;lt;gv_data&gt;+lv_pos(lv_m_length).&lt;br /&gt;        endif.&lt;br /&gt;* Globalen Positionszeiger vorsetzen&lt;br /&gt;        gv_pos = lv_pos + lv_m_length.&lt;br /&gt;      else.&lt;br /&gt;* Jetzt Länge des CONTINUE Records ermitteln (little Endian Format!)&lt;br /&gt;        lv_pos = lv_next_pos + 2.&lt;br /&gt;        lv_m_xlength+1  = &amp;lt;gv_data&gt;+lv_pos(1).&lt;br /&gt;        add 1 to lv_pos.&lt;br /&gt;        lv_m_xlength(1) = &amp;lt;gv_data&gt;+lv_pos(1).&lt;br /&gt;* lv_pos auf erstes Byte des CONTINUE Records setzen:&lt;br /&gt;        add 1 to lv_pos.&lt;br /&gt;* Neue lv_next_pos = Länge des CONTINUE Records + 4      + Offset von CONTINUE&lt;br /&gt;*                  = Länge des CONTINUE Records + lv_pos&lt;br /&gt;        lv_next_pos = lv_m_xlength + lv_pos.&lt;br /&gt;* Der auf CONTINUE folgende nächste Key muss ermittelt werden:&lt;br /&gt;        lv_length = lv_next_pos + 2.&lt;br /&gt;        if lv_length &amp;lt;= gv_length.&lt;br /&gt;          lv_next_key = &amp;lt;gv_data&gt;+lv_next_pos(2).&lt;br /&gt;          concatenate lv_next_key+1(1) lv_next_key(1) into lv_next_key in byte mode.&lt;br /&gt;        else.&lt;br /&gt;* Keine Daten mehr -&gt; Schleife ist beendet&lt;br /&gt;          exit.&lt;br /&gt;        endif.&lt;br /&gt;* String lesen und an &amp;amp;2 anhängen&lt;br /&gt;        &amp;amp;2 = &amp;lt;gv_data&gt;+lv_pos(&amp;amp;1).&lt;br /&gt;* Globalen Positionszeiger vorsetzen&lt;br /&gt;        gv_pos = lv_pos + &amp;amp;1.&lt;br /&gt;      endif.&lt;br /&gt;    endif.&lt;br /&gt;  end-of-definition.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt; &lt;br /&gt;Der Code ist nicht falsch, aber trotzdem scheusslich und für das, was er tun soll, zu lang. Ein Beispiel: Wenn man sich die Bedingung des inneren &lt;tt&gt;if&lt;/tt&gt; genau anschaut, so folgt sie bereits aus der Bedingung des umgebenden &lt;tt&gt;else&lt;/tt&gt;-Zweigs. Das bedeutet, der innere &lt;tt&gt;else&lt;/tt&gt;-Zweig kann nie durchlaufen werden und kann somit gelöscht werden. &lt;br /&gt;&lt;br /&gt;Ich hatte nun die Möglichkeit, das Macro komplett durch ein Unterprogramm zu ersetzen. Um die ursprüngliche Intention (die Performance-Bedenken) zu berücksichtigen, habe ich das Macro aber so stehengelassen, dass wenigstens der Hauptfall weiterhin ohne Unterprogramm ausgeführt werden kann. Nur den eher weniger häufigen Fall, dass beim Einlesen von Bytes ein &lt;tt&gt;CONTINUE&lt;/tt&gt;-Record übersprungen werden muss, habe ich in ein Unterprogramm ausgelagert. Das Macro sieht neu wie folgt aus: &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;  define _get_data.&lt;br /&gt;&lt;br /&gt;    _compute_offset_by &amp;amp;1 lv_pos.&lt;br /&gt;&lt;br /&gt;    if lv_next_key ne gc_record-continue or&lt;br /&gt;       lv_pos &amp;lt;= lv_next_pos.&lt;br /&gt;&lt;br /&gt;*---------------------------------------------------------------------*&lt;br /&gt;* Fall 1: Zu lesendes Datum liegt vollständig im aktuellen Record&lt;br /&gt;*---------------------------------------------------------------------*&lt;br /&gt;* Feld einlesen&lt;br /&gt;      &amp;amp;2 = &amp;lt;gv_data&gt;+gv_pos(&amp;amp;1).&lt;br /&gt;* Globalen Positionszeiger vorsetzen&lt;br /&gt;      add &amp;amp;1 to gv_pos.&lt;br /&gt;&lt;br /&gt;    else.&lt;br /&gt;&lt;br /&gt;*---------------------------------------------------------------------*&lt;br /&gt;* Fall 2: Zu lesendes Datum muss einen Continue-Record überspringen&lt;br /&gt;*---------------------------------------------------------------------*&lt;br /&gt;      perform read_over_continue using    &amp;amp;1 space&lt;br /&gt;                                 changing &amp;amp;2&lt;br /&gt;                                          lv_m_options&lt;br /&gt;                                          lv_next_pos&lt;br /&gt;                                          lv_next_key.&lt;br /&gt;    endif.&lt;br /&gt;  end-of-definition.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Das ist vor allem viel weniger Code als früher: So wenig, dass man sich trauen kann, das &lt;tt&gt;_get_data&lt;/tt&gt; im Debugger mit einem Schritt auszuführen und das Ergebnis gerade noch mit blossem Auge nachvollziehen kann - zumindest wenn es nicht in den &lt;tt&gt;else&lt;/tt&gt;-Zweig geht, aber in den könnte man mittels &lt;i&gt;Einzelschritt&lt;/i&gt; (F5) hineindebuggen, da er ja in einem Unterprogramm ausgeführt wird. &lt;br /&gt;&lt;br /&gt;Die anderen Macros wie &lt;tt&gt;_get_number&lt;/tt&gt; zum Einlesen von einzelnen Records habe ich vollständig durch Unterprogramme ersetzt.&lt;br /&gt;&lt;br /&gt;Mithilfe von Unit Tests konnte ich darüberhinaus einige Bugs aufdecken, die im Code noch schlummerten. Der erste einfache Test funktionierte noch so, dass ich ein komplettes File im Binärcode einlas:&lt;br /&gt;&lt;pre class="sh_abap"&gt;  method test_simple.&lt;br /&gt;&lt;br /&gt;    data: lv_xls type xstring,&lt;br /&gt;          ls_xls type zexcel,&lt;br /&gt;          ls_exp type zexcel.&lt;br /&gt;&lt;br /&gt;* Einfaches XLS-Worksheet im Binärformat aufbauen&lt;br /&gt;* | abc | 123 | xyz |&lt;br /&gt;* | 456 | pqr | 789 |&lt;br /&gt;    perform get_simple_xls in program zutil_excel_testdata changing lv_xls.&lt;br /&gt;&lt;br /&gt;* ... und dem Funktionsbaustein vorlegen&lt;br /&gt;    call function 'Z_EXCEL_TO_INTERNAL_TABLE'&lt;br /&gt;      exporting&lt;br /&gt;        iv_file_as_stream = lv_xls&lt;br /&gt;      importing&lt;br /&gt;        es_excel          = ls_xls.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;* Testerwartung&lt;br /&gt;    _insert_row_n_fields ls_exp-cells 'row;col;typ;ref' :&lt;br /&gt;      '0001;0001;S;00000001',&lt;br /&gt;      '0001;0002;F;00000001',&lt;br /&gt;      '0001;0003;S;00000002',&lt;br /&gt;      '0002;0001;F;00000002',&lt;br /&gt;      '0002;0002;S;00000003',&lt;br /&gt;      '0002;0003;F;00000003'.&lt;br /&gt;&lt;br /&gt;    define _append.&lt;br /&gt;      append &amp;amp;2 to &amp;amp;1.&lt;br /&gt;    end-of-definition.&lt;br /&gt;&lt;br /&gt;    _append ls_exp-strings:&lt;br /&gt;      'abc',&lt;br /&gt;      'xyz',&lt;br /&gt;      'pqr'.&lt;br /&gt;&lt;br /&gt;    _append ls_exp-float:&lt;br /&gt;      123,&lt;br /&gt;      456,&lt;br /&gt;      789.&lt;br /&gt;&lt;br /&gt;    cl_aunit_assert=&gt;assert_equals( act = ls_xls&lt;br /&gt;                                    exp = ls_exp ).&lt;br /&gt;&lt;br /&gt;  endmethod.                    "test_simple&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Um die Load der Funktionsgruppe nicht unnötig zu vergrössen, habe ich dabei die Herstellung des &lt;tt&gt;xls&lt;/tt&gt;-Dokuments in ein externes Unterprogramm ausgelagert:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;program zutil_excel_testdata.&lt;br /&gt;&lt;br /&gt;form get_simple_xls changing cv_xls type xstring.&lt;br /&gt;&lt;br /&gt;  data: lv_line type xstring.&lt;br /&gt;&lt;br /&gt;  define _add_line.&lt;br /&gt;    lv_line = &amp;amp;1.&lt;br /&gt;    concatenate cv_xls lv_line into cv_xls in byte mode.&lt;br /&gt;  end-of-definition.&lt;br /&gt;&lt;br /&gt;* Binäres Bild einer Excel-Datei mit folgendem Inhalt:&lt;br /&gt;* | abc | 123 | xyz |&lt;br /&gt;* | 456 | pqr | 789 |&lt;br /&gt;&lt;br /&gt;  _add_line:&lt;br /&gt;'D0CF11E0A1B11AE100000...0FEFFFFFF00000000FEFF',&lt;br /&gt;'FFFF000000001800000FF...FFFFFFFFFFFFFFFFFFFFF',&lt;br /&gt;...&lt;br /&gt;endform.&lt;/pre&gt;&lt;br /&gt;Würde ich diesen totalen Weg über den Funktionsbaustein für alle Testfälle wählen, die mir einfallen, so wäre die Gesamt-Ausführungszeit der Unit Tests zu hoch. Ich entschied mich daher, für die Teile des Codes, die die einzelnen Records auswerten, isolierte Tests zu schreiben. &lt;br /&gt;&lt;br /&gt;Hier zum Beispiel ist eine Methode, die das Einlesen einer im Format &lt;tt&gt;RK_NUMBER&lt;/tt&gt; codierten Ganzzahl testet:&lt;br /&gt;&lt;pre class="sh_abap"&gt;  method test_get_rk_i.&lt;br /&gt;&lt;br /&gt;    data: ls_excel type zexcel,&lt;br /&gt;          lv_int type i.&lt;br /&gt;&lt;br /&gt;    get_rk_number( exporting iv_data = '3A85F102'&lt;br /&gt;            changing  cs_excel  = ls_excel ).&lt;br /&gt;&lt;br /&gt;    read table ls_excel-int into lv_int index 1.&lt;br /&gt;    assert_subrc( act = sy-subrc&lt;br /&gt;                  msg = 'Die Ganzzahl wurde nicht erkannt' ).&lt;br /&gt;&lt;br /&gt;    assert_equals( act = lv_int&lt;br /&gt;                   exp = 12345678&lt;br /&gt;                   msg = 'Ganzzahl wurde nicht korrekt gelesen' ).&lt;br /&gt;&lt;br /&gt;  endmethod.                    "test_get_rk_i&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Die &lt;tt&gt;RK_NUMBER&lt;/tt&gt;-Tests laufen immer über die Hilfsmethode &lt;tt&gt;get_rk_number&lt;/tt&gt; der Testklasse. Für die anderen Recordtypen gibt es ähnliche Hilfsmethoden. Sie stellen die benötigten globalen Daten - das Feldsymbol &lt;tt&gt;&amp;lt;gv_data&gt;&lt;/tt&gt; mit dem Stream, sowie &lt;tt&gt;gv_length&lt;/tt&gt; mit dessen Länge, bereit.  &lt;br /&gt;&lt;pre class="sh_abap"&gt;  method get_rk_number.&lt;br /&gt;&lt;br /&gt;    data: lv_next_pos type i,&lt;br /&gt;          lv_next_key type recordkey,&lt;br /&gt;          ls_cell type zexcelcell.&lt;br /&gt;&lt;br /&gt;    assign iv_data to &amp;lt;gv_data&gt;.&lt;br /&gt;    gv_length = xstrlen( &amp;lt;gv_data&gt; ).&lt;br /&gt;    gv_pos = 0.&lt;br /&gt;&lt;br /&gt;    perform get_rk_number using ls_cell&lt;br /&gt;            changing lv_next_pos lv_next_key cs_excel.&lt;br /&gt;&lt;br /&gt;  endmethod.                    "get_rk_number&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Mit solchen kleinen Testmethoden konnte ich Probleme isolieren, die beim Einlesen von grösseren Excel-Dateien auftraten. Dann konnte ich den Code so lange ändern, bis auch der neue Test auf Grün ging. Wenn ich die Suite aktuell im Unit Test Browser mit Abdeckungsmesung laufen lasse, komme ich auf eine Prozedurenabdeckung von 85%. Für einzelne Routinen ist es klar, dass sie nicht durch Modultests abgedeckt werden können, z.B. &lt;tt&gt;read_file&lt;/tt&gt;: Hier wird eine Datei aus dem Filesystem im Binärformat eingelesen und in einen &lt;tt&gt;xstring&lt;/tt&gt; gestellt. Mehr als den Aufruf der Methode &lt;tt&gt;gui_upload()&lt;/tt&gt; von &lt;tt&gt;cl_gui_frontend_services&lt;/tt&gt; sowie dem Fehlerhandling enthält diese Routine aber auch nicht. Das ist also eine Lücke, die man lassen kann. Andere Stellen, die nicht abgedeckt sind, betreffen Grenzfälle: Nicht alle Parametrisierungen, mit denen der Baustein theoretisch aufgerufen würde, auch wenn sie zu einem Fehler führten, werden in einem Test durchgespielt. Wichtiger erschienen mir bei diesen nachträglich hinzugefügten Unit Tests die Kernfunktionen: Das Einlesen der verschiedenen Recordformate für Zahlen und Strings.  &lt;br /&gt;&lt;br /&gt;Natürlich bin ich noch lange nicht zufrieden mit dem Ergebnis der Refaktorisierungen. Man könnte die Sache noch weitertreiben. Da ich nur begrenzte Kapazitäten dafür aufwenden kann, muss aber irgendwo ein Punkt gemacht werden. Ich habe den Campingplatz sicher sauberer hinterlassen als ich ihn diesmal vorgefunden habe. Vielleicht gibt es ja ein nächstes Mal, um weitere Verbesserungen einzubringen. &lt;br /&gt;&lt;br /&gt;Sehr hilfreich bei der Analyse des Formats und bei der Erstellung von Beispielen war mir übrigens ein altes, einfaches Programm namens &lt;a href="http://b2xtranslator.sourceforge.net/download.html#biffviewer"&gt;biffview.exe&lt;/a&gt;, das den Stream eines Worksheets analysiert und die einzelnen Bestandteile hexadezimal ausgibt - mit Verweis auf die Spezifikation des jeweiligen Recordtyps.  &lt;br /&gt;&lt;br /&gt;[1] Was das neuere OpenXML-Format angeht, so ist die Konvertierung von und nach ABAP mit der sehr komfortablen Softwarekomponente &lt;a href="http://wiki.sdn.sap.com/wiki/display/ABAP/abap2xlsx"&gt;abap2xlsx&lt;/a&gt; gelöst, die eine bemerkenswert grosse Teilmenge des Excel-Feature-Sets unterstützt.&lt;br /&gt;[2] Horst Keller, Wolf Hagen Thümmel: &lt;i&gt;ABAP-Programmierichtlinien&lt;/i&gt;, Galileo Press, 2009&lt;br /&gt;[3] Robert C. Martin: &lt;i&gt;Clean Code: A Handbook of Agile Software Craftsmanship&lt;/i&gt;, Prentice Hall International, 2008.&lt;br /&gt;[4] Es ist allerdings ein Mythos zu glauben, Macros liessen sich grundsätzlich nicht debuggen. Das Problem für den Debugger besteht ja nur darin, dass er den ausgeführten Anweisungen keine laufende Quelltextzeile zuordnen kann. Wenn man den Macro-Quelltext in eine Include-Datei auslagert - etwa so:&lt;br /&gt;&lt;pre class="sh_abap"&gt;define _langes_macro.&lt;br /&gt;  include z_langes_macro_corpus.&lt;br /&gt;end-of-definition.&lt;/pre&gt;&lt;br /&gt;dann werden die einzelnen Anweisungen im Debugger ganz normal erreichbar. Das funktioniert allerdings nur für parameterlose Macros, denn die Symbole &lt;tt&gt;&amp;1&lt;/tt&gt; etc. können vom Compiler nur im Gültigkeitsbereich einer Datei aufgelöst werden.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-166839106963612412?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/166839106963612412/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=166839106963612412' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/166839106963612412'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/166839106963612412'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/12/biff-eine-ubung-im-refaktorisieren.html' title='BIFF - eine Übung im Refaktorisieren'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-1625198670506549046</id><published>2011-10-15T11:30:00.016+01:00</published><updated>2011-10-28T06:52:00.503+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Eine kleine Anfrage - und die Folgen</title><content type='html'>Am 22. September erhielt ich per Mail die Anfrage,&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;&lt;br /&gt;...ob Sie die Lösung von folgender Rechnung zufällig wissen:&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;1 2 3 4 5 6 7 8 9 = 2011&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;Die Zahlen müssen der Reihenfolge nach gebraucht werden, es darf mit +, -, &amp;middot; und : gerechnet werden und Klammern gesetzt. &lt;br /&gt;&lt;br /&gt;Ich wünsche Ihnen einen schönen Abend!&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;Natürlich wusste ich die Lösung dieser Rechnung &lt;i&gt;nicht&lt;/i&gt;. Ich wusste aber, dass Aufgaben dieser Art häufig in den Rätselecken von Zeitschriften zu finden und meist mit ein bisschen Getüftel und Probieren zu bewältigen sind &amp;ndash; eine Tätigkeit, die von Laien oft mit Mathematik verwechselt wird, aber praktisch nichts mit ihr zu tun hat. Mathematik wäre es, wenn es einen "Top Down Approach" zur Lösung gäbe, man also aufgrund von systematischen, grundsätzlichen Überlegungen eine Lösung finden könnte. Das schien bei diesem Problem nicht der Fall zu sein. &lt;br /&gt;&lt;br /&gt;Wen es als Tüftelei interessiert: Die Fussnote [1] zeigt einen Weg zu &lt;i&gt;einer&lt;/i&gt; Lösung. Was man dabei nicht erfährt: gibt es eventuell noch mehr Lösungen? Der Lösungsweg ist nicht systematisch, sondern eine Art zielgerichtetes Probieren. Gibt es auch einen Ansatz, mit dem man mit Papier und Bleistift systematisch alle Lösungen herausfinden kann? (Vermutlich nicht.)&lt;br /&gt;&lt;br /&gt;Da mir aber kein &lt;i&gt;mathematischer&lt;/i&gt; systematischer Zugang zum Problem einfiel, wechselte ich schliesslich die Rolle: Ich schaute mir das Problem als Informatiker an. Wie praktikabel ist zum Beispiel die Holzhammermethode, alle möglichen Ausdrücke mit einem Computer auszurechnen? Mit wie vielen Möglichkeiten hat man es eigentlich zu tun?&lt;br /&gt;&lt;br /&gt;Es fällt auf, dass die Zahl der Möglichkeiten offenbar ein Produkt ist: Die Zahl der möglichen Klammerungen multipliziert mit den Wahlmöglichkeiten für die acht Operationen. Letztere Anzahl ist leicht zu bestimmen: Da nur die vier Grundrechenarten zulässig sind und jede der acht Operationen unabhängig von den anderen gewählt werden kann, gibt es &lt;pre class="sh_sourceCode"&gt;4&lt;sup&gt;8&lt;/sup&gt; = 65'536&lt;/pre&gt; Wahlmöglichkeiten für die Operationen. &lt;br /&gt;&lt;br /&gt;Die möglichen Klammerungen führen zu den Baumdarstellungen arithmetischer Ausdrücke und schliesslich zu den sogenannten &lt;a href="http://de.wikipedia.org/wiki/Catalan-Zahl"&gt;Catalanschen Zahlen&lt;/a&gt;. In diesem Fall kommt man auf &lt;pre class="sh_sourceCode"&gt;C&lt;sub&gt;8&lt;/sub&gt; = 1430&lt;/pre&gt;&lt;br /&gt;verschiedene Möglichkeiten, den Ausdruck zu klammern. Hier fragte ich mich - von diesem Problem ausgehend: Kann man die möglichen Klammerungen in Form eines Algorithmus der Reihe nach durchzählen? Im Blog &lt;a href="http://ruediger-plantiko.blogspot.com/2011/10/ein-algorithmus-zum-aufzahlen-von.html"&gt;Ein Algorithmus zum Aufzählen von Ausdrucksbäumen&lt;/a&gt; habe ich das Problem und die Lösung skizziert. In allen Details dargelegt, führte das zu einem kleinen &lt;a href="http://ruediger-plantiko.net/etree.pdf"&gt;Artikel&lt;/a&gt;, in dem ich den Weg vom Ausdrucksbaum über ein zugeordnetes Schema, eine Signatur und Zahlenfolgen, die ich "gerichtet" nannte, beschritt. In diesem Artikel beschreibe ich auch den gesuchten &lt;a href="http://pastebin.com/mehTM4Z0"&gt;Algorithmus&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Das Produkt der Klammerungen mit den möglichen Belegungen der acht Operationen ergibt also 93'716'480 zu prüfende Ausdrücke - eine vom Rechner leicht zu handhabende Grösse, falls man sich für die richtige Programmiersprache entscheidet.[2]&lt;br /&gt;&lt;br /&gt;Hier noch einmal die Anforderungen: &lt;br /&gt;&lt;ul&gt;&lt;br /&gt;  &lt;li&gt;Es sollte nicht mit den üblichen Gleitkommazahlen, sondern mit &lt;i&gt;rationalen Zahlen&lt;/i&gt;, also Paaren &lt;tt&gt;{zaehler,nenner}&lt;/tt&gt; ganzer Zahlen gerechnet werden, damit nicht aufgrund von Genauigkeitsverlusten eine Lösung verlorengeht. &lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Die Auswertung eines Ausdrucks kann natürlich abgebrochen werden, sobald eine Division durch Null auftritt.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Nach dem Assoziativgesetz identische Ausdrücke wie &lt;tt&gt;a&amp;middot;(b&amp;middot;c)&lt;/tt&gt; und &lt;tt&gt;(a&amp;middot;b)&amp;middot;c&lt;/tt&gt; sollen nur einmal in der Ergebnisliste vorkommen.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Die gefundenen Klammerausdrücke sollten in einer für Menschen lesbaren Form ausgegeben werden &amp;ndash; unabhängig davon, welche Datenstruktur intern zur Darstellung der Ausdrücke gewählt wird.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;In der Produktbildung&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;Klammerungen&lt;/i&gt; &amp;times; &lt;i&gt;Mögliche Wahlen der Operationen&lt;/i&gt;&lt;/blockquote&gt; ist bereits ein ideales Schema für die Parallelisierung angelegt: Für jedes festes Klammerschema ergibt sich die Aufgabe, alle acht Plätze mit den verschiedenen möglichen Operationen zu belegen, den entstehenden Ausdruck zu evaluieren und zu prüfen, ob er das gewünschte Ergebnis liefert. Der Zeitpunkt, wenn neue Klammerschemata aufgebaut wurden, ist daher gut für einen &lt;i&gt;Schnitt&lt;/i&gt; geeignet, bei dem man Threads starten und parallel abarbeiten kann. &lt;br /&gt;&lt;br /&gt;Den vollständigen Quelltext und die Binaries des Programms, das ich zur Lösung dieser Aufgabe implementiert habe, findet man bei &lt;tt&gt;github&lt;/tt&gt; unter dem Link&lt;br /&gt;&lt;br /&gt;&lt;a href="https://github.com/rplantiko/computeExpressions"&gt;https://github.com/rplantiko/computeExpressions&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Hier will ich einige Ideen der Implementierung genauer erörtern.&lt;br /&gt;&lt;br /&gt;Eine grundlegende Designentscheidung war, für ein einzelnes Klammerschema eine Klasse &lt;tt&gt;ExpressionTemplate&lt;/tt&gt; einzuführen. Ein Objekt des Type &lt;tt&gt;ExpressionTemplate&lt;/tt&gt; enthält die Angaben über ein konkretes Klammerschema, aber auch einen Vektor für die gefundenen Ergebnisse. Es kann also mit seinen eigenen Daten arbeiten, ohne vom Rest des Programms abzuhängen. &lt;br /&gt;&lt;br /&gt;Immer wenn der Algorithmus für Klammerungen ein neues Klammerschema aufgebaut hat, wird eine neue Instanz von &lt;tt&gt;ExpressionTemplate&lt;/tt&gt; gebildet und kommt in einen Vorrat, die globale Variable &lt;pre class="sh_cpp"&gt;std::vector&amp;lt;ExpressionTemplate*&gt; worklist;&lt;br /&gt;&lt;/pre&gt;Dies ist übrigens die einzige globale Variable, die sich während des Programmlaufs noch ändert.  &lt;br /&gt;&lt;br /&gt;Über einen Parameter &lt;tt&gt;numThreads&lt;/tt&gt;, den man dem Programm zur Ausführung mitgeben kann, wird gesteuert, wieviele Klammerschemata von der CPU auf einmal verarbeitet werden dürfen. Sobald die &lt;tt&gt;worklist&lt;/tt&gt; diese Anzahl von Einträgen erreicht hat, werden die Threads gestartet. Jeder Thread arbeitet auf einer eigenen Instanz von &lt;tt&gt;ExpressionTemplate&lt;/tt&gt; und kommt damit weder den anderen noch dem aufrufenden Thread ins Gehege. Da die Threads ungefähr alle die gleiche Verarbeitungszeit haben, genügt ein FIFO-Join, um die Beendigung aller Threads abzuwarten: Auf die Beendigung des zuerst gestarteten Threads wird als erstes gewartet, danach auf den zweiten usw.[3] &lt;br /&gt;&lt;br /&gt;Hier die Besucherfunktion &lt;tt&gt;nextExecutionPlan&lt;/tt&gt;, die für jedes Klammerschema einmal aufgerufen wird.[4]&lt;pre class="sh_cpp"&gt;void nextExecutionPlan( node* root ) {&lt;br /&gt;&lt;br /&gt;  if (root != 0) {  // There must be a final cleanup call with root = 0&lt;br /&gt;    worklist.push_back( &lt;br /&gt;      new ExpressionTemplate(root,numbers,expectedResult,size) &lt;br /&gt;      );&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;  if (worklist.size() &gt;= numThreads || root == 0) {&lt;br /&gt;  &lt;br /&gt;    std::vector&amp;ltpthread_t&gt; threads;&lt;br /&gt;  &lt;br /&gt;    for (auto templ = worklist.begin(); templ != worklist.end(); ++templ) {&lt;br /&gt;      pthread_t t;&lt;br /&gt;      pthread_create( &amp;t, 0, evaluate, *templ );&lt;br /&gt;      threads.push_back( t );&lt;br /&gt;      }&lt;br /&gt;    &lt;br /&gt;// Waiting for the end of the threads, in the order of their creation &lt;br /&gt;// (not so bad, but could be improved, of course)    &lt;br /&gt;    for (auto t = threads.begin(); t != threads.end(); ++t ) {&lt;br /&gt;      pthread_join( *t, 0 );      &lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;// The work of this package is done. Print the result &amp; free the ressources   &lt;br /&gt;    for (auto templ = worklist.begin(); templ != worklist.end(); ++templ) {&lt;br /&gt;      (*templ)-&gt;printResults();&lt;br /&gt;      delete *templ;&lt;br /&gt;      }    &lt;br /&gt;    &lt;br /&gt;    worklist.resize( 0 );&lt;br /&gt;    &lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;Nach Ausführung muss schliesslich noch ein allfällig verbliebener Rest von Threads abgearbeitet werden (wenn nämlich die Gesamtzahl der Klammerschemata nicht durch die gewählte &lt;tt&gt;numThreads&lt;/tt&gt; teilbar ist). Das erledige ich, indem ich am Schluss die Funktion noch einmal mit &lt;tt&gt;root = 0&lt;/tt&gt; aufrufe.&lt;br /&gt;&lt;br /&gt;Wie habe ich die Auswertung eines Ausdrucks implementiert? Das Klammerschema wird im Konstruktor der Klasse &lt;tt&gt;ExpressionTemplate&lt;/tt&gt; in eine "angereichertes" Datenstruktur überführt. Hier ist der Typ eines angereicherten Knotens:&lt;pre class="sh_cpp"&gt;typedef struct valuatedNode {&lt;br /&gt;  bool isLeaf;&lt;br /&gt;  int* op;&lt;br /&gt;  rational value;&lt;br /&gt;  struct valuatedNode* left;&lt;br /&gt;  struct valuatedNode* right;&lt;br /&gt;} valuatedNode;&lt;/pre&gt;&lt;br /&gt;Neben den bekannten Elementen &lt;tt&gt;isLeaf&lt;/tt&gt;, &lt;tt&gt;left&lt;/tt&gt; und &lt;tt&gt;right&lt;/tt&gt; gibt es einen Zeiger auf eine Ganzzahl in einem für die ganze Laufzeit der Klasse fixen Array &lt;tt&gt;ops = new int[size];&lt;/tt&gt; Die Plätze dieses Arrays nehmen nur die Zahlen 0 bis 3 an und werden als Offsets für die Ermittlung des richtigen Funktionszeigers aus der Konstanten &lt;tt&gt;OPERATION&lt;/tt&gt; verwendet.&lt;br /&gt;&lt;pre class="sh_cpp"&gt;// Operations with rational numbers  &lt;br /&gt;void plus(rational *first, rational second);&lt;br /&gt;void minus(rational *first, rational second);&lt;br /&gt;void times(rational *first, rational second);&lt;br /&gt;void by(rational *first, rational second);&lt;br /&gt;&lt;br /&gt;// Any operation on rationals&lt;br /&gt;typedef void(*operation)(rational*, rational);&lt;br /&gt;&lt;br /&gt;const operation OPERATION[4] = { plus, minus, times, by  };&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt; So braucht man für jede neue Auswahl von Operationen nur die Ganzzahlen im Array &lt;tt&gt;ops&lt;/tt&gt; zu ändern, und die Evaluierung greift dann automatisch auf die richtige Operation zu.&lt;br /&gt;&lt;br /&gt;Um die Kombinationen der Operationen der Reihe nach durchzugehen, verwende ich eine Ganzzahl, die ich einfach von 0 bis 65535 durchzähle. Ihre acht Bitpaare, die ich in den Array &lt;tt&gt;ops&lt;/tt&gt; schreibe, liefern dann alle möglichen Variationen der Operationen. Ein weiterer Trick ist, dass ich die Stelle &lt;tt&gt;value&lt;/tt&gt; des &lt;tt&gt;valuatedNode&lt;/tt&gt;-Typs, die ja streng nur für Blätter, nicht für Operationsknoten nötig ist, für Operationsknoten mit dem aktuellen Zwischenergebnis besetze. Da ich den Array der Knoten von hinten nach vorne durcharbeite, sind alle Referenzen, auf die ein Knoten verweist, bereits mit Werten belegt. So kann der Ausdruck ohne Rekursion in einer ganz gewöhnlichen Schleife abgearbeitet werden.[5] &lt;pre class="sh_cpp"&gt;void ExpressionTemplate::evaluateAllocations() {&lt;br /&gt;&lt;br /&gt;// Create all 4**size combinations of operations,&lt;br /&gt;// using a bit field of length 2*size&lt;br /&gt;// If size becomes &gt; 31, think of another implementation of this part&lt;br /&gt;  long long int counter = 1 &amp;lt&amp;lt 2*size;&lt;br /&gt;&lt;br /&gt;  while (counter) {&lt;br /&gt;    counter--;&lt;br /&gt;&lt;br /&gt;    long long int allocation = counter;&lt;br /&gt;    nodes[0].value.denom = 0;  // Makes result unequal to everything&lt;br /&gt;    int iOp = size-1;&lt;br /&gt;&lt;br /&gt;    for (valuatedNode* current =  nodes + 2*size;&lt;br /&gt;                       current &gt;= nodes;&lt;br /&gt;                       current-- ) {&lt;br /&gt;&lt;br /&gt;      if (!current-&gt;isLeaf) {&lt;br /&gt;&lt;br /&gt;// Pop next operation from bit field&lt;br /&gt;        ops[iOp--] = allocation &amp; 0b11;&lt;br /&gt;        allocation &gt;&gt;= 2;&lt;br /&gt;&lt;br /&gt;// Skip [ *, [ *, ...], &amp;lt&gt;[*,...] ] &amp; the like by associativity&lt;br /&gt;        if  (isDuplicateByAssociativity( current )) break;&lt;br /&gt;        &lt;br /&gt;// Compute next intermediate result&lt;br /&gt;        current-&gt;value = current-&gt;left-&gt;value;&lt;br /&gt;        OPERATION[*current-&gt;op](&amp;amp;current-&gt;value,current-&gt;right-&gt;value);&lt;br /&gt;&lt;br /&gt;// ignore div 0&lt;br /&gt;        if (current-&gt;value.denom == 0) break;&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;    if (equals(&amp;expectedResult,nodes[0].value)) {&lt;br /&gt;      int* result = new int[size];&lt;br /&gt;      for (int i=0;i&amp;ltsize;i++) result[i] = ops[i];&lt;br /&gt;      results.push_back( result );&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt; &lt;br /&gt;Die Laufzeiten des Programms ergaben noch eine Überraschung. Bei leichten Schwankungen von +/- 0.5 sec ergibt sich folgende Aufstellung [6]. &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;Anz. Threads    Laufzeit&lt;br /&gt;1             : 6.1 sec&lt;br /&gt;2             : 4.1 sec&lt;br /&gt;3             : 4.0 sec&lt;br /&gt;5             : 3.3 sec&lt;br /&gt;10            : 2.5 sec&lt;br /&gt;20            : 2.5 sec&lt;br /&gt;50            : 2.0 sec&lt;br /&gt;100           : 1.9 sec&lt;br /&gt;200           : 1.6 sec&lt;br /&gt;500           : 1.6 sec&lt;br /&gt;1000          : 1.5 sec&lt;br /&gt;1500          : 1.4 sec&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Naiv könnte man ja meinen, dass es nichts bringt, eine CPU, die acht Prozessorthreads hat, mit mehr als acht Threads zu behelligen. Die interne Pufferung von auszuführenden Threads ist aber auf modernen CPUs mittlerweile so weit optimiert, dass man offenbar am besten fährt, gleich alles was man hat, auf die CPU zu werfen und ihr selbst die Ausführung zu überlassen! Die Ausführungszeit sinkt kontinuierlich mit der Anzahl der gleichzeitig gestarteten Threads, ohne dass hier ein Minimum erkennbar wird! Wenn alle 1430 Threads gleichzeitig gestartet werden, ist man um den Faktor vier schneller als bei der serieller Abarbeitung.&lt;br /&gt;&lt;br /&gt;Ach ja, die Ergebnisse selbst sollen nicht verschwiegen werden! Das Programm gibt die folgenden 41 Lösungen aus. Viele Mehrfachnennungen aufgrund des Assoziativgesetzes werden durch die Implementierung bereits unterdrückt. Manche der Lösungen würde man trotzdem noch als gleichartig betrachten.&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;2011 = 1-(2&amp;middot;(3+(((4-5)-(6+7))&amp;middot;8&amp;middot;9)))&lt;br /&gt;2011 = 1-((2&amp;middot;3)+(4:((5-6):7)&amp;middot;8&amp;middot;9))&lt;br /&gt;2011 = 1-((2&amp;middot;3)+(4&amp;middot;(5-6)&amp;middot;7&amp;middot;8&amp;middot;9))&lt;br /&gt;2011 = 1+(2:(3:(((4+5)&amp;middot;6&amp;middot;7&amp;middot;8)-9)))&lt;br /&gt;2011 = 1-((2&amp;middot;3)+(4:(((5-6):7):(8&amp;middot;9))))&lt;br /&gt;2011 = 1-((2&amp;middot;3)+(4&amp;middot;(5-6)&amp;middot;7&amp;middot;8&amp;middot;9))&lt;br /&gt;2011 = ((1+(2&amp;middot;(3+(4&amp;middot;5))&amp;middot;6))&amp;middot;7)+(8&amp;middot;9)&lt;br /&gt;2011 = 1-(2&amp;middot;(3+((4-(5+6+7))&amp;middot;8&amp;middot;9)))&lt;br /&gt;2011 = (((1:2)-((3-(4+5))&amp;middot;6&amp;middot;7))&amp;middot;8)-9&lt;br /&gt;2011 = 1-(((2:3)+(4:(5-6)&amp;middot;7&amp;middot;8))&amp;middot;9)&lt;br /&gt;2011 = 1-(((2:3)+(4&amp;middot;(5-6)&amp;middot;7&amp;middot;8))&amp;middot;9)&lt;br /&gt;2011 = 1-(((2:3)+(4:((5-6):(7&amp;middot;8))))&amp;middot;9)&lt;br /&gt;2011 = 1-(((2:3)+(4&amp;middot;(5-6)&amp;middot;7&amp;middot;8))&amp;middot;9)&lt;br /&gt;2011 = 1+(2&amp;middot;3&amp;middot;((4&amp;middot;((5&amp;middot;6)+(7&amp;middot;8)))-9))&lt;br /&gt;2011 = 1-(2:(3:(4+5-(6&amp;middot;7&amp;middot;8&amp;middot;9))))&lt;br /&gt;2011 = (1&amp;middot;2)+((3+4)&amp;middot;((((5&amp;middot;6)+7)&amp;middot;8)-9))&lt;br /&gt;2011 = 1&amp;middot;(2+((3+4)&amp;middot;((((5&amp;middot;6)+7)&amp;middot;8)-9)))&lt;br /&gt;2011 = (1&amp;middot;2)+((3+4)&amp;middot;(5+(6&amp;middot;((7&amp;middot;8)-9))))&lt;br /&gt;2011 = 1&amp;middot;(2+((3+4)&amp;middot;(5+(6&amp;middot;((7&amp;middot;8)-9)))))&lt;br /&gt;2011 = 1+((2+((3-(4&amp;middot;((5:6)-7)))&amp;middot;8))&amp;middot;9)&lt;br /&gt;2011 = 1-((2&amp;middot;3)+(4:(5-6)&amp;middot;7&amp;middot;8&amp;middot;9))&lt;br /&gt;2011 = 1-((2&amp;middot;3)+(4&amp;middot;(5-6)&amp;middot;7&amp;middot;8&amp;middot;9))&lt;br /&gt;2011 = 1+(2:(3:(((4+5)&amp;middot;6&amp;middot;7&amp;middot;8)-9)))&lt;br /&gt;2011 = 1-((2&amp;middot;3)+(4:((5-6):(7&amp;middot;8&amp;middot;9))))&lt;br /&gt;2011 = 1-((2&amp;middot;3)+(4&amp;middot;(5-6)&amp;middot;7&amp;middot;8&amp;middot;9))&lt;br /&gt;2011 = ((1+2+3+4)&amp;middot;((5&amp;middot;6&amp;middot;7)-8))-9&lt;br /&gt;2011 = 1&amp;middot;((((2&amp;middot;3)+4)&amp;middot;((5&amp;middot;6&amp;middot;7)-8))-9)&lt;br /&gt;2011 = (1&amp;middot;((2&amp;middot;3)+4)&amp;middot;((5&amp;middot;6&amp;middot;7)-8))-9&lt;br /&gt;2011 = (((1&amp;middot;2&amp;middot;3)+4)&amp;middot;((5&amp;middot;6&amp;middot;7)-8))-9&lt;br /&gt;2011 = ((1+2+3+4)&amp;middot;((5&amp;middot;6&amp;middot;7)-8))-9&lt;br /&gt;2011 = (1&amp;middot;2)-((3+4)&amp;middot;(((5-(6&amp;middot;7))&amp;middot;8)+9))&lt;br /&gt;2011 = 1&amp;middot;(2-((3+4)&amp;middot;(((5-(6&amp;middot;7))&amp;middot;8)+9)))&lt;br /&gt;2011 = 1-(((2:3)-4)&amp;middot;(5+6+(7&amp;middot;8))&amp;middot;9)&lt;br /&gt;2011 = 1-(2&amp;middot;3&amp;middot;(((4-(5+(6&amp;middot;7)))&amp;middot;8)+9))&lt;br /&gt;2011 = 1+(2:3&amp;middot;(4-(5-(6&amp;middot;7&amp;middot;8)))&amp;middot;9)&lt;br /&gt;2011 = 1+(2:(3:((4-(5-(6&amp;middot;7&amp;middot;8)))&amp;middot;9)))&lt;br /&gt;2011 = (1:(2-3))+(4&amp;middot;(5-(6-(7&amp;middot;8&amp;middot;9))))&lt;br /&gt;2011 = (1&amp;middot;(2-3))+(4&amp;middot;(5-(6-(7&amp;middot;8&amp;middot;9))))&lt;br /&gt;2011 = (1&amp;middot;2)-(3-(4&amp;middot;(5-(6-(7&amp;middot;8&amp;middot;9)))))&lt;br /&gt;2011 = 1&amp;middot;(2-(3-(4&amp;middot;(5-(6-(7&amp;middot;8&amp;middot;9))))))&lt;br /&gt;2011 = 1-(2:(3:(4+5-(6&amp;middot;7&amp;middot;8&amp;middot;9))))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Fussnoten&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;[1] Mein Kollege Rolf Bertschinger fand die folgende Lösung: Von 2011 eins abgezogen, ergibt 2010, eine durch 6 = 2*3 teilbare Zahl. Man hat also eventuell gute Chancen, wenn man den Ausdruck mit &lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;1 + 2&amp;middot;3&amp;middot;...&lt;/pre&gt;&lt;br /&gt;beginnen lässt. 2010 ergibt, durch 6 dividiert, 335. Der gepunktete Teil des Ausdrucks sollte also irgendwie 335 ergeben. Nun muss man die Grösse der verbleibenden Möglichkeiten, Produkte zu bilden, ins Spiel bringen: Wir haben noch die Zahlen 4 bis 9 zur Verfügung. Wenn man Produkte von drei Zahlen bildet, ergibt 6&amp;middot;7&amp;middot;8 schon fast das Gewünschte, nämlich 336. Wenn wir die 9 einen Moment aussen vor lassen, hätten wir mit &lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;4 - 5 + 6&amp;middot;7&amp;middot;8 = 335&lt;/pre&gt;&lt;br /&gt;schon einen brauchbaren Ansatz. Leider stört dann aber die 9. Es scheint hoffnungslos, die 9 nun noch wegzubekommen, wenn doch alle anderen Zahlen schon verbraucht sind! Das Aha ist nun: Wenn man vorne, statt, mit 3 zu multiplizieren, durch 3 dividiert, hat man im Vergleich den nachfolgenden Ausdruck durch 9 dividiert. Das wird gerade korrigiert, wenn man am Schluss noch mit 9 malnimmt! So kommt man auf&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;1 + ( 2 : 3 ) &amp;middot; ( 4 - 5 + 6·7·8) &amp;middot; 9&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;[2] Weil ich bei dieser Gelegenheit die Groovy-Bibliothek &lt;a href="http://gpars.codehaus.org/"&gt;GPars&lt;/a&gt; kennenlernen wollte, machte ich eine erste Implementierung in Groovy. Das war für dieses Problem eine schlechte Wahl. Da Groovy eine hoch dynamische Sprache ist, die alles Nötige erst zur Laufzeit ermittelt, benötigte mein Programm, obwohl es parallelisiert auf 8 Prozessorthreads lief, &lt;b&gt;zehn Stunden&lt;/b&gt;, bis es alle Ergebnisse ermittelt hatte! Das ist keine Kritik, sondern nur eine Beobachtung! Groovy ist eine extrem ausdrucksstarke Sprache, die sich dem Ideal "Development by Config" schon sehr stark nähert. Applikationsdesign, bei dem man es mit einem User Interface zu tun hat, kann bei Einsatz von Groovy nur gewinnen &amp;ndash; wie etwa das Webframework &lt;a href="http://grails.org/"&gt;Grails&lt;/a&gt;, oder das auf Swing aufsetzende MVC-Framework &lt;a href="http://griffon.codehaus.org/"&gt;Griffon&lt;/a&gt;. Aber für rechenintensive Aufgaben wie diese ist Groovy eher nicht zu wählen.&lt;br /&gt;&lt;br /&gt;Wen es interessiert: Meine Groovy-Implementierung für diese Aufgabe findet man bei &lt;tt&gt;pastebin&lt;/tt&gt; unter &lt;a href="http://pastebin.com/GzcSW0d9"&gt;http://pastebin.com/GzcSW0d9&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;[3] Wenn die Ausführungszeiten der einzelnen Threads stark streuen, wäre hier ein anderes Verfahren angebracht. Man könnte eine synchronisierte Variable &lt;tt&gt;int threadsWorking&lt;/tt&gt; einführen, die vom Thread beim Start hoch- und bei Beendigung heruntergezählt wird. Der Master-Thread wartet dann darauf, dass &lt;tt&gt;threadsWorking == 0&lt;/tt&gt; wird.&lt;br /&gt;&lt;br /&gt;[4] Ich verwende aus technischen Gründen POSIX-Threads, obwohl ich eigentlich gerne einmal das neu in C++11 eingebaute Multithreading ausprobiert hätte. Leider ist es auf meinem Windows-System noch nicht verfügbar, da die &lt;tt&gt;MinGW&lt;/tt&gt; es noch nicht enthält, und ich hatte bislang noch keine Lust, mir eine zweite Festplatte mit Linux einzurichten. (Alles, was mit blosser Computeradministration zu tun hat, ist unproduktiver, lästiger Zeitfrass.)&lt;br /&gt;&lt;br /&gt;[5] Die Rekursion ist für Baumstrukturen im allgemeinen vorzuziehen, da sie lesbarer ist. An dieser Stelle aber, wo es auf höchstmögliche Effizienz ankommt, ist schon der Aufbau neuer Stackebenen pro Operation ein zu vermeidender Overhead.&lt;br /&gt;&lt;br /&gt;[6] Ausgeführt auf meinem Rechner, der einen Prozessor vom Typ &lt;i&gt;Intel Core i7 2600K @3.4 GHz&lt;/i&gt; hat und somit über acht Prozessorthreads verfügt.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-1625198670506549046?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/1625198670506549046/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=1625198670506549046' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/1625198670506549046'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/1625198670506549046'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/10/eine-kleine-anfrage-und-die-folgen.html' title='Eine kleine Anfrage - und die Folgen'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-5805782887398229920</id><published>2011-10-02T21:10:00.031+01:00</published><updated>2011-10-21T23:31:32.895+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Ein Algorithmus zum Aufzählen von Ausdrucksbäumen</title><content type='html'>Damit über das Ergebnis eines ohne Klammern hingeschriebenen Terms wie &lt;br /&gt;&lt;pre class='shCode'&gt;24 - 3 * 5 - 4&lt;br /&gt;&lt;/pre&gt;keine Diskussionen entstehen, gibt es bekanntlich einige Vorrangregeln: Die Punktrechnung wird der Strichrechnung vorgezogen, und der Ausdruck wird von links nach rechts ausgerechnet. Wenn man sich an diese Konventionen hält,ergibt obiger Ausdruck den Wert 5. Wenn man es anders wünscht, muss man Klammern setzen. Zum Beispiel liefert der folgende Ausdruck&lt;br /&gt;&lt;pre&gt;( 24 - 3 ) * ( 5 - 4 )&lt;br /&gt;&lt;/pre&gt;ein anderes Ergebnis, obwohl die Zahlen und Rechenoperationen immer noch in der gleichen Reihenfolge dastehen. Durch die hinzugefügten Klammern wurde die entscheidende Information hinzugefügt, dass die in den Klammern stehenden Ausdrücke zuerst gerechnet werden sollen. &lt;br /&gt;&lt;br /&gt;Eine gegebene Folge von Zahlen und Rechenoperationen vorausgesetzt: Wie viele unterschiedliche Ausführungsreihenfolgen gibt es denn insgesamt für die Ausrechnung? Das heisst: Auf wie viele verschiedene Weisen kann man &lt;i&gt;klammern&lt;/i&gt;?&lt;br /&gt;&lt;br /&gt;Um an ein solches Problem heranzugehen, muss man es zuerst auf geeignete Art modellieren. Unsere übliche Notation mit Klammern und Vorrangsregeln ist für eine theoretische Betrachtung eher ungeeignet. Es empfiehlt sich eine Stippvisite bei den Informatikern. Diese bevorzugen für Terme wie den obigen eine Baumdarstellung. Jede Rechenoperation wird durch einen Knoten mit zwei Kindknoten repräsentiert. Diese können selbst wieder Kinder haben - wenn sie wiederum eine Rechenoperation sind - oder nicht: wenn es Zahlen sind. Die Zahlen sind also die terminalen Knoten des Ausdrucksbaums, die auch Blätter genannt werden. Die Rechnung &lt;b&gt;1 + 1&lt;/b&gt; wird dann durch folgendes Diagramm repräsentiert:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-BMjAtgSuqcM/TonyTrtNELI/AAAAAAAAAME/ZnFpd-HIwTc/s1600/eins_plus_eins.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 179px; height: 155px;" src="http://3.bp.blogspot.com/-BMjAtgSuqcM/TonyTrtNELI/AAAAAAAAAME/ZnFpd-HIwTc/s400/eins_plus_eins.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5659320826731499698" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Der Ausdruck &lt;b&gt;&lt;tt&gt;24 - 3 * 5 - 4&lt;/tt&gt;&lt;/b&gt;, der mir hier als Aufhänger diente, sieht mit der Brille des Informatikers so aus:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-P7l58kjEs5I/TonzLHQYiLI/AAAAAAAAAMM/3XbCNmbmt_o/s1600/tree2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 215px; height: 347px;" src="http://4.bp.blogspot.com/-P7l58kjEs5I/TonzLHQYiLI/AAAAAAAAAMM/3XbCNmbmt_o/s400/tree2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5659321779019614386" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Der Ausdruck &lt;b&gt;&lt;tt&gt;(24 - 3) * (5 - 4)&lt;/tt&gt;&lt;/b&gt; behält die Reihenfolge der Zahlen und Operationen bei, klammert sie aber anders. Das ergibt den folgenden Baum:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-ePSOY1onpjk/Ton6-jQ6K3I/AAAAAAAAAMU/_Fv0MmUl8X4/s1600/tree3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 371px; height: 251px;" src="http://4.bp.blogspot.com/-ePSOY1onpjk/Ton6-jQ6K3I/AAAAAAAAAMU/_Fv0MmUl8X4/s400/tree3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5659330359292734322" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Bei Wahl dieses Modells lässt sich die Aufgabe, alle möglichen Klammerungen eines Ausdrucks aufzuzählen, leichter behandeln. Für einen Ausdruck mit &lt;i&gt;n&lt;/i&gt; Operationen und - daraus folgend - mit &lt;i&gt;n+1&lt;/i&gt; Zahlen haben wir also alle  "Ausdrucksbäume" mit insgesamt &lt;i&gt;2n+1&lt;/i&gt; Knoten zu bilden, wobei genau &lt;i&gt;n+1&lt;/i&gt; Knoten Blätter sind. Die &lt;i&gt;n&lt;/i&gt; inneren Knoten, die den Operationen entsprechen, also zwei Kindknoten haben, wollen wir Operationsknoten nennen.&lt;br /&gt;&lt;br /&gt;Bei der Suche nach einem rekursiven Algorithmus, um alle Ausdrucksbäume aufzuzählen, verrannte ich mich zuerst in dem Bestreben, den Algorithmus gewissermassen "von oben" beginnen zu lassen, beim Wurzelknoten. Ich habe den Verdacht, dass dies überhaupt nicht oder nur unter grossen Qualen möglich ist. &lt;br /&gt;&lt;br /&gt;Der Trick bei dieser Aufgabe ist, dass man besser "von unten" anfängt: Mit einer Operation wie &lt;b&gt;1+1&lt;/b&gt;, also einem Knoten, der zwei Blätter als Kinder enthält und damit eine Art "Ende" des Graphen darstellt. Wenn man den Algorithmus bei einem solchen Knoten beginnen lässt, wird alles ganz einfach: Von dort aus kann man die möglichen Ausdrücke wie folgt aufbauen: &lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Auf jeder Stufe des Algorithmus wollen wir einen neuen Operationsknoten mit Kindknoten besetzen. Als Kindknoten können wir uns aus einem Vorrat von "freien", d.h. schon auf einer früheren Stufe mit Kindknoten besetzten, aber noch nicht in den Ausdruck eingebauten Operationsknoten bedienen.&lt;br /&gt;&lt;li&gt;Dieser Vorrat, der auch leer sein kann, &lt;i&gt;darf auf jeder Stufe&lt;/i&gt; höchstens gleich der Anzahl der noch verfügbaren, d.h. noch gar nicht eingebauten Operationsknoten sein. Sonst liessen sich nicht mehr alle freien Operationsknoten in den Baum einbauen.&lt;br /&gt;&lt;li&gt;Um einen Operationsknoten mit Kindknoten zu besetzen, können wir &lt;i&gt;keinen, einen oder zwei freie Operationsknoten&lt;/i&gt; zu Kindern erklären. Die offene oder offenen Stellen des Operationsknotens besetzen wir mit Blättern. Bei der Alternative, einen Operationsknoten zu besetzen, sind auch noch die beiden Fälle zu unterscheiden, dass man das linke oder rechte Kind mit dem Operationsknoten belegt. &lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;  &lt;br /&gt;Der Algorithmus terminiert beim Wurzelknoten: Aufgrund der Restriktion an die Zahl der freien Operationsknoten liegen beim Wurzelknoten höchstens zwei freie Operationsknoten vor, die man auf jeden Fall in einer letzten Operation "verdrahten" kann.&lt;br /&gt;&lt;br /&gt;Zur Verdeutlichung will ich den Algorithmus am Fall &lt;i&gt;n=3&lt;/i&gt; einmal durchspielen. Das folgende Schema zeigt die möglichen Klamemerausdrücke am Beispiel eines Ausdrucks mit drei Operationen &lt;b&gt;O&lt;sub&gt;0&lt;/sub&gt;&lt;/b&gt; (der Wurzeloperation), &lt;b&gt;O&lt;sub&gt;1&lt;/sub&gt;&lt;/b&gt;, &lt;b&gt;O&lt;sub&gt;2&lt;/sub&gt;&lt;/b&gt; und vier Zahlen:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-_eMKj7fgexk/TooP14-H-WI/AAAAAAAAAMc/qhXv3XE3a4k/s1600/op3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 319px;" src="http://3.bp.blogspot.com/-_eMKj7fgexk/TooP14-H-WI/AAAAAAAAAMc/qhXv3XE3a4k/s400/op3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5659353300244887906" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Jedes Doppelkästchen steht für die beiden Kindknoten der Operation. Ein graues Kästchen steht für die Belegung mit einem Operationsknoten, ein weisses Kästchen für die Belegung mit einem Blatt. Jede Zeile steht für eine mögliche Klammerung. &lt;br /&gt;&lt;br /&gt;Man beginnt ganz rechts, also mit der Ausprägung von &lt;b&gt;O&lt;sub&gt;2&lt;/sub&gt;&lt;/b&gt;. Da es am Anfang noch keine freien Operationsknoten gibt, muss &lt;b&gt;O&lt;sub&gt;2&lt;/sub&gt;&lt;/b&gt; in jedem möglichen Fall mit zwei Blättern belegt werden. Nun ist ein freier Operationsknoten entstanden.&lt;br /&gt;&lt;br /&gt;Erst beim nächsten Operationsknoten &lt;b&gt;O&lt;sub&gt;1&lt;/sub&gt;&lt;/b&gt; ergeben sich nun Wahlmöglichkeiten: Entweder belegt man auch diesen Knoten mit zwei Blättern &amp;ndash; das ergibt die erste Möglichkeit &amp;ndash; oder man belegt einen der beiden Kindknoten von &lt;b&gt;O&lt;sub&gt;1&lt;/sub&gt;&lt;/b&gt; mit &lt;b&gt;O&lt;sub&gt;1&lt;/sub&gt;&lt;/b&gt;. Das ergibt jeweils zwei weitere Fälle.&lt;br /&gt;&lt;br /&gt;Schliesslich sind wir auf der dritten und letzten Ebene: Der Wurzelknoten &lt;b&gt;O&lt;sub&gt;0&lt;/sub&gt;&lt;/b&gt; ist zu belegen. In der ersten dargestellten Zeile haben wir nun zwei freie Operationsknoten. Diese müssen nun mit dem Wurzelknoten verdrahtet werden. Die Reihenfolge spielt, wenn zwei Operationsknoten verdrahtet werden, keine Rolle &amp;ndash; wie in diesem Fall. Wenn aber nur ein Operationsknoten zugeordnet und die andere Stelle mit einem Blatt besetzt wird, spielt die Reihenfolge durchaus eine Rolle. &lt;br /&gt;&lt;br /&gt;So ergeben sich insgesamt fünf Möglichkeiten, einen Ausdruck mit vier Zahlen und drei Operationen zu klammern. Aus dem Algorithmus ergibt sich, dass es für das Resultat auf die zahlenmässige Zuordnung (also die Subskripte 0,1,2 der Operationsknoten) nicht ankommt. Eine Möglichkeit lässt sich zahlenmässig durch eine "Signatur" beschreiben: Ein Tupel &lt;i&gt;(a&lt;sub&gt;n-1&lt;/sub&gt;, a&lt;sub&gt;n-2&lt;/sub&gt;, &amp;hellip;, a&lt;sub&gt;0&lt;/sub&gt;)&lt;/i&gt; von Zahlen zwischen 0 und 2, wobei in den Fällen a&lt;sub&gt;i&lt;/sub&gt;=1 noch zu notieren ist, ob das linke (L) oder rechte (R) Kindelement für den Operationsknoten verwendet wurde. Die im Beispiel resultierenden Signaturen habe ich oben angefügt.&lt;br /&gt;&lt;br /&gt;Diese Zuordnung ergibt einen Zusammenhang zwischen der Anzahl binärer Ausdrucksbäume und den sogenannten &lt;a href="http://en.wikipedia.org/wiki/Combination"&gt;Kombinationen&lt;/a&gt;, also verschiedenen möglichen Auswahlen einer Teilmenge aus einer grösseren Menge &amp;mdash; hier den Auswahlen von &lt;i&gt;n-1&lt;/i&gt; aus &lt;i&gt;2n&lt;/i&gt; Elementen. Von hier gelangt man zu den wohlbekannten &lt;a href="http://de.wikipedia.org/wiki/Catalan-Zahl"&gt;Catalanschen Zahlen&lt;/a&gt;. Da die Diskussion die Grösse eines Blogs sprengt, habe ich  diesen Zusammenhang in einem kleinen Artikel erläutert:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ruediger-plantiko.net/etree.pdf"&gt;http://ruediger-plantiko.net/etree.pdf&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Ein vollständiges C-Programm, das die Ausdrucksbäume gemäss diesem Algorithmus aufzählt, habe ich auf Pastebin unter der URL &lt;a href="http://pastebin.com/mehTM4Z0"&gt;http://pastebin.com/mehTM4Z0&lt;/a&gt; verfügbar gemacht. &lt;br /&gt;&lt;br /&gt;Das Programm erzeugt z.B. für n=4 die folgende Aufzählung der 14 möglichen Klammerungen eines Ausdrucks mit vier Operationen:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;[op,[op,[op,num,num],num],[op,num,num]]&lt;br /&gt;[op,[op,num,[op,num,num]],[op,num,num]]&lt;br /&gt;[op,[op,[op,num,num],[op,num,num]],num]&lt;br /&gt;[op,num,[op,[op,num,num],[op,num,num]]]&lt;br /&gt;[op,[op,num,num],[op,[op,num,num],num]]&lt;br /&gt;[op,[op,[op,[op,num,num],num],num],num]&lt;br /&gt;[op,num,[op,[op,[op,num,num],num],num]]&lt;br /&gt;[op,[op,num,[op,[op,num,num],num]],num]&lt;br /&gt;[op,num,[op,num,[op,[op,num,num],num]]]&lt;br /&gt;[op,[op,num,num],[op,num,[op,num,num]]]&lt;br /&gt;[op,[op,[op,num,[op,num,num]],num],num]&lt;br /&gt;[op,num,[op,[op,num,[op,num,num]],num]]&lt;br /&gt;[op,[op,num,[op,num,[op,num,num]]],num]&lt;br /&gt;[op,num,[op,num,[op,num,[op,num,num]]]]&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-5805782887398229920?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/5805782887398229920/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=5805782887398229920' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/5805782887398229920'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/5805782887398229920'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/10/ein-algorithmus-zum-aufzahlen-von.html' title='Ein Algorithmus zum Aufzählen von Ausdrucksbäumen'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-BMjAtgSuqcM/TonyTrtNELI/AAAAAAAAAME/ZnFpd-HIwTc/s72-c/eins_plus_eins.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-4973532287018887329</id><published>2011-09-18T20:17:00.018+01:00</published><updated>2011-12-04T20:43:09.774+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Daten und Code</title><content type='html'>Der Mathematiker &lt;a href="http://de.wikipedia.org/wiki/Bill_Gosper"&gt;Bill Gosper&lt;/a&gt; hat einmal gesagt: &lt;i&gt;"Data is just a dumb kind of programming."&lt;/i&gt;[1] Das widerspricht unseren Denkgewohnheiten: Wir finden es gut und richtig, Code und Daten sorgfältig voneinander zu trennen. Es gilt als unsauber, in den Code Daten aufzunehmen und als unmöglich, Daten direkt auszuführen: Sie können allenfalls von einem Programm eingelesen werden und die Programmausführung steuern - etwa in Form von Customizing.&lt;br /&gt;&lt;br /&gt;Aber das Zitat von Gosper weist zuerst einmal darauf hin, dass Code und Daten näher beieinanderliegen, vergleichbarer sind als es den Anschein hat. Schliesslich besteht auch Programmcode aus Daten: Er ist eine Aneinanderreihung von Schlüsselwörtern, Symbolen, Literalen und besonderen Zeichen aus dem Vorrat einer Programmiersprache. Wie alle Daten ist Programmcode "nur" Information. Ein Compiler erzeugt aus Code ein ausführbares Programm: Das ist eine Abfolge von Bytes, die zur Laufzeit dem Prozessor (oder der virtuellen Maschine) zur Ausführung vorgelegt werden. Die Bits und Bytes der Op-Codes werden dann im Prozessor nach fest verdrahteten Regeln interpretiert und ausgeführt. Auch Codezeilen sind demnach Daten. Es gibt keinen grundsätzlichen Unterschied zwischen Customizingdaten, die von einem Programm eingelesen werden und seinen Ablauf beeinflussen, und Programmcode, der von einem Prozessor eingelesen und verarbeitet wird. &lt;br /&gt;&lt;br /&gt;Da alle Änderungen in unserem System über das Transportwesen vom Entwicklungs- über das Konsolidierungs in das Produktivsystem gelangen und all diese Transporte in einer Liste dokumentiert werden, habe ich mir eine Liste der letzten 100 Änderungen in unserem System auf die Frage hin angesehen, welcher Art die durchgeführten Änderungen waren [2] &amp;ndash; mit folgendem Ergebnis: &lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;i&gt;Etwa die Hälfte&lt;/i&gt; aller Änderungen wurden am Code vorgenommen.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;Etwa ein Drittel der Änderungen&lt;/i&gt; betraf &lt;i&gt;steuernde Daten&lt;/i&gt;: Customizing, Anwendungsparameter und transportrelevante Stammdaten.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Die übrigen Einträge der Liste führten teilweise gar nicht zu Transporten, weil sie reine Beratungsfragen darstellten oder ein nicht reproduzierbares Symptom beschrieben. Ferner gab es Transporte von &lt;i&gt;Texten&lt;/i&gt; und &lt;i&gt;Übersetzungen&lt;/i&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Interessanter als "Ist es eine echte Fehlermeldung oder die Anforderung eines neuen Features?" finde ich jedenfalls die Frage, auf welcher Ebene und mit welchem Aufwand geändert wurde: Gibt es Änderungsarten, die günstiger sind als andere? &lt;br /&gt;&lt;br /&gt;Jede Änderung bedeutet, dass der Wert einer bestimmten steuernden Grösse geändert wird - seien es Teile von Programmcode oder Einträge im Customizing. Die Frage nach dem Aufwand ist die Frage, wie gut man an diese Steuergrösse herankommt, wie gut der Kontext der Steuergrösse im System die Semantik dieser Grösse darstellt, wie kontrollierbar die Auswirkungen der Änderung sind und zuletzt: ob ein Entwickler nötig ist, um die Änderung durchzuführen.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;&lt;u&gt;Lokalisierung der Steuergrösse&lt;/u&gt; Wie viel Aufwand benötige ich, um die Schraube zu finden, an der ich drehen muss?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;u&gt;Isoliertheit&lt;/u&gt; Wie seiteneffektfrei ist die nötige Änderung?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;u&gt;Darstellung&lt;/u&gt; Wie steuert die änderbare Grösse das System? Wie ist diese änderbare Grösse modelliert? Wie leicht ist diese Steuerung für den Leser des Modells (des Codes, der Customizingtabelle etc.) nachvollziehbar?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;u&gt;Zuständigkeit&lt;/u&gt; Muss die Änderung durch einen Entwickler oder kann sie  durch den Prozessberater, der die Änderung anfordert, selbst geleistet werden? Im letzteren Fall würde man sich eine Instanz sparen, was den Aufwand für die Durchführung der Änderung natürlich reduziert.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;In komplexen Systemen, die einen Querschnittsaspekt der Betriebswirtschaft eines grossen Unternehmens abbilden, etwa dessen Logistik, kann eine steuernde Codestelle praktisch nicht mehr per Dokumentation ermittelt werden. Die Dokumentation kann bestenfalls noch einen groben Anhalt geben, in welcher Richtung (in welchen Programmen, Klassen, Funktionsgruppen) man suchen muss. Es wird für die Änderung ein konkreter Anwendungsfall benötigt, der die fehlgeschlagene Erwartung zeigt. Da in der Regel keine strikte, sondern nur die schwache Form von &lt;a href="http://martinfowler.com/bliki/CodeOwnership.html"&gt;Code Ownership&lt;/a&gt; praktiziert wird, muss man als Nicht-Experte des jeweiligen Gebietes mit dem Debugger herausfinden, wie das System aktuell funktioniert und an welcher Stelle man es verändern müsste, um den Ablauf zu ändern. Ohne tiefere Kenntnis des Programmentwurfs, ohne Kommunikation mit den Programmautoren, ist das eine heikle Aufgabe, an der man grandios scheitern kann. &lt;br /&gt;&lt;br /&gt;Der Code in einer 3GL-Programmiersprache ist meist &lt;i&gt;ausführungsorientiert&lt;/i&gt;, nicht &lt;i&gt;problemorientiert&lt;/i&gt;. Man muss sich beim Programmieren gewissermassen in das Ausführungsmodell der Maschine eindenken, und meist bleibt es dabei: Der Code entsteht in einer Anpassung an die Ausführungslogik der Maschine. Die Sichtweise von Jack Reeves "&lt;a href="http://www.developerdotstar.com/mag/articles/reeves_design_main.html"&gt;Der Code ist Design"&lt;/a&gt;[3], wonach der Programmcode vor allem als ein Text für menschliche Leser gedacht ist - und nur in einer speziellen Syntax formuliert ist, die die maschinelle Ausführung ermöglicht - dass &lt;i&gt;Programmcode also ein maschinell ausführbares Designdokument ist&lt;/i&gt;, ist in den Köpfen noch nicht sehr verbreitet. Man klebt zu sehr an den Details der Maschine, hört zu schnell mit Code-Verbesserungen auf, sobald "es läuft" oder gibt zu schnell auf, wenn die Maschine nicht so reagiert wie erwartet, nur um dann an anderen Orten eine brutale Alternativlösung einzubauen. &lt;br /&gt;&lt;br /&gt;Das leitet zum zweiten Thema über, der &lt;i&gt;Isoliertheit einer Änderung&lt;/i&gt;. Je mehr ein System bereits durch Änderungen verschraubt ist, umso schwieriger ist es, Seiteneffekte zu überblicken. Das gilt vor allem, wenn der Code Wiederholungen enthält. Mit statischer Codeanalyse ist es meist zu aufwendig, alle Prozesse zu finden, auf die sich eine Änderung auswirkt. Die einzige Möglichkeit, die Seiteneffekte von Änderungen mit guter Qualität zu kontrollieren, wäre ein dichtes Sicherheitsnetz von automatischen Tests &amp;ndash; Unit Tests ebenso wie Integrationstests. Je weniger automatische Tests existieren, umso grösser ist das mit einer Änderung verbundene Risiko, umso grösser wird bei jeder Änderung die Angst, unerwünschte Nebeneffekte zu produzieren. Das bremst auch den allfällig vorhandenen guten Willen zur Verbesserung von Codequalität!&lt;br /&gt;&lt;br /&gt;Wie kann man die &lt;i&gt;Darstellung&lt;/i&gt; von Code verbessern? DSLs sind ein Ansatz, um von ausführungsorientierter zu problemorientierter Programmierung überzugehen. Ich habe im Blog &lt;a href="http://ruediger-plantiko.blogspot.com/2011/01/domanenspezifische-programmierung-in.html"&gt;Domänenspezifische Programmierung in ABAP&lt;/a&gt; einige Beispiele gegeben, wie man Ausführungsdetails im Programmcode verbergen kann. Die DSL ist eine Ebene über der 3GL-Programmiersprache angesiedelt und steuert mit einer klareren, aber auch spezialisierteren Syntax bestimmte Teilaspekte des Systems. Das alles ist aber noch Zukunftsmusik, auch wenn sich im Bereich der Tools und Frameworks für DSLs einiges tut[4], und wir in ABAP das &lt;a href="http://www.sdn.sap.com/irj/sdn/index?rid=/webcontent/uuid/90754865-f283-2b10-6d9f-b10f3c28c3a0"&gt;Business Rules Framework BRFplus&lt;/a&gt; als neues Werkzeug in unserem Instrumentarium erhalten haben.   &lt;br /&gt;&lt;br /&gt;Wer ist schliesslich für eine Änderung &lt;i&gt;zuständig&lt;/i&gt;? Änderungen am Code müssen natürlich immer von einem Entwickler gemacht werden - bei schwacher Code Ownership von irgendjemandem aus dem Team. Ein SAP-Prozessberater kann zwar den Code anschauen, aber da er in seiner täglichen Arbeit an Prozessen und nicht am Ausführungsmodell einer Maschine orientiert ist, ist selbst das Lesen von Programmcode für ihn eine mühsame Übung. Anders sähe es aus, wenn der Code selbst prozessorientiert wäre, das steuernde Modell des Programms also aus dem für die blosse Programmausführung nötigen Code herausextrahiert wäre. Womit wir wieder beim DSL-Thema sind. Der Berater könnte dann, so wie er aktuell das Customizing seine Domäne gut kennt, auch ein in einer DSL formuliertes Programm gut kennen und Änderungen entweder vorschlagen oder sogar selbst durchführen. &lt;br /&gt;&lt;br /&gt;In allen vier Punkten gewinnen die Daten &amp;ndash; der "dumme Code" nach Gosper &amp;ndash; gegenüber dem schlauen Code, dem 3GL-Programmcode, was die schnelle Änderbarkeit betrifft. Die für eine Änderung des Systemverhaltens nötige Customizingeinstellung ist in der Regel schneller lokalisiert als eine Codestelle. Sie ist auch leichter zu ändern und ist im Kontext &amp;ndash; Viewpflege mit Anwendungsdoku und F4- und F1-Hilfe für das steuernde Feld &amp;ndash;  klar nachvollziehbar. Wenn nicht der Umweg über einen Entwickler gewählt werden muss, sondern der Anforderer der Änderung diese auch gleich selbst umsetzen kann, spart man weitere Zeit. Lediglich bei der &lt;i&gt;Isoliertheit&lt;/i&gt; kann man bei einer Datenänderung ebenso in die Komplexitätsfalle laufen wie bei Codeänderungen. Wenn ich eine neue Auftragsart einführe, kann ich nicht wissen, an welchen Stellen auf die bestehenden Auftragsarten programmiert ist und die neue Auftragsart mit aufgenommen werden muss.[5] Egal ob Code oder Daten - zur zuverlässigen Kontrolle von Seiteneffekten hilft letztlich nur das Netz automatischer Tests.&lt;br /&gt;&lt;br /&gt;Wenn sich das Verhältnis in der Changeliste umkehren würde: Wenn die Hälfte der Änderungen als Datenänderungen und nur ein Drittel als Codeänderungen gemacht werden müssten, hätte das bereits eine deutliche Verringerung der Aufwände zur Folge und würde das Unternehmen somit agiler machen: Es kann schneller auf Änderungen reagieren. Wir arbeiten an diesem Ziel, wenn wir, wo immer es möglich ist, die Steuerung des Programms &amp;ndash; in der betriebswirtschaftlichen Begrifflichkeit, nicht in Begriffen der Maschine &amp;ndash; aus dem Programmcode extrahieren und in geeignete Daten auslagern. Das können klassische Customizingtabellen sein, oder auch moderne Datenstrukturen wie die des Business Rule Frameworks, oder eine Instanz einer geeignet definierten DSL.   &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Zitiert in dem Aufsatz von Charles Petzold, &lt;i&gt;On-the-Fly Code Generation for Image Processing&lt;/i&gt;, in: Andy Oram, Greg Wilson (Eds.), &lt;i&gt;Beautiful Code&lt;/i&gt;, O'Reilly, Cambridge 2007, S. 105.&lt;br /&gt;[2] Über die Frage, ob es sich bei den Änderungen um "echte Fehler" oder um Entwicklungswünsche, Nice-To-Haves und dergleichen handelt, kann man sich in Meetings sehr schön erhitzen. Aber für die Reflexion sind diese Klassifizierungen uninteressant. Ich versuche, die vorbelasteten Ausdrücke "Bug", "Fehler", "Korrektur" zu vermeiden und spreche schlicht von "Änderungen".&lt;br /&gt;[3] Jack W. Reeves, &lt;i&gt;What is Software Design?&lt;/i&gt;, &lt;a href="http://www.developerdotstar.com/mag/articles/reeves_design_main.html"&gt;http://www.developerdotstar.com/mag/articles/reeves_design_main.html&lt;/a&gt;&lt;br /&gt;[4] Siehe &lt;a href="http://www.languageworkbenches.net/"&gt;http://www.languageworkbenches.net/&lt;/a&gt; &lt;br /&gt;[5] Die Ursache dafür ist also wieder der nicht überblickbare Code. Zwar kann man im Beispiel der Auftragsarten eine globale Konstante mit allen verwendeten Werten einführen. Man hat aber keine Garantie, dass sich jeder daran hält oder ob nicht ein zweiter Entwickler in Unkenntnis der Existenz der ersten einfach eine weitere, zweite Konstante mit derselben oder einer leicht abweichenden Wertemenge definiert hat. Und das ist kein theoretisches Beispiel!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-4973532287018887329?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/4973532287018887329/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=4973532287018887329' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/4973532287018887329'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/4973532287018887329'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/09/daten-und-code.html' title='Daten und Code'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-4932736625818604565</id><published>2011-09-12T20:56:00.028+01:00</published><updated>2011-09-14T06:44:47.460+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Ein JSON-Builder in ABAP</title><content type='html'>Zu dem in einem früheren Post beschriebenen &lt;a href="http://ruediger-plantiko.blogspot.com/2010/12/ein-json-parser-in-abap.html"&gt;JSON-Parser&lt;/a&gt; gibt es ein Gegenstück &amp;ndash; den &lt;i&gt;JSON-Builder&lt;/i&gt;. Er kann gegebene ABAP-Daten in das JSON-Datenformat wandeln. Die ABAP-Daten sind dabei &amp;ndash; wie der Zieltyp des JSON-Parsers &amp;ndash; vom Typ &lt;tt&gt;ZUT_DATA&lt;/tt&gt; und ermöglichen beliebig tiefe Schachtelung. Hier der Typ von &lt;tt&gt;ZUT_DATA&lt;/tt&gt;, wenn man ihn nicht, wie es richtig ist, im DDIC, sondern als lokalen Datentyp definieren würde:&lt;br /&gt;&lt;pre class="sh_abap"&gt;types:&lt;br/&gt;  begin of zut_data,&lt;br/&gt;    type type c,&lt;br/&gt;    value type ref to data,&lt;br/&gt;  end of zut_data.&lt;/pre&gt;&lt;br /&gt;Der Typschlüssel &lt;tt&gt;type&lt;/tt&gt; kann dabei die Wert &lt;tt&gt;S&lt;/tt&gt; für String, &lt;tt&gt;N&lt;/tt&gt; für Zahl, &lt;tt&gt;B&lt;/tt&gt; für Boolesch, &lt;tt&gt;a&lt;/tt&gt; für Array und &lt;tt&gt;h&lt;/tt&gt; für Hash annehmen.&lt;br /&gt;&lt;br /&gt;Es gäbe verschiedene Strategien für die Implementierung eines solchen Builders. Man könnte alles in ABAP ausprogrammieren oder sich eine andere der in ABAP verfügbaren Sprachen zunutze machen &amp;ndash; in diesem Fall bietet sich der JavaScript-Interpreter &lt;tt&gt;cl_java_script&lt;/tt&gt; oder der XSLT-Prozessor an (in ABAP mit der Anweisung &lt;tt&gt;call transformation&lt;/tt&gt; integriert). &lt;br /&gt;&lt;br /&gt;Ich habe mich in diesem Fall gefühlsmässig für XSLT entschieden: Die erforderliche Rekursion durch tiefe Datenstrukturen wird durch XSLT gut unterstützt, auch gilt der in ABAP eingebaute XSLT-Prozessor als schnell. Über den JavaScript-Prozessor kenne ich keine Performance-Angaben, jedoch habe ich Forenbeiträge gelesen, in denen der JavaScript-Prozessor als sehr ressourcenintensiv beschrieben wurde. Eine &lt;i&gt;reine&lt;/i&gt; ABAP-Implementierung schliesslich wäre ebenso gut möglich gewesen. Ob die Stringverarbeitung von ABAP für grosse Datenobjekte mit der XSLT-Verarbeitung konkurrieren kann, wäre eine interessante Frage. Hier will ich aber den Weg über eine XSLT-Transformation &lt;tt&gt;zabap2json&lt;/tt&gt; beschreiben.&lt;br /&gt;&lt;br /&gt;Bei Verwendung einer Transformation ist die zentrale &lt;tt&gt;build&lt;/tt&gt;-Methode, die einen &lt;tt&gt;zut_data&lt;/tt&gt;-Parameter entgegennimmt und einen String im JSON-Format zurückgibt, eine reine Delegation:&lt;br /&gt;&lt;pre class="sh_abap"&gt;method build.&lt;br/&gt;  call transformation zabap2json&lt;br/&gt;    options data_refs = 'heap-or-create'&lt;br/&gt;    source data_root = is_data&lt;br/&gt;    result json = ev_json.&lt;br/&gt;  replace regex '^\s+' in ev_json with space.&lt;br/&gt;  replace regex '\s+$' in ev_json with space.&lt;br/&gt;endmethod.&lt;/pre&gt;&lt;br /&gt;Wichtig ist hier allerdings der Zusatz &lt;tt&gt;data_refs = 'heap-or-create'&lt;/tt&gt;. Hiermit weisen wir den Prozessor an, alle &lt;i&gt;nicht selbständigen&lt;/i&gt;, also auf den Stack oder auf globalen Daten verweisende Referenzen so zu behandeln wie &lt;i&gt;selbständige&lt;/i&gt;, also mit &lt;tt&gt;create data&lt;/tt&gt; auf dem Heap angelegte Datenobjekte. Würden wir diese Option nicht angeben, so würden die nicht selbständigen Datenreferenzen nicht ins ABAPXML-Format serialisiert und könnten daher nicht von der XSLT-Transformation bearbeitet werden.&lt;br /&gt;&lt;br /&gt;In diesem Fall haben wir einen Aufruf von &lt;tt&gt;call transformation&lt;/tt&gt;, der ABAP-Daten entgegennimmt und auch wieder an den Aufrufer zurückgibt. XML tritt nur als Zwischenformat auf - als Quell- und Zielformat der Transformation.&lt;br /&gt;&lt;br /&gt;Wie sieht nun die Transformation &lt;tt&gt;zabap2json&lt;/tt&gt; aus? Die ersten Templates steigen zunächst bis zum Basistemplate &lt;tt&gt;showData&lt;/tt&gt; der Rekursion ab und spezifizieren, dass der Ergebnisstring in ein &lt;tt&gt;&amp;lt;JSON&gt;&lt;/tt&gt;-Element eingepackt werden soll &amp;ndash; unter diesem Namen kann das Ergebnis dann in ABAP als String abgeholt werden:&lt;br /&gt;&lt;pre class="sh_xml"&gt;  &amp;lt;xsl:template match="/"&gt;&lt;br/&gt;    &amp;lt;xsl:apply-templates select="/asx:abap/asx:values"/&gt;&lt;br/&gt;  &amp;lt;/xsl:template&gt;&lt;br/&gt;&lt;br/&gt;  &amp;lt;xsl:template match="DATA_ROOT"&gt;&lt;br/&gt;    &amp;lt;xsl:call-template name="getData"/&gt;&lt;br/&gt;  &amp;lt;/xsl:template&gt;&lt;br/&gt;&lt;br/&gt;  &amp;lt;xsl:template name="getData"&gt;&lt;br/&gt;    &amp;lt;asx:abap&gt;&lt;br/&gt;      &amp;lt;asx:values&gt;&lt;br/&gt;        &amp;lt;JSON&gt;&lt;br/&gt;          &amp;lt;xsl:call-template name="showData"&gt;&lt;br/&gt;            &amp;lt;xsl:with-param name="data" select="."/&gt;&lt;br/&gt;          &amp;lt;/xsl:call-template&gt;&lt;br/&gt;        &amp;lt;/JSON&gt;&lt;br/&gt;      &amp;lt;/asx:values&gt;&lt;br/&gt;    &amp;lt;/asx:abap&gt;&lt;br/&gt;  &amp;lt;/xsl:template&gt;&lt;/pre&gt;&lt;br /&gt;Das zentrale Template &lt;tt&gt;showData&lt;/tt&gt; muss nun den Typschlüssel auswerten und an ein dazu passendes Template delegieren. Da es in XSLT keine Möglichkeit gibt, Templates dynamisch aufzurufen, muss der passende Templateaufruf in einem &lt;tt&gt;&amp;lt;xsl:choose&gt;&lt;/tt&gt;-Block programmiert werden:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_xml"&gt;  &amp;lt;xsl:template name="showData"&gt;&lt;br/&gt;    &amp;lt;xsl:param name="data"/&gt;&lt;br/&gt;    &amp;lt;xsl:variable name="type" select="$data/TYPE"/&gt;&lt;br/&gt;    &amp;lt;xsl:for-each select="$data/DATA"&gt;&lt;br/&gt;      &amp;lt;xsl:choose&gt;&lt;br/&gt;        &amp;lt;xsl:when test="$type = 'N'"&gt;&lt;br/&gt;          &amp;lt;xsl:call-template name="simpleValue"/&gt;&lt;br/&gt;        &amp;lt;/xsl:when&gt;&lt;br/&gt;        &amp;lt;xsl:when test="$type = 'S'"&gt;&lt;br/&gt;          &amp;lt;xsl:call-template name="stringValue"/&gt;&lt;br/&gt;        &amp;lt;/xsl:when&gt;&lt;br/&gt;        &amp;lt;xsl:when test="$type = 'B'"&gt;&lt;br/&gt;          &amp;lt;xsl:call-template name="booleanValue"/&gt;&lt;br/&gt;        &amp;lt;/xsl:when&gt;&lt;br/&gt;        &amp;lt;xsl:when test="$type = 'h'"&gt;&lt;br/&gt;          &amp;lt;xsl:call-template name="showHash"/&gt;&lt;br/&gt;        &amp;lt;/xsl:when&gt;&lt;br/&gt;        &amp;lt;xsl:when test="$type = 'a'"&gt;&lt;br/&gt;          &amp;lt;xsl:call-template name="showArray"/&gt;&lt;br/&gt;        &amp;lt;/xsl:when&gt;&lt;br/&gt;      &amp;lt;/xsl:choose&gt;&lt;br/&gt;    &amp;lt;/xsl:for-each&gt;&lt;br/&gt;  &amp;lt;/xsl:template&gt;&lt;/pre&gt;&lt;br /&gt;Das &lt;tt&gt;&amp;lt;for-each&gt;&lt;/tt&gt; wird natürlich nur einmal durchlaufen und setzt dabei den &lt;i&gt;current node&lt;/i&gt; für die Templates.&lt;br /&gt;&lt;br /&gt;Die Regel für den Hash zeigt, dass das Template &lt;tt&gt;showData&lt;/tt&gt; bei der Auflösung tiefer Datenstrukturen parametrisiert aufgerufen wird. Auch sieht man am &lt;tt&gt;select&lt;/tt&gt;-Attribut der &lt;tt&gt;&amp;lt;for-each&gt;&lt;/tt&gt;-Schleife über die Hash-Elemente, wie der Zugriff auf eine Datenreferenz im Heap zu bewerkstelligen ist: Die ID im Heap entspricht dem Wert des Attributs &lt;tt&gt;href&lt;/tt&gt; in der Referenz ab dem 2. Zeichen (XLink- bzw. XPointer-konform wird einem &lt;tt&gt;href&lt;/tt&gt;-Verweis auf ein Element mit einer ID im aktuellen Dokument ein &lt;tt&gt;#&lt;/tt&gt; vorangestellt): &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_xml"&gt;  &amp;lt;xsl:template name="showHash" match="ZUT_DATA" mode="h"&gt;&lt;br/&gt;    &amp;lt;xsl:variable name="id" select="substring(@href,2)"/&gt;&lt;br/&gt;    {&lt;br/&gt;    &amp;lt;xsl:for-each select="/asx:abap/asx:heap/*[@id = $id]/*"&gt;&lt;br/&gt;      &amp;lt;xsl:if test="position() &gt; 1"&gt;,&amp;lt;/xsl:if&gt;&lt;br/&gt;      &amp;lt;xsl:call-template name="showHashRow"/&gt;&lt;br/&gt;    &amp;lt;/xsl:for-each&gt;&lt;br/&gt;    }&lt;br/&gt;  &amp;lt;/xsl:template&gt;&lt;br/&gt;&lt;br/&gt;  &amp;lt;xsl:template name="showHashRow"&gt;&lt;br/&gt;    "&amp;lt;xsl:value-of select="KEY"/&gt;":&lt;br/&gt;    &amp;lt;xsl:call-template name="showData"&gt;&lt;br/&gt;      &amp;lt;xsl:with-param name="data" select="."/&gt;&lt;br/&gt;    &amp;lt;/xsl:call-template&gt;&lt;br/&gt;  &amp;lt;/xsl:template&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Das Template für einen &lt;tt&gt;simpleValue&lt;/tt&gt; ist so gestaltet, dass es mit Direktwerten ebenso wie mit Referenzen umgehen kann:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_xml"&gt;  &amp;lt;xsl:template name="simpleValue"&gt;&lt;br/&gt;    &amp;lt;xsl:choose&gt;&lt;br/&gt;      &amp;lt;xsl:when test="@href"&gt;&lt;br/&gt;        &amp;lt;xsl:call-template name="getReference"&gt;&lt;br/&gt;          &amp;lt;xsl:with-param name="href" select="./@href"/&gt;&lt;br/&gt;        &amp;lt;/xsl:call-template&gt;&lt;br/&gt;      &amp;lt;/xsl:when&gt;&lt;br/&gt;      &amp;lt;xsl:otherwise&gt;&lt;br/&gt;        &amp;lt;xsl:value-of select="."/&gt;&lt;br/&gt;      &amp;lt;/xsl:otherwise&gt;&lt;br/&gt;    &amp;lt;/xsl:choose&gt;&lt;br/&gt;  &amp;lt;/xsl:template&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Umständlich wird leider nur das Template zur Darstellung eines Strings. Es müssen ja alle &lt;tt&gt;"&lt;/tt&gt; im String druch &lt;tt&gt;\"&lt;/tt&gt; und alle einfachen Backslashs &lt;tt&gt;\&lt;/tt&gt; durch doppelte &lt;tt&gt;\\&lt;/tt&gt; maskiert werden. Da es keine XPath-Funktion zum Ersetzen von Strings gibt, ist diese Funktion separat dazuzuprogrammieren. Gott sei Dank gibt es das Internet, in dem sich Funktionen wie &lt;tt&gt;XSLT string-replace&lt;/tt&gt; auffinden lassen - danke Erik!&lt;br /&gt;&lt;pre class="sh_xml"&gt;  &amp;lt;xsl:template name="stringValue"&gt;&lt;br/&gt;    "&amp;lt;xsl:call-template name="string-replace-all"&gt;&lt;br/&gt;      &amp;lt;xsl:with-param name="text"&gt;&lt;br/&gt;        &amp;lt;xsl:call-template name="string-replace-all"&gt;&lt;br/&gt;          &amp;lt;xsl:with-param name="text"&gt;&lt;br/&gt;            &amp;lt;xsl:call-template name="simpleValue"/&gt;&lt;br/&gt;          &amp;lt;/xsl:with-param&gt;&lt;br/&gt;          &amp;lt;xsl:with-param name="replace"&gt;\&amp;lt;/xsl:with-param&gt;&lt;br/&gt;          &amp;lt;xsl:with-param name="by"&gt;\\&amp;lt;/xsl:with-param&gt;&lt;br/&gt;        &amp;lt;/xsl:call-template&gt;&lt;br/&gt;      &amp;lt;/xsl:with-param&gt;&lt;br/&gt;      &amp;lt;xsl:with-param name="replace"&gt;"&amp;lt;/xsl:with-param&gt;&lt;br/&gt;      &amp;lt;xsl:with-param name="by"&gt;\"&amp;lt;/xsl:with-param&gt;&lt;br/&gt;    &amp;lt;/xsl:call-template&gt;"&lt;br/&gt;  &amp;lt;/xsl:template&gt;&lt;br/&gt;&lt;br/&gt;&amp;lt;!-- Template dankend übernommen&lt;br/&gt;     aus http://geekswithblogs.net/Erik/archive/2008/04/01/120915.aspx --&gt;&lt;br/&gt;  &amp;lt;xsl:template name="string-replace-all"&gt;&lt;br/&gt;    &amp;lt;xsl:param name="text" /&gt;&lt;br/&gt;    &amp;lt;xsl:param name="replace" /&gt;&lt;br/&gt;    &amp;lt;xsl:param name="by"/&gt;&lt;br/&gt;    &amp;lt;xsl:choose&gt;&lt;br/&gt;      &amp;lt;xsl:when test="contains($text, $replace)"&gt;&lt;br/&gt;        &amp;lt;xsl:value-of select="substring-before($text,$replace)" /&gt;&lt;br/&gt;        &amp;lt;xsl:value-of select="$by" /&gt;&lt;br/&gt;        &amp;lt;xsl:call-template name="string-replace-all"&gt;&lt;br/&gt;          &amp;lt;xsl:with-param name="text"&lt;br/&gt;          select="substring-after($text,$replace)" /&gt;&lt;br/&gt;          &amp;lt;xsl:with-param name="replace" select="$replace" /&gt;&lt;br/&gt;          &amp;lt;xsl:with-param name="by" select="$by" /&gt;&lt;br/&gt;        &amp;lt;/xsl:call-template&gt;&lt;br/&gt;      &amp;lt;/xsl:when&gt;&lt;br/&gt;      &amp;lt;xsl:otherwise&gt;&lt;br/&gt;        &amp;lt;xsl:value-of select="$text" /&gt;&lt;br/&gt;      &amp;lt;/xsl:otherwise&gt;&lt;br/&gt;    &amp;lt;/xsl:choose&gt;&lt;br/&gt;  &amp;lt;/xsl:template&gt;&lt;/pre&gt;&lt;br /&gt;Wer sich für die Transformation näher interessiert, kann den vollständigen Quelltext bei &lt;a href="http://pastebin.com/FCkhLVQp"&gt;Pastebin&lt;/a&gt; betrachten oder herunterladen.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-4932736625818604565?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/4932736625818604565/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=4932736625818604565' title='8 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/4932736625818604565'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/4932736625818604565'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/09/ein-json-builder-in-abap.html' title='Ein JSON-Builder in ABAP'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-4185228040433737886</id><published>2011-08-30T19:21:00.018+01:00</published><updated>2011-09-06T06:50:55.380+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Groovy vereinfacht SAP-Zugriffe mit dem Java Connector</title><content type='html'>Es ist keine Propaganda zur Verbreitung einer (nicht mehr so ganz) neuen Programmierprache, sondern es stimmt wirklich: Wer seine Aufgaben in Groovy statt in Java löst, produziert wesentlich weniger Code, der obendrein noch besser lesbar ist. Das kann man mit allen möglichen Aufgaben zeigen &amp;ndash; zum Beispiel auch mit einem RFC-Aufruf in ein SAP-System und der Auswertung der Rückgabewerte, wobei für die Verbindung der &lt;a href="http://help.sap.com/javadocs/NW04/current/jc/com/sap/mw/jco/JCO.html"&gt;Java Connector&lt;/a&gt; zum Einsatz kommt.&lt;br /&gt;&lt;br /&gt;Die Besonderheit von Groovy ist, dass es - als &lt;i&gt;dynamische&lt;/i&gt; Programmiersprache - zur Laufzeit versucht, das Gemeinte zu erraten und damit meistens erstaunlich richtig liegt. Wenn es sich doch einmal anders als gewünscht entscheidet, kann man ihm diese Unarten ohne viel Aufwand abgewöhnen. So entfällt im Code viel "syntaktisches Rauschen" (&lt;i&gt;syntactic noise&lt;/i&gt;), wie man es von statischen Programmiersprachen kennt. Übrig bleibt der näher beim Problem liegende Code. Durch diese Beschränkung auf das Wesentliche wird der verbleibende Code automatisch lesbarer. Es gibt in Groovy die Möglichkeit, bei wichtigen grundlegenden Zeitpunkten zur Laufzeit einzugreifen, etwa beim Methoden-Lookup und beim Setzen und Abfragen eines Attributs. Dadurch hat man die Möglichkeit, die Syntax des Codes zu gestalten. Diese Freiheit kann genutzt werden, um die Klarheit, Lesbarkeit und Kompaktheit des Codes noch weiter zu steigern.  &lt;br /&gt;&lt;br /&gt;Was ist der Preis? Man könnte argwöhnen, dass die Performance durch die vielen dynamischen Features von Groovy leidet. Zunächst wird man überrascht sein zu sehen, dass die Performance von Groovy-Programmn entgegen solchen Erwartungen sehr gut ist. Dennoch mag der Einwand für Programmteile stimmen, in denen hochperformant gearbeitet werden muss. Da Groovy mit Java kompatibel ist, hat man aber immer die Möglichkeit, mit &lt;i&gt;Plain Old Java Objects&lt;/i&gt; (POJOs) zu arbeiten oder im Extremfall &amp;ndash; wie in Java &amp;ndash; native Bibliotheken aufzurufen. Je näher man aber in den Bereich der Benutzerschnittstelle kommt &amp;ndash; wenn Dialoge involviert sind oder auf irgendeine Art die unendlich langsame menschliche Reaktionszeit ins Spiel kommt &amp;ndash; desto unerheblicher wird die Frage, ob man mit einer statischen oder dynamischen Sprache arbeitet. Für Dinge wie automatische Tests - in denen Benutzerschnittstellen durchlaufen werden - ist das Laufzeitverhalten einer dynamischen Sprache wie Groovy mehr als ausreichend. &lt;br /&gt;&lt;br /&gt;Wie gut man eine Sprache wie Groovy zur Formulierung von automatischen Tests verwenden kann, habe ich in meinem letzten Blog über &lt;a href="http://ruediger-plantiko.blogspot.com/2011/08/selenium-mit-groovy.html"&gt;Selenium und Groovy&lt;/a&gt; gezeigt.&lt;br /&gt;&lt;br /&gt;Hier will ich beschreiben, wie sich die Zugriffe auf komplexe ABAP-Datenstrukturen mit Groovy vereinfachen. Für den Zugriff selbst verwende ich dabei den &lt;a href="http://help.sap.com/javadocs/NW04/current/jc/com/sap/mw/jco/JCO.html"&gt;SAP Java Connector (JCo)&lt;/a&gt;. Der JCo ist ein Tool zur Ausführung entfernter Funktionsbausteinaufrufe (RFC) in einem SAP-System. Mittels eines Metadaten-API kann man den Aufruf vor dem Baustein mit Daten versorgen und hinterher aus den Rückgabewerten Daten auslesen, wobei viele Daten bereits passend konvertiert werden: Ein ABAP-Feld vom Typ D (Datum) ergibt z.B. in Java einen Feldwert vom Typ &lt;tt&gt;java.util.Date&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;Nun muss man zum Auslesen komplexer Daten auch in Java viel hinschreiben. Für jede einzelne Auswertung bedarf es eines eigenen Methodenaufrufs, und Zwischenergebniss müssen häufig in lokalen Variablen gehalten werden. Eine komplexe Struktur &lt;tt&gt;beleg&lt;/tt&gt; müsste man mit dem JCo z.B. wie folgt auslesen, um den Preis der 5. Position zu ermitteln: &lt;br /&gt;&lt;pre class="sh_java"&gt;double getPrice( JCO.Record order, int pos) {&lt;br/&gt;// ITEMS sei Komponente der ABAP-Struktur BELEG&lt;br/&gt;  JCO.Table items = order.getValue( "ITEMS" ); &lt;br/&gt;// Workarea der JCO.Table auf Position pos setzen  &lt;br/&gt;  items.setRow( pos );&lt;br/&gt;// Nun kann der Preis ausgelesen werden  &lt;br/&gt;  return items.getValue( "PRICE" );&lt;br/&gt;  }&lt;br/&gt;&lt;/pre&gt;&lt;br /&gt;Diese aufwendigen Folge von im Grunde uninteressanten Methodenaufrufen stört das Auge und erschwert den Blick auf das Wesentliche. Wäre es nicht schöner, einfach folgendes schreiben zu können?&lt;br /&gt;&lt;pre class="sh_java"&gt;order.ITEMS[pos].PRICE&lt;/pre&gt;&lt;br /&gt;Groovy bietet die Möglichkeit, die Wertabfrage komplexer Daten genau so zu notieren. Man könnte den Ausdruck sogar auf der linken Seite stehen haben,&lt;br /&gt;&lt;pre class="sh_java"&gt;order.ITEMS[pos].PRICE = 10.00&lt;/pre&gt;&lt;br /&gt;und es würde eine Zuweisung an die Komponente PRICE der Zeile &lt;tt&gt;pos&lt;/tt&gt; in der tabellenförmigen Komponente &lt;tt&gt;ITEMS&lt;/tt&gt; der Struktur &lt;tt&gt;order&lt;/tt&gt; ausgeführt.&lt;br /&gt;&lt;br /&gt;Wie könnte man so etwas erreichen? Zunächst braucht man einen Wrapper (Adapter) - eine Klasse, die intern Referenzen auf die JCO-Objekte verwaltet, deren Methoden aufruft  und die Möglichkeit der Rekursion bietet &amp;ndash; denn Rekursion ist für Werteabfragen komplexer Datenobjekte das passende Verfahren:&lt;br /&gt;&lt;pre class="sh_java"&gt;class Data {&lt;br/&gt;&lt;br/&gt;  private JCO.Record record&lt;br/&gt;  &lt;br/&gt;  Data( JCO.Record record) {&lt;br/&gt;    this.record = record;&lt;br/&gt;    }&lt;br/&gt;...    &lt;br/&gt;  }&lt;/pre&gt;&lt;br /&gt;Nun kommt etwas Groovy-Spezifisches: Mit den Methoden &lt;tt&gt;getProperty(name)&lt;/tt&gt; und &lt;tt&gt;setProperty(name,value)&lt;/tt&gt; kann in Groovy der Member-Operator "." überschrieben werden. Wenn die Groovy-Laufzeit auf den "." trifft, so versucht sie, bevor der Zugriff auf ein statisches Attribut gemacht wird, zuerst die Methode &lt;tt&gt;getProperty&lt;/tt&gt; (oder &lt;tt&gt;setProperty&lt;/tt&gt; auf der linken Seite einer Zuweisung) auszuführen, wenn eine Methode dieses Namens vorhanden ist. Ist dies der Fall, so wird die Operation auf die Methoden umgelenkt. Das heisst, an Stelle des Zugriffs &lt;tt&gt;a.b&lt;/tt&gt; wird intern &lt;tt&gt;a.getProperty( "b" )&lt;/tt&gt; evaluiert, und an Stelle der Zuweisung &lt;tt&gt;a.b = c&lt;/tt&gt; wird intern die Methode &lt;tt&gt;a.setProperty( "b", c )&lt;/tt&gt; ausgeführt.&lt;br /&gt;&lt;br /&gt;Obwohl also ein &lt;tt&gt;Data&lt;/tt&gt;-Objekt für die Zeilenstruktur der &lt;tt&gt;USERLIST&lt;/tt&gt; kein Attribut &lt;tt&gt;USERNAME&lt;/tt&gt; besitzt, kann der &lt;i&gt;Zugriff so ausgeführt werden, als wäre es vorhanden.&lt;/i&gt; Hinter den Kulissen werden dabei die Methoden &lt;tt&gt;getProperty&lt;/tt&gt; bzw. &lt;tt&gt;setProperty&lt;/tt&gt; aufgerufen. Die Rekursion lösen wir, indem wir das Ergebnis automatisch in eine neue &lt;tt&gt;Data&lt;/tt&gt;-Instanz einpacken, falls es wieder ein komplexes Datenobjekt ist. Die Methoden sehen daher im wesentlichen so aus:&lt;br /&gt;&lt;pre class="sh_java"&gt;class Data {&lt;br/&gt;...&lt;br/&gt;  def getProperty(String name) {&lt;br/&gt;// Spezielle Attribute des Datenobjekts müssen hier gesondert behandelt werden&lt;br/&gt;    if (name == "length") return record.getNumRows()&lt;br/&gt;    if (name == "record") return record&lt;br/&gt;// Allgemeiner Fall: Mit JCo in der Struktur record suchen    &lt;br/&gt;    def f = record.getValue( name )&lt;br/&gt;    if (record.isTable( name ) || record.isStructure( name )) &lt;br/&gt;      return new Data( f )&lt;br/&gt;    else {&lt;br/&gt;      return f&lt;br/&gt;      }&lt;br/&gt;    }&lt;br/&gt;    &lt;br/&gt;  void setProperty(String name, Object value) {&lt;br/&gt;    record.getField( name ).setValue( value );&lt;br/&gt;    }      &lt;br/&gt;  ...&lt;br/&gt;}&lt;/pre&gt;&lt;br /&gt;Auch den Subscript-Operator &lt;tt&gt;[]&lt;/tt&gt; können wir für Data-Objekte nach eigenem Gusto definieren.&lt;br /&gt;&lt;pre class="sh_java"&gt;  def getAt(i) {&lt;br/&gt;    record.setRow( i );&lt;br/&gt;// Danach erfolgt ein Komponetenzugriff &lt;br/&gt;    this   &lt;br/&gt;    }&lt;/pre&gt;&lt;br /&gt;Hier wird nur die Anfrage nach der &lt;tt&gt;i&lt;/tt&gt;-ten Tabellenzeile vom &lt;tt&gt;Data&lt;/tt&gt;-Objekt an das JCo-Repository-Objekt &lt;tt&gt;record&lt;/tt&gt; delegiert. Die Rückgabe von &lt;tt&gt;this&lt;/tt&gt; dient nur dem &lt;i&gt;method chaining&lt;/i&gt;: Nachdem man in eine &lt;tt&gt;JCo.Table&lt;/tt&gt; mittels &lt;tt&gt;setRow&lt;/tt&gt; eine Zeile eingestellt hat, funktioniert sie so wie eine &lt;tt&gt;JCo.Structure&lt;/tt&gt; &amp;ndash; man kann auf einzelne Komponenten der Zeile mittels &lt;tt&gt;getValue&lt;/tt&gt; zugreifen. Das wäre aber genau das, was &lt;tt&gt;Data&lt;/tt&gt; beim Komponentenzugriff sowieso macht.&lt;br /&gt;&lt;br /&gt;Als angenehme Zugabe habe ich noch eine Methode &lt;tt&gt;set&lt;/tt&gt; vorgesehen, die es erlaubt, mehrere Komponenten einer Struktur auf einmal zu setzen. So kann ich mehrere Name/Wert-Paare auf einmal übergeben. Hier ein Beispielaufruf, um zwei Importparameter eines Funktionsbausteins auf einmal zu setzen:&lt;br /&gt;&lt;pre class="sh_java"&gt;    userList.importParameter.set( &lt;br/&gt;      MAX_ROWS : 50,&lt;br/&gt;      WITH_USERNAME : "X"&lt;br/&gt;      )&lt;/pre&gt;&lt;br /&gt;Die Lesbarkeit ist deutlich besser - während die Implementierung der benötigten &lt;tt&gt;set()&lt;/tt&gt;-Methode denkbar einfach ist:&lt;br /&gt;&lt;pre class="sh_java"&gt;  def set = { pairs -&gt;&lt;br/&gt;    for (p in pairs) {&lt;br/&gt;      setProperty p.key, p.value&lt;br/&gt;      }&lt;br/&gt;    }&lt;/pre&gt;&lt;br /&gt;Schliesslich können wir noch das Interface &lt;tt&gt;Iterator&lt;/tt&gt; sinnvoll für interne Tabellen implementieren:&lt;br /&gt;&lt;pre class="sh_java"&gt;class Data implements Iterator { &lt;br/&gt;...&lt;br/&gt;// Iterator interface  &lt;br/&gt;  void remove() {}&lt;br/&gt;&lt;br/&gt;  boolean hasNext() {&lt;br/&gt;    return !record.isLastRow()&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;  def next() {&lt;br/&gt;    record.nextRow()&lt;br/&gt;    this&lt;br/&gt;    }&lt;/pre&gt;&lt;br /&gt;Was ist der Gewinn? Wir können in Groovy (und auch in Java) nun mit dem &lt;tt&gt;for ( .. in .. )&lt;/tt&gt; wie in einer ABAP-Loop die Zeilen der Tabelle durchlaufen. Mit der folgenden kleinen &lt;tt&gt;display&lt;/tt&gt;-Methode bilden wir z.B. aus den Spalten &lt;tt&gt;USERNAME&lt;/tt&gt; und &lt;tt&gt;FULLNAME&lt;/tt&gt; einer internen Tabelle einen zweidimensionalen Array, wie ihn die &lt;tt&gt;JTable&lt;/tt&gt; als Datenoobjekt entgegennimmt:&lt;br /&gt;&lt;pre class="sh_java"&gt;// Show a JTable with the result    &lt;br/&gt;  void display( userlist ) {&lt;br/&gt;    def data = []&lt;br/&gt;    for (record in userlist) { &lt;br/&gt;      data.add( [ record.USERNAME, record.FULLNAME ] )&lt;br/&gt;      }&lt;br/&gt;    new GUI().showTable( data, ["Benutzername","Bürgerlicher Name"] )  &lt;br/&gt;    }  &lt;/pre&gt;&lt;br /&gt;Der &lt;tt&gt;record&lt;/tt&gt; ist hier, was in ABAP eine Workarea wäre. Die Schleife ist in dieser angenehm lesbaren Form verwendbar, weil &lt;tt&gt;userlist&lt;/tt&gt; das Interface &lt;tt&gt;Iterator&lt;/tt&gt; implementiert.  &lt;br /&gt;&lt;br /&gt;Die folgende Testmethode ruft den Funktionsbaustein &lt;tt&gt;BAPI_USER_GETLIST&lt;/tt&gt; per RFC auf und stellt die Ergebnisse in einer Tabelle dar. Was sich für andere Aufgaben wiederverwenden lässt, ist in Hilfsklassen ausgelagert.&lt;br /&gt;&lt;pre class="sh_java"&gt;  @Test void test() {      &lt;br/&gt;  &lt;br/&gt;// Provide handle to a remotely callable function  &lt;br/&gt;    def userList = repo.getFunction "BAPI_USER_GETLIST"&lt;br/&gt;    &lt;br/&gt;// Parametrize it       &lt;br/&gt;    userList.importParameter.set( &lt;br/&gt;      MAX_ROWS : 50,&lt;br/&gt;      WITH_USERNAME : "X"&lt;br/&gt;      )&lt;br/&gt;      &lt;br/&gt;// Do the call      &lt;br/&gt;    userList.execute( )&lt;br/&gt;&lt;br/&gt;// Get and display result&lt;br/&gt;    display userList.tableParameter.USERLIST&lt;br/&gt;&lt;br/&gt;  }&lt;/pre&gt;&lt;br /&gt; &lt;br /&gt; Ich habe nicht geschummelt. Das vollständige Codebeispiel zum Auslesen und Anzeigen der Tabelle von Benutzern eines SAP-Systems ist nicht wesentlich grösser als die hier gezeigten Ausschnitte und kann auf &lt;tt&gt;Pastebin&lt;/tt&gt; unter folgendem Link eingesehen werden:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pastebin.com/jKN67XxL"&gt;Accessing ABAP data efficiently&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;[1] Ich verwende die Groovy-Version 1.8.0, in das gemäss Release Notes einiger Aufwand in Performance-Optimierungen gesetzt wurde. Im übrigen gilt für Groovy wie für alles andere: "Don't blame the tools". 90% aller Performanceprobleme kommen nicht der verwendeten Plattform, sondern dem Applikationscode zuschulden.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-4185228040433737886?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/4185228040433737886/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=4185228040433737886' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/4185228040433737886'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/4185228040433737886'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/08/groovy-vereinfacht-sap-zugriffe-mit-dem.html' title='Groovy vereinfacht SAP-Zugriffe mit dem Java Connector'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-8099968231701647456</id><published>2011-08-08T19:56:00.035+01:00</published><updated>2011-08-10T08:45:24.358+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Selenium mit Groovy</title><content type='html'>Hin und wieder muss man sich einfach einmal umsehen. Zwar verwendet meine Firma  für die Testautomatisierung &lt;a href="http://en.wikipedia.org/wiki/HP_QuickTest_Professional"&gt;Quick Test Professional&lt;/a&gt; - aber wie fühlt es sich im Vergleich dazu an, mit einem anderen System wie etwa &lt;a href="http://seleniumhq.org/"&gt;Selenium&lt;/a&gt; zu arbeiten? &lt;br /&gt;&lt;br /&gt;Schon länger benutze ich einen kleinen Teil von Selenium - die sogenannte &lt;a href="http://seleniumhq.org/projects/ide/"&gt;Selenium IDE&lt;/a&gt;. Es handelt sich um ein auch für sich allein sehr nützliches Plugin für den Firefox.[1] Es hat eine Recorderfunktion und kann Dialogschritte im Web aufzeichnen und in Form einer XHTML-Tabelle abspeichern. Angenehm ist die Möglichkeit, über die rechte Maustaste Assertions und Verifications in den Test einzufügen. Eine ausgezeichnete kontextsensitive Hilfe unterstützt beim Einfügen von Kommandos. Und die Identifizierung von Elementen ist auf verschiedene, unter Web-Entwicklern verbreitete Weisen möglich: Natürlich kann man direkt den Elementnamen oder die ID verwenden. Aber auch CSS-Selektoren, JavaScript-DOM-Terme und XPath-Ausdrücke sind möglich.&lt;br /&gt;&lt;br /&gt;Ich verwende die Selenium-IDE hauptsächlich, um immer gleich Sequenzen von Dialogschritten in einer Web-Anwendung abzuspielen. Das ist vor allem während des Entwickelns nötig: Wenn ich das fünfte Bild einer Anwendung entwickle, kommt mir ein Tool entgegen, das auf Knopfdruck alle Daten initialisiert und sich dann auf "die normale Weise" bis zu diesem fünften Bild durcharbeitet. So kann ich die Applikation schnell immer wieder in den gleichen Zustand bringen und mir anschauen, wie sich meine letzten Änderungen auswirken. Wenn dieses "Anschauen" - das ja ein Kontrollieren ist - sich in Form von Assertions formulieren lässt, baue ich sie gleich in den Test ein. Wenn ich dann mit dem sechsten Bild fortfahre, habe ich durch das Abspielen des Tests zugleich eine gewisse Sicherheit, dass alle vorigen Bilder weiterhin korrekt durchlaufen werden.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-WvdyKAEQabk/TkA4atMKbcI/AAAAAAAAALs/e5JuqSGT6NU/s1600/selenium_ide.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 307px;" src="http://3.bp.blogspot.com/-WvdyKAEQabk/TkA4atMKbcI/AAAAAAAAALs/e5JuqSGT6NU/s400/selenium_ide.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5638568764926225858" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Wenn der Geradeausfall realisiert ist, kommen Randfälle an die Reihe, Sonderfunktionen einzelner Bilder, aber auch Tests zum Nachstellen eines Bugs.&lt;br /&gt;So entsteht im Laufe des Projekts eine Halde von XHTML-Dateien mit Tests. Tests, die wirklich nicht mehr sind als eine Aufzeichnung von Dialogschritten. &lt;br /&gt;&lt;br /&gt;Allerdings: Wiederverwendbarkeit ist auf dieser Ebene nicht möglich. Es gibt meines Wissens innerhalb der Selenium-IDE keine Möglichkeit, Tests in Tests aufzurufen oder auf irgendeine andere Weise Folgen von Selenium-Kommandos zu Modulen zusammenzufassen. Das ist auch nicht so gedacht. Der vorgesehene Ablauf ist, dass diese Aufzeichnungen nun in eine geeignete Programmiersprache exportiert werden und dort z.B. mittels eines UnitTest-Frameworks ausgeführt, zu Suiten zusammengefasst, modularisiert, parametrisiert, nachgebessert, refaktorisiert werden. Erst auf dieser Ebene hat man ein geeignetes Programmierumfeld. Dort beginnt erst das solide Arbeiten an Testcode. &lt;br /&gt;&lt;br /&gt;Um auch diese Dimension von Selenium einmal kennenzulernen, habe ich mich dazu entschieden, einige der Tests as meinem letzten Projekt in die Programmiersprache &lt;a href="http://groovy.codehaus.org/"&gt;Groovy &lt;/a&gt;zu exportieren und den Code dann dort nachzubearbeiten.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://groovy.codehaus.org/"&gt;Groovy&lt;/a&gt; ist vor allem wegen seiner DSL-Features zur Formulierung von Testfällen ideal geeignet. Das gilt auch für Selenium - obwohl, wie ich feststellen musste, der Groovy-Export nur unvollständig funktioniert[2], und obwohl eine zunächst vorgesehene Hilfsklasse &lt;tt&gt;GroovySeleneseTestCase&lt;/tt&gt; als Basisklasse in früheren Releases einmal vorgesehen war, aber mittlerweile nicht mehr in der Distribution enthalten ist. &lt;br /&gt; &lt;br /&gt;Auch davon abgesehen, bietet der generierte Code nur einen groben Anhalt. Wenn man sich aber anschaut, wie die Kommandos unter Verwendung des Objekts &lt;tt&gt;Selenium.java&lt;/tt&gt; abgesetzt werden können, ist es nicht besonders mühsam, die Scripts manuell zu erfassen. Auch wäre es kein Hexenwerk, einen eigenen Exporter des XHTML-Testfalls nach Groovy zu schreiben. Beispiele, wie man so etwas mit XSLT machen könnte, finden sich im "XSLT-Kochbuch" von Sal Mangano.[3] &lt;br /&gt;&lt;br /&gt;Ich habe den &lt;tt&gt;GroovySeleneseTestCase.groovy&lt;/tt&gt; wieder auferstehen lassen, aber nur mit dem Zweck, den Setup- und Teardown-Code nur einmal hinzuschreiben, mit dem der "Selenium-Server", der Vermittler zwischen dem Testscript und der zu testenden Anwendung, gestartet und heruntergefahren wird:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_java"&gt;import org.junit.*;&lt;br/&gt;import com.thoughtworks.selenium.*;&lt;br/&gt;&lt;br/&gt;public class GroovySeleneseTestCase {&lt;br/&gt;  @Delegate protected SeleniumHelper seleniumHelper;        &lt;br/&gt;  @Before&lt;br/&gt;  public void startSelenium() {	&lt;br/&gt;    Selenium s = new DefaultSelenium(&lt;br/&gt;      "localhost", &lt;br/&gt;      4444, &lt;br/&gt;      "*chrome", &lt;br/&gt;      "http://test.mgb.ch/sap/bc/bsp/sap/ztm_bestellung/main.do"&lt;br/&gt;      );&lt;br/&gt;    seleniumHelper = new SeleniumHelper( s );&lt;br/&gt;    start()    &lt;br/&gt;    }	    &lt;br/&gt;  @After&lt;br/&gt;   public void tearDown() {&lt;br/&gt;     stop()&lt;br/&gt;     }&lt;br/&gt;  @Test @Ignore void dummy() {}       &lt;br/&gt;}&lt;/pre&gt;&lt;br/&gt;Den nicht auszuführenden Dummy-Test habe ich nur eingefügt, um die Ausführung der Setup- und Teardown-Methode zu verhindern, wenn man aus irgendwelchen Gründen diese Groovy-Klasse nicht nur kompilieren, sondern ausführen will.[4] Denn diese Klasse ist nur als Oberklasse für die anderen, die eigentlichen Testklassen gedacht.&lt;br /&gt;&lt;br /&gt;Man beachte die schöne Groovy-Annotation &lt;tt&gt;@Delegate&lt;/tt&gt;. Sie wirkt so, als würde man in einem statischen Programm alle Methoden der Klasse &lt;tt&gt;SeleniumHelper&lt;/tt&gt; zu eigenen erklären, also&lt;br /&gt;&lt;pre class="sh_java"&gt;public void start() {&lt;br/&gt;  seleniumHelper.start();&lt;br/&gt;  }&lt;br/&gt;public void stop() {&lt;br/&gt;  seleniumHelper.stop(); &lt;br/&gt;  } &lt;br/&gt;usw.&lt;br/&gt;&lt;/pre&gt;Allerdings wirkt die Annotation &lt;tt&gt;@Decorate&lt;/tt&gt; nicht statisch, sondern dynamisch &amp;ndash; indem sie dem Trägerobjekt beim Methoden-Lookup eine Methode des Delegationsobjekts mit der gleichen Signatur anbietet. &lt;br /&gt;&lt;br /&gt;Der &lt;tt&gt;SeleniumHelper.groovy&lt;/tt&gt; wiederum ist meine Erweiterung der &lt;tt&gt;Selenium&lt;/tt&gt;-Klasse. Mit demselben Trick &lt;tt&gt;@Decorate&lt;/tt&gt; sind alle Methoden von &lt;tt&gt;Selenium.java&lt;/tt&gt; verfügbar - darüberhinaus aber ist diese Klasse ein Pool, in den ich alle wiederkehrenden Code-Abschnitte auslagern kann, die nicht anwendungsspezifisch sind, sondern die Ausführung von Selenium-Kommandos betreffen. &lt;br /&gt;&lt;pre class="sh_java"&gt;class SeleniumHelper implements Selenium {  &lt;br/&gt;  @Delegate Selenium selenium;    &lt;br/&gt;  SeleniumHelper(Selenium s) {&lt;br/&gt;    selenium = s;&lt;br/&gt;    }  &lt;br/&gt;  static final timeout = "30000"  	      &lt;br/&gt;  def pageLoaded = {&lt;br/&gt;    waitForPageToLoad(timeout);&lt;br/&gt;    true&lt;br/&gt;    }      &lt;br/&gt;  def clickAndWait = {&lt;br/&gt;    click it&lt;br/&gt;    waitFor pageLoaded&lt;br/&gt;    }        &lt;br/&gt;  public waitFor(f) {&lt;br/&gt;    if (f == pageLoaded)&lt;br/&gt;      pageLoaded()&lt;br/&gt;    else &lt;br/&gt;      for (int second = 0;; second++) {                        &lt;br/&gt;        Thread.sleep(1000);                                    &lt;br/&gt;        if (second &gt;= 60) fail("timeout");                     &lt;br/&gt;        try { if (f) break; } catch (Exception e) {}			     &lt;br/&gt;        }                                                      &lt;br/&gt;    }    &lt;br/&gt;  }&lt;/pre&gt; &lt;br /&gt;Wie in Scriptsprachen üblich, verwende ich die Datenstrukturen von Hashs und Arrays, um die Testdaten zu notieren - Dinge wie Partner-, Kontrakt-, Artikelnummern usw., wie sie für den zu testenden Bestellvorgang benötigt werden:&lt;br /&gt;&lt;pre class="sh_java"&gt;// Testdaten&lt;br/&gt;    def test = [&lt;br/&gt;      partner:"0010006179",&lt;br/&gt;      kontrakt:"0020007901",&lt;br/&gt;      items: [&lt;br/&gt;        [&lt;br/&gt;          matnr:"400617200000",&lt;br/&gt;          menge:"2",&lt;br/&gt;          vkhm:"8000000851",&lt;br/&gt;          type:"P",&lt;br/&gt;          lieferantPos:"0",&lt;br/&gt;          listPos:"0003"          &lt;br/&gt;        ],&lt;br/&gt;        [&lt;br/&gt;          matnr:"400617200000",&lt;br/&gt;          menge:"2",&lt;br/&gt;          vkhm:"8000001585",&lt;br/&gt;          type:"N",&lt;br/&gt;          lieferantPos:"1",&lt;br/&gt;          listPos:"0005"&lt;br/&gt;        ]       &lt;br/&gt;      ]&lt;br/&gt;    ]&lt;/pre&gt;&lt;br /&gt; &lt;br /&gt;Den eigentlichen Testfall kann ich unter Verwendung der Groovy-Sprachfeatures sehr kompakt programmieren. Hier ist er:&lt;br /&gt;&lt;pre class="sh_java"&gt;class StraightOrder extends GroovySeleneseTestCase {  &lt;br/&gt;  @Delegate OrderSteps orderSteps  &lt;br/&gt;  @Before&lt;br/&gt;  void setup() {&lt;br/&gt;    orderSteps = new OrderSteps( seleniumHelper )&lt;br/&gt;    clearSessionData&lt;br/&gt;    }&lt;br/&gt;  @Test&lt;br/&gt;  void selectIndividualItemsAndSave() {    &lt;br/&gt;    selectTagsWithContract partner:test.partner, kontrakt:test.kontrakt    &lt;br/&gt;    takeToBasket test.items &lt;br/&gt;    selectPrintOfficeFor test.items    &lt;br/&gt;    goTo "OrderData"&lt;br/&gt;    setRequestedDeliveryDateToday()&lt;br/&gt;    goTo "Bestellung"&lt;br/&gt;    assert isElementPresent("final_item_list")    &lt;br/&gt;    click "jurinfo_gelesen"&lt;br/&gt;    clickAndWait "saveBestellung"&lt;br/&gt;    // Erfolgsmeldung nach dem Sichern:&lt;br/&gt;    assert getText("msg") =~ /^Ihre Bestellung .* wurde versendet!$/&lt;br/&gt;    }&lt;br/&gt;  }&lt;/pre&gt;Einige der hier verwendeten Methoden - wie &lt;tt&gt;click&lt;/tt&gt;, &lt;tt&gt;isElementPresent&lt;/tt&gt; und &lt;tt&gt;open&lt;/tt&gt; gehen direkt an &lt;tt&gt;Selenium.java&lt;/tt&gt;. Andere wie &lt;tt&gt;selectTagsWithContract&lt;/tt&gt; sind bereits das Ergebnis einer Modularisierung, die ich in eine Delegationsklasse &lt;tt&gt;OrderSteps.groovy&lt;/tt&gt; ausgelagert habe. Dort stehen sie für weitere Testklassen und -methoden zur Verfügung. &lt;br /&gt;&lt;br /&gt;Hier der Beginn der Klasse &lt;tt&gt;OrderSteps.groovy&lt;/tt&gt; mit der Implementierung von &lt;tt&gt;selectTagsWithContract&lt;/tt&gt;:&lt;pre class="sh_java"&gt;class OrderSteps {&lt;br/&gt;  @Delegate private SeleniumHelper seleniumHelper;&lt;br/&gt;  OrderSteps( SeleniumHelper s) {&lt;br/&gt;    seleniumHelper = s;&lt;br/&gt;    }&lt;br/&gt;  def clearSessionData = {&lt;br/&gt;    open "/ztm/bestellung/clearBasketAndSession"&lt;br/&gt;    }&lt;br/&gt;  def selectTagsWithContract = { selected -&gt;&lt;br/&gt;    open "../ztm_suche/main.do"&lt;br/&gt;    type "partner", selected.partner&lt;br/&gt;    type "kontrakt", selected.kontrakt&lt;br/&gt;    clickAndWait "startSelection"&lt;br/&gt;    }&lt;br/&gt;  def goTo = {&lt;br/&gt;    clickAndWait "css=.goto$it"&lt;br/&gt;    }&lt;br/&gt;  ...&lt;/pre&gt;Was ist mein Fazit? Die Kombination Selenium mit Groovy, auch wenn sie vom Selenium-Team eher stiefmütterlich behandelt wird, ist vielversprechend und macht Appetit auf mehr! &lt;br /&gt;&lt;br /&gt;Der Testcode ist ein wichtiger Teil eines Automatisierungstools. Die Entscheidung des ehemaligen Mercury-Teams, im &lt;i&gt;QuickTestProfessional&lt;/i&gt; System ausgerechnet &lt;tt&gt;vbScript&lt;/tt&gt; zugrundezulegen, zeugt von einer unangemessenen Verachtung der Code-Ebene. Auch als das Tool entstand, gab es bereits bessere Optionen. Hier ist Selenium klar im Vorteil, denn es wurde im Design auf eine Entkopplung der Ausführungsumgebung vom steuernden Code grosser Wert gelegt. &lt;br /&gt;&lt;br /&gt;Aus Entwicklungssicht bedeutet genau diese Eigenschaft des Selenium-Systems den entscheidenden Vorteil. Für eine Gesamtbeurteilung muss aber auch das Umfeld einbezogen werden. QTP ist ab Werk mit dem &lt;i&gt;Quality Center&lt;/i&gt; verknüpft, und damit mit einem Bugtrackingsystem, mit Planungs- und Auswertungsmöglichkeiten für ganze Entwicklungsprojekte - die Betriebsphase eingeschlossen. Ob Selenium (eventuell in Kombination mit auf Selenium aufsetzenden anderen Produkten) hier mithalten kann, weiss ich nicht.  &lt;br /&gt;&lt;br /&gt;[1] Leider ist die Selenium IDE aktuell (Stand 8.8.2011) noch nicht für Firefox 4 und höher freigegeben. Sie ist aber trotzdem auch in Firefox-Releases ab 4.0 verwendbar, indem man sich durch Installation des &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/add-on-compatibility-reporter/"&gt;Add-on Compatibility Reporters&lt;/a&gt; zum Releasetester erklärt. Ich habe das gemacht und bislang noch keinen Fehler bei der Verwendung in FF 4 entdeckt.&lt;br /&gt;[2] Beispielsweise wird ein in regulären Ausdrücken vorkommendes Dollarzeichen nicht maskiert, was zu einem Fehler in &lt;tt&gt;groovyc&lt;/tt&gt; führt, der beim Dollarzeichen einen Variablennamen oder einen evaluierbaren Ausdruck erwartet.&lt;br /&gt;[3] Sal Mangano, &lt;i&gt;Das XSLT-Kochbuch&lt;/i&gt;, O'Reilly 2006. &lt;br /&gt;[4] Und solche "irgendwelchen Gründe" liegen bei mir vor. Ich habe mir nämlich in UltraEdit ein Werkzeug für die Groovy-Bearbeitung eingerichtet, das erfolgreich compilierte Klassen immer auch gleich ausführt.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-8099968231701647456?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/8099968231701647456/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=8099968231701647456' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/8099968231701647456'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/8099968231701647456'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/08/selenium-mit-groovy.html' title='Selenium mit Groovy'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-WvdyKAEQabk/TkA4atMKbcI/AAAAAAAAALs/e5JuqSGT6NU/s72-c/selenium_ide.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-5447082467958123912</id><published>2011-08-05T22:02:00.020+01:00</published><updated>2011-09-18T22:34:59.937+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Dekorierte BSP-Elemente</title><content type='html'>Ein BSP-Element ist eine Softwareobjekt mit einer besonderen Aufrufkonvention:  Es wird nicht direkt von ABAP-Codeeinheiten wie Methoden, Routinen oder Funktionsbausteinen aufgerufen, sondern als XML-Codefragment in einem View.   In einem &lt;a href="http://ruediger-plantiko.blogspot.com/2011/07/abap-unit-tests-fur-bsp-elemente.html"&gt;früheren Blog&lt;/a&gt; habe ich gezeigt, dass dies Auswirkungen auf die Unittests eines BSP-Elements hat: Der normale Aufrufrahmen für die Testklasse eines BSP-Elements enthält die Ausführung eines HTTP-Requests auf eine spezielle Testseite.&lt;br /&gt;&lt;br /&gt;In diesem Blog will ich die Konsequenzen hinsichtlich der &lt;i&gt;Erweiterbarkeit&lt;/i&gt; von BSP-Elementen nach dem &lt;a href="http://de.wikipedia.org/wiki/Open-Closed_Prinzip"&gt;Open-Closed-Prinzip&lt;/a&gt; von Bertrand Meyer betrachten:&lt;br /&gt;&lt;blockquote&gt;Modules should be both open (for extension) and closed (for modification).&lt;/blockquote&gt;Lässt sich also ein bestehendes (z.B. in der BSP-Extension eines Drittlieferanten  ausgeliefertes) BSP-Element erweitern, ohne das Element selbst modifizieren zu müssen?&lt;br /&gt;&lt;br /&gt;Die Schnittstelle eines BSP-Elements besteht nicht aus Import- und Exportparametern, sondern aus Attributen und dem Elementinhalt. In Analogie zu den domänenspezifischen Sprachen (DSL), die ja nach Martin Fowler als eine API mit einer auf den Verwender angepassten Syntax aufgefasst  werden sollten (er betrachtet DSL als "an alternative interface to a library than  the usual command-query API" [1]), können wir auch ein BSP-Element als eine &lt;i&gt;domänenspezifische Schnittstelle&lt;/i&gt; betrachten: Denn die BSP-Elemente fügen sich dank ihrer XML-Syntax nahtlos in den HTML-, XHTML- oder XML-Code ein, mit dem ein View üblicherweise formuliert wird.&lt;br /&gt;&lt;br /&gt;Ein einfaches Beispiel: Um eine HTML-Tabelle mit den drei Spalten für Position, Material und Bezeichnung aus Modeldaten,  etwa einer internen Tabelle &lt;tt&gt;gt_items&lt;/tt&gt; in einem Model &lt;tt&gt;bestellung&lt;/tt&gt; zu erzeugen, kann ich mit Vorteil das BSP-Element &lt;tt&gt;&amp;lt;z:table&amp;gt;&lt;/tt&gt; verwenden, indem ich es im View auf folgende Weise aufrufe: &lt;pre class="sh_html"&gt;&amp;lt;z:table table_id="items" binding="//bestellung/gt_items"&amp;gt;&lt;br/&gt;  &amp;lt;z:column name="posnr"&lt;br/&gt;               text="&amp;lt;=otr(z_bestellung/position&amp;gt;"&lt;br/&gt;               listPos="010"/&amp;gt;&lt;br/&gt;  &amp;lt;z:column name="matnr"&lt;br/&gt;               text="&amp;lt;=otr(z_bestellung/material&amp;gt;"&lt;br/&gt;               listPos="020"/&amp;gt;&lt;br/&gt;  &amp;lt;z:column name="arktx"&lt;br/&gt;               text="&amp;lt;=otr(z_bestellung/bezeichnung&amp;gt;"&lt;br/&gt;               listPos="030"/&amp;gt;&lt;br/&gt;&amp;lt;/z:table&amp;gt;&lt;/pre&gt;&lt;br /&gt;Das Element &lt;tt&gt;&amp;lt;z:table&amp;gt;&lt;/tt&gt; aus der &lt;a href="http://bsp.mits.ch/code/wtag/z"&gt;Tag Library Z&lt;/a&gt; meines BSP-Frameworks erzeugt bei diesem Aufruf eine HTML-Tabelle mit den angegebenen Spalten, von der Art &lt;a href="http://bsp.mits.ch/buch/ztaglib_demo/main.do?table.rearrangeColumns"&gt;dieses Beispiels&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Über das nackte Gerüst einer (mit Stylesheetklassen und ID's versehenen) HTML-Tabelle hinaus bietet die &lt;tt&gt;&amp;lt;z:table&amp;gt;&lt;/tt&gt; eine Reihe von Features, die es selten nötig machen, das Element wirklich grundsätzlich erweitern zu müssen. Einige dieser Features habe ich in meiner Artikelserie zum Tabellentag erläutert.[2] Vor allem der Beitrag über die &lt;a href="http://bsp.mits.ch/supplements/context.htm"&gt;Exits&lt;/a&gt; des Tabellen-Tags zeigt, dass mit den eingebauten Zeitpunkten bereits viele Erweiterungen im Sinne des &lt;i&gt;Open-Closed Principle&lt;/i&gt; möglich sind, d.h. ohne die Implementierung des &lt;tt&gt;&amp;lt;z:table&amp;gt;&lt;/tt&gt;-Elements ändern zu müssen.&lt;br /&gt;&lt;br /&gt;Hier will ich aber den Fall diskutieren, dass man sich die Logik des &lt;tt&gt;&amp;lt;z:table&amp;gt;&lt;/tt&gt;-Elements in einem eigenen Tabellenelement zueigen machen möchte, sagen wir &lt;tt&gt;&amp;lt;zz:table&amp;gt;&lt;/tt&gt;, indem man sie wiederverwendet - und damit den Teil des Elements, der aus der internen Tabelle die HTML-Elemente &lt;tt&gt;&amp;lt;table&amp;gt;&lt;/tt&gt;, &lt;tt&gt;&amp;lt;tr&amp;gt;&lt;/tt&gt;, &lt;tt&gt;&amp;lt;td&amp;gt;&lt;/tt&gt; usw. generiert, nicht selbst implementieren muss.&lt;br /&gt;&lt;br /&gt;Für gewöhnliche Klassen würde man in einem solchen Fall das Entwurfsmuster &lt;a href="http://de.wikipedia.org/wiki/Decorator"&gt;Dekorierer&lt;/a&gt; verwenden: Die neue Klasse enthält eine Referenz auf die bestehende Klasse in Form eines privaten Attributs und trägt (mindestens) die gleiche Schnittstelle wie die bestehende Klasse, indem sie z.B. ein Interface implementiert. Diese Implementierungen enthalten neben dem delegierenden Aufruf derselben Interfacemethode in der bestehenden Klasse noch den weiteren Code, um den sie durch die neue Klasse "angereichert" wird (daher der Name Dekorierer).&lt;br /&gt;&lt;br /&gt;Nun steckt hinter einem BSP-Element ebenfalls eine Klasse: Seine Elementbehandlerklasse. Hier lässt sich der Dekorierer-Ansatz sofort übertragen. Die gemeinsame Schnittstelle ist das Interface &lt;a href="http://help.sap.com/saphelp_nw04/helpdata/de/e3/c21d3c55a0f503e10000000a114084/frameset.htm"&gt;if_bsp_element&lt;/a&gt;. Wir benötigen also zunächst einmal in der neuen Behandlerklasse – wenn das Element &lt;tt&gt;&amp;lt;zz:table&amp;gt;&lt;/tt&gt; heisst, sollte sie &lt;tt&gt;zcl_bsp_zz_table&lt;/tt&gt; heissen – eine Referenz auf eine Instanz der bestehenden Klasse &lt;tt&gt;zcl_bsp_z_table&lt;/tt&gt;. Nennen wir diese Variable &lt;tt&gt;go_table&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;Zum Zeitpunkt &lt;tt&gt;do_at_beginning&lt;/tt&gt; (d.h. in der &lt;tt&gt;if_bsp_element&lt;/tt&gt;-Methode dieses Namens) konstruieren wir das neue Objekt mit der dafür vorgesehenen Fabrikmethode:&lt;br /&gt;&lt;pre class="sh_abap"&gt;method if_bsp_element~do_at_beginning.&lt;br/&gt;* &amp;lt;z:table&amp;gt;-Element zum Erzeugen der Tabelle&lt;br/&gt;call method zcl_bsp_z_table=&amp;gt;factory&lt;br/&gt;  exporting&lt;br/&gt;    binding            = binding&lt;br/&gt;    ...&lt;br/&gt;    zebra              = zebra&lt;br/&gt;  receiving&lt;br/&gt;    element            = go_table.&lt;br/&gt;...    &lt;br/&gt;endmethod.&lt;/pre&gt;&lt;br /&gt;Bevor wir nun an das &lt;tt&gt;&amp;lt;z:table&amp;gt;&lt;/tt&gt;-Element delegieren, müssen wir allerdings noch zwei weitere Dinge tun:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Erstens&lt;/i&gt; müssen wir den Viewkontext der BSP-Laufeit an das Delegationsobjekt übergeben:&lt;br /&gt;&lt;pre class="sh_abap"&gt;* Seitenkontext an &amp;lt;z:table&amp;gt; übergeben&lt;br/&gt;go_table-&amp;gt;m_page_context = m_page_context.&lt;/pre&gt;&lt;br /&gt;Kaum ein BSP-Element, das wirklich etwas Nichttriviales macht, wird ohne diese Zuweisung richtig funktionieren. Die aktuellen Informationen zum Seitenkontext werden an zahlreichen Stellen in der Implementierung benötigt – nicht zuletzt braucht ein BSP-Element eine aktuelle &lt;tt&gt;writer&lt;/tt&gt;-Instanz (die über den Seitenkontext zugänglich wird), um z.B. den generierten HTML-Code auszugeben.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Zweitens&lt;/i&gt; hängen wir das neu erzeugte BSP-Element, auch wenn es im View-Code nicht sichtbar ist, sondern verdeckt unter der Fassade des &lt;tt&gt;&amp;lt;zz:table&amp;gt;&lt;/tt&gt;-Elements lebt, in den aktuellen Elementstack ein (das Casting auf die Context-Klasse ist hier nötig, weil die Operationen auf dem Elementstack bewusst nicht als Teil der Schnittstelle &lt;tt&gt;if_bsp_page_context&lt;/tt&gt; entworfen wurden, sondern nur als öffentliche Methoden der implementierenden Klasse &lt;tt&gt;cl_bsp_page_context&lt;/tt&gt; verfügbar sind):&lt;br /&gt;&lt;pre class="sh_abap"&gt;  data: lo_page_context type ref to cl_bsp_page_context,&lt;br/&gt;      lo_delta0 type ref to if_bsp_delta_handler.&lt;br/&gt;&lt;br/&gt;lo_page_context ?= m_page_context.&lt;br/&gt;lo_page_context-&amp;gt;element_push( element = go_table delta_handler = lo_delta0 ).&lt;/pre&gt;&lt;br/&gt;Das hat den Vorteil, dass &lt;i&gt;innere Elemente&lt;/i&gt;, wie im obige Beispiel das Element &lt;tt&gt;&amp;lt;z:column&amp;gt;&lt;/tt&gt;, mittels der Methode &lt;tt&gt;get_class_named_parent()&lt;/tt&gt; genau wie im Standardfall mit dem übergeordneten &lt;tt&gt;&amp;lt;z:table&amp;gt;&lt;/tt&gt;-Element kommunizieren können. Würden wir das Element nicht auf den Stack legen, so würden innere Elemente i.a. nicht mehr wie vorgesehen funktionieren.&lt;br/&gt;&lt;br/&gt;Natürlich müssen wir das eingehängte Element nach Abarbeitung des erweiterten Elements auch wieder aus dem Elementstack entfernen. Das geschieht gegen Ende der Implementierung von &lt;tt&gt;do_at_end()&lt;/tt&gt;:&lt;br/&gt;&lt;pre class="sh_abap"&gt;  data: lo_page_context type ref to cl_bsp_page_context,&lt;br/&gt;      lo_delta0 type ref to if_bsp_delta_handler.&lt;br/&gt;&lt;br/&gt;lo_page_context ?= m_page_context.&lt;br/&gt;lo_page_context-&amp;gt;element_pop( element       = go_table&lt;br/&gt;                              delta_handler = lo_delta0 ).&lt;/pre&gt;&lt;br /&gt;Nun können wir wie beim Dekorierermuster verfahren und neben der Delegation neue Funktionalität hinzufügen. In der Regel wird das zu den Zeitpunkten &lt;tt&gt;do_at_beginning&lt;/tt&gt; und &lt;tt&gt;do_at_end&lt;/tt&gt; geschehen, es stehen aber natürlich auch alle anderen Methoden des Interface &lt;tt&gt;if_bsp_element&lt;/tt&gt; für Erweiterungen zur Verfügung.&lt;br /&gt;&lt;br /&gt;Ist dies gemacht, können wir das neue Element &lt;tt&gt;&amp;lt;zz:table&amp;gt;&lt;/tt&gt; genauso wie das alte aufrufen,&lt;br /&gt;aber auch zusätzliche Funktionen einfügen. Im folgenden Beispiel gibt es ein neues Attribut &lt;tt&gt;funkyAttribute&lt;/tt&gt; und ein neues inneres Element &lt;tt&gt;&amp;lt;zz:funkyInnerElement&amp;gt;&lt;/tt&gt;:&lt;br /&gt;&lt;pre class="sh_html"&gt;&amp;lt;zz:table table_id="items"&lt;br/&gt;       binding="//bestellung/gt_items"&lt;br/&gt;       funkyAttribute="dizzle"&amp;gt;&lt;br/&gt;  &lt;b&gt;&amp;lt;zz:funkyInnerElement foo="bar"/&amp;gt;&lt;/b&gt;&lt;br/&gt;  &amp;lt;z:column name="posnr"&lt;br/&gt;               text="&amp;lt;=otr(z_bestellung/position&amp;gt;"&lt;br/&gt;               listPos="010"/&amp;gt;&lt;br/&gt;  &amp;lt;z:column name="matnr"&lt;br/&gt;               text="&amp;lt;=otr(z_bestellung/material&amp;gt;"&lt;br/&gt;               listPos="010"/&amp;gt;&lt;br/&gt;  &amp;lt;z:column name="arktx"&lt;br/&gt;               text="&amp;lt;=otr(z_bestellung/bezeichnung&amp;gt;"&lt;br/&gt;               listPos="010"/&amp;gt;http://www.blogger.com/img/blank.gif&lt;br/&gt;&amp;lt;/z:table&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;[1] Interview mit Martin Fowler und Rebecca Parsons über DSL in&lt;br /&gt;Dr. Dobb's Journal (12/2010), &lt;a href="http://drdobbs.com/architecture-and-design/228200852"&gt;http://drdobbs.com/architecture-and-design/228200852&lt;/a&gt;.&lt;br /&gt;[2] &lt;a href="http://bsp.mits.ch/supplements/tableWithCSS.htm"&gt;Darstellung interner Tabellen mit CSS&lt;/a&gt;, &lt;a href="http://bsp.mits.ch/supplements/tableData.htm"&gt;Präsentation von Tabellen: die Datenquelle&lt;/a&gt; und &lt;a href="http://bsp.mits.ch/supplements/context.htm"&gt;Contexte und Tabellen-Exits&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-5447082467958123912?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/5447082467958123912/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=5447082467958123912' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/5447082467958123912'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/5447082467958123912'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/08/dekorierte-bsp-elemente.html' title='Dekorierte BSP-Elemente'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-3474882754932080255</id><published>2011-07-30T16:18:00.039+01:00</published><updated>2011-08-03T09:28:12.988+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Der Microblog</title><content type='html'>Mehr als Twitter, aber weniger als Blogspot &amp;ndash; einen solchen &lt;i&gt;Microblog&lt;/i&gt; wünschte ich mir. Nun kann er schon seit einigen Monaten unter &lt;a href="http://ruediger-plantiko.net/microblog/"&gt;http://ruediger-plantiko.net/microblog/&lt;/a&gt; betrachtet werden. Hier will ich meine Ankündigung wahrmachen, ihn kurz zu dokumentieren.&lt;br /&gt;&lt;br /&gt;An dem, was ich hier auf den "Blogspot" stelle, habe ich meist längere Zeit laboriert &amp;ndash; habe es im Geiste vorbereitet, abgewogen, das Für und Wider bedacht, es dann aufgeschrieben, korrigiert, überarbeitet, ...  &lt;br /&gt;&lt;br /&gt;Auf der anderen Seite nutze ich meinen &lt;a href="http://twitter.com/#!/rplantiko"&gt;Twitter-Account&lt;/a&gt;, um schnell zwischendurch einmal etwas in die Welt zu posaunen: Eine Mini-Zustandsmeldung, einen bedenkenswerten Artikel, den ich gerade gelesen habe, einen Link auf ein &lt;a href="http://codepad.org/users/rplantiko"&gt;Stück ausführbaren Code&lt;/a&gt;, an dem ich gerade herumexperimentiere, und so weiter. Die Beschränkung auf 140 Zeichen für eine Nachricht ist für diese Art von Zustandsmeldung gerade richtig. &lt;br /&gt;&lt;br /&gt;Aber manchmal gibt es auch Gedanken, Themen, Fragen, die sich einfach nicht in 140 Zeichen fassen lassen, sondern ein oder zwei Abschnitte Prosa erfordern, um sie klar zu vermitteln. Ich hatte das Bedürfnis, sie in kurzer Frist niederzulegen - ohne sie erst lange mit mir herumzutragen und zu einem umfangreichen Artikel zu verarbeiten. Genau für diese Zwecke erbaute ich mir meinen persönlichen Microblog. Wenn ich dort - mit einer sprechenden URL - etwas hineinstelle, steht es mir frei, diese URL zusätzlich auch noch zu twittern, um den Inhalt zu verbreiten. Die - durchaus sinnvolle - Begrenzung von Twitter ist im Microblog aufgehoben.  &lt;br /&gt;&lt;br /&gt;Meine URLs sind rein semantisch aufgebaut und folgen dem Schema "Wer - macht - was". Ein Beispiel: &lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;&lt;a href="http://ruediger-plantiko.net/microblog/?eine-bresche-schlagen"&gt;http://ruediger-plantiko.net/microblog/?eine-bresche-schlagen&lt;/a&gt;&lt;/pre&gt;Der URL kann man entnehmen, dass &lt;i&gt;Rüdiger Plantiko&lt;/i&gt; einen &lt;i&gt;Microblog&lt;/i&gt; geschrieben hat, der anscheinend das Thema &lt;i&gt;Eine Bresche schlagen&lt;/i&gt; hat. Mehr nicht. Ob der angezeigte Inhalt eine HTML-Seite ist oder einen anderen Inhaltstyp hat, ist nicht erkennbar. Auch nicht, ob für die Anzeige des Blogs CGI oder irgendeine andere Technologie verwendet wird. Statt einer künstliche Artikel-ID (etwa in der Art &lt;tt&gt;?article_id=D0F5C7&lt;/tt&gt;) nutze ich den Queryteil der URL für eine lesbare Kennzeichnung des Artikels, die nach einer unmittelbar nachvollziehbaren Regel aus der Überschrift gewonnen ist. &lt;br /&gt;&lt;br /&gt;Diese URLs werden gültig bleiben - auch wenn es mir irgendwann einmal in den Sinn kommen sollte, das dahinterliegende technische Konzept völlig zu ändern. Egal mit welcher Programmiersprache der Request bedient wird, egal ob eine rein serverseitige Generierung oder eine Mischung aus Client- und Servercode verwendet wird: Die URL kann immer für den Abruf des entsprechenden Inhalts verwendet werden.&lt;br /&gt;&lt;br /&gt;Wie aber ist der Microblog aktuell implementiert? Ich habe eine Mischung aus JavaScript und Perl in Form eines "Client-Server-Handshaking" gewählt, die sich auch schon bei früheren Aufgaben bewährt hat. Statt einer Framework-Kanone wie Prototype oder jQuery verwende ich meine eigene minimalistische JavaScript-Bibliothek &lt;a href="http://ruediger-plantiko.net/minlib/"&gt;minlib&lt;/a&gt;, vor allem da sie unkomprimiert nur etwa 10 KB benötigt, was sich günstig auf die Ladezeiten auswirkt.&lt;br /&gt;&lt;br /&gt;Egal welchen Eintrag meines Microblogs man abruft, wird zunächst eine immer gleiche HTML-Seite an den Browser übergeben, die als Rahmen dient. Nach dem Laden der Seite wird der gewünschte Text mittels eines Ajax-Requests vom Server abgeholt und in den dafür vorgesehenen Inhaltsbereich gestellt. &lt;br /&gt;&lt;br /&gt;Wenn ich einen Eintrag erstelle, wird dieser auf dem Server in einer Datei &lt;tt&gt;microblog.dat&lt;/tt&gt; eingefügt. Hier ein Beispieleintrag:&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;---&lt;br /&gt;title:Eine Bresche schlagen&lt;br /&gt;id:eine-bresche-schlagen&lt;br /&gt;timestamp:20110524205854&lt;br /&gt;content:Beim Entwickeln einer Applikation ist es eine brauchbare &lt;br /&gt;Strategie, zuerst &amp;lt;i&gt;eine Bresche bis zum Ziel zu schlagen&amp;lt;/i&gt;:&lt;br /&gt;&amp;lt;ul&gt;&lt;br /&gt;  &amp;lt;li&gt;Die für jede Applikation im jeweiligen Konzept benötigten ...&lt;/pre&gt;Wie man sieht, beginnt jeder Eintrag mit dem speziellen Trennzeichen &lt;tt&gt;---&lt;/tt&gt;, das in einer Zeile für sich stehen muss. Ein Eintrag hat vier Attribute, von denen drei in eine Zeile passen und das vierte, der eigentliche Text des Eintrags, von beliebiger Länge ist.&lt;br /&gt;&lt;br /&gt;Es hat sich bewährt, bei allen Anwendungen, die mit Ajax operieren, durchgängig mit dem &lt;a href="http://de.wikipedia.org/wiki/UTF-8"&gt;UTF8&lt;/a&gt;-Format zu arbeiten. Die Datei &lt;tt&gt;microblog.dat&lt;/tt&gt; ist daher ebenso im &lt;tt&gt;UTF8&lt;/tt&gt;-Format codiert wie die zu sendende Webseite. Sowohl in Perl (mein Provider verwendet aktuell die Version 5.8.4) als auch in JavaScript ist bei Verwendung von &lt;tt&gt;UTF8&lt;/tt&gt; kein Extra-Code für die Umwandlung der Zeichencodierung nötig.[1] &lt;br /&gt;&lt;br /&gt;Das Perl-Script kann nun anhand des Query-Strings zu verschiedenen Aktionen veranlasst werden. Der Query-String kann einerseits die ID eines Topics sein &amp;ndash; dann werden die Attribute dieses Topics im JSON-Format angeliefert:&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;&lt;a href="http://ruediger-plantiko.net/cgi-bin/microblog.pl?cassian-ueber-die-fressgier"&gt;/cgi-bin/microblog.pl?cassian-ueber-die-fressgier&lt;/a&gt;&lt;/pre&gt;bringt die Antwort:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;[&lt;br /&gt;{&lt;br /&gt;id:"cassian-ueber-die-fressgier",&lt;br /&gt;title:"Cassian über die Fressgier",&lt;br /&gt;timestamp:"20110724210423",&lt;br /&gt;content:"Der Mönch Johannes Cassian (ca. 360 - ca. 435) ..."&lt;br /&gt;}&lt;br /&gt;]&lt;/pre&gt;Die URL kann statt der ID eines Topics aber auch ein Verb enthalten, um gezielt eine bestimmte Aktion auszuführen. Mit der folgenden URL beispielsweise werden die letzten 5 Topics im JSON-Format beschafft.&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;&lt;a href="http://ruediger-plantiko.net/cgi-bin/microblog.pl?last-5"&gt;/cgi-bin/microblog.pl?last-5&lt;/a&gt;&lt;/pre&gt;Das obige Datenbeispiel zeigt, dass als Übergabeformat die Datenstruktur eines &lt;i&gt;Arrays of Hashs (AoH)&lt;/i&gt; gewählt wurde. In dieser Form lassen sich die Daten auch auf der JavaScript-Ebene leicht verarbeiten und ins HTML-Dokument einmischen.&lt;br /&gt;&lt;br /&gt;Da einem Querystring nicht auf Anhieb angesehen werden kann, ob es sich um eine ausführbare Aktion (wie &lt;tt&gt;last-5&lt;/tt&gt;) oder um eine Topic-ID (wie &lt;tt&gt;cassian-ueber-die-fressgier&lt;/tt&gt;) handelt, muss das aus Performancesicht Günstigere zuerst ausprobiert werden: Das Programm prüft zunächst, ob es sich beim Querystring eventuell um den Namen eines Unterprogramms handelt (das ist nämlich nur ein Lookup in der Symboltabelle, die nach dem Parsen des Programms durch den Perl-Interpreter sowieso im Speicher vorliegt). Nur wenn kein Unterprogramm dieses Namens gefunden wird, wird das Argument als Themen-ID interpretiert:&lt;br /&gt;&lt;pre class="sh_perl"&gt;#!/usr/bin/perl&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;&lt;br /&gt;print "Content-Type:text/plain\n\n";&lt;br /&gt;&lt;br /&gt;_do( $ENV{"QUERY_STRING"} || "get-all" );&lt;br /&gt;&lt;br /&gt;# --- Eine Aktion ausführen&lt;br /&gt;sub _do {&lt;br /&gt;  my $action = shift;&lt;br /&gt;# Ausführbare Aktion?&lt;br /&gt;  if (not _do_action( $action )) {&lt;br /&gt;# Letzter Versuch: Ist es vielleicht eine ID?  &lt;br /&gt;  _get($action);&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;Als Konvention, um Verwechslungen mit Topic-ID's zu vermeiden, lasse ich alle Aktionen = Unterprogramme in diesem Script mit einem Unterstrich beginnen. Das Unterprogramm &lt;tt&gt;_do_action&lt;/tt&gt; führt ein Unterprogramm des im Querystring angegebenen Namens aus, falls es ein solches gibt, und gibt in diesem Fall eine 1 zurück. Wenn es kein solches Unterprogramm gibt, wird 0 zurückgegeben:&lt;br /&gt;&lt;pre class="sh_perl"&gt;# --- Die übergebene Aktion ausführen&lt;br /&gt;sub _do_action {&lt;br /&gt;  my $action = shift;&lt;br /&gt;  my ($subname,$arg);&lt;br /&gt;# Ein numerisches Argument am Schluss finden, z.B. bei  "last-25"  &lt;br /&gt;  ($subname,$arg) = ($action =~ /^([\w-]+?)(?:-(\d+))?$/);&lt;br /&gt;# Hyphens sind netter lesbar als Unterstriche&lt;br /&gt;  $subname =~ s/-/_/g;&lt;br /&gt;# Ggf. führenden Unterstrich setzen&lt;br /&gt;  $subname =~ s/^(?!_)/_/;&lt;br /&gt;# Unterprogramm dynamisch aufrufen   &lt;br /&gt;  return _call( $subname, $arg );&lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;Für den dynamischen Aufruf eines Unterprogramms ist es nötig, die strenge Syntaxprüfung &lt;tt&gt;strict 'refs'&lt;/tt&gt;, die mit &lt;tt&gt;use strict;&lt;/tt&gt; normalerweise eingeschaltet ist, zu deaktivieren. Dies sollte natürlich für einen möglichst kleinen Codeabschnitt geschehen. Das war einer der Gründe, warum ich den tatsächlichen Aufruf des Unterprogramms wieder in einer eigenen Subroutine gekapselt habe.&lt;br /&gt;&lt;pre class="sh_perl"&gt;# --- Unterprogramm dynamisch aufrufen&lt;br /&gt;sub _call {&lt;br /&gt;  no strict 'refs';  &lt;br /&gt;  my ($subname,$arg) = @_;&lt;br /&gt;  if (defined &amp;{$subname}) {&lt;br /&gt;    &amp;{$subname}($arg);&lt;br /&gt;    return 1;&lt;br /&gt;    }&lt;br /&gt;  return 0;      &lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;Nun verbleiben wir mit dem Fall, dass der Querystring eine Topic-ID enthält, das heisst mit dem Top-Level-Aufruf des Unterprogramms &lt;tt&gt;_get()&lt;/tt&gt;:&lt;br /&gt;&lt;pre class="sh_perl"&gt;# --- Einen einzelnen Post über seine ID zurückgeben&lt;br /&gt;sub _get {&lt;br /&gt;  my $id = shift;&lt;br /&gt;  my $posts = _select_posts();&lt;br /&gt;  my $post = $posts-&gt;{$id};&lt;br /&gt;  print "[\n";&lt;br /&gt;  _print_post( $post ) if $post;&lt;br /&gt;  print "]\n";&lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;Wenn ich einmal mehr als ca. 500 Einträge im Microblog habe, werde ich den Zugriff auf einen einzelnen Post eventuell optimieren. Bis dahin ist es völlig ausreichend, bei jedem Zugriff die volle Datei &lt;tt&gt;microblog.dat&lt;/tt&gt; als &lt;i&gt;Hash of Hashs&lt;/i&gt; einzulesen und den Eintrag mit der gewünschten ID auszugeben, falls ein solcher existiert. &lt;br /&gt;&lt;br /&gt;Hier die Routine zum Einlesen:&lt;br /&gt;&lt;pre class="sh_perl"&gt;# --- microblog.dat einlesen und in Hash wandeln&lt;br /&gt;sub _select_posts {    &lt;br /&gt;  my ($posts, $post, $fields, $content);  &lt;br /&gt;  &lt;br /&gt;  local $/;&lt;br /&gt;  $/ = "---";&lt;br /&gt;  open BLOG, "microblog.dat" &lt;br /&gt;    or die "Kann Datei microblog.dat nicht zum Lesen öffnen";&lt;br /&gt;  foreach $post (&amp;lt;BLOG&gt;) { &lt;br /&gt;    chomp $post;&lt;br /&gt;    $fields = {};&lt;br /&gt;# Progressives Matching für die Ausdrücke der Form "Feldname:Feldwert"&lt;br /&gt;# Ausser für "content" enthalten die Feldwerte keine Zeilenumbrüche&lt;br /&gt;    while ( $post =~ /(\w+)\s*:\s*(.*?)\s*\n/g ) {&lt;br /&gt;      $fields-&gt;{$1}=$2;&lt;br /&gt;# Wenn das Feld "content" als nächstes erreicht wird, ...&lt;br /&gt;      if ($post=~ /\Gcontent:(.*)/s) {&lt;br /&gt;# dann den ganzen Rest dieses "Records" als Feldwert aufnehmen        &lt;br /&gt;        $content = $1;&lt;br /&gt;# Anführungszeichen und Umbrüche müssen maskiert werden        &lt;br /&gt;        $content =~ s/"/\\"/mg;&lt;br /&gt;        $content =~ s/\n/\\n/mg;&lt;br /&gt;        $fields-&gt;{content} = $content;&lt;br /&gt;        last;&lt;br /&gt;        } &lt;br /&gt;      }    &lt;br /&gt;    $posts-&gt;{$fields-&gt;{id}} = $fields; &lt;br /&gt;    }&lt;br /&gt;  close BLOG;&lt;br /&gt;  return $posts;  &lt;br /&gt;  }&lt;/pre&gt;Hier finden ein paar nützliche Perl-Idiome Verwendung, aufgrund derer der Code ziemlich kompakt gestaltet werden kann. Indem ich den &lt;i&gt;Record Separator&lt;/i&gt; &lt;tt&gt;$/&lt;/tt&gt;, das Trennzeichen zwischen zwei Records, das standardmässig ein Zeilenumbruch ist, für dieses Unterprogramm begrenzt auf &lt;tt&gt;---&lt;/tt&gt; setze, liefert mir das &lt;tt&gt;foreach&lt;/tt&gt; der Reihe nach die einzelnen Topics an. Pro Topic suche ich nun mittels &lt;i&gt;progressivem Matching&lt;/i&gt; nach Paaren der Form &lt;tt&gt;Name:Wert&lt;/tt&gt;, die dann in die innere Hashreferenz &lt;tt&gt;$fields&lt;/tt&gt; aufgenommen werden. Der äussere Hash verwendet danach die ID als Schlüssel und &lt;tt&gt;$fields&lt;/tt&gt; als Wert.&lt;br /&gt;&lt;br /&gt;Das Feldwert zum Namen &lt;tt&gt;content&lt;/tt&gt; wird im Gegensatz zu den vorigen nicht durch einen Zeilenumbruch beendet, sondern ist bis zum Ende des Records zu übernehmen. Ich verwende daher innerhalb des progressiven Matchings, nach Zuweisung des aktuellen Namens und Wertes, einen "vorausschauenden" zweiten regulären Ausdruck, der mit der Zusicherung &lt;tt&gt;\G&lt;/tt&gt; für das Ende des aktuellen Treffers schaut, ob der nächste Feldname wohl &lt;tt&gt;content&lt;/tt&gt; ist. In diesem Fall übernimmt der reguläre Ausdruck den ganzen Rest des Records in den Feldwert, nimmt das Feld in den &lt;tt&gt;$fields&lt;/tt&gt;-Hash auf und verlässt die Schleife mit &lt;tt&gt;last&lt;/tt&gt; (denn &lt;tt&gt;content&lt;/tt&gt; ist das letzte Feld im Record, und das muss auch so sein).&lt;br /&gt;&lt;br /&gt;Damit sollte die Wirkungsweise des serverseitigen Scripts ausreichend beschrieben sein. Wie ist dieses nun im Client eingebunden? Wie erwähnt, wird nach dem Laden der Seite ein Request an das Perl-Script abgesetzt und das Ergebnis in das HTML-Dokument eingemischt. Hier ist die zuständige &lt;tt&gt;doOnLoad()&lt;/tt&gt;-Funktion:&lt;br /&gt;&lt;pre class="sh_javascript_dom"&gt;// --- Post(s) einlesen und anzeigen&lt;br /&gt;function doOnLoad() {&lt;br /&gt;  var microblog = byId("microblog");&lt;br /&gt;  var queryPart = document.location.href.match(/(\?.+)/) ? &lt;br /&gt;                    RegExp.$1 : "";&lt;br /&gt;  doRequest( "../cgi-bin/microblog.pl"+queryPart, function() { &lt;br /&gt;    var p;&lt;br /&gt;    eval("p="+this.responseText);&lt;br /&gt;    p.each( function() {&lt;br /&gt;      microblog.appendChild(createPost(this));&lt;br /&gt;      }); &lt;br /&gt;    if (window.sh_highlightDocument) sh_highlightDocument();  &lt;br /&gt;    });&lt;br /&gt;  }&lt;/pre&gt;Die zur Ausführung des Requests verwendete Funktion &lt;tt&gt;doRequest()&lt;/tt&gt; entstammt meiner JavaScript-Bibliothek &lt;a href="http://ruediger-plantiko.net/minlib/"&gt;minlib&lt;/a&gt; (Die Verwendung ist unter diesem Link dokumentiert). Der zurückgegebenen Array of Hashs wird in einem &lt;tt&gt;each()&lt;/tt&gt;-Iterator abgearbeitet. Dabei wird aus jedem Topic mittels &lt;tt&gt;createPost()&lt;/tt&gt; ein HTML-Fragment aufgebaut, das dann in den Seitenbereich &lt;tt&gt;microblog&lt;/tt&gt; eingefügt wird. Schliesslich wird explizit noch einmal das Syntax-Highlighting aufgerufen, da der Zeitpunkt &lt;tt&gt;onload&lt;/tt&gt;, zu dem dies normalerweise erfolgt, beim Empfang der Ajax-Antwort ja schon längst verstrichen ist.&lt;br /&gt;&lt;br /&gt;Zum Aufbau des HTML-Fragments für einen Topic verwende ich die DOM-API. Es empfiehlt sich, für jedes Teil des Gesamtobjekts eine passend benannte Funktion zu definieren, das macht den Code übersichtlicher und steigert die Wiederverwendbarkeit:&lt;br /&gt;&lt;pre class="sh_javascript_dom"&gt;// --- DOM-Fragment für einen Post erzeugen&lt;br /&gt;function createPost( post ) {&lt;br /&gt;  var t = createDiv( "post", {id:post.id} );&lt;br /&gt;  createLabel( post.id, t );  &lt;br /&gt;  var title = createDiv( "title", "", t );&lt;br /&gt;  createLink( "?"+post.id, post.title, title );  &lt;br /&gt;  createDiv("content", {innerHTML:formatHTML(post.content) }, t );  &lt;br /&gt;  createDiv("timestamp", {innerHTML:getPublishedText(post.timestamp) }, t);  &lt;br /&gt;  return t;&lt;br /&gt;  } &lt;br /&gt;  &lt;br /&gt;// --- Einen &amp;lt;div&gt; erzeugen und in das Element parent einhängen&lt;br /&gt;function createDiv( className, more, parent ) {&lt;br /&gt;  var div = newElement( "div", more, parent );&lt;br /&gt;  div.className = className;&lt;br /&gt;  return div;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;// --- Einen &amp;lt;a href&gt; erzeugen und in das Element parent einhängen  &lt;br /&gt;function createLink( href, content, parent ) {&lt;br /&gt;  return newElement( "a", {href:href,innerHTML:content}, parent );&lt;br /&gt;  }     &lt;br /&gt;  &lt;br /&gt;// --- Einen Label &amp;lt;a name&gt; erzeugen und in das Element parent einhängen  &lt;br /&gt;function createLabel( name, parent ) {&lt;br /&gt;  return newElement( "a", {name:name}, parent );&lt;br /&gt;  }  &lt;br /&gt;&lt;br /&gt;// Element mit Attributen erzeugen und in das Element parent einhängen&lt;br /&gt;function newElement( elementName, more, parent ) {&lt;br /&gt;  var el = document.createElement( elementName );&lt;br /&gt;  if (more) setAttributes( el, more );&lt;br /&gt;  if (parent) parent.appendChild( el );&lt;br /&gt;  return el;&lt;br /&gt;  }&lt;/pre&gt;Die hier verwendete Funktion &lt;tt&gt;setAttributes()&lt;/tt&gt; (alle Paare des als zweites Argument übergebenen Hashs in das im ersten Argument übergebene Objekt einsetzen) entstammt meiner Bibliothek &lt;a href="http://ruediger-plantiko.net/minlib/"&gt;minlib&lt;/a&gt; &amp;ndash; im übrigen wird auf dieser Ebene nur noch die DOM-API verwendet.  &lt;br /&gt;&lt;br /&gt;Bevor der Text in die Seite gesetzt wird, habe ich mir in der Funktion &lt;tt&gt;formatHTML&lt;/tt&gt; noch die Gelegenheit gegeben, ihn nach bestimmten Regeln zu überarbeiten. So möchte ich beispielsweise, dass Wörter, die mit &lt;tt&gt;http://...&lt;/tt&gt; anfangen, automatisch als Links gesetzt werden. Ausserdem sollen Absätze im Text als HTML-Absätze mit dem Element &lt;tt&gt;&amp;lt;p&gt;&lt;/tt&gt; gerendert werden:&lt;br /&gt;&lt;pre class="sh_javascript_dom"&gt;// --- HTML-Code eines Post-Contents überarbeiten&lt;br /&gt;function formatHTML( content ) {&lt;br /&gt;  return content.&lt;br /&gt;    replace(/\n\n/g,"&amp;lt;p&gt;").&lt;br /&gt;    replace( /(\s+|^|&gt;)(https?:\/\/[^\s&lt;]+)/g, '$1&amp;lt;a href="$2"&gt;$2&amp;lt;/a&gt;' );&lt;br /&gt;  }&lt;/pre&gt;Weitere Formatierungen sind mir nicht eingefallen. Sollte mir noch etwas in den Sinn kommen, wäre dies die Stelle, um es einzufügen.&lt;br /&gt;&lt;br /&gt;Ich sehe nur einen Nachteil dieser Lösung: Menschen, die mit einem Textbrowser wie &lt;tt&gt;lynx&lt;/tt&gt; im Internet unterwegs sind, werden die Inhalte des Microblogs nicht zu sehen bekommen. Das liegt natürlich daran, dass clientseitige Logik zur Datenbeschaffung verwendet wird. Das Problem liesse sich lösen, indem die Logik komplett auf die Serverseite verlagert wird. Ich erlaube mir aber, diesen Punkt bis auf weiteres zu ignorieren &amp;ndash; Menschen, die mit einem Textbrowser unterwegs sind, dürften nicht nur an meinem Microblog, sondern überhaupt am Internet wenig Freude haben.[2]&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Umgekehrt ist es aber nicht ohne grosse Klimmzüge möglich, Ajax-Requests beispielsweise in der Latin1-Codierung zu versenden und entgegenzunehmen - selbst wenn die umgebende HTML-Seite Latin1-codiert ist.&lt;br /&gt;[2] Womit ich nicht sage, dass Textbrowser grundsätzlich unnütz sind. Sie können sinnvoll sein, um automatisierte Anfragen im Web zu erstellen, z.B. bei einem Wetterdienst, der seine Prognosen immer in einem wohlbestimten Format liefert. Aber auch dafür verwendet man wohl besser eingebaute Funktionen, in Perl zum Beispiel &lt;tt&gt;LWP::Simple&lt;/tt&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-3474882754932080255?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/3474882754932080255/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=3474882754932080255' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/3474882754932080255'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/3474882754932080255'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/07/der-microblog.html' title='Der Microblog'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-929382115944940425</id><published>2011-07-25T21:13:00.022+01:00</published><updated>2011-08-10T08:57:13.890+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>ABAP Unit Tests für BSP-Elemente</title><content type='html'>Unit Tests sollten möglichst wenig Abhängigkeiten von der Aussenwelt aufweisen. Sie sollten sich darauf beschränken, eine isolierte Softwarekomponente aufzurufen und die erwarteten Ergebnisse zu prüfen.&lt;br /&gt;&lt;br /&gt;Soweit die Theorie. Wie ist diese auf ein &lt;i&gt;BSP-Element&lt;/i&gt; anzuwenden? Wir werden sehen, dass in diesem Fall Abstriche beim Prinzip der &lt;i&gt;Isoliertheit&lt;/i&gt; von Modultests gemacht werden müssen &amp;ndash; nach dem Motto "Keine Regel ohne Ausnahme". Ausnahmen erfordern allerdings eine Begründung.&lt;br /&gt;&lt;br /&gt;Ein BSP-Element wird in einem View aufgerufen und produziert HTML-Code (in der Regel). Um diesen Aufruf in einem automatischen Test durchzuführen, muss die nötige Infrastruktur bereitgestellt werden: Der View kann sich nur in einer BSP-Applikation befinden. Diese muss durch die BSP-Runtime aufgerufen werden. Der Test muss also in irgendeiner Form in der Lage sein, einen HTTP-Request auszuführen und das Ergebnis beispielsweise in Form eines Strings entgegenzunehmen, um es gegen das erwartete Ergebnis zu prüfen. &lt;br /&gt;&lt;br /&gt;Das ist die einzige Möglichkeit, BSP-Elemente über ihre "öffentliche Schnittstelle" zu testen, denn die öffentliche Schnittstelle besteht in diesem Fall nicht aus einem &lt;i&gt;Interface&lt;/i&gt; im technischen Sinne: Obwohl alle BSP-Elementbehandlerklassen das Interface &lt;tt&gt;IF_BSP_ELEMENT&lt;/tt&gt; implementieren, kann es für den Testaufruf ebensowenig dienen wie irgendeine andere öffentliche Methode. Damit ein Element korrekt arbeitet, muss nämlich ein aktueller Seitenkontext bereitstehen - &lt;i&gt;mindestens&lt;/i&gt; in Form einer Referenz auf einen View und einer HTTP-Response-Instanz. &lt;br /&gt;&lt;br /&gt;Die vollständige Liste aller benötigten Zutaten verbirgt sich aber in den Details der BSP-Runtime. Es wäre also eine schlechte Strategie, für alle Hilfs- und Kontextobjekte, von denen wir feststellen, dass sie benötigt werden, Stub-Objekte bereitzustellen. Mit dieser Strategie würden wir uns von den Implementierungsdetails der BSP-Runtime abhängig machen.&lt;br /&gt;&lt;br /&gt;Die natürliche öffentliche Schnittstelle eines BSP-Elements ist ihr Aufruf in einem BSP-View. Der natürliche "Rückgabewert" eines solchen Aufrufs ist HTML-Code.&lt;br /&gt;&lt;br /&gt;Der Unit Test eines BSP-Elements muss also einen HTTP-Request ausführen, um eine Testseite aufzurufen, die ausser dem BSP-Element selbst gar keinen oder nur wenig Code enthält. Der Test ist somit nicht mehr wirklich isoliert. Es werden Hilfsklassen, sogar eine Hilfs-BSP benötigt, und die Basisklassen für die Durchführung von HTTP-Requests müssen aufgerufen werden, so dass sehr viel mehr Code mitläuft als nur der Code der zu testenden Elementbehandlerklasse. &lt;br /&gt;&lt;br /&gt;Aber: Die Verletzung des "Isoliertheits"-Prinzips für Modultests ist im Fall der BSP-Elemente wirklich notwendig, um das Element über seine öffentliche Schnittstelle, also in seinem normalen Laufzeit-Einsatz zu beobachten.&lt;br /&gt;&lt;br /&gt;Wie macht man das nun konkret?&lt;br /&gt;&lt;br /&gt;Es empfiehlt sich, alle Modultestklassen von einer gemeinsamen (lokalen) Oberklasse erben zu lassen. In dieser Oberklasse implementiert man die Ausführung des HTTP-Requests, denn dieser Code wird in allen Modultestklassen benötigt werden, gehört also "nach oben". &lt;br /&gt;&lt;br /&gt;In der Modultestklasse kann man dann den Request ausführen, indem man die dafür zuständige Methode der Oberklasse aufruft: frühestens in der Methode &lt;tt&gt;CLASS_SETUP&lt;/tt&gt; und spätestens in der einzelnen Testmethode selbst (wenn der Request nur dort gebraucht wird). Die Ausführung des Requests erfolgt mit der Klasse &lt;tt&gt;CL_HTTP_CLIENT&lt;/tt&gt;. &lt;br /&gt;&lt;br /&gt;Die folgende Abbildung zeigt einen typischen Callstack, der dabei entsteht:&lt;br /&gt;&lt;br /&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;" src="http://3.bp.blogspot.com/-IzXUMkAiSos/Ti3dFZWivlI/AAAAAAAAALk/AC2KF2n-hDU/s1600/bsp_element_callstack.png" alt="Callstack eines HTTP-Requests im Unittest eines BSP-Elements" id="BLOGGER_PHOTO_ID_5633401793684880978" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Der Callstack eines beliebigen Modultests beginnt immer mit dem Basisreport &lt;tt&gt;RS_AUNIT_CLASSTEST_SESSION&lt;/tt&gt;. Dieser wird vom Modultestframework per &lt;tt&gt;SUBMIT&lt;/tt&gt; aufgerufen: So ist sichergestellt, dass jeder Test in einem eigenen internen Modus läuft, so dass er gegenüber der Laufzeit und gegenüber anderen Tests besser gekapselt ist. &lt;br /&gt;&lt;li&gt;In diesem Beispiel wird der HTTP-Request bereits beim &lt;tt&gt;CLASS_SETUP&lt;/tt&gt; ausgeführt, der auf Ebene 11 aufgerufen wird.&lt;br /&gt;&lt;li&gt;Der &lt;tt&gt;CLASS_SETUP&lt;/tt&gt; ruft daher in der allen Modultestklassen gemeinsamen Oberklasse &lt;tt&gt;LCL_AU_ALL&lt;/tt&gt; die Methode &lt;tt&gt;RENDER_VIEW&lt;/tt&gt; auf (Ebene 12).&lt;br /&gt;&lt;li&gt;Ebene 13 ist die Utility-Methode &lt;tt&gt;zcl_http_util-&gt;do_request&lt;/tt&gt;, die einen vereinfachten Zugriff auf das HTTP-Client-Objekt &lt;tt&gt;cl_http_client&lt;/tt&gt; durchführt.&lt;br /&gt;&lt;li&gt;&lt;i&gt;Jeder&lt;/i&gt; HTTP-Request an den ABAP-Stack eines SAP-Systems durchläuft den Funktionsbaustein &lt;tt&gt;HTTP_DISPATCH_REQUEST&lt;/tt&gt; (hier auf Ebene 16).&lt;br /&gt;&lt;li&gt;Das ICF stellt fest, dass ein BSP-Controller aufzurufen ist, was über den Behandler &lt;tt&gt;CL_HTTP_EXT_BSP&lt;/tt&gt; gemacht wird: Ebene 18.&lt;br /&gt;&lt;li&gt;Das BSP-Framework ruft den zuständigen Controller auf, in dessen &lt;tt&gt;DO_REQUEST&lt;/tt&gt;-Implementierung (Ebene 21)...&lt;br /&gt;&lt;li&gt;... dann die Testseite aufgerufen wird (Ebene 26).&lt;br /&gt;&lt;li&gt;In dieser Seite befindet sich das hier zu testende &amp;lt;z:table&amp;gt;-Tag. Dessen &lt;tt&gt;DO_AT_END()&lt;/tt&gt;-Methode enthält schliesslich den zu testenden Code (Ebene 29)! &lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Interessant an diesem Callstack ist noch, dass Aufruf des HTTP-Requests und seine Behandlung im selben Callstack stattfinden. Das ist nicht normal: Normalerweise würde ein eingehender HTTP-Request über einen ganz eigenen Prozess vom System abgearbeitet. In diesem Fall aber wird der Funktionsbaustein &lt;tt&gt;HTTP_DISPATCH_REQUEST&lt;/tt&gt; &lt;i&gt;direkt&lt;/i&gt; aufgerufen. Um dies zu erreichen, haben wir das HTTP-Clientobjekt mit der dafür vorgesehenen Methode &lt;tt&gt;cl_http_client=&gt;create_internal&lt;/tt&gt; erzeugt. Der Vorteil ist, dass man bei Bedarf vor oder nach Ausführung des Requests im Test auf öffentliche statische Komponenten des zu testenden Objekts zugreifen kann. Das kann manchmal nützlich sein.&lt;br /&gt;&lt;br /&gt;Der Preis für den internen HTTP-Request ist, dass pro Testklasse nur ein Request auf einen BSP-Controller ausgeführt werden kann: Die klasse &lt;tt&gt;CL_BSP_RUNTIME&lt;/tt&gt; merkt sich nämlich in einer statischen Variablen einige Objekte des aktuellen Kontexts, z.B. die Controllerinstanz, und baut diese nach Abarbeitung des Requests nicht ab. Erfolgt im selben Modus ein zweiter Request, wird die Controllerinstanz des letzten Aufrufs verwendet, was zu unerwarteten Ergebnissen führt. &lt;br /&gt;&lt;br /&gt;Die Lösung dafür ist: Man organisiere seine Tests so, dass pro Modultestklasse genau ein HTTP-Request ausgeführt wird! Das Ergebnis, beispielsweise in Form eines Instanzattributs &lt;tt&gt;gv_html&lt;/tt&gt;, kann dann von den einzelnen Testmethoden nach verschiedenen Gesichtspunkten geprüft werden.&lt;br /&gt;&lt;br /&gt;Trotz des beeindruckend grossen Callstacks bei der Ausführung kann der eigentliche Testcode des Unittests durchaus kompakt und lesbar bleiben. Um dies zu demonstrieren, sei hier eine Testmethode gezeigt, die verifiziert, dass das vom Element &lt;tt&gt;&amp;lt;z:table&gt;&lt;/tt&gt; gerenderte HTML-Fragment eine HTML-Table &lt;tt&gt;&amp;lt;table&gt;&lt;/tt&gt; mit der übergebenen ID ist (in weiteren Methoden derselben Testklasse wird dann der Inhalt dieser &lt;tt&gt;&amp;lt;table&gt;&lt;/tt&gt; geprüft:&lt;br /&gt;&lt;pre class="sh_abap"&gt;  method check_table_id.&lt;br/&gt;    data: lo_root     type ref to if_ixml_element,&lt;br/&gt;          lv_name     type string,&lt;br/&gt;          lv_id       type string.&lt;br/&gt;* &amp;lt;table id="test"&gt;&lt;br/&gt;    lo_root = go_document-&gt;get_root_element( ).&lt;br/&gt;    lv_name = lo_root-&gt;get_name( ).&lt;br/&gt;    assert_equals( exp = 'table'&lt;br/&gt;                   act = lv_name&lt;br/&gt;                   msg = 'Root-Element ist nicht &amp;lt;table&gt;' ).&lt;br/&gt;    lv_id   = lo_root-&gt;get_attribute( 'id' ).&lt;br/&gt;    assert_equals( exp = 'test'&lt;br/&gt;                   act = lv_id&lt;br/&gt;                   msg = 'Tabellen-ID ist nicht ''test''' ).&lt;br/&gt;  endmethod.                    "check_table_id&lt;/pre&gt;Da das vom Element &lt;tt&gt;&amp;lt;z:table&gt;&lt;/tt&gt; erzeugte HTML-Codefragment nach XML-Syntax wohlgeformt ist (nähere Infos zum erzeugten HTML-Code des &amp;lt;z:table&gt;-Tags enthält mein Artikel &lt;a href="http://bsp.mits.ch/supplements/tableWithCSS.htm"&gt;Präsentation von Tabellen mit CSS&lt;/a&gt;), parse ich es mit Vorteil in ein Objekt &lt;tt&gt;go_document&lt;/tt&gt; vom Typ &lt;tt&gt;ref to if_ixml_document&lt;/tt&gt; und kann dieses dann in den Testmethoden mit den XML-DOM-Zugriffsfunktionen untersuchen.&lt;br /&gt;&lt;br /&gt;Ergänzend möchte ich hier noch den immer gleichen Code zeigen, der zur Ausführung eines HTTP-Requests benötigt wird. Ich habe ihn in die Methode &lt;tt&gt;do_request&lt;/tt&gt; einer Utility-Klasse &lt;tt&gt;zcl_http_util&lt;/tt&gt; gepackt, deren vollständiger Code auch unter &lt;a href="http://bsp.mits.ch/code/clas/zcl_http_util"&gt;http://bsp.mits.ch/code/clas/zcl_http_util&lt;/a&gt; betrachtet werden kann. Diese Klasse wird natürlich nicht nur für den hier diskutierten Fall verwendet, sondern überall im ganzen System, wenn es darum geht, vom SAP-System aus HTTP-Requests auszuführen (an welches System auch immer diese gerichtet sind). &lt;br /&gt;&lt;br /&gt;Wie man sieht, lasse ich dem Aufrufer neben der Ausführung des Requests im eigenen Callstack auch die Option eines "normalen" Requests, der für URLs ohne Host und Port (deren erstes Zeichen also ein '/' ist) über die eingebaute HTTP-Destination &lt;tt&gt;'NONE'&lt;/tt&gt; an das eigene System gerichtet wird. Alle anderen Requests richten sich wirklich an einen anderen Server.&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;method do_request .&lt;br/&gt;&lt;br/&gt;  data: lv_text   type string,&lt;br/&gt;        lv_url    type string.&lt;br/&gt;&lt;br/&gt;* Einen HTTP-Request ausführen&lt;br/&gt;  lv_url = iv_url.&lt;br/&gt;  if iv_internal eq 'X'.&lt;br/&gt;* Request geht auf diesen Server - dann wird gleich der FB&lt;br/&gt;* HTTP_DISPATCH_REQUEST aufgerufen (im selben Callstack!)&lt;br/&gt;* Achtung: Das Attribut server-&gt;request wird überschrieben,&lt;br/&gt;* da die aktuelle server-Instanz wiederverwendet wird!&lt;br/&gt;* Daher iv_internal = 'X' nur verwenden,&lt;br/&gt;* wenn man nicht bereits in einer Requestbearbeitung ist.&lt;br/&gt;    cl_http_client=&gt;create_internal(&lt;br/&gt;      importing client = go_client ).&lt;br/&gt;    cl_http_utility=&gt;set_request_uri( request = go_client-&gt;request&lt;br/&gt;                                      uri     = lv_url ).&lt;br/&gt;  else.&lt;br/&gt;    if iv_reuse_client_object eq space or&lt;br/&gt;       go_client is initial.&lt;br/&gt;      if lv_url(1) = '/'.&lt;br/&gt;* URL = Pfad bedeutet, Request geht auf diesen Server&lt;br/&gt;* iv_internal = space: Request mit eigenem Callstack absetzen&lt;br/&gt;        call method cl_http_client=&gt;create_by_destination&lt;br/&gt;          exporting&lt;br/&gt;            destination              = 'NONE'&lt;br/&gt;          importing&lt;br/&gt;            client                   = go_client&lt;br/&gt;          exceptions&lt;br/&gt;            argument_not_found       = 1&lt;br/&gt;            destination_not_found    = 2&lt;br/&gt;            destination_no_authority = 3&lt;br/&gt;            plugin_not_active        = 4&lt;br/&gt;            internal_error           = 5&lt;br/&gt;            others                   = 6.&lt;br/&gt;        if sy-subrc &lt;&gt; 0.&lt;br/&gt;          message id sy-msgid type sy-msgty number sy-msgno&lt;br/&gt;            with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4&lt;br/&gt;            raising http_error.&lt;br/&gt;        endif.&lt;br/&gt;        cl_http_utility=&gt;set_request_uri( request = go_client-&gt;request&lt;br/&gt;                                          uri     = lv_url ).&lt;br/&gt;      else.&lt;br/&gt;* Normalfall: Externer HTTP-Request&lt;br/&gt;        call method cl_http_client=&gt;create_by_url&lt;br/&gt;          exporting&lt;br/&gt;            url                = lv_url&lt;br/&gt;          importing&lt;br/&gt;            client             = go_client&lt;br/&gt;          exceptions&lt;br/&gt;            argument_not_found = 1&lt;br/&gt;            plugin_not_active  = 2&lt;br/&gt;            internal_error     = 3&lt;br/&gt;            others             = 4.&lt;br/&gt;        if sy-subrc ne 0.&lt;br/&gt;          message id sy-msgid type sy-msgty number sy-msgno&lt;br/&gt;            with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4&lt;br/&gt;            raising http_error.&lt;br/&gt;        endif.&lt;br/&gt;      endif.&lt;br/&gt;    else.&lt;br/&gt;      cl_http_utility=&gt;set_request_uri( request = go_client-&gt;request&lt;br/&gt;                                        uri     = lv_url ).&lt;br/&gt;    endif.&lt;br/&gt;  endif.&lt;br/&gt;&lt;br/&gt;* CDATA angegeben? Dann mit senden&lt;br/&gt;  if iv_cdata is not initial.&lt;br/&gt;    go_client-&gt;request-&gt;set_cdata( iv_cdata ).&lt;br/&gt;  endif.&lt;br/&gt;&lt;br/&gt;* Weitere spezifische Anreichungen des Request-Objekts?&lt;br/&gt;  raise event prepare_request&lt;br/&gt;    exporting&lt;br/&gt;      eo_request = go_client-&gt;request.&lt;br/&gt;&lt;br/&gt;* Request absenden&lt;br/&gt;  call method go_client-&gt;send&lt;br/&gt;    exceptions&lt;br/&gt;      http_communication_failure = 1&lt;br/&gt;      http_invalid_state         = 2&lt;br/&gt;      http_processing_failed     = 3&lt;br/&gt;      others                     = 4.&lt;br/&gt;  if sy-subrc ne 0.&lt;br/&gt;    go_client-&gt;get_last_error( importing message = lv_text ).&lt;br/&gt;    message lv_text type 'I'&lt;br/&gt;      raising http_error.&lt;br/&gt;  endif.&lt;br/&gt;&lt;br/&gt;  go_client-&gt;receive( exceptions others = 1 ).&lt;br/&gt;  if sy-subrc ne 0.&lt;br/&gt;    go_client-&gt;get_last_error( importing message = lv_text ).&lt;br/&gt;    message lv_text type 'I'&lt;br/&gt;      raising http_error.&lt;br/&gt;  endif.&lt;br/&gt;&lt;br/&gt;  ev_html = go_client-&gt;response-&gt;get_cdata( ).&lt;br/&gt;&lt;br/&gt;* Aus Kompatibilitätsgründen muss et_html angeboten werden&lt;br/&gt;  if et_html is requested.&lt;br/&gt;    call function 'Z_SPLIT_STRING_TO_TABLE'&lt;br/&gt;      exporting&lt;br/&gt;        iv_string = ev_html&lt;br/&gt;      importing&lt;br/&gt;        et_table  = et_html.&lt;br/&gt;  endif.&lt;br/&gt;&lt;br/&gt;* Ggf. Verbindung schliessen&lt;br/&gt;  if iv_reuse_client_object eq space.&lt;br/&gt;    go_client-&gt;close( ).&lt;br/&gt;    clear go_client.&lt;br/&gt;  endif.&lt;br/&gt;&lt;br/&gt;endmethod.&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-929382115944940425?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/929382115944940425/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=929382115944940425' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/929382115944940425'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/929382115944940425'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/07/abap-unit-tests-fur-bsp-elemente.html' title='ABAP Unit Tests für BSP-Elemente'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-IzXUMkAiSos/Ti3dFZWivlI/AAAAAAAAALk/AC2KF2n-hDU/s72-c/bsp_element_callstack.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-2922930888612224734</id><published>2011-05-13T19:10:00.025+01:00</published><updated>2011-05-17T07:56:29.218+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Resultatbaumfragmente sind keine Knotenmengen!</title><content type='html'>XML ist eine Auszeichnungssprache für strukturierte Daten. XSLT ist eine Programmiersprache zur Transformation von XML-Dokumenten. Wenn wir also ein XSLT-Programm schreiben und darin strukturierte Daten benötigen (in einer Situation, in der man in anderen Programmiersprachen eine auf Hashs, Arrays und Records aufgebaute Datenstruktur verwenden würde), liegt es nahe, auch diese Datenstruktur in XML-Form zu notieren. &lt;br /&gt;&lt;br /&gt;Als triviales, illustrierendes Beispiel &amp;ndash; ohne jeden praktischen Nutzen &amp;ndash; nehmen wir an, wir wollen eine Übersetzung von Zahlwörtern in die Zahlen implementieren, für die sie stehen. Dann könnten wir mit einer Variablen &lt;tt&gt;$dict&lt;/tt&gt; arbeiten, die einen XML-Baum als Inhalt hat:&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;xsl:variable name="dict"&gt;&lt;br /&gt;  &amp;lt;one&gt;1&amp;lt;/one&gt;&lt;br /&gt;  &amp;lt;two&gt;2&amp;lt;/two&gt;&lt;br /&gt;  &amp;lt;three&gt;3&amp;lt;/three&gt;&lt;br /&gt;&amp;lt;/xsl:variable&gt;&lt;/pre&gt;&lt;br /&gt;Wir könnten versucht sein, den Zugriff auf diese Variable mit einem &lt;tt&gt;XPath&lt;/tt&gt;-Ausdruck auszuführen: Wenn ein Parameter &lt;tt&gt;$key&lt;/tt&gt; ein Zahlwort enthält, könnten wir probieren, die zugeordnete Zahl mit dem Ausdruck &lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;xsl:value-of select="$dict/*[name(.)=$key]"/&gt;&lt;/pre&gt;&lt;br /&gt;zu ermitteln. Das sieht so aus, als könnte es funktionieren. Es funktioniert aber nur für einige XSLT-Prozessoren, nämlich im Internet Explorer und in ABAP. &lt;br /&gt;&lt;br /&gt;Mehr noch: Gemäss XSLT-Spezifikation muss es auch gar nicht funktionieren! Denn der Inhalt einer &lt;tt&gt;&amp;lt;xsl:variable&gt;&lt;/tt&gt;-Anweisung evaluiert nicht zu einer &lt;i&gt;Knotenmenge&lt;/i&gt; (worauf sich ein XPath-Ausdruck anwenden liesse), sondern zu einem &lt;i&gt;Resultatbaumfragment&lt;/i&gt; (worauf sich ein XPath-Ausdruck &lt;i&gt;nicht&lt;/i&gt; anwenden lässt). &lt;br /&gt;&lt;br /&gt;Der folgende Code, von dem man erwarten könnte, dass er eine &lt;tt&gt;2&lt;/tt&gt; ausgibt, &lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;xsl:variable name="key" select="'two'"/&gt;&lt;br /&gt;&amp;lt;xsl:value-of select="$dict/*[name(.)=$key]"/&gt;&lt;/pre&gt;&lt;br /&gt;liefert beispielsweise auf dem in Java eingebauten Xalan-Prozessor nicht eine '2', sondern die nicht ganz klar nachvollziehbare Fehlermeldung &lt;blockquote&gt;&lt;em&gt;Fehler beim Überprüfen des Typs des Ausdrucks 'FilterParentPath(variable-ref(dict/result-tree), step("child", 1, pred(=(funcall(name, [step("self", -1)]), variable-ref(key/string)))))'.&lt;/em&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Um dieses Problem zu lösen, gibt es eine Erweiterungsfunktion: Die unter dem Namen &lt;tt&gt;exslt&lt;/tt&gt; (mit dem Namensraum-URI &lt;tt&gt;http://exslt.org/common&lt;/tt&gt;) zusammengefassten Erweiterungen enthalten eine Funktion namens &lt;tt&gt;node-set( )&lt;/tt&gt;, die ein Resultatbaumfragment in eine Knotenmenge konvertieren kann. Das obige Beispiel funktioniert also in Xalan, wenn wir es leicht modifizieren:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;xsl:variable name="key" select="'two'"/&gt;&lt;br /&gt;&amp;lt;xsl:value-of select="exslt:node-set($dict)/*[name(.)=$key]"/&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Das ist schön für Xalan, Google Chrome, Firefox und viele andere. Nicht so schön aber für Prozessoren, die die Erweiterungen von &lt;tt&gt;exslt&lt;/tt&gt; nicht kennen, wozu der XSLT-Prozessor von ABAP und der des Microsoft Internet Explorers gehören.&lt;br /&gt;&lt;br /&gt;Wenn es nur darum geht, auch noch Microsofts IE in den Kreis der Prozessoren aufzunehmen, die unser XSLT-Programm ausführen sollen, gibt es einen &lt;a href="http://dpcarlisle.blogspot.com/2007/05/exslt-node-set-function.html "&gt;schönen Trick von David Carlisle&lt;/a&gt;. Er definiert die Funktion &lt;tt&gt;exslt:node-set()&lt;/tt&gt; im Erweiterungsraum &lt;tt&gt;msxml&lt;/tt&gt; von Microsoft:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;msxsl:script language="JScript" implements-prefix="exslt"&gt;&lt;br /&gt;  this['node-set'] =  function (x) {&lt;br /&gt;    return x;&lt;br /&gt;    }&lt;br /&gt;&amp;lt;/msxsl:script&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Dabei macht er sich zunutze, dass die Variable &lt;tt&gt;$dict&lt;/tt&gt; bei Microsoft ja bereits eine Knotenmenge &lt;i&gt;ist&lt;/i&gt;. Daher ist die Implementierung von &lt;tt&gt;exslt:node-set()&lt;/tt&gt; für diesen Prozessor einfach die identische Funktion.&lt;br /&gt;&lt;br /&gt;Ein Trick, der aber leider nicht auf den ABAP-Prozessor anwendbar ist. Wenn wir eine Lösung für alle aufgeführten XSLT-Prozessoren suchen (die Prozessoren von Firefox, MSIE, ABAP und Java (Xalan) ), führt ein anderer Weg zum Erfolg, der leider nicht ganz so kurz ist: Wir packen für den Anfang unsere Beispielaufgabe in ein mit zwei Parametern versehenes Template: Der Parameter &lt;tt&gt;$dict&lt;/tt&gt; enthält unsere strukturierten Daten als Knotenmenge. Der zweite Parameter &lt;tt&gt;$key&lt;/tt&gt; ist ein String: Der Name des Elements, das wir suchen. Das Template stellt den Inhalt dieses Elements ins Ergebnis:&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;xsl:template name="_getValue"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="dict"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="key"/&gt;&lt;br /&gt;  &amp;lt;xsl:value-of select="$dict/*[local-name(.) = $key] "/&gt;&lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;br /&gt;&lt;/pre&gt;    &lt;br /&gt;Statt nun dieses Template direkt aufzurufen, rufen wir es indirekt über einen Wrapper auf, der den Parameter &lt;tt&gt;$dict&lt;/tt&gt; je nach Prozessor zunächst mit &lt;tt&gt;exslt:node-set()&lt;/tt&gt; in eine Knotenmenge verwandelt oder ihn direkt durchreicht (weil er schon eine Knotenmenge &lt;i&gt;ist&lt;/i&gt;). Um prozessorabhängig festzustellen, welcher Fall vorliegt, können wir die Standardfunktion &lt;tt&gt;function-available()&lt;/tt&gt; verwenden:&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;xsl:template name="getValue"&gt;&lt;br /&gt;  &amp;lt;xsl:param name="dict"/&gt;&lt;br /&gt;  &amp;lt;xsl:param name="key"/&gt;&lt;br /&gt;  &amp;lt;xsl:choose&gt; &lt;br /&gt;    &amp;lt;xsl:when test="function-available('exslt:node-set')"&gt;&lt;br /&gt;      &amp;lt;xsl:call-template name="_getValue"&gt;&lt;br /&gt;        &amp;lt;xsl:with-param name="dict" select="exslt:node-set($dict)"/&gt;&lt;br /&gt;        &amp;lt;xsl:with-param name="key" select="$key"/&gt;&lt;br /&gt;      &amp;lt;/xsl:call-template&gt;&lt;br /&gt;    &amp;lt;/xsl:when&gt;&lt;br /&gt;    &amp;lt;xsl:otherwise&gt;&lt;br /&gt;      &amp;lt;xsl:call-template name="_getValue"&gt;&lt;br /&gt;        &amp;lt;xsl:with-param name="dict" select="$dict"/&gt;&lt;br /&gt;        &amp;lt;xsl:with-param name="key" select="$key"/&gt;&lt;br /&gt;      &amp;lt;/xsl:call-template&gt;&lt;br /&gt;    &amp;lt;/xsl:otherwise&gt;&lt;br /&gt;  &amp;lt;/xsl:choose&gt;    &lt;br /&gt;&amp;lt;/xsl:template&gt;&lt;/pre&gt;&lt;br /&gt;Dieses einhüllende Template ist also die Zwischenschicht, die von den konkreten Prozessorimplementierungen abstrahiert. Die implizite Annahme dieses Templates ist, dass ein Prozessor, der die Funktion &lt;tt&gt;exslt:node-set&lt;/tt&gt; nicht kennt, einen Variableninhalt ohne weiteres Zutun als Knotenmenge behandeln kann. Für alle von mir betrachteten Prozessoren ist das der Fall.&lt;br /&gt;&lt;br /&gt;Der folgende Aufruf des Templates stellt also die Zahl &lt;b&gt;3&lt;/b&gt; in die Ausgabe - sowohl wenn er auf dem Web Application Server als auch wenn es in einem Browser durchlaufen wird (und darum ging es mir):&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;xsl:call-template name="getValue"&gt;&lt;br /&gt;  &amp;lt;xsl:with-param name="dict" select="$dict"/&gt;&lt;br /&gt;  &amp;lt;xsl:with-param name="key" select="'three'"/&gt;      &lt;br /&gt;&amp;lt;/xsl:call-template&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Das Zwischen-Template kann man sich übrigens nicht ersparen. Eine direkte, bedingte Verwendung, je nach Fall von &lt;tt&gt;$dict&lt;/tt&gt; oder &lt;tt&gt;exslt:node-set($dict)&lt;/tt&gt; führt zu Fehlermeldungen: sobald der Prozessor herausfindet, dass die Variable &lt;tt&gt;$dict&lt;/tt&gt; ein Resultatbaumfragment enthält, lehnt er es ab, sie in einem XPath-Ausdruck als Node-Set zu verwenden, auch wenn dieser Ausführungszweig ihn zur Laufzeit gar nicht betreffen wird. Nur durch den Templateaufruf wird eine Zwischenebene geschaffen: das Wissen um den Typ des Aktualparameters geht beim Abstieg im Callstack gewissermassen verloren, es darf bei der Prüfung des Templates selbst keine Rolle spielen. Nur dieser Tatsache ist es zu verdanken, dass die hier vorgestellte Lösung funktioniert.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-2922930888612224734?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/2922930888612224734/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=2922930888612224734' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/2922930888612224734'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/2922930888612224734'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/05/resultatbaumfragmente-sind-keine.html' title='Resultatbaumfragmente sind keine Knotenmengen!'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-645904598358969902</id><published>2011-04-20T20:33:00.013+01:00</published><updated>2011-05-05T19:03:26.751+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Betrachtungen'/><title type='text'>Das Geschenk der Freiheit</title><content type='html'>&lt;blockquote&gt;Dreissig Speichen umgeben eine Nabe.&lt;br /&gt;In ihrem Nichts besteht des Wagens Werk. &lt;br /&gt;&lt;i&gt;LaoTse&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Die Welt, in der wir leben, ist eigentlich eine doppelte. Neben der gewordenen, um uns sichtbaren, fertig gestalteten Welt ist da immer auch die noch unsichtbare, werdende, sich gerade ins Sein schickende Welt, eine Welt der Möglichkeit, die von einer zarten Morgenröte umgeben ist. Sie ist eben gerade nicht &lt;i&gt;vorhanden&lt;/i&gt;, so wie etwa ein Tisch vorhanden ist, und doch ist sie spürbar, ist sie die eigentliche Essenz des Seins. Wenn der Gnostiker Valentinus sagt, dass ein erstes Götterpaar des Urgrundes (bythos) und des Schweigens (sige) die Welt ins Sein brachte, geht das in die gleiche Richtung: Der Urgrund trägt den Stoff bei, aber erst in der dialektischen Spannung zum Schweigen &amp;ndash; zu dem, das eben kein Stoff, kein Inhalt, keine Aussage ist, entzündet sich die Welt. Das ist das wahre Geheimnis der "schöpferischen Pause".&lt;br /&gt;&lt;br /&gt;Die Freiheit ist ein solches Nichts im Sinne von LaoTse. Jemand, der die Freiheit hat, bekommt zwar alle Möglichkeiten der Gestaltung, ist aber gerade aufgrund dieser Freiheit auf nichts festgelegt. Im Fehlen des Festgelegten liegt die Freiheit. Insofern ist Freiheit ein negativer Wert, da sie nicht konkret ausgefüllt werden kann.&lt;br /&gt;&lt;br /&gt;Für die Freiheit lässt sich daher nicht gut Werbung machen, sie macht keine konkreten Versprechungen. In den Freiheitsrechten geht es um den freien, unbestimmten Raum, der geschützt werden muss vor den Machtansprüchen anderer, damit Individuen sich ihren Freiraum nach ihrem eigenen Gutdünken gestalten, ihr &lt;i&gt;individuelles Streben nach Glück&lt;/i&gt; (Richard Cumberland/John Locke) verwirklichen können. Für einen leeren Raum ist eben schwierig Werbung zu machen. &lt;br /&gt;&lt;br /&gt;Einfacher haben es da die Feinde der Freiheit, die die weissen Wände mit verlockenden Versprechungen bemalen. Wie einfach könnte euer Leben sein, seht hier diese klare und einfache Rechtleitung, euren Plan für den Seelenfrieden, die Einladung in die beste Gemeinschaft, die Einladung zum Paradies: Der kleine Preis für all diese konkreten Herrlichkeiten ist nur, dass ihr "niederfallen und euch unterwerfen" müsst (vgl. Mt. 4,9).&lt;br /&gt;&lt;br /&gt;Ohne Frage: Die Versuchung, diesen "kleinen Preis" zu bezahlen ist gross. Auch deshalb, weil die Freiheit furchterregend und anstrengend ist. Die Gefühle, die uns von der Freiheit Abstand nehmen lassen, sind vornehmlich Furcht und Faulheit. Furcht nicht nur vor der Gewalt, mit der die Feinde der Freiheit uns ihre Alternativen aufdrängen, sondern vor allem die Furcht vor der Freiheit selbst, eine Art &lt;i&gt;horror vacui&lt;/i&gt; vor der mit dem Wahrnehmen der Freiheit verbundenen  &lt;i&gt;transzendentalen Obdachlosigkeit&lt;/i&gt; (Georg Lukács). Wer die Freiheit annimmt, wird einsam, denn er begibt sich der Geborgenheit einer Gemeinschaft oder geschlossenen Ideologie. Ein bisschen wird man darin dem Lehrer ähnlich, der sagte: &lt;i&gt;Die Füchse haben Höhlen, die Vögel des Himmels haben Nester; aber der Sohn des Menschen hat keinen Ort, wo er sein Haupt niederlegen kann&lt;/i&gt; (Mt 8,20). Wieviel bequemer und einfacher ist es da, auf dem Pfad der Unfreiheit zu wandeln, sich seine Wege und Werte vorgeben zu lassen, die anstrengende kontinuierliche Bemühung um die Freiheit aufzugeben &amp;ndash; und von Tag zu Tage zu leben wie der &lt;i&gt;letzte Mensch&lt;/i&gt;: &lt;i&gt;Man hat sein Lüstchen für den Tag und sein Lüstchen für die Nacht: aber man ehrt die Gesundheit&lt;/i&gt; (Nietzsche).   &lt;br /&gt;&lt;br /&gt;Über diese Dinge hinaus, die allein schon der Freiheit eine ungeheure Bedeutung und Würde geben, hat die Freiheit auch eine beachtliche religiöse Tiefendimension. Die Freiheit ist der einzige Reim, den ich mir mit meinem beschränkten Verstand auf das Theodizee-Problem machen kann, also auf die Frage, warum Gott nicht machtvoll eingreift, um unsere Geschichte zum Besseren zu korrigieren, wenn er doch allmächtig ist &amp;ndash; salopp gesagt: warum er Lissabon, Sumatra und Auschwitz zugelassen hat. Wenn Gott solche Ungeheuerlichkeiten zulässt, ohne einzugreifen, muss ein solches Nichteingreifen um eines unendlich Kostbareren willen geschehen. Dieses unendlich Kostbarere kann nur das Geschenk der Freiheit sein: weil nämlich die Freiheit der einzige Wert ist, der durch eine machtvolle göttliche Intervention bis in seine Grundfesten vernichtet würde. &lt;br /&gt;&lt;br /&gt;Es ist derselbe Grund, warum Gott nicht als mächtiger König in die Welt kommt &amp;ndash; stattdessen lässt er sich von den Menschen wie ein Verbrecher kreuzigen, und  nur zum Hohn bekommt er am Kreuz den Königstitel. Deswegen irren die, die in Reichtum und Macht ein Zeichen von Gottgefälligkeit sehen, so wie sie umgekehrt in Leid und Katastrophen Strafen Gottes sehen wollen. Naturgewalten, selbst die Widerfahrnisse eines persönlichen Schicksals sind oft genug blind gegenüber den Individualitäten, die durch sie zum Opfer werden. So kann sich göttliche Führung doch nicht äussern!&lt;br /&gt;&lt;br /&gt;Wie ich schon im Blog &lt;a href="http://ruediger-plantiko.blogspot.com/2009/04/zufall-und-notwendigkeit.html"&gt;Zufall und Notwendigkeit&lt;/a&gt; angedeutet hatte, ist das Geheimnis der Freiheit mit der Existenz echten Zufalls verknüpft. Dass echter Zufall existiert, also Ereignisse, die sich prinzipiell nicht vorhersagen lassen, selbst bei theoretisch noch so genauer Kenntnis der Umstände, ist in der Quantenmechanik eine gesicherte Erkenntnis. Der radioaktive Zerfall eines Teilchens ist von dieser Art: Man kann grundsätzlich nicht sagen, zu welchem Zeitpunkt ein konkretes strahlendes Teilchen zerfallen wird, auch nicht in einer perfektionierten Physik der Zukunft, die alle uns noch "verborgenen Parameter" des Vorgangs kennt. Die Dualität von Zufall und Notwendigkeit, in die unsere Welt zerfällt, korrespondiert mit den am Anfang dieses Blogs beschriebenen zwei Welten: Die gewordene Welt entspricht dem Reich der Notwendigkeit &amp;ndash; die Welt des Werdens dagegen ist sinngemäss dem Reich des Zufalls oder besser: dem Reich der Freiheit zuzuordnen. Das Schöpferische als spontane Realisierung der Freiheit ist ebensowenig vorhersehbar wie der Zerfall eines Radium-Atoms. Die Freiheit ist von ähnlicher Natur wie der Zufall: Ein Nichtsein, das unvermutet und spontan in die Wirklichkeit überspringen kann. Kreativität ist ein spontanes Ereignis wie der Lichtbogen, der plötzlich aufflammend das Nichts zwischen den Polen überbrückt.&lt;br /&gt;&lt;br /&gt;Ein Ort, in dem sich die Kreativität als spontanes Verwirklichen der Freiheit in besonders herausragender Weise manifestieren kann, ist das &lt;i&gt;Gespräch&lt;/i&gt;. Das  wache Gespräch, das in Rede und Gegenrede versucht, die Wahrheit zu umkreisen: fair, ohne zu monologisieren, ohne zu belehren, ohne zu moralisieren, in jedem Moment auf das völlig Neue eingestellt, auf das neue Argument, den neuen Gesichtspunkt, stets - mit dem Gegenüber - bemüht, in wacher Aufmerksamkeit das Richtige zu erkennen. Das ist der kostbarste Gipfelpunkt der Freiheit, es ist der Moment, in dem wir uns in besonderem Masse als &lt;i&gt;lebendig&lt;/i&gt;e Wesen spüren und verwirklichen können.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-645904598358969902?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/645904598358969902/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=645904598358969902' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/645904598358969902'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/645904598358969902'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/04/das-geschenk-der-freiheit.html' title='Das Geschenk der Freiheit'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-6452868321378012012</id><published>2011-03-06T14:48:00.038+01:00</published><updated>2011-03-11T20:20:33.754+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Automatische Tests für JavaScript-Code</title><content type='html'>Vor kurzem habe ich auf diesem Blog meine &lt;a href="http://ruediger-plantiko.blogspot.com/2011/02/eine-minimalistische-javascript.html"&gt;minimalistische JavaScript-Bibliothek&lt;/a&gt; beschrieben. Der Post wäre nicht ganz vollständig, wenn ich nicht darauf hinweisen würde, dass diese Bibliothek namens &lt;a href="http://ruediger-plantiko.net/minlib/"&gt;minlib.js&lt;/a&gt; unter der milden &lt;a href="http://de.wikipedia.org/wiki/BSD-Lizenz"&gt;BSD-Lizenz&lt;/a&gt; für alle Interessierten verfügbar ist.  &lt;br /&gt;&lt;br /&gt;Wie immer, erschliesst sich die Verwendung einer Bibliothek nicht nur durch die &lt;a href="http://www.ruediger-plantiko.net/minlib/"&gt;Dokumentation&lt;/a&gt;, sondern auch durch die &lt;a href="http://www.ruediger-plantiko.net/minlib/source.html?minlibTests.js"&gt;Unit-Tests&lt;/a&gt;: Unit Tests zeigen, wie die Funktionen konkret aufgerufen werden und welche erwarteten Ergebnisse die Funktionsaufrufe produzieren. Auf der &lt;a href="http://www.ruediger-plantiko.net/minlib/minlib.html"&gt;Unit-Testseite&lt;/a&gt; können diese Aufrufe darüberhinaus &lt;i&gt;in action&lt;/i&gt; beobachtet werden. Man kann sie bei Interesse debuggen und sich Schritt für Schritt ansehen, was die Funktionen tun. &lt;br /&gt;&lt;br /&gt;Vor mehreren Jahren hatte ich mich zum Testen von JavaScript-Code für das schmale Unittest-Framework &lt;a href="http://ruediger-plantiko.net/minlib/ecmaunit.js"&gt;ECMA Unit&lt;/a&gt; aus dem Kupu-Projekt entschieden. Auch in diesem Blog schrieb ich darüber &lt;a href="http://ruediger-plantiko.blogspot.com/2007/06/erste-erfahrungen-mit-ecma-unit.html"&gt;bereits am 1.6.2007&lt;/a&gt;. Bislang hatte ich keinen Anlass, diese Entscheidung zu bereuen. Ein Framework für Unit Tests muss eben nicht üppig sein. Man muss nur in der Lage sein, ohne viel redundanten Code sofort die geplanten Selbsttestfunktionen hinschreiben und ausführen zu können. &lt;br /&gt;&lt;br /&gt;"Testfälle" sind in ECMA Unit Objekte zur Gruppierung von "Tests". Sie sind von einem zentralen Objekt &lt;tt&gt;TestCase&lt;/tt&gt; abgeleitet, das auch die verschiedenen Zusicherungen wie &lt;tt&gt;assert&lt;/tt&gt;, &lt;tt&gt;assertEquals&lt;/tt&gt;, &lt;tt&gt;assertFalse&lt;/tt&gt; usw. beinhaltet. Die einzelnen Tests sind diejenigen Methoden des Testobjekts, deren Name mit dem Präfix &lt;tt&gt;test&lt;/tt&gt; beginnt. Vor Ausführung eines Testfalls wird eine &lt;tt&gt;setUp()&lt;/tt&gt;-Methode aufgerufen, falls eine solche im Testobjekt hinterlegt ist; ebenso wird nach Ausführung eine &lt;tt&gt;tearDown()&lt;/tt&gt;-Methode aufgerufen. Eine Reihe von Testfällen kann zur Ausführung in einer Suite registriert werden, die schliesslich mit &lt;tt&gt;runSuite()&lt;/tt&gt; aufgerufen wird. Für die Protokollierung gibt es ein Objekt &lt;tt&gt;HTMLTestReporter&lt;/tt&gt;, oder, für die Ausgabe auf der Konsole, einen &lt;tt&gt;StdoutReporter&lt;/tt&gt;. Das ist alles. Die (spartanische) Ausgabe des HTML-Reporters sieht folgendermassen aus:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-p0A7s7aqqCg/TXOdUXxze6I/AAAAAAAAALI/GYMVzRk_n28/s1600/ecma_unit_reporter.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 162px;" src="http://2.bp.blogspot.com/-p0A7s7aqqCg/TXOdUXxze6I/AAAAAAAAALI/GYMVzRk_n28/s600/ecma_unit_reporter.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5580977336548031394" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Im Fehlerfall erhält man auch den Hinweis darauf, welcher Test in welchem Testfall welche Assertion verletzte:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-ekvEEc09Dac/TXOeq14VVPI/AAAAAAAAALQ/aEUGNSVmKS4/s1600/ecma_unit_reporter_failure.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 600px; height: 102px;" src="http://2.bp.blogspot.com/-ekvEEc09Dac/TXOeq14VVPI/AAAAAAAAALQ/aEUGNSVmKS4/s600/ecma_unit_reporter_failure.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5580978822097229042" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Beim Entwickeln von JavaScript-Code kann man also ein Browserfenster mit der einmal eingerichteten ECMA-Unit-Testseite geöffnet lassen und nach jeder Änderung mittels &lt;i&gt;Auffrischen&lt;/i&gt; sehen, ob die bisherigen Tests noch alle erfolgreich durchlaufen werden. &lt;br /&gt;&lt;br /&gt;Beim Schreiben von Testcode kommt es darauf an, den Test so kurz und lesbar wie möglich zu formulieren. Ein Kommentar ist meistens besser als Meldungsstring für eine Assertion plaziert. Das erhöht die Chance, dass der Text im Fehlerfall von einem Entwickler wirklich gelesen wird. &lt;br /&gt;&lt;br /&gt;Hier ein einfaches Beispiel, das die zu testende Funktion &lt;tt&gt;byId()&lt;/tt&gt; an einem Element der Testseite ausprobiert: &lt;br /&gt;&lt;pre class="sh_javascript"&gt;this.testById = function(){&lt;br /&gt;  var element = byId( "form2" );               &lt;br /&gt;  this.assert( element,&lt;br /&gt;    "Element der ID 'form2' muss gefunden werden");&lt;br /&gt;  this.assertEquals( element.id, "form2",&lt;br /&gt;    "Element der ID 'form2' muss korrekt identifiziert werden");&lt;br /&gt;  };                                                                 &lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;Die erste Zusicherung mit &lt;tt&gt;assert&lt;/tt&gt; prüft, ob die Methode &lt;tt&gt;byId()&lt;/tt&gt; überhaupt ein Objekt zurückgeliefert hat. Mit &lt;tt&gt;assertEquals&lt;/tt&gt; folgt dann die spezifischere Zusicherung, dass es auch das richtige Element ist, das gefunden wurde. Die Aufteilung auf zwei Zusicherungen hat den Vorteil einer besseren Lesbarkeit im Fehlerfall. Würde &lt;tt&gt;byId()&lt;/tt&gt; nämlich &lt;tt&gt;null&lt;/tt&gt; oder &lt;tt&gt;undefined&lt;/tt&gt; zurückgeben, so bekäme man ohne den vorgängigen &lt;tt&gt;assert&lt;/tt&gt; beim Zugriff auf &lt;tt&gt;element.id&lt;/tt&gt; eine Ausnahme, etwa "&lt;i&gt;element.id is null or not an object&lt;/i&gt;". In der obigen Form aber würde der Test die Meldung "&lt;i&gt;Element der ID 'form2' muss gefunden werden&lt;/i&gt;" bringen, was einen spezifischeren Hinweis auf die Fehlerursache enthält.&lt;br /&gt;&lt;br /&gt;Für den Test von Callbackfunktionen können wir uns Closures zunutze machen. Die folgende Methode prüft, ob eine &lt;tt&gt;each()&lt;/tt&gt;-Iteration mit der speziellen Ausnahme &lt;tt&gt;$break&lt;/tt&gt; wirklich verlassen wird:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// ---&lt;br /&gt;this.testLeaveEachWithBreak = function() {&lt;br /&gt;  var lastItem;&lt;br /&gt;  [1,2,3,4,5,6,7].each( function() { &lt;br /&gt;    lastItem = this;&lt;br /&gt;    if (this==5) { throw $break; }  &lt;br /&gt;    });&lt;br /&gt;  this.assertEquals( lastItem, 5, &lt;br /&gt;    "each() muss mit $break abgebrochen werden können"&lt;br /&gt;    ); &lt;br /&gt;  };&lt;/pre&gt;&lt;br /&gt;Die anonyme Iteratorfunktion dieses Tests kann auf die lokale Variable &lt;tt&gt;lastItem&lt;/tt&gt; zugreifen, obwohl diese nicht in ihrem eigenen Geltungsbereich, sondern in dem der Testmethode deklariert wurde. Somit können wir nach Ausführung der Schleife bequem prüfen, ob &lt;tt&gt;lastItem&lt;/tt&gt; wirklich den erwarteten Wert hat.&lt;br /&gt;&lt;br /&gt;Ich hatte angekündigt, die Datei &lt;tt&gt;minlib.js&lt;/tt&gt; nicht mehr wesentlich erweitern zu wollen. Die Funktion &lt;tt&gt;extend()&lt;/tt&gt;, die ich noch hinzugefügt habe, ist von ihrer Grösse her sicher unwesentlich, nicht aber von ihrer Bedeutung. Die Funktion ist der von der Mozilla Developer Community empfohlene &lt;a href="https://developer.mozilla.org/en/JavaScript/Guide/Inheritance_Revisited"&gt;Vererbungsmechanismus&lt;/a&gt;. Unter Verwendung von &lt;tt&gt;extend()&lt;/tt&gt; kann man Vererbung so implementieren, wie es der folgende &lt;tt&gt;testRedefineFunction&lt;/tt&gt; vorführt:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;this.testRedefineFunction = function() {&lt;br /&gt;&lt;br /&gt;  var A = function(x,y) {&lt;br /&gt;    this.x = x;&lt;br /&gt;    this.y = y;&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;  A.prototype = {&lt;br /&gt;    t:1,&lt;br /&gt;    x:null,&lt;br /&gt;    y:null,&lt;br /&gt;    f:function() {&lt;br /&gt;      return "t:"+this.t+",x:"+ this.x + ",y:" + this.y ;&lt;br /&gt;      }&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;  var B = function(x,y,z) {&lt;br /&gt;    A.call(this,x,y);&lt;br /&gt;    this.z = z;&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;  B.prototype = { z:null,&lt;br /&gt;    f:function() {&lt;br /&gt;      return A.prototype.f.call(this) + ",z:" + this.z;&lt;br /&gt;      }&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;  extend( B, A);&lt;br /&gt;&lt;br /&gt;  var b = new B(2,3,4);&lt;br /&gt;&lt;br /&gt;  this.assertEquals( b.f(), "t:1,x:2,y:3,z:4");&lt;br /&gt;&lt;br /&gt;  };&lt;/pre&gt;&lt;br /&gt;Der Test zeigt mehreres auf einmal: &lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Methoden eines Objekts definiert man nicht im Konstruktor, sondern im Prototyp. Damit wird die Deklaration nur einmal durchlaufen (und nicht bei jeder Instanzbildung). &lt;br /&gt;&lt;li&gt;Alle Komponenten des Objekts sollten im Prototyp aufgezählt sein.&lt;br /&gt;&lt;li&gt;Es müssen aber nicht alle Komponenten bereits im Konstruktor auftreten. Hier kommt z.B. das Attribut &lt;tt&gt;t&lt;/tt&gt; nur über den Prototyp ins Objekt.&lt;br /&gt;&lt;li&gt;Im Konstruktor von &lt;tt&gt;B&lt;/tt&gt; sieht man einen Aufruf des Superkonstruktors mittels &lt;tt&gt;A.call(this,x,y)&lt;/tt&gt; (und nicht etwa einfach nur &lt;tt&gt;A(x,y)&lt;/tt&gt;). Nur so arbeitet man auf der gewünschten Instanz, nämlich der gerade zu bildenden Instanz von B.&lt;br /&gt;&lt;li&gt;Die &lt;tt&gt;extend()&lt;/tt&gt;-Funktion übernimmt den Prototyp von &lt;tt&gt;A&lt;/tt&gt; in den Prototyp von &lt;tt&gt;B&lt;/tt&gt;. Wenn möglich, durch blosse Zuweisung der Referenz des Prototyps (der ja wie alles in JavaScript nur ein Hash ist) an das dafür vorgesehene Pseudo-Attribut &lt;tt&gt;__proto__&lt;/tt&gt;. Falls dieses von der JavaScript-Implementierung nicht offengelegt ist, werden wenigstens alle Prototypkomponenten von &lt;tt&gt;A&lt;/tt&gt; in &lt;tt&gt;B&lt;/tt&gt; übernommen, die in &lt;tt&gt;B&lt;/tt&gt; nicht definiert wurden.&lt;br /&gt;&lt;li&gt;Der Zugriff auf &lt;tt&gt;x&lt;/tt&gt; und &lt;tt&gt;y&lt;/tt&gt; würde auch ohne &lt;tt&gt;extend(B,A)&lt;/tt&gt; im Subtyp &lt;tt&gt;B&lt;/tt&gt; funktionieren, da diese Komponenten auch im Konstruktor von &lt;tt&gt;A&lt;/tt&gt; definiert werden. Der Zugriff auf &lt;tt&gt;t&lt;/tt&gt; wäre aber ohne &lt;tt&gt;extend(B,A)&lt;/tt&gt; erfolglos, da &lt;tt&gt;t&lt;/tt&gt; im Prototyp, aber nicht im Konstruktor verwendet wird. &lt;br /&gt;&lt;li&gt;Man redefiniert Funktionen, indem man sie mit gleichem Namen im Prototyp des Subtyps definiert. Wie im Konstruktor den Superkonstruktor, kann man in der redefinierten Methode mittels &lt;tt&gt;A.prototype.f.call(this,...)&lt;/tt&gt; die Supermethode aufrufen.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Wie testet man eine Funktion, die HTTP-Requests durchführt &amp;ndash; z.B. die Funktion &lt;tt&gt;doRequest&lt;/tt&gt; von &lt;tt&gt;minlib.js&lt;/tt&gt;? Der Unit Test muss nur den Code der Funktion &lt;tt&gt;doRequest&lt;/tt&gt; selbst überprüfen, nicht aber den auszuführenden HTTP-Request. Der Unit Test prüft also, ob das &lt;tt&gt;XMLHttpRequest&lt;/tt&gt;-Objekt anhand der Aufrufparameter von &lt;tt&gt;doRequest&lt;/tt&gt; richtig instrumentiert wird. Hierzu kann die Funktion &lt;tt&gt;getRequestor&lt;/tt&gt;, die je nach Browser die richtige Version von &lt;tt&gt;XMLHttpRequest&lt;/tt&gt; beschafft, durch eine passende Mock-Funktion überschrieben werden. &lt;br /&gt;&lt;br /&gt;Im Testfall für HTTP definieren wir also mit folgendem Code ein billiges Ersatzobjekt für &lt;tt&gt;XMLHttpRequest&lt;/tt&gt; &amp;ndash; ein Objekt, das so tut, als beherrschte es alle Operationen eines veritablen &lt;tt&gt;XMLHttpRequests&lt;/tt&gt;:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;function RequestorMock() {};&lt;br /&gt;RequestorMock.prototype = {&lt;br /&gt;   headerFields:{},&lt;br /&gt;   readyState:0,&lt;br /&gt;   onreadystatechange:null,&lt;br /&gt;   body:null,&lt;br /&gt;   open:function(action,url,sync) {&lt;br /&gt;     this.url = url;&lt;br /&gt;     this.action = action;&lt;br /&gt;     this.sync = sync;&lt;br /&gt;     },&lt;br /&gt;   send:function(data) {&lt;br /&gt;     this.body = data;&lt;br /&gt;     this.readyState = 4;&lt;br /&gt;     if (this.onreadystatechange) {&lt;br /&gt;       this.onreadystatechange.call(this);&lt;br /&gt;       }&lt;br /&gt;     },&lt;br /&gt;   setRequestHeader:function(name,value) {&lt;br /&gt;     this.headerFields[name] = value;&lt;br /&gt;     }&lt;br /&gt;   };&lt;/pre&gt;&lt;br /&gt;In der &lt;tt&gt;send()&lt;/tt&gt;-Methode des Mocks wird sofort der &lt;tt&gt;readyState=4&lt;/tt&gt; hergestellt und die Callbackfunktion aufgerufen. Das reicht, um die richtige Verwendung des &lt;tt&gt;XMLHttpRequest&lt;/tt&gt;-Objekts in &lt;tt&gt;doRequest&lt;/tt&gt; zu prüfen. &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;function() {&lt;br /&gt;  this.name = "HttpRequest";&lt;br /&gt;  var getRequestorSave = null;&lt;br /&gt;  var requestorMock = null;&lt;br /&gt;  this.setUp = function() {&lt;br /&gt;    getRequestorSave = getRequestor;&lt;br /&gt;    getRequestor = function() {&lt;br /&gt;      return requestorMock = new RequestorMock();&lt;br /&gt;      };&lt;br /&gt;     };&lt;br /&gt;  this.tearDown = function() {&lt;br /&gt;    getRequestor = getRequestorSave;&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;  this.testDoRequest = function() {&lt;br /&gt;&lt;br /&gt;    var data = 'var i=0;',&lt;br /&gt;        url = 'http://ruediger-plantiko.net',&lt;br /&gt;        callbackCalled = false;&lt;br /&gt;&lt;br /&gt;//  doRequest(url,callback,data,action,headerFields)&lt;br /&gt;    doRequest( url,&lt;br /&gt;               function() { callbackCalled = true; },&lt;br /&gt;               data,&lt;br /&gt;               "POST",&lt;br /&gt;               { "Content-Type":"text/javascript" } );&lt;br /&gt;&lt;br /&gt;    this.assertEquals( requestorMock.url,  url );&lt;br /&gt;    this.assertEquals( requestorMock.body, data );&lt;br /&gt;    this.assertEquals( requestorMock.headerFields["Content-Type"], &lt;br /&gt;      "text/javascript" );&lt;br /&gt;    this.assertEquals( requestorMock.action, "POST" );&lt;br /&gt;    this.assert( callbackCalled, &lt;br /&gt;      "Die Callbackfunktion muss aufgerufen werden" );&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Im Setup also wird die (globale) &lt;tt&gt;getRequest&lt;/tt&gt;-Funktion durch den Zeiger auf eine anonyme Funktion überschrieben, die eine Mock-Instanz zurückliefert. Wir merken uns die Instanz noch in einer lokalen privaten Variablen &lt;tt&gt;requestorMock&lt;/tt&gt;, um den Zustand des Objekts im Zusicherungsteil des Tests verwenden zu können. Im Teardown wird die ursprüngliche Funktion wiederhergestellt. &lt;br /&gt;&lt;br /&gt;In der Testmethode selbst wird nur noch die &lt;tt&gt;doRequest()&lt;/tt&gt;-Methode mit einigen Dummydaten aufgerufen. Der HTTP-Request selbst wird dann zwar niemals ausgeführt. Wohl aber wird geprüft, dass der Aufruf korrekt ist. Und das ist genau die Aufgabe der Funktion &lt;tt&gt;doRequest()&lt;/tt&gt;: Den HTTP-Request entsprechend den beim Aufruf übergebenen Parametern korrekt auszuführen.&lt;br /&gt;&lt;br /&gt;Der Versand von Formularen lässt sich auf noch einfachere Weise überprüfen, indem man das Formularattribut &lt;tt&gt;action&lt;/tt&gt;, das normalerweise die Ziel-URL enthält, durch eine Dummy-URL ersetzt, zum Beispiel &lt;tt&gt;action="javascript:void(-1);"&lt;/tt&gt;. Dann kann nach dem &lt;tt&gt;submit()&lt;/tt&gt; inspiziert werden, ob das Formular wie gewünscht gefüllt wurde. So funktioniert der Test der &lt;tt&gt;gotoURL()&lt;/tt&gt;-Funktion:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;this.testGotoURL = function() {&lt;br /&gt;&lt;br /&gt;  var elem, parent;&lt;br /&gt;  var action = "javascript:void(-1);";&lt;br /&gt;&lt;br /&gt;  gotoURL( action, { testField:"testValue" } );&lt;br /&gt;&lt;br /&gt;  elem = firstByName("testField");&lt;br /&gt;  this.assert( elem, "gotoURL() muss Formularfeld erzeugen" );&lt;br /&gt;  this.assertEquals( elem.nodeName, "INPUT" );&lt;br /&gt;  this.assertEquals( elem.type, "hidden" );&lt;br /&gt;  this.assertEquals( elem.value, "testValue" );&lt;br /&gt;&lt;br /&gt;  parent = elem.parentNode;&lt;br /&gt;  this.assertEquals( parent.nodeName, "FORM" );&lt;br /&gt;  this.assertEquals( parent.action, action );&lt;br /&gt;  this.assertEquals( parent.method, "post" );&lt;br /&gt;&lt;br /&gt;  };&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Auch die korrekte Registrierung und Deregistrierung von Ereignisbehandlern durch die Funktionen &lt;tt&gt;registerFor()&lt;/tt&gt; und &lt;tt&gt;unregister()&lt;/tt&gt; lässt sich automatisch testen, sobald man weiss, wie man simulativ ein Ereignis produzieren kann, z.B. ein Click-Event. In diesem Fall punktet einmal der Internet Explorer: um einen Mausclick auf das Element &lt;tt&gt;e&lt;/tt&gt; auszulösen, programmiert man hier &lt;tt&gt;e.click()&lt;/tt&gt; &amp;ndash; einfacher geht es nicht!&lt;br /&gt;&lt;pre class="sh_javascript"&gt;fireClick = navigator.userAgent.match(/MSIE/) ?&lt;br /&gt;function( element ) {&lt;br /&gt;// IE = wunderbar einfach&lt;br /&gt;  element.click();&lt;br /&gt;  } :&lt;br /&gt;function( element ) {&lt;br /&gt;// Alle anderen: Dasselbe total umständlich&lt;br /&gt;  var evObj = document.createEvent('MouseEvents');&lt;br /&gt;  evObj.initEvent( 'click', true, true );&lt;br /&gt;  element.dispatchEvent(evObj);&lt;br /&gt;  };&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Der Geradeausfall zum Testen, ob ein registrierter Click-Behandler auch aufgerufen wird, lässt sich dann folgendermassen schreiben:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;function() {&lt;br /&gt;  var f = null;&lt;br /&gt;  var idCopiedByHandler = "";&lt;br /&gt;  this.setUp = function() {&lt;br /&gt;    idCopiedByHandler = "";&lt;br /&gt;    f = registerFor( byId("btnTest"), "click", function() {&lt;br /&gt;      idCopiedByHandler = this.id;&lt;br /&gt;      });&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;  this.tearDown = function() {&lt;br /&gt;    unregister( byId("btnTest"), "click", f );&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;// ---&lt;br /&gt;  this.testHandlerCalledOnClick = function() {&lt;br /&gt;&lt;br /&gt;    var btnTest = byId("btnTest");&lt;br /&gt;&lt;br /&gt;    fireClick( btnTest );&lt;br /&gt;    this.assertEquals( idCopiedByHandler, "btnTest",&lt;br /&gt;      "Registrierter Clickbehandler muss bei Click aufgerufen werden" );&lt;br /&gt;&lt;br /&gt;    };&lt;br /&gt;  ...  &lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ob der Callback aufgerufen wurde, erkennt man daran, dass die ID des Elements (auf das im Behandler mit der Pseudovariable &lt;tt&gt;this&lt;/tt&gt; zugegriffen werden kann) in die private Variable &lt;tt&gt;idCopiedByHandler&lt;/tt&gt; kopiert wurde. &lt;br /&gt;&lt;br /&gt;Um mehrere Tests mit derselben Registrierungsfunktion ausführen zu können, gefiel es mir, die Registrierung in den &lt;tt&gt;setUp&lt;/tt&gt; zu setzen und im &lt;tt&gt;tearDown&lt;/tt&gt; wieder zu entfernen. So reduziert sich der Code der Testmethode, wie man sieht,  drastisch auf das absolut Wesentliche: Das Auslösen des Clicks und die Prüfung, dass der Clickbehandler aufgerufen wurde.&lt;br /&gt;&lt;br /&gt;Für &lt;i&gt;JavaScript-Bibliotheken&lt;/i&gt; sind Unit Tests dieser Art bereits ausreichend. Für Webanwendungen, die intensiv mit JavaScript arbeiten, sind natürlich weitergehende Anwendungstests angebracht, zum Beispiel mit QTP oder Selenium. Das wäre Thema eines separaten Blogs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-6452868321378012012?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/6452868321378012012/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=6452868321378012012' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/6452868321378012012'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/6452868321378012012'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/03/automatische-tests-fur-javascript-code.html' title='Automatische Tests für JavaScript-Code'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-p0A7s7aqqCg/TXOdUXxze6I/AAAAAAAAALI/GYMVzRk_n28/s72-c/ecma_unit_reporter.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-3133856442104291218</id><published>2011-02-18T07:49:00.009+01:00</published><updated>2011-02-20T00:34:42.899+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Workflow-Events bei Änderungen</title><content type='html'>Wenn das Workflow-Laufzeitsystem in einem SAP-System einmal eingerichtet ist, unterstützt es die Erweiterung aller Geschäftsprozesse &amp;ndash; in vielen Fällen ohne zusätzlichen Programmieraufwand, mit einem modellbasierten Ansatz. Mit dem &lt;i&gt;Workflow Builder&lt;/i&gt; können Abläufe auf Basis der im BOR (&lt;i&gt;Business Object Repository&lt;/i&gt;) definierten Objekte im Zusammenspiel mit manuellen Schritten (Entscheidungen, Bekanntmachungen usw.) definiert und implementiert werden. &lt;br /&gt;&lt;br /&gt;Grundidee ist dabei die lose, customizebare Kopplung von BOR-Ereignissen an &lt;i&gt;Verbraucher&lt;/i&gt;. Die Kopplung kann auch im Produktivsystem noch an- und abgeschaltet werden. Verbraucher können Workflows, Tasks, BOR- oder Klassenmethoden oder auch Funktionsbausteine sein. Die Ereignisauslösung ist dabei im gesamten System omnipräsent. Es dürfte kaum ein betriebswirtschaftlich relevantes Ereignis geben, das nicht im Workflowsystem eine Entsprechung findet. Typische Beispiele sind die Anlage neuer Belege oder die Änderung von System- oder Anwenderstatus. Selbstverständlich können Workflow-Ereignisse auch programmgesteuert ausgelöst werden (falls unwahrscheinlicherweise doch einmal eines fehlen sollte), wofür man die Methode &lt;tt&gt;raise&lt;/tt&gt; der Klasse &lt;tt&gt;cl_swf_evt_event&lt;/tt&gt; verwenden sollte.&lt;br /&gt;&lt;br /&gt;Hier will ich zeigen, wie man sich für die Änderung bestimmter Felder im Artikelstamm registrieren kann. Im konkret vorliegenden Fall sollte sich ein Verbraucher für die Änderung zweier bestimmter Felder registrieren. Um nun nicht für &lt;i&gt;jede&lt;/i&gt; Änderung ein Ereignis auszulösen, erschien es mir nötig, ein Custom Event genau für diesen Änderungsfall zu definieren. &lt;br /&gt;&lt;br /&gt;Ereignisse sind Bestandteile von BOR-Objekten. Um modifikationsfrei ein neues Ereignis zu definieren, habe ich einen Subtyp des BOR-Objekts &lt;tt&gt;BUS1001001&lt;/tt&gt; (Handelsartikel) definiert:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-r2NqxTwqxzI/TV4brV5A8VI/AAAAAAAAAKQ/_A4PgNHZ19M/s1600/swo1_einstieg.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 241px;" src="http://2.bp.blogspot.com/-r2NqxTwqxzI/TV4brV5A8VI/AAAAAAAAAKQ/_A4PgNHZ19M/s400/swo1_einstieg.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5574923820155859282" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Den neuen Typ &lt;tt&gt;ZBU1001001&lt;/tt&gt; verwende ich, um ein neues Ereignis &lt;tt&gt;FIELDSET_CHANGED&lt;/tt&gt; zu definieren:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/-uOrp41CL4eQ/TV4br2A_x9I/AAAAAAAAAKY/r_UUPR8Zeiw/s1600/swo1_ereignis.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 158px;" src="http://3.bp.blogspot.com/-uOrp41CL4eQ/TV4br2A_x9I/AAAAAAAAAKY/r_UUPR8Zeiw/s400/swo1_ereignis.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5574923828779272146" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Dieses Workflow-Ereignis muss nun an die gewünschten Änderungen des Artikelstamms gebunden werden. Das SAP-System schreibt Änderungen an Geschäftsobjekten in den Tabellen &lt;tt&gt;CDHDR&lt;/tt&gt; und &lt;tt&gt;CDPOS&lt;/tt&gt; fort. Dabei gibt es pro Geschäftsobjekt einen Typschlüssel, das sogenannte Änderungsbelegobjekt. Beim Entwickeln des Geschäftsobjekts (also in der Regel in der SAP-Standard-Entwicklung) wurde dieses Änderungsbelegobjekt eingerichtet, wobei auch automatische Funktionsbausteine zum Fortschreiben generiert wurden. Heisst das Änderungsbelegobjekt beispielsweise &lt;tt&gt;MAT_FULL&lt;/tt&gt;, so bekommt der zugeordnete Funktionsbaustein den Namen &lt;tt&gt;MAT_FULL_WRITE_DOCUMENT&lt;/tt&gt;. Dieser Baustein wird dann, im allgemeinen in der verzögerten Verbuchung (V2), beim Sichern des Geschäftsobjekts aufgerufen. &lt;br /&gt; &lt;br /&gt;Um nun dem Änderungsbelegobjekt mitzuteilen, dass es das Ereignis eines BOR-Objekts auslösen soll, definiert man eine Zuordnung in der Transaktion &lt;tt&gt;swec&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-20PgfJITikc/TV4bshIaEyI/AAAAAAAAAKw/zp0YrlfJHiE/s1600/swec.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 109px;" src="http://1.bp.blogspot.com/-20PgfJITikc/TV4bshIaEyI/AAAAAAAAAKw/zp0YrlfJHiE/s400/swec.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5574923840353080098" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Im Detailbild zu dieser Zeile kann man über den Bedingungseditor beispielsweise noch festlegen, dass das Ereignis nur bei Änderung bestimmter Felder ausgelöst werden soll: &lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-zBOPOgw2haw/TV4bsWWo5GI/AAAAAAAAAKo/1wl60RClv4o/s1600/swec_bedingung.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 301px;" src="http://2.bp.blogspot.com/-zBOPOgw2haw/TV4bsWWo5GI/AAAAAAAAAKo/1wl60RClv4o/s400/swec_bedingung.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5574923837459981410" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Nach diesen Einstellungen wird das neue Ereignis bereits ausgelöst. Bis hierhin liess sich alles mit blossem Customizing erledigen. Wenn nun an das Ereignis an einen Verbraucher gekoppelt werden soll, kann man sich entscheiden: Möchte man selbst einen spezifischen Verbraucher implementieren, oder kann man die gewünschten Funktionen im Rahmen eines Workflows modellieren. Der häufig gewünschte 08/15-Fall, dass irgendjemand gern eine E-Mail-Benachrichtigung hätte, lässt sich eigentlich immer mit Workflowmitteln lösen, d.h. ohne Programmierung. Die Verwendung einer Workflow-Aufgabe zur E-Mail-Erzeugung hat darüberhinaus den Vorteil, dass die Kopf- und Detaildaten der Mail frei einstellbar sind, wobei als Platzhalter Anwendungsdaten verwendet werden können.   &lt;br /&gt;&lt;br /&gt;Wenn man es möchte, &lt;i&gt;kann&lt;/i&gt; man auch programmierte Logik an das Ereignis koppeln. In der folgenden Ereignis-Typ-Kopplung wird zum Beispiel der Funktionsbaustein &lt;tt&gt;Z_CONSUME_FIELDSET_CHANGED&lt;/tt&gt; mit dem neuen Ereignis &lt;tt&gt;FIELDSET_CHANGED&lt;/tt&gt; verknüpft. Die Kopplung bekommt einen symbolischen Namen (der im System nicht weiter verprobt wird), hier &lt;tt&gt;MARA_FIELDSET_CHANGED&lt;/tt&gt;:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-Mp9UiTjQIRM/TV4bsP_Y2JI/AAAAAAAAAKg/jrVSMY2UNFc/s1600/swetypv.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 333px;" src="http://1.bp.blogspot.com/-Mp9UiTjQIRM/TV4bsP_Y2JI/AAAAAAAAAKg/jrVSMY2UNFc/s400/swetypv.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5574923835751848082" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Der Funktionsbaustein muss die folgende Schnittstelle aufweisen und ist ansonsten frei definierbar. Die interne Tabelle &lt;tt&gt;EVENT_CONTAINER&lt;/tt&gt; enthält Detaildaten zum Anwendungsobjekt: in diesem Fall die Nummer des Änderungsbelegs. &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;function z_consume_fieldset_changed.&lt;br /&gt;*"----------------------------------------------------------------------&lt;br /&gt;*"*"Lokale Schnittstelle:&lt;br /&gt;*"  IMPORTING&lt;br /&gt;*"     VALUE(EVENT) TYPE  SWETYPECOU-EVENT&lt;br /&gt;*"     VALUE(RECTYPE) TYPE  SWETYPECOU-RECTYPE&lt;br /&gt;*"     VALUE(OBJTYPE) TYPE  SWETYPECOU-OBJTYPE&lt;br /&gt;*"     VALUE(OBJKEY) TYPE  SWEINSTCOU-OBJKEY&lt;br /&gt;*"     VALUE(EXCEPTIONS_ALLOWED) TYPE  SWEFLAGS-EXC_OK DEFAULT SPACE&lt;br /&gt;*"  EXPORTING&lt;br /&gt;*"     VALUE(REC_ID) TYPE  SWELOG-RECID&lt;br /&gt;*"  TABLES&lt;br /&gt;*"      EVENT_CONTAINER STRUCTURE  SWCONT&lt;br /&gt;*"----------------------------------------------------------------------&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Erwähnenswert ist noch, dass es für Workflowereignisse ein sehr nützliches &lt;i&gt;Trace&lt;/i&gt; gibt, das sich über die Transaktion &lt;tt&gt;swels&lt;/tt&gt; ein- und ausschalten lässt (das Ausschalten ist natürlich wichtig, vor allem im Produktivsystem!). In Transaktion &lt;tt&gt;swel&lt;/tt&gt; kann man sich das Log des Ereignis-Trace ansehen. Zum Testen habe ich nacheinander zwei Feldänderungen durchgeführt. Das Log zeigt, dass jeweils das Ereignis ausgelöst und der Verbraucher gemäss der Kopplung &lt;tt&gt;MARA_FIELDSET_CHANGED&lt;/tt&gt; identifiziert und korrekt gestartet wurde. &lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-rThu0HLN5tk/TV6M78qMjTI/AAAAAAAAALA/OkfDtM1A8aE/s1600/swel.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 76px;" src="http://4.bp.blogspot.com/-rThu0HLN5tk/TV6M78qMjTI/AAAAAAAAALA/OkfDtM1A8aE/s400/swel.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5575048350254533938" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-3133856442104291218?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/3133856442104291218/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=3133856442104291218' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/3133856442104291218'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/3133856442104291218'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/02/workflow-events-bei-anderungen.html' title='Workflow-Events bei Änderungen'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-r2NqxTwqxzI/TV4brV5A8VI/AAAAAAAAAKQ/_A4PgNHZ19M/s72-c/swo1_einstieg.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-2761894701588950975</id><published>2011-02-16T21:18:00.078+01:00</published><updated>2011-12-22T23:46:10.549+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Eine minimalistische JavaScript-Bibliothek</title><content type='html'>HTML ist, wie Stefan Münz es so schön sagte, die &lt;a href="http://de.selfhtml.org/intro/technologien/html.htm"&gt;lingua franca des Web&lt;/a&gt;: HTML bleibt &lt;i&gt;die&lt;/i&gt; Sprache, in der man als Server mit einem Browser reden sollte &amp;ndash; flankiert von CSS-Code für die kompakte und wiederverwendbare Notation von &lt;i&gt;Stil&lt;/i&gt;, und JavaScript-Code für die Implementierung von clientseitigem &lt;i&gt;Verhalten&lt;/i&gt;. Manche errichten allerdings prächtige Gebäude, in Form von &lt;i&gt;Web Application Frameworks&lt;/i&gt; und denken sich lustige Namen für diese aus, nur um ihre Verwender vor dem direkten Kontakt mit dieser vermeintlich schlimmen Trias zu bewahren: Als würden Entwickler beim Anblick von HTML-, CSS- und JavaScript-Code zu Stein erstarren. &lt;br /&gt;&lt;br /&gt;Aber diese prächtigen Gebäude setzen sich alle nicht so richtig durch. Das Interesse, Overhead durch zusätzliche, oft nicht wirklich benötigte Indirektionsebenen zu vermeiden, ist höher als die Angst, aus Versehen doch wirklich einmal eine HTML-Notation zu verwenden, die doch tatsächlich vom Netscape Navigator 4.0 nicht mehr korrekt interpretiert wird. Und da HTML, CSS und selbst JavaScript leicht zu erlernen sind, schreckt die Zielgruppe, an die sich die prächtigen Gebäude eigentlich wenden, nicht davor zurück, statt mit abstrahierenden Frameworks direkt auf der HTML-Ebene zu arbeiten. Dazu kommt, dass es für Content Management und Webanwendungen eine Menge konkurrierende Lösungen gibt, so dass man sich bei Verwendung eines bestimmten Frameworks in eine Abhängigkeit begibt.&lt;br /&gt;&lt;br /&gt;Wer also zu der grossen Gruppe der Webanwendungsentwickler gehört, die ihre Anwendungen direkt mit HTML, CSS und JavaScript formulieren, ohne dabei so schreckliche Dinge wie WYSIWYG-Editoren mit automatischer HTML-Codegenerierung zu verwenden, der wird zwar kein Framework benötigen, wohl aber eine Bibliothek von JavaScript-Funktionen. Und zwar aus drei Gründen:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;weil er gewisse Funktionen immer wieder benötigt und sie daher nur einmal programmieren möchte (&lt;a href="http://c2.com/cgi/wiki?DontRepeatYourself"&gt;Don't Repeat Yourself&lt;/a&gt;),&lt;/li&gt; &lt;br /&gt;&lt;li&gt;um auf browserspezifisch unterschiedlich implementierte Features mit einer Abstraktion zugreifen zu können,&lt;/li&gt;&lt;br /&gt;&lt;li&gt;um den &lt;i&gt;konkreten&lt;/i&gt; JavaScript-Code der Webanwendung flüssig lesbar und doch kompakt formulieren zu können.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;Nun gibt es eine Reihe von JavaScript-Bibliotheken für solche Zwecke. Aber jede Bibliothek möchte natürlich besonders gut und umfassend werden, die Konkurrenz in allen obigen Punkten um Längen schlagen. Da erwacht der Ehrgeiz der Bibliotheksentwickler, und die Bibliotheken werden wirklich besser und umfassender - aber auch immer grösser. &lt;a href="http://dojotoolkit.org/download"&gt;Dojo 1.5&lt;/a&gt; hat unkomprimiert 370 KB (komprimiert immer noch 88), &lt;a href="http://jquery.com/"&gt;jQuery 1.5&lt;/a&gt; hat 207 KB (komprimiert 82), und &lt;a href="http://www.prototypejs.org/"&gt;Prototype 1.7&lt;/a&gt; hat unkomprimiert 169 KB (und bietet keine komprimierte Version an; wenn ich aber Frank Marcias &lt;a href="http://fmarcia.info/jsmin/test.html"&gt;JS Minifier&lt;/a&gt; verwende, reduziert sich Prototype 1.7 auf 120 KB). Das ist immerhin bedenklich: Braucht man wirklich so grosse Bibliotheken?&lt;br /&gt;&lt;br /&gt;Man könnte einwenden, dass die Parsezeiten für Bibliotheken dieser Grössenordnung schon heute vernachlässigbar sind, während die JavaScript-Interpreter immer noch beständig weiter optimiert werden (vom IE9 hört man über &lt;a href="http://blogs.technet.com/b/sieben/archive/2010/11/17/internet-explorer-9-platform-preview-7-mit-neuem-geschwindigkeitsrekord.aspx"&gt;traumhafte Performanceergebnisse&lt;/a&gt;, die selbst Google Chromes Wunderwaffe &lt;a href="http://code.google.com/p/v8/"&gt;V8&lt;/a&gt; noch in den Schatten stellen sollen). Bei Anwendungen, die mit den klassischen Form-Submit-Zyklen arbeiten, schlägt diese Parsezeit allerdings bei jedem Dialogschritt zu Buche. Es empfiehlt sich in jedem Fall das Ökonomieprinzip: Warum soll man etwas laden, das man zu grossen Teilen gar nicht benötigt oder verwendet? &lt;i&gt;Später&lt;/i&gt; lässt sich nicht mehr genau sagen, welche Funktionen in welchen Teilen der Anwendung wirklich verwendet werden. &lt;br /&gt;&lt;br /&gt;Daher stellt sich die Frage: Welche Funktionen werden besonders häufig benötigt oder ergeben einen besonders grossen Nutzen?&lt;br /&gt;&lt;br /&gt;Da sind sicher einmal einfache &lt;i&gt;DOM-Zugriffsfunktionen&lt;/i&gt;. Die Funktionen &lt;tt&gt;$()&lt;/tt&gt; und &lt;tt&gt;$$()&lt;/tt&gt; in Prototype und jQuery verwenden die Syntax von CSS-Selektoren, um Knotenmengen aus dem HTML-DOM zu extrahieren. Das ist eine kompakte Syntax, die gut ankommt und von der Zielgruppe verstanden und gelesen wird. Eine gute Wahl. Aber der Parser für die Selektorstrings will erstmal geschrieben werden. Schon eine reine Abkürzung für das geschwätzige &lt;tt&gt;document.getElementById&lt;/tt&gt; macht den Code lesbarer:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// Shortcut für document.getElementById&lt;br /&gt;function byId(id) {&lt;br /&gt;  return document.getElementById(id);&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Mit &lt;i&gt;Iteratorfunktionen&lt;/i&gt; füllen die Bibliotheken eine weitere Lücke im JavaScript-Sprachumfang. Diese wird zwar mit JavaScript 1.6 durch Methoden wie &lt;tt&gt;forEach&lt;/tt&gt; geschlossen. Bis dahin aber mag ich nicht warten. Mit wenigen Codezeilen lässt sich die Lücke füllen, indem ich dem Array-Objekt des JavaScript-Standards eine neue &lt;tt&gt;each&lt;/tt&gt;-Methode unterschiebe. Die in der Implementierung verwendete, "ausprogrammierte" indexbasierte (also zwangsläufig hässliche) Schleife hilft, viele indexbasierte Schleifen im Applikations-Code durch besser lesbare &lt;tt&gt;each()&lt;/tt&gt;-Konstrukte zu ersetzen.[1]  &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// --- Array-Iteration mit each()&lt;br /&gt;// Die Callbackfunktion erhält den Wert des Array-Elements als "this"&lt;br /&gt;// Als Argument wird der aktuelle Array-Index übergeben&lt;br /&gt;var _break = {};  // Spezielle Exception, um aus each() auszubrechen&lt;br /&gt;Array.prototype.each = function(f,context) {&lt;br /&gt;  var i,n=this.length;&lt;br /&gt;  if (typeof f != "function") { &lt;br /&gt;    alert( "Programmfehler: each-Argument muss eine Funktion sein"); &lt;br /&gt;    return; &lt;br /&gt;    }&lt;br /&gt;  try {&lt;br /&gt;    for (i=0;i&amp;lt;n;++i) {&lt;br /&gt;      f.call(context||this[i],this[i],i);&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;    catch(e) {&lt;br /&gt;      if (e!=_break) {throw e; } &lt;br /&gt;      }&lt;br /&gt;  };&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Weitere Iteratorfunktionen wie etwa &lt;tt&gt;any()&lt;/tt&gt; und &lt;tt&gt;all()&lt;/tt&gt;, die feststellen, ob ein oder jedes Element des Arrays eine Bedingungsfunktion erfüllt, halte ich für nicht so wichtig. Eine Filter- oder Subset-Funktion dagegen ist hilfreich, um verkettete Methodenaufrufe zu ermöglichen:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// --- subset(filter)&lt;br /&gt;// Die Teilmenge aller Arrayelemente bilden, &lt;br /&gt;// für die die filter-Funktion true ergibt&lt;br /&gt;Array.prototype.subset = function(filter) {&lt;br /&gt;  var result = [];&lt;br /&gt;  this.each( function() {&lt;br /&gt;    if (filter.call(this)) { result.push(this); }&lt;br /&gt;    });&lt;br /&gt;  return result;  &lt;br /&gt;  };&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Nun können auch weitere DOM-Zugriffsfunktionen geschrieben werden wie &lt;tt&gt;byName(name,parentNode)&lt;/tt&gt; und &lt;tt&gt;byTagName(tagname,parentNode)&lt;/tt&gt;  und &lt;tt&gt;byClass(classname,parentNode)&lt;/tt&gt; (wobei der zweite Parameter &lt;tt&gt;parentNode&lt;/tt&gt; optional ist, um nicht das ganze Dokument durchsuchen zu müssen, sondern nur alle Knoten, die unterhalb von &lt;tt&gt;parentNode&lt;/tt&gt; liegen). Zu beachten ist, dass die native Implementierung des Rückgabewertes einer DOM-Funktion wie &lt;tt&gt;getElementsByTagName&lt;/tt&gt; kein Array sein muss, sondern gemäss DOM-Spezifikation nur eine &lt;tt&gt;NodeList&lt;/tt&gt;. Die Implementierung dieser &lt;tt&gt;NodeList&lt;/tt&gt; ist browserspezifisch und nicht in jedem Browser erweiterbar.  Um also den Rückgabewert mit &lt;tt&gt;each()&lt;/tt&gt; iterieren zu können, muss das Ergebnis leider in einen Array abgebildet werden:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// Shortcut für getElementsByTagName&lt;br /&gt;function byTagName(tagname,theParent) {&lt;br /&gt;  var i,n,result=[];&lt;br /&gt;  var nodeList = (theParent||document).getElementsByTagName(tagname);&lt;br /&gt;  for (i=0,n=nodeList.length;i&amp;lt;n;++i) { result.push(nodeList[i]); }&lt;br /&gt;  return result;&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ein Verwendungsbeispiel dieser Funktionen: Um alle Eingabefelder eines mit der ID &lt;tt&gt;adressdaten&lt;/tt&gt; bezeichneten Formulars als Name/Wert-Paare in einem Hash einzusammeln, muss ich folgendes schreiben:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;var fields = {};&lt;br /&gt;byTagName( "input", byId("adressdaten") ).each( function() { &lt;br /&gt;  fields[this.name] = this.value;&lt;br /&gt;  });&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Komplexere und geschachtelte Bedingungen kann man mit der Funktion &lt;tt&gt;byCondition(condition,parentNode)&lt;/tt&gt; realisieren, die den Baum ab dem angegebenen Element traversiert und alle Elemente einsammelt, für die die Funktion &lt;tt&gt;condition&lt;/tt&gt; zu &lt;tt&gt;true&lt;/tt&gt; evaluiert (auch hier ist, weil &lt;tt&gt;NodeList&lt;/tt&gt; kein Array und nicht erweiterbar sein muss, wieder eine indexbasierte Schleife nötig. Je mehr notwendige indexbasierte Schleifen wir aber in eine Bibliothek extrahieren können, umso weniger hat sie der Anwendungscode nötig):&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// --- Alle Elemente, die eine Bedingung erfüllen&lt;br /&gt;function byCondition( condition, theParent) {&lt;br /&gt;  var children, n, i, result = [];&lt;br /&gt;  children = (theParent || document).childNodes;&lt;br /&gt;  n = children.length;&lt;br /&gt;  for (i=0;i&amp;lt;n;++i) {&lt;br /&gt;    if (children[i].nodeType==1) {&lt;br /&gt;      if (condition.call(children[i])) { result.push( children[i] ); }&lt;br /&gt;      Array.prototype.push.apply( result, &lt;br /&gt;        byCondition( condition, children[i] ) );&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  return result;&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Man kann die in &lt;tt&gt;byCondition&lt;/tt&gt; enthaltene Traversierung des DOM auch isoliert benutzen: Wer über &lt;tt&gt;byCondition&lt;/tt&gt; verfügt, hat keine Notwendigkeit mehr, selbst an irgendeiner Stelle noch einmal das rekursive Durchlaufen aller Elemente des DOM zu programmieren. &lt;br /&gt;&lt;br /&gt;Was man sicher braucht, ist eine Funktion &lt;tt&gt;doRequest(url,callback,data,contentType)&lt;/tt&gt;, die HTTP-Zugriffe durchführt (wobei nur die ersten beiden Argumente obligatorisch sind) und die angegebene callback-Funktion in einen Wrapper aufnimmt, um sie bei &lt;tt&gt;readyState = 4&lt;/tt&gt; aufzurufen. Für die meisten Browser, auch für IE ab Version 7, kann dabei auf das Objekt &lt;tt&gt;XMLHttpRequest&lt;/tt&gt; zugegriffen werden. Für frühere IE-Versionen kann man Fallback-Objekte verwenden. Diesen Code zur Instanzbeschaffung, verpackt in der Funktion &lt;tt&gt;getRequestor()&lt;/tt&gt;, zeige ich hier nicht. Er ist direkt aus dem &lt;a href="http://de.wikipedia.org/wiki/XMLHttpRequest"&gt;Wikipedia-Artikel zu XMLHttpRequest&lt;/a&gt; übernommen. &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// --- Browserübergreifender HTTP-Request&lt;br /&gt;// callback,data,action,headerFields sind optional&lt;br /&gt;function doRequest(url,callback,data,action,headerFields) {&lt;br /&gt;  var field,value,&lt;br /&gt;      theData   = data || null,&lt;br /&gt;      requestor =  getRequestor();&lt;br /&gt;  requestor.open(action||"GET",url,!!callback);  &lt;br /&gt;  if (callback) { requestor.onreadystatechange = function() {&lt;br /&gt;     if (requestor.readyState ==4) {&lt;br /&gt;       callback.call(requestor);  // Mit requestor = this aufrufen&lt;br /&gt;       }&lt;br /&gt;     };&lt;br /&gt;    }&lt;br /&gt;  if (headerFields) {&lt;br /&gt;    for (field in headerFields) {&lt;br /&gt;       requestor.setRequestHeader(field,headerFields[field]);&lt;br /&gt;       }&lt;br /&gt;    }&lt;br /&gt;  requestor.send(theData);&lt;br /&gt;  return callback ? requestor : requestor.responseText;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Weiter gibt es je nach Browser verschiedene Arten, einen Ereignisbehandler zu registrieren:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// --- Browserübergreifende Registrierung einer Funktion&lt;br /&gt;function registerFor( theElement, theEvent, theHandler ) {&lt;br /&gt;  var eventNormalized;&lt;br /&gt;  if (typeof theHandler != "function") {&lt;br /&gt;    alert( "Programmfehler: Nur Funktionen können registriert werden");&lt;br /&gt;    return;&lt;br /&gt;    }&lt;br /&gt;  var f = function(e) { theHandler.call(getSource(e),e); };&lt;br /&gt;  if (window.addEventListener) {&lt;br /&gt;    eventNormalized = theEvent.replace(/^on/,""); // immer ohne "on"&lt;br /&gt;    theElement.addEventListener(eventNormalized, f, false);&lt;br /&gt;    }&lt;br /&gt;  else {&lt;br /&gt;    eventNormalized = theEvent.replace(/^(?!on)/,"on"); // immer mit "on"&lt;br /&gt;    theElement.attachEvent(eventNormalized, f );&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Die Validierung, dass der übergebene Behandler wirklich eine Funktion ist, ist sehr nützlich, denn viele Entwickler schreiben aus Gewohnheit &lt;br /&gt;&lt;pre class="sh_javascript"&gt;registerFor(window,"load",doOnLoad&lt;span style="font-weight:bold;color:red"&gt;()&lt;/span&gt;);  // falsch!!!&lt;br /&gt;&lt;/pre&gt;was natürlich grundfalsch ist (sie machen es, weil sie es seit DOM Level 0 gewohnt sind, Ereignisbehandler direkt im HTML zu notieren). Die Klammern nach &lt;tt&gt;doOnLoad&lt;/tt&gt; sind hier falsch: es soll ja nicht die Behandlerfunktion zum Zeitpunkt des Aufrufs von &lt;tt&gt;registerFor&lt;/tt&gt; evaluiert und das Ergebnis dieses Aufrufs registriert werden (das meistens &lt;tt&gt;undefined&lt;/tt&gt; oder &lt;tt&gt;false&lt;/tt&gt; ist), sondern die Funktion &lt;tt&gt;doOnLoad&lt;/tt&gt; für die spätere Ausführung zum Zeitpunkt &lt;tt&gt;load&lt;/tt&gt; vorgemerkt werden.&lt;br /&gt;&lt;br /&gt;Ein Ereignisbehandler muss auf das Element zugreifen können, von welchem aus das Ereignis ausgelöst wurde. Am einfachsten geht das, wenn dieses Element der Funktion bereits als &lt;tt&gt;this&lt;/tt&gt;-Argument bereitsteht, wie es z.B. in Prototype gelöst ist. Das macht auch meine Registrierungsfunktion &lt;tt&gt;registerFor&lt;/tt&gt;: Wie zu sehen ist, wird nicht die übergebene Funktion (&lt;tt&gt;theHandler&lt;/tt&gt;) selbst, sondern ein Wrapper &lt;tt&gt;f&lt;/tt&gt; registriert, der bei Ereignisauslösung zunächst die Ereignisquelle ermittelt und das &lt;tt&gt;this&lt;/tt&gt;-Argument mit diesem Element belegt. Die dafür verwendete browserübergreifende Funktion &lt;tt&gt;getSource(event)&lt;/tt&gt; ist im Grunde ein Einzeiler, aber solche Dinge soll man nur einmal hinschreiben müssen &amp;ndash; die Benennung als &lt;i&gt;Source&lt;/i&gt; (wie bei Microsoft) finde ich übrigens besser als &lt;i&gt;Target&lt;/i&gt; (wie bei Mozilla): &lt;br /&gt;&lt;pre class="sh_javascript"&gt;// Browserübergreifende Ermittlung einer Event-Source&lt;br /&gt;function getSource( event ) {&lt;br /&gt;  return event &amp;&amp; event.target || window.event.srcElement;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;// Browserübergreifende Ermittlung eines key-Codes&lt;br /&gt;function getKeyCode(event) {&lt;br /&gt;  var e = event || window.event;&lt;br /&gt;  return e.keyCode || e.which;&lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;Weitere Funktionen: &lt;tt&gt;setText()&lt;/tt&gt; und &lt;tt&gt;getText()&lt;/tt&gt; setzen und lesen Texte beliebiger Elemente (in &amp;lt;input&gt;-Elementen wird der &lt;tt&gt;value&lt;/tt&gt; verwendet, für andere Elemente der erste textförmige Kindknoten, der ggf. erst anzulegen ist). &lt;tt&gt;gotoURL(url, fields)&lt;/tt&gt; erstellt und versendet ein adhoc-Formular, damit die URL nicht mit Get-Parametern verschmutzt wird.&lt;br /&gt;&lt;br /&gt;Zusammen mit einigen weiteren kleinen Funktionen dieser Art habe ich eine minimalistische JavaScript-Bibliothek erstellt, die &lt;i&gt;unkomprimiert&lt;/i&gt; nur rund 8 KB umfasst und die ich nicht mehr nennenswert erweitern werde:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ruediger-plantiko.net/minlib/"&gt;http://ruediger-plantiko.net/minlib/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;tt&gt;minlib.js&lt;/tt&gt; ist eigentlich bei der Refaktorisierung meiner MVC-JavaScript-Bibliothek &lt;a href="http://bsp.mits.ch/buch/public/mvc/global.js"&gt;global.js&lt;/a&gt; entstanden. Interessanterweise war die Datei vor wie nach der Refaktorisierung ungefähr gleich gross (nämlich 20KB), obwohl sie hinterher die &lt;tt&gt;minlib&lt;/tt&gt;-Funktionen enthielt! Das heisst, die 8KB habe ich bei der Refaktorisierung des restlichen, MVC-spezifischen Teils wieder herausgeholt, indem ich den Code nun kompakter formulieren konnte! Das Ergebnis war eine besser lesbare Datei, die um einige nützliche wiederverwendbare Funktionen angewachsen war.&lt;br /&gt;&lt;br /&gt;[1] Die &lt;tt&gt;each&lt;/tt&gt;-Funktion ist so geschrieben, dass sie funktionsidentisch mit der &lt;tt&gt;each()&lt;/tt&gt;-Funktion des Prototype-Frameworks abläuft. Das ist mit Absicht so gemacht, damit im Bedarfsfall prototype und minlib.js gleichzeitig verwendet werden können.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-2761894701588950975?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/2761894701588950975/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=2761894701588950975' title='2 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/2761894701588950975'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/2761894701588950975'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/02/eine-minimalistische-javascript.html' title='Eine minimalistische JavaScript-Bibliothek'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-1940811958447360153</id><published>2011-02-13T13:46:00.082+01:00</published><updated>2011-12-26T22:32:17.617+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Betrachtungen'/><title type='text'>Allahs ist nicht der Okzident!</title><content type='html'>11. September 2001 - ich war gerade dabei, einen normalen Arbeitstag bei SAP in Walldorf zu beenden. Auf dem Weg nach Hause fiel mein Blick auf den grossen, am Haupteingang angebrachten Monitor, über den normalerweise SAP-Infos und Marketingfilme ausgestrahlt wurden. An diesem Abend zeigte er in einer Sondersendung die schockierenden Bilder von den im Namen Allahs verübten Mordtaten. Eine Traube von Kollegen hatte sich schon darum gebildet, die kopfschüttelnd und sichtlich entsetzt die Bilder verfolgten. Auch ich war fassungslos. Für mich war dieser Tag ein Wendepunkt und eine grausame Offenbarung. Ich fühlte, wie ich innerlich Partei nahm. Alle Differenzen, die es im Westen gibt, sei es zwischen Konservativen und Liberalen, zwischen Bürgerlichen, Sozialisten oder Kommunisten, sei es zwischen Christen und Juden, Agnostikern und Atheisten, all diese Differenzen wurden plötzlich unbedeutend angesichts des &lt;i&gt;Krieges der Barbarei gegen die Zivilisation&lt;/i&gt; (&lt;a href="http://de.wikipedia.org/wiki/Wafa_Sultan"&gt;Wafa Sultan&lt;/a&gt;) [1], der sich hier manifestierte. Alle Unvollkommenheiten oder Ungerechtigkeiten der westlichen Systeme wurden klein und unwichtig angesichts dieses Einbruchs der rohen Gewalt. &lt;br /&gt;&lt;br /&gt;Die Somalierin &lt;a href="http://de.wikipedia.org/wiki/Ayaan_Hirsi_Ali"&gt;Ayaan Hirsi Ali&lt;/a&gt; war genauso entsetzt und fassungslos wie viele im Westen aufgewachsene Menschen. Sie sah sich damals noch selbst als Muslimin. Wieder und wieder las sie die Argumente Osama bin Ladens, mit denen dieser die Taten rechtfertigte. Sie prüfte die Koranstellen und stellte fest, dass es stimmte: Der Koran verlangt wirklich die Tötung Ungläubiger, daran lässt sich nichts herumdeuten.[2] Damit stand ihre Entscheidung fest, dass sie dem Islam den Rücken kehren musste. Auch für sie war dieser Tag demnach ein Wendepunkt. Sie schreibt, dass es ihr nicht leicht fiel, sich von der Angst vor Höllenstrafen loszusagen. Noch lange nach ihrer Abwendung vom muslimischen Glauben ertappte sie sich bei Schuldgefühlen vor Allah. Im Ganzen ist ihre Bilanz aber positiv - sie konnte endlich in einer zivilisierten Gesellschaft ihren Drang nach Freiheit leben. Noch schöner wäre es natürlich, wenn sie dies nicht unter ständigem Polizeischutz tun müsste, denn aus den Reihen ihrer früheren Glaubensgemeinschaft droht man ihr mit Mord &amp;ndash; wie jeder "Apostat" (dramatisierend für: Ausgetretene) nach islamischem Recht des Todes ist: &lt;i&gt;Und wenn sie sich abwenden, dann greift sie und tötet sie, wo immer ihr sie findet, und nehmt euch niemand von ihnen zum Freund oder Helfer!&lt;/i&gt; (Sure 4,89 - mehr dazu unter [3]). Die Polizei fand einen Zettel mit einer gegen Ayaan Hirsi Ali gerichteten Morddrohung auf dem Leichnam des von einem Muslim ermordeten holländischen Regisseurs Theo van Gogh (mit dem zusammen sie einen islamkritischen Film gedreht hatte). Seine Tat selbst hatte der Mörder aus der Sure 5,33 abgeleitet: &lt;i&gt;Siehe, der Lohn derer, welche Allah und seinen Gesandten befehden und Verderben auf der Erde betreiben, ist nur der, dass sie getötet oder gekreuzigt oder an den Händen und Füßen wechselseitig verstümmelt oder aus dem Lande vertrieben werden. Das ist ihr Lohn hienieden, und im Jenseits wird ihnen schmerzliche Strafe&lt;/i&gt;.[4] &lt;br /&gt;&lt;br /&gt;Die Todesdrohung gegen Menschen, die sich vom Islam lossagen, wird nämlich nicht nur gegen prominente Islamkritiker wie Ayaan Hirsi Ali ausgesprochen; auch ist sie keineswegs ein altertümliches Relikt, das nur noch in einigen fernen barbarischen Gesellschaften praktiziert wird. Wir können uns mit keinen Ausreden vor der harten Tatsache schützen, dass die Todesdrohung gegen "Apostaten" inmitten unserer westlichen Gesellschaften lebendig und wirksam ist, wie es zum Beispiel André Glasmacher über in Berlin lebende, zum Christentum konvertierte Iraner und Türken berichtet: Diese Christen müssen entgegen der staatlich zugesicherten Religionsfreiheit ihren Glauben im Verborgenen leben (ähnlich den Urchristen Roms, die sich in den Katakomben trafen) um sich vor ihren ehemaligen muslimischen Glaubensgenossen zu schützen.[5] &lt;br /&gt;&lt;br /&gt;Ayaan Hirsi Alis beeindruckendes Buch &lt;i&gt;Ich bin eine Nomadin&lt;/i&gt; [2] gibt Zeugnis vom Leben und vom Freiheitsdurst einer Persönlichkeit, die sich aus eigener Kraft die Werte der Aufklärung erarbeitet hat. Eindringlich appelliert sie an die Menschen im Westen, nicht leichtfertig diese Werte preiszugeben, die für unter der Scharia lebende Menschen meist unerreichbar sind &amp;ndash; wie die Religionsfreiheit (inclusive der Freiheit, sich von der Religion abzuwenden, in die man hineingeboren ist), die freie Meinungsäusserung, den Grundsatz der Gleichbehandlung  vor dem Gesetz, das aktive und passive Wahlrecht. Dies alles sind in Wahrheit kostbare Dinge, auch wenn wir das durch Gewöhnung oft nicht mehr wahrnehmen. Ihr Buch ist aber auch als Denkanstoss an die im Westen lebenden Muslime gerichtet, deren Probleme sie aus erster Hand kennt. Das Hauptproblem ist demnach die &lt;i&gt;Inschallah&lt;/i&gt;-Mentalität ("so Allah will"): die mangelnde Eigenverantwortung. Statt das Leben mit all den Chancen, die es bietet, aktiv zu ergreifen und die eigene Freiheit zu leben, herrscht eine Versorgungs- und Opfermentalität; es wird ein bequemes generelles Beleidigtsein gepflegt, und die fremden Freiheiten werden dazu genutzt, um Sonderrechte für die Muslime einzufordern. Einwanderern aus islamischen Ländern machen laut Ayaan Hirsi Ali vor allem drei Problemfelder zu schaffen: Geld, Sexualität und Gewalt. In allen drei Bereichen werden auf eine anachronistische und mittlerweile oft selbstzerstörerische Weise Normen der Herkunftsländer gelebt - häufig verbunden mit einer Verachtung der Gastländer: Es war und ist in der islamischen Welt üblich, auf die "Dhimmis", d.h. Christen, Juden und Zoroastrier als Bürger zweiter Klasse herabzuschauen, zu deren Pflichten es gehört, z.B. durch hohe Steuerabgaben, den Muslimen einen höheren Lebensstandard zu ermöglichen.[5a] Ayaan Hirsi Ali fordert die Muslime auf, sich von dieser simplen, aber eingefleischten Mentalität zu trennen, den Westen nicht mehr als &lt;i&gt;Dar al-Harb&lt;/i&gt; (Haus des Krieges) zu betrachten und als Wohnung der &lt;i&gt;Kuffar&lt;/i&gt; (Ungläubigen) geringzuschätzen, sondern die Freiheitsrechte als einen elementaren Grundbestandteil einer modernen Gesellschaft und als Aufforderung an jeden hier lebenden Menschen zu betrachten &amp;ndash; es gilt das ungeheure Potential zu entdecken, das eine freie Gesellschaft für die Entwicklung jedes einzelnen bereithält. &lt;br /&gt;&lt;br /&gt;Besonders beeindruckend fand ich die Leserbriefe, die Ayaan Hirsi Ali von hier lebenden Muslimen und Musliminnen erhält, wie zum Beispiel den folgenden [6, Hervorhebung von mir]:&lt;br /&gt;&lt;br /&gt;&lt;div class="quote"&gt;Eine Frau aus dem Sudan, die in Virginia lebt, schickte mir eine E-Mail: "Ich dachte, als muslimische Frau ist es meine Pflicht, Ihr Buch zu hassen, doch dann habe ich es gelesen und mich mit Ihnen identifiziert. Jedes Gefühl, das Sie in diesem Buch in Worte zu fassen suchten, habe auch ich schon erlebt. Jeden geistigen Konflikt, den Sie mit sich austrugen, habe auch ich schon gespürt... Ich merke, dass ich den Islam verstehen will und es einfach nicht kann. Was hat der Islam an sich, dass er für meine Eltern so verlockend und pefekt ist, für mich aber so falsch erscheint? ... Ich verurteile den Islam nicht, weil ich glaube, dass er eine gewisse Wahrheit in sich birgt &amp;ndash; &lt;i&gt;und wenn ich ihn verurteilen würde, wohin sollte ich dann gehen?&lt;/i&gt;"&lt;/div&gt; &lt;br /&gt;&lt;br /&gt;Das wirft ein Licht auf einen geistigen Gärungs- und Umdenkprozess, der sicher schon bei vielen hier lebenden Muslimen im Gange ist. Die hervorgehobene Stelle zeigt nebenbei die Situation der &lt;i&gt;Erpressung durch die Verhältnisse&lt;/i&gt;, in denen die Muslimin lebt: So strafbewehrt wie der Glaubensabfall in ihrem sozialen System ist, würde es einen ungeheuren Ruck erfordern, sich vom Islam loszusagen &amp;ndash; denn dieser Schritt bedeutet, alle bestehenden Sicherheiten des Lebens zu verlieren: von der eigenen Familie, insbesondere den Kindern entfremdet zu werden und darüberhinaus für seine Existenz selbst sorgen zu müssen. Da ist es verständlich, wie wenige sich zu dem aufraffen, was sie mit der Vernunft bereits als den besseren Weg erkennen.-&lt;br /&gt;&lt;br /&gt;Auf viele Menschen wirkte der Anschlag vom 11. September 2001 so wie auf Ayaan Hirsi Ali: als ein Augenöffner über die wahre, unverhüllte Natur des Islam, über seinen totalitäten politischen Kern. Leider muss ich auch über eine andere Form der Reaktion reden: Die Leichen des Anschlags waren noch nicht kalt, da versuchte man schon, den Islam als Souffleur dieser Morde aus der Schusslinie zu nehmen, zu verschweigen, zu verharmlosen, ja die Akteure selbst als Opfer darzustellen. Da hiess es etwa: Die USA seien aufgrund ihrere Aussenpolitik, zum Beispiel ihrer Israel- und Afghanistanpolitik, selbst schuld an dieser Tat. Man müsse Verständnis mit den Tätern haben, die in einer Notlage radikalen Elementen auf den Leim gegangen seien. Oder, besonders in Europa beliebt: Diese Morde seien einer fehlgegangenen, nicht fürsorglich genug durchgeführten Integrationspolitik zuzuschreiben. All dies sind aber, nüchtern betrachtet, nichts als Verdrehungen der tatsächlichen Verhältnisse &amp;ndash; nach dem Motto: &lt;i&gt;Nicht der Mörder, sondern der Ermordete ist schuld!&lt;/i&gt; &lt;br /&gt;&lt;br /&gt;Die Entschuldigung der Täter, das Verschweigen der von diesen selbst angeführten Motive, die Beschwichtigungsformeln, die Ablenkung von den tatsächlichen Verhältnissen, sind psychologisch verständlich. Sie erklären sich durch einen Mechanismus, den die Psychologen als &lt;i&gt;Identifikation mit dem Aggressor&lt;/i&gt; oder als &lt;a href="http://en.wikipedia.org/wiki/Stockholm_syndrome"&gt;Stockholm-Syndrom&lt;/a&gt; bezeichnen. Die Polizei kennt dieses Verhalten sehr gut von Geiselnahmen. Amerikaner erinnern sich an den Fall von &lt;a href="http://de.wikipedia.org/wiki/Patty_Hearst"&gt;Patty Hearst&lt;/a&gt;, einem jungen Mädchen aus reichem Hause, die 1974 von einer wirren Terrorgruppe entführt wurde. Im Laufe ihrer Geiselhaft wurde sie selbst zu einer überzeugten Anhängerin dieser Gruppe und führte später Raubüberfälle für sie durch. Durch das blosse Erlebnis der Gewalt wurde sie gewissermassen umgepolt, nahm den Standpunkt ihrer Entführer an. Die Identifikation mit dem Aggressor ist aber nicht auf Geiselnahme beschränkt, sondern wird bei allen Formen von angedrohter oder tatsächlich ausgeübter Gewalt wirksam, und vor allem geht sie über ein bloss erpresstes Verhalten weit hinaus. Das gewünschte Verhalten wird nicht bloss widerwillig ausgeführt, sondern begeistert, von ganzem Herzen und voller Faszination. Dank des Mechanismus der Identifikation mit dem Aggressor wird gewissermassen die ganze Individualität erobert. &lt;br /&gt;&lt;br /&gt;Dies ist nur ein Sonderfall der leider allgemeingültigen Regel: Gewalt ist sexy. &lt;i&gt;Immer wieder fühlen sich Frauen zu verurteilten Schwerverbrechern hingezogen, schreiben Liebesbriefe an Mörder, Vergewaltiger oder Bankräuber. In deutschen Gefängnissen gehört dieses Psycho-Phänomen zum Alltag.&lt;/i&gt; [6a] Dies gilt in besonderem Masse und gerade für Jugendliche, wenn die Gewalt mit einer Opposition zu den tragenden Werten der Gesellschaft verknüpft ist. Das bedeutet: Gerade die Blutspur des Islam, die mit seiner Praxis verknüpfte Gewalt, nicht nur in Form von einzelnen Terroranschlägen, sondern auch als institutionalisierter Terror in Ländern wie Saudi-Arabien, Afghanistan, Iran, der einem freiheitsliebenden Menschen im Westen das Blut in den Adern gefrieren lässt, wirkt nicht allein durch die implizit vorhandene Drohung, indem vorauseilend Freiheitsrechte beschnitten werden, sobald als Folge ihrer Ausübung muslimischer Terror zu erwarten ist; darüber noch hinausgehend ist es &lt;i&gt;gerade die Gewalt&lt;/i&gt;, die den Islam für viele Menschen im Westen attraktiv macht. Vielen jungen Menschen, die heute im Westen zum Islam konvertieren, wird allerdings ein böses Erwachen blühen. Denn der Weg zurück ist versperrt: auf die Konversion zur nächsten Modereligion in einigen Jahren steht die Todesstrafe! Den Konvertiten zum Islam steht eine ähnlich harte Landung bevor wie den westlichen Frauen, die sich in eine Beziehung mit einem Muslim begeben haben und deren erschütternde Geschichten auf der Website des &lt;a href="http://www.1001geschichte.de/inhaltsverzeichnis-geschichten-1/"&gt;Bezness Forums&lt;/a&gt; gesammelt wurden.&lt;br /&gt;&lt;br /&gt;Der amerikanische Islamkritiker &lt;a href="http://de.wikipedia.org/wiki/Robert_Spencer"&gt;Robert Spencer&lt;/a&gt; macht sich die Mühe, die im Auftrag des Islam seit dem 11. September begangenen Terroranschläge laufend zu dokumentieren. Er kommt (Stand Februar 2011) auf die gewaltige Zahl von 16.800 tödlichen Anschlägen seit dem 11. September 2001. Das heisst, der Islam forderte in einem Zeitraum von knapp zehn Jahren &lt;i&gt;weit über zehnmal mehr Tote als die spanische Inquisition in ihrer gesamten unseligen 260jährigen Geschichte.&lt;/i&gt;[7]  Wer diese Zahl bezweifelt, möge die Aufstellungen von Robert Spencer untersuchen, sie &lt;i&gt;unterschätzen&lt;/i&gt; meines Erachtens eher die tatsächliche Zahl der Opfer des islamischen Terrors. Angesichts solcher Zahlen kann man nicht mehr, wie es häufig getan wird, von verwirrten Einzeltätern sprechen, von einer Handvoll Geisteskranker, sondern muss sich der Tatsache stellen, dass es sich bei diesen Morden um ein strukturelles, dem Islam inhärentes Phänomen handelt. Auch kann man nicht sagen, dass die Täter den Koran nur falsch interpretieren, denn die im Koran enthaltenen Aufrufe zur Ermordung von Nichtgläubigen und Andersgläubigen lassen praktisch keinen Spielraum für unterschiedliche Deutungen.[8] &lt;br /&gt;&lt;br /&gt;Ein üblicher Verschleierungsversuch ist es, die im Namen des Islam begangenen hässlichen Praktiken nicht dem Islam selbst, sondern den sogenannten &lt;i&gt;Islamisten&lt;/i&gt; zuzuschreiben. Der Islamist sei der Schlimme, der die an sich friedliche Religion des Islam in ihr Gegenteil verkehre und zu einem Mordinstrument mache. Der Islam habe mit diesen schrecklichen Dingen überhaupt nichts zu tun, sei er doch in Wahrheit die "Religion des Friedens" (wofür die Ähnlichkeit der Wörter &lt;i&gt;salam&lt;/i&gt;, Frieden, und &lt;i&gt;islam&lt;/i&gt;, das Sich-Unterwerfen, herangezogen wird). Wenn das stimmt, ginge es nur darum, die kleine Gruppe der "Islamisten", die den Islam pervertieren, ausfindig und unschädlich zu machen &amp;ndash; eine Aufgabe, die man bequem an Geheimdienste und Sicherheitsorgane delegieren kann, ohne dass ein eigener persönlicher Einsatz nötig wäre. Eine schöne Lösung.&lt;br /&gt;&lt;br /&gt;Das Problem dabei ist nur: Die Trennung von Islam und Islamismus oder "islamischem Fundamentalismus" ist eine künstliche. Selbst Muslime wie der türkische Ministerpräsident &lt;a href="http://de.wikipedia.org/wiki/Recep_Tayyip_Erdo%C4%9Fan"&gt;Recep Tyyip Erdogan&lt;/a&gt; (der im Westen ironischerweise selbst als "moderater Muslim"  geführt wird) verwahren sich dagegen [9]:  &lt;br /&gt;&lt;br /&gt;&lt;div class="quote"&gt;Diese Bezeichnungen [moderater Islam / Islamismus, RP] sind sehr hässlich, es ist anstössig und eine Beleidigung unserer Religion. Es gibt keinen moderaten oder nicht-moderaten Islam. Islam ist Islam, und damit hat es sich... Unsere Religion ist ohne Fehler.&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;In der Tat kann der Fundamentalismus an sich niemals das Problem sein, sondern nur dasjenige, &lt;i&gt;auf das der Fundamentalismus angewendet wird.&lt;/i&gt; Ein Fundamentalist ist bloss jemand, der seinen Glauben besonders ernst nimmt, ihn besonders genau und streng anwendet und kein anderes Kriterium als seinen Glauben gelten lassen möchte. Der Fundamentalist ist demnach nicht an sich gefährlich: Es kommt darauf an, &lt;i&gt;an was&lt;/i&gt; er glaubt! Ein fundamentalistischer Buddhist mag sich in die Berge zurückziehen und radikal nach Erleuchtung streben. Eine Gefahr für die Gesellschaft geht von ihm sicher nicht aus. Ein fundamentalistischer Muslim dagegen strebt danach, das "Haus des Islam" zu vergrössern und im religiösen Kampf (Dschihad) möglichst viele Ungläubige zu töten. Das Problem liegt folglich nicht im Fundamentalismus an sich, sondern in dessen Bezugsobjekt - hier also: im Islam selbst. Es wird durch die Menschen offenbar, die den Islam ernstnehmen, weil diese die Inhalte ihrer Religion am deutlichsten zum Ausdruck bringen. &lt;br /&gt;&lt;br /&gt;An dieser Stelle folgt meist der Hinweis auf die 1.6 Milliarden Muslime, die allein aufgrund ihrer hohen Zahl doch nicht alle Terroristen sein können! Das ist ohne Zweifel wahr: Es gibt Muslime, die darauf verzichten, den bewaffneten Kampf gegen die Ungläubigen auszuüben. Es gibt Muslime, die für sich privat nur Teile des Korans und der Überlieferung gelten lassen. Es gibt auch Muslime, die den Koran schlechter kennen als ein durchschnittlicher Islamkritiker &amp;ndash; so wie es Christen gibt, die nicht wissen, was eigentlich zu Ostern gefeiert wird. Es gibt Muslime, die nur qua Geburt Muslime sind und sich eigentlich überhaupt nicht um ihren Glauben scheren. Es gibt Muslime, die nur ein paar ihrer religiösen Pflichten einhalten, ihren Glauben äusserst lax praktizieren. Gott sei Dank ist das so. &lt;br /&gt;&lt;br /&gt;Aber, umgekehrt gefragt: Warum sind &lt;i&gt;alle&lt;/i&gt; Selbstmordattentäter, mit denen wir heute konfrontiert sind, muslimischen Glaubens? Wenn uns die vielen, aus Hass, Rache und Clandenken verübten Gewalttaten von Muslimen immer als Taten von verirrten Einzeltätern präsentiert werden, die den Koran nur falsch verstanden haben: Wo sind dann die Selbstmordattentate, Ehrenmorde, Genitalverstümmlungen, Zwangsverheiratungen von Buddhisten, von Christen, von Baha'i, von Atheisten, die ihren Glauben nur falsch verstanden haben? Warum gibt es sie fast ausschliesslich unter Muslimen, die "wirren Einzeltäter", die "Den-Glauben-bloss-falsch-Versteher"?  Diese Frage bleibt beim Erklärungsmuster des "wirren Einzeltäters" rätselhaft, ja unlogisch. &lt;br /&gt;&lt;br /&gt;Manfred Kleine-Hartlage hat diesen Umkehrschluss einmal durchdekliniert (Hervorhebungen von mir). Zwar ist die grosse Mehrheit der Muslime friedlich, das ist unbestritten &amp;ndash; aber [10]:&lt;br /&gt;&lt;br /&gt;&lt;div class="quote"&gt;&lt;i&gt;Wenn&lt;/i&gt; eine junge Frau auf offener Strasse von ihrem eigenen Bruder erschossen wird, darf man getrost hohe Summen darauf verwetten, dass beide aus einer muslimischen Familie stammen. &lt;i&gt;Wenn&lt;/i&gt; einer Geisel vor laufender Kamera die Kehle durchgeschnitten wird, wird der Täter Muslim sein. Wer von "deutschen (englischen, schwedischen) Schlampen" redet, ist Muslim. Wer "mehr Respekt" für sich und seine Gemeinschaft einfordert, ist Muslim. Wer sich und andere mit einem Dynamitgürtel in die Luft sprengt, einen solchen Mörder zum "Märtyrer" erklärt, das Wort "Ungläubige" benutzt, seine Tochter nicht schwimmen lernen lässt, sie stattdessen lehrt, Gott befehle die Tötung von Juden, ist Muslim. Länder, in denen vergewaltigte Frauen wegen "Ehebruchs" ins Gefängnis geworfen werden, sind islamisch. Länder, in denen die Bibel nicht verkauft werden darf, wohl aber die "Protokolle der Weisen vom Zion", sind islamisch. Länder, in denen Homosexuelle an Baukränen aufgehängt werden, sind islamisch. Länder, in denen gesteinigt wird, sind islamisch.&lt;/div&gt; &lt;br /&gt;&lt;br /&gt;"Wirre Einzeltäter" müssten nach den Gesetzen der Wahrscheinlichkeit unter allen möglichen Glaubensgemeinschaften auftreten. De facto aber konzentrieren sich die aufgeführten Praktiken auf Muslime. Die m.E. einzig logische Erklärung ist: Diese Praktiken leiten sich aus den Lehren des Islam ab, und nicht etwa aus denen der Baha'i, Christen, Buddhisten etc. Es ist also etwas Besonderes um den Islam, das ihn von anderen Bekenntnissen unterscheidet. Diesen Unterschied gilt es wahrzunehmen und herauszuarbeiten, statt ihn zu verschleiern oder wegzudiskutieren.&lt;br /&gt;&lt;br /&gt;Im Westen besteht allerdings ein politisches, praktisches Interesse daran, den Unterschied des Islam von anderen Religionen wegzudiskutieren und alle konkreten Glaubensbekenntnisse einander gleichzumachen. Man tut niemandem weh, wenn man die Religionen alle als gleich behandelt und diese Unterschiede ignoriert, und man muss sich nicht Konsequenzen stellen, die gerade aus den Unterschieden der Religionen resultieren könnten.  &lt;br /&gt;&lt;br /&gt;Eines sollte aber klar sein: Durch Wegschauen dient man einem faulen Frieden und arbeitet selbst an der Zementierung der Ungerechtigkeiten mit, macht sich mitschuldig. Vielmehr ist es nötig, die problematischen Sachverhalte klar auszusprechen: &lt;i&gt;Genau so kann man das Denken öffnen: Durch ehrlichen, offenen Dialog. Vielleicht fliessen Tränen dabei, aber kein Blut&lt;/i&gt; (Ayaan Hirsi Ali, [11]). Das Verschweigen unangenehmer Tatsachen kann sich nicht auf die Toleranz berufen, sondern ist genau besehen nichts als Feigheit: Furcht vor den praktischen Konsequenzen, die aus diesen unangenehmen Tatsachen abzuleiten wären. Das Ideal der Toleranz würde es in Wahrheit erfordern, gerade besonders genau hinzusehen. Toleranz hat keine Augenbinde auf, sondern nimmt die Verschiedenheiten wahr. &lt;br /&gt;&lt;br /&gt;Auch kann Toleranz niemals gegenüber der Intoleranz geübt werden, auf Strafe ihres Untergangs. Toleranz kann nur dann wirken, wenn das nackte Recht des Stärkeren ausgehebelt ist, wenn die Ausübung bürgerlicher Grundrechte wie der freien Meinungsäusserung nicht durch Gewalt oder die blosse Drohung von Gewalt verhindert werden kann. Dies ist nicht erst seit Sir Karl R. Poppers "Die offene Gesellschaft und ihre Feinde" Gemeingut, sondern seit dem 18. Jahrhundert, seit dem Bestehen moderner, aufgeklärter, demokratischer Gesellschaften.[12] Sie könnten gar nicht bis heute bestanden haben, ohne sich gegen ihre äusseren und inneren Feinde zu wehren. &lt;br /&gt;&lt;br /&gt;In Gesprächen höre ich oft: Judentum und Christentum seien doch grundsätzlich dem Islam ähnlich. Das glauben Westeuropäer, die alle Kulturen als grundsätzlich gleichwertig betrachten &lt;i&gt;wollen&lt;/i&gt; und dafür bereit sind, schmerzhafte Unterschiede zu ignorieren oder mit leicht durchschaubaren Argumenten zu verleugnen. Den Frieden der Religionen, den sie sich so sehnlich wünschen, versuchen sie also dadurch herzustellen, dass sie vor den Tatsachen, die diesen Frieden bedrohen oder verhindern, die Augen verschliessen. &lt;i&gt;Das Problem mit den Leuten aus dem Westen ist nicht, dass sie den Islam nicht verstehen. Ihr Problem ist, dass sie den Islam nicht verstehen &lt;b&gt;wollen&lt;/b&gt;&lt;/i&gt;, klagte schon Wafa Sultan.[13]&lt;br /&gt;&lt;br /&gt;Muslime selbst kennen dagegen den wesentlichen Unterschied ihrer Religion zum Christentum und Judentum sehr genau. Schon im 14. Jahrhundert präzisierte der islamische Gelehrte &lt;a href="http://de.wikipedia.org/wiki/Ibn_Khaldun"&gt;Ibn Khaldun&lt;/a&gt; (1332 – 1406) in seiner "Muqaddima" [14], &lt;br /&gt;&lt;br /&gt;&lt;div class="quote"&gt;das Judentum sei zwar fähig, sich politisch im Diesseits zu behaupten, doch es habe keinen universalen Anspruch, umgekehrt habe das Christentum zwar einen universalen Anspruch, doch es verfolge ihn nicht mit politischen und militärischen Mitteln. Der Islam sei beiden Religionen überlegen, weil er beides vereine: "Im Islam ist der Djihad (der Heilige Krieg) gesetzlich vorgeschrieben, weil er einen universalen Auftrag hat und gehalten ist, die gesamte Menschheit freiwillig oder gezwungen zur Religion des Islams zu bekehren".&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Ibn Khaldun weist hiermit auf einen ganz wesentlichen Unterschied des Islam zum Christentum und zum Judentum hin: Der Islam verlangt, die ganze Welt unter sein Gesetz zu stellen, &lt;i&gt;freiwillig oder gezwungen&lt;/i&gt;  &amp;ndash; und das bedeutet natürlich: &lt;i&gt;nur&lt;/i&gt; gezwungen, denn eine Entscheidung kann nicht frei sein, wenn sie von der Drohung des Zwangs begleitet ist. Die Endperspektive ist die totale islamische Herrschaft: &lt;i&gt;Und kämpft gegen sie, damit keine Verführung mehr stattfinden kann, und kämpft, bis sämtliche Verehrung auf Allah allein gerichtet ist. Stehen sie jedoch vom Unglauben ab, dann, wahrlich, sieht Allah sehr wohl, was sie tun.&lt;/i&gt; (Sure 8,39). Dies ist offensichtlich kein theologischer Lehrsatz, sondern eine konkrete, ganz weltlich zu nehmende Handlungsaufforderung. Hierzu wird die Welt von Muslimen in zwei Reiche aufgeteilt: Das &lt;a href="http://de.wikipedia.org/wiki/Dar_al-Islam"&gt;Dar al-Islam&lt;/a&gt;, das Haus des Islam, in dem dieser bereits Staatsreligion und die Scharia gültiges Recht ist, und das &lt;a href="http://de.wikipedia.org/wiki/D%C4%81r_al-Harb"&gt;Dar al-Harb&lt;/a&gt;, das Haus des Krieges, d.h. die noch nicht vom Islam beherrschten Teile der Welt. Zu den Verhaltensregeln für Muslime gehört es eigentlich, sich nicht im Haus des Krieges niederzulassen. Wenn sie sich doch ins Haus des Krieges begeben, so ist ihre Aufgabe, dort den Djihad (wörtlich: &lt;i&gt;Anstrengung&lt;/i&gt; - dazu gehören nicht nur kriegerische Akte, sondern alle Arten des Einsatzes für den Islam) zu führen, also der Sache des Islam zum Siege zu verhelfen. Durch diese Normen erweist sich der Islam als eine totalitäre, nach dem globalen Kalifat, also der Weltherrschaft strebende Ideologie. &lt;br /&gt;&lt;br /&gt;Ibn Khaldun anerkennt dagegen, durchaus im Einklang mit dem Koran, dass die Juden sehr wohl auch diesseitig denken und handeln und ihre Religion auch militärisch zu verteidigen wissen. Der &lt;i&gt;universale Anspruch&lt;/i&gt;, wie ihn der Islam hegt, die eigene Religion zur Religion der ganzen Welt zu machen, ist dem Judentum dagegen fremd. Man könnte hinzufügen, dass es in der Geschichte auch Kriege gab, die den Juden gemäss der Bibel von Gott befohlen wurden, etwa den Eroberungszug gegen die Kanaaniten (Dtn 20,16-18 und Jos 10,40). Dies waren aber zeitlich und räumlich begrenzte Kriege. Der &lt;i&gt;universale Anspruch&lt;/i&gt; fehlt &amp;ndash; und das ist ein wichtiger Unterschied, wie schon Ibn Khaldun erkannte und woraus er die Überlegenheit des Islam ableitete.&lt;br /&gt;&lt;br /&gt;Das Christentum dagegen hat sehr wohl den universalen Anspruch: &lt;i&gt;Darum gehet hin und lehret alle Völker und taufet sie im Namen des Vaters und des Sohnes und des Heiligen Geistes! Und lehret sie halten alles, was ich euch befohlen habe! Und siehe, ich bin bei euch alle Tage bis an der Welt Ende.&lt;/i&gt; (Mt 28,19-20). Es ist aber ein Anspruch der Mission, der Verkündigung. Ibn Khaldun weiss richtig, dass das Christentum nicht gebietet, diesen Anspruch &lt;i&gt;mit politischen und militärischen Mitteln&lt;/i&gt; zu verfolgen. Das Christentum trennt nämlich die weltlichen und geistlichen Dinge: &lt;i&gt;So gebet dem Kaiser, was des Kaisers ist, und Gott, was Gottes ist&lt;/i&gt; (Mt 22,21), und gerade der Kerngehalt der christlichen Lehre hat keine konkreten weltlichen Implikationen, sondern betrifft den Menschen als spirituelles Wesen und eine transzendente, wenn auch in diese Welt hineinleuchtende Ordnung (das "Reich Gottes" oder Himmelreich). Als Religion hätte das Christentum allerdings nicht überlebt, wenn sich die Gemeinschaft der an Christus Glaubenden, also die Kirche, nicht auch weltlich formiert hätte, zu einer Institution dieser Welt geworden wäre. Diese Institution befindet sich in einem permanenten Spannungsverhältnis zu der Botschaft, zu deren Schutz, Erhaltung und Verbreitung sie angetreten ist. Das kann gar nicht anders sein, denn eine Institution, die &lt;i&gt;dem Bösen keinen Widerstand&lt;/i&gt; (Mt 5,39) leistete, bedingungslos &lt;i&gt;ihre Feinde liebte&lt;/i&gt; (Mt 5,44) und &lt;i&gt;nicht richtete, auf dass sie nicht gerichtet werde&lt;/i&gt; (Mt 7,1), könnte in dieser Welt nicht lange bestehen.&lt;br /&gt;&lt;br /&gt;Der fundamentale Unterschied des Christentums zum Islam wird in einem "Exkurs" von Manfred Kleine-Hartlage sehr schön herausgearbeitet.[15] Schon das Welt- und Menschenbild unterscheidet sich grundlegend. Der Islam bejaht die Welt in der Form, wie sie vorgefunden wird. Für ihn ist der gläubige Mensch so, wie er ist, ein Instrument Allahs. Es gibt keine grundlegende Kritik an dieser Welt. Diese Welt ist Gottes Schöpfung, die gesamte vorgefundene Welt- und Geselllschaftsordnung ist Teil von Gottes Wille. Das macht den Islam extrem konservativ: Zur Zeit Mohammeds gab es Sklaverei, Polygamie, Beschneidung von Frauen, beduinische Raubzüge, Clan- und Gewaltherrschaft an Stelle garantierter Rechte für alle. Allah/Mohammed bejaht und legitimiert all diese Praktiken, er hat an keiner etwas auszusetzen, solange sie im Rahmen der Umma, der muslimischen Gemeinschaft ausgeübt wird. Der Islam ist dieser Welt gegenüber affirmativ eingestellt. Zwar kennt auch der Islam die jenseitige Welt, den Himmel. Aber bereits das Diesseits ist von Gott in seiner Grundstruktur genau so gewollt, wie es ist. Der Muslim kann sich in dieser Welt als verlängerter Arm Gottes fühlen. Mit dem Koran hat er eine göttliche Wegleitung in der Hand, die einfach formuliert und nicht interpretationsbedürftig ist. Indem sich der Muslim Allah unterwirft (was der Wortsinn von "Islam" ist), wird er zum Teil der Gemeinschaft der Gläubigen in dieser Welt, der &lt;i&gt;besten Gemeinde, die je für die Menschen entstand. Ihr gebietet das, was Rechtens ist, und ihr verbietet das Unrecht.&lt;/i&gt; (Sure 3,110). Er darf sich also durch seine Teilhabe an der Umma dem Rest der Menschheit überlegen fühlen und geniesst gegenüber den beiden anderen Klassen von Menschen, den Dhimmis und den Ungläubigen, dank der Scharia Sonderrechte (eingeschlossen das Recht, Gewalt gegenüber diesen auszuüben). &lt;br /&gt;&lt;br /&gt;Das Christentum ist grundsätzlich anders: Der Christ leidet an der Welt und in ihr. Für das Christentum ist die Welt, wie sie ist, aus der Ordnung Gottes herausgefallen. Sie ist nicht mehr in ihrem ursprünglichen, heiligen, ganzen Zustand &amp;ndash; sondern krank. Zwar deutet alles in ihr noch auf den reinen, unverfälschten Ursprung. Aber dieser Ursprung ging durch eine Art historischer, universal-menschheitlicher Katastrophe verloren: den "Sündenfall". Der Mensch kennt nur noch die Sehnsucht nach dem "verlorenen Paradies", und die Dinge dieser Welt erinnern ihn daran. Aber in ihrer konkreten Gestalt enthalten sie so viel Ungöttliches, ja Widergöttliches, dass sich eine affirmative Haltung zur Welt für den Christen verbietet. Das Reich Gottes &lt;i&gt;ist nicht von dieser Welt&lt;/i&gt; (Jo 18:36), und &lt;i&gt;alle Weisheit dieser Welt ist Torheit vor Gott&lt;/i&gt; (1. Kor. 1,20). Der Mensch hat durch die &lt;i&gt;Nachfolge Christi&lt;/i&gt; eine metaphysische Perspektive auf die Wiederherstellung einer ursprünglichen göttlichen Ordnung, denn Christus ist das Licht (Jo 8,12), das in diese Welt hineinleuchtet, um sie in der von Gott gewollten Ordnung auferstehen zu lassen &amp;ndash; und Christus ist als &lt;i&gt;neuer Adam&lt;/i&gt; der erste, der diesen Weg gegangen ist (Röm 5,12-15). Der Christ kann daher in seinem Handeln nie ganz in dieser Welt aufgehen &amp;ndash; es bleibt immer ein Rest, ein Vorbehalt. In der Konsequenz bejaht das Christentum seinem ganzen Wesen nach das Säkularitätsprinzip, die Trennung der weltlichen von der göttlichen Ordnung. Für den Christen gilt der bereits zitierte, wichtige Grundsatz: &lt;i&gt;Gebt dem Kaiser, was des Kaisers ist und Gott, was Gottes ist&lt;/i&gt; (Mk 12,13). Weder das Aufbegehren gegen die gesellschaftliche Ordnung um einer besseren Ordnung willen [15a], noch die vollständige Eingliederung in ein gesellschaftliches System, können Sache des Christen sein. Er lebt in der Welt, ist aber als Geistwesen zugleich dieser Welt enthoben. Partei nehmen für eine weltliche Sache &amp;ndash; das ist etwas, worin er nie ganz aufgehen kann. Jesus, der Sohn Gottes und zugleich irdischer Mensch, vereinte in sich die beiden Ordnungen. Aber sein ganzes Leben zeigt, welche Präferenzen er setzte: Er lebte in den niederen, wenig geachteten Gesellschaftsschichten und starb - aus Liebe zu den Menschen - den schmählichen Tod am Kreuz, den Tod eines Verbrechers &amp;ndash; und dies, obwohl er niemals zur Gewalt aufgerufen hatte. Da dies muslimischem Welt- und Religionsverständnis fundamental entgegenläuft, verleugnet Mohammed/Allah in Sure 4,157 den Kreuzestod Jesu und hängt stattdessen der gnostischen Lehre an, "ein anderer" sei an Jesu Stelle gekreuzigt worden. &lt;br /&gt;&lt;br /&gt;Nun ist aber gerade der Kreuzestod Jesu eines der zentralen Elemente des christlichen Glaubens, ohne den auch die Perspektive der Heilung, des wiederhergestellten Lebens in der Auferstehung undenkbar wäre. Der Bischof &lt;a href="http://de.wikipedia.org/wiki/Melito_von_Sardes"&gt;Melito von Sardes&lt;/a&gt; &amp;ndash; er lebte im 2. Jahrhundert, als auch die heidnischen Mysterien noch verbreitet waren &amp;ndash; beschreibt dieses "neue Mysterium" [16]:&lt;br /&gt;&lt;br /&gt;&lt;div class="quote"&gt;Die Natur erschauderte und sprach erstaunt:&lt;br /&gt;Was ist dies für ein neues Mysterium?&lt;br /&gt;&lt;br /&gt;Der Richter wird gerichtet und verhält sich ruhig,&lt;br /&gt;Der Unsichtbare wird geschaut und scheut sich nicht,&lt;br /&gt;Der Unfassbare wird erfasst und entrüstet sich nicht,&lt;br /&gt;Der Unmessbare wird gemessen und widerstrebt nicht,&lt;br /&gt;Der Leidlose leidet und rächt sich nicht,&lt;br /&gt;Der Unsterbliche stirbt und weigert es nicht.&lt;br /&gt;&lt;br /&gt;Was ist dies für ein neues Mysterium? &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;&lt;br /&gt;Wenn Gott sich nach Ansicht der Christen in Gestalt eines in dieser Welt absolut ohnmächtigen Menschen offenbart hat, so kann der Glaube nicht mit Gewalt verbreitet werden, sondern nur auf freier Entscheidung jedes einzelnen basieren. Der Mensch wird durch die Glaubensentscheidung zum Kind Gottes, statt nur ein ausführender Sklave des Gotteswillens zu sein: &lt;i&gt;Denn alle, die sich vom Geist Gottes leiten lassen, sind Söhne Gottes. Denn ihr habt nicht einen Geist empfangen, der euch zu Sklaven macht, so dass ihr euch immer fürchten müsstet, sondern ihr habt den Geist empfangen, der euch zu Söhnen macht, den Geist, in dem wir rufen: Abba, Vater! So bezeugt der Geist selber unserem Geist, dass wir Kinder Gottes sind&lt;/i&gt; (Röm 8,14-15). Das ist der Grund, warum gerade die Freiheit ein wichtiges Fundament des christlichen Glaubens ist. &lt;br /&gt;&lt;br /&gt;Die Kirche als durchaus weltliche Institution steht, wie erwähnt, in einem Spannungsverhältnis zu der radikal unpolitischen Botschaft Jesu, zu deren Schutz und Überlieferung sie ja eigentlich existiert. Als weltliche Gemeinschaft wird die Kirche angreifbar, fehlerhaft, irdisch und kann natürlich auch von Christen im Namen der christlichen Botschaft angegriffen werden. Wie viel weltliche Macht gerade noch ausgeübt werden darf und kann, um die christliche Botschaft und Gemeinschaft vor dem Untergang zu bewahren, der aus einer konsequenten Realisierung der zentralen christlichen Lehren resultieren würde, war Gegenstand eines lang andauernden Kampfes zwischen kirchlicher und weltlicher Macht. So ist die Geschichte der Kirche auch eine Geschichte von Glaubensspaltungen und Schismen, die in der Reformation als einem vorläufigen Höhepunkt gipfelten.  &lt;br /&gt;&lt;br /&gt;Es ist eine verbreitete, aber falsche Auffassung, dass die Aufklärung eine reine Gegenbewegung zum Christentum war. Auf einer oberflächlichen Ebene ist das richtig, denn einige Aufklärer stellten sich energisch gegen Kirche und Christentum, und unbestritten gab es auch von kirchlicher Seite heftigen Widerstand gegen die Aufklärung. Das ist aber nicht entscheidend. In ihrem Kern sind die Grundwerte der Aufklärung nämlich nichts anderes als &lt;i&gt;transformierte, säkularisierte christliche Werte&lt;/i&gt;. Die Aufklärung hat auf keinem anderen Boden entstehen können als dem des christlichen Abendlandes. Werte, mit denen ein moderner Atheist oder Agnostiker lebt, leiten sich teilweise direkt aus christlichen Normen ab. Zu solchen, direkt aus dem Christentum hervorgehenden Normen zählen: das soziale Element der &lt;i&gt;Hinwendung zu den Schwachen&lt;/i&gt;, das &lt;i&gt;Verbot zu richten&lt;/i&gt;, das &lt;i&gt;Liebesgebot&lt;/i&gt; inclusive der &lt;i&gt;Feindesliebe&lt;/i&gt;, sowie &lt;i&gt;rückbezügliche Normen&lt;/i&gt; in Form einer &lt;i&gt;für alle Menschen&lt;/i&gt;, nicht nur für die Angehörigen des eigenen Glaubens, gültigen Goldenen Regel: &lt;i&gt;Alles nun, was ihr wollet, das euch die Leute tun sollen, das tut ihr ihnen&lt;/i&gt; (Mt 7,12).[17] &lt;br /&gt;&lt;br /&gt;Ob wir wollen oder nicht, ob wir uns als Christen verstehen oder nicht: Die rückbezüglichen Normen des Christentums sind uns in Fleisch und Blut übergegangen, wir können gar nicht mehr (oder nur mit grösster Mühe) aus ihnen heraustreten. Beispielsweise sind aktuelle Ideologien wie der Kulturrelativismus nichts anderes als die verinnerlichte christliche Norm: &lt;i&gt;Richtet nicht, auf dass ihr nicht gerichtet werdet&lt;/i&gt; (Mt 7,1). Die Kritik an jemand anderem oder an einer Gruppe, der wir nicht angehören, verursacht dem Abendländer einen inneren Schmerz. Er ist darauf abgerichtet, Kritik sofort mit Selbstkritik zu relativieren und das, was er dem anderen aufgeben will, zuerst einmal und besonders streng sich selbst aufzugeben. &lt;br /&gt;&lt;br /&gt;Das ist eine wunderbare Norm &amp;ndash; die jedoch nur solange funktionieren kann, wie auch das Gegenüber sie verinnerlicht hat. Jemand, der diese Norm nicht kennt, erlebt die radikal auf sich selbst zurückgeworfene Kritik als Selbsterniedrigung, als Zugeständnis der eigenen Minderwertigkeit. Im Falle des Islam wirkt also unser ständiges reflektierendes Zaudern, unser skrupulöses Haften an der Rechtsstaatlichkeit, die permanente Selbstkritik, die laute Anprangerung selbst geringfügiger Vergehen gegen garantierte Grundrechte, das Zulassen radikaler Kritik um der Meinungsfreiheit willen, das schonungslose Aussprechen von eigenen Misständen, nur in einem Sinne: Als &lt;i&gt;Selbsteingeständnis der Minderwertigkeit unserer Kultur&lt;/i&gt;. Da dies vollständig mit dem Bild konform ist, das der Islam sowieso von der christlich-jüdischen Kultur zeichnet, so dass Muslime sich ermuntert sehen, für ihre eigene Kultur offensiver einzutreten, erweisen sich genau die Normen mit relativierendem Rückbezug, deren Umsetzung in Form von freiheitlichen Gesellschaftsordnungen die bislang grösste Errungenschaft der Menschheit darstellt, als die eigentliche Achillesferse unserer gesamten abendländischen Zivilisation.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Wafa Sultan: In ihrer glasklaren, &lt;a href="http://www.youtube.com/watch?v=ISNpOkpcWqg&amp;NR=1"&gt;auf Al-Jazira ausgestrahlten Stellungnahme&lt;/a&gt; die mittlerweile zu einem Klassiker avanciert ist, begründet sie, dass der Konflikt nicht, wie Samuel P. Huntington meint, ein "Kampf der Zivilisationen" sei: denn Zivilisation kämpfen nicht, sie wetteifern. Vielmehr seien wir Zeugen eines Kampfes der Barbarei gegen die Zivilisation. &lt;br /&gt;[2] Ayaan Hirsi Ali, &lt;i&gt;Ich bin eine Nomadin&lt;/i&gt;, Piper Verlag, München 2010, S. 13. Zur Ableitung von Gewalt und Mord gegen Ungläubige siehe auch Fussnoten [4] und [8].&lt;br /&gt;[3] Die Apostasie ist laut Koran das Allerschlimmste, was ein Muslim tun kann, und er wird dafür für immer verflucht sein (Sure 4, 137). Siehe auch Sure 16,106 und Sure 4,36ff. sowie 9,68. Ein informatives Gutachten hierzu liefert Christine Schirrmacher auf &lt;a href="http://www.igfm.de/Wenn-Muslime-Christen-werden-Glaubensabfall-und-Todesstrafe-im.466.0.html"&gt;http://www.igfm.de/Wenn-Muslime-Christen-werden-Glaubensabfall-und-Todesstrafe-im.466.0.html&lt;/a&gt;  &lt;br /&gt;[4] Auch dieses Koranzitat fand sich auf dem an das Opfer gehefteten Zettel, siehe &lt;a href="http://widerworte.wordpress.com/2010/09/17/gewaltaufrufe-im-koran/"&gt;http://widerworte.wordpress.com/2010/09/17/gewaltaufrufe-im-koran/&lt;/a&gt;.&lt;br /&gt;[5] André Glasmacher, &lt;i&gt;Angst vor dem strafenden Islam&lt;/i&gt;, Tagesspiegel vom 21.4.2007, &lt;a href="http://www.tagesspiegel.de/berlin/angst-vor-dem-strafenden-islam/v_default"&gt;http://www.tagesspiegel.de/berlin/angst-vor-dem-strafenden-islam/v_default&lt;/a&gt;,837238.html&lt;br /&gt;[5a] Bat Ye'or, &lt;i&gt;Der Niedergang des orientalischen Christentums unter dem Islam&lt;/i&gt;, Resch Verlag, Gräfelfing 2005. &lt;br /&gt;[6] Ayaan Hirsi Ali, a.a.O., S. 277.&lt;br /&gt;[6a] &lt;a href="http://www.bild.de/BILD/news/2010/04/14/mord-in-liebeszelle-frauen/die-faszination-des-abscheulichen.html"&gt;Die Faszination des Abscheulichen&lt;/a&gt;, Bild online vom 14.4.2010. Die sogenannten &lt;i&gt;Serial Killer Groupies (SKG)&lt;/i&gt; sind Gegenstand kriminalpsychologischer Untersuchungen (siehe &lt;a href="http://www.trutv.com/library/crime/criminal_mind/psychology/s_k_groupies/4.html"&gt;http://www.trutv.com/library/crime/criminal_mind/psychology/s_k_groupies/4.html&lt;/a&gt;). &lt;br /&gt;[7] Robert Spencer, &lt;a href="http://ruediger-plantiko.net/cgi-bin/attacks.pl"&gt;Anschläge im Namen Allahs seit dem 11. September 2001&lt;/a&gt;, &lt;a href="http://thereligionofpeace.com"&gt;http://therelegionofpeace.com&lt;/a&gt;. Zu den Todesopfern der spanischen Inquisition, siehe &lt;a href="http://en.wikipedia.org/wiki/Spanish_Inquisition#Death_tolls"&gt;http://en.wikipedia.org/wiki/Spanish_Inquisition#Death_tolls&lt;/a&gt;  &lt;br /&gt;[8] Es genügt, wenn - im Rahmen einer Arbeitsteilung - auch nur ein Teil der Muslime sich für die konkrete Umsetzung der insgesamt 206 Koranstellen zuständig fühlt, die explizit zur Gewalt gegen Ungläubige aufrufen (eine Auflistung dieser Stellen enthält z.B. die Webseite &lt;a href="http://www.koran.terror.ms/"&gt;http://www.koran.terror.ms/&lt;/a&gt;) &amp;ndash; solange sie von der restlichen Umma nicht klar bekämpft werden, wird so ein Klima der Angst geschaffen, des Appeasement und der Erpressung, wie es sich vor unseren Augen tatsächlich entfaltet. Seine "Fatwa" von 1998 zur Rechtfertigung des Terrorismus gegen Amerika gründet Bin Laden unter anderem auf Sure 9,5: "Und wenn die heiligen Monate abgelaufen sind, dann tötet die Götzendiener, wo immer ihr sie findet, und ergreift sie und lauert ihnen aus jedem Hinterhalt auf."  (&lt;a href="http://www.mideastweb.org/osamabinladen2.htm"&gt;http://www.mideastweb.org/osamabinladen2.htm&lt;/a&gt;). &lt;br /&gt;[9] Erdogan, zitiert in der Zeitung Milliyet vom 21.8.2007. Siehe &lt;a href="http://www.politik.de/forum/religion/212757-ministerpraesident.html"&gt;http://www.politik.de/forum/religion/212757-ministerpraesident.html&lt;/a&gt;&lt;br /&gt;[10] Manfred Kleine-Hartlage, Das Dschihad-System. Wie der Islam funktioniert. S.9. &lt;br /&gt;[11] Ayaan Hirsi Ali, a.a.O., S. 268.&lt;br /&gt;[12] Karl R. Popper, &lt;i&gt;Die offene Gesellschaft und ihre Feinde&lt;/i&gt;, Zwei Bände, Francke Verlag, Tübingen 1980.&lt;br /&gt;[13] Wafa Sultan, &lt;a href="http://www.920whjj.com/cc-common/mediaplayer/player.html?redir=yes&amp;mps=helenglover.php&amp;mid=http://a1135.g.akamai.net/f/1135/18227/1h/cchannel.download.akamai.com/18227/podcast/PROVIDENCE-RI/WHJJ-AM/helen%2012-3-09%209am%20good.mp3?CPROG=PCAST?CCOMRRMID&amp;CPROG=RICHMEDIA&amp;MARKET=PROVIDENCE-RI&amp;NG_FORMAT=newstalk&amp;NG_ID=whjj920am&amp;OR_NEWSFORMAT=&amp;OWNER=&amp;SERVER_NAME=www.920whjj.com&amp;SITE_ID=1160&amp;STATION_ID=WHJJ-AM&amp;TRACK"&gt;Interview in der Helen Glover Show vom 12.3.09&lt;/a&gt;.&lt;br /&gt;[14] Ibn Khaldun, &lt;i&gt;Muqaddima&lt;/i&gt;. Zitiert aus &lt;a href="http://koptisch.wordpress.com/2010/09/17/die-grunde-fur-die-christlichen-kreuzzuge/"&gt;http://koptisch.wordpress.com/2010/09/17/die-grunde-fur-die-christlichen-kreuzzuge/&lt;/a&gt;&lt;br /&gt;[15] Manfred Kleine-Hartlage, a.a.O. Exkurs: Warum das Christentum mit der Moderne vereinbar ist, der Islam aber nicht, S.135-146. &lt;br /&gt;[15a] Der Umsturz der Ordnung, in der er lebt, ist nicht Sache des Christen - wohl aber der Kampf gegen Unrecht. Beispielsweise bekämpften Christen schon in den ersten nachchristlichen Jahrhunderten die Sklaverei, weil sie ihrem Grundsatz der Gleichheit der Menschen vor Gott widerspricht (während es im Islam nie einen nennenswerten Widerstand gegen die Sklaverei gab und in vielen islamischen Ländern die Sklaverei erst durch die Kolonialherren abgeschafft wurde). Siehe hierzu Egon Flaig, &lt;i&gt;Weltgeschichte der Sklaverei&lt;/i&gt;, Beck Verlag, München 2009, S. 199ff.&lt;br /&gt;[16] Melito von Sardes, zitiert aus &lt;a href="http://www.kathsurf.at/gebete/vorfahren/vorfahren.html#Ein neues Mysterium"&gt;http://www.kathsurf.at/gebete/vorfahren/vorfahren.html#Ein neues Mysterium&lt;/a&gt;&lt;br /&gt;[17] Im Gegensatz zum Christentum kennt der Islam keine universale, für alle Menschen gültige Goldene Regel. Im Koran kommt eine Goldene Regel überhaupt nicht vor. Im Gegenteil ist die Aufteilung der Menschen in Höherwertige (Muslime) und Minderwertige (Dhimmis, Ungläubige) ein klarer Verstoss gegen die Goldene Regel, die sich durch den ganzen Koran und natürlich die Scharia zieht, die sich als &lt;i&gt;Recht der Ungleichbehandlung&lt;/i&gt; erweist. Auch die &lt;a href="http://www.politicalislam.com/blog/golden-rule-islam/"&gt;Hadithstelle 1,2,13&lt;/a&gt; lässt sich nicht als Gegenbeweis heranziehen, denn die (saudische) Übersetzung lässt keinen Zweifel, dass diese Regel auf die eigene Glaubensgemeinschaft beschränkt ist, womit ihr gerade das wesentliche Kriterium der universellen Anwendbarkeit fehlt, das die Goldene Regel ausmacht. Die Goldene Regel würde dem Koran und der islamischen Wirklichkeit widersprechen.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-1940811958447360153?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/1940811958447360153/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=1940811958447360153' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/1940811958447360153'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/1940811958447360153'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/02/allahs-ist-nicht-der-okzident.html' title='Allahs ist nicht der Okzident!'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-7394858032957042611</id><published>2011-02-11T22:04:00.033+01:00</published><updated>2011-02-17T17:34:03.187+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Astrologie'/><title type='text'>Eine Mohammedstudie von Johannes Kepler</title><content type='html'>In einer kleinen, 1604 erschienen Schrift &lt;i&gt;Judicium Kepleri de Prognostico P. Sutorii&lt;/i&gt; setzt sich der Astronom und kaiserliche Hofmathematiker &lt;a href="http://wiki.astro.com/astrowiki/de/Johannes_Kepler"&gt;Johannes Kepler&lt;/a&gt; mit einer Untersuchung des Astrologen Paulus Sutorius über Mohammed und die Zukunft des Islam auseinander. Dieser hatte laut Kepler prognostiziert, die Christenheit werde in den Jahren von 1604 bis 1644 besonders schweren Wirrnissen ausgesetzt, die als Strafe für ihre &lt;i&gt;halsstarrigen Sünden&lt;/i&gt;[1] anzusehen seien. Die Schrift von Sutorius gipfelte in der Vorhersage, dass für das Jahr 1700 &lt;i&gt;die von allen wahrhaftigen Christen sehnlich erwartete letzte Wiederkunft unseres himmlischen Königs und Heilands Jesu Christi sich herzu nahe und diese Welt hiermit ihr Ende nehmen werde.&lt;/i&gt;[1] Kepler nimmt zu der Schrift von Sutorius Stellung und diskutiert ihre astrologischen, politischen und historischen Argumente sehr ausführlich.&lt;br /&gt;&lt;br /&gt;Für Astrologen ist es eine interessante Frage, welche Horoskope damals einer Untersuchung über den Islam zugrundegelegt wurden - denn um dessen Heimsuchungen handelt es sich bei den prophezeiten &lt;i&gt;schrecklichen Wirrnissen&lt;/i&gt;. &lt;br /&gt;&lt;br /&gt;Da Mohammed nicht fürstlicher Abstammung war, waren seine genauen Geburtsdaten unbekannt. Es scheint auch im Islam keine Quelle zu geben, in der eine Art zweifelsfreier, offizieller Geburtszeit mitgeteilt wurde (wohl aber gab es eine, dem Koran beigegebene Zeittafel mit einem Geburtsdatum, die er &lt;i&gt;Chronicum ad Alcoranum&lt;/i&gt; nennt). Kepler fasst zusammen:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Mercator will, er sei geboren Anno Christi 569, Chronicum ad Alcoranum 571, 21. Sept., alii 596. 23. April, Sutorius 594, 23. April.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Kepler findet das Geburtsjahr 594 unplausibel. Denn damit wäre Mohammed zum Zeitpunkt der Hedschra erst 28 Jahre gewesen &amp;ndash; zu jung für ein Unterfangen, &lt;i&gt;das mehr im verborgenen Rat bestand als in blosser Kühnheit.&lt;/i&gt; Kepler wusste um die Hedschra als ein sicheres Datum: &lt;i&gt;Denn gewiss, dass er anno 622, da er schon lange zuvor gepredigt gehabt, hat fliehen müssen und in solcher seiner Flucht sich zum weltlichen Haupt aufgeworfen.&lt;/i&gt; Später dazu: &lt;i&gt;Er muss ohne Zweifel schon viele Jahre vorher mit seiner Lehre, für die ein rechtes Alter nötig ist, umhergezogen sein, bis er sich so viel Gefahr und Feindschaft auf den Hals geladen hatte, dass er entweichen musste.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Er geht nun auf die Deutung des Horoskops durch Sutorius ein, wobei er die Gelegenheit nicht auslässt, das Glücksrad (&lt;i&gt;pars fortunae&lt;/i&gt;) als einen fiktiven Punkt zu erklären, dem man wie allen arabischen Punkten keine Bedeutung beizumessen habe.  &lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-y6OKhwghb-I/TVXROWAZSnI/AAAAAAAAAKA/ay4dt0jyJhI/s1600/mohammed_nach_sutorius.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 398px; height: 400px;" src="http://2.bp.blogspot.com/-y6OKhwghb-I/TVXROWAZSnI/AAAAAAAAAKA/ay4dt0jyJhI/s400/mohammed_nach_sutorius.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5572590158296468082" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Im Mohammed-Horoskop nach Sutorius ist der Mond eleviert und steht in den Fischen in seiner Erhöhung, ist demnach stark gestellt. Er bildet ein Sextil zu einer Merkur/Venus-Konjunktion und steht in Opposition zu Mars. Hierzu schreibt Kepler, &lt;i&gt;dass die Vermischung Mercurii et Veneri in sextili Lunae zu allerhand Wollust neige. Welches sich wohl auf des Mahomet Lehre reimt, der die Polygamie zulässt; und weil aus der Geschichte bekannt ist, dass Kepler durch Heirat aufgestiegen ist, ist der Erfahrungssatz zu zitieren: wenn der Mond vornehm gestellt und von vielen Planeten durch Aspekte bestrahlt wird, wie hier, so bedeutet dies vornehme, reiche Heirat.&lt;/i&gt;   &lt;br /&gt; &lt;br /&gt;Obwohl er also das Horoskop von Sutorius in vielen Punkten für passend befindet, lehnt er es ab, weil der Geburtstermin nach seiner Ansicht zu spät sei. Er hält sich lieber an ein &lt;i&gt;Chronicon Arabicum&lt;/i&gt;, das dem Koran beigefügt sei und als Geburtstermin den 21. September 571 mittags angibt. Da er den Mond dann mit 10° Widder angibt, muss er sich um einen Tag verrechnet haben, denn dort stand der Mond bereits am 20. September. Um der Nachvollziehbarkeit des Textes folgt hier die Geburtsfigur für den 20. September 571: &lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-QsLlzvamHdU/TVXRevJ6-vI/AAAAAAAAAKI/Q_Mf4YTiA_0/s1600/mohammed_nach_kepler.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 395px;" src="http://4.bp.blogspot.com/-QsLlzvamHdU/TVXRevJ6-vI/AAAAAAAAAKI/Q_Mf4YTiA_0/s400/mohammed_nach_kepler.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5572590439925218034" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Alle Planeten ausser Mond innerhalb 45 Grad. Sonne Mond, Mars und Merkur sowie Drachenkopf und Drachenschwanz in der Nähe der Äquinoktialpunkte bedeuten eine herausragende Person, weil auch die Finsternisse dieses und des darauffolgenden Jahres in Widder und Waage stattfinden. Dazu führt Bodinus aus Plutarch an, dass die Finsternis in der Nähe von Äquinoktialpunkten immer grosse Veränderungenm gebracht habe. Saturn und Jupiter vereinigt am Anfang des Skorpions, unweit davon die Venus. Dies ist bedeutsamer, als wenn sie nur durch ein Sextil, und noch ausserhalb der wässrigen Zeichen, verbunden wären. Sonne und Mars vereinigt bringen eine mutige, vorwärtspreschende Natur. Mars und Merkur in einer fast gradgenauen Konjunktion bei Spica Virginis, [...] bedeuten einen Aufrührer, Räuber und Mörder, Betrüger [falsarium] und alles andere, was Röslin oben aus der blossen Triangulation beider Planeten deduziert.- Cardanus will, dass Spica Virginis in Zusammenhang mit der christlichen Religion steht. Hier würde nun der christlichen Religion grosser Schaden bedeutet, durch Mord und Lügen, propter Martem et Mercurium conjunctos Spicae. Dort aber, und wenn 594 das richtige Jahr wäre, stünde der gütige Jupiter bei Spica. Nun hat Mahomet der christlichen Religion nicht so gütlich getan, dass es dieser Bedeutung bedürfte.- Mond in Opposition Merkur bedeutet den grossen Geist, aber auch Verwirrtheit, Konfusion. Ebenso bedeutet der Vollmond nahe der Opposition des Mars Jähzorn. Diese beiden Deutungen sind übliche astrologische Regeln. &lt;/i&gt;[2]&lt;br /&gt;&lt;br /&gt;Sutorius ist laut Kepler nun so vorgegangen, dass er &lt;i&gt;aus Mohammeds Radixhoroskop gleichsam eine Allegorie macht und daraus durch Direktionen mit acht Signifikatoren die achtmal acht wichtigsten Änderungen im Türkischen Reich zwingend herleiten will.&lt;/i&gt; Kepler hält es dagegen für besser, mit einem Horoskop der Hedschra zu arbeiten, denn dieses würde gewissermassen der Krönung Mohammeds entsprechen: &lt;i&gt;Es soll einen an diesen grossen Astrologen Röslin und Sutorios schon Wunder nehmen, dass sie die Inthronisation so gar nicht beachten, von der unsere astrologischen Autoritäten sonst so viel halten... es ist aus der Geschichte wohlbekannt, der erste Muharram [arabischer Monatsname], von welchem die Araber ihre Hedschra anfangen, sei der Tag, an welchem Mohammed ausgezogen und von seiner Banditenrotte zum Haupt gewählt worden war. Denn Hedschra heisst gemäss Scaliger so viel wie Verfolgung wegen des Glaubens, oder Exil. Dieser erste Muharram der Hedschra fängt nach jüdischer Art am Abend des 15. Juli an und fällt in den 16. Juli, einen Freitag, im Jahre Christi 622, und am Himmel ist folgende Figur gestanden:&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/-a1prlsF4fog/TVXGDiuJ9vI/AAAAAAAAAJ4/8zUCNM93GFU/s1600/hedschra.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 394px;" src="http://3.bp.blogspot.com/-a1prlsF4fog/TVXGDiuJ9vI/AAAAAAAAAJ4/8zUCNM93GFU/s400/hedschra.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5572577878103160562" /&gt;&lt;/a&gt;&lt;br /&gt; &lt;br /&gt;Es folgt eine Deutung, die Kepler (wie so oft) &lt;i&gt;nach den Regeln der Astrologen&lt;/i&gt; vornimmt, die nicht seine eigenen sind:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Ohne Zweifel haben Sutorius und Röslin diese Figur ignoriert, weil sie ihnen nicht sonderlich getaugt hat: Denn nach astrologischer Lehre findet sich hier nichts besonders Wichtiges, und Jupiter und Mars sind in entgegengesetzten Zeichen, Mars steht verworfen im achten Haus, Jupiter ist rückläufig, Glückspunkt mit Drachenschwanz im gegenüberliegenden Domizil des Saturn [in Wassermann], dieser selbst in seinem Fall [nämlich auch in Löwe], im Quadrat zur Himmelsmitte. Das wären schlechte Bedeutungen. Doch hätten sie auch manches gefunden, was zu loben gewesen wäre: Jupiter steht in seinem Domizil, im Trigon zum MC, Sonne und Mond in Rezeption, der Mond mit Drachenkopf, Regulus und Venus, dass also auch hier der Mond wieder trefflich passt. Ausserdem ist das Medium Coeli am Ort der grossen Konjunktion der Jahre 571 und 630 [nämlich am Anfang des Skorpion.] Auch der Jupiter, der den Ort der Grossen Konjunktion von 631 günstig bestrahlt [aus den Fischen], während die Sonne im dritten der Wasserzeichen steht [im Krebs]. Weil das Medium Coeli die Herrschaft bedeutet, könnten diese Konstellationen den Grossen Konjunktionen subsumiert werden, was die Argumentation Röslins weiter stützt, das wässrige Trigon würde die Mohammedaner begünstigen. Aber das Feuer kann er dann nicht verwerfen [als für die Mohammedaner irrelevant betrachten], weil der Mond darinnen so trefflich wohl steht. Auch würde die in diesem Reich herrschende Polygamie gut zu dieser Stellung des Mondes passen, weil wieder Venus und Merkur sich mit ihm mischen. Mars als Herrscher des Aszendenten [durch Erhöhung] und des Medium Coeli [durch Domizil], im Sextil zur Sonne und im Trigon zum Aszendenten bedeutet kriegerische, streitbare Regenten, im Haus des Merkur [Jungfrau] betrügerisch und treulos. Jupiter, Herrscher von Sonne und Mond [nämlich via Dekanat?], bedeutet aus religiösen Dingen abgeleitete Ehre und Herrschaft, und dass König und Priester eine Person sind. Saturn mit Mondknoten zwischen den Lichtern [also in der hier kurzen Strecke zwischen Sonne und Mond] steht für tyrannische Regenten und Zerstörer, für starke Polizei und Ordnung.&lt;br /&gt;&lt;br /&gt;Dies alles habe ich nach den Lehren der Astrologen abgeleitet, um zu zeigen, dass es Sutorius gar nicht nötig gehabt hätte, in neuer, unpassender Weise aus der Nativität Mohammeds Direktionen zu berechnen, statt, wie es die Astrologen lehren, das Horoskop der Inthronisation für die Direktionen zu verwenden, das das Reich mehr betreffen wird als das persönliche Horoskop Mohammeds.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Vermutlich sind die hier aufgeführten Horoskope Mohammeds konstruiert. Auch die Araber waren der Astrologie mächtig und dürften ihren Propheten zu einer astrologisch passenden Zeit in die Welt treten lassen. Anders steht es um die Hedschra: Da mit der Hedschra tatsächlich eine neue Zeitzählung begann, ist dieses Datum vermutlich korrekt. Das Horoskop wird auch anderwärts in der astrologischen Literatur verwendet.[3]  Ob die Geschichte des Islams wirklich in Konkordanz mit den Direktionen dieses Horoskops verläuft, wäre einmal zu untersuchen. &lt;br /&gt;&lt;br /&gt;Der immer wieder hervorgehobene Zusammenhang mit der Mutation der Grossen Konjunktionen ins Wasserelement (Konjunktion vom 29.8.571 jul. auf 3°31' Skorpion) ist jedenfalls bedenkenswert. Interessant ist auch die Zuordnung des Wasserelements zum Islam und des Feuerelements zum Christentum (weil das Leben Jesu mit der Mutation ins Feuerelement begann), worin Kepler offenbar mit Röslin und Sutorius übereinstimmt.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Kepler, Judicium de Prognostici P. Sutorii (1604), in: Opera Omnia, ed. C. Frisch, Band VIII, S. 302. Siehe &lt;a href=" http://books.google.de/ebooks/reader?id=-pcqAAAAYAAJ&amp;printsec=frontcover&amp;output=reader"&gt;http://books.google.de/ebooks/reader?id=-pcqAAAAYAAJ&amp;printsec=frontcover&amp;output=reader&lt;/a&gt;, das Gutachten beginnt auf S. 300. Alle im Text folgenden Zitate stammen aus diesem Gutachten Keplers. Die Wörter habe ich teilweise um der besseren Verständlichkeit in heutiges Deutsch übertragen. Kommentare in eckigen Klammern sind von mir hinzugefügt.&lt;br /&gt;[2] Kepler findet einige dieser Konstellationen verwandt mit denen des ihm vorliegenden Lutherhoroskops für den 10. November 1483, 11. Stunde - und bemerkt an dieser Stelle, dass es einen Streit um das richtige Geburtsjahr Luthers gibt.&lt;br /&gt;[3] So in José Luis San Miguel de Pablos, &lt;i&gt;Uranus-Neptun-Konjunktionen&lt;/i&gt;, Verlag Hier und Jetzt, Hamburg 1993, S. 35. Ihm geht es um die Uranus-Neptun-Konjunktion vom 18.9.623 jul., die sich im Hedschra-Horoskop bereits anbahnt.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-7394858032957042611?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/7394858032957042611/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=7394858032957042611' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/7394858032957042611'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/7394858032957042611'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/02/eine-mohammedstudie-von-johannes-kepler.html' title='Eine Mohammedstudie von Johannes Kepler'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-y6OKhwghb-I/TVXROWAZSnI/AAAAAAAAAKA/ay4dt0jyJhI/s72-c/mohammed_nach_sutorius.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-7517581074612596532</id><published>2011-01-06T19:01:00.039+01:00</published><updated>2011-05-17T08:07:01.604+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Domänenspezifische Programmierung in ABAP</title><content type='html'>Mit dem Thema &lt;i&gt;domänenspezifischer Sprachen&lt;/i&gt; (DSL) habe ich mich hier schon beschäftigt und werde es noch häufiger tun, da ich es für ein zukunftsweisendes Feld in der Programmierung halte. &lt;br /&gt;&lt;br /&gt;Die einfachste und dem ABAP-Entwickler geläufigste Form der domänenspezifischen Programmierung ist es sicher, &lt;i&gt;Customizingtabellen&lt;/i&gt; zu definieren und für die Steuerung des Systems zu verwenden [1]: In einer gut definierten Customizingtabelle sind z.B. die möglichen Festwerte einer (DDIC-)Domäne unmittelbar in der Fachsprache der Anwendung nachvollziehbar. &lt;br /&gt;&lt;br /&gt;Manchmal ist der Rahmen einer Datenbanktabelle für die gewünschte Form der Steuerbarkeit des Systems unzureichend: Man möchte z.B. flexibler sein. Vielleicht ist das relationale Datenmodell, das ausschliesslich Schlüssel/Wert-Beziehungen ableitet, für die Abbildung der gewünschten Steuerung unpassend. Oder man möchte die Steuerung des Programms im Quelltext der Klasse hinterlegen, die die Logik ausführt, um sie nahe beim Code selbst zu haben. &lt;br /&gt;&lt;br /&gt;Eine Lösung ist es dann, die Logik so in einer Methode zu programmieren, dass der Quelltext dieser Methode radikal auf den Anwendungskontext beschränkt ist - die Programmierung selbst läuft gewissermassen hinter den Kulissen. Das kann man in ABAP mit Macros und der Doppelpunkt/Komma-Syntax erreichen.[2]&lt;br /&gt;&lt;br /&gt;Ein Beispiel. In einer Klasse namens &lt;tt&gt;ZCL_GDBW_SELECTOR&lt;/tt&gt; hatte ich die Aufgabe implementiert, Selektionen von verschiedenen externen Datenbanktabellen auszuführen, die im wesentlichen ähnlich strukturiert sind. Die Details, in denen sie voneinander abweichen, habe ich in einer statischen Hashtabelle festgehalten, die beim Laden der Klasse aufgebaut wird. Das Auffüllen dieser Tabelle ist programmiertechnisch ein harmloser, geradezu langweiliger Vorgang. Die &lt;tt&gt;insert&lt;/tt&gt;-Anweisungen sowie die Zuweisungen an die Felder des Arbeitsbereichs gehören zum "Rauschen" der Sprache (&lt;i&gt;syntactic noise&lt;/i&gt;): Notwendig zwar für die Maschine, für den Menschen aber erschweren sie den Blick auf das Wesentliche. In diesem Fall habe ich diese Anweisungen in Macros verborgen, so dass nur die wesentlichen, die steuernden Parameter im Quelltext der Methode stehenbleiben: &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;method build_info_on_tables_and_conns.&lt;br /&gt;  data: ls_table_data type ty_table_data,&lt;br /&gt;        ls_con_data   type ty_con_data.&lt;br /&gt;&lt;br /&gt;  _add_table_data&lt;br /&gt;*  Connection     Node          Zusatz&lt;br /&gt;*                 Databox       Tabellenname&lt;br /&gt;   'GDBW'         'gdnf1bw'     ' '      :&lt;br /&gt;                  'INBOX'       'V_INFO_DATEN_RECV',&lt;br /&gt;                  'OUTBOX'      'V_INFO_DATEN_SEND',&lt;br /&gt;                  'ALL'         'V_INFO_DATEN_ALL',&lt;br /&gt;                  'ARCH'        'V_INFO_DATEN_SEND_ARCH'.&lt;br /&gt;&lt;br /&gt;  _add_table_data&lt;br /&gt;    'MVN'         'gdnf2bw'      'X'      :&lt;br /&gt;                  'INBOX'        'V_INFO_DATEN_RECV_NF',&lt;br /&gt;                  'OUTBOX'       'V_INFO_DATEN_SEND_NF',&lt;br /&gt;                  'ARCH'         'V_INFO_DATEN_SEND_ARCHIV'.&lt;br /&gt;&lt;br /&gt;* ...&lt;br /&gt;&lt;br /&gt;* Q-Verbindungen&lt;br /&gt;  _copy_table_data_from_to :&lt;br /&gt;                   'MVN'   'QMVN',&lt;br /&gt;                   'KMVN'  'KQMVN',&lt;br /&gt;*                   ...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Der "Domänenexperte" ist in diesem Fall jemand, der sich mit den externen Datenbanken und ihren Verbindungen zum SAP-System auskennt. &lt;br /&gt;&lt;br /&gt;Man beachte in diesem Beispiel auch die Verwendung der Doppelpunkt/Komma-Notation: Der vor dem Doppelpunkt stehende Teil des Statements wird der Reihe nach als immer gleicher Vordersatz für alle durch Komma getrennten, nach dem Doppelpunkt stehenden Hintersätze verwendet.  Auch dies erleichtert den Blick auf das Wesentliche. Alternativ hätte ich auch die Vordersätze n-mal hinschreiben müssen. Die Doppelpunkt/Komma-Notation und Macros haben mir in diesem Beispiel zu einer radikalen Durchsetzung von &lt;i&gt;Don't repeat yourself&lt;/i&gt; verholfen. Keine Information muss mehrfach hingeschrieben werden, auch keine Programmzeile.&lt;br /&gt;&lt;br /&gt;Ein anderes Beispiel aus meinem BSP-Thread &lt;a href="http://groups.google.com/group/bsp-praxis/browse_thread/thread/eccc01740584040c"&gt;Eingabebereitschaft in deklarativer Syntax&lt;/a&gt; zeigt den Einsatz dieser Technik, um zu steuern, wann welche Buttons in einer Anwendung aktiv, inaktiv und wann sie unsichtbar sind - abhängig vom Transaktionsmodus (Anzeigen, Ändern, Anlegen). Auch hier wieder die Reduktion auf das Wesentliche. Die tatsächliche Manipulation des Feldstatus erfolgt hinter den Kulissen, verborgen im Macro: &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;     _set_button_state_per_mode : &lt;br /&gt;*      FCODE    \  CREATE     CHANGE     DISPLAY &lt;br /&gt;       'ACTU'      active     active     disabled, &lt;br /&gt;       'SAVE'      active     active     disabled, &lt;br /&gt;       'UNDO'      active     invisible  invisible, &lt;br /&gt;       'CANCEL'    invisible  active     disabled, &lt;br /&gt;       'PRINT'     invisible  active     active. &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Wieder erlaubt es die Anwendung, die Steuerung in Tabellenform zu notieren. Das bedeutet, für diese Art von Steuerungen wäre es im Prinzip auch möglich, kleine Customizingtabellen zu definieren, die dasselbe leisten.[3]&lt;br /&gt;&lt;br /&gt;In beiden Fällen habe ich bewusst keine Customizingtabelle gewählt, schon aus Gründen der Sparsamkeit: Wenn ich alle Fallentscheidungen und Steuerungen dieser Art in Customizingtabellen ausprägen würde, würde ich das System mit Customizingtabellen geradezu fluten. &lt;br /&gt;&lt;br /&gt;Ausserdem hätte ich dann nicht nur einen Ort, sondern zwei, die für die Steuerung zuständig sind: Um später in Problemfällen zu verstehen, warum ein bestimmter Status gewählt wird, muss ich mir die Einträge der Customizingtabelle &lt;i&gt;und&lt;/i&gt; das Programm ansehen, das sie auswertet. &lt;br /&gt;&lt;br /&gt;In der Macro-Notation habe ich dagegen alles an einem Ort, im selben Programmobjekt. Das Macro dient mir nur dazu, die Programmlogik &lt;i&gt;innerhalb des Objekts&lt;/i&gt; von den eigentlich wichtigen steuernden Werten zu trennen, denn diese sind das Wesentliche. Die hinter den Kulissen laufende Programmlogik ist für alle Buttons die gleiche. Einmal programmiert - und mit einem Unit Test abgesichert - wird man in der Wartung nie mehr mit dem Code zu tun haben. Wohl aber mit den steuernden Werten: Es kann zum Beispiel ein neuer Button dazukommen. Oder ein neuer Auftraggeber findet es geschmackvoller als sein Vorgänger, Buttons grundsätzlich auf &lt;tt&gt;invisible&lt;/tt&gt; zu schalten, wenn sie nicht verwendbar sind. Das sind typische Fälle von Änderungen - und immer müssen nur die Werte im obigen Codeabschnitt angepasst oder erweitert werden.&lt;br /&gt;&lt;br /&gt;Macros und die Doppelpunkt/Komma-Syntax verhelfen also in ABAP zur Erstellung von &lt;i&gt;internen DSL&lt;/i&gt;s, also Sprachen, die eingebettet in den normalen ABAP-Kontext verarbeitet werden.&lt;br /&gt;&lt;br /&gt;Weitere Möglichkeiten ergeben sich durch die in ABAP integrierten XSLT- und JavaScript-Prozessoren. In meinem &lt;a href="http://bsp.mits.ch"&gt;BSP-Framework&lt;/a&gt; stellt die in der Datei &lt;tt&gt;config.xml&lt;/tt&gt; ausgeprägte Flow Logic eine DSL für die Ablaufsteuerung der Views in einer BSP-Applikation dar. Sie wird mit einer einzigen ABAP-Anweisung in ein Set von internen Tabellen gewandelt (siehe den Konstruktor der Klasse &lt;a href="http://bsp.mits.ch/code/clas/zcl_mvc_framework"&gt;zcl_mvc_framework&lt;/a&gt;):&lt;br /&gt;&lt;pre class="sh_abap"&gt;* config.xml in ABAP-Daten wandeln&lt;br /&gt;  call transformation (config_to_abap) source xml lv_sxml&lt;br /&gt;         result application = ls_application&lt;br /&gt;                tree        = tree&lt;br /&gt;                controller  = controllers&lt;br /&gt;                model       = models.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In anderen Fällen kann es sinnvoller sein, statt XML die JavaScript-Objektnotation (&lt;a href="http://json.org"&gt;JSON&lt;/a&gt;) zu verwenden. Denn XML produziert auch selbst wieder einen beachtlichen &lt;i&gt;syntactic noise&lt;/i&gt;. Die JavaScript-Daten, die man z.B. in einem Standardtext (&lt;tt&gt;SO10&lt;/tt&gt;) ablegen könnte, können entweder mit dem von mir &lt;a href="http://ruediger-plantiko.blogspot.com/2010/12/ein-json-parser-in-abap.html"&gt;in diesem Blog&lt;/a&gt; vorgestellten JSON-Parser oder mit dem in ABAP zugänglichen JavaScript-Interpreter &lt;tt&gt;cl_java_script&lt;/tt&gt; eingelesen werden. Steuernde, sitzungsübergreifend relevante Informationen sollten, nachdem sie in ABAP-Daten gewandelt wurden, auf jeden Fall auf dem Application Server gepuffert werden.[4]&lt;br /&gt;&lt;br /&gt;Aber auch JSON ist nicht ganz frei von &lt;i&gt;syntactic noise&lt;/i&gt;. Der Anwender - und der DSL-Programmierer - will ja nicht die vielen geschweiften Klammern und Anführungszeichen sehen, sondern auf den ersten Blick bereits die wesentlichen Informationen. Dann kommen wir allmählich in die Gefilde einer "echten", &lt;i&gt;externen DSL&lt;/i&gt;. Die Notation der Daten wird für ein bestimmtes spezifisches Problem ad hoc entworfen. Ein typisches Beispiel einer solchen DSL ist ein CSS-Sheet. Die CSS-Notation ist optimal auf genau einen Zweck zugeschnitten: Das Erscheinungsbild von HTML-Elementen zu steuern.&lt;br /&gt;&lt;br /&gt;Eine externe DSL muss in ein semantisches Modell übersetzt werden. Für jede Notation wird ein eigener Übersetzer benötigt. Das klingt nach einem beträchtlichen Extraaufwand, da mit einem solchen Übersetzer ja eine weitere Softwarekomponente benötigt wird. Der Aufwand, einen Parser für DSLs oder an einen Parser angekoppelte Übersetzer zu schreiben, wird aber tendenziell überschätzt. Je eingeschränkter die Verwendung der DSL ist &amp;ndash; und ein eingeschränkter Sprachumfang gehört zu den Charakteristika einer DSL &amp;ndash; umso einfacher wird es auch, sie zu parsen, vor allem wenn man mit testgetriebener Entwicklung arbeitet (siehe zu diesem Thema den schönen Bliki &lt;a href="http://www.martinfowler.com/bliki/ParserFear.html"&gt;ParserFear&lt;/a&gt; von Martin Fowler). Parser sind geradezu ein Paradebeispiel für die Effizienz testgetriebener Entwicklung, da sie so wenig Abhängigkeiten haben: Sie wandeln nur einen textförmigen Input in einen Syntaxbaum oder irgendein semantisches Modell um. Es bestehen keine weiteren Abhängigkeiten, etwa von Datenbanktabellen. Auch müssen ausser Basisfunktionen - etwa im Bereich der Stringverarbeitung - keine weiteren API's aufgerufen werden.  &lt;br /&gt;&lt;br /&gt;Für die Arbeit auf der Stringebene stehen uns in ABAP darüberhinaus die mächtigen regulären Ausdrücke zur Verfügung. Das dem eigentlichen Parsen oft vorangestellte &lt;i&gt;Tokenizing&lt;/i&gt; lässt sich häufig bereits durch eine Folge von einfachen &lt;tt&gt;replace regex&lt;/tt&gt;-Konstrukten realisieren.&lt;br /&gt;&lt;br /&gt;Zur Illustration mag eine DSL zur Formulierung von sogenannten &lt;i&gt;Verpackungsregeln&lt;/i&gt; dienen. Mit einer Verpackungsregel kann gesteuert werden, wie der Inhalt von mehreren SAP-Lieferungen auf einzelne Paletten aufzuteilen oder zu gruppieren ist. Ich habe für solche Verpackungsregeln einen kleinen Editor gebaut. Die Regeln werden von unserem SAP CC  zum Test verschiedener Prozessfälle gepflegt und verwendet. Hier ein Beispiel:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;1. Palette ( &lt;br /&gt;  2. Palette ( &lt;br /&gt;    1. Lieferung, 1. Pos.&lt;br /&gt;      )&lt;br /&gt;  3.Palette ( &lt;br /&gt;    1. Lieferung, Rest&lt;br /&gt;      )&lt;br /&gt;  4.Palette ( &lt;br /&gt;    2. Lieferung, 1. Pos.&lt;br /&gt;    2. Lieferung, 2. Pos., 50%&lt;br /&gt;      )&lt;br /&gt;  5.Palette ( &lt;br /&gt;    2. Lieferung, Rest&lt;br /&gt;     )&lt;br /&gt;   )&lt;/pre&gt;&lt;br /&gt;Hier wird also eine hierarchische Handling Unit mit dem Inhalt von zwei Lieferungen bestückt, wobei eine Gruppenbildung von Lieferpositionen oder Teilmengen von Lieferpositionen möglich ist. Eine solche Regel kann mit einem Namen versehen und zur Laufzeit in einem Testfall zum automatischen Verpacken herangezogen werden. Ein Parser validiert die syntaktische Richtigkeit der Regel bereits zur Designzeit und erstellt ein semantisches Modell in Form von internen Tabellen. Dieses semantische Modell wird dann zur Laufzeit auf die real vorliegenden Lieferungen angewendet.&lt;br /&gt;&lt;br /&gt;Zur Illustration folgt hier der Tokenizer für diese DSL, der vollständig mit regulären Ausdrücken notiert ist. Er erzeugt aus den vom Benutzer eingegebenen Regeln eine Folge von wohldefinierten, normalisierten Tokens in Form einer &lt;tt&gt;stringtab&lt;/tt&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;  lv_norm = iv_code.&lt;br /&gt;&lt;br /&gt;  translate lv_norm to upper case.&lt;br /&gt;  replace all occurrences of regex :&lt;br /&gt;&lt;br /&gt;* Komma wird wie ein Trenner behandelt&lt;br /&gt;    ','                          &lt;br /&gt;          in lv_norm with gc_space,&lt;br /&gt;&lt;br /&gt;* Normierung der Schlüsselwörter&lt;br /&gt;    '\bLIEF(\.|ER\.|ERUNG)?'       &lt;br /&gt;          in lv_norm with ' LIEF ',&lt;br /&gt;    '\bPOS(\.|ITION)?'             &lt;br /&gt;          in lv_norm with ' POS ',&lt;br /&gt;    '\bPAL(\.|ETTE)?'              &lt;br /&gt;          in lv_norm with ' PAL ',&lt;br /&gt;    '\b(ALLES|REST)'               &lt;br /&gt;          in lv_norm with ' REST ',&lt;br /&gt;&lt;br /&gt;* Mehrfache Leerzeichen, Zeilenumbrüche etc. auf je 1 Space normieren&lt;br /&gt;    '\s+'                        &lt;br /&gt;          in lv_norm with gc_space,&lt;br /&gt;&lt;br /&gt;* Menge mit Postfix MG&lt;br /&gt;    '([\d.]+) ?(CU|%)'         &lt;br /&gt;          in lv_norm with '$1 MG $2',&lt;br /&gt;&lt;br /&gt;* Keywort folgt nach Nummer&lt;br /&gt;    '(\d+)\. ?(LIEF|POS|PAL)'  &lt;br /&gt;          in lv_norm with '$1 $2',&lt;br /&gt;&lt;br /&gt;* 2x, da Tokens aneinanderhängen können: "(REST)"&lt;br /&gt;    '([\d.]+|\(|\)|REST|LIEF|POS|PAL)(\(|\)|REST|LIEF|POS|PAL)'  &lt;br /&gt;          in lv_norm with '$1 $2',&lt;br /&gt;    '([\d.]+|\(|\)|REST|LIEF|POS|PAL)(\(|\)|REST|LIEF|POS|PAL)'  &lt;br /&gt;          in lv_norm with '$1 $2'.&lt;br /&gt;&lt;br /&gt;  shift lv_norm left deleting leading space.&lt;br /&gt;  split lv_norm at space into table et_code.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Die Tokentabelle wird nun in einem folgenden Schritt in eine Tabelle von Tokens gewandelt; diese Tabelle enthält für jeden Token ein allfällig erfordertes numerisches Argument.&lt;br /&gt;&lt;pre class="sh_abap"&gt;  loop at lt_code into lv_token.&lt;br /&gt;    case lv_token.&lt;br /&gt;      when '('.&lt;br /&gt;        _add_token ct_tokens bra space.&lt;br /&gt;      when ')'.&lt;br /&gt;        _add_token ct_tokens ket space.&lt;br /&gt;      when 'REST'.&lt;br /&gt;        _add_token ct_tokens rest space.&lt;br /&gt;      when 'PAL'.&lt;br /&gt;        _add_token ct_tokens pal lv_number.&lt;br /&gt;      when 'LIEF'.&lt;br /&gt;        _add_token ct_tokens lief lv_number.&lt;br /&gt;      when 'POS'.&lt;br /&gt;        _add_token ct_tokens pos lv_number.&lt;br /&gt;      when 'MG'.&lt;br /&gt;        _add_token ct_tokens meng lv_number.&lt;br /&gt;      when 'CU' or '%'.&lt;br /&gt;        _add_token ct_tokens einh lv_token.&lt;br /&gt;      when others.&lt;br /&gt;        find regex '(\d+)' in lv_token submatches lv_number.&lt;br /&gt;        if sy-subrc ne 0.&lt;br /&gt;* Fehlermeldung - unbekanntes Token lv_token&lt;br /&gt;          _raise_syntax lv_token text-003.&lt;br /&gt;        else.&lt;br /&gt;* Kein clear lv_number!&lt;br /&gt;          continue.&lt;br /&gt;        endif.&lt;br /&gt;    endcase.&lt;br /&gt;    clear lv_number.&lt;br /&gt;  endloop.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In dieser Form kann sie nun vom Builder Befehl für Befehl abgearbeitet werden, um das gewünschte semantische Modell zu erzeugen.&lt;br /&gt;&lt;br /&gt;Wenn wir in ABAP einen Parsergenerator aus der mächtigen Familie der &lt;i&gt;Parsing Expression Grammars&lt;/i&gt; (PEG) zur Verfügung hätten, etwa &lt;a href="http://www.tinlizzie.org/ometa/"&gt;OMeta&lt;/a&gt;, so liesse sich ein Parser für diese DSL mit wesentlich weniger Codezeilen notieren. Das folgende OMeta-Objekt dient zugleich als Parser, als Builder des semantischen Modells (hier in Form eines AST mit verschachtelten JavaScript-Arrays) wie auch zur Definition der verwendeten Grammatik. Darüberhinaus ist es auch lesbarer als die oben ausprogrammierte Folge von regulären Ausdrücken, die demselben Zweck dient.&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;ometa HandlingUnitDefinition &lt;: Parser { &lt;br /&gt;  ordnum = digit+:ds '.'            -&gt; parseInt(ds.join('')),&lt;br /&gt;// Vollständige Lieferung mit allen Pos. bekommt Posnr 0:&lt;br /&gt;  dlv  = ordnum:n ( "Lieferung" | "Lief" | "LF"  )     -&gt; ["DLV", n, 0],&lt;br /&gt;// Eine SSCC (Serial Shipment Container Code = ID einer Handling Unit)&lt;br /&gt;  sscc = ordnum:n ( "Palette"   | "Pal" | "SSCC" )     -&gt; ["SSCC", n], &lt;br /&gt;// Ausdruck für eine Position&lt;br /&gt;  item = ordnum:n ( "Position"  | "Pos" | "POS"  )     -&gt; ["POS", n], &lt;br /&gt;// Lieferposition&lt;br /&gt;  dlvItem      = dlv:d "," spaces item:p       -&gt; ["DLV",d[1],p[1]], &lt;br /&gt;// Rekursiver Aufbau einer HU in beliebige Tiefe:&lt;br /&gt;  contentPart = dlvItem | dlv | hu | sscc,&lt;br /&gt;// Regel für den Inhalt einer HU&lt;br /&gt;  content = contentPart:x (spaces contentPart)*:xs &lt;br /&gt;          -&gt; [x].concat(xs),&lt;br /&gt;  hu      = sscc:s spaces "(" spaces content:c spaces ")"   &lt;br /&gt;          -&gt;  ["HU", s[1],c],&lt;br /&gt;// expr definiert die gesamte Regel und damit die DSL:&lt;br /&gt;  expr    = spaces hu:first (spaces hu)*:more      &lt;br /&gt;          -&gt; [first].concat(more)              &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Diese OMeta-Grammatik ist fast vollständig äquivalent zu dem von mir ausprogrammierten ABAP-Code, von dem ich oben einen Auszug gezeigt habe! Das zeigt Perspektiven, in die man gehen könnte, um DSLs noch schneller und einfacher zu formulieren und zu definieren. Da es eine JavaScript-Implementierung von OMeta gibt, könnten sich Grammatiken auch in der ABAP-Welt verwendet werden - denn es gibt ja den in der Klasse &lt;tt&gt;CL_JAVA_SCRIPT&lt;/tt&gt; verschalten JavaScript-Interpreter. Die sogenannten &lt;i&gt;semantischen Aktionen&lt;/i&gt;, die nach jeder Regel hinter dem Pfeil-Operator &lt;tt&gt;-&gt;&lt;/tt&gt; folgen, könnten darüberhinaus Callbacks in ABAP enthalten. Dass der &lt;tt&gt;CL_JAVA_SCRIPT&lt;/tt&gt; ressourcenintensiv ist und dass eine interpretierte Sprache nicht besonders schnell ist, gibt auch keinen Grund zur Beunruhigung. Denn das Ergebnis des Parse-Vorgangs wird man sowieso auf dem Application Server puffern, da es normalerweise für alle Benutzer identisch ist. &lt;br /&gt;&lt;br /&gt;Statt ein semantisches Modell in Form von internen Tabellen zu füllen, kann ein DSL-Parser natürlich auch verwendet werden, um ABAP-Programmcode zu erzeugen. Auch hierfür halten unsere SAP-Systeme eine Reihe von Beispielen bereit. Diese gedenke ich in künftigen Blogs zu dokumentieren.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Dass die Einträge von Customizingtabellen als ein "DSL-Programm" aufgefasst werden können, mag Verwunderung oder gar Befremden hervorrufen: Ich hatte diesen Standpunkt bereits in meinem Blog &lt;a href="http://ruediger-plantiko.blogspot.com/2009/06/software-steinzeit.html"&gt;Software-Steinzeit&lt;/a&gt; erläutert.&lt;br /&gt;&lt;br /&gt;[2] Macros werden im Debugger wie eine Ausführung behandelt, man kann nicht in ein Macro hineindebuggen. Daher empfiehlt es sich, das Macro ausser in den einfachsten Fällen nur zum Aufruf von Methoden zu verwenden und die Logik des Macros in diesen Methoden zu programmieren. Das Macro hilft dann, die beabsichtigte Steuerung in einer besser lesbaren Form hinzuschreiben.  &lt;br /&gt;  &lt;br /&gt;[3] Um Tippfehler zu vermeiden, habe ich in diesem Fall die möglichen Zustände eines Buttons in Form von Konstanten ausgeprägt. Hinter den Kulissen gibt es Konstanten &lt;tt&gt;gc_state-active&lt;/tt&gt;, &lt;tt&gt;gc_state-invisible&lt;/tt&gt; usw. So hält mich bereits der ABAP Syntaxchecker davon ab, ungültige Zustandswerte einzugeben.&lt;br /&gt;&lt;br /&gt;[4] Nur in Entwicklungssystemen sind Pufferungen normalerweise lästig: Der Entwickler ist mehr daran interessiert, bei seinen Entwicklertests stets die  aktuellsten Daten zu sehen, die er für seine Tests auch ständig ändert. Im Entwicklungssystem zu puffern, kann lästige Debuggingsitzungen verursachen. Dagegen ändern sich die steuernden Daten im Produktivsystem eher selten, während andererseits die höchstmögliche Performance angestrebt werden sollte: Dort also unbedingt puffern! Schliesslich sollen nicht hunderte von Benutzern in ihren Sitzungen alle dieselbe Konfigurationsdatei von der Datenbank oder vom Dateisystem einlesen müssen. Aus demselben Grund sind Customizingtabellen im DDIC mit einem Click - ohne zusätzlichen Programmieraufwand - auf dem Applikationsserver pufferbar.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-7517581074612596532?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/7517581074612596532/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=7517581074612596532' title='2 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/7517581074612596532'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/7517581074612596532'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/01/domanenspezifische-programmierung-in.html' title='Domänenspezifische Programmierung in ABAP'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-6675080008127908850</id><published>2011-01-04T22:23:00.017+01:00</published><updated>2011-01-05T10:33:26.699+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Micro-Versionierung</title><content type='html'>Vor kurzem wäre ich beinahe Opfer eines schlimmen Datenverlustes geworden [1]: Durch den Upload eines falschen JavaScript-Files hätte ich die Frucht von fast zwei Tagen intensiver Programmierarbeit verloren. Zwar ist es meine Erfahrung, dass das erneute Schreiben des Programms in solchen Fällen ein besseres Ergebnis liefert, auch wenn das erste Produkt doch wieder auftaucht oder wiederhergestellt werden kann. Auch Briefe gewinnen deutlich an Qualität, wenn man sie nach Abfassung zerreisst und komplett neu schreibt! &lt;br /&gt;&lt;br /&gt;Dennoch versuche ich natürlich vernünftigerweise, Datenverlust möglichst zu vermeiden. Im SAP-System gelingt mir dies durch häufige Freigabe meiner Änderungen ins Testsystem. Dadurch entstehen zwar viele kleine Transporte - aber die Arbeit wird dadurch besser dokumentiert und nachvollziehbar. Die Kurztexte der Transporte kann ich darüberhinaus zur Dokumentation der kleinen Änderungen nutzen, die ich jeweils vorgenommen habe. So enthält die Versionsverwaltung schliesslich eine Dokumentation der an einem Quelltext vorgenommenen Arbeitsschritte.&lt;br /&gt;&lt;br /&gt;Nur für Arbeiten an "Wegwerfprototypen" verzichte ich auf Transporte und verwende lokale Pakete. Genau daher bekam ich mein Problem. Dabei haben Prototypen in der Entwicklungsarbeit durchaus einen hohen Stellenwert: sie können - wie in diesem Fall - als Proof Of Concept die Vergabe eines Entwicklungsprojekts legitimieren. &lt;br /&gt;&lt;br /&gt;Für Entwicklungen in JavaScript, Perl, C/C++ und Java benutze ich häufig den Editor &lt;a href="http://www.ultraedit.com/"&gt;UltraEdit&lt;/a&gt;. Dieser erlaubt es zwar, automatisch numerierte Backups der letzten Änderungen zu erstellen. Diese Einstellung ist aber pauschal, gilt dann für jedes File, das ich im Editor überhaupt ändere. Mir ist es lieber, die Versionierung gezielt selbst anstossen zu können. Ausserdem gefällt mir die Verschwendung nicht, jede dieser Versionen als Vollversion zu speichern (statt nur die Differenzen).&lt;br /&gt;&lt;br /&gt;Daher habe ich UltraEdit mit einem Versionierungswerkzeug auf Basis von CVS ausgestattet (CVS ist zwar ein bisschen aus der Mode gekommen, aber zweifellos "gut abgehangen"). Wer CVS nicht mag, sondern lieber mit GIT oder SVN arbeitet, kann meine Lösung leicht abändern, da die Kommandozeilenbefehle für das Ein- und Auschecken in allen diesen Systemen ähnlich sind.  &lt;br /&gt;&lt;br /&gt;Wie funktioniert es? Wenn ich eine Version der aktuell bearbeiteten Datei ziehen möchte, wähle ich mein neues Werkzeug "Versionieren":&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_JitR0EqIJ_A/TSOYfr7QS2I/AAAAAAAAAJQ/tsrEss-tlNs/s1600/versionieren.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 389px; height: 400px;" src="http://4.bp.blogspot.com/_JitR0EqIJ_A/TSOYfr7QS2I/AAAAAAAAAJQ/tsrEss-tlNs/s400/versionieren.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5558454035239947106" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Die Versionen, ihre Diffs und Patches kann ich mir nachher mit einem GUI für CVS ansehen, z.B. &lt;a href="http://www.tortoisecvs.org/"&gt;TortoiseCVS&lt;/a&gt; oder &lt;a href="http://sourceforge.net/projects/cvsgui/"&gt;wincvs&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Der Vollständigkeit halbe füge ich hier noch das Batchfile an, das die Versionierung ausführt. Es bekommt von Ultraedit zwei Argumente, den Verzeichnisnamen und den Dateinamen (im Werkzeugkommando durch die Symbole %p und %n%e bezeichnet). Die Definition des Werkzeugs lautet also&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;makeVersion.bat "%p" %n%e&lt;/pre&gt;&lt;br /&gt;Das Batchfile muss mit seiner mittelalterlichen Stringverarbeitung diese Anführungszeichen wieder entfernen, um Name und Pfad in eigenen Kommandos verwenden zu können. Es richtet dann ein temporäres Arbeitsverzeichnis ein, checkt die Datei gleichen Namens aus einem lokalen Repository aus, kopiert den aktuellen Stand in das Arbeitsverzeichnis und führt schliesslich den &lt;tt&gt;commit&lt;/tt&gt; aus. Auf Dokumentationen der Änderungen mit dem &lt;tt&gt;-m&lt;/tt&gt; Parameter verzichte ich. Denn es geht mir ja nur um Microversionen - die viele kleinsten Versionen, die während der Arbeit an einem Programm entstehen. Aus dem gleichen Grunde reicht es mir, die Versionierung nur anhand des Dateinamens auszuführen. Wenn ich vor vielen Monaten schon einmal eine ganz andere Datei desselben Namens bearbeitet hatte, stören mich die von dieser Zeit noch vorliegenden Versionen nicht: Ich schaue ja gar nicht so weit zurück, mir geht es um Änderungen in einem Zeitrahmen von Minuten oder Stunden. &lt;br /&gt;&lt;br /&gt;In meiner IDE der Zukunft sind all diese Dinge in den Editor integriert. Für die Fehlersuche steht mir dann eine Zeitlinie zur Verfügung, in der auch bei der gleichzeitigen Arbeit an mehreren Dateien die letzten konsistenten Codestände testhalber wiederhergestellt können, so dass ich ohne aufwendige Analyse direkt ermitteln kann, ab welcher Änderung sich das Fehlerchen eingeschlichen hat.&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;@ECHO OFF&lt;br /&gt;&lt;br /&gt;rem SYNTAX:&lt;br /&gt;rem makeVersion &lt;Verzeichnisname&gt; &lt;Dateiname&gt;&lt;br /&gt;rem Versioniert die angegebene Datei, wobei für jeden Dateinamen&lt;br /&gt;rem ein neuer Modul im Wurzelverzeichnis ue32work angelegt wird.&lt;br /&gt;&lt;br /&gt;set CVSWORKDIR=\UltraEdit-32\cvswork&lt;br /&gt;set WTEMP=\Temp&lt;br /&gt;set UE32WORK=%WTEMP%\ue32work&lt;br /&gt;set CVSCMD=\cvsnt\cvs -d %CVSWORKDIR%&lt;br /&gt;set FILEPATH=%1%&lt;br /&gt;for /f "useback tokens=*" %%a in ('%FILEPATH%') do set FILEPATH=%%~a&lt;br /&gt;set FILENAME=%2%&lt;br /&gt;for /f "useback tokens=*" %%a in ('%FILENAME%') do set FILENAME=%%~a&lt;br /&gt;set FULLFILENAME="%FILEPATH%%FILENAME%"&lt;br /&gt;&lt;br /&gt;rmdir /S /Q %UE32WORK%&lt;br /&gt;mkdir %UE32WORK%&lt;br /&gt;if exist %CVSWORKDIR%\ue32work\%FILENAME% ( &lt;br /&gt;@ECHO ON&lt;br /&gt;cd %WTEMP%&lt;br /&gt;%CVSCMD% co ue32work\%FILENAME%&lt;br /&gt;copy /Y %FULLFILENAME% %UE32WORK%\%FILENAME%&lt;br /&gt;cd %UE32WORK%\%FILENAME% &lt;br /&gt;%CVSCMD% commit -m "Dummy" %FILENAME%&lt;br /&gt;) else (&lt;br /&gt;@ECHO ON&lt;br /&gt;mkdir %UE32WORK%\%FILENAME%&lt;br /&gt;copy /Y %FULLFILENAME% %UE32WORK%\%FILENAME% &lt;br /&gt;cd %UE32WORK%\%FILENAME%&lt;br /&gt;%CVSCMD% import -m "Dummy" ue32work\%FILENAME% "UE32WORK" "r1" &lt;br /&gt;)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;[1] Wieso wurde ich nur &lt;i&gt;beinahe&lt;/i&gt; ein Datenverlust-Opfer? Dank eines wahnsinnigen Zufalls hatte mein Kollege eine Version des Files in seinem Browser-Cache, die immerhin nur eine halbe Stunde alt war. Das genügte.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-6675080008127908850?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/6675080008127908850/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=6675080008127908850' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/6675080008127908850'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/6675080008127908850'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2011/01/micro-versionierung.html' title='Micro-Versionierung'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_JitR0EqIJ_A/TSOYfr7QS2I/AAAAAAAAAJQ/tsrEss-tlNs/s72-c/versionieren.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-6604549238131236517</id><published>2010-12-21T20:27:00.015+01:00</published><updated>2010-12-23T16:07:46.059+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Ein JSON-Parser in ABAP</title><content type='html'>Wenn es darum geht, strukturierte Daten zwischen verschiedenen Systemen auszutauschen, bietet sich das &lt;a href="http://www.json.org/json-de.html"&gt;JSON&lt;/a&gt;-Datenformat an. Es unterstützt Arrays, Hashs, einige elementare Datentypen für Zahlen, Strings und Wahrheitswerte und erlaubt es, Datenobjekte beliebig tief ineinander zu schachteln.&lt;br /&gt;&lt;br /&gt;Die Notation ist einfach und Programmierern der verschiedensten Sprachen (C, C++, C#, Java, JavaScript) in dieser oder ähnlicher Form geläufig. Im Vergleich zu XML gibt es weniger Overhead zur Definition der Struktur, so dass das Wesentliche, der eigentliche Dateninhalt, besser ins Auge fällt.&lt;br /&gt;&lt;br /&gt;Hier einige einfache Beispiele:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;[ "rot", "grün", "blau" ]&lt;/pre&gt;&lt;br /&gt;ist ein &lt;i&gt;Array&lt;/i&gt; von Strings. Der folgende &lt;i&gt;Hash&lt;/i&gt; definiert beispielhaft einige Name/Wert-Paare: &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;{ &lt;br /&gt;  "Alter":42, &lt;br /&gt;  "Beruf":"Stenotypistin", &lt;br /&gt;  "Raucher":true &lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Natürlich lassen sich Arrays und Hashs beliebig kombinieren, zum Beispiel zu einem Hash of Arrays (HoA): &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;{&lt;br /&gt;  "Adam" : ["Kain", "Abel", "Seth"],&lt;br /&gt;  "Abraham" : ["Ismael", "Isaak"],&lt;br /&gt;  "Isaak" : ["Jakob", "Esau"]&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt; Da JSON im wesentlichen der Notation von Daten in JavaScript entspricht, wird es häufig in Webanwendungen für die Kommunikation mit dem Server verwendet. Das Parsen im JavaScript-Layer einer Webanwendung kann einfach mit der Anweisung &lt;tt&gt;eval&lt;/tt&gt; erfolgen.[1] &lt;br /&gt;&lt;br /&gt;Um die Benutzeroberfläche portabel entwickeln und ohne grossen Aufwand zwischen verschiedenen Backends wählen zu können, empfiehlt sich ein standardisiertes Datenformat wie JSON für beide Richtungen des Datentransports, für das Senden wie das Empfangen der Daten.&lt;br /&gt;&lt;br /&gt;Da ich seit kurzem an einer Webanwendung arbeite, die Ajax-Requests mit JSON-Daten an ein SAP-System sendet und von diesem empfängt, benötigte ich im ABAP-Stack einen JSON-Parser. Da es einen solchen anscheinend im SAP-System bisher nicht gibt, habe ich ihn selbst programmiert: Die Klasse &lt;a href="http://bsp.mits.ch/code/clas/zcl_json_parser"&gt;ZCL_JSON_PARSER&lt;/a&gt; kann eine JSON-Eingabe in ein allgemein typisiertes ABAP-Datenobjekt umwandeln, das dann im Client Code durchsucht oder auf die anwendungsspezifischen Datenstrukturen abgebildet werden kann. &lt;br /&gt;&lt;br /&gt;Bei Mappingaufgaben wie dieser ist das testgetriebene Entwickeln besonders effizient: Das liegt daran, dass wir nicht wie sonst mit Seiteneffekten zu tun haben - es gibt praktisch keine API-Aufrufe, die so oder so ausgehen können, und keine Abhängigkeit von den Inhalten irgendwelcher Datenbanktabellen. Wir haben ein Input und ein nur von diesem abhängendes Ouput. Das ruft geradezu danach, die Funktionalitäten Schritt für Schritt als Erfüllung von Testerwartungen zu implementieren. [3]&lt;br /&gt;&lt;br /&gt;Bevor wir aber auch nur einen syntaktisch korrekten Test hinschreiben können, muss wenigstens die Schnittstelle der zu testenden Methode festgelegt sein. In unserem Fall werden wir eine öffentliche Methode &lt;tt&gt;parse&lt;/tt&gt; haben, die sicher folgendermassen aussieht: &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;methods parse&lt;br /&gt;  importing&lt;br /&gt;    iv_json type string&lt;br /&gt;  returning&lt;br /&gt;    value(es_data) type zut_data&lt;br /&gt;  raising&lt;br /&gt;    zcx_parse_error.&lt;/pre&gt;&lt;br /&gt;Eingabe ist sicher ein String. Ausgabe ist ein Datenobjekt, das mit ABAP-Mitteln ausgewertet werden kann und beliebig tiefe Verschachtelungen zulässt. Hierzu benötigen wir einen generischen Datentyp, der beliebige Datenobjekte fassen kann. Der Datentyp &lt;tt&gt;any&lt;/tt&gt; kann leider nicht verwendet werden, da er nur eine Abstraktion aller bestehenden Datentypen darstellt und dem Compiler nicht sagen kann, wieviele Bytes für diesen Typ alloziert werden sollen. Der Typ &lt;tt&gt;ref to data&lt;/tt&gt; hat dieses Problem nicht. Er ist "ein Zeiger auf irgendetwas" und benötigt eine bekannte Anzahl von Bytes: Die Länge einer Speicheradresse, üblicherweise vier Bytes. &lt;br /&gt;&lt;br /&gt;Der obige Rückgabetyp ist im Data Dictionary definiert. In ABAP würde man ihn äquivalent wie folgt notieren:&lt;br /&gt;&lt;pre class="sh_abap"&gt;types: begin of zut_data,&lt;br /&gt;  type type c length 1,&lt;br /&gt;  data type ref to data,&lt;br /&gt;end of zut_data.&lt;/pre&gt;&lt;br /&gt;Der Typschlüssel &lt;tt&gt;type&lt;/tt&gt; spezifiert den Typ des Datenobjekts, auf das der Zeiger &lt;tt&gt;data&lt;/tt&gt; zeigt. Das können elementare Datentypen wie String (S), Number (N), Boolean (B) sein, aber auch zusammengesetzte Typen wie Hash (h) oder Array (a). &lt;br /&gt;&lt;br /&gt;Man beachte, dass die so typisierte Schnittstelle schon eine wichtige Designentscheidung enthält: &lt;i&gt;Das Ziel unseres Mappings wird ein Datenobjekt, nicht die Instanz einer Klasse.&lt;/i&gt; In der Hierarchie der ABAP-Objekte haben Datenobjekte und Instanzen von Klassen keinen gemeinsamen Oberbegriff. &lt;br /&gt;&lt;br /&gt;Man hätte alternativ eine Klasse zur Definition des Zieltyps in ABAP verwenden können. Das hätte gewisse Vorteile gebracht, zum Beispiel hätte der Typschlüssel eliminiert werden können [2] &amp;ndash; aber um den Preis beträchtlicher Performanceeinbussen: Der &lt;tt&gt;create data&lt;/tt&gt; Befehl hat wesentlich bessere Ausführungszeiten als &lt;tt&gt;create object&lt;/tt&gt;.   &lt;br /&gt;&lt;br /&gt;Für die einfachen Datentypen können wir nun schon einige Tests hinschreiben: &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;* --- Zahlen erkennen&lt;br /&gt;  method test_42.&lt;br /&gt;&lt;br /&gt;    data: ls_data type zut_data.&lt;br /&gt;    ls_data = go_ref-&gt;parse( '42' ).&lt;br /&gt;&lt;br /&gt;* Parser erkennt '42' als Zahl:&lt;br /&gt;    assert_equals( act = ls_data-type&lt;br /&gt;                   exp = 'N' ).&lt;br /&gt;&lt;br /&gt;* Wert 42 wurde korrekt ermittelt&lt;br /&gt;    _assert_equals( act = ls_data-data&lt;br /&gt;                    exp = 42 ).&lt;br /&gt;&lt;br /&gt;  endmethod.&lt;br /&gt;&lt;br /&gt;* --- Einen String erkennen&lt;br /&gt;  method test_string.&lt;br /&gt;    data: ls_data type zut_data.&lt;br /&gt;    ls_data = go_ref-&gt;parse( '"Abc"' ).&lt;br /&gt;&lt;br /&gt;* Typ String wird erkannt&lt;br /&gt;    assert_equals( act = ls_data-type&lt;br /&gt;                   exp = 'S' ).&lt;br /&gt;&lt;br /&gt;* String wird richtig gelesen&lt;br /&gt;    _assert_equals( act = ls_data-data&lt;br /&gt;                    exp = 'Abc' ).&lt;br /&gt;&lt;br /&gt;  endmethod.                    "test_string&lt;/pre&gt; &lt;br /&gt;Hierbei ist &lt;tt&gt;_assert_equals&lt;/tt&gt; eine Hilfsmethode, die im wesentlichen wie &lt;tt&gt;assert_equals&lt;/tt&gt; funktioniert; wenn aber &lt;tt&gt;act&lt;/tt&gt; ein Zeiger ist, wird er vor dem Vergleich mit &lt;tt&gt;exp&lt;/tt&gt; dereferenziert.&lt;br /&gt;&lt;br /&gt;Wenn ein leerer Input hereingereicht wird, soll auch eine leere Struktur zurückgegeben werden: &lt;br /&gt;&lt;pre class="sh_abap"&gt;* --- Keine Eingabe -&gt; Keine Ausgabe&lt;br /&gt;  method test_nothing.&lt;br /&gt;    data: ls_data type zut_data.&lt;br /&gt;&lt;br /&gt;    ls_data = go_ref-&gt;parse( '' ).&lt;br /&gt;&lt;br /&gt;* Leerer String ergibt leeres Ergebnis (keine Ausnahme)&lt;br /&gt;    assert_initial( ls_data ).&lt;br /&gt;&lt;br /&gt;  endmethod.                    "test_nothing&lt;/pre&gt;&lt;br /&gt;Wie ist es nun mit zusammengesetzten Typen - hier Hashs und Arrays? Es liegt nahe, Hashs und Arrays in ABAP durch interne Tabellen mit Hash- bzw. Standardzugriff abzubilden. Für Arrays ist dies relativ einfach: &lt;br /&gt;&lt;pre class="sh_abap"&gt;types: zut_array_tab &lt;br /&gt;  type standard table of zut_data.&lt;/pre&gt;&lt;br /&gt;leistet das Gewünschte: Denn jedes Arrayelement kann ja wieder ein beliebiges Datenobjekt enthalten. Also ist der Zeilentyp des Arrays wieder &lt;tt&gt;zut_data&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;Hashs sind Mengen von Schlüssel/Wert-Paaren. Der Schlüssel ist ein String, der Wert ist wieder ein beliebiges Datenobjekt. Ein Hash-Element sieht also folgendermassen aus:&lt;br /&gt;&lt;pre class="sh_abap"&gt;types: begin of zut_hash_element,&lt;br /&gt;  key type string.&lt;br /&gt;  include type zut_data as value.&lt;br /&gt;types: end of zut_hash_element.&lt;/pre&gt;&lt;br /&gt;Der Hash selbst ist nun im ABAP-Sinne eine Hash-Tabelle mit Zeilentyp &lt;tt&gt;zut_hash_element&lt;/tt&gt; und Schlüsselspalte &lt;tt&gt;key&lt;/tt&gt;:&lt;br /&gt;&lt;pre class="sh_abap"&gt;types: zut_hash_tab type hashed table of zut_hash_element &lt;br /&gt;  with unique key key.&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Nun können wir auch Tests für Hashs und Arrays formulieren, mit dem einfachsten Fall beginnend: &lt;br /&gt;&lt;br /&gt;Es soll sicher der &lt;i&gt;leere Array&lt;/i&gt; erkannt werden (&lt;tt&gt;go_ref&lt;/tt&gt; ist das Object Under Test, also die zu testende Instanz von &lt;tt&gt;ZCL_JSON_PARSER&lt;/tt&gt;):&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;* --- Der leere Array []&lt;br /&gt;  method test_empty_array.&lt;br /&gt;&lt;br /&gt;    data: ls_data type zut_data.&lt;br /&gt;&lt;br /&gt;    field-symbols: &amp;lt;lt_array&gt; type zut_array_tab.&lt;br /&gt;&lt;br /&gt;    ls_data = go_ref-&gt;parse( ' [   ]  ' ).&lt;br /&gt;&lt;br /&gt;* Typ array wird erkannt&lt;br /&gt;    assert_equals( act = ls_data-type&lt;br /&gt;                   exp = 'a' ).&lt;br /&gt;&lt;br /&gt;    assert_bound( ls_data-data ).&lt;br /&gt;&lt;br /&gt;    assign ls_data-data-&gt;* to &amp;lt;lt_array&gt;.&lt;br /&gt;    assert_subrc( act = sy-subrc&lt;br /&gt;                  msg = 'Datenobjekt hat nicht den korrekten Typ' ).&lt;br /&gt;&lt;br /&gt;* Es wurde der leere array ermittelt&lt;br /&gt;    assert_initial( &amp;lt;lt_array&gt; ).&lt;br /&gt;&lt;br /&gt;  endmethod.                    "test_empty_array&lt;/pre&gt;&lt;br /&gt;Der letzte und komplizierte Testfall, das Parsen einer verschachtelten komplexen Struktur soll zugleich zeigen, wie die resultierende Struktur in ABAP ausgewertet werden kann, um die in der Anwendung gewünschten Daten zu extrahieren:&lt;br /&gt;&lt;pre class="sh_abap"&gt;* --- Verschachtelte Struktur&lt;br /&gt;  method test_complex_nested.&lt;br /&gt;&lt;br /&gt;    data: ls_data type zut_data,&lt;br /&gt;          lv_array_length type i.&lt;br /&gt;&lt;br /&gt;    field-symbols: &amp;lt;lt_array&gt; type zut_array_tab,&lt;br /&gt;                   &amp;lt;lt_hash&gt;  type zut_hash_tab,&lt;br /&gt;                   &amp;lt;lt_hash2&gt;  type zut_hash_tab,&lt;br /&gt;                   &amp;lt;ls_line&gt;  type zut_data,&lt;br /&gt;                   &amp;lt;ls_element&gt; type zut_hash_element,&lt;br /&gt;                   &amp;lt;ls_element2&gt; type zut_hash_element.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    ls_data = go_ref-&gt;parse( &lt;br /&gt;      ' [ 1, { x:[1,2,3],y:{"c":1} }, "a" , true ]  ' &lt;br /&gt;      ).&lt;br /&gt;&lt;br /&gt;* Basistyp Array wird erkannt&lt;br /&gt;    assert_equals( act = ls_data-type&lt;br /&gt;                   exp = 'a' ).&lt;br /&gt;&lt;br /&gt;* Äusseren Array anschauen&lt;br /&gt;    assign ls_data-data-&gt;* to &amp;lt;lt_array&gt;.&lt;br /&gt;&lt;br /&gt;* Er hat vier Elemente&lt;br /&gt;    describe table &amp;lt;lt_array&gt; lines lv_array_length.&lt;br /&gt;    assert_equals( act = lv_array_length&lt;br /&gt;                   exp = 4 ).&lt;br /&gt;&lt;br /&gt;* Auf zweites Element positionieren&lt;br /&gt;    read table &amp;lt;lt_array&gt; assigning &amp;lt;ls_line&gt; &lt;br /&gt;      index 2.&lt;br /&gt;    assert_equals( act = &amp;lt;ls_line&gt;-type exp = 'h' ).&lt;br /&gt;    assign &amp;lt;ls_line&gt;-data-&gt;* to &amp;lt;lt_hash&gt;.&lt;br /&gt;&lt;br /&gt;* Den Hash (das zweite Element des äusseren Arrays) anschauen&lt;br /&gt;* Auf Element zum Schlüssel 'y' positionieren&lt;br /&gt;    read table &amp;lt;lt_hash&gt; assigning &amp;lt;ls_element&gt; &lt;br /&gt;      with table key key = 'y'.&lt;br /&gt;    assert_subrc( sy-subrc ).&lt;br /&gt;&lt;br /&gt;* Das Element zum Schlüssel 'y' ist wieder ein Hash&lt;br /&gt;    assert_equals( act = &amp;lt;ls_element&gt;-type exp = 'h' ).&lt;br /&gt;    assign &amp;lt;ls_element&gt;-data-&gt;* to &amp;lt;lt_hash2&gt;.&lt;br /&gt;&lt;br /&gt;* Dieser innerste Hash enthält zum Schlüssel 'c' die Zahl 1:&lt;br /&gt;    read table &amp;lt;lt_hash2&gt; assigning &amp;lt;ls_element2&gt; &lt;br /&gt;         with table key key = 'c'.&lt;br /&gt;    assert_subrc( sy-subrc ).&lt;br /&gt;    assert_equals( act = &amp;lt;ls_element2&gt;-type exp = 'N' ).&lt;br /&gt;    _assert_equals( act = &amp;lt;ls_element2&gt;-data exp = 1 ).&lt;br /&gt;&lt;br /&gt;  endmethod.&lt;/pre&gt;&lt;br /&gt;Zum Entwurf der Klasse &lt;a href="http://bsp.mits.ch/code/clas/zcl_json_parser"&gt;ZCL_JSON_PARSER&lt;/a&gt; noch einige Anmerkungen: &lt;br /&gt;&lt;br /&gt;Kernstück ist die Methode &lt;tt&gt;get_any&lt;/tt&gt;, die ein beliebiges JavaScript-Datenobjekt einliest. Sie wird nicht nur von der einzigen öffentlichen Methode &lt;tt&gt;parse&lt;/tt&gt; für das Top-Datenobjekt aufgerufen, sondern auch an den dafür vorgesehenen Plätzen in Arrays und Hashs, was zu rekursiven Aufrufen führt.&lt;br /&gt;&lt;br /&gt;Es gefiel mir, die Klasse vollständig von globalen Variablen freizuhalten: Sie besitzt überhaupt keine Attribute. Der Preis dafür war lediglich, alle Teilmethoden mit einem Parameter &lt;tt&gt;cv_pos&lt;/tt&gt; zu versehen, der die aktuelle Position im String angibt und von den Teilmethoden bei erfolgreichem Lesen eines Ausdrucks erhöht werden kann. Die Aufrufe der Teilmethoden werden dadurch etwas unhandlicher als sie sein könnten: Jede Methode übergibt per Referenz den vollständigen zu parsenden String und die Position im String als Changing-Parameter. Dazu gibt es meist den Exportparameter &lt;tt&gt;ev_found&lt;/tt&gt;, ein Flag, das angibt, ob das gesuchte Teilobjekt gefunden wurde. &lt;br /&gt;&lt;br /&gt;Die Klasse kann ein bisschen mehr als das JSON-Format: Schlüssel von Hashes können wie in JavaScript ohne Anführungszeichen hingeschrieben werden, wenn sie einen gültigen Variablennamen darstellen. Und Strings können auch durch einfache Hochkommata begrenzt werden. Einzige Einschränkung für Strings: Unicode-Zeichen in der Notation &lt;tt&gt;\uXXXX&lt;/tt&gt; sind nicht unterstützt.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Wegen der theoretischen Möglichkeit von &lt;i&gt;Code Injection&lt;/i&gt;-Angriffen wird für Anwendungen, die im Public Internet laufen, allerdings empfohlen, auch in JavaScript einen ausprogrammierten Parser zu verwenden, der wirklich nur Datenobjekte evaluiert und keine JavaScript-Anweisungen ausführt. Die bekannteren JavaScript-Frameworks wie Prototype oder jQuery enthalten einen JSON-Parser.&lt;br /&gt;[2] Das ist Martin Fowlers Refactoringmuster: "Typschlüssel durch Unterklassen ersetzen".&lt;br /&gt;[3] Das ist auch der Grund, warum in TDD-Einführungen häufig Mappingaufgaben als Beispiel verwendet werden, etwa ein Konverter von Ganzzahlen in römische Zahlwerte.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-6604549238131236517?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/6604549238131236517/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=6604549238131236517' title='2 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/6604549238131236517'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/6604549238131236517'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2010/12/ein-json-parser-in-abap.html' title='Ein JSON-Parser in ABAP'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-6310666493548947822</id><published>2010-11-25T22:21:00.022+01:00</published><updated>2010-11-26T09:29:37.726+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Keine Message-Befehle in Geschäftslogik!</title><content type='html'>Vor kurzem erhielt ich per Mail die Anfrage eines Entwicklers, wie er denn mit ABAP Units testen könne, ob eine bestimmte Fehlermeldung, die er in seiner Geschäftslogik durchläuft, gesendet oder nicht gesendet wird. Er hatte das schon auf verschiedene Weisen probiert, kam an dieser Stelle aber nicht weiter.&lt;br /&gt;&lt;br /&gt;Wenn etwas sich als kompliziert testbar erweist, liegt es oft nicht am Unit Test oder gar, wie hier indirekt geargwöhnt wurde, am Unit Test Framework (zwischen den Zeilen lese ich "Pah! Mit ABAP Unit kann man nicht mal eine Errormeldung abfangen bzw. abfragen"), sondern meistens liegt das Problem im zu testenden Code selbst. So auch hier. &lt;br /&gt;&lt;br /&gt;Nehmen wir an, es sei eine Plausibilitätsprüfung gefordert, dass ein vom Benutzer einzugebender Preis einen bestimmten Maximalbetrag nicht überschreiten darf. In der an die Transaktion gebundenen Klasse, die die Eingabewerte überprüft, finden wir vielleicht den folgenden Code:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;if iv_input_price &gt; gv_max_price.&lt;br /&gt;* Der eingegebene Preis &amp;1 ist zu hoch (Maximum: &amp;2)&lt;br /&gt;  message e100 with iv_input_price gv_max_price.&lt;br /&gt;endif.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Das funktioniert im GUI alles ganz wunderbar. Aber beim automatischen Testen des Codes ergeben sich Schwierigkeiten. Der Unit Test, der eine Überschreitung des Maximalbetrags simuliert, lässt sich nicht bis zu Ende ausführen. Wenn er auf die Fehlermeldung stösst, übergibt der Unit Test die Kontrolle zwangsläufig an den GUI zur Anzeige des Fehlers. Das bedeutet aber: Die Ausführung des Unit Tests stoppt an dieser Stelle. Beides - die Anzeige des Fehlers und die Unmöglichkeit, den Unit Test korrekt zu Ende zu führen - ist natürlich unerwünscht.&lt;br /&gt;&lt;br /&gt;Wo liegt hier das Problem?&lt;br /&gt;&lt;br /&gt;Auch wenn der produktive Code anscheinend ja korrekt funktioniert und das Problem nur beim Unit Test auftritt, liegt das Problem nicht beim Unit Test und schon gar nicht beim Unit Test Framework. Das Problem besteht darin, dass obiger Code die Prüflogik an die Existenz eines GUI bindet: Der &lt;tt&gt;message&lt;/tt&gt;-Befehl ist eine Anweisung, die an den GUI zur Ausführung übergeben wird. Es ist nicht möglich, die Prüfung unabhängig vom GUI auszuführen. Das ist in vieler Hinsicht schädlich:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Der Code kann nicht im Hintergrund ausgeführt werden, z.B. in einem Job: Eine Errormeldung führt zum sofortigen Abbruch des Jobs. Wenn aber z.B. eine ganze Reihe von Belegen im Hintergrund verbucht werden sollen, ist dies nicht das erwünschte Verhalten: Dann soll normalerweise nur die Bearbeitung des &lt;i&gt;aktuellen&lt;/i&gt; Belegs abgebrochen und mit dem nächsten Beleg fortgefahren werden.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Es ist nicht möglich, die Klasse mit dieser Prüfung in einer Web-Anwendung oder mit einem ganz anderen User Interface einzusetzen: Da die &lt;tt&gt;message&lt;/tt&gt;-Anweisung nicht ausgeführt werden kann, kommt es zu einem Abbruch des Prozesses mit einem Kurzdump - im Web also zu einem "internen Serverfehler" (HTTP 500).&lt;/li&gt; &lt;br /&gt;&lt;li&gt;Es ist nicht möglich, die Logik dieser Klasse als API-Funktion anzubieten, da der Fehlerfall zu einer Situation führt, die nicht durch die Schnittstelle kontrolliert ist.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Alle Probleme der Informatik können durch Einführung einer neuen Indirektionsebene gelöst werden&lt;/i&gt;, lautet ein berühmtes Zitat von &lt;a href="http://de.wikipedia.org/wiki/Butler_Lampson"&gt;Butler Lampson&lt;/a&gt;.[1] Das ist auch in diesem Fall so. Genau um die Behandlung eines Fehlers von dessen Erkennung zu trennen, hat man die Exception erfunden. Statt eine Fehlersituation direkt im GUI zu melden, sollte das Programm eine Ausnahme auslösen, und zwar am besten gleich eine klassenbasierte. Diese Ausnahme wird in der Schnittstelle der Prüfmethode deklariert. Der Konsument dieser Methode kann dann mit einem &lt;tt&gt;try... catch... &lt;/tt&gt; Block selbst entscheiden, was er im Ausnahmefall machen möchte. Nach Einführung einer solchen Ausnahme kann der Code beispielsweise folgendermassen aussehen:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;if iv_input_price &gt; gv_max_price.&lt;br /&gt;  raise exception type zcx_price_exceeds_bound&lt;br /&gt;    exporting &lt;br /&gt;      price = iv_input_price&lt;br /&gt;      bound = gv_max_price.&lt;br /&gt;endif.      &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Der Unit Test einer solchen Logik gestaltet sich entsprechend einfach &amp;mdash; und einfache Unit Tests sind meist ein Zeichen, dass der produktive Code ganz gut geraten ist. Hier der Test, dass die Prüfung eine Grenzwertüberschreitung erkennt und die erwartete Ausnahme auslöst:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;method test_bound_detected.&lt;br /&gt;  try.&lt;br /&gt;      go_validator-&gt;check_price( gc_very_high_price ).&lt;br /&gt;* Nicht OK - Ausnahme wurde nicht ausgelöst:&lt;br /&gt;      fail( 'Zu hoher Preis soll Ausnahme auslösen!').&lt;br /&gt;    catch zcx_price_exceeds_bound into lo_ex.&lt;br /&gt;* OK - optional noch die Ausnahmeparameter prüfen:&lt;br /&gt;      assert_equals( &lt;br /&gt;        act = lo_ex-&gt;price&lt;br /&gt;        exp = gc_very_high_price&lt;br /&gt;        msg = 'Ausnahmeobjekt falsch parametrisiert' ).    &lt;br /&gt;      assert_equals( &lt;br /&gt;        act = lo_ex-&gt;bound&lt;br /&gt;        exp = go_validator-&gt;gv_max_price&lt;br /&gt;        msg = 'Ausnahmeobjekt falsch parametrisiert' ).    &lt;br /&gt;  endtry.      &lt;br /&gt;endmethod.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Auch der umgekehrte Test ist sinnvoll: Wenn ein hinreichend kleiner Preis (z.B. 1) eingegeben wird, soll &lt;i&gt;keine&lt;/i&gt; Ausnahme ausgelöst werden:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;method test_no_ex_for_price_in_range.&lt;br /&gt;  go_validator-&gt;check_price( 1 ).&lt;br /&gt;endmethod.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Fehlt hier nicht noch etwas? Nein! Denn das Abfangen einer Ausnahme können wir getrost dem Unit Test Framework überlassen: Wenn es zu einer Ausnahme kommt, ist das ja ein Fehler im Programm. Also reagiert das Unit Test Framework bereits korrekt, indem es den Test &lt;tt&gt;test_no_exc_for_price_in_range&lt;/tt&gt; in der Übersicht als fehlerhaft markieren wird. Siehe hierzu auch meinen Blog &lt;a href="http://ruediger-plantiko.blogspot.com/2009/03/unbehandelte-ausnahmen-als.html"&gt;Unbehandelte Ausnahmen als Zusicherung&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Damit könnten wir den Fall als erledigt betrachten: &lt;i&gt;Error Messages in der Prüflogik sollen unbedingt vermieden werden.&lt;/i&gt; Ende der Durchsage.&lt;br /&gt;&lt;br /&gt;Trotzdem bleibt der unterschwellige Vorwurf bestehen, das Unit Test Framework könnte keine Error Messages abfangen (unabhängig davon, ob dies sinnvoll ist oder nicht). Die Antwort ist: Es &lt;i&gt;kann&lt;/i&gt; - wenn wir bereit sind, eine weitere Indirektionsebene einzuführen! Indem ich ausführe, wie das geht, will ich keineswegs zur Verwendung von Error Messages raten. Es könnte aber sein, dass das Wissen um diese Möglichkeit in irgendwelchen Fällen einmal nützlich ist. &lt;br /&gt;&lt;br /&gt;Tatsächlich bietet ABAP eine Möglichkeit, Fehlermeldungen abzufangen: Funktionsbausteine haben stets die eingebaute Ausnahme &lt;tt&gt;error_message&lt;/tt&gt;. Wenn diese Ausnahme bei Aufruf eines Funktionsbausteins explizit abgefangen wird, so wird sie beim Auftreten einer Error Message innerhalb des vom Funktionsbaustein durchlaufenen oder aufgerufenen Codes ausgelöst. Man kann also nach Aufruf des Funktionsbausteins wie üblich den &lt;tt&gt;sy-subrc&lt;/tt&gt; abfragen, um zu erkennen, dass eine Error Message ausgelöst wurde.  &lt;br /&gt; &lt;br /&gt;Nun gibt es für Methoden aus gutem Grund (s.o.) keinen solchen Mechanismus. Wir können jedoch einen Funktionsbaustein einführen, der dynamisch Methoden eines zur Laufzeit gegebenen Objekts mit einer zur Laufzeit gegebenen Parameterliste aufruft: &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;FUNCTION Z_TEST_E_MESSAGE.&lt;br /&gt;*"----------------------------------------------------------------------&lt;br /&gt;*"*"Lokale Schnittstelle:&lt;br /&gt;*"  IMPORTING&lt;br /&gt;*"     REFERENCE(IO_OBJECT) TYPE REF TO  OBJECT&lt;br /&gt;*"     REFERENCE(IV_METHOD) TYPE  CSEQUENCE&lt;br /&gt;*"     REFERENCE(IT_PARAMETERS) TYPE  ABAP_PARMBIND_TAB&lt;br /&gt;*"----------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;  call method io_object-&gt;(iv_method)&lt;br /&gt;    parameter-table it_parameters.&lt;br /&gt;&lt;br /&gt;ENDFUNCTION.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Das folgende Beispielprogramm zeigt dann mithilfe eines Miniobjekts &lt;tt&gt;lcl_testee&lt;/tt&gt;,&lt;br /&gt;das auf Wunsch Error Messages erzeugt, wie das Auftreten oder Nichtauftreten einer&lt;br /&gt;Error Message im Unit Test geprüft werden kann:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;* ---&lt;br /&gt;report  z_test_e_message.&lt;br /&gt;&lt;br /&gt;* --- How to test for error messages within unit tests&lt;br /&gt;&lt;br /&gt;* ---&lt;br /&gt;class lcl_testee definition.&lt;br /&gt;  public section.&lt;br /&gt;    methods unwise_check &lt;br /&gt;      importing iv_error type flag.&lt;br /&gt;endclass.                    "lcl_testee DEFINITION&lt;br /&gt;&lt;br /&gt;* ---&lt;br /&gt;class lcl_testee implementation.&lt;br /&gt;  method unwise_check.&lt;br /&gt;    if iv_error eq 'X'.&lt;br /&gt;      message e001(bl) &lt;br /&gt;        with 'Never issue E-message in a validator'.&lt;br /&gt;    endif.&lt;br /&gt;  endmethod.                    "unwise_check&lt;br /&gt;endclass.                    "lcl_testee IMPLEMENTATION&lt;br /&gt;&lt;br /&gt;* ---&lt;br /&gt;class lcl_test definition &lt;br /&gt;      for testing  " #AU Risk_Level Harmless&lt;br /&gt;      inheriting from cl_aunit_assert. " #AU Duration Short&lt;br /&gt;&lt;br /&gt;  private section.&lt;br /&gt;    data go_testee type ref to lcl_testee.&lt;br /&gt;    methods:&lt;br /&gt;      setup,&lt;br /&gt;      prepare_parameters &lt;br /&gt;        importing iv_error type flag&lt;br /&gt;        exporting et_parameters type abap_parmbind_tab,&lt;br /&gt;      test_error for testing,&lt;br /&gt;      test_no_error for testing.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;endclass.                    "lcl_test DEFINITION&lt;br /&gt;&lt;br /&gt;*&lt;br /&gt;class lcl_test implementation.&lt;br /&gt;*&lt;br /&gt;  method setup.&lt;br /&gt;    create object go_testee.&lt;br /&gt;  endmethod.                    "setup&lt;br /&gt;*&lt;br /&gt;  method test_error.&lt;br /&gt;    data: lt_parameters type abap_parmbind_tab,&lt;br /&gt;          lv_subrc type i.&lt;br /&gt;    call method prepare_parameters&lt;br /&gt;      exporting iv_error = 'X'&lt;br /&gt;      importing et_parameters = lt_parameters.&lt;br /&gt;    call function 'Z_TEST_E_MESSAGE'&lt;br /&gt;      exporting&lt;br /&gt;        io_object     = go_testee&lt;br /&gt;        iv_method     = 'UNWISE_CHECK'&lt;br /&gt;        it_parameters = lt_parameters&lt;br /&gt;      exceptions&lt;br /&gt;        error_message = 1.&lt;br /&gt;    lv_subrc = sy-subrc.&lt;br /&gt;    assert_not_initial( &lt;br /&gt;      act = lv_subrc&lt;br /&gt;      msg = 'Method should issue an error message' ).&lt;br /&gt;  endmethod.                    "test&lt;br /&gt;*&lt;br /&gt;  method test_no_error.&lt;br /&gt;    data: lt_parameters type abap_parmbind_tab.&lt;br /&gt;    call method prepare_parameters&lt;br /&gt;      exporting iv_error = space&lt;br /&gt;      importing et_parameters = lt_parameters.&lt;br /&gt;    call function 'Z_TEST_E_MESSAGE'&lt;br /&gt;      exporting&lt;br /&gt;        io_object     = go_testee&lt;br /&gt;        iv_method     = 'UNWISE_CHECK'&lt;br /&gt;        it_parameters = lt_parameters&lt;br /&gt;      exceptions&lt;br /&gt;        error_message = 1.&lt;br /&gt;    assert_subrc( sy-subrc ).&lt;br /&gt;  endmethod.               "test_no_error&lt;br /&gt;*&lt;br /&gt;  method prepare_parameters.&lt;br /&gt;    data: ls_parameter type abap_parmbind,&lt;br /&gt;          lv_error type ref to flag.&lt;br /&gt;&lt;br /&gt;    clear et_parameters.&lt;br /&gt;&lt;br /&gt;    create data lv_error.&lt;br /&gt;    lv_error-&gt;* = iv_error.&lt;br /&gt;    ls_parameter-name  = 'IV_ERROR'.&lt;br /&gt;    ls_parameter-kind  = cl_abap_objectdescr=&gt;exporting.&lt;br /&gt;    ls_parameter-value = lv_error.&lt;br /&gt;&lt;br /&gt;    insert ls_parameter into table et_parameters.&lt;br /&gt;&lt;br /&gt;  endmethod.&lt;br /&gt;&lt;br /&gt;endclass.                    "lcl_test IMPLEMENTATION&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Dieses Programm beweist, dass es durchaus möglich ist, Error Messages in Unit Tests abzufangen. Der beträchtliche Umfang dieses Testcodes für eine so einfache Sache wie das Auslösen einer Fehlermeldung ist allerdings ein untrüglicher "&lt;a href="http://de.wikipedia.org/wiki/Code_smell"&gt;Code Smell&lt;/a&gt;" &amp;ndash; in diesem Fall dafür, dass der &lt;tt&gt;message&lt;/tt&gt;-Befehl an einem ungeeigneten Ort verwendet wird.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Zitiert aus Greg Wilson, Andy Oram [Hg.]: &lt;i&gt;Beautiful Code&lt;/i&gt;, O'Reilly, Sebastopol (CA), Juni 2007, S.279.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-6310666493548947822?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/6310666493548947822/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=6310666493548947822' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/6310666493548947822'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/6310666493548947822'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2010/11/keine-message-befehle-in-geschaftslogik.html' title='Keine Message-Befehle in Geschäftslogik!'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-6169947904187976123</id><published>2010-11-23T23:29:00.023+01:00</published><updated>2011-03-06T21:17:30.824+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Eine objektorientierte Metasprache</title><content type='html'>In Computerzeitschriften und Büchern über modellgetriebene Softwareentwicklung wird in jüngster Zeit häufig das Framework &lt;a href="http://www.eclipse.org/Xtext/"&gt;Xtext&lt;/a&gt; beschrieben und gepriesen &amp;mdash; was vor allem dem Umstand zu verdanken ist, dass es für die Integration von Programmiersprachen in die Eclipse IDE verwendet wird.[1]  Xtext (bzw. dessen zugrundeliegender Parsergenerator &lt;a href="http://www.antlr.org/"&gt;Antlr&lt;/a&gt;) ist ein leicht zu erlernendes Tool, um die Grundzüge der Entwicklung domänenspezifischer Sprachen kennenzulernen &amp;mdash; gerade deshalb eignet es sich auch gut für den Einstieg in dieses Gebiet. Es gibt aber interessante Alternativen, die man zumindest kennen sollte. &lt;br /&gt;&lt;br /&gt;Eine interessantere Option für zur Laufzeit pflegbare Modelle, für Codegenerierung wie auch für die Entwicklung von DSLs stellen in meinen Augen die sogenannten &lt;a href="http://en.wikipedia.org/wiki/Parsing_expression_grammar"&gt;Parsing Expression Grammars (PEG)&lt;/a&gt; dar, insbesondere die von Alessandro Warth entwickelte objektorientierte Metasprache &lt;a href="http://tinlizzie.org/ometa"&gt;OMeta&lt;/a&gt;. Der Grundgedanke aller PEGs ist ein verallgemeinerter Begriff des Pattern Matching, wie es z.B. in regulären Ausdrücken verwendet wird. PEGs nutzen die Erkenntnis, dass alle üblicherweise mit dem Bau von Compilern und Interpretern verbundenen Aufgaben in gewisser Weise nur Varianten eines gemeinsamen Mechanismus sind - der Mustererkennung: [2] &lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt; Der &lt;i&gt;Tokenizer&lt;/i&gt; (auch &lt;i&gt;Lexer&lt;/i&gt; oder &lt;i&gt;Scanner&lt;/i&gt; genannt) transformiert einen Array von Zeichen (die Eingabe, den Quelltext) in eine Folge von syntaktischen Grundeinheiten, die Tokens. Klassisch verwendet man hierzu ein Werkzeug wie &lt;a href="http://dinosaur.compilertools.net/lex/index.html"&gt;lex&lt;/a&gt; oder dessen Weiterentwicklung &lt;a href="http://dinosaur.compilertools.net/flex/index.html"&gt;Flex&lt;/a&gt;. Im Prinzip stellt ein Lex- oder Flexfile eine Liste von regulären Ausdrücken dar, die der Reihe nach auf die Eingabe angewendet werden, bis ein passender Ausdruck gefunden wird. Jedem regulären Ausdruck ist eine "Aktion" in Form von C-Code zugeordnet, die schliesslich mit der Rückgabe eines wohldefinierten Tokens mitsamt eines etwaigen Token-Arguments enden muss. Zur Illustration, wie ein solches Flexfile notiert wird, mag mein &lt;a href="http://astropatterns.cvs.sourceforge.net/viewvc/astropatterns/apc%2B/apc%2Blexer.lex?view=markup"&gt;Flex-Definitionsfile apc_lexer.lex&lt;/a&gt; im Projekt &lt;a href="http://astropatterns.sourceforge.net"&gt;astropatterns&lt;/a&gt; dienen. &lt;br /&gt;&lt;li&gt; Der &lt;i&gt;Parser&lt;/i&gt; operiert auf dem Array von Tokens, um eine interne Datenstruktur zu erzeugen, die für die effiziente maschinelle Abarbeitung besonders gut geeignet ist. In der Regel ist das Ergebnis des Parsings ein abstrakter Syntaxbaum (AST). Auch das Parsing ist im Grunde Mustererkennung, nur auf einer höheren Ebene: Er arbeitet mit einer anderer Art "Alphabet", das aus terminalen und nichtterminalen Symbolen besteht, und die Muster sind die Syntaxregeln, die sogenannten Produktionsregeln. Parsergeneratoren haben eine lange Geschichte, in denen &lt;a href="http://dinosaur.compilertools.net/yacc/index.html"&gt;yacc&lt;/a&gt; (&lt;i&gt;&lt;b&gt;y&lt;/b&gt;et &lt;b&gt;a&lt;/b&gt;nother &lt;b&gt;c&lt;/b&gt;ompiler &lt;b&gt;c&lt;/b&gt;ompiler&lt;/i&gt;) einen ersten Meilenstein darstellte. &lt;a href="http://dinosaur.compilertools.net/yacc/index.html"&gt;yacc&lt;/a&gt;, obwohl vor über 30 Jahren entwickelt, wird bis heute für den Entwurf von Programmiersprachen verwendet. Ich habe mit &lt;a href="http://dinosaur.compilertools.net/bison/index.html"&gt;Bison&lt;/a&gt;, dem Nachfolger von yacc, eine &lt;a href="http://astropatterns.sourceforge.net/apc_plus.html"&gt;astrologische DSL&lt;/a&gt; implementiert, die hier ebenfalls der Veranschaulichung dienen soll. Die &lt;a href="http://astropatterns.cvs.sourceforge.net/viewvc/astropatterns/apc%2B/apc%2Bparser.y?view=markup"&gt;Bison-Grammatik dieser DSL&lt;/a&gt; zeigt als strukturelle Ähnlichkeit, dass auch hier die Eingabe mit einer Reihe sogenannter Produktionsregeln verglichen wird. Wenn eine passende Regel gefunden wird, wird die zugeordnete &lt;i&gt;semantische Aktion&lt;/i&gt; ausgeführt.  &lt;br /&gt;&lt;li&gt; &lt;i&gt;Type Checker&lt;/i&gt; und &lt;i&gt;Optimierer&lt;/i&gt; sind Transformationen des AST, die bestimmte Muster erkennen und durch spezifischere oder effizientere Konstruktionen ersetzen. Da das Produkt des Parsers in der Regel ein Syntaxbaum ist, verwendet man häufig das &lt;i&gt;Besucher&lt;/i&gt;-Entwurfsmuster, um den Baum zu traversieren und dabei je nach Anwendungsfall spezifische Operationen auszuführen.[3] &lt;br /&gt;&lt;li&gt; Das Gleiche gilt schliesslich für den &lt;i&gt;Code Generator&lt;/i&gt;, der den AST in Programmquelltext oder Binärcode für einen Prozessor oder eine virtuelle Maschine transformiert. Auch hierfür werden an das Besuchermuster angelehnte Methoden verwendet, obwohl sich auch diese Aufgabe in Form einer Grammatik mit Produktionsregeln formulieren liesse.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Die Programmiersprache OMeta bietet einen Mechanismus, um all diese Aufgaben in Form einer PEG-Grammatik zu formulieren und zu lösen. Das Konzept ist so allgemein gehalten, dass im Prinzip beliebige Transformationen von Quelltexten und Datenstrukturen in andere Zielstrukturen möglich sind. Die OMeta-Syntax kann in verschiedene Hostsprachen eingebettet werden, es gibt z.B. OMeta-Implementierungen für &lt;tt&gt;Squeak&lt;/tt&gt;, &lt;tt&gt;COLA&lt;/tt&gt; (der ersten verwendeten Hostsprache &amp;ndash; einer Mischung aus Scheme und Smalltalk), &lt;tt&gt;C#&lt;/tt&gt;, &lt;tt&gt;JavaScript&lt;/tt&gt;, &lt;tt&gt;Ruby&lt;/tt&gt; u.v.a.m. &amp;ndash; die Zahl der Implementierungen steigt. Besonders attraktiv finde ich dabei die Kooperation mit dynamischen Hostsprachen wie JavaScript oder Python. Denn die interpretierten Sprachen sind offenbar besonders gut gerüstet für die Vision der modellgetriebenen Softwareentwicklung, zur Laufzeit in einer domänenspezifischen Sprache das Systemverhalten steuern zu können. Die Sprache JavaScript hat darüberhinaus den zusätzlichen Vorteil, dass sie als "Standardsprache der Webbrowser" ohne Extrakosten in Webanwendungen verfügbar ist. Weil ich die Sprache OMeta so wichtig finde, habe ich auf meiner Homepage eine Oberfläche zum Experimentieren  unter dem Link &lt;br /&gt;&lt;br /&gt;&lt;a href="http://ruediger-plantiko.net/ometa/"&gt;http://ruediger-plantiko.net/ometa/&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;bereitgestellt. Sie ist ähnlich der auf &lt;a href="http://tinlizzie.org"&gt;http://tinlizzie.org&lt;/a&gt; bereitgestellten Seite, trennt jedoch klarer den Grammatik-Definitionsteil von der Ausführung des daraus generierten Parsers.&lt;br /&gt;&lt;br /&gt;OMeta ist, wie es der Name sagt, eine &lt;i&gt;objektorientierte Metasprache&lt;/i&gt;, die viele interessante Features aufweist:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;  &lt;li&gt;Die Eingabe von OMeta muss nicht als Quelltext oder überhaupt als Zeichenfolge vorliegen, sondern kann &lt;i&gt;ein Array von beliebigen Objekten&lt;/i&gt; der Hostsprache sein. Das ermöglicht z.B. auch die Transformation von abstrakten Syntaxbäumen. Denn jeder Syntaxbaum ist i.w. als eventuell verschachtelte Folge von Arrays, Tokens und Objekten darstellbar. Das JavaScript-Statement &lt;br /&gt;  &lt;pre class="sh_javascript_dom"&gt;document.getElementById("btnSave").click();&lt;/pre&gt;&lt;br /&gt;  könnte z.B. durch folgenden AST dargestellt werden:&lt;br /&gt;  &lt;pre class="sh_javascript_dom"&gt;[ APPLY, &lt;br /&gt;    [ APPLY, document, "getElementById", ["btnSave" ] ], &lt;br /&gt;    "click", [] ]&lt;/pre&gt;&lt;br /&gt;  Das ist, formal gesehen, ein Array, der aus Objekten besteht - nämlich wiederum aus Arrays, aus Symbolen wie &lt;tt&gt;APPLY&lt;/tt&gt; (die z.B. auch als Strings realisierbar wären), aus gewöhnlichen JavaScript-Objekten der Hostsprache, wie hier dem DOM-Objekt &lt;tt&gt;document&lt;/tt&gt;, und Datentypen wie Strings, Zahlen, usw.[4] In dieser Form könnte der AST wiederum einem OMeta-Programm zur Verarbeitung vorgelegt werden. &lt;br /&gt;  &lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;i&gt;Objektorientierung&lt;/i&gt; erlaubt die Trennung von Produktionsregeln in verschiedenen Namensräumen. So ist jede Grammatik frei in ihrer Namenswahl für die verwendeten Symbole, unbeeinflusst durch eventuell bereits im selben Prozess bestehende Grammatiken. Ausnahme ist natürlich der explizit gewünschte Bezug auf eine bestehende Grammatik durch den Vererbungsmechanismus. Es ist möglich, eine OMeta-Grammatik von einer anderen erben zu lassen und sich in der Formulierung der Produktionsregeln auf die Regeln der Superklasse zu beziehen. Das erlaubt es, bestehende Parser, z.B. den notwendigerweise existierenden Parser der Hostsprache, mit wenigen Codezeilen um eigene Konstrukte zu erweitern. Dadurch ist OMeta ein ideales Werkzeug für Entwickler von Programmiersprachen, um neue syntaktische Idiome auszuprobieren.  &lt;br /&gt;  &lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Im Gegensatz zu kontextfreien Grammatiken (CFG) ist der Auswahloperator &lt;tt&gt;|&lt;/tt&gt; in einer PEG nichtdeterministisch: Es ist ein priorisierter, "kurzschliessender" Operator, dessen Evaluation bei der ersten passenden Alternative abgebrochen wird. Das kann als Nachteil angesehen werden, da die "veroderten" Operanden dadurch nicht mehr gleichberechtigt sind. Andererseits ist es auch ein Vorteil, da die Interpretation eines priosierten Auswahloperators keine Mehrdeutigkeiten produziert, wodurch die PEGs einfacher nachvollziehbar werden.   &lt;br /&gt;  &lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Das letztgenannte Feature des priorisierten Auswahloperators bringt üblicherweise das Problem von Endlosschleifen für linksrekursive Regeln mit sich. Die Regel &lt;br /&gt;  &lt;pre class="sh_javascript"&gt;expr = expr "-" number | number&lt;/pre&gt;  &lt;br /&gt;  zur Definition von Subtraktions-Ausdrücken kann in einer herkömmlichen PEG nicht ausgewertet werden: Um diese Regel zu erkennen, wird zuerst der linke Teil der Alternative ausprobiert; hierzu muss wiederum die Erkennung von &lt;tt&gt;expr&lt;/tt&gt; ausgeführt werden usw. Das führt zu einem unendlichen Abstieg. In komplizierteren Fällen bildet sich ein geschlossener Zykel aus mehreren voneinander abhängigen Regeln, die sogenannte indirekte Linksrekursion.&lt;br /&gt;  &lt;br /&gt;  Nun lassen sich alle Regeln, die auf das Problem der Linksrekursion stossen, von Hand so umschreiben, dass die Linksrekursion nicht mehr auftritt. Aber die Ausdrücke werden dadurch umständlicher und sind nicht mehr so leicht nachvollziehen. Aus Sicht eines Grammatik-Entwicklers ist es eine unnötige Komplikation, in seiner Grammatik Linksrekursionen vermeiden zu müssen. &lt;br /&gt;  &lt;br /&gt;  Nun ist es  Alessandro Warth mit OMeta gelungen, dieses Problem zu lösen. In OMeta kann man auch linksrekursive Regeln wie die obige notieren. Die Effizienz leidet darunter nicht wesentlich - die Parsezeiten hängen in der Regel weiterhin linear von der Länge des Inputs ab. Die Möglichkeit rekursiver Regeldefinitionen erhöht die Ausdruckskraft von OMeta in der Verwendung als alternative PEG-Implementierungen.  &lt;br /&gt;  &lt;/li&gt;&lt;br /&gt;  &lt;li&gt;OMeta erlaubt die Definition sogenannter &lt;i&gt;parametrisierter Regeln&lt;/i&gt;. Das sind, wie der Name sagt, Regeln, die von Parametern abhängen. Warth erläutert dieses Idiom anhand einer Regel für Zeichenmengen. Zeichenmengen kann man traditionell durch den Auswahloperator definieren. Wenn es die eingebaute Regel &lt;tt&gt;digit&lt;/tt&gt; für die Erkennung von Ziffern in OMeta nicht schon gäbe, könnte man sie so definieren:&lt;br /&gt;  &lt;pre class="sh_javascript"&gt;digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' &lt;/pre&gt;&lt;br /&gt;  Solche Regeln sind fehleranfällig und langsam in der Ausführung. Hier helfen parametrisierte Regeln wie die folgende (die überdies zeigt, dass auch in parametrisierten Regeln sogenannte semantische Aktionen angegeben werden können, wie hier die Rückgabe des erkannten Zeichens):&lt;br /&gt;  &lt;pre class="sh_javascript"&gt;charRange :x :y = char:c ?(x &lt;= c &amp;&amp; c &lt;= y) -&gt; c&lt;/pre&gt;&lt;br /&gt;  Ist die Regel &lt;tt&gt;charRange&lt;/tt&gt; einmal erklärt, kann sie in weiteren "konkreten" Regeln wiederverwendet werden:&lt;br /&gt;  &lt;pre class="sh_javascript"&gt;digit     = charRange('0','9'),&lt;br /&gt;lowerCase = charRange('a','z'),&lt;br /&gt;upperCase = charRange('A','Z')&lt;/pre&gt;&lt;br /&gt;Das Beispiel lässt erkennen, dass die parametrisierten Regeln eine höhere Abstraktionsebene einführen. Wie Vererbung erlauben sie eine "Oben-Unten-Trennung", eine Trennung des Allgemeinen vom Konkreten.&lt;br /&gt;  &lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Wie um dies noch zu überbieten, unterstützt OMeta sogar &lt;i&gt;Regeln höherer Ordnung&lt;/i&gt;: Das sind Regeln, die andere Regeln als Argument haben! Dieses Feature wird möglich durch eine Kombination der parametrisierten Regeln mit einer speziellen eingebauten Regel namens &lt;tt&gt;apply&lt;/tt&gt;. &lt;tt&gt;apply&lt;/tt&gt; nimmt den &lt;i&gt;Namen&lt;/i&gt; einer Regel entgegen und führt diese aus. Als sinnvolles, praktisches Beispiel gibt Warth folgende OMeta-Regel: &lt;br /&gt;  &lt;pre class="sh_javascript"&gt;listOf :p = apply(p) ( "," apply(p) )*&lt;/pre&gt; &lt;br /&gt;  Diese Regel erkennt ein oder mehrere, durch Komma getrennte Vorkommen von "etwas, das der Regel &lt;tt&gt;p&lt;/tt&gt; genügt". Beispielsweise erkennt der Term&lt;br /&gt;  &lt;pre class="sh_javascript"&gt;listOf('expression')&lt;/pre&gt;&lt;br /&gt;  eine mit Kommas getrennte Liste von Ausdrücken (die einer früher definierten Regel &lt;tt&gt;expression&lt;/tt&gt; genügen), während&lt;br /&gt;  &lt;pre class="sh_javascript"&gt;listOf('name')&lt;/pre&gt;&lt;br /&gt;  auf eine Liste von Namen (die der früher definierten Regel &lt;tt&gt;name&lt;/tt&gt; genügen) passt. &lt;br /&gt;  &lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Ich kann hier nicht die gesamte Sprache &lt;tt&gt;OMeta&lt;/tt&gt; erläutern, sondern nur zu ihrem Studium anregen. Die obigen Erklärungen fassen wesentliche Teile von Warths Dissertation zusammen. Wer mehr erfahren will, sei auf &lt;a href="http://www.tinlizzie.org/ometa/"&gt;http://www.tinlizzie.org/ometa/&lt;/a&gt; verwiesen. &lt;br /&gt;&lt;br /&gt;Immerhin will ich noch ein kleines, in der Szene der "Compilerbauer" beliebtes Beispiel vorführen: einen "Tischrechner". Dieser konkrete Tischrechner kann sogar mit Variablen arbeiten, was nebenbei beweist, dass man mit OMeta auch zustandsbehaftete Parser bauen kann. Auf meiner &lt;a href="http://ruediger-plantiko.net/ometa/"&gt;Testseite&lt;/a&gt; erreicht man seine Definition durch Auswahl von &lt;tt&gt;Calculator&lt;/tt&gt; in der Liste der verfügbaren Grammatiken. Auch dieses Beispiel ist der Dissertation von Warth entnommen. Ich will es hier kurz diskutieren.&lt;br /&gt;&lt;br /&gt;Mit der ersten Zeile&lt;br /&gt;&lt;pre class="sh_javascript"&gt;ometa Calc &lt;: Parser {&lt;/pre&gt;&lt;br /&gt;wird eine neue Grammatik (eine neue Transformation, ein neuer Parser, ein neues OMeta-Objekt) deklariert. In diesem Fall soll es von der eingebauten Grammatik &lt;tt&gt;Parser&lt;/tt&gt; erben. Diese eingebaute Klasse enthält aktuell nur eine einzige grundlegende Parser-Funktionalität &amp;ndash; nämlich eine &lt;tt&gt;token&lt;/tt&gt;-Regel, mit der durch Leerzeichen getrennte Strings erkannt werden können. Diese &lt;tt&gt;token&lt;/tt&gt;-Regel wird in allen von &lt;tt&gt;Parser&lt;/tt&gt; erbenden Grammatikdefinitionen automatisch den in Doppelhochkommata eingeschlossenen Strings unterlegt. Mehr bekommt man durch diese Vererbung nicht.&lt;br /&gt;&lt;br /&gt;Die nächste Zeile&lt;br /&gt;&lt;pre class="sh_javascript"&gt;var           = spaces letter:x             -&gt; x,&lt;/pre&gt;&lt;br /&gt;zeigt die typische Syntax einer Produktionsregel in OMeta. Dem Namen der Regel folgt nach einem Gleichheitszeichen ihre Definition, und nach der Definition kann, mit einem Pfeil &lt;tt&gt;-&gt;&lt;/tt&gt; abgetrennt, eine &lt;i&gt;semantische Aktion&lt;/i&gt; angegeben werden. Eine semantische Aktion ist im Code der Hostsprache notiert und wird für ein erkanntes Muster evaluiert. Das Ergebnis der Evaluation ist auch der Rückgabewert der sogenannten &lt;tt&gt;matchAll()&lt;/tt&gt;-Methode, mit der man schliesslich den Parser aufrufen kann.&lt;br /&gt;&lt;br /&gt;In diesem Fall haben wir eine Regel &lt;tt&gt;var&lt;/tt&gt; zur Erkennung von Variablennamen. Zunächst wird festgelegt, dass Leerzeichen unmittelbar vor einem Variablennamen ignoriert werden sollen. Dies erreicht man mit der eingebauten Regel &lt;tt&gt;spaces&lt;/tt&gt;, die "Null oder mehr Leerzeichen" erkennt. Danach folgt die eigentliche Festlegung, was als Variable erkannt werden soll: Variablen sollen in unserem Tischrechner einbuchstabig sein. Hierzu benutzen wir die eingebaute OMeta-Basisregel &lt;tt&gt;letter&lt;/tt&gt;. Nach dem Doppelpunkt folgt nun ein Variablenname, der bei erfolgreicher Anwendung der Regel das Evaluationsergebnis der semantischen Aktion enthält (oder, wenn keine semantische Aktion ist, den als passend erkannten Teil der Eingabe). Auf diese Variablen, die während des Parsens zugewiesen werden, kann man dann in der semantischen Aktion zurückgreifen &amp;ndash; wie hier. Wir legen fest, dass der Rückgabewert der Regel genau der erkannte Buchstabe ist.&lt;br /&gt;&lt;br /&gt;Eine einfachere, völlig äquivalente Formulierung wäre übrigens&lt;br /&gt;&lt;pre class="sh_javascript"&gt;var           = spaces letter&lt;/pre&gt;&lt;br /&gt;Aber wir wollen ja schliesslich lernen, wie OMeta funktioniert, und dafür ist die erste Variante besser geeignet. &lt;br /&gt;&lt;br /&gt;Nach den Variablen legt die folgende Regel die erlaubten Zahlen fest - positive, ganze Zahlen:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;num           = num:n digit:d        -&gt; (n*10 + d*1)&lt;br /&gt;              | digit:d              -&gt; (d*1),&lt;/pre&gt;&lt;br /&gt;Hier ist neu der Gebrauch des Auswahloperators &lt;tt&gt;|&lt;/tt&gt; für die Deklaration der Alternativen. Ausserdem lernen wir, dass auch Teilen einer Regel bereits eine semantische Aktion zugeordnet werden darf. Der erste Teil der Regel enthält darüberhinaus die bereits besprochene Linksrekursion. &lt;br /&gt;&lt;br /&gt;Diese Regel hätte man äquivalent auch so schreiben können:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;num           = digit+:d        -&gt; (1*d.join('')),&lt;/pre&gt;&lt;br /&gt;Neu ist hier der Quantifier &lt;tt&gt;+&lt;/tt&gt; mit derselben Semantik wie in regulären Ausdrücken. Ein mit dem Quantifier &lt;tt&gt;+&lt;/tt&gt; oder &lt;tt&gt;*&lt;/tt&gt; angereicherter Ausdrucks evaluiert immer zu einem Array mit den einzelnen Treffern als Elementen. Den Array können wir in der semantischen Aktion mittels der Methode &lt;tt&gt;join()&lt;/tt&gt; der JavaScript-Klasse &lt;tt&gt;Array&lt;/tt&gt; zu einem String zusammenspleissen und schliesslich durch Multiplikation mit 1 (type coercion) die Umwandlung in den numerischen Datentyp erzwingen. Auch diese Variante der Regel &lt;tt&gt;num&lt;/tt&gt; ist sicher lehrreich. &lt;br /&gt;&lt;br /&gt;Es folgen nun die &lt;i&gt;primären Ausdrücke&lt;/i&gt;: Die Eingabe eines Variablennamens soll deren Inhalt zurückliefern, die Eingabe eines Zahlenstrings soll ebendiese Zahl zur Antwort haben, und komplexere Ausdrücke sollen geklammert werden können:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;primaryExpr   = spaces var:x         -&gt; self.vars[x]&lt;br /&gt;              | spaces num:n         -&gt; n&lt;br /&gt;              | "(" expr:r ")"       -&gt; r,&lt;/pre&gt;&lt;br /&gt;Neu ist hier der Zugriff auf die Variable &lt;tt&gt;self.vars&lt;/tt&gt;, die in einer speziellen, bei der Parserinstanziierung aufgerufenen sogenannten &lt;tt&gt;initialize()&lt;/tt&gt;-Methode als leerer Hash definiert wird und uns als Container (genauer: als Symboltabelle) für die im Tischrechner eingegebenen Variableninhalte dient.                  &lt;br /&gt;                &lt;br /&gt;Es kommen nun die Multiplikations- und Additionsregeln:                &lt;br /&gt;&lt;pre class="sh_javascript"&gt;mulExpr       = mulExpr:x "*" primaryExpr:y -&gt; ( x * y )&lt;br /&gt;              | mulExpr:x "/" primaryExpr:y -&gt; ( x / y )&lt;br /&gt;              | primaryExpr,&lt;br /&gt;addExpr       = addExpr:x "+" mulExpr:y     -&gt; ( x + y )&lt;br /&gt;              | addExpr:x "-" mulExpr:y     -&gt; ( x - y )&lt;br /&gt;              | mulExpr,&lt;/pre&gt;&lt;br /&gt;Wir benutzen hierbei die Eigenschaft, dass der Auswahloperator priorisierend ist, um der Punktrechnung vor der Strichrechnung den Vorzug zu geben: Es werden immer zuerst die &lt;tt&gt;mulExpr&lt;/tt&gt; erkannt (und evaluiert), bevor ein &lt;tt&gt;addExpr&lt;/tt&gt; erkannt wird. Nicht ganz dem entsprechend, was der Name suggeriert, subsumiert die Regel &lt;tt&gt;addExpr&lt;/tt&gt; insbesondere auch den &lt;tt&gt;mulExpr&lt;/tt&gt; (dritte Alternation der Regel für additive Ausdrücke). Auf der rechten Seite stehen wieder die semantische Aktionen - in den Klammern kann beliebiger JavaScript-Code codiert sein. &lt;br /&gt;&lt;br /&gt;Die &lt;tt&gt;addExpr&lt;/tt&gt; ist somit allgemeiner als &lt;tt&gt;mulExpr&lt;/tt&gt; und allgemeiner als die &lt;tt&gt;primaryExpr&lt;/tt&gt;. Sie enthält letztere als Spezialfälle. Mit der folgenden Regel &lt;br /&gt;&lt;pre class="sh_javascript"&gt;expr          = var:x  "=" expr:r           -&gt; (self.vars[x] = r)&lt;br /&gt;                | addExpr,&lt;/pre&gt;&lt;br /&gt;umfassen wir somit alle für den Tischrechner gültigen Ausdrücke. Variablenzuweisungen ebenso wie arithmetische Ausdrücke (die auch wieder Variablen enthalten dürfen).&lt;br /&gt;&lt;br /&gt;Die letzte Regel &lt;tt&gt;doit&lt;/tt&gt; legt fest, dass soviele Ausdrücke wie möglich evaluiert werden sollen, und schliesslich das Ergebnis des letzten gültigen Ausdrucks ausgegeben werden soll. &lt;br /&gt;&lt;pre class="sh_javascript"&gt;doit          = (expr:r)* spaces end        -&gt; r&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Hier folgt noch - ausserhalb der OMeta-Grammatik, die bereits erwähnte &lt;tt&gt;initialize()&lt;/tt&gt;-Methode, mit der der Rechner in den Startzustand versetzt wird:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;Calc.initialize = function() {  this.vars = {}; }&lt;/pre&gt;  &lt;br /&gt;In meiner &lt;a href="http://ruediger-plantiko.net/ometa/"&gt;"Workbench"&lt;/a&gt; kann ich nun die Regel &lt;tt&gt;doit&lt;/tt&gt; auf die Eingabe&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;x=1 y=2 x+y&lt;/pre&gt;&lt;br /&gt;anwenden und erhalte die Ausgabe &lt;tt&gt;3&lt;/tt&gt;. Das liegt daran, dass die &lt;tt&gt;doit&lt;/tt&gt;-Regel mehrere, durch Leerraum getrennte Ausdrücke erkennt und auswertet. Ich hätte aber auch drei Eingaben nacheinander tätigen können: Zuerst&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;x=1&lt;/pre&gt;&lt;br /&gt;danach&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;y=2&lt;/pre&gt;&lt;br /&gt;und schliesslich &lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;x+y&lt;/pre&gt;&lt;br /&gt;Auch dann hätte ich die Ausgabe &lt;tt&gt;3&lt;/tt&gt; erhalten. Der Grund ist, dass meine "Workbench" in jedem Dialogschritt mit derselben Parserinstanz arbeitet und sich daher alle bereits erfolgten Variablenzuweisungen in der Symboltabelle &lt;tt&gt;vars&lt;/tt&gt; gemerkt hat. Es ist eben ein &lt;i&gt;stateful parser&lt;/i&gt; &amp;ndash; und mit dieser Eigenschaft ist OMeta auch für REPL Shells verwendbar (&lt;i&gt;read-evaluate-print loops&lt;/i&gt;).&lt;br /&gt;&lt;br /&gt;Wer mag, kann sich auf meiner &lt;a href="http://ruediger-plantiko.net/ometa/"&gt;OMeta-Workbench&lt;/a&gt; auch meinen in OMeta implementierten XML-Parser anschauen: Ein meiner Ansicht nach durchaus lesbarer Quelltext von nur 42 Zeilen erlaubt die Transformation eines XML-Dokuments in einen in JavaScript modellierten Objektbaum. Das sind die Grössen, mit denen man in OMeta arbeiten kann; es ist auch eine der Fragestellungen, mit denen das &lt;a href="http://www.viewpointsresearch.org/"&gt;Viewpoints Research Institute&lt;/a&gt; angetreten ist, unter dessen Dach auch OMeta entwickelt wurde: Um wieviele Zehnerpotenzen kann bestehender  Programmquelltext bei Einsatz geeigneter, möglichst ausdrucksstarker Programmierumgebungen gekürzt werden, um dennoch das zu erreichen, wozu man heute Computer einsetzt (Textverarbeitung, Graphik, Internet usw.)? &lt;br /&gt;&lt;br /&gt;Dies möge als ein erster Einblick in einen hochinteressanten Parsergenerator genügen, den ich zum näheren Studium nur empfehlen kann. Die Einsatzfelder, rund um den Bereich domänenspezifischer Sprachen (DSL) scheinen mir sehr vielversprechend zu sein. Die Sprache ist kompakt und ausdrucksstark. Ihren mächtigen Features, wie etwa der Objektorientierung, den parametrisierten Regeln und der Rekursion, ist es zu verdanken, dass Grammatiken wesentlich eleganter notiert werden können als in früheren Werkzeuge dieser Art. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] So in Thomas Stahl, Markus Völter, Sven Efftinge, Arno Haase: &lt;i&gt;Modellgetriebene Software-Entwicklung&lt;/i&gt;, 2. Auflage, dpunkt Verlag, Heidelberg 2007.  &lt;br /&gt;Jan Köhnlein und Sebastian Zarnekow, &lt;i&gt;Xtext-Praxis&lt;/i&gt;, eclipse-Magazin 1.2010,  S. 50.&lt;br /&gt;[2] Die folgenden Ideen sind der Dissertation von Alessandro Warth &lt;i&gt;Experimenting with Programming Languages&lt;/i&gt; entnommen. &lt;br /&gt;[3] Es geht jedoch auch ohne das Besucher-Entwurfsmuster, das leider intrusiv ist und dem in der Umsetzung eine gewisse Schwerfälligkeit anhaftet (wie ich in meinem &lt;a href="http://ruediger-plantiko.blogspot.com/2010/05/eine-alternative-zum-besucher.html"&gt;Blog&lt;/a&gt; gezeigt habe).&lt;br /&gt;[4] Dabei wird die Anwendung einer Methode &lt;tt&gt;m&lt;/tt&gt; des Objekts &lt;tt&gt;o&lt;/tt&gt; mit dem Argument-Array &lt;tt&gt;[args...]&lt;/tt&gt; durch das Konstrukt &lt;tt&gt;[APPLY, o, m, [args...]]&lt;/tt&gt; modelliert.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-6169947904187976123?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/6169947904187976123/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=6169947904187976123' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/6169947904187976123'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/6169947904187976123'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2010/11/eine-objektorientierte-metasprache.html' title='Eine objektorientierte Metasprache'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-9133785167328672275</id><published>2010-10-28T07:45:00.021+01:00</published><updated>2010-10-29T08:10:35.942+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Eine Erfolgsgeschichte mit Simple Transformations</title><content type='html'>&lt;a href="http://help.sap.com/saphelp_nw04/helpdata/de/e3/7d4719ca581441b6841f1054ff1326/frameset.htm"&gt;Simple Transformations (ST)&lt;/a&gt; sind eine wunderbare und effiziente Möglichkeit, ABAP-Daten in XML-Dokumente und zurück zu transformieren. Sie sind vom Konzept her bidirektional, somit ideal für die Serialisierung und Deserialisierung geeignet &amp;ndash; und ausserdem sind sie, wie wir beobachten konnten, hocheffizient. Wenn man sich einmal in das Konzept eingearbeitet hat, sind sie darüberhinaus wirklich &lt;i&gt;simple&lt;/i&gt;, wie es der Name verheisst, also einfach in der Implementierung.&lt;br /&gt;&lt;br /&gt;Dank der Simple Transformations ist es uns gelungen, eine unzuverlässige Komponente unseres XI-Systems (den JDBC-Adapter) durch einen stabil laufenden, in ABAP programmierten Adapter zu ersetzen, der über seine Arbeit im Joblog genau Rechenschaft gibt und obendrein einen deutlich besseren Durchsatz hat als die bisherige Lösung.&lt;br /&gt;&lt;br /&gt;Wir haben eine entfernte Datenbank, in die im Laufe des Tages aus verschiedenen Quellen immer wieder neue "Meldungen" eingespeist werden (Wert- und Warenflüsse). Um diese einzulesen und den Java-Mappingklassen zur weiteren Verarbeitung zuzuführen, haben wir bislang den von SAP mitgelieferten sogenannten &lt;a href="http://help.sap.com/saphelp_nw04/helpdata/de/22/b4d13b633f7748b4d34f3191529946/frameset.htm"&gt;JDBC-Adapter&lt;/a&gt; verwendet, der gemäss Beschreibung genau für eine solche Aufgabe geeignet wäre. Leider hat dieser Adapter (in unserem System) eine Schwachstelle, die sich trotz intensiver Bemühungen nicht beheben liess &amp;ndash; er leidet gewissermassen unter &lt;a href="http://de.wikipedia.org/wiki/Narkolepsie"&gt;Narkolepsie&lt;/a&gt;: Ganz unvermittelt stellt er immer wieder mal seine Arbeit komplett ein &amp;ndash; ohne noch irgendein Lebenszeichen von sich zu geben, aber auch ohne dass der Prozess abbricht, geschieht mitten in einer Verarbeitung einfach nichts mehr.&lt;br /&gt;&lt;br /&gt;Da die eingehenden Meldungen zeitkritisch sind, führte dies immer wieder zu extra Wartungsaufwand. Es mussten Überwachungsprogramme geschrieben werden, um die fristgerechte Abarbeitung der Meldungen zu garantieren. Zur Unzeit (so etwas passiert &lt;i&gt;immer&lt;/i&gt; zur Unzeit!) wurde unser Support mit Alarm-SMS aus dem Bett geholt, weil es wieder einmal passiert war. Der manuelle Workaround, um den JDBC-Adapter wieder zum Laufen zu bekommen, bestand dann darin, einen zweiten, identisch konfigurierten Kanal zu definieren und auf diesen zu wechseln. &lt;br /&gt;&lt;br /&gt;Der JDBC-Adapter ist für uns leider eine "Black Box". Das ist das Problematische an den von SAP ausgelieferten Java-Komponenten (nicht an der Sprache Java selbst &amp;ndash; die Sprache Java ist grossartig). Wir können die SAP-Java-Komponenten nicht wie ein ABAP-Programm studieren, um unsere eigenen Schlussfolgerungen zu ziehen.[1] Wir können auch keine Fangschaltung einbauen, um diesen spontan auftretenden Fehler näher zu lokalisieren. Wir sind gezwungen, &lt;i&gt;jedes&lt;/i&gt; Problem an den Support von SAP zu addressieren.  SAP selbst hatte keine Antwort auf unser Problem und musste die OSS-Meldungen schliessen, da das Problem ja nicht reproduzierbar ist. &lt;br /&gt;&lt;br /&gt;Wir haben uns daher dazu entschlossen, diesen JDBC-Adapter aus dem XI-Eingangsprozess zu eliminieren und durch eine in ABAP programmierte Komponente zu ersetzen, die letztlich den sogenannten &lt;i&gt;Plain Adapter&lt;/i&gt; verwendet. Das ist ein vom ABAP-seitigen ICF angesprochener REST-artiger HTTP-Service (zu erreichen unter &lt;tt&gt;/sap/xi/adapter_plain&lt;/tt&gt;). Im Body der HTTP-Anfrage erwartet der Plain Adapter das XML-Dokument in der Form, wie es schliesslich vom XI-Mapping verarbeitet wird. Er reicht dieses Dokument dann an die XI-Mappingschicht weiter. &lt;br /&gt;&lt;br /&gt;Die eigenprogrammierte Komponente ist ein in regelmässigen Abständen gestarteter Job, der&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;die Daten aus der entfernten Datenbank in interne Tabellen einliest,&lt;br /&gt;&lt;li&gt;die ABAP-Daten in ein XML-Dokument transformiert &amp;ndash; dies ist der Ort, an dem die Simple Transformations zum Einsatz kommen &amp;ndash; und&lt;br /&gt;&lt;li&gt;das XML-Dokument schliesslich über einen "internen" HTTP-Request an den Plain Adapter weiterleitet.[2]&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;In einem (typischen) Joblauf wurden 403 Meldungen mit insgesamt 125.254 Meldungspositionen in 123 Sekunden verarbeitet. Das bedeutet einen Durchsatz von rund 1 Millisekunde pro Position. Hierin enthalten ist das Lesen der Position von der Datenbank, die Abbildung der Meldungen in ein XML-Dokument und schliesslich die Verarbeitung im "Plain Adapter". &lt;i&gt;Das sind wunderbare Laufzeiten!&lt;/i&gt; &lt;br /&gt;&lt;br /&gt;Die folgende Graphik zeigt, wie sich die Laufzeit für einen typischen Joblauf auf diese Schritte verteilt: &lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_JitR0EqIJ_A/TMkcW4idW_I/AAAAAAAAAI4/oyxWbKdWe8Y/s1600/st_stat.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 241px;" src="http://4.bp.blogspot.com/_JitR0EqIJ_A/TMkcW4idW_I/AAAAAAAAAI4/oyxWbKdWe8Y/s400/st_stat.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5532984796660980722" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Für das &lt;a href="http://de.wikipedia.org/wiki/Marshalling"&gt;Marshalling&lt;/a&gt;, also die Konvertierung der ABAP-Daten in ein XML-Dokument, wurden nur 12 der 125 Sekunden benötigt. Die XML-Konvertierung, einschliesslich eines Prepare-Schritts in ABAP, in dem die Daten für die Transformation passend aufbereitet werden, schafft also 10 Detailsätze pro Millisekunde. Das ist mehr als zufriedenstellend. &lt;br /&gt;&lt;br /&gt;Es ist sinnvoll, vor dem eigentlichen Aufruf der ST einen vorbereitenden Schritt zu implementieren, in dem die Daten für den Zugriff der ST passend aufbereitet werden. Simple Transformations sind nicht nur einfach zu verstehen, sie sollten auch einfach konzipiert werden. Denn je simpler eine Simple Transformation gerät, desto effizienter wird sie auch. Im vorliegenden Fall erzeugt die ST eine Sequenz von Elementen ("Zeilen"), die jeweils Daten aus Kopf und Position enthalten. Die passende Datenstruktur hierfür ist eine interne Tabelle mit einem tiefen Zeilentyp: Jede Zeile steht für eine Meldung, und die Komponente &lt;tt&gt;detail&lt;/tt&gt; der Zeile ist selbst eine interene Tabelle, die die Positionen enthält. Bei geschachtelten Strukturen ist auf den erhöhten Speicherbedarf zu achten. Eine überschlägige Rechnung zeigte uns aber, dass der Hauptspeicher für das benötigte Datenvolumen auch für sehr grosse Meldungen noch ausreicht. Ausserdem handelt es sich ja nur um eine Hilfstabelle mit extrem kurzer Lebensdauer - sie lebt als lokales Feld "auf dem Stack" und wird genau in der Methode auf- und abgebaut, die die Simple Transformation durchführt: [3]&lt;br /&gt;&lt;pre class="sh_abap"&gt;* Header/Detail in Tabelle mit tiefer Zeilenstruktur abbilden&lt;br /&gt;  loop at it_header assigning &amp;lt;ls_header&gt;.&lt;br /&gt;&lt;br /&gt;    clear ls_meldung.&lt;br /&gt;    move-corresponding &amp;lt;ls_header&gt; to ls_meldung.&lt;br /&gt;&lt;br /&gt;    loop at it_detail assigning &amp;lt;ls_detail&gt;&lt;br /&gt;         where  absender            = &amp;lt;ls_header&gt;-absender and&lt;br /&gt;                meldungs_id         = &amp;lt;ls_header&gt;-meldungs_id and&lt;br /&gt;                meldungs_datum      = &amp;lt;ls_header&gt;-meldungs_datum and&lt;br /&gt;                partition_knoten    = &amp;lt;ls_header&gt;-partition_knoten and&lt;br /&gt;                partition_tag       = &amp;lt;ls_header&gt;-partition_tag.&lt;br /&gt;      insert &amp;lt;ls_detail&gt; into table ls_meldung-detail.&lt;br /&gt;    endloop.&lt;br /&gt;&lt;br /&gt;    insert ls_meldung into table lt_meldungen.&lt;br /&gt;&lt;br /&gt;  endloop.&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Der Aufruf der Transformation ist dann sehr einfach: &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_abap"&gt;* Meldungsspezifisch in XML transformieren&lt;br /&gt;  call transformation (gv_transformation)&lt;br /&gt;    source meldungen = lt_meldungen&lt;br /&gt;    result xml ev_xml.&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Die Transformation wird dynamisch aufgerufen, wir haben für jeden Meldungstyp eine andere Simple Transformation. Die zuvor aufbereitete Meldungstabelle wird nun als &lt;tt&gt;source&lt;/tt&gt; übergeben - das Resultatdokument wird im Parameter &lt;tt&gt;ev_xml&lt;/tt&gt; vom Typ &lt;tt&gt;xstring&lt;/tt&gt; entgegengenommen (es könnte auch ein &lt;tt&gt;string&lt;/tt&gt; oder ein Objekt vom Typ &lt;tt&gt;if_ixml_document&lt;/tt&gt; sein - die Transformation erkennt selbständig den erwarteten Typ).&lt;br /&gt;&lt;br /&gt;Die Simple Transformation selbst ist nun wirklich einfach: Nach Festlegung des Referenz-Datenobjekts mittels &lt;tt&gt;&amp;lt;tt:root&gt;&lt;/tt&gt; werden die Meldungen mit einer geschachtelten &lt;tt&gt;&amp;lt;tt:loop&gt;&lt;/tt&gt; abgearbeitet. In der inneren Loop wird schliesslich die Ergebniszeile hergestellt, indem die entsprechenden Quellfelder aus Meldungskopf oder Meldungsposition in das durch das Mapping gewünschte Zielfeld übernommen werden. &lt;br /&gt;&lt;br /&gt;Hier ein typisches Beispiel (wir haben rund ein Dutzend solcher Transformationen, und sie sehen alle ähnlich aus):&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_xml"&gt;&amp;lt;?sap.transform.simple?&gt;&lt;br /&gt;&amp;lt;!-- Automatisch generierte Transformation. &lt;br /&gt;     Bitte Anpassungen nur an der Vorlage machen &lt;br /&gt;     (XSLT-Transformation ZGDBW_TO_XI_CREATE)--&gt;&lt;br /&gt;&amp;lt;tt:transform xmlns:tt="http://www.sap.com/transformation-templates"&gt;&lt;br /&gt;  &amp;lt;tt:root name="MELDUNGEN"/&gt;&lt;br /&gt;  &amp;lt;tt:template&gt;&lt;br /&gt;    &amp;lt;ns:MT_MVN_DESADV2500 xmlns:ns="http://migros.ch/xi/DESADV2500"&gt;&lt;br /&gt;      &amp;lt;tt:loop ref=".MELDUNGEN" name="header"&gt;&lt;br /&gt;        &amp;lt;tt:loop ref="DETAIL"&gt;&lt;br /&gt;          &amp;lt;row&gt;&lt;br /&gt;            &amp;lt;ABSENDER&gt;&lt;br /&gt;            &amp;lt;tt:value ref="$header.ABSENDER"/&gt;&lt;br /&gt;            &amp;lt;/ABSENDER&gt;&lt;br /&gt;            &amp;lt;MELDUNGS_ID&gt;&lt;br /&gt;            &amp;lt;tt:value ref="$header.MELDUNGS_ID"/&gt;&lt;br /&gt;            &amp;lt;/MELDUNGS_ID&gt;&lt;br /&gt;            (... weitere Kopffelder ...)&lt;br /&gt;            &amp;lt;FELD_1 &gt;&lt;br /&gt;              &amp;lt;tt:value ref="FELD_1 "/&gt;&lt;br /&gt;            &amp;lt;/FELD_1 &gt;&lt;br /&gt;            (... weitere Detailfelder ...)&lt;br /&gt;          &amp;lt;/row&gt;&lt;br /&gt;        &amp;lt;/tt:loop&gt;&lt;br /&gt;      &amp;lt;/tt:loop&gt;&lt;br /&gt;    &amp;lt;/ns:MT_MVN_DESADV2500&gt;&lt;br /&gt;  &amp;lt;/tt:template&gt;&lt;br /&gt;&amp;lt;/tt:transform&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Was heisst hier "automatisch generiert"? Ein weiterer starker Vorteil der Simple Transformations (ebenso wie der XSLT-Transformationen) ist, dass es ein einfach zu bedienendes API zum Erstellen einer Transformation im Repository gibt - die Klasse &lt;tt&gt;CL_O2_API_XSLTDESC&lt;/tt&gt;. Dies kam uns in unserer Situation sehr entgegen: Dinge wie der Namensraum des Zieldokuments und die tatsächlich zu extrahierenden Felder variieren nämlich je nach Meldungstyp. Wir haben daher eine simple Customizingtabelle vorgesehen, in der wir pro Meldungstyp diese Unterschiede festlegen. Ein Report &lt;tt&gt;Z_REGENERATE_ST&lt;/tt&gt; baut aus dieser Customizingtabelle alle vorgesehenen ST-Transformationen auf (wobei er sich selbst einer XSLT-Transformation bedient). Diese werden dann dynamisch aufgerufen. Dieses Vorgehen hat den Vorteil, dass die Transformationen möglichst einfach und sehr schnell werden. Ausserdem hat eine im Repository gepflegte Transformation den Vorteil, dass sie bei Gebrauch im Hauptspeicher des Servers gepuffert wird - was ihre Ausführung noch mehr beschleunigt.   &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Zwar gibt es neben dem Disassembler &lt;tt&gt;javap&lt;/tt&gt; hervorragende Java-Decompiler mit begeisternden Benutzerschnittstellen, wie z.B. &lt;a href="http://java.decompiler.free.fr/"&gt;jd-gui&lt;/a&gt;. Es ist aber umständlich, ein Programm erst decompilieren zu müssen, um seine Logik zu verstehen und ggf. zu modifizieren. Bei der in der Branche zunehmenden Kleingeistigkeit ist ausserdem vermehrt mit dem Einsatz von Bytecode-Verdunklern (Obfuskatoren) zu rechnen, die die Lesbarkeit des decompilierten Codes weiter erschweren.&lt;br /&gt; &lt;br /&gt;[2] Das geschieht mittels eines &lt;i&gt;internen&lt;/i&gt; HTTP-Requests - d.h. der Baustein &lt;tt&gt;HTTP_DISPATCH_REQUEST&lt;/tt&gt; erscheint im eigenen Callstack, der Request wird nicht in einem separaten Task bearbeitet. Insbesondere ist die Verarbeitung der Requests somit notwendigerweise synchron: Wenn der Job durchgelaufen ist, haben sämtliche Meldungen den Plain Adapter passiert.&lt;br /&gt;&lt;br /&gt;[3] Wir haben hier eine Loop über eine Tabelle mit N Eintragen, die eine Loop über eine zweite Tabelle mit N*M Einträgen enthält. Das könnte ein Problem ergeben. Damit hier keine quadratischen Effekte auftreten, darf die Positionstabelle keine Standardtabelle sein. In unserem Fall sind beide Tabellen sortiert. Die innere Loop mit Where-Bedingung nutzt implizit die Sortierordnung gemäss Tabellenschlüssel aus, so dass kein Performanceproblem entsteht. Eine - noch etwas effizientere - Alternative wäre gewesen, mit der &lt;tt&gt;loop at it_detail&lt;/tt&gt; zu beginnen (ohne Where-Bedingung) und mittels &lt;tt&gt;at new&lt;/tt&gt; die Kopfdaten beim Wechsel zu einer neuen Meldung nachzulesen. Aber, wie man an den Laufzeiten sieht, ist der "Marshalling Prepare" Schritt auch in dieser Form unkritisch.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-9133785167328672275?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/9133785167328672275/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=9133785167328672275' title='2 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/9133785167328672275'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/9133785167328672275'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2010/10/eine-erfolgsgeschichte-mit-simple.html' title='Eine Erfolgsgeschichte mit Simple Transformations'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_JitR0EqIJ_A/TMkcW4idW_I/AAAAAAAAAI4/oyxWbKdWe8Y/s72-c/st_stat.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-7127847274061751684</id><published>2010-10-09T23:06:00.006+01:00</published><updated>2010-11-24T11:03:02.554+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Tabellenpflege als Webanwendung</title><content type='html'>Die Situation ist elementar und kommt häufig vor: Irgendwelche tabellenförmigen Daten &amp;ndash; zum Beispiel die Buchungen eines Kontos &amp;ndash; sollen mit Hilfe eine Webanwendung gepflegt werden. Trotzdem habe ich nach einer ersten Sichtung keine passende Software für diesen allereinfachsten Anwendungsfall gefunden.[1] &lt;br /&gt;&lt;br /&gt;Hier zeige ich eine Implementierung für die Pflegeoberfläche mit JavaScript, die die gesamte Änderungsverwaltung auf dem Client übernimmt. Die Applikation besteht aus einer einzigen Webseite, die weder verlassen noch neu geladen wird. Sie kommuniziert intern über Ajax-Requests mit dem Server, der die Daten anliefert und Änderungen fortschreibt.&lt;br /&gt;&lt;br /&gt;Im einzelnen muss eine Anwendung zur Tabellenpflege &lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;die Tabellendaten vom Server lesen,&lt;br /&gt;&lt;li&gt;dem Benutzer zur Ansicht und Pflege anbieten,&lt;br /&gt;&lt;li&gt;der Zellen verändern, ganze Zeilen löschen oder neue Zeilen hinzufügen kann&lt;br /&gt;&lt;li&gt;und schliesslich diese Änderungen &lt;i&gt;sichern&lt;/i&gt; kann,&lt;br /&gt;&lt;li&gt;wodurch sie auf den Server zurückgeschrieben werden.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Wie ist eine solche Anwendung zu entwerfen? Sicher einmal benötigt man ein physisches Bild der Tabelle, das irgendwo im Filesystem hinterlegt sein muss. Auf der anderen Seite bedarf es einer Präsentationsschicht (GUI), um die Tabelle dem Benutzer anzuzeigen und ihm Änderungen anzubieten. Für Webanwendungen besteht die Präsentationsschicht in der Regel aus HTML-Seiten. &lt;br /&gt;&lt;br /&gt;Zwischen diesen beiden Endpunkten &amp;ndash; GUI und Filesystem &amp;ndash; liegen im wesentlichen zwei Softwarekomponenten, die bei einer solchen Anwendung zusammenarbeiten: Eine Persistenzkomponente, die dafür zuständig ist, dass die Daten über die Sitzung des Benutzers hinaus existieren. Und eine Änderungsverwaltung, die die Änderungen des Benutzers protokolliert und zu einem vom Benutzer gewählten Zeitpunkt an die Persistenzkomponente übergibt.&lt;br /&gt;&lt;br /&gt;An den Jargon der Datenbankprogrammierer angelehnt, liegt die Tabelle in der Anwendung in zwei Formen vor: Als &lt;i&gt;Before-Image&lt;/i&gt; repräsentiert sie den Stand der Daten, bevor der Benutzer sie zu Gesicht bekommt und manipulieren kann (mit dem &lt;i&gt;Image&lt;/i&gt; ist das Pflegebild gemeint, auf dem der Benutzer die Daten ändern kann). Das &lt;i&gt;After-Image&lt;/i&gt; modelliert folglich die Änderungen, die der Benutzer an den Daten vorgenommen hat. Aus dem Vergleich des &lt;i&gt;Before-Image&lt;/i&gt; mit dem &lt;i&gt;After-Image&lt;/i&gt; ist eine Reihe von &lt;tt&gt;insert&lt;/tt&gt;-, &lt;tt&gt;update&lt;/tt&gt;- und &lt;tt&gt;delete&lt;/tt&gt;-Operationen ableitbar, die an der Tabelle vorgenommen werden müssen.&lt;br /&gt;&lt;br /&gt;Wenn die Tabelle vom Server kommt, stellt sie das Before-Image dar. Diese Daten muss der Client in einer Form präsentieren, die dem Benutzer leicht Änderungen ermöglicht. Neben der Präsentation verwaltet der Client auch die vom Benutzer getätigten Änderungen. Der Client weiss jederzeit, ob und welche Änderungen vorgenommen wurden. Nur wenn wirklich Änderungen vorgenommen wurden, braucht man dem Benutzer einen &lt;i&gt;Sichern&lt;/i&gt;-Button anzubieten, mit dem er die gerade getätigten Änderungen "committen", das heisst in der Persistenzschicht fortschreiben kann.&lt;br /&gt;&lt;br /&gt;Hier meine Beispiel-Implementierung [2]:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.ruediger-plantiko.net/konto/"&gt;http://www.ruediger-plantiko.net/konto&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Die Anwendung besteht aus einer einzigen HTML-Seite. Clientseitige Logik ist mit JavaScript abgebildet. Die Kommunikation mit dem Server erfolgt über Ajax, wobei als Struktur für den Datenaustausch in beide Richtungen das JavaScript-Datenformat JSON verwendet wird. Die HTML-Seite wird mittels JavaScript dynamisch manipuliert. Auf Serverseite nimmt ein Perl-Programm unter CGI die Anfrage entgegen, wobei ein Query-Parameter names &lt;tt&gt;action&lt;/tt&gt; dem Server mitteilt, was er machen soll. Beispielsweise teilt die URL&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;&lt;span class="sh_url"&gt;/cgi-bin/konto.pl?action=get_all&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;dem Programm &lt;tt&gt;konto.pl&lt;/tt&gt; mit, dass es alle Buchungszeilen aus der Kontodatei einlesen und dem Client im JSON-Format senden soll. Dagegen teilt eine zweite &lt;tt&gt;action&lt;/tt&gt; namens &lt;tt&gt;save&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;&lt;span class="sh_url"&gt;/cgi-bin/konto.pl?action=save&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;dem Server mit, dass sich im Bauch des HTTP-Requests ein JSON-Hash mit Zeilendaten befindet, die in der Kontodatei aktualisiert werden sollen. Er verbucht diese Änderungen und sendet dann dem Client wie bei &lt;tt&gt;get_all&lt;/tt&gt; das aktualisierte Bild der Datei (wieder im JSON-Format). Nur diese beiden &lt;tt&gt;action&lt;/tt&gt;s werden übrigens benötigt.&lt;br /&gt;&lt;br /&gt;Die beiden Schichten kommunizieren nur über diese Schnittstelle und sind ansonsten völlig unabhängig voneinander. Das heisst beispielsweise: Statt mit Perl unter CGI könnte man den Request auch mit einer beliebigen anderen Technologie behandeln. In meiner Implementierung ist der Schnitt zwischen den Systemen zugleich auch der Schnitt zwischen den beiden erwähnten Softwarekomponenten: Der Server verwaltet die Persistenzschicht, während die Verwaltung der Änderungen auf dem Client erfolgt. &lt;br /&gt;&lt;br /&gt;Zu den Besonderheiten dieser Implementierung gehört, dass die Daten auf dem Client nur in der dynamischen Tabelle &lt;tt&gt;buchungen&lt;/tt&gt; gehalten werden - das ist genau die Tabelle, die der Benutzer sieht. Es gibt kein Extrabild dieser Daten, etwa in Form eines globalen Arrays. Ähnlich ist es mit der Änderungsverwaltung: Es gibt kein globales Flag &lt;tt&gt;dataLoss&lt;/tt&gt;. Wenn der Benutzer Änderungen vornimmt, werden diese für ihn sichtbar in der Tabelle als geändert markiert. Es gibt kein globales Flag, wohl aber eine Funktion &lt;tt&gt;dataLoss()&lt;/tt&gt;, die einfach schaut, ob Zellen als geändert markiert sind.&lt;br /&gt;&lt;br /&gt;Aber fangen wir mit dem Datenformat an. Ich habe mir der Einfachheit ein CSV-artiges Format ausgedacht. Man kann Kommentarzeilen und Leerzeilen verwenden. Diese bleiben bei Updates erhalten und werden im übrigen ignoriert. Kommentarzeilen beginnen wie in Perl mit dem Doppelkreuz (&lt;tt&gt;#&lt;/tt&gt;). Ausserdem soll es neben den Buchungszeilen noch andere Zeilen in der Datei geben, die manuell oder von einem anderen Programm eingefügt werden könnten. Beispielsweise könnte ein Cron-Job regelmässig den Kontostand abfragen und als realen Saldo in die Datei einfügen. Unser Programm soll all diese Zeilen reproduzieren, aber für die eigene Verarbeitung ignorieren. &lt;br /&gt;&lt;br /&gt;Das lässt sich so einrichten, dass die erste Spalte im CSV-Format eine ID des Satzes darstellt. Diese ID kann für die allein zu berücksichtigenden Buchungszeilen das Präfix &lt;tt&gt;buch&lt;/tt&gt; haben, während andere Zeilen andere Präfixe haben. Saldozeilen zum Beispiel könnten mit dem Präfix &lt;tt&gt;saldo&lt;/tt&gt; eingeleitete ID's haben. Die Datei kann also etwa folgendermassen aussehen:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_sourceCode"&gt;&lt;span class="sh_comment"&gt;# Abhebungen&lt;/span&gt;&lt;br /&gt;buch1;1.09.2010;150.00;Norbert;Kursgebühr "Modernes JavaScript"&lt;br /&gt;buch2;10.9.2010;605.00;Petra;Flug nach Florenz, Mietauto, Übernachtung &lt;br /&gt;buch3;23.9.2010;200.00;Norbert;Spende an Wikipedia&lt;br /&gt;saldo1;30.9.2010;15362.00&lt;br /&gt;buch4;3.10.2010;350.00;Petra;Neues Smartphone&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Wenn die Tabellenpflege nach Anmeldung im Web aufgerufen wird, erhält die zuständige Perl-Klasse &lt;a href="http://github.com/rplantiko/konto/blob/master/cgi/CsvTableMaintainer.pm"&gt;CsvTableMaintainer&lt;/a&gt; den Befehl zum Laden aller Buchungszeilen (&lt;tt&gt;action=get_all&lt;/tt&gt;). Die obige Beispieldatei wird dabei in folgendes JSON-Objekt transformiert:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;{ buchungen:[&lt;br /&gt;    ["buch1","1.09.2010","150.00","Norbert","Kursgebühr \"Modernes JavaScript\""],&lt;br /&gt;    ["buch2","10.9.2010","605.00","Petra","Flug nach Florenz, Mietauto, Übernachtung "],&lt;br /&gt;    ["buch3","23.9.2010","200.00","Norbert","Spende an Wikipedia"],&lt;br /&gt;    ["buch4","3.10.2010","350.00","Petra","Neues Smartphone"]&lt;br /&gt;    ], &lt;br /&gt;    user:"petra", &lt;br /&gt;    msg:"" &lt;br /&gt;    }&lt;/pre&gt;&lt;br /&gt;  &lt;br /&gt;Dieser Hash wird nun im Browser von der JavaScript-Funktion &lt;tt&gt;updatePage()&lt;/tt&gt; entgegengenommen. Diese überträgt zunächst den User, unter dem die Anmeldung erfolgte, in ein dafür vorgesehenes Feld (wenn es nicht angezeigt werden soll, kann man dieses Feld auf unsichtbar setzen). Für jedes weitere Element des Hashs wird eine Funktion &lt;tt&gt;&amp;lt;key&gt;_update(&amp;lt;value&gt;)&lt;/tt&gt; aufgerufen, wobei &lt;tt&gt;&amp;lt;key&gt;&lt;/tt&gt; den Schlüssel und &lt;tt&gt;&amp;lt;value&gt;&lt;/tt&gt; den Wert zu diesem Eintrag bedeutet &amp;mdash; falls eine Funktion dieses Namens existiert. Wenn nicht, wird &lt;tt&gt;&amp;lt;key&gt;&lt;/tt&gt; einfach als ID eines Elements im HTML-DOM betrachtet, dessen Inhalt durch &lt;tt&gt;&amp;lt;value&gt;&lt;/tt&gt; zu ersetzen ist. Schliesslich wird noch die Sanduhr-Graphik auf unsichtbar geschaltet, die dem Benutzer die Server-Aktivität anzeigte: &lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// --- Nach Rückkehr eines Ajax-Requests: Seitenteile aktualisieren&lt;br /&gt;function updatePage(transport) { &lt;br /&gt;// Parameter transport ist das Ajax-Objekt (letztlich XMLHttpRequest)&lt;br /&gt;  var id, newCode;&lt;br /&gt;  newCode = transport.responseText.evalJSON();&lt;br /&gt;  &lt;br /&gt;// Den User zuerst aktualisieren  &lt;br /&gt;  if (newCode.user) {&lt;br /&gt;    user_update( newCode.user );&lt;br /&gt;    delete newCode.user;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;  for (id in newCode) {&lt;br /&gt;// Entweder mit spezieller Methode, falls implementiert ...    &lt;br /&gt;    if (typeof self[id+"_update"] == "function") {&lt;br /&gt;      self[id+"_update"](newCode[id]);&lt;br /&gt;      }&lt;br /&gt;// ... oder einfach durch Austausch des HTML-Contents       &lt;br /&gt;    else {&lt;br /&gt;      $(id).update( newCode[id] );&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;// Ladezustand zurücksetzen    &lt;br /&gt;  $("loading").hide();&lt;br /&gt;  }&lt;/pre&gt;  &lt;br /&gt;  &lt;br /&gt;Für den Schlüssel &lt;tt&gt;buchungen&lt;/tt&gt; gibt es eine designierte Funktion &lt;tt&gt;buchungen_update()&lt;/tt&gt;, die demnach mit dem Array der Buchungszeilen aufgerufen wird. Sie bekommt einen Array of Arrays übergeben (AoA), kann also auf jede Zelle jeder Zeile zugreifen. Sie durchläuft die anzuzeigenden Zellen in einer geschachtelten &lt;tt&gt;each()&lt;/tt&gt;-Schleife und baut dabei die einzelnen &lt;tt&gt;&amp;lt;tr&gt;&lt;/tt&gt;- und &lt;tt&gt;&amp;lt;td&gt;&lt;/tt&gt;-Elemente der HTML-Tabelle auf. Die vom Anmeldeuser getätigten Buchungen bekommen darüberhinaus noch eine weitere Zelle, die &lt;tt&gt;controlCell&lt;/tt&gt;, mit Ikonen zum Ändern und Löschen von Zeilen. Diese müssen dann noch alle klicksensitiv gemacht werden, indem der &lt;tt&gt;doOnClick()&lt;/tt&gt; für das Event &lt;tt&gt;click&lt;/tt&gt; registriert wird.  Schliesslich wird der Button zum Sichern verborgen: Denn immer wenn diese Funktion durchlaufen wird, enthält die Tabelle den reinen Datenbankstand. Ein Sichern ist also unnötig:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// --- Vom Server als Array of Array (AoA) gesendete Buchungszeilen &lt;br /&gt;//     ins HTML übernehmen&lt;br /&gt;function buchungen_update( rows ) {&lt;br /&gt;  &lt;br /&gt;  var tbody = $("buchungen").down("tbody");&lt;br /&gt;  var user = $("user").innerHTML;&lt;br /&gt;  &lt;br /&gt;  tbody.update("");&lt;br /&gt;  &lt;br /&gt;  var controlCellCode = controlCell(); &lt;br /&gt;  &lt;br /&gt;  rows.each( function(cells) {&lt;br /&gt;    var rowid = cells.shift();&lt;br /&gt;    var row = new Element( "tr", {id:rowid} );    &lt;br /&gt;    cells.each( function(cellData, index) {    &lt;br /&gt;      row.appendChild( &lt;br /&gt;        new Element( "td", &lt;br /&gt;          {className:"c"+(index+1)})&lt;br /&gt;          .update(cellData) );&lt;br /&gt;      });&lt;br /&gt;    row.appendChild( &lt;br /&gt;      new Element( "td", &lt;br /&gt;        {className:"c5"} ).update(&lt;br /&gt;      (cells[2] == user) ? controlCellCode : "" ) );&lt;br /&gt;    tbody.appendChild(row);  &lt;br /&gt;    });&lt;br /&gt;    &lt;br /&gt;  $("buchungen").show();  &lt;br /&gt;&lt;br /&gt;// Alle Bilder in der Buchungstabelle sind clicksensitiv  &lt;br /&gt;  $$("#buchungen img").each( function( img ) {&lt;br /&gt;    img.observe("click", doOnClick );    &lt;br /&gt;    });&lt;br /&gt;    &lt;br /&gt;// Datenbankstand - Sichern ist unnötig    &lt;br /&gt;  $("save").hide();  &lt;br /&gt;  &lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Wenn der Benutzer nun "Ändern" oder "Neuer Eintrag" klickt, öffnet sich ein Formularbereich zum Pflegen einer einzelnen Tabellenzeile. Dieses Formular dient wirklich nur zum Editieren, es wird niemals verschickt. Zum Abschliessen drückt der Benutzer im Formularbereich auf den Button "Übernehmen". Dann schliesst sich das Formular, und die geänderten oder neuen Zellinhalte werden in der Tabelle eingetragen und erhalten eine markierung in Form der CSS-Klasse &lt;tt&gt;changed&lt;/tt&gt;. Diese Markierung zeigt dem User ebenso wie dem Programm, dass diese Felder sich vom Datenbankstand unterscheiden. &lt;br /&gt;&lt;br /&gt;Da die Funktion &lt;tt&gt;dataLoss()&lt;/tt&gt; nun anspricht, wird auch der Button zum Sichern angeboten. Hierfür ist die Funktion &lt;tt&gt;checkDataLoss()&lt;/tt&gt; zuständig, die nach allen Operationen, die potentiell zu Datenänderungen führen, aufgerufen wird. Diese ruft ihrerseits die schon erwähnte Funktion &lt;tt&gt;dataLoss()&lt;/tt&gt; auf, um den Änderungsstatus durch Inspektion der Tabelle zu ermitteln. Das kann man mit dem Prototype-Framework sehr kompakt formulieren. Die vier Terme der Funktion lesen sich so: Schaue nach, ob es irgendeine echte Datenzeile der Buchungstabelle gibt, die entweder auf Zeilenebene die CSS-Klasse &lt;tt&gt;deleted&lt;/tt&gt; besitzt oder irgendeine Zelle mit der CSS-Klasse &lt;tt&gt;changed&lt;/tt&gt; enthält. Da die verwendeten Funktionen &lt;tt&gt;any()&lt;/tt&gt; und &lt;tt&gt;down()&lt;/tt&gt; kurzschliessen, d.h. die Iteration nach dem ersten Fund abbrechen, ist die Implementierung der Methode &lt;tt&gt;dataLoss()&lt;/tt&gt; nicht nur kurz, sondern auch effizient:&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;// --- Save-Button nur anbieten, wenn sich Daten geändert haben&lt;br /&gt;function checkDataLoss() {&lt;br /&gt;  $("save").style.display = dataLoss() ? "inline" : "none";&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;// --- Feststellen, ob Daten geändert wurden  &lt;br /&gt;function dataLoss() {&lt;br /&gt;  return $("buchungen").down("tbody").&lt;br /&gt;    select("tr").any( function(row) {&lt;br /&gt;      return row.hasClassName("deleted") ||&lt;br /&gt;             row.down("td[class~=changed]");  &lt;br /&gt;      });&lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Irgendwann drückt der Benutzer "Sichern". Dann werden die Änderungen in der Funktion &lt;tt&gt;extractChanges()&lt;/tt&gt; ermittelt und in Form eines JSON-Hashs an den Server übergeben. Neu angelegte Zeilen (die dadurch zu erkennen sind, dass ihre (vorläufige) ID mit dem Präfix &lt;tt&gt;new&lt;/tt&gt; beginnt) werden hierbei gleich behandelt wie geänderte Zeilen: Die Zeilendaten werden als Datenteil zur ID in den Hash eingefügt. Für gelöschte Zeilen das spezielle Schlüsselwort &lt;tt&gt;deleted&lt;/tt&gt; als Datenteil übergeben. Wenn die Benutzerin Petra in obigem Beispiel die Zeile &lt;tt&gt;buch2&lt;/tt&gt; löscht, den Betrag der Zeile &lt;tt&gt;buch4&lt;/tt&gt; von 350.00 CHF auf 400.00 CHF ändert und eine neue Zeile einfügt, um ihre Hannoversche Hotelrechnung zu erfassen, sieht der an den Server übergebene Hash folgendermassen aus:&lt;br /&gt;&lt;pre class="sh_javascript"&gt;{&lt;br /&gt;  buch2:"deleted",&lt;br /&gt;  buch4:"3.10.2010;400.00;Petra;Neues Smartphone",&lt;br /&gt;  new1:"9.10.2010;100.00;Übernachtung Hannover"&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Der Server erhält diesen JSON-Hash im Body des HTTP-Requests, zusammen mit dem Wert &lt;tt&gt;save&lt;/tt&gt; für den URL-Parameter &lt;tt&gt;action&lt;/tt&gt;. Er wertet den Hash aus und übersetzt ihn in Anweisungen zur Änderung des Datenfiles. Auch wird für neu angelegte Zeilen eine endgültige ID vergeben. Schliesslich gibt er &amp;mdash; wie das Kommando &lt;tt&gt;get_all&lt;/tt&gt; &amp;mdash; den aktuellen Stand der Buchungszeilen an den Client zurück.[3]&lt;br /&gt;&lt;br /&gt;Der Einstiegspunkt für alle internen, von der Webseite mittels Ajax abgesetzten CGI-Anfragen ist das Perl-Programm &lt;tt&gt;konto.pl&lt;/tt&gt;. Es wertet den URL-Parameter &lt;tt&gt;action&lt;/tt&gt; aus. Wie macht es das? Der CGI-Mechanismus ist sehr einfach: Der Query-Teil einer URL wird dem aufgerufenen Programm in Form einer Umgebungsvariablen zur Verfügung gestellt. Der Body des Requests kann aus der Standardeingabe eingelesen werden, und alle Ausgaben an die Standardausgabe bilden den Body der HTTP-Antwort. Das Programm &lt;tt&gt;konto.pl&lt;/tt&gt; extrahiert daher den Wert von &lt;tt&gt;action&lt;/tt&gt; aus dem Query-String der URL und ruft dann dynamisch das Unterprogramm dieses Namens auf. Das geht so:&lt;br /&gt;&lt;pre class="sh_perl"&gt;#!W:/perl/bin/perl.exe&lt;br /&gt;&lt;br /&gt;# CGI Perl-Requesthandler, der mit der Kontopflegeseite kommuniziert&lt;br /&gt;&lt;br /&gt;use strict;&lt;br /&gt;use warnings;&lt;br /&gt;no strict 'refs';&lt;br /&gt;&lt;br /&gt;use CsvTableMaintainer;  # Tabellenpflegeklasse&lt;br /&gt;use MiniJSON;            # Benötigte Perl-JSON-Konvertierungen&lt;br /&gt;&lt;br /&gt;# URL-Querystring&lt;br /&gt;my $queryString = $ENV{"QUERY_STRING"} &lt;br /&gt;   || "action=get_all";  # Für standalone Testausführungen   &lt;br /&gt;my ($action) = ($queryString =~ /action=(\w+)/);&lt;br /&gt;&lt;br /&gt;# Instanz der Tabellenpflegeklasse bilden&lt;br /&gt;my $tableMaintainer = CsvTableMaintainer::new( file=&gt;"konto.dat" );&lt;br /&gt;&lt;br /&gt;# Erste Zeile der HTTP-Antwort&lt;br /&gt;print "Content-Type:text/plain\n\n";&lt;br /&gt;&lt;br /&gt;# Im Queryparameter übergebene Aktion ausführen&lt;br /&gt;print &amp;$action() if $action;&lt;br /&gt;&lt;br /&gt;sub get_all {&lt;br /&gt;...&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;sub save {&lt;br /&gt;...&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;&lt;br /&gt;Wie man sieht, ist &lt;tt&gt;konto.pl&lt;/tt&gt; nur der Dispatcher, der die Anfragen entgegennimmt und die entsprechenden Subroutinen aufruft. &lt;tt&gt;konto.pl&lt;/tt&gt; ist schmal und hat nur wenige Subroutinen &amp;mdash; normalerweise nur die den &lt;tt&gt;action&lt;/tt&gt;s entsprechenden Unterprogramme. Bemerkenswert ist, dass für einfache Aufgaben wie diese auch kein &lt;tt&gt;use CGI::&amp;lt;irgendwas&gt;&lt;/tt&gt; notwendig ist: Query-Parameter auslesen und auf Payloads der ein- und ausgehenden HTTP-Nachrichten zuzugreifen, ist unter CGI so einfach, dass keine weiteren Hilfspakete dafür nötig sind. &lt;br /&gt;&lt;br /&gt;&lt;tt&gt;konto.pl&lt;/tt&gt; ist natürlich auch deshalb so schmal, weil es die eigentlichen Aufgaben an die Klasse &lt;tt&gt;CsvTableMaintainer&lt;/tt&gt; und zu einem kleineren Teil an das Paket &lt;tt&gt;MiniJSON&lt;/tt&gt; delegiert. Diese Programmteile befinden sich - wie auch alle anderen, für dieses Beispiel benötigten Programmteile - in meinem &lt;tt&gt;github&lt;/tt&gt;-Reposoritory &lt;a href="http://github.com/rplantiko/konto/"&gt;konto&lt;/a&gt;. Wer sich die Beispielapplikation daher noch genauer ansehen will, sei auf dieses Repository verwiesen.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Vielleicht habe ich nicht gründlich genug gesucht oder war mit den gefundenen Objekten nicht zufrieden.  &lt;br /&gt;[2] Neben dem JavaScript-Framework Prototype von Sam Stephenso, das mich dabei unterstützt, lesbares JavaScript zu schreiben, verwende ich den &lt;i&gt;Datepicker&lt;/i&gt; von Hugo Ortega Fernandez. Und - ja, ich mag die SAP-Ikonen!&lt;br /&gt;[3] Um Codeduplizierung zu vermeiden, ruft er dafür nach Ausführung des Sicherns schlicht die Aktion &lt;tt&gt;get_all&lt;/tt&gt; auf.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-7127847274061751684?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/7127847274061751684/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=7127847274061751684' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/7127847274061751684'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/7127847274061751684'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2010/10/tabellenpflege-als-webanwendung.html' title='Tabellenpflege als Webanwendung'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-7909692796705700939</id><published>2010-09-30T20:26:00.017+01:00</published><updated>2010-10-12T20:16:07.104+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programmierkunst'/><title type='text'>Das schriftliche Wurzelziehen</title><content type='html'>Vor ein paar Tagen brachte mein Sohn interessante Rechenaufgaben aus der Schule mit: Der Mathematiklehrer hatte den Kindern das gute alte &lt;em&gt;schriftliche Wurzelziehen&lt;/em&gt; beigebracht &amp;mdash; ein Verfahren, bei dem die erste binomische Formel ausgenutzt wird, um sukzessive die Stellen der Wurzel zu ermitteln.  Das Verfahren ist heute fast völlig in Vergessenheit geraten, wird jedoch an den Waldorfschulen noch in Ehren gehalten &amp;mdash; ihrem ausgeprägten Konservatismus sei Dank.&lt;br /&gt;&lt;br /&gt;Für mich war es eine nette Erfahrung, das Verfahren in Form von JavaScript-Code nachzubilden &amp;mdash; und zwar so, dass es zum Üben benutzt werden kann. Statt die gesamte Rechnung auf einen Schlag anzuzeigen, sollten auf Knopfdruck die einzelnen Schritte ausgeführt werden. So kann man auf dem Papier das Verfahren selbst probieren und sich bei jedem Schritt vergewissern, keinen Fehler gemacht zu haben. &lt;br /&gt;&lt;br /&gt;Das Programm soll die Rechnung, wie sie auf dem Papier entsteht, erzeugen, die Schritte sollen mit Kommentaren versehen werden, und in einem eigenen Bereich sollen kleine Nebenrechnungen ausgeführt werden &amp;mdash; analog dem Schmierpapier, das man bei der schriftlichen Rechnung braucht. &lt;br /&gt;&lt;br /&gt;Ich setzte mir ein Zeitlimit von drei Stunden, das ich gerade so einhalten konnte &amp;mdash; in nostalgischer Erinnerung an die guten alten Zeiten, als ich mich noch in Prüfungen bewähren musste: "Sie haben drei Stunden &amp;mdash; die Zeit läuft." Allerdings habe ich später noch eine weitere Stunde für ein paar Refaktorisierungen aufgewendet. Die Webseite&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ruediger-plantiko.net/wurzeln/"&gt;http://ruediger-plantiko.net/wurzeln/&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;zeigt das Ergebnis. Es folgen ein paar Anmerkungen zum Programmentwurf. &lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Die HTML-Seite zeigt nichts anderes als das Seitenlayout. Sie ist vollständig von JavaScript freigehalten. Auch Clickbehandler, &lt;tt&gt;onload&lt;/tt&gt;-Behandler usw. sind in die Scriptdatei ausgelagert. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Um lesbares und dennoch kurzes JavaScript zu produzieren, verwende ich das Framework &lt;a href="http://prototypejs.org"&gt;Prototype&lt;/a&gt;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Der Unterbruch zwischen den einzelnen Rechenschritten erfordert es leider, dass Zwischenergebnisse in globalen Variablen mitgeführt werden müssen. Wenigstens werden diese globalen Variablen alle an einem Ort deklariert und an einem zweiten Ort, in einer &lt;tt&gt;reset()&lt;/tt&gt;-Funktion wieder gelöscht.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Der Algorithmus besteht aus insgesamt neun Schritten. Die ersten drei Schritte werden nur einmal ausgeführt. Die nachfolgenden sechs Schritte werden so lange ausgeführt, bis keine weiteren Ziffern vom Radikanden mehr heruntergeholt werden können. Dann ist das Verfahren beendet. &lt;br /&gt;&lt;br /&gt;Mein Entwurf packt diese neun Schritte als &lt;i&gt;anonyme Funktionen&lt;/i&gt; in einen Array &lt;tt&gt;execute[]&lt;/tt&gt;, so dass sie dynamisch über einen Index angesprochen und ausgeführt werden können. Die Methode &lt;tt&gt;executeNextStep()&lt;/tt&gt;, die bei Click auf den &lt;i&gt;Weiter&lt;/i&gt;-Button aufgerufen wird, kann die Schleife dann ohne umständliche &lt;tt&gt;switch...case&lt;/tt&gt; Konstrukte realisieren, indem der Schrittzähler &lt;tt&gt;step&lt;/tt&gt; kontinuierlich erhöht wird, der Divisionsrest modulo 6 aber als Index für den Zugriff auf den &lt;tt&gt;execute&lt;/tt&gt;-Array fungiert.[1]&lt;br /&gt;&lt;br /&gt;Um aus der Schleife auszubrechen, empfiehlt sich eine Exception - im simpelsten Fall, wie hier, einfach in Form eines Stringobjekts, das dann angezeigt wird.&lt;br /&gt;&lt;br /&gt;&lt;pre class="sh_javascript"&gt;function executeNextStep() {&lt;br /&gt;&lt;br /&gt;  try {&lt;br /&gt;&lt;br /&gt;// Schrittzähler erhöhen&lt;br /&gt;    incrementStepCounter( );&lt;br /&gt;&lt;br /&gt;// Nächsten Schritt ausführen&lt;br /&gt;    if (step &amp;lt;= 2)&lt;br /&gt;// Schritte 0 bis 2 nur einmal&lt;br /&gt;      execute[step]();&lt;br /&gt;    else&lt;br /&gt;// Ab Schritt 3 wird es periodisch&lt;br /&gt;      execute[3+((step-3)%6)]();&lt;br /&gt;&lt;br /&gt;    } catch (e) {&lt;br /&gt;&lt;br /&gt;// Ausnahme (z.B. STOP) melden und Verfahren beenden&lt;br /&gt;      commentOhneSchritt( "&amp;lt;b&gt;" + e + "&amp;lt;/b&gt;");&lt;br /&gt;      verfahrenBeenden();&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;// Hier folgen die Schritte:&lt;br /&gt;execute = [&lt;br /&gt;  function() {&lt;br /&gt;    comment("Zahl nach links in Zweiergruppen aufteilen");  &lt;br /&gt;    var r = radikand+"",i;&lt;br /&gt;    for (i=r.length-2;i&gt;=-1;i-=2) {&lt;br /&gt;      if (i&gt;=0) &lt;br /&gt;        groups.unshift( r.substring(i,i+2) );&lt;br /&gt;      else &lt;br /&gt;        groups.unshift( r.substring(0,1) ); &lt;br /&gt;      }    &lt;br /&gt;    s = "";&lt;br /&gt;    groups.each( function(g) {&lt;br /&gt;      s+='&amp;lt;span class="group"&gt;' + g + '&amp;lt;/span&gt;';&lt;br /&gt;      }); &lt;br /&gt;    write(s);&lt;br /&gt;    },  &lt;br /&gt;  function() {&lt;br /&gt;    ...&lt;br /&gt;    },&lt;br /&gt;  ... &lt;br /&gt;];&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Diese Art, das Problem zu lösen, lässt sich sicher auch in anderen Kontexten einsetzen: Nämlich immer dann, wenn ein Algorithmus stückweise auszuführen ist, etwa wie hier in einer Schleife mit Abbruchbedingung. Der Zugriff auf Funktionen als Arrayelemente ist für eine &lt;i&gt;Abfolge von Schritten&lt;/i&gt; naheliegender als der Zugriff über den Funktionsnamen, etwa &lt;tt&gt;self["schritt2"]()&lt;/tt&gt;, der ja alternativ auch möglich wäre. Die Lösung ist aber vor allem kompakter als eine &lt;tt&gt;switch...case...&lt;/tt&gt;-Konstruktion, ohne an Lesbarkeit einzubüssen. Im Gegenteil, gerade die mühsam abzuhandelnden &lt;tt&gt;case's&lt;/tt&gt;, die in C-artigen Sprachen (wie JavaScript) auch noch jeden &lt;tt&gt;case&lt;/tt&gt; mit einem &lt;tt&gt;break&lt;/tt&gt; beenden müssen, erschweren nach meiner Ansicht die Lesbarkeit von Code.&lt;br /&gt; &lt;br /&gt;Im Rückblick fand ich es unbefriedigend, dass ich für mein geradezu spartanisches UI-Design relativ viel Zeit aufwenden musste. Für die eigentlichen Logik des Wurzelziehens brauchte ich nur knapp die Hälfte der Zeit, die ich mir vorgegeben hatte. Zu erwarten wäre eigentlich, dass man für die &lt;i&gt;Problem Domain&lt;/i&gt; die meiste Zeit aufwenden muss, nicht für das &lt;i&gt;User Interface&lt;/i&gt;. Das hat bei mir sicher auch damit zu tun, dass ich nicht wie ein hauptberuflicher Webdesigner ständig neue HTML/CSS-Oberflächen kreiere, sondern an einmal geschaffenen Entwürfen sehr lange und sehr genügsam festhalte, denn auch beruflich programmiere ich hauptsächlich Geschäftslogik und modifziere meist nur bestehende Entwürfe von Benutzeroberflächen. Insgesamt muss ich jedoch meine Kritik aus dem &lt;a href="http://ruediger-plantiko.blogspot.com/2009/08/das-bose-tabellenlayout.html"&gt;bösen Tabellenlayout&lt;/a&gt; wiederholen, dass ich CSS in der jetzigen Form für die Gestaltung der Präsentationsschicht einfach noch nicht griffig genug finde.&lt;br /&gt;&lt;br /&gt;[1] Auf den Namen &lt;tt&gt;execute&lt;/tt&gt; für den Funktions-Array bin ich übrigens erst nach einigem Versuchen gekommen: Ausdrücke wie &lt;tt&gt;execute[step]()&lt;/tt&gt; gewinnen durch ihn an Lesbarkeit.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7017123333978978050-7909692796705700939?l=ruediger-plantiko.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ruediger-plantiko.blogspot.com/feeds/7909692796705700939/comments/default' title='Kommentare zum Post'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7017123333978978050&amp;postID=7909692796705700939' title='0 Kommentare'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/7909692796705700939'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7017123333978978050/posts/default/7909692796705700939'/><link rel='alternate' type='text/html' href='http://ruediger-plantiko.blogspot.com/2010/09/das-schriftliche-wurzelziehen.html' title='Das schriftliche Wurzelziehen'/><author><name>Rüdiger Plantiko</name><uri>http://www.blogger.com/profile/02393666282077884370</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://www.ruediger-plantiko.net/plantiko.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7017123333978978050.post-9214017344512984333</id><published>2010-09-17T15:35:00.041+01:00</published><updated>2010-11-08T1
