{"id":28112515,"url":"https://github.com/google/escapevelocity","last_synced_at":"2025-05-14T05:04:14.565Z","repository":{"id":43182282,"uuid":"130419204","full_name":"google/escapevelocity","owner":"google","description":"A subset reimplementation of Apache Velocity with a much simpler API.","archived":false,"fork":false,"pushed_at":"2025-04-14T18:40:39.000Z","size":223,"stargazers_count":36,"open_issues_count":2,"forks_count":10,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-04-19T22:27:17.776Z","etag":null,"topics":["apache-velocity","java","template","template-engine","template-evaluation","template-language","velocity"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/google.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-04-20T22:26:30.000Z","updated_at":"2025-04-14T18:40:41.000Z","dependencies_parsed_at":"2024-02-02T20:27:00.745Z","dependency_job_id":"84ffa6f2-1d6c-4e50-896c-81a11d9af8c5","html_url":"https://github.com/google/escapevelocity","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fescapevelocity","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fescapevelocity/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fescapevelocity/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fescapevelocity/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/google","download_url":"https://codeload.github.com/google/escapevelocity/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254076839,"owners_count":22010611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apache-velocity","java","template","template-engine","template-evaluation","template-language","velocity"],"created_at":"2025-05-14T05:01:10.788Z","updated_at":"2025-05-14T05:04:14.547Z","avatar_url":"https://github.com/google.png","language":"Java","funding_links":[],"categories":["模板引擎"],"sub_categories":[],"readme":"# EscapeVelocity summary\n\nEscapeVelocity is a templating engine that can be used from Java. It is a reimplementation of a subset of\nfunctionality from [Apache Velocity](http://velocity.apache.org/).\n\nThis is not an official Google product.\n\nFor a fuller explanation of Velocity's functioning, see its\n[User Guide](http://velocity.apache.org/engine/releases/velocity-1.7/user-guide.html)\n\nIf EscapeVelocity successfully produces a result from a template evaluation, that result should be\nthe exact same string that Velocity produces. If not, that is a bug.\n\nEscapeVelocity has no facilities for HTML escaping and it is not appropriate for producing\nHTML output that might include portions of untrusted input.\n\n## Motivation\n\nVelocity has a convenient templating language. It is easy to read, and it has widespread support\nfrom tools such as editors and coding websites. However, *using* Velocity can prove difficult.\nIts use to generate Java code in the [AutoValue][AutoValue] annotation processor required many\n[workarounds][VelocityHacks]. The way it dynamically loads classes as part of its standard operation\nmakes it hard to [shade](https://maven.apache.org/plugins/maven-shade-plugin/) it, which in the case\nof AutoValue led to interference if Velocity was used elsewhere in a project. Velocity also has a\nlarge and complex API, and has introduced several incompatible changes over the years.\n\nEscapeVelocity has a\n[simple API](https://javadoc.io/doc/com.google.escapevelocity/escapevelocity/latest/index.html)\nthat does not involve any class-loading or other sources of problems. It and its\ndependencies can be shaded with no difficulty. We take care to avoid incompatible changes.\n\n## Loading a template\n\nThe entry point for EscapeVelocity is the `Template` class. To obtain an instance, use\n`Template.from(Reader)`. If a template is stored in a file, that file conventionally has the\nsuffix `.vm` (for Velocity Macros). But since the argument is a `Reader`, you can also load\na template directly from a Java string, using `StringReader`.\n\nHere's how you might make a `Template` instance from a template file that is packaged as a resource\nin the same package as the calling class:\n\n```java\nInputStream in = getClass().getResourceAsStream(\"foo.vm\");\nif (in == null) {\n  throw new IllegalArgumentException(\"Could not find resource foo.vm\");\n}\nTemplate template = Template.parseFrom(new InputStreamReader(in));\n```\n\n## Expanding a template\n\nOnce you have a `Template` object, you can use it to produce a string where the variables in the\ntemplate are given the values you provide. You can do this any number of times, specifying the\nsame or different values each time.\n\nSuppose you have this template:\n\n```\nThe $language word for $original is $translated.\n```\n\nYou might write this code:\n\n```java\nMap\u003cString, String\u003e vars = new HashMap\u003c\u003e();\nvars.put(\"language\", \"French\");\nvars.put(\"original\", \"toe\");\nvars.put(\"translated\", \"orteil\");\nString result = template.evaluate(vars);\n```\n\nThe `result` string would then be: `The French word for toe is orteil.`\n\n## Comments\n\nThe characters `##` introduce a comment. Characters from `##` up to and including the following\nnewline are omitted from the template. This template has comments:\n\n```\nLine 1 ## with a comment\nLine 2\n```\n\nIt is the same as this template:\n```\nLine 1 Line 2\n```\n\n## References\n\nEscapeVelocity supports most of the reference types described in the\n[Velocity User Guide](http://velocity.apache.org/engine/releases/velocity-1.7/user-guide.html#References)\n\n### Variables\n\nA variable has an ASCII name that starts with a letter (a-z or A-Z) and where any other characters\nare also letters or digits or hyphens (-) or underscores (`_`). A variable reference can be written\nas `$foo` or as `${foo}`. The value of a variable can be of any Java type. If the value `v` of\nvariable `foo` is not a String then the result of `$foo` in a template will be `String.valueOf(v)`.\nVariables must be defined before they are referenced; otherwise an `EvaluationException` will be\nthrown.\n\nVariable names are case-sensitive: `$foo` is not the same variable as `$Foo` or `$FOO`.\n\nInitially the values of variables come from the Map that is passed to `Template.evaluate`. Those\nvalues can be changed, and new ones defined, using the `#set` directive in the template:\n\n```\n#set ($foo = \"bar\")\n```\n\nSetting a variable affects later references to it in the template, but has no effect on the\n`Map` that was passed in or on later template evaluations.\n\n### Properties\n\nIf a reference looks like `$purchase.Total` then the value of the `$purchase` variable must be a\nJava object that has a public method `getTotal()` or `gettotal()`, or a method called `isTotal()` or\n`istotal()` that returns `boolean`. The result of `$purchase.Total` is then the result of calling\nthat method on the `$purchase` object.\n\nIf you want to have a period (`.`) after a variable reference *without* it being a property\nreference, you can use braces like this: `${purchase}.Total`. If, after a property reference, you\nhave a further period, you can put braces around the reference like this:\n`${purchase.Total}.nonProperty`.\n\nAs a special case, if `$purchase` is a Java `Map`, `$purchase.Total` is the result of calling\n`get(\"Total\")` on the `Map`.\n\n### Methods\n\nIf a reference looks like `$purchase.addItem(\"scones\", 23)` then the value of the `$purchase`\nvariable must be a Java object that has a public method `addItem` with two parameters that match\nthe given values. Unlike Velocity, EscapeVelocity requires that there be exactly one such method.\nIt is OK if there are other `addItem` methods provided they are not compatible with the\narguments provided.\n\nProperties are in fact a special case of methods: instead of writing `$purchase.Total` you could\nwrite `$purchase.getTotal()`. Braces can be used to make the method invocation explicit\n(`${purchase.getTotal()}`) or to prevent method invocation (`${purchase}.getTotal()`).\n\nIf the object that the method is being called on is an instance of `java.lang.Class`, then the\nmethod can be one of the methods of `java.lang.Class`, _or_ it can be a static method in the\nclass in question. For example if `$Objects` is `java.util.Objects.class`, then\n`$Objects.equals($a, $b)` will invoke the static method\n[`java.util.Objects.equals`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Objects.html#equals(java.lang.Object,java.lang.Object))\nwith the given parameters.\n\nA method parameter can be `null` to indicate a null value. For example\n`$Objects.equals(null, null)` would evaluate to `true`, given the above definition of `$Objects`.\n\n### Indexing\n\nIf a reference looks like `$indexme[$i]` then the value of the `$indexme` variable must be a Java\nobject that has a public `get` method that takes one argument that is compatible with the index.\nFor example, `$indexme` might be a `List` and `$i` might be an integer. Then the reference would\nbe the result of `List.get(int)` for that list and that integer. Or, `$indexme` might be a `Map`,\nand the reference would be the result of `Map.get(Object)` for the object `$i`. In general,\n`$indexme[$i]` is equivalent to `$indexme.get($i)`.\n\nFor lists specifically, the index can be negative, and then it counts from the end of the list.\nFor example `$list[-1]` is the last element of `$list`.\n\nUnlike Velocity, EscapeVelocity does not allow `$indexme` to be a Java array.\n\n### Undefined references\n\nIf a variable has not been given a value, either by being in the initial Map argument or by being\nset in the template, then referencing it will provoke an `EvaluationException`. There is\na special case for `#if`: if you write `#if ($var)` then it is allowed for `$var` not to be defined,\nand it is treated as false.\n\n### Null references\n\nA reference can produce a null value, for example `$foo` if the input `Map` has an entry for `\"foo\"`\nwith a null value, or `$indexme[$i]` if `$indexme` is a `List` that has a null element at index\n`$i`. If you try to insert a null reference into the output of a template then you will get an\nexception. If you use `$!` instead of `$`, like `$!foo` or `$!indexme[$i]`, then a null reference\nwill instead produce nothing in the output.\n\n### Setting properties and indexes: not supported\n\nUnlke Velocity, EscapeVelocity does not allow `#set` assignments with properties or indexes:\n\n```\n#set ($data.User = \"jon\")        ## Allowed in Velocity but not in EscapeVelocity\n#set ($map[\"apple\"] = \"orange\")  ## Allowed in Velocity but not in EscapeVelocity\n```\n\n## Expressions\n\nIn certain contexts, such as the `#set` directive we have just seen or certain other directives,\nEscapeVelocity can evaluate expressions. An expression can be any of these:\n\n* A reference, of the kind we have just seen. The value is the value of the reference.\n* A string literal, as described below.\n* An integer literal such as `23` or `-100`. EscapeVelocity does not support floating-point\n  literals.\n* A Boolean literal, `true` or `false`.\n* A list literal, as described below.\n* A map literal, like `{'key1': $value1, $key2: 'value2'}`. The value is a mutable Java map\n  with the given keys and values.\n* Simpler expressions joined together with operators that have the same meaning as in Java:\n  `!`, `==`, `!=`, `\u003c`, `\u003c=`, `\u003e`, `\u003e=`, `\u0026\u0026`, `||`, `+`, `-`, `*`, `/`, `%`. The operators have the\n  same precedence as in Java.\n* A simpler expression in parentheses, for example `(2 + 3)`.\n\n### String literals\n\nThere are two forms of string literals that can appear in expressions. The simpler form is\nsurrounded with single quotes (`'...'`) and represents a string containing everything between those\nquotes. The other form is surrounded with double quotes (`\"...\"`) and again represents a string\ncontaining everything between the quotes, but this time the text can contain references like\n`$purchase.Total` and directives like `#if ($condition) yes #end`.\n\nString literals can span more than one line.\n\n### List literals\n\nThere are two forms of list literals that can appear in expressions. An explicit list\nsuch as `[]`, `[23]`, or `[\"a\", \"b\"]` evaluates to a Java `List` containing those values.\nA range such as `[0..$i]` or `[$from .. $to]` evaluates to a Java `List` containing the\ninteger values from the first number to the second number, inclusive. If the second number\nis less than the first, the list values decrease.\n\n## Directives\n\nA directive is introduced by a `#` character followed by a word. We have already seen the `#set`\ndirective, which sets the value of a variable. The other directives are listed below.\n\nDirectives can be spelled with or without braces, so `#set` or `#{set}`.\n\n### `#if`/`#elseif`/`#else`\n\nThe `#if` directive selects parts of the template according as a condition is true or false.\nThe simplest case looks like this:\n\n```\n#if ($condition) yes #end\n```\n\nThis evaluates to the string ` yes ` if the variable `$condition` is defined and has a true value,\nand to the empty string otherwise. It is allowed for `$condition` not to be defined in this case,\nand then it is treated as false.\n\nThe expression in `#if` (here `$condition`) is considered true if its value is not null and not\nequal to the Boolean value `false`.\n\nAn `#if` directive can also have an `#else` part, for example:\n\n```\n#if ($condition) yes #else no #end\n```\n\nThis evaluates to the string ` yes ` if the condition is true or the string ` no ` if it is not.\n\nAn `#if` directive can have any number of `#elseif` parts. For example:\n\n```\n#if ($i == 0) zero #elseif ($i == 1) one #elseif ($i == 2) two #else many #end\n```\n\n### `#foreach`\n\nThe `#foreach` directive repeats a part of the template once for each value in a list.\n\n```\n#foreach ($product in $allProducts)\n  ${product}!\n#end\n```\n\nThis will produce one line for each value in the `$allProducts` variable. The value of\n`$allProducts` can be a Java `Iterable`, such as a `List` or `Set`; or it can be an object array;\nor it can be a Java `Map`. When it is a `Map` the `#foreach` directive loops over every *value*\nin the `Map`.\n\nIf `$allProducts` is a `List` containing the strings `oranges` and `lemons` then the result of the\n`#foreach` would be this:\n\n```\n\n  oranges!\n\n\n  lemons!\n\n```\n\nWhen the `#foreach` completes, the loop variable (`$product` in the example) goes back to whatever\nvalue it had before, or to being undefined if it was undefined before.\n\nWithin the `#foreach`, the special variable `$foreach` is defined.\n\n`$foreach.hasNext` will be true if there are more values after this one or false if this\nis the last value. `$foreach.index` will be the index of the iteration, starting at 0.  For example:\n\n```\n#foreach ($product in $allProducts)${foreach.index}: ${product}#if ($foreach.hasNext), #end#end\n```\n\nThis would produce the output `0: oranges, 1: lemons` for the list above. (The example is scrunched up\nto avoid introducing extraneous spaces, as described in the [section](#spaces) on spaces\nbelow.)\n\n`$foreach.first` and `$foreach.last` are true for the first and last iteration, respectively, and false\nfor other iterations. So `$foreach.last` is the negation of `$foreach.hasNext`.\n\n`$foreach.count` is one more than `$foreach.index`.\n\nThe `#foreach` directive is often used with list literals:\n\n```\n#foreach ($i in [1..$n])\n  #foreach ($j in [\"a\", \"b\", \"c\"])\n    $someObject.someMethod($i, $j)\n  #end\n#end\n```\n\n### Macros\n\nA macro is a part of the template that can be reused in more than one place, potentially with\ndifferent parameters each time. In the simplest case, a macro has no arguments:\n\n```\n#macro (hello) bonjour #end\n```\n\nThen the macro can be referenced by writing `#hello()` and the result will be the string ` bonjour `\ninserted at that point.\n\nMacros can also have parameters:\n\n```\n#macro (greet $hello $world) $hello, $world! #end\n```\n\nThen `#greet(\"bonjour\", \"monde\")` would produce ` bonjour, monde! `. The comma is optional, so\nyou could also write `#greet(\"bonjour\" \"monde\")`.\n\nWhen a macro completes, the parameters (`$hello` and `$world` in the example) go back to whatever\nvalues they had before, or to being undefined if they were undefined before.\n\nAll macro definitions take effect before the template is evaluated, so you can use a macro at a\npoint in the template that is before the point where it is defined. This also means that you can't\ndefine a macro conditionally:\n\n```\n## This doesn't work!\n#if ($language == \"French\")\n#macro (hello) bonjour #end\n#else\n#macro (hello) hello #end\n#end\n```\n\nThere is no particular reason to define the same macro more than once, but if you do it is the\nfirst definition that is retained. In the `#if` example just above, the `bonjour` version will\nalways be used.\n\nMacros can make templates hard to understand. You may prefer to put the logic in a Java method\nrather than a macro, and call the method from the template using `$methods.doSomething(\"foo\")`\nor whatever.\n\n## Block quoting\n\nIf you have text that should be treated verbatim, you can enclose it in `#[[...]]#`. The text\nrepresented by `...` will be copied into the output. `#` and `$` characters will have no\neffect in that text.\n\n```\n#[[ This is not a #directive, and this is not a $variable. ]]#\n```\n\n## Including other templates\n\nIf you want to include a template from another file, you can use the `#parse` directive.\nThis can be useful if you have macros that are shared between templates, for example.\n\n```\n#set ($foo = \"bar\")\n#parse(\"macros.vm\")\n#mymacro($foo) ## #mymacro defined in macros.vm\n```\n\nFor this to work, you will need to tell EscapeVelocity how to find \"resources\" such as\n`macro.vm` in the example. You might use something like this:\n\n```\nResourceOpener resourceOpener = resourceName -\u003e {\n  InputStream inputStream = getClass().getResource(resourceName).openStream();\n  if (inputStream == null) {\n    throw new IOException(\"Unknown resource: \" + resourceName);\n  }\n  return new InputStreamReader(inputStream, StandardCharsets.UTF_8);\n};\nTemplate template = Template.parseFrom(\"foo.vm\", resourceOpener);\n```\n\nIn this case, the `resourceOpener` is used to find the main template `foo.vm`, as well as any\ntemplates it may reference in `#parse` directives.\n\nA `#parse` directive only reads and parses the named template (`macros.vm` in the example)\nwhen the containing template (`foo.vm`) is evaluated (`template.evaluate(vars)`). The result\nis cached, so if you do `template.evaluate(vars)` a second time it will use the already-parsed\n`macros.vm` from the first time.\n\n## \u003ca name=\"spaces\"\u003e\u003c/a\u003e Spaces\n\nFor the most part, spaces and newlines in the template are preserved exactly in the output.\nTo avoid unwanted newlines, you may end up using `##` comments. In the `#foreach` example above\nwe had this:\n\n```\n#foreach ($product in $allProducts)${product}#if ($foreach.hasNext), #end#end\n```\n\nThat was to avoid introducing unwanted spaces and newlines. A more readable way to achieve the same\nresult is this:\n\n```\n#foreach ($product in $allProducts)##\n${product}##\n#if ($foreach.hasNext), #end##\n#end\n```\n\nSpaces are ignored between the `#` of a directive and the `)` that closes it, so there is no trace\nin the output of the spaces in `#foreach ($product in $allProducts)` or `#if ($foreach.hasNext)`.\nSpaces are also ignored inside references, such as `$indexme[ $i ]` or `$callme( $i , $j )`.\n\nIf you are concerned about the detailed formatting of the text from the template, you may want to\npost-process it. For example, if it is Java code, you could use a formatter such as\n[google-java-format](https://github.com/google/google-java-format). Then you shouldn't have to\nworry about extraneous spaces.\n\n[VelocityHacks]: https://github.com/google/auto/blob/ca2384d5ad15a0c761b940384083cf5c50c6e839/value/src/main/java/com/google/auto/value/processor/TemplateVars.java#L54\n[AutoValue]: https://github.com/google/auto/tree/main/value\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Fescapevelocity","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoogle%2Fescapevelocity","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Fescapevelocity/lists"}