blob: dfbb6b38cf3c319648ad9019df944e474cd2debe [file] [log] [blame]
<!DOCTYPE html>
<meta charset="UTF-8">
<title>Xtext - Template Language</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description"
content="The website of Eclipse Xtext, an open-source framework for development of programming languages and domain-specific languages">
<meta name="author" content="Sven Efftinge">
<meta name="author" content="Miro Spoenemann">
<!-- styles -->
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src=""></script>
<!-- Le fav and touch icons -->
<link rel="shortcut icon" href="/Xtext/images/favicon.png">
<link href="/Xtext/css/bootstrap.css" rel="stylesheet" type='text/css'>
<link href="/Xtext/css/bootstrap-responsive.css" rel="stylesheet" type='text/css'>
<link href="/Xtext/css/shield-responsive.css" rel="stylesheet" type='text/css'>
<link href='/Xtext/css/fonts.css' rel='stylesheet' type='text/css'>
<link href="/Xtext/css/prettyPhoto.css" rel="stylesheet" media="screen" type='text/css'>
<link href="/Xtext/css/prettify.css" type="text/css" rel="stylesheet"/>
<link href="/Xtext/css/style.css" rel="stylesheet" type='text/css'>
<!-- cover flow -->
<link href="/Xtext/css/coverflow.css" rel="stylesheet" type='text/css'>
<!--[if lt IE 9]>
<link href="/css/iebugs.css" rel="stylesheet" type='text/css'>
<!-- Google Tag Manager -->
<noscript><iframe src="//"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
<!-- End Google Tag Manager -->
<header class="site-header">
<!-- Navbar -->
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse"
data-target=".nav-collapse"> <span class="icon-bar"></span> <span
class="icon-bar"></span> <span class="icon-bar"></span>
</a> <a class="brand" href="/Xtext/index.html"></a>
<div class="nav-collapse collapse" style="height: 0px;">
<ul class="nav">
<!--li ><a href="/Xtext/news.html">News</a></li-->
<li ><a href="/Xtext/download.html">Download</a></li>
<li ><a href="/Xtext/documentation/index.html">Documentation</a></li>
<li ><a href="/Xtext/community.html">Community</a></li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Support &amp; Trainings<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="" target="_blank">itemis</a></li>
<li><a href="" target="_blank">TypeFox</a></li>
<li ><a href="">Xtend</a></li>
<!--div class="nav pull-right">
<li ><a><iframe src="" frameborder="0" scrolling="0" width="170px" height="20px"></iframe></a></li>
<!--/.nav-collapse -->
<!-- Navbar End -->
<div class="page-content">
function startSearch(event) {
if (event.keyCode == 13) {
var q = '' +;'' + q, "_self");
<div class="wrapper">
<div id="page">
<div class="inner">
<div id="maincontainer" class="container">
<span class="edit-on-github pull-right">
<a href="">Edit on Github</a>
<div class="span3" style="margin-left: 0px;">
<div class="search-bar">
<img src="/Xtext/images/search-gray.png"/>
<input type="search" id="google-search" onkeyup="startSearch(event);"/>
<ul id="nav-outline" style="margin-left: 0px;">
<li class="nav-part">Getting Started</li>
<li><a href="102_domainmodelwalkthrough.html">15 Minutes Tutorial</a></li>
<li><a href="103_domainmodelnextsteps.html">15 Minutes Tutorial - Extended</a></li>
<li><a href="104_jvmdomainmodel.html">Five simple steps to your JVM language</a></li>
<!--li class="nav-part">Seven JVM Languages Built With Xbase</li>
<li><a href="201_sevenlang_introduction.html">Introduction</a></li>
<li><a href="202_scripting.html">Scripting Language</a></li>
<li><a href="203_builddsl.html">Build Language</a></li>
<li><a href="204_mongodb.html">DSL for MongoDB</a></li>
<li><a href="205_guice.html">DSL for Guice</a></li>
<li><a href="206_httprouting.html">Http Routing Language</a></li>
<li><a href="207_template.html">Template Language</a></li>
<li><a href="208_tortoise.html">Little Tortoise</a></li-->
<li class="nav-part">Reference Documentation</li>
<li><a href="301_grammarlanguage.html">The Grammar Language</a></li>
<li><a href="302_configuration.html">Configuration</a></li>
<li><a href="303_runtime_concepts.html">Language Implementation</a></li>
<li><a href="305_xbase.html">Integration with Java</a></li>
<!--li><a href="306_mwe2.html">MWE2</a></li-->
<!--li><a href="307_special_languages.html">Typical Language Configurations</a></li-->
<li><a href="308_emf_integration.html">Integration with EMF</a></li>
<li><a href="310_eclipse_support.html">Eclipse Support</a></li>
<!--li><a href="320_idea_support.html">IntelliJ IDEA Support</a></li-->
<li><a href="330_web_support.html">Web Editor Support</a></li>
<li><a href="350_continuous_integration.html">Continuous Integration</a></li>
<!--li class="nav-part">Appendix</li>
<li><a href="401_migrating_from_1_0_x.html">Migrating from Xtext 1.0.x to 2.0</a></li>
<li><a href="402_migrating_from_0_7.html">Migrating from Xtext 0.7.x to 1.0</a></li-->
<div class="span8 doc-contents">
<h1 id="template">Template Language</h1>
<p>This is a little template language specialized in generating HTML documents.</p>
<p><img src="images/template_screenshot.png" alt="" /></p>
<p>The language allows web designers to do their job and lets developers put in the dynamic parts. The syntax and terminals are chosen to be readable and allow rendering the templates in the browser as well as in any HTML 5 compatible WYSIWYG editors. Still, when opened in the DSL editor you get the fully featured, statically typed Eclipse editor.</p>
<h2 id="templates-solution">Overview</h2>
<p>A template language works in two modes: Plain text mode, where everything goes directly into the output and the expression mode, where expressions have to be evaluated and the result is inserted into the text. To switch between text mode and expression mode, we use the French quotes <code>«</code> and <code>»</code>. A document starts in text mode.</p>
<p>The template will be compiled to a Java class with a <code>generate(params)</code> method. You can provide additional information like a package declaration, imports and parameters in the preamble inside a template at the beginning of the document.</p>
<p>We provide additional <code>FOR-ENDFOR</code> and <code>IF-ELSE-ENDIF</code> statements to iterate / branch over fixed text blocks. To distinguish them from the Xbase expressions with the same names, they are in uppercase.</p>
<h2 id="templates-running">Running the Example</h2>
<p>In the runtime workspace, run the <em>GenerateHtml</em> file as a Java application (<em>Run as… → Java Application</em> from the context menu). This will execute the template <em>MyWebsite</em> and print the result to the console.</p>
<h2 id="templates-grammar">Grammar</h2>
<p>This is the grammar of the templates DSL:</p>
<pre><code class="language-xtext">grammar org.xtext.template.Template
with org.eclipse.xtext.xbase.annotations.XbaseWithAnnotations
generate template ""
import '' as xbase
('package' package=QualifiedName)?
'param' type=JvmTypeReference? name=ID ('=' defaultexp=XExpression)?;
RichString returns xbase::XBlockExpression:
(expressions+=RichStringPart expressions+=RichStringLiteral)*;
RichStringLiteral returns xbase::XStringLiteral:
{RichStringLiteral} value=TEXT;
RichStringPart returns xbase::XExpression:
XExpressionInsideBlock |
RichStringForLoop |
RichStringForLoop returns xbase::XForLoopExpression:
"FOR" declaredParam=JvmFormalParameter ':' forExpression=XExpression
RichStringIf returns xbase::XIfExpression:
"IF" if=XExpression
(else=RichStringElseIf | "ELSE" else=RichString)?
RichStringElseIf returns xbase::XIfExpression:
(else=RichStringElseIf | "ELSE" else=RichString)?;
terminal TEXT : '»' (!'«')* (EOF|'«');
<p>It becomes quite straightforward once you have understood the escaping. Have a look at the last rule <em>TEXT</em> first: It says that a text starts with a closing French quote and ends with an opening quote or the end of the file. By inverting opening and closing quotes we mark up text instead of expressions.</p>
<p>A <em>TemplateFile</em> starts with a comment and switches to the expression mode for the preamble part consisting of the package declaration, the imports and the parameter declaration. The body is a <em>RichString</em>, which is an alternating sequence of <em>RichStringLiterals</em> and <em>RichStringPart</em>. The <em>RichStringLiterals</em> is essentially a text block (in inverted French quotes). The <em>RichStringPart</em> is either an Xbase expression, a <em>RichStringForLoop</em> or a <em>RichStringIf</em>. The latter inherit from the Xbase expressions with the same name to reuse as much of the Xbase infrastructure as possible. The rest should be easy.</p>
<h2 id="templates-inferrer">Translation to Java</h2>
<p>Each <em>TemplateFile</em> is compiled to a Java class with a <code>generate</code> method that takes a lambda expression as a parameter. The lambda expression is called to initialize the template’s properties, by handling the template itself as an argument. This comes along nicely, especially when called from Xtend.</p>
<pre><code class="language-templates">&lt;!--«
<pre><code class="language-java">public class MyWebsite {
public String generate(final Procedure1&lt;MyWebsite&gt; init) {
if (init != null)
String result = generate().toString();
// remove leading --&gt;
result = result.replaceAll("^--&gt;\\r?\\n","");
// trim multi-newline to single newline
result = result.replaceAll("\\r?\\n\\s*\\r\\n?", System.getProperty("line.separator"));
return result;
<p>The corresponding code in the <a href="">TemplateJvmModelInferrer</a> is:</p>
<pre><code class="language-xtend">class TemplateJvmModelInferrer extends AbstractModelInferrer {
def dispatch void infer(TemplateFile element,
IJvmDeclaredTypeAcceptor acceptor,
boolean isPreIndexingPhase) {
val simpleName = element.eResource.URI.trimFileExtension.lastSegment
val qualifiedName = if(element.getPackage != null)
element.getPackage + "." + simpleName
val javaClass = element.toClass(qualifiedName)
// generate a method accepting an initializer lambda expression
members += element.toMethod("generate", typeRef(String)) [
parameters += element.toParameter(
"init", typeRef(Procedures.Procedure1, typeRef(javaClass))
body = '''
if (init != null)
String result = generate().toString();
// remove leading --&gt;
result = result.replaceAll("^--&gt;\\r?\\n","");
// trim multi-newline to single newline
result = result.replaceAll("\\r?\\n\\s*\\r?\\n",System.getProperty("line.separator"));
return result;
<p>Each <em>Parameter</em> becomes a Java property, i.e. a field with a getter and a setter.</p>
<pre><code class="language-templates">param title = "No Title"
<pre><code class="language-java">private String title = "No Title";
public void setTitle(final String title) {
this.title = title;
public String getTitle() {
return this.title;
<p>In the inferrer, note that we derive the property’s type in three steps: Take the declared one, if there is none, derive it from the initializer and if even that fails, use <a href="">String</a> as default.</p>
<pre><code class="language-xtend">for (param : element.params) {
val type = param.type
?: param.defaultexp?.inferredType
?: typeRef(String)
members += param.toField(, type) [
if (param.defaultexp != null)
initializer = param.defaultexp
members += param.toSetter(, type)
members += param.toGetter(, type)
<p>The body of the template is compiled into a big private <code>generate()</code> method. We skip the inferrer code here, as it is straightforward. But we had to extend the compiler to support rich strings and the new <code>FOR</code> loop our new control structures. This is described in the <a href="207_template.html#templates-compiler">next section</a>.</p>
<pre><code class="language-templates">»&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;meta charset="utf-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-sca..."&gt;
&lt;meta name="description"
&lt;meta name="author" content="«...
<pre><code class="language-java">private CharSequence generate() {
StringBuilder _appendable = new StringBuilder();
"--&gt;\n&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n\t&lt;meta...",
// appends galore
<h2 id="templates-compiler">Extending the Compiler</h2>
<p>We have added additional expressions to Xbase, so we have to tell the compiler how to translate them to Java. The <a href="">XbaseCompiler</a> has a method <code>doInternalToJavaStatement</code> that dispatches the compilation with regard to the type of the current expression. This is where we have to hook in with our <a href="">TemplateCompiler</a>:</p>
<pre><code class="language-xtend">class TemplateCompiler extends XbaseCompiler {
override protected doInternalToJavaStatement(XExpression expr,
ITreeAppendable it,
boolean isReferenced) {
switch expr {
RichString : {
... }
RichStringForLoop : {
... }
default :
super.doInternalToJavaStatement(expr, it, isReferenced)
<p>For a <em>RichString</em>, we declare a variable <code>_appendable</code> of type <a href="">StringBuilder</a> and append the results of all its evaluated expressions. Remember from the grammar that every second expression is a <em>RichStringPart</em> which can be <code>null</code>, so we use the ‘elvis operator’ <code>?:</code> to insert an empty string in this case.</p>
<pre><code class="language-xtend">RichString : {
val name = declareVariable(expr, '_appendable')
StringBuilder «name» = new StringBuilder();
for (nestedExpression : expr.expressions) {
nestedExpression.internalToJavaStatement(it, true)
<p>As our <em>RichStringLiteral</em> inherits from <a href="">XStringLiteral</a>, it does not need any special treatment. The same holds for <em>RichStringIf</em> and <em>RichStringElseIif</em>. The <em>RichStringForLoop</em> requires special treatment, because as opposed to the <a href="">XForLoopExpression</a> that always returns <code>null</code>, we want it to return a concatenation of its results. This looks like</p>
<pre><code class="language-xtend">RichStringForLoop : {
expr.forExpression.internalToJavaStatement(it, true)
val paramType = typeProvider.getTypeForIdentifiable(expr.declaredParam)
val name = declareVariable(expr, '_forLoopResult')
StringBuilder «name» = new StringBuilder();
for (final ''')
serialize(paramType, expr, it);
append(''' «declareVariable(expr.declaredParam,
makeJavaIdentifier(» : ''')
internalToJavaExpression(expr.forExpression, it)
append(") {").increaseIndentation
expr.eachExpression.internalToJavaStatement(it, true)
<p>The compiler now knows how to handle the new expressions in a statement context. In addition, we have to teach it to compile them in an expression context. This is what the second method does:</p>
<pre><code class="language-xtend">override protected internalToConvertedExpression(XExpression obj,
ITreeAppendable it) {
if (hasName(obj))
super.internalToConvertedExpression(obj, it)
<p>As usual, we have to bind our <a href="">TemplateCompiler</a> in the <a href="">TemplateRuntimeModule</a> in order to be picked up as the <a href="">XbaseCompiler</a> in the context of our language.</p>
<h2 id="templates-type-provider">Type Computation</h2>
<p>The type system has to know how to determine the types of our new expressions. This is the job of the <a href="">TemplateTypeComputer</a>: <em>RichString</em> becomes a <a href="">StringBuilder</a>. As opposed to its super type <a href="">XForLoopExpression</a> a <em>RichStringForLoop</em> is of type <a href="">StringBuilder</a> as well. The for-loop’s body is expected to have a type, as the results must be concatenatable, which is different from Xbase’s for-loop.</p>
<pre><code class="language-xtend">class TemplateTypeComputer extends XbaseWithAnnotationsTypeComputer {
def dispatch computeTypes(RichString expression, ITypeComputationState state) {
super._computeTypes(expression as XBlockExpression, state)
state.acceptActualType(getTypeForName(StringBuilder, state))
def dispatch computeTypes(RichStringForLoop expression, ITypeComputationState state) {
super._computeTypes(expression as XForLoopExpression, state)
state.acceptActualType(getTypeForName(StringBuilder, state))
<p>Like the compiler, we have to bind this implementation in our <a href="">runtime module</a> as well.</p>
<h2 id="templates-value-converter">Value Converter</h2>
<p>The <em>RichStringLiterals</em> still have the French quotes around their values. As we do not want to see them in the output, we have implemented the <a href="">TemplateValueConverterService</a> and bound it in the <a href="">runtime module</a>.</p>
<h2 id="templates-content-assist">Content Assist</h2>
<p>The French quotes are not easy to type on every keyboard. We have adapted content assist to insert them when the cursor is inside a TEXT terminal:</p>
<pre><code class="language-java">public class TemplateProposalProvider
extends AbstractTemplateProposalProvider {
public void complete_TEXT(EObject model,
RuleCall ruleCall,
ContentAssistContext context,
ICompletionProposalAcceptor acceptor) {
acceptor.accept(new CompletionProposal("«»",
context.getOffset(), 0, 1));
<h2 id="templates-highlighting">Syntax Highlighting</h2>
<p>Sometimes it is hard to see whether you are in text mode or in expression mode. To give the user better feedback, we have changed the way the text is highlighted. This customization consists of two parts: Add new highlighting styles in the <a href="">TemplateHighlightingConfiguration</a> and apply them to the text in the <a href="">TemplateHighlightingCalculator</a>. As this is rather extensively covered in the <a href="310_eclipse_support.html#highlighting">Xtext documentation</a>, we skip a deeper explanation here.</p>
<hr />
<p><strong><a href="208_tortoise.html">Next Chapter: Little Tortoise</a></strong></p>
<footer class="site-footer">
<div id="extra">
<div class="inner">
<div class="container">
<div class="row">
<div class="span6">
<h3 class="footer-links-header">Quick Links</h3>
<ul class="footer-links clearfix">
<li><a href="">Privacy Policy</a></li>
<li><a href="">Terms of Use</a></li>
<li><a href="">Copyright Agent</a></li>
<li><a href="">Legal</a></li>
<ul class="footer-links clearfix">
<li><a href="">Eclipse Home</a></li>
<li><a href="">Market Place</a></li>
<li><a href="">Eclipse Live</a></li>
<li><a href="">Eclipse Planet</a></li>
<div class="span6">
<h3 class="footer-links-header"><a href="" style="color: white;">@Xtext</a> on Twitter</h3>
<a class="twitter-timeline" href="" data-widget-id="346625441290928128"
data-chrome="noheader nofooter transparent"
data-theme="dark">Tweets by @xtext</a>
!function(d,s,id) {
var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
if(!d.getElementById(id)) {
<a href="#" class="scrollup fadeOutRight animated" style="display: none;">ScrollUp</a>
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="/Xtext/js/jquery-1.11.3.min.js"></script>
<script src="/Xtext/js/bootstrap.min.js"></script>
<script src="/Xtext/js/jquery.easing.1.3.js" type="text/javascript"></script>
<script src="/Xtext/js/jquery.prettyPhoto.js" type="text/javascript"></script>
<script src="/Xtext/js/twitter.js" type="text/javascript"></script>
<script src="/Xtext/js/prettify.js" type="text/javascript"></script>
<script src="/Xtext/js/lang-xtend.js" type="text/javascript"></script>
<script src="/Xtext/js/lang-common.js" type="text/javascript"></script>
<script src="/Xtext/js/custom.js" type="text/javascript"></script>
<script src="" async defer></script>
<!--script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push([ '_setAccount', 'UA-2429174-3' ]);
_gaq.push([ '_trackPageview' ]);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl'
: 'http://www')
+ '';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
<script src="/Xtext/js/coverflow.min.js" type="text/javascript"></script>
$(function() {
active : 1,
visibleAside: 2,
overlap : 0.5,
scale : 0.9,
angle : 20,
trigger : {
"itemfocus" : true,
"swipe" : true,
"mousewheel" : false
$('#coverflow :hidden').toggle();
$(window).resize(function() {