1# EscapeVelocity summary 2 3EscapeVelocity is a templating engine that can be used from Java. It is a reimplementation of a subset of 4functionality from [Apache Velocity](http://velocity.apache.org/). 5 6This is not an official Google product. 7 8For a fuller explanation of Velocity's functioning, see its 9[User Guide](http://velocity.apache.org/engine/releases/velocity-1.7/user-guide.html) 10 11If EscapeVelocity successfully produces a result from a template evaluation, that result should be 12the exact same string that Velocity produces. If not, that is a bug. 13 14EscapeVelocity has no facilities for HTML escaping and it is not appropriate for producing 15HTML output that might include portions of untrusted input. 16 17 18## Motivation 19 20Velocity has a convenient templating language. It is easy to read, and it has widespread support 21from tools such as editors and coding websites. However, *using* Velocity can prove difficult. 22Its use to generate Java code in the [AutoValue][AutoValue] annotation processor required many 23[workarounds][VelocityHacks]. The way it dynamically loads classes as part of its standard operation 24makes it hard to [shade](https://maven.apache.org/plugins/maven-shade-plugin/) it, which in the case 25of AutoValue led to interference if Velocity was used elsewhere in a project. 26 27EscapeVelocity has a simple API that does not involve any class-loading or other sources of 28problems. It and its dependencies can be shaded with no difficulty. 29 30## Loading a template 31 32The entry point for EscapeVelocity is the `Template` class. To obtain an instance, use 33`Template.from(Reader)`. If a template is stored in a file, that file conventionally has the 34suffix `.vm` (for Velocity Macros). But since the argument is a `Reader`, you can also load 35a template directly from a Java string, using `StringReader`. 36 37Here's how you might make a `Template` instance from a template file that is packaged as a resource 38in the same package as the calling class: 39 40```java 41InputStream in = getClass().getResourceAsStream("foo.vm"); 42if (in == null) { 43 throw new IllegalArgumentException("Could not find resource foo.vm"); 44} 45Reader reader = new BufferedReader(new InputStreamReader(in)); 46Template template = Template.parseFrom(reader); 47``` 48 49## Expanding a template 50 51Once you have a `Template` object, you can use it to produce a string where the variables in the 52template are given the values you provide. You can do this any number of times, specifying the 53same or different values each time. 54 55Suppose you have this template: 56 57``` 58The $language word for $original is $translated. 59``` 60 61You might write this code: 62 63```java 64Map<String, String> vars = new HashMap<>(); 65vars.put("language", "French"); 66vars.put("original", "toe"); 67vars.put("translated", "orteil"); 68String result = template.evaluate(vars); 69``` 70 71The `result` string would then be: `The French word for toe is orteil.` 72 73## Comments 74 75The characters `##` introduce a comment. Characters from `##` up to and including the following 76newline are omitted from the template. This template has comments: 77 78``` 79Line 1 ## with a comment 80Line 2 81``` 82 83It is the same as this template: 84``` 85Line 1 Line 2 86``` 87 88## References 89 90EscapeVelocity supports most of the reference types described in the 91[Velocity User Guide](http://velocity.apache.org/engine/releases/velocity-1.7/user-guide.html#References) 92 93### Variables 94 95A variable has an ASCII name that starts with a letter (a-z or A-Z) and where any other characters 96are also letters or digits or hyphens (-) or underscores (_). A variable reference can be written 97as `$foo` or as `${foo}`. The value of a variable can be of any Java type. If the value `v` of 98variable `foo` is not a String then the result of `$foo` in a template will be `String.valueOf(v)`. 99Variables must be defined before they are referenced; otherwise an `EvaluationException` will be 100thrown. 101 102Variable names are case-sensitive: `$foo` is not the same variable as `$Foo` or `$FOO`. 103 104Initially the values of variables come from the Map that is passed to `Template.evaluate`. Those 105values can be changed, and new ones defined, using the `#set` directive in the template: 106 107``` 108#set ($foo = "bar") 109``` 110 111Setting a variable affects later references to it in the template, but has no effect on the 112`Map` that was passed in or on later template evaluations. 113 114### Properties 115 116If a reference looks like `$purchase.Total` then the value of the `$purchase` variable must be a 117Java object that has a public method `getTotal()` or `gettotal()`, or a method called `isTotal()` or 118`istotal()` that returns `boolean`. The result of `$purchase.Total` is then the result of calling 119that method on the `$purchase` object. 120 121If you want to have a period (`.`) after a variable reference *without* it being a property 122reference, you can use braces like this: `${purchase}.Total`. If, after a property reference, you 123have a further period, you can put braces around the reference like this: 124`${purchase.Total}.nonProperty`. 125 126### Methods 127 128If a reference looks like `$purchase.addItem("scones", 23)` then the value of the `$purchase` 129variable must be a Java object that has a public method `addItem` with two parameters that match 130the given values. Unlike Velocity, EscapeVelocity requires that there be exactly one such method. 131It is OK if there are other `addItem` methods provided they are not compatible with the 132arguments provided. 133 134Properties are in fact a special case of methods: instead of writing `$purchase.Total` you could 135write `$purchase.getTotal()`. Braces can be used to make the method invocation explicit 136(`${purchase.getTotal()}`) or to prevent method invocation (`${purchase}.getTotal()`). 137 138### Indexing 139 140If a reference looks like `$indexme[$i]` then the value of the `$indexme` variable must be a Java 141object that has a public `get` method that takes one argument that is compatible with the index. 142For example, `$indexme` might be a `List` and `$i` might be an integer. Then the reference would 143be the result of `List.get(int)` for that list and that integer. Or, `$indexme` might be a `Map`, 144and the reference would be the result of `Map.get(Object)` for the object `$i`. In general, 145`$indexme[$i]` is equivalent to `$indexme.get($i)`. 146 147Unlike Velocity, EscapeVelocity does not allow `$indexme` to be a Java array. 148 149### Undefined references 150 151If a variable has not been given a value, either by being in the initial Map argument or by being 152set in the template, then referencing it will provoke an `EvaluationException`. There is 153a special case for `#if`: if you write `#if ($var)` then it is allowed for `$var` not to be defined, 154and it is treated as false. 155 156### Setting properties and indexes: not supported 157 158Unlke Velocity, EscapeVelocity does not allow `#set` assignments with properties or indexes: 159 160``` 161#set ($data.User = "jon") ## Allowed in Velocity but not in EscapeVelocity 162#set ($map["apple"] = "orange") ## Allowed in Velocity but not in EscapeVelocity 163``` 164 165## Expressions 166 167In certain contexts, such as the `#set` directive we have just seen or certain other directives, 168EscapeVelocity can evaluate expressions. An expression can be any of these: 169 170* A reference, of the kind we have just seen. The value is the value of the reference. 171* A string literal enclosed in double quotes, like `"this"`. A string literal must appear on 172 one line. EscapeVelocity does not support the characters `$` or `\\` in a string literal. 173* An integer literal such as `23` or `-100`. EscapeVelocity does not support floating-point 174 literals. 175* A Boolean literal, `true` or `false`. 176* Simpler expressions joined together with operators that have the same meaning as in Java: 177 `!`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `&&`, `||`, `+`, `-`, `*`, `/`, `%`. The operators have the 178 same precedence as in Java. 179* A simpler expression in parentheses, for example `(2 + 3)`. 180 181Velocity supports string literals with single quotes, like `'this`' and also references within 182strings, like `"a $reference in a string"`, but EscapeVelocity does not. 183 184## Directives 185 186A directive is introduced by a `#` character followed by a word. We have already seen the `#set` 187directive, which sets the value of a variable. The other directives are listed below. 188 189Directives can be spelled with or without braces, so `#set` or `#{set}`. 190 191### `#if`/`#elseif`/`#else` 192 193The `#if` directive selects parts of the template according as a condition is true or false. 194The simplest case looks like this: 195 196``` 197#if ($condition) yes #end 198``` 199 200This evaluates to the string ` yes ` if the variable `$condition` is defined and has a true value, 201and to the empty string otherwise. It is allowed for `$condition` not to be defined in this case, 202and then it is treated as false. 203 204The expression in `#if` (here `$condition`) is considered true if its value is not null and not 205equal to the Boolean value `false`. 206 207An `#if` directive can also have an `#else` part, for example: 208 209``` 210#if ($condition) yes #else no #end 211``` 212 213This evaluates to the string ` yes ` if the condition is true or the string ` no ` if it is not. 214 215An `#if` directive can have any number of `#elseif` parts. For example: 216 217``` 218#if ($i == 0) zero #elseif ($i == 1) one #elseif ($i == 2) two #else many #end 219``` 220 221### `#foreach` 222 223The `#foreach` directive repeats a part of the template once for each value in a list. 224 225``` 226#foreach ($product in $allProducts) 227 ${product}! 228#end 229``` 230 231This will produce one line for each value in the `$allProducts` variable. The value of 232`$allProducts` can be a Java `Iterable`, such as a `List` or `Set`; or it can be an object array; 233or it can be a Java `Map`. When it is a `Map` the `#foreach` directive loops over every *value* 234in the `Map`. 235 236If `$allProducts` is a `List` containing the strings `oranges` and `lemons` then the result of the 237`#foreach` would be this: 238 239``` 240 241 oranges! 242 243 244 lemons! 245 246``` 247 248When the `#foreach` completes, the loop variable (`$product` in the example) goes back to whatever 249value it had before, or to being undefined if it was undefined before. 250 251Within the `#foreach`, a special variable `$foreach` is defined, such that you can write 252`$foreach.hasNext`, which will be true if there are more values after this one or false if this 253is the last value. For example: 254 255``` 256#foreach ($product in $allProducts)${product}#if ($foreach.hasNext), #end#end 257``` 258 259This would produce the output `oranges, lemons` for the list above. (The example is scrunched up 260to avoid introducing extraneous spaces, as described in the [section](#spaces) on spaces 261below.) 262 263Velocity gives the `$foreach` variable other properties (`index` and `count`) but EscapeVelocity 264does not. 265 266### Macros 267 268A macro is a part of the template that can be reused in more than one place, potentially with 269different parameters each time. In the simplest case, a macro has no arguments: 270 271``` 272#macro (hello) bonjour #end 273``` 274 275Then the macro can be referenced by writing `#hello()` and the result will be the string ` bonjour ` 276inserted at that point. 277 278Macros can also have parameters: 279 280``` 281#macro (greet $hello $world) $hello, $world! #end 282``` 283 284Then `#greet("bonjour", "monde")` would produce ` bonjour, monde! `. The comma is optional, so 285you could also write `#greet("bonjour" "monde")`. 286 287When a macro completes, the parameters (`$hello` and `$world` in the example) go back to whatever 288values they had before, or to being undefined if they were undefined before. 289 290All macro definitions take effect before the template is evaluated, so you can use a macro at a 291point in the template that is before the point where it is defined. This also means that you can't 292define a macro conditionally: 293 294``` 295## This doesn't work! 296#if ($language == "French") 297#macro (hello) bonjour #end 298#else 299#macro (hello) hello #end 300#end 301``` 302 303There is no particular reason to define the same macro more than once, but if you do it is the 304first definition that is retained. In the `#if` example just above, the `bonjour` version will 305always be used. 306 307Macros can make templates hard to understand. You may prefer to put the logic in a Java method 308rather than a macro, and call the method from the template using `$methods.doSomething("foo")` 309or whatever. 310 311## Block quoting 312 313If you have text that should be treated verbatim, you can enclose it in `#[[...]]#`. The text 314represented by `...` will be copied into the output. `#` and `$` characters will have no 315effect in that text. 316 317``` 318#[[ This is not a #directive, and this is not a $variable. ]]# 319``` 320 321## Including other templates 322 323If you want to include a template from another file, you can use the `#parse` directive. 324This can be useful if you have macros that are shared between templates, for example. 325 326``` 327#set ($foo = "bar") 328#parse("macros.vm") 329#mymacro($foo) ## #mymacro defined in macros.vm 330``` 331 332For this to work, you will need to tell EscapeVelocity how to find "resources" such as 333`macro.vm` in the example. You might use something like this: 334 335``` 336ResourceOpener resourceOpener = resourceName -> { 337 InputStream inputStream = getClass().getResource(resourceName); 338 if (inputStream == null) { 339 throw new IOException("Unknown resource: " + resourceName); 340 } 341 return new BufferedReader(InputStreamReader(inputStream, StandardCharsets.UTF_8)); 342}; 343Template template = Template.parseFrom("foo.vm", resourceOpener); 344``` 345 346In this case, the `resourceOpener` is used to find the main template `foo.vm`, as well as any 347templates it may reference in `#parse` directives. 348 349## <a name="spaces"></a> Spaces 350 351For the most part, spaces and newlines in the template are preserved exactly in the output. 352To avoid unwanted newlines, you may end up using `##` comments. In the `#foreach` example above 353we had this: 354 355``` 356#foreach ($product in $allProducts)${product}#if ($foreach.hasNext), #end#end 357``` 358 359That was to avoid introducing unwanted spaces and newlines. A more readable way to achieve the same 360result is this: 361 362``` 363#foreach ($product in $allProducts)## 364${product}## 365#if ($foreach.hasNext), #end## 366#end 367``` 368 369Spaces are ignored between the `#` of a directive and the `)` that closes it, so there is no trace 370in the output of the spaces in `#foreach ($product in $allProducts)` or `#if ($foreach.hasNext)`. 371Spaces are also ignored inside references, such as `$indexme[ $i ]` or `$callme( $i , $j )`. 372 373If you are concerned about the detailed formatting of the text from the template, you may want to 374post-process it. For example, if it is Java code, you could use a formatter such as 375[google-java-format](https://github.com/google/google-java-format). Then you shouldn't have to 376worry about extraneous spaces. 377 378[VelocityHacks]: https://github.com/google/auto/blob/ca2384d5ad15a0c761b940384083cf5c50c6e839/value/src/main/java/com/google/auto/value/processor/TemplateVars.java#L54 379[AutoValue]: https://github.com/google/auto/tree/master/value 380