From models to text
A model can serve as one source for several outputs. The alarm’s component names and connections can become a parts list, an HTML report, a Graphviz diagram description or Java source. Model-to-text generation derives those outputs from the model so that a change can be reflected by regenerating. It does not make the output automatically correct: a generator must escape data, use valid identifiers and run only on models whose required properties have been checked.
Epsilon uses EGL (Epsilon Generation Language) for templates and EGX to coordinate template runs and target files. Both use the EOL navigation you learned in Module 4. In this module, the captured output is the generated text. For EGX, the local runner also names each generated file. Start with the small alarm; then try changing the model and rerunning a template.
Write an EGL template
An EGL template mixes text with dynamic regions. Text outside [% ... %] is copied unchanged. A block [% ... %] runs EOL statements, while [%= expression %] inserts an expression’s value. In the first example, a loop visits each component and writes one line with its port count.
Architecture: [%=Architecture.all.first().name%]
[% for (c in Component.all) { %]
- [%=c.name%]: ports=[%=c.ports.size()%]
[% } %]
More files: alarm.flexmi, components.emf
<?nsuri components?>
<architecture name="Alarm">
<component name="OrGate">
<inPort name="door" type="DIGITAL"/>
<inPort name="window" type="DIGITAL"/>
<outPort name="open" type="DIGITAL"/>
</component>
<component name="AndGate">
<inPort name="open" type="DIGITAL"/>
<inPort name="armed" type="DIGITAL"/>
<outPort name="sound" type="DIGITAL"/>
</component>
<component name="Siren">
<inPort name="sound" type="DIGITAL"/>
</component>
<connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
<connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
@namespace(uri="components", prefix="components")
package components;
enum PortType {
DIGITAL;
ANALOG;
}
class Architecture {
attr String[1] name;
val Component[*]#architecture components;
val Connector[*] connectors;
}
class Component {
attr String[1] name;
ref Architecture#components architecture;
val Port[*] ports;
}
abstract class Port {
attr String[1] name;
attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}
class Connector {
ref OutPort source;
ref InPort target;
}
Architecture: Alarm
- OrGate: ports=3
- AndGate: ports=3
- Siren: ports=1
The template has a fixed heading and a dynamic list. Architecture.all.first().name supplies the architecture name. If you add a component to the Flexmi model, another line appears without editing the template. The generated file is a derived artefact: keep the model and template as the sources of truth, then regenerate the artefact after changes.
Static template text supplies the output’s shape. Dynamic EOL expressions supply values taken from the model. Both parts are needed for a readable generated document.
Generate several formats
EGL is not limited to one target language. This template uses HTML tags as its static text and inserts component names and counts into table cells.
<h2>[%=Architecture.all.first().name%]</h2>
<table>
<tr><th>Component</th><th>Inputs</th><th>Outputs</th></tr>
[% for (c in Component.all) { %]
<tr><td>[%=c.name%]</td><td>[%=c.ports.select(p | p.isTypeOf(InPort)).size()%]</td><td>[%=c.ports.select(p | p.isTypeOf(OutPort)).size()%]</td></tr>
[% } %]
</table>
More files: alarm.flexmi, components.emf
<?nsuri components?>
<architecture name="Alarm">
<component name="OrGate">
<inPort name="door" type="DIGITAL"/>
<inPort name="window" type="DIGITAL"/>
<outPort name="open" type="DIGITAL"/>
</component>
<component name="AndGate">
<inPort name="open" type="DIGITAL"/>
<inPort name="armed" type="DIGITAL"/>
<outPort name="sound" type="DIGITAL"/>
</component>
<component name="Siren">
<inPort name="sound" type="DIGITAL"/>
</component>
<connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
<connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
@namespace(uri="components", prefix="components")
package components;
enum PortType {
DIGITAL;
ANALOG;
}
class Architecture {
attr String[1] name;
val Component[*]#architecture components;
val Connector[*] connectors;
}
class Component {
attr String[1] name;
ref Architecture#components architecture;
val Port[*] ports;
}
abstract class Port {
attr String[1] name;
attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}
class Connector {
ref OutPort source;
ref InPort target;
}
<h2>Alarm</h2>
<table>
<tr><th>Component</th><th>Inputs</th><th>Outputs</th></tr>
<tr><td>OrGate</td><td>2</td><td>1</td></tr>
<tr><td>AndGate</td><td>2</td><td>1</td></tr>
<tr><td>Siren</td><td>1</td><td>0</td></tr>
</table>
The Playground may render the HTML; the captured block shows the exact generated markup. In a real generator, escape names before inserting them into HTML. The teaching model uses simple names, so the example focuses on the EGL structure rather than a complete HTML escaping library.
The next template produces Graphviz DOT, a textual graph description. Each component becomes a node and each connector becomes an arrow between the components that own its endpoint ports.
digraph Alarm {
[% for (c in Component.all) { %]
"[%=c.name%]";
[% } %]
[% for (wire in Connector.all) { %]
"[%=wire.source.eContainer().name%]" -> "[%=wire.target.eContainer().name%]";
[% } %]
}
More files: alarm.flexmi, components.emf
<?nsuri components?>
<architecture name="Alarm">
<component name="OrGate">
<inPort name="door" type="DIGITAL"/>
<inPort name="window" type="DIGITAL"/>
<outPort name="open" type="DIGITAL"/>
</component>
<component name="AndGate">
<inPort name="open" type="DIGITAL"/>
<inPort name="armed" type="DIGITAL"/>
<outPort name="sound" type="DIGITAL"/>
</component>
<component name="Siren">
<inPort name="sound" type="DIGITAL"/>
</component>
<connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
<connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
@namespace(uri="components", prefix="components")
package components;
enum PortType {
DIGITAL;
ANALOG;
}
class Architecture {
attr String[1] name;
val Component[*]#architecture components;
val Connector[*] connectors;
}
class Component {
attr String[1] name;
ref Architecture#components architecture;
val Port[*] ports;
}
abstract class Port {
attr String[1] name;
attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}
class Connector {
ref OutPort source;
ref InPort target;
}
digraph Alarm {
"OrGate";
"AndGate";
"Siren";
"OrGate" -> "AndGate";
"AndGate" -> "Siren";
}
DOT is another concrete output, not another model in the MDE sense used here. It omits port details: both the alarm model and an altered one with different port names could generate the same component-level graph. Decide deliberately which information the target artefact needs. A generated diagram can be helpful for review, but a text preview also makes the exact edge data easy to inspect.
One file per element with EGX
A single EGL template naturally makes one stream of text. EGX runs templates for selected model elements and chooses where each result goes. The rule below runs once per Component, passes that component as c to template.egl, and names the output file from c.name.
rule Component2Java
transform c : Component {
template: "template.egl"
target: c.name + ".java"
}
public final class [%=c.name%] {
public static final int INPUTS = [%=c.ports.select(p | p.isTypeOf(InPort)).size()%];
public static final int OUTPUTS = [%=c.ports.select(p | p.isTypeOf(OutPort)).size()%];
}
More files: alarm.flexmi, components.emf
<?nsuri components?>
<architecture name="Alarm">
<component name="OrGate">
<inPort name="door" type="DIGITAL"/>
<inPort name="window" type="DIGITAL"/>
<outPort name="open" type="DIGITAL"/>
</component>
<component name="AndGate">
<inPort name="open" type="DIGITAL"/>
<inPort name="armed" type="DIGITAL"/>
<outPort name="sound" type="DIGITAL"/>
</component>
<component name="Siren">
<inPort name="sound" type="DIGITAL"/>
</component>
<connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
<connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
@namespace(uri="components", prefix="components")
package components;
enum PortType {
DIGITAL;
ANALOG;
}
class Architecture {
attr String[1] name;
val Component[*]#architecture components;
val Connector[*] connectors;
}
class Component {
attr String[1] name;
ref Architecture#components architecture;
val Port[*] ports;
}
abstract class Port {
attr String[1] name;
attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}
class Connector {
ref OutPort source;
ref InPort target;
}
--- AndGate.java ---
public final class AndGate {
public static final int INPUTS = 2;
public static final int OUTPUTS = 1;
}
--- OrGate.java ---
public final class OrGate {
public static final int INPUTS = 2;
public static final int OUTPUTS = 1;
}
--- Siren.java ---
public final class Siren {
public static final int INPUTS = 1;
public static final int OUTPUTS = 0;
}
The captured output has three file headers and three Java classes. template identifies the EGL template; target computes a file name. The template uses EOL’s select to count input and output ports. For a production generator, validate that every component name is a safe, unique Java identifier and escape any text inserted into generated source. Here the fixed example names meet those conditions.
EGX rules can also have guards, parameters and additional workflow controls. The EGX reference describes them. Keep decisions about which files to produce in EGX and decisions about what each file contains in EGL; that makes each part easier to change.
Protected regions
Regeneration would normally overwrite hand edits in a generated file. EGL can mark a protected region so that a file-generating workflow may preserve a marked section from an existing destination. The example emits a region named manual-note using out.preserve.
public final class AlarmNotes {
[%=out.preserve("/*", "*/", "manual-note", true, " // Add project-specific notes here")%]
}
More files: alarm.flexmi, components.emf
<?nsuri components?>
<architecture name="Alarm">
<component name="OrGate">
<inPort name="door" type="DIGITAL"/>
<inPort name="window" type="DIGITAL"/>
<outPort name="open" type="DIGITAL"/>
</component>
<component name="AndGate">
<inPort name="open" type="DIGITAL"/>
<inPort name="armed" type="DIGITAL"/>
<outPort name="sound" type="DIGITAL"/>
</component>
<component name="Siren">
<inPort name="sound" type="DIGITAL"/>
</component>
<connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
<connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
@namespace(uri="components", prefix="components")
package components;
enum PortType {
DIGITAL;
ANALOG;
}
class Architecture {
attr String[1] name;
val Component[*]#architecture components;
val Connector[*] connectors;
}
class Component {
attr String[1] name;
ref Architecture#components architecture;
val Port[*] ports;
}
abstract class Port {
attr String[1] name;
attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}
class Connector {
ref OutPort source;
ref InPort target;
}
public final class AlarmNotes {
/* protected region manual-note on begin */
// Add project-specific notes here
/* protected region manual-note end */
}
The captured output demonstrates the markers and initial content. It does not demonstrate a merge with an existing file: this lesson’s local runner processes the template without an earlier destination to merge, and the Playground is not an Eclipse file-generation workflow. To test preservation in a local project, generate to a file, edit only the marked region, then regenerate to the same destination with merging enabled. Keep generated and hand-written responsibilities clear; protected regions are not a substitute for reviewing changes to the model or template. The EGL merge documentation explains the marker and merge behaviour.
Review generated artefacts
Before using generated text, ask what it assumes. The Java generator assumes names make valid class identifiers. The HTML generator assumes names can be inserted safely into markup. The Graphviz generator ignores ports and signal types. None of the templates alone verifies that a connector is sound; Module 5’s EVL rules should check that first when those assumptions affect the output.
A useful generation workflow has three steps: validate the model, generate deterministically, then inspect or test the artefacts. The examples here make the inputs and outputs small enough to compare by eye. In a real project, include generated files in a build and test the code or documents in their target environment. Module 9 shows how to run Epsilon outside the Playground.
Exercises
Static or dynamic? In an HTML table template, classify <tr><td>, [%=c.name%] and [% for (c in Component.all) { %] as static text, value insertion or executable template code.
Show solution
<tr><td> is static text copied to the output. [%=c.name%] inserts a value from the current component. The for block is executable EOL code controlling how often subsequent text is emitted.
What is lost? The Graphviz example creates one node per component and one edge per connector. Name two facts in the source model that the generated graph does not record.
Show solution
It omits individual port names and their DIGITAL/ANALOG types. It also does not say which exact ports a connector joins. The generator intentionally selects a component-level view.
Who owns each decision? In the Java example, which file decides the output file name, and which file decides the class body? What should be checked before using the generated classes?
Show solution
The EGX rule computes c.name + ".java"; template.egl writes the class body. Validate unique, legal class names and the model rules the generated code assumes. Compile or otherwise test the produced Java in a real workflow.
Compact HTML. Make an EGL template that produces an unordered list with one item per component and its port count.
Show solution
The loop surrounds one static <li> line, while expressions insert its values:
<ul>
[% for (c in Component.all) { %]
<li>[%=c.name%] (ports: [%=c.ports.size()%])</li>
[% } %]
</ul>
More files: alarm.flexmi, components.emf
<?nsuri components?>
<architecture name="Alarm">
<component name="OrGate">
<inPort name="door" type="DIGITAL"/>
<inPort name="window" type="DIGITAL"/>
<outPort name="open" type="DIGITAL"/>
</component>
<component name="AndGate">
<inPort name="open" type="DIGITAL"/>
<inPort name="armed" type="DIGITAL"/>
<outPort name="sound" type="DIGITAL"/>
</component>
<component name="Siren">
<inPort name="sound" type="DIGITAL"/>
</component>
<connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
<connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
@namespace(uri="components", prefix="components")
package components;
enum PortType {
DIGITAL;
ANALOG;
}
class Architecture {
attr String[1] name;
val Component[*]#architecture components;
val Connector[*] connectors;
}
class Component {
attr String[1] name;
ref Architecture#components architecture;
val Port[*] ports;
}
abstract class Port {
attr String[1] name;
attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}
class Connector {
ref OutPort source;
ref InPort target;
}
<ul>
<li>OrGate (ports: 3)</li>
<li>AndGate (ports: 3)</li>
<li>Siren (ports: 1)</li>
</ul>
One summary per component. Write an EGX rule that invokes template.egl once for each component and writes <name>.txt. Each file should report that component’s port count.
Show solution
The rule chooses targets; the template produces the contents. The local output shows three separate files:
rule Component2Summary
transform c : Component {
template: "template.egl"
target: c.name + ".txt"
}
[%=c.name%]: ports=[%=c.ports.size()%].
More files: alarm.flexmi, components.emf
<?nsuri components?>
<architecture name="Alarm">
<component name="OrGate">
<inPort name="door" type="DIGITAL"/>
<inPort name="window" type="DIGITAL"/>
<outPort name="open" type="DIGITAL"/>
</component>
<component name="AndGate">
<inPort name="open" type="DIGITAL"/>
<inPort name="armed" type="DIGITAL"/>
<outPort name="sound" type="DIGITAL"/>
</component>
<component name="Siren">
<inPort name="sound" type="DIGITAL"/>
</component>
<connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
<connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
@namespace(uri="components", prefix="components")
package components;
enum PortType {
DIGITAL;
ANALOG;
}
class Architecture {
attr String[1] name;
val Component[*]#architecture components;
val Connector[*] connectors;
}
class Component {
attr String[1] name;
ref Architecture#components architecture;
val Port[*] ports;
}
abstract class Port {
attr String[1] name;
attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}
class Connector {
ref OutPort source;
ref InPort target;
}
--- AndGate.txt ---
AndGate: ports=3.
--- OrGate.txt ---
OrGate: ports=3.
--- Siren.txt ---
Siren: ports=1.
Label the edges. Modify the DOT template so each connector arrow has a label naming its source and target ports.
Show solution
The added label uses wire.source.name and wire.target.name:
digraph Alarm {
[% for (wire in Connector.all) { %]
"[%=wire.source.eContainer().name%]" -> "[%=wire.target.eContainer().name%]" [label="[%=wire.source.name%] to [%=wire.target.name%]"];
[% } %]
}
More files: alarm.flexmi, components.emf
<?nsuri components?>
<architecture name="Alarm">
<component name="OrGate">
<inPort name="door" type="DIGITAL"/>
<inPort name="window" type="DIGITAL"/>
<outPort name="open" type="DIGITAL"/>
</component>
<component name="AndGate">
<inPort name="open" type="DIGITAL"/>
<inPort name="armed" type="DIGITAL"/>
<outPort name="sound" type="DIGITAL"/>
</component>
<component name="Siren">
<inPort name="sound" type="DIGITAL"/>
</component>
<connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
<connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
@namespace(uri="components", prefix="components")
package components;
enum PortType {
DIGITAL;
ANALOG;
}
class Architecture {
attr String[1] name;
val Component[*]#architecture components;
val Connector[*] connectors;
}
class Component {
attr String[1] name;
ref Architecture#components architecture;
val Port[*] ports;
}
abstract class Port {
attr String[1] name;
attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}
class Connector {
ref OutPort source;
ref InPort target;
}
digraph Alarm {
"OrGate" -> "AndGate" [label="open to open"];
"AndGate" -> "Siren" [label="sound to sound"];
}
Self-check quiz
[%=c.name%] do?Further reading
- The EGL reference covers templates and protected regions.
- The EGX reference covers rule-based template orchestration.
- The Epsilon code-generation tutorial develops a larger example.