Model-to-Code Teil 3

XML-Parser: Ein praxisnahes Beispiel für Model-to-Code

In diesem Abschnitt stelle ich ein praxisnahes Beispiel vor, das die Möglichkeiten von Model-to-Code zeigt. Der Übersichtlichkeit halber habe ich die Aufgabe allerdings stark vereinfacht und vom Umfang verringert.

So wurden im Projekt, das die Vorlage bildet, ungefähr 90 Klassen generiert.

Voraussetzungen

Sie sollten das Tutorial (Teil 2 der Serie) zum Einrichten von Eclipse Epsilon durchgearbeitet haben.
Da der Code umfangreicher ist, zeige ich ihn teilweise nur in Ausschnitten.

Da ich die Eclipse-Projekte in github abgelegt habe, sollten Sie in der Lage sein, mit git ein "clone" auszuführen.
Den Zustand der Projekte zu den jeweiligen Kapiteln und Abschnitten finden Sie in Unterverzeichnissen. So entspricht "Flexml.Generator.Ch3.2" dem Zustand in Kapitel 3, Abschnitt 2.

Aufgabenstellung

Es soll ein "fehlertoleranter" XML-Parser erstellt werden. Für die XML-Dateien existiert eine XSD,  allerdings verletzen viele Dokumente die XSD. Der "fehlertolerante" Parser korrigiert diese Fehler und liefert "korrekte" Dokumente zurück.
In dieser vereinfachten Version korrigiert der Parser fehlerhafte Tags, z.B. "tr" statt "th" für eine Tabellenzeile im Element "header". Die folgende Abbildung stellt die erwartete Struktur der Abweichung gegenüber.

Erwartete Struktur Abweichung

<table>

<header>

<th>

<td></td>

</th>

</header>

</table>

<table>

<header>

<tr>

<td></td>

</tr>

</header>

</table>

Getrennte Projekte

Der Parser soll keine Abhängigkeiten zu Epsilon oder EMF haben. Daher arbeite ich mit zwei Projekten.
Die Code-Generierung wird mittels Model-to-Code im Generator-Projekt "Flexml.Generator" durchgeführt (1a).
Der generierte Code wird in das Parser-Projekt "Flexml.Parser" kopiert (2a), (2b).
Die folgende Grafik zeigt das Zusammenspiel der Projekte.

 

3.0 Die Projekte aus git importieren

Starten Sie den Import

File → Import → Projects from git (with smart import) → Clone URI

Geben Sie die URI des git Repositories ein

Geben Sie folgende URI ein: https://github.com/iks-gmbh-playground/ModelToCode.git und klicken Sie auf "Next".

Wählen Sie den Branch aus

Klicken Sie auf "Next".

Legen Sie Ihr lokales Verzeichnis fest

Wählen Sie Ihr lokales Verzeichnis aus und klicken Sie auf Next.

Wählen Sie die Projekte aus

Nachdem Eclipse den Import durchgeführt hat, zeigt es Ihnen die folgenden Projekte an:

Einige Projekte sind mit einem weißen Kreuz auf rotem Grund markiert.
Wenn Sie die Anleitungen in den folgenden Kapiteln durchgearbeitet haben, sollten alle fehlenden Dateien erstellt worden sein. Und es sollten alle Projekte fehlerfrei sein.

3.1 Das Metamodell und Modell

Wir bilden die XML-Datei als eine Baumstruktur ab. Als Besonderheit geben wir mit der Multiplizität vor, wie viele Kind-Elemente eines Typs ein Element haben kann.
Die folgende Grafik beschreibt, dass ein Element "File" null oder ein Element "Table" als Kind haben kann.

Der vollständige EMF-Sourcecode des Metamodells:

@namespace(
    uri="http://flexible.xml/version1",
    prefix="f")
package flexml;
enum Multiplicity {
    ZeroOrOne;
    One;
    ZeroOrMany;
    OneOrMany;
}
class NamedObject {
    attr String name;
    attr String desc;
}
class Attribute extends NamedObject {
}
class Element extends NamedObject {
    attr String [*] aliasNames;  // Aliases for this element
    val Attribute[*] attributes;
    val Child[*] children;
    attr Boolean hasText; // Does this element contain text?
}
class Child {
    attr Multiplicity multiplicity; // Multiplicity of this child type
    ref Element child;
}
class Definition {
    val Element root; // The root element of the XML
    val Element[+] elements; // The set of elements. There has to be at least one: the root
}

Auf Basis dieser Definition habe ich das eigentliche Modell angelegt.
Im Editor wird die Datei model/ FlexibleXmlParser.model wie folgt angezeigt:

Die Anzeige des Modells könnte allerdings aussagekräftiger sein. Besonders bei umfangreicheren und komplexeren Modellen ist das hilfreich.
Dieses Ziel können Sie mit ein paar einfachen Annotationen im Metamodell für den Editor Exeed erreichen.
Dabei wird mittels "label=" das Label zu einem Element, und mit "icon=" das angezeigte Icon geändert.
Die folgende Abbildung zeigt die notwendigen Änderungen:

@exeed(label="
var str : String;
str = '<' + self.name + '>';
// concat aliases, if set
if (not self.aliasName.isEmpty()) {
 str = str + ' (' + self.aliasName.concat + ')';    
}
return  str;", classIcon="class")
class Element extends NamedObject {
    attr String [*] aliasNames;  // Aliases for this element
    val Attribute[*] attributes;
    val Child[*] children;
    attr Boolean hasText; // Does this element contain text?
}
@exeed(label="return '<'+self.child.name+'>' + ' (' +self.multiplicity + ')';", classIcon="link")
class Child {
    attr Multiplicity multiplicity; // Multiplicity of this child type
    ref Element child;
}
@exeed(classIcon="model")
class Definition {
    val Element root; // The root element of the XML
    val Element[+] elements; // The set of elements. There has to be at least one: the root
}

Wenn Sie jetzt das Modell mittels "Open with Exeed Editor" öffnen, sehen Sie folgende Verbesserungen:

  • Der Name der Elemente wird grundsätzlich in spitzen Klammern angezeigt.
  • Zu einem "Child" erhalten Sie nicht nur die Multiplizität, sondern auch das referenzierte Element angezeigt
  • Wenn für ein "Element" Aliase definiert sind, werden sie in Klammern angezeigt, siehe "<th>"

Das Metamodell und das Modell finden Sie im Eclipse-Projekt „Flexml.Generator.Ch3.1".

3.2 Entwurf der Anwendung und der Templates

Mein Vorgehen bei Model-to-Code

Ich entwerfe zunächst eine grobe Anwendungsstruktur (1) und schreibe in (2) zwei Bestandteile:

  1. Den "Rumpf", d.h. so viele Klassen, wie ich brauche, um das Zusammenspiel der generierten Klassen auszuprobieren.
  2. Den "Aufrufer", der die Schnittstelle zu den generierten Klassen darstellt.

Dann erzeuge ich aus dem "Rumpf" die Templates (3) und starte die Korrekturschleife.
Ich lasse aus den Templates den Code generieren (4) und teste den generierten Code(5).
Falls die Tests nicht erfolgreich waren, überarbeite ich Modell und/oder Templates (6) und starte einen neuen Schleifendurchlauf.
Dieser Ablauf sieht schematisch so aus:

Schritt 1: Die Anwendungsstruktur entwerfen

Mein Entwurf für die Anwendung ist wie folgt:
Das oberste Element im XML, File, wird von FileParser (2) eingelesen, in einer Instanz von FileElement (3) gespeichert und zurück geliefert.
Die direkten Kinder von File, in diesem Fall Table, werden von TableParser (4) eingelesen und in Instanzen von TableElement (5) gespeichert.
Dieses Vorgehen setzt sich dann für die untergeordneten Elemente immer weiter fort.
Die oberste Ebene, FlexmlParser (1), dient als Adapter zwischen der Anwendung und dem generierten Code. Da er Aufgaben ausführt, die über die generierten Parser-Klassen hinausgehen, habe ich ihn von Hand geschrieben.
Mit den drei Ebenen FlexmlParser, FileParser und TableParser habe ich sowohl die Struktur als auch die Schnittstellen und die Implementation der einzelnen Klassen so weit geklärt, dass ich in der Lage war, die Templates zu erstellen.

Aus diesem Entwurf ergibt sich die Code-Erzeugung: Aus dem Modell werden für jedes XML-Element zwei Klassen erstellt: Eine Klasse für die Speicherung des Elements und eine Parser-Klasse, um das Element aus dem XML zu lesen.

Die generierten Dateien in das Zielprojekt kopieren

Aus dem Generator-Projekt werden durch build.xml zwei Arten von Code in das Parser-Projekt kopiert.

  1. Generierter Code wird nach src-gen/main/java kopiert (2a). Dies sind z.B. die Klassen FileElement und FileParser.
  2. Handgeschriebener Code wird nach src/main/java kopiert (2b). Dies ist die Klasse FlexmlParser.

Arbeiten mit den Projekten

Das Generator-Projekt finden Sie im Eclipse-Projekt "Flexml.Generator.Ch3.2".
Das Parser-Projekt finden Sie im Eclipse-Projekt "Flexml.Parser.Ch3".

Im Generator-Projekt:

  • Erstellen Sie die ecore-Datei und registrieren Sie die EPackages für FlexibleXmlParser.emf. Dies ist in Abschnitt 2 des Tutorials beschrieben.
  • Führen Sie build.xml aus. Verwenden Sie beim ersten Mal den Punkt "Run as Ant Build..." und wählen Sie unter dem Reiter "JRE" den Punkt "Run in the same JRE as the workspace" aus.

Führen Sie einen Refresh auf dem Generator-Projekt und dem Parser-Projekt aus. Die Fehler müssten verschwunden sein.


Jetzt teilen: