Qute Reference Guide

Qute 是一款专门设计以满足 Quarkus 需求的模板引擎。反射的使用被最小化以减少本机映像的大小。该 API 结合了命令式和非阻塞式响应式编码风格。在开发模式下,src/main/resources/templates 文件夹中的所有文件都将被监视,而修改将立即出现在您的应用程序中。此外,Qute 会尝试在构建时检测到大多数模板问题并快速失败。

Qute is a templating engine designed specifically to meet the Quarkus needs. The usage of reflection is minimized to reduce the size of native images. The API combines both the imperative and the non-blocking reactive style of coding. In the development mode, all files located in the src/main/resources/templates folder are watched for changes and modifications are immediately visible in your application. Furthermore, Qute attempts to detect most of the template problems at build time and fail fast.

在本指南中,您将找到一个 introductory example,其中包括了 core featuresQuarkus integration 的详情描述。

In this guide, you will find an hello_world_example, the description of the core_features and quarkus_integration details.

Qute 主要设计为 Quarkus 扩展。也可以将其用作“独立”库。但是,在这种情况下,某些功能不可用。一般来说,在 Quarkus Integration 部分中提到的任何功能都不可用。在 Qute Used as a Standalone Library 部分中查找有关限制和可能的更多信息。

Qute is primarily designed as a Quarkus extension. It is possible to use it as a "standalone" library too. However, in such case some features are not available. In general, any feature mentioned under the Quarkus Integration section is missing. Find more information about the limitations and possibilities in the Qute Used as a Standalone Library section.

The Simplest Example

试用 Qute 最简单的方法是使用方便的 io.quarkus.qute.Qute 类,并调用其一个 fmt() 静态方法,可以用它来格式化简单的消息:

The easiest way to try Qute is to use the convenient io.quarkus.qute.Qute class and call one of its fmt() static methods that can be used to format simple messages:

import io.quarkus.qute.Qute;

Qute.fmt("Hello {}!", "Lucy"); 1
// => Hello Lucy!

Qute.fmt("Hello {name} {surname ?: 'Default'}!", Map.of("name", "Andy")); 2
// => Hello Andy Default!

Qute.fmt("<html>{header}</html>").contentType("text/html").data("header", "<h1>My header</h1>").render(); 3
// <html><h1>Header</h1></html> 4

Qute.fmt("I am {#if ok}happy{#else}sad{/if}!", Map.of("ok", true)); 5
// => I am happy!
1 The empty expression {} is a placeholder that is replaced with an index-based array accessor, i.e. {data[0]}.
2 You can provide a data map instead.
3 A builder-like API is available for more complex formatting requirements.
4 Note that for a "text/html" template the special chars are replaced with html entities by default.
5 You can use any basic-building-blocks in the template. In this case, the If Section is used to render the appropriate part of the message based on the input data.

Quarkus 中,用于格式化消息的引擎与由 @Inject Engine 注入的引擎相同。因此,您可以使用任何 Quarkus 特定的集成功能,如 Template Extension MethodsInjecting Beans Directly In Templates 甚至 Type-safe Message Bundles

In quarkus_integration, the engine used to format the messages is the same as the one injected by @Inject Engine. Therefore, you can make use of any Quarkus-specific integration feature such as Template Extension Methods, Injecting Beans Directly In Templates or even Type-safe Message Bundles.

Qute.fmt(String) 方法返回的格式对象可以延迟求值,并用作日志消息等:

The format object returned by the Qute.fmt(String) method can be evaluated lazily and used e.g. as a log message:

LOG.info(Qute.fmt("Hello {name}!").data("name", "Foo"));
// => Hello Foo! and the message template is only evaluated if the log level INFO is used for the specific logger

请阅读 io.quarkus.qute.Qute 类的 javadoc 以获取更多详细信息。

Please read the javadoc of the io.quarkus.qute.Qute class for more details.

Hello World Example

在此示例中,我们希望在使用 Qute 模板时演示 basic workflow。我们从一个简单的“hello world”示例开始。我们将始终需要一些 template contents

In this example, we would like to demonstrate the basic workflow when working with Qute templates. Let’s start with a simple "hello world" example. We will always need some template contents:

hello.html
<html>
  <p>Hello {name}! 1
</html>
1 {name} is a value expression that is evaluated when the template is rendered.

然后,我们需要将内容解析到一个 template definition Java 对象中。模板定义是 io.quarkus.qute.Template 的一个实例。

Then, we will need to parse the contents into a template definition Java object. A template definition is an instance of io.quarkus.qute.Template.

如果使用独立的 Qute,“首先需要创建一个 io.quarkus.qute.Engine 实例。 Engine 代表一个用于模板管理的中心点配有专门的配置。我们来使用这个方便的构建器:

If using Qute "standalone" you’ll need to create an instance of io.quarkus.qute.Engine first. The Engine represents a central point for template management with dedicated configuration. Let’s use the convenient builder:

Engine engine = Engine.builder().addDefaults().build();

在 Quarkus 中,有一个预配置的 Engine 可用于注入 - 请参阅 Quarkus Integration

In Quarkus, there is a preconfigured Engine available for injection - see Quarkus Integration.

一旦我们有一个 Engine 实例,我们就可以解析模板内容:

Once we have an Engine instance we could parse the template contents:

Template hello = engine.parse(helloHtmlContent);

在 Quarkus 中,您可以直接注入模板定义。该模板会自动解析并缓存 - 请参阅 Quarkus Integration

In Quarkus, you can simply inject the template definition. The template is automatically parsed and cached - see Quarkus Integration.

最后,创建一个 template instance、设置数据并渲染输出:

Finally, create a template instance, set the data and render the output:

// Renders <html><p>Hello Jim!</p></html>
hello.data("name", "Jim").render(); 1 2
1 Template.data(String, Object) is a convenient method that creates a template instance and sets the data in one step.
2 TemplateInstance.render() triggers a synchronous rendering, i.e. the current thread is blocked until the rendering is finished. However, there are also asynchronous ways to trigger the rendering and consume the results. For example, there is the TemplateInstance.renderAsync() method that returns CompletionStage<String> or TemplateInstance.createMulti() that returns Mutiny’s Multi<String>.

因此,工作流十分简单:

So the workflow is simple:

  1. Create the template contents (hello.html),

  2. Parse the template definition (io.quarkus.qute.Template),

  3. Create a template instance (io.quarkus.qute.TemplateInstance),

  4. Render the output.

Engine 可以缓存模板定义,这样就不必一遍又一遍地解析内容了。在 Quarkus 中,缓存会自动完成。

The Engine is able to cache the template definitions so that it’s not necessary to parse the contents again and again. In Quarkus, the caching is done automatically.

Core Features

Basic Building Blocks

模板的动态部分包括注释、表达式、章节和未解析的字符数据。

The dynamic parts of a template include comments, expressions, sections and unparsed character data.

Comments

A comment starts with the sequence {! and ends with the sequence !}, e.g. {! This is a comment !}. Can be multiline and may contain expressions and sections: {! {#if true} !}. The content of a comment is completely ignored when rendering the output.

Expressions

An expressions outputs an evaluated value. It consists of one or more parts. A part may represent simple properties: {foo}, {item.name}, and virtual methods: {item.get(name)}, {name ?: 'John'}. An expression may also start with a namespace: {inject:colors}.

Sections

A sections may contain static text, expressions and nested sections: {#if foo.active}{foo.name}{/if}. The name in the closing tag is optional: {#if active}ACTIVE!{/}. A section can be empty: {#myTag image=true /}. Some sections support optional end tags, i.e. if the end tag is missing then the section ends where the parent section ends. A section may also declare nested section blocks: {#if item.valid} Valid. {#else} Invalid. {/if} and decide which block to render.

Unparsed Character Data

It is used to mark the content that should be rendered but not parsed. It starts with the sequence {| and ends with the sequence |}: {| <script>if(true){alert('Qute is cute!')};</script> |}, and could be multi-line.

之前,未解析的字符数据可以用 {[ 开头,用 ]} 结尾。由于此语法与其他语言的结构经常冲突,现已废除。

Previously, unparsed character data could start with {[ and end with ]}. This syntax is now removed due to common collisions with constructs from other languages.

Identifiers and Tags

标识符用于表达式和章节标签。有效的标识符是非空白字符序列。但是,建议用户仅在表达式中使用有效的 Java 标识符。

Identifiers are used in expressions and section tags. A valid identifier is a sequence of non-whitespace characters. However, users are encouraged to only use valid Java identifiers in expressions.

如果您需要指定一个包含句点的标识符,可以使用方括号符号,例如 {map['my.key']}

You can use bracket notation if you need to specify an identifier that contains a dot, e.g. {map['my.key']}.

解析模板文档时,解析器会识别所有 tags。一个标签以花括号开始和结束,例如 {foo}。标签的内容必须以以下内容之一开头:

When parsing a template document the parser identifies all tags. A tag starts and ends with a curly bracket, e.g. {foo}. The content of a tag must start with:

  • a digit, or

  • an alphabet character, or

  • underscore, or

  • a built-in command: #, !, @, /.

如果它没有以任何上述内容开头,就会被解析器忽略。

If it does not start with any of the above it is ignored by the parser.

Tag Examples
<html>
   <body>
   {_foo.bar}   1
   {! comment !}2
   {  foo}      3
   {{foo}}      4
   {"foo":true} 5
   </body>
</html>
1 Parsed: an expression that starts with underscore.
2 Parsed: a comment
3 Ignored: starts with whitespace.
4 Ignored: starts with {.
5 Ignored: starts with ".

也可以使用转义序列 \{`和 `\}`在文本中插入定界符。事实上,转义序列通常仅对开始定界符需要,例如 `{foo}`将被渲染为 `{foo}(不会发生解析/评估)。

It is also possible to use escape sequences \{ and \} to insert delimiters in the text. In fact, an escape sequence is usually only needed for the start delimiter, i.e. {foo} will be rendered as {foo} (no parsing/evaluation will happen).

Removing Standalone Lines From the Template

在默认情况下,解析器会从模板输出中移除独立行。A *standalone line*是至少包含一个部分标记(例如 {#each}`和 `{/each})、参数声明(例如 {@org.acme.Foo foo})或注释的代码行,但不包含表达式和非空白字符。换句话说,不包含部分标记或参数声明的代码行是 *not*独立行。同样,包含 _expression_或 _non-whitespace character_的代码行是 *not*独立行。

By default, the parser removes standalone lines from the template output. A standalone line is a line that contains at least one section tag (e.g. {#each} and {/each}), parameter declaration (e.g. {@org.acme.Foo foo}) or comment but no expression and no non-whitespace character. In other words, a line that contains no section tag or a parameter declaration is not a standalone line. Likewise, a line that contains an expression or a non-whitespace character is not a standalone line.

Template Example
<html>
  <body>
     <ul>
     {#for item in items} 1
       <li>{item.name} {#if item.active}{item.price}{/if}</li>  2
                          3
     {/for}               4
     </ul>
   </body>
</html>
1 This is a standalone line and will be removed.
2 Not a standalone line - contains an expression and non-whitespace characters
3 Not a standalone line - contains no section tag/parameter declaration
4 This is a standalone line.
Default Output
<html>
  <body>
     <ul>
       <li>Foo 100</li>

     </ul>
   </body>
</html>

在 Quarkus 中,可以通过将属性 `quarkus.qute.remove-standalone-lines`设置为 `false`禁用默认行为。在此情况下,将独立行中的所有空白字符打印到输出。

In Quarkus, the default behavior can be disabled by setting the property quarkus.qute.remove-standalone-lines to false. In this case, all whitespace characters from a standalone line will be printed to the output.

Output with quarkus.qute.remove-standalone-lines=false
<html>
  <body>
     <ul>

       <li>Foo 100</li>


     </ul>
   </body>
</html>

Expressions

评估表达式并输出值。它具有一个或多个部分,其中每个部分都表示属性访问器(又名字段访问表达式)或虚拟方法调用(又名方法调用表达式)。

An expression is evaluated and outputs the value. It has one or more parts, where each part represents either a property accessor (aka Field Access Expression) or a virtual method invocation (aka Method Invocation Expression).

访问属性时,可以使用点表示法或方括号表示法。在 `object.property`点表示法)语法中,`property`必须为 valid identifier。在 `object[property_name]`方括号表示法)语法中,`property_name`必须是非空 literal值。

When accessing the properties you can either use the dot notation or bracket notation. In the object.property (dot notation) syntax, the property must be a identifiers. In the object[property_name] (bracket notation) syntax, the property_name has to be a non-null literals value.

表达式可以从可选项名称空间开始,后跟冒号 (:)。有效名称空间由字母数字字符和下划线组成。名称空间表达式以不同的方式解析 - 另请参见 Resolution

An expression can start with an optional namespace followed by a colon (:). A valid namespace consists of alphanumeric characters and underscores. Namespace expressions are resolved differently - see also Resolution.

Property Accessor Examples
{name} 1
{item.name} 2
{item['name']} 3
{global:colors} 4
1 no namespace, one part: name
2 no namespace, two parts: item, name
3 equivalent to {item.name} but using the bracket notation
4 namespace global, one part: colors

表达式的部分可以是 virtual method,在这种情况下,名称后面可以紧跟圆括号中用逗号分隔的参数列表。虚拟方法的参数可以是嵌套表达式或 literal值。我们将这些方法称为 "virtual",因为它们不必由真实的 Java 方法支持。您可以在 following section中了解更多有关虚拟方法的信息。

A part of an expression can be a virtual method in which case the name can be followed by a list of comma-separated parameters in parentheses. A parameter of a virtual method can be either a nested expression or a literals value. We call these methods "virtual" because they do not have to be backed by a real Java method. You can learn more about virtual methods in the virtual_methods.

Virtual Method Example
{item.getLabels(1)} 1
{name or 'John'} 2
1 no namespace, two parts - item, getLabels(1), the second part is a virtual method with name getLabels and params 1
2 infix notation that can be used for virtual methods with single parameter, translated to name.or('John'); no namespace, two parts - name, or('John')

Supported Literals

Literal Examples

boolean

true, false

null

null

string

’value'`, "string"

integer

1, -5

long

1l, -5L

double

1D, -5d

float

1f, -5F

Resolution

表达式的第一部分始终针对 current context object解析。如果没有找到第一部分的结果,则针对父上下文对象(如果可用)解析。对于从名称空间开始的表达式,可以使用所有可用的 NamespaceResolver 找到当前上下文对象。对于不以名称空间开头的表达式,当前上下文对象是标记的 derived from the position。表达式的所有其他部分都使用针对前面解析结果的所有 ValueResolver 解析。

The first part of the expression is always resolved against the current_context_object. If no result is found for the first part, it’s resolved against the parent context object (if available). For an expression that starts with a namespace the current context object is found using all the available `NamespaceResolver`s. For an expression that does not start with a namespace the current context object is derived from the position of the tag. All other parts of an expression are resolved using all `ValueResolver`s against the result of the previous resolution.

例如,表达式 {name} 没有名称空间和单个部分 - name。“名称” 将使用所有可用的值解析器解析针对当前上下文对象。然而,表达式 {global:colors} 具有名称空间 global 和单个部分 - colors。首先,所有可用的 NamespaceResolver 都将用来查找当前上下文对象。并且随后值解析器将用来针对已找到的上下文对象解析“颜色”。

For example, expression {name} has no namespace and single part - name. The "name" will be resolved using all available value resolvers against the current context object. However, the expression {global:colors} has the namespace global and single part - colors. First, all available `NamespaceResolver`s will be used to find the current context object. And afterwards value resolvers will be used to resolve "colors" against the context object found.

传递给模板实例的数据始终可以使用 data 名称空间访问。该名称空间对于访问密钥被覆盖的数据可能很有用:

Data passed to the template instance are always accessible using the data namespace. This could be useful to access data for which the key is overridden:

<html>
{item.name} 1
<ul>
{#for item in item.derivedItems} 2
  <li>
  {item.name} 3
  is derived from
  {data:item.name} 4
  </li>
{/for}
</ul>
</html>
1 item is passed to the template instance as a data object.
2 Iterate over the list of derived items.
3 item is an alias for the iterated element.
4 Use the data namespace to access the item data object.

Current Context

如果一个表达式未指定名称空间,则 current context object 由标签的位置推导而来。默认情况下,当前上下文对象表示传递给模板实例的数据。然而,节可以改变当前上下文对象。一个典型的例子是 <<<`let`,let_section>> 节,可用来定义具有名称的局部变量:

If an expression does not specify a namespace, the current context object is derived from the position of the tag. By default, the current context object represents the data passed to the template instance. However, sections may change the current context object. A typical example is the <<`let`,let_section>> section that can be used to define named local variables:

{#let myParent=order.item.parent myPrice=order.price} 1
  <h1>{myParent.name}</h1>
  <p>Price: {myPrice}</p>
{/let}
1 The current context object inside the section is the map of resolved parameters.

可以经由隐式绑定 this 访问当前上下文。

The current context can be accessed via the implicit binding this.

Built-in Resolvers

Name Description Examples

Elvis Operator

Outputs the default value if the previous part cannot be resolved or resolves to null.

{person.name ?: 'John'}, {person.name or 'John'}, {person.name.or('John')}

orEmpty

Outputs an empty list if the previous part cannot be resolved or resolves to null.

{pets.orEmpty.size} outputs 0 if pets is not resolvable or null

Ternary Operator

Shorthand for if-then-else statement. Unlike in If Section nested operators are not supported.

{item.isActive ? item.name : 'Inactive item'} outputs the value of item.name if item.isActive resolves to true.

Logical AND Operator

Outputs true if both parts are not falsy as described in the If Section. The parameter is only evaluated if needed.

{person.isActive && person.hasStyle}

Logical OR Operator

Outputs true if any of the parts is not falsy as described in the If Section. The parameter is only evaluated if needed.

`{person.isActive

在三元运算符中,如果该值不被视为 falsy(如 If Section 中所述),则条件评估为 true

The condition in a ternary operator evaluates to true if the value is not considered falsy as described in If Section.

实际上,操作符实现为消耗一个参数的“虚拟方法”,并且可以使用中缀表示法。例如,{person.name or 'John'}`被翻译为 `{person.name.or('John')},而 {item.isActive ? item.name : 'Inactive item'}`被翻译为 `{item.isActive.ifTruthy(item.name).or('Inactive item')}

In fact, the operators are implemented as "virtual methods" that consume one parameter and can be used with infix notation. For example {person.name or 'John'} is translated to {person.name.or('John')} and {item.isActive ? item.name : 'Inactive item'} is translated to {item.isActive.ifTruthy(item.name).or('Inactive item')}

Arrays

您可以使用 Loop Section迭代数组的元素。此外,还可以获取指定数组的长度并通过索引值直接访问元素。另外,您可以通过 `take(n)/takeLast(n)`方法访问首/尾 `n`个元素。

You can iterate over elements of an array with Loop Section. Moreover, it’s also possible to get the length of the specified array and access the elements directly via an index value. Additionally, you can access the first/last n elements via the take(n)/takeLast(n) methods.

Array Examples
<h1>Array of length: {myArray.length}</h1> 1
<ul>
  <li>First: {myArray.0}</li> 2
  <li>Second: {myArray[1]}</li> 3
  <li>Third: {myArray.get(2)}</li> 4
</ul>
<ol>
 {#for element in myArray}
 <li>{element}</li>
 {/for}
</ol>
First two elements: {#each myArray.take(2)}{it}{/each} 5
1 Outputs the length of the array.
2 Outputs the first element of the array.
3 Outputs the second element of the array using the bracket notation.
4 Outputs the third element of the array via the virtual method get().
5 Outputs the first two elements of the array.

Character Escapes

如果设置了模板变体,对于 HTML 和 XML 模板而言,字符`, "<>、`&`默认会转义。

For HTML and XML templates the ’, `", <, >, & characters are escaped by default if a template variant is set.

在 Quarkus 中,将自动为位于 `src/main/resources/templates`中的模板设置一个变体。默认情况下,使用 `java.net.URLConnection#getFileNameMap()`来确定模板文件的类型。可以通过 `quarkus.qute.content-types`设置后缀内容类型的其他映射。

In Quarkus, a variant is set automatically for templates located in the src/main/resources/templates. By default, the java.net.URLConnection#getFileNameMap() is used to determine the content-type of a template file. The additional map of suffixes to content types can be set via quarkus.qute.content-types.

如果您需要呈现未转义的值:

If you need to render the unescaped value:

  1. Either use the raw or safe properties implemented as extension methods of the java.lang.Object,

  2. Or wrap the String value in a io.quarkus.qute.RawString.

<html>
<h1>{title}</h1> 1
{paragraph.raw} 2
</html>
1 title that resolves to Expressions & Escapes will be rendered as Expressions & Escapes
2 paragraph that resolves to <p>My text!</p> will be rendered as <p>My text!</p>

默认情况下,具有以下内容类型之一的模板会转义: text/htmltext/xmlapplication/xml`和 `application/xhtml+xml。但是,可以通过 `quarkus.qute.escape-content-types`配置属性扩展此列表。

By default, a template with one of the following content types is escaped: text/html, text/xml, application/xml and application/xhtml+xml. However, it’s possible to extend this list via the quarkus.qute.escape-content-types configuration property.

Virtual Methods

虚拟方法是一种类似于常规 Java 方法调用的 part of an expression。之所以称其为“虚拟”,是因为它不必与 Java 类的实际方法匹配。实际上,与普通属性一样,虚拟方法也由值解析器处理。唯一不同的是,对于虚拟方法,值解析器消耗的参数也是表达式。

A virtual method is a part of an expression that looks like a regular Java method invocation. It’s called "virtual" because it does not have to match the actual method of a Java class. In fact, like normal properties a virtual method is also handled by a value resolver. The only difference is that for virtual methods a value resolver consumes parameters that are also expressions.

Virtual Method Example
<html>
<h1>{item.buildName(item.name,5)}</h1> 1
</html>
1 buildName(item.name,5) represents a virtual method with name buildName and two parameters: item.name and 5 . The virtual method could be evaluated by a value resolver generated for the following Java class:[source, java]
class Item {
   String buildName(String name, int age) {
      return name + ":" + age;
   }
}

虚拟方法通常由为 <<@TemplateExtension 方法,template_extension_methods>>, <<@TemplateData,template_data>> 或 parameter declarations中使用的类生成的值解析器评估。然而,还可以注册不被任何 Java 类/方法支持的自定义值解析器。

Virtual methods are usually evaluated by value resolvers generated for <<@TemplateExtension methods,template_extension_methods>>, <<@TemplateData,template_data>> or classes used in typesafe_expressions. However, a custom value resolver that is not backed by any Java class/method can be registered as well.

可以利用中缀表示法调用具有单个参数的虚拟方法:

A virtual method with single parameter can be called using the infix notation:

Infix Notation Example
<html>
<p>{item.price or 5}</p>  1
</html>
1 item.price or 5 is translated to item.price.or(5).

虚拟方法参数可以是“嵌套”虚拟方法调用。

Virtual method parameters can be "nested" virtual method invocations.

Nested Virtual Method Example
<html>
<p>{item.subtractPrice(item.calculateDiscount(10))}</p>  1
</html>
1 item.calculateDiscount(10) is evaluated first and then passed as an argument to item.subtractPrice().

Evaluation of CompletionStage and Uni Objects

以特殊方式评估实现 java.util.concurrent.CompletionStageio.smallrye.mutiny.Uni 的对象。如果表达式的部分解析为 CompletionStage,则在该阶段完成后解析继续,并且如果有的,则根据已完成阶段的结果评估表达式的下一部分。例如,如果存在表达式 {foo.size} 并且 foo 解析为 CompletionStage<List<String>>,则根据已完成的结果(即 List<String>)来解析 size。如果表达式的部分解析为 Uni,则使用 Uni#subscribeAsCompletionStage()Uni 创建 CompletionStage,然后如上所述进行评估。

Objects that implement java.util.concurrent.CompletionStage and io.smallrye.mutiny.Uni are evaluated in a special way. If a part of an expression resolves to a CompletionStage, the resolution continues once this stage is completed and the next part of the expression (if any) is evaluated against the result of the completed stage. For example, if there is an expression {foo.size} and foo resolves to CompletionStage<List<String>> then size is resolved against the completed result, i.e. List<String>. If a part of an expression resolves to a Uni, a CompletionStage is first created from Uni using Uni#subscribeAsCompletionStage() and then evaluated as described above.

请注意,每个 Uni#subscribeAsCompletionStage() 都会导致一个新订阅。您可能需要配置 Uni 项的记忆化或在将其用作模板数据(即 myUni.memoize().indefinitely())之前配置故障。

Note that each Uni#subscribeAsCompletionStage() results in a new subscription. You might need to configure memoization of the Uni item or failure before it’s used as template data, i.e. myUni.memoize().indefinitely().

可能会出现 CompletionStage 永远不完成或 Uni 不发出任何项目/故障的情况。在这种情况下,渲染方法(例如 TemplateInstance#render()TemplateInstance#createUni())在特定超时之后会失败。超时可以指定为模板实例 timeout 属性。如果未设置 timeout 属性,则使用全局渲染超时。

It can happen that a CompletionStage never completes or a Uni emits no item/failure. In this case, the rendering methods (such as TemplateInstance#render() and TemplateInstance#createUni()) fail after a specific timeout. The timeout can be specified as a template instance timeout attribute. If no timeout attribute is set the global rendering timeout is used.

在 Quarkus 中,默认超时可以通过 io.quarkus.qute.timeout 配置属性设置。如果独立使用 Qute,可以使用 EngineBuilder#timeout() 方法。

In Quarkus, the default timeout can be set via the io.quarkus.qute.timeout configuration property. If using Qute standalone then the EngineBuilder#timeout() method can be used.

在以前的版本中,只有 TemplateInstance#render() 方法遵守超时属性。您可以使用 io.quarkus.qute.useAsyncTimeout=false 配置属性来保留旧行为并自行处理超时,例如 templateInstance.createUtni().ifNoItem().after(Duration.ofMillis(500)).fail()

In previous versions, only the TemplateInstance#render() method honored the timeout attribute. You can use the io.quarkus.qute.useAsyncTimeout=false config property to preserve the old behavior and take care of the timeout yourself, for example templateInstance.createUtni().ifNoItem().after(Duration.ofMillis(500)).fail().

How to Identify a Problematic Part of the Template

当发生超时时,很难找到模板中出现问题的部分。您可以为日志记录器 io.quarkus.qute.nodeResolve 设置 TRACE 级别,然后尝试分析日志输出。

It’s not easy to find the problematic part of a template when a timeout occurs. You can set the TRACE level for the logger io.quarkus.qute.nodeResolve and try to analyze the log output afterwards.

application.properties Example
quarkus.log.category."io.quarkus.qute.nodeResolve".min-level=TRACE
quarkus.log.category."io.quarkus.qute.nodeResolve".level=TRACE

您应该为模板中使用的每个表达式和部分看到以下一对日志消息:

You should see the following pair of log messages for every expression and section used in a template:

TRACE [io.qua.qut.nodeResolve] Resolve {name} started: Template hello.html at line 8
TRACE [io.qua.qut.nodeResolve] Resolve {name} completed: Template hello.html at line 8

如果缺少 completed 日志消息,那么您有一个值得探索的候选消息。

If a completed log message is missing then you have a good candidate to explore.

Missing Properties

可能会出现表达式在运行时可能未评估的情况。例如,如果存在表达式 {person.age} 并且未在 Person 类上声明属性 age。行为不同,具体取决于是否启用了 Strict Rendering

It can happen that an expression may not be evaluated at runtime. For example, if there is an expression {person.age} and there is no property age declared on the Person class. The behavior differs based on whether the Strict Rendering is enabled or not.

如果启用,则缺少的属性始终会导致 TemplateException,并且渲染中止。您可以使用 default valuessafe expressions 来抑制错误。

If enabled then a missing property will always result in a TemplateException and the rendering is aborted. You can use default values and safe expressions in order to suppress the error.

如果禁用,则默认情况下特殊常量 NOT_FOUND 写入输出。

If disabled then the special constant NOT_FOUND is written to the output by default.

在 Quarkus 中,可以通过 quarkus.qute.property-not-found-strategy 更改默认策略,如 [configuration-reference] 中所述。

In Quarkus, it’s possible to change the default strategy via the quarkus.qute.property-not-found-strategy as described in the [configuration-reference].

如果使用 Type-safe ExpressionsType-safe Templates,则在构建时会检测到类似错误。

Similar errors are detected at build time if Type-safe Expressions and Type-safe Templates are used.

Sections

部分具有一个以 # 开头的开始标记,后跟部分的名称,例如 {#if}{#each}。它可能为空,即开始标记以 / 结尾: {#myEmptySection /}。部分通常包含嵌套的表达式和其它部分。结束标记以 / 开头,并包含部分的名称(可选): {#if foo}Foo!{/if}{#if foo}Foo!{/}。一些部分支持可选结束标记,也就是说,如果缺少结束标记,则部分在父部分结束的位置结束。

A section has a start tag that starts with #, followed by the name of the section such as {#if} and {#each}. It may be empty, i.e. the start tag ends with /: {#myEmptySection /}. Sections usually contain nested expressions and other sections. The end tag starts with / and contains the name of the section (optional): {#if foo}Foo!{/if} or {#if foo}Foo!{/}. Some sections support optional end tags, i.e. if the end tag is missing then the section ends where the parent section ends.

#let Optional End Tag Example
{#if item.isActive}
  {#let price = item.price} 1
  {price}
  // synthetic {/let} added here automatically
{/if}
// {price} cannot be used here!
1 Defines the local variable that can be used inside the parent {#if} section.
Built-in section Supports Optional End Tag

{#for}

{#if}

{#when}

{#let}

{#with}

{#include}

User-defined Tags

{#fragment}

{#cached}

Parameters

开始标记可以用可选的名称定义参数,例如 {#if item.isActive}{#let foo=1 bar=false}。参数由一个或多个空格分隔。名称和值通过等号分隔。名称和值可以在前面和后面加任意数量的空格作为前缀和后缀,例如 {#let id='Foo'}{#let id = 'Foo'} 是等效的,其中参数的名称是 id,而值是 Foo。可以使用括号对值进行分组,例如 {#let id=(item.id ?: 42)},其中名称是 id,而值是 item.id ?: 42。部分可以以任何方式解释参数值,例如按原样获取该值。但是,在大多数情况下,参数值注册为 expression,并在使用之前进行评估。

A start tag can define parameters with optional names, e.g. {#if item.isActive} and {#let foo=1 bar=false}. Parameters are separated by one or more spaces. Names are separated from the values by the equals sign. Names and values can be prefixed and suffixed with any number of spaces, e.g. {#let id='Foo'} and {#let id = 'Foo'} are equivalents where the name of the parameter is id and the value is Foo. Values can be grouped using parentheses, e.g. {#let id=(item.id ?: 42)} where the name is id and the value is item.id ?: 42. Sections can interpret parameter values in any way, e.g. take the value as is. However, in most cases, the parameter value is registered as an expressions and evaluated before use.

一个部分可能包含多个内容 blocks。“main” 块总是存在的。附加/嵌套块也以 # 开始,也可以有参数 - {#else if item.isActive}。定义部分逻辑的部分助手可以“执行”任何块并评估参数。

A section may contain several content blocks. The "main" block is always present. Additional/nested blocks also start with # and can have parameters too - {#else if item.isActive}. A section helper that defines the logic of a section can "execute" any of the blocks and evaluate the parameters.

#if Section Example
{#if item.name is 'sword'}
  It's a sword! 1
{#else if item.name is 'shield'}
  It's a shield! 2
{#else}
  Item is neither a sword nor a shield. 3
{/if}
1 This is the main block.
2 Additional block.
3 Additional block.

Loop Section

循环部分使得可能遍历 IterableIterator、数组、Map(元素是 Map.Entry)、StreamIntegerLongint`和 `long(基本值)。null 参数值产生无操作。

The loop section makes it possible to iterate over an instance of Iterable, Iterator, array, Map (element is a Map.Entry), Stream, Integer, Long, int and long (primitive value). A null parameter value results in a no-op.

此部分有两种类型。第一种是使用名称 each,并且 it 是迭代元素的隐式别名。

This section has two flavors. The first one is using the name each and it is an implicit alias for the iteration element.

{#each items}
  {it.name} 1
{/each}
1 name is resolved against the current iteration element.

另一种形式是使用名称 for,并且指定用于引用迭代元素的别名:

The other form is using the name for and specifies the alias used to reference the iteration element:

{#for item in items} 1
  {item.name}
{/for}
1 item is the alias used for the iteration element.

还可以通过以下键在循环内访问迭代元数据:

It’s also possible to access the iteration metadata inside the loop via the following keys:

  • count - 1-based index

  • index - zero-based index

  • hasNext - true if the iteration has more elements

  • isLast - true if hasNext == false

  • isFirst - true if count == 1

  • odd - true if the element’s count is odd

  • even - true if the element’s count is even

  • indexParity - outputs odd or even based on the count value

但是,这些键不能直接使用。相反,使用前缀来避免与外部范围中的变量发生冲突。默认情况下,以一个下划线作为后缀的迭代元素别名用作前缀。例如,hasNext 键必须在 {#each} 部分的 it_ 前加上前缀:{it_hasNext}

However, the keys cannot be used directly. Instead, a prefix is used to avoid possible collisions with variables from the outer scope. By default, the alias of an iterated element suffixed with an underscore is used as a prefix. For example, the hasNext key must be prefixed with it_ inside an {#each} section: {it_hasNext}.

each Iteration Metadata Example
{#each items}
  {it_count}. {it.name} 1
  {#if it_hasNext}<br>{/if} 2
{/each}
1 it_count represents one-based index.
2 <br> is only rendered if the iteration has more elements.

并且必须在具有 item 元素别名的 {#for} 部分中采用 {item_hasNext} 的形式使用。

And must be used in a form of {item_hasNext} inside a {#for} section with the item element alias.

for Iteration Metadata Example
{#for item in items}
  {item_count}. {item.name} 1
  {#if item_hasNext}<br>{/if} 2
{/for}
1 item_count represents one-based index.
2 <br> is only rendered if the iteration has more elements.

迭代元数据前缀可以配置,通过 EngineBuilder.iterationMetadataPrefix() 针对独立的 Qute 或通过 quarkus.qute.iteration-metadata-prefix 配置属性在 Quarkus 应用程序中进行配置。可以使用三个特殊常量:

The iteration metadata prefix is configurable either via EngineBuilder.iterationMetadataPrefix() for standalone Qute or via the quarkus.qute.iteration-metadata-prefix configuration property in a Quarkus application. Three special constants can be used:

  1. <alias_> - the alias of an iterated element suffixed with an underscore is used (default)

  2. <alias?> - the alias of an iterated element suffixed with a question mark is used

  3. <none> - no prefix is used

for 语句也能与从 1 开始的整数一起使用。在下面的示例中,考虑到 total = 3

The for statement also works with integers, starting from 1. In the example below, considering that total = 3:

{#for i in total}
  {i}: ({i_count} {i_indexParity} {i_even})<br>
{/for}

输出将为:

And the output will be:

1: (1 odd false)
2: (2 even true)
3: (3 odd false)

循环节还能定义当没有需要迭代的项时要执行的 {#else} 块:

A loop section may also define the {#else} block that is executed when there are no items to iterate:

{#for item in items}
  {item.name}
{#else}
  No items.
{/for}

If Section

if 节表示基本的控制流节。最简单的可行版本接受单个参数并在条件判断为 true 时渲染内容。没有运算符的条件会在值不被认为是 falsy 时求值为 true,即当值不是 nullfalse、空集合、空映射、空数组、空字符串/char 序列或等于零的数字时。

The if section represents a basic control flow section. The simplest possible version accepts a single parameter and renders the content if the condition is evaluated to true. A condition without an operator evaluates to true if the value is not considered falsy, i.e. if the value is not null, false, an empty collection, an empty map, an empty array, an empty string/char sequence or a number equal to zero.

{#if item.active}
  This item is active.
{/if}

还能在条件中使用以下运算符:

You can also use the following operators in a condition:

Operator Aliases Precedence (higher wins)

logical complement

!

4

greater than

gt, >

3

greater than or equal to

ge, >=

3

less than

lt, <

3

less than or equal to

le, <=

3

equals

eq, ==, is

2

not equals

ne, !=

2

logical AND (short-circuiting)

&&, and

1

logical OR (short-circuiting)

`

A simple operator example
{#if item.age > 10}
  This item is very old.
{/if}

还支持多个条件。

Multiple conditions are also supported.

Multiple conditions example
{#if item.age > 10 && item.price > 500}
  This item is very old and expensive.
{/if}

括号可以覆盖优先级规则。

Precedence rules can be overridden by parentheses.

Parentheses example
{#if (item.age > 10 || item.price > 500) && user.loggedIn}
  User must be logged in and item age must be > 10 or price must be > 500.
{/if}

还能添加任意数量的 else 块:

You can also add any number of else blocks:

{#if item.age > 10}
  This item is very old.
{#else if item.age > 5}
  This item is quite old.
{#else if item.age > 2}
  This item is old.
{#else}
  This item is not old at all!
{/if}

When Section

此节类似于 Java 的 switch 或 Kotlin 的 when 构造。它顺序地将 tested value 与所有块进行匹配,直至某个条件达成。执行首个匹配的块。所有其他块都会被忽略(此行为与 Java switch 不同,其中需要 break 语句)。

This section is similar to Java’s switch or Kotlin’s when constructs. It matches a tested value against all blocks sequentially until a condition is satisfied. The first matching block is executed. All other blocks are ignored (this behavior differs to the Java switch where a break statement is necessary).

Example using the when/is name aliases
{#when items.size}
  {#is 1} 1
    There is exactly one item!
  {#is > 10} 2
    There are more than 10 items!
  {#else} 3
    There are 2 -10 items!
{/when}
1 If there is exactly one parameter it’s tested for equality.
2 It is possible to use when_operators to specify the matching logic. Unlike in the If Section nested operators are not supported.
3 else is block is executed if no other block matches the value.
Example using the switch/case name aliases
{#switch person.name}
  {#case 'John'} 1
    Hey John!
  {#case 'Mary'}
    Hey Mary!
{/switch}
1 case is an alias for is.

将经过测试的值解析为枚举时,它会得到特殊的处理。is/ case 块的参数不会按表达式求值,而是与 toString() 在经过测试的值上调用的结果进行比较。

A tested value that resolves to an enum is handled specifically. The parameters of an is/case block are not evaluated as expressions but compared with the result of toString() invocation upon the tested value.

{#when machine.status}
  {#is ON}
    It's running. 1
  {#is in OFF BROKEN}
    It's broken or OFF. 2
{/when}
1 This block is executed if machine.status.toString().equals("ON").
2 This block is executed if machine.status.toString().equals("OFF") or machine.status.toString().equals("BROKEN").

如果被测值具有可用的类型信息并且解析为枚举类型,则验证枚举常量。

An enum constant is validated if the tested value has a type information available and resolves to an enum type.

is/case 块条件中,支持以下运算符:

The following operators are supported in is/case block conditions:

Operator Aliases Example

not equal

!=, not, ne

{#is not 10},{#case != 10}

greater than

gt, >

{#case le 10}

greater than or equal to

ge, >=

{#is >= 10}

less than

lt, <

{#is < 10}

less than or equal to

le, <=

{#case le 10}

in

in

{#is in 'foo' 'bar' 'baz'}

not in

ni,!in

{#is !in 1 2 3}

Let Section

此部分允许您定义命名的局部变量:

This section allows you to define named local variables:

{#let myParent=order.item.parent isActive=false age=10 price=(order.price + 10)} 12
  <h1>{myParent.name}</h1>
  Is active: {isActive}
  Age: {age}
{/let} 3
1 The local variable is initialized with an expression that can also represent a literals, i.e. isActive=false and age=10.
2 The infix notation is only supported if parentheses are used for grouping, e.g. price=(order.price + 10) is equivalent to price=order.price.plus(10).
3 Keep in mind that the variable is not available outside the let section that defines it.

如果部分参数的键(例如局部变量名称)以 ? 结尾,则只有当没有 ? 后缀的键解析为 null"not found" 时才设置局部变量:

If a key of a section parameter, such as the name of the local variable, ends with a ?, then the local variable is only set if the key without the ? suffix resolves to null or "not found":

{#let enabled?=true} 1 2
  {#if enabled}ON{/if}
{/let}
1 true is effectively a default value that is only used if the parent scope does not define enabled already.
2 enabled?=true is a short version of enabled=enabled.or(true).

此部分标记还可以在 set 别名下注册:

This section tag is also registered under the set alias:

{#set myParent=item.parent price=item.price}
  <h1>{myParent.name}</h1>
  <p>Price: {price}
{/set}

With Section

此部分可用于设置当前内容对象。这对于简化模版结构非常有用:

This section can be used to set the current context object. This could be useful to simplify the template structure:

{#with item.parent}
  <h1>{name}</h1>  1
  <p>{description}</p> 2
{/with}
1 The name will be resolved against the item.parent.
2 The description will be also resolved against the item.parent.

请注意,with 部分不能用于定义 Type-safe ExpressionsType-safe Templates 或模版。原因在于,这阻止了 Qute 验证嵌套表达式。如果可能,请使用声明显式绑定的 {#let} 部分替换 with 部分:

Note that the with section should not be used in Type-safe Templates or templates that define Type-safe Expressions. The reason is that it prevents Qute from validating the nested expressions. If possible, replace it with the {#let} section which declares an explicit binding:

{#let it=item.parent}
  <h1>{it.name}</h1>
  <p>{it.description}</p>
{/let}

当我们想要避免多次调用时,此部分也可能派上用场:

This section might also come in handy when we’d like to avoid multiple expensive invocations:

{#with item.callExpensiveLogicToGetTheValue(1,'foo',bazinga)}
  {#if this is "fun"} 1
    <h1>Yay!</h1>
  {#else}
    <h1>{this} is not fun at all!</h1>
  {/if}
{/with}
1 this is the result of item.callExpensiveLogicToGetTheValue(1,'foo',bazinga). The method is only invoked once even though the result may be used in multiple expressions.

Include Section

此部分可用于包括另一个模板,并可能替代模板的一些部分(参见下面的 template inheritance)。

This section can be used to include another template and possibly override some parts of the template (see the template inheritance below).

Simple Example
<html>
<head>
<meta charset="UTF-8">
<title>Simple Include</title>
</head>
<body>
  {#include foo limit=10 /} 12
</body>
</html>
1 Include a template with id foo. The included template can reference data from the current context.
2 It’s also possible to define optional parameters that can be used in the included template.

Template inheritance 便于重用模板布局。

Template inheritance makes it possible to reuse template layouts.

Template "base"
<html>
<head>
<meta charset="UTF-8">
<title>{#insert title}Default Title{/}</title> 1
</head>
<body>
  {#insert}No body!{/} 2
</body>
</html>
1 insert sections are used to specify parts that could be overridden by a template that includes the given template.
2 An insert section may define the default content that is rendered if not overridden. If there is no name supplied then the main block of the relevant {#include} section is used.
Template "detail"
{#include base} 1
  {#title}My Title{/title} 2
  <div> 3
    My body.
  </div>
{/include}
1 include section is used to specify the extended template.
2 Nested blocks are used to specify the parts that should be overridden.
3 The content of the main block is used for an {#insert} section with no name parameter specified.

部分块还可以定义一个可选的结束标记 -{/title}

Section blocks can also define an optional end tag - {/title}.

User-defined Tags

用户定义的标记可用于包含 tag template,也可以选择传递一些参数并可能覆盖模板的一些部分。让我们假设我们有一个名为 itemDetail.html 的标记模板:

User-defined tags can be used to include a tag template, optionally pass some arguments and possibly override some parts of the template. Let’s suppose we have a tag template called itemDetail.html:

{#if showImage} 1
  {it.image} 2
  {nested-content} 3
{/if}
1 showImage is a named parameter.
2 it is a special key that is replaced with the first unnamed parameter of the tag.
3 (optional) nested-content is a special key that will be replaced by the content of the tag.

在 Quarkus 中,src/main/resources/templates/tags 中的所有文件都会自动注册和监控。对于 Qute 独立模式,您需要使用名称 itemDetail.html 放置已解析的模板,并向引擎注册一个相关的 UserTagSectionHelper

In Quarkus, all files from the src/main/resources/templates/tags are registered and monitored automatically. For Qute standalone, you need to put the parsed template under the name itemDetail.html and register a relevant UserTagSectionHelper to the engine:

Engine engine = Engine.builder()
                   .addSectionHelper(new UserTagSectionHelper.Factory("itemDetail","itemDetail.html"))
                   .build();
engine.putTemplate("itemDetail.html", engine.parse("..."));

然后,我们可以像这样调用标记:

Then, we can call the tag like this:

<ul>
{#for item in items}
  <li>
  {#itemDetail item showImage=true} 1
    = <b>{item.name}</b> 2
  {/itemDetail}
  </li>
{/for}
</ul>
1 item is resolved to an iteration element and can be referenced using the it key in the tag template.
2 Tag content injected using the nested-content key in the tag template.

默认情况下,标签模板无法引用父级上下文中的数据。Qute 以 _isolated_的方式执行该标签,即无法访问调用标签的模板的上下文。但是,有时更改默认行为并禁用隔离可能会很有用。在这种情况下,只需在调用位置添加 _isolated=false`或 `_unisolated`参数,例如 `{#itemDetail item showImage=true _isolated=false /}`或 `{#itemDetail item showImage=true _unisolated /}

By default, a tag template cannot reference the data from the parent context. Qute executes the tag as an isolated template, i.e. without access to the context of the template that calls the tag. However, sometimes it might be useful to change the default behavior and disable the isolation. In this case, just add _isolated=false or _unisolated argument to the call site, for example {#itemDetail item showImage=true _isolated=false /} or {#itemDetail item showImage=true _unisolated /}.

Arguments

命名的参数可以直接在标签模板中访问。但是,第一个参数不需要定义一个名称,并且它可以使用 it`别名进行访问。此外,如果一个参数没有定义名称且其值为一个单独的标识符,例如 `foo,则该名称会默认设为该值标识符,例如: {#myTag foo /}`变为 `{#myTag foo=foo /}。换而言之,参数值 `foo`已解决,并且可以使用标签模板中的 `{foo}`进行访问。

Named arguments can be accessed directly in the tag template. However, the first argument does not need to define a name and it can be accessed using the it alias. Furthermore, if an argument does not have a name defined and the value is a single identifier, such as foo, then the name is defaulted to the value identifier, e.g. {#myTag foo /} becomes {#myTag foo=foo /}. In other words, the argument value foo is resolved and can be accessed using {foo} in the tag template.

如果一个参数没有名称并且其值为一个单独的单词字符串文字,例如 "foo",则该名称将被默认为该值,引号也会被去掉,例如,{#myTag "foo" /}`变为 `{#myTag foo="foo" /}

If an argument does not have a name and the value is a single word string literal , such as "foo", then the name is defaulted and quotation marks are removed, e.g. {#myTag "foo" /} becomes {#myTag foo="foo" /}.

可以使用 `_args`别名在标签中访问 `io.quarkus.qute.UserTagSectionHelper.Arguments`元数据。

io.quarkus.qute.UserTagSectionHelper.Arguments metadata are accessible in a tag using the _args alias.

  • _args.size - returns the actual number of arguments passed to a tag

  • _args.empty/_args.isEmpty - returns true if no arguments are passed

  • _args.get(String name) - returns the argument value of the given name or null

  • _args.filter(String…​) - returns the arguments matching the given names

  • _args.filterIdenticalKeyValue - returns the arguments with the name equal to the value; typically foo from {#test foo="foo" bar=true} or {#test "foo" bar=true /}

  • _args.skip(String…​) - returns only the arguments that do not match the given names

  • _args.skipIdenticalKeyValue - returns only the arguments with the name not equal to the value; typically bar from {#test foo="foo" bar=true /}

  • _args.skipIt - returns all arguments except for the first unnamed argument; typically bar from {#test foo bar=true /}

  • _args.asHtmlAttributes - renders the arguments as HTML attributes; e.g. foo="true" readonly="readonly"; the arguments are sorted by name in alphabetical order and the ’`, ", <, >, & characters are escaped

_args`也是 `java.util.Map.Entry`的序列: `{#each _args}{it.key}={it.value}{/each}

_args is also iterable of java.util.Map.Entry: {#each _args}{it.key}={it.value}{/each}.

例如,我们可以使用 `{#test 'Martin' readonly=true /}`来调用下面定义的 user 标签。

For example, we can call the user tag defined below with {#test 'Martin' readonly=true /}.

tags/test.html
{it} 1
{readonly} 2
{_args.filter('readonly').asHtmlAttributes} 3
1 it is replaced with the first unnamed parameter of the tag.
2 readonly is a named parameter.
3 _args represents arguments metadata.

结果如下:

The result would be:

Martin
true
readonly="true"
Inheritance

用户标记还可以像常规 {#include} 节一样使用模板继承。

User tags can also make use of the template inheritance in the same way as regular {#include} sections do.

Tag myTag
This is {#insert title}my title{/title}! 1
1 insert sections are used to specify parts that could be overridden by a template that includes the given template.
Tag Call Site
<p>
  {#myTag}
    {#title}my custom title{/title} 1
  {/myTag}
</p>
1 The result would be something like <p>This is my custom title!</p>.

Fragments

片段代表模板的一部分,可以当作单独的模板来处理,即将其单独渲染。引入此功能的主要动机之一是支持类似于 htmx fragments 的用例。

A fragment represents a part of the template that can be treated as a separate template, i.e. rendered separately. One of the main motivations to introduce this feature was to support use cases like htmx fragments.

片段可以使用 {#fragment} 节定义。每个片段都有仅能包含字母数字和下划线的标识符。

Fragments can be defined with the {#fragment} section. Each fragment has an identifier that can only consist of alphanumeric characters and underscores.

请注意,片段标识符在模板中必须唯一。

Note that a fragment identifier must be unique in a template.

Fragment Definition in item.html
{@org.acme.Item item}
{@java.util.List<String> aliases}

<h1>Item - {item.name}</h1>

<p>This document contains a detailed info about an item.</p>

{#fragment id=item_aliases} 1
<h2>Aliases</h2>
<ol>
    {#for alias in aliases}
    <li>{alias}</li>
    {/for}
</ol>
{/fragment}
1 Defines a fragment with identifier item_aliases. Note that only alphanumeric characters and underscores can be used in the identifier.

您可以通过 io.quarkus.qute.Template.getFragment(String) 方法以编程方式获取片段。

You can obtain a fragment programmatically via the io.quarkus.qute.Template.getFragment(String) method.

Obtaining a Fragment
@Inject
Template item;

String useTheFragment() {
   return item.getFragment("item_aliases") 1
            .data("aliases", List.of("Foo","Bar")) 2
            .render();
}
1 Obtains the template fragment with identifier item_aliases.
2 Make sure the data are set correctly.

上述代码段应渲染类似于如下内容:

The snippet above should render something like:

<h2>Aliases</h2>
<ol>
    <li>Foo</li>
    <li>Bar</li>
</ol>

在 Quarkus 中,还可以定义 type-safe fragment

In Quarkus, it is also possible to define a type_safe_fragments.

您还可以使用 {#include} 节将片段包括在另一个模板中或定义该片段的模板中。

You can also include a fragment with an {#include} section inside another template or the template that defines the fragment.

Including a Fragment in user.html
<h1>User - {user.name}</h1>

<p>This document contains a detailed info about a user.</p>

{#include item$item_aliases aliases=user.aliases /} 12
1 A template identifier that contains a dollar sign $ denotes a fragment. The item$item_aliases value is translated as: Use the fragment item_aliases from the template item.
2 The aliases parameter is used to pass the relevant data. We need to make sure that the data are set correctly. In this particular case the fragment will use the expression user.aliases as the value of aliases in the {#for alias in aliases} section.

如果要从同一模板引用片段,请跳过 $ 之前的部分,即类似于 {#include $item_aliases /} 的内容。

If you want to reference a fragment from the same template, skip the part before $, i.e. something like {#include $item_aliases /}.

您可以指定 {#include item$item_aliases _ignoreFragments=true /} 以禁用此功能,即模板标识符中的美元符号 $ 不会导致片段查找。

You can specify {#include item$item_aliases _ignoreFragments=true /} in order to disable this feature, i.e. a dollar sign $ in the template identifier does not result in a fragment lookup.

Hidden Fragments

默认情况下,片段通常作为原始模板的一部分进行渲染。然而,有时将片段标记为 rendered=falsehidden 可能会很有用。一个有趣的用例是可以在定义它的模板中多次使用的片段。

By default, a fragment is normally rendered as a part of the original template. However, sometimes it might be useful to mark a fragment as hidden with rendered=false. An interesting use case would be a fragment that can be used multiple-times inside the template that defines it.

Fragment Definition in item.html
{#fragment id=strong rendered=false} 1
<strong>{val}</strong>
{/fragment}

<h1>My page</h1>
<p>This document
{#include $strong val='contains' /} 2
a lot of
{#include $strong val='information' /} 3
!</p>
1 Defines a hidden fragment with identifier strong. In this particular case, we use the false boolean literal as the value of the rendered parameter. However, it’s possible to use any expression there.
2 Include the fragment strong and pass the value. Note the syntax $strong which is translated to include the fragment strong from the current template.
3 Include the fragment strong and pass the value.

以上代码片段呈现类似如下内容:

The snippet above renders something like:

<h1>My page</h1>
<p>This document
<strong>contains</strong>
a lot of
<strong>information</strong>
!</p>

Eval Section

此部分可用于动态解析和评估模板。此行为与 Include Section非常相似,但:

This section can be used to parse and evaluate a template dynamically. The behavior is very similar to Include Section but:

  1. The template content is passed directly, i.e. not obtained via an io.quarkus.qute.TemplateLocator,

  2. It’s not possible to override parts of the evaluated template.

{#eval myData.template name='Mia' /} 123
1 The result of myData.template will be used as the template. The template is executed with the Current Context, i.e. can reference data from the template it’s included into.
2 It’s also possible to define optional parameters that can be used in the evaluated template.
3 The content of the section is always ignored.

每次执行部分时,都会解析并评估已评估的模板。换言之,无法缓存已解析的值以节省资源并优化性能。

The evaluated template is parsed and evaluated every time the section is executed. In other words, it is not possible to cache the parsed value to conserve resources and optimize performance.

Cached Section

有时缓存那些很少更改的模板部分是实用的。为了使用缓存功能,注册并配置内置 io.quarkus.qute.CacheSectionHelper.Factory

Sometimes it’s practical to cache parts of the template that rarely change. In order to use the caching capability, register and configure the built-in io.quarkus.qute.CacheSectionHelper.Factory:

// A simple map-based cache
ConcurrentMap<String, CompletionStage<ResultNode>> map = new ConcurrentHashMap<>();
engineBuilder
    .addSectionHelper(new CacheSectionHelper.Factory(new Cache() {
        @Override
        public CompletionStage<ResultNode> getValue(String key,
           Function<String, CompletionStage<ResultNode>> loader) {
              return map.computeIfAbsent(key, k -> loader.apply(k));
           }
     })).build();

如果 quarkus-cache 扩展存在于某个 Quarkus 应用程序中,则 CacheSectionHelper 会注册并配置 automatically。缓存的名称是 qute-cache。可以 in a standard way 配置它,甚至 @Inject @CacheName("qute-cache") Cache 可以以编程方式管理它。

If the quarkus-cache extension is present in a Quarkus application then the CacheSectionHelper is registered and configured automatically. The name of the cache is qute-cache. It can be configured in a standard way and even managed programmatically via @Inject @CacheName("qute-cache") Cache.

然后,{#cached} 部分可以在模板中使用:

Then, the {#cached} section can be used in a template:

{#cached} 1
 Result: {service.findResult} 2
{/cached}
1 If the key param is not used then all clients of the template share the same cached value.
2 This part of the template will be cached and the {service.findResult} expression is only evaluated when a cache entry is missing/invalidated.
{#cached key=currentUser.username} 1
 User-specific result: {service.findResult(currentUser)}
{/cached}
1 The key param is set and so a different cached value is used for each result of the {currentUser.username} expression.

当使用缓存时,通常非常重要的是,要能够通过特定键使缓存条目失效。在 Qute 中,缓存条目的键是一个 String,它由模板名称、起始 {#cached} 标记的行和列以及可选的 key 参数构成: {TEMPLATE}:{LINE}:{COLUMN}_{KEY}。例如,foo.html:10:1_alpha 是模板 foo.html 中缓存部分的键,{#cached} 标记位于第 10 行第 1 列。而且,可选的 key 参数解析为 alpha

When using cache it’s very often important to have the option to invalidate a cache entry by the specific key. In Qute the key of a cache entry is a String that consist of the template name, line and column of the starting {#cached} tag and the optional key parameter: {TEMPLATE}:{LINE}:{COLUMN}_{KEY}. For example, foo.html:10:1_alpha is a key for the cached section in a template foo.html, the {#cached} tag is placed on the line 10, column 1. And the optional key parameter resolves to alpha.

Rendering Output

TemplateInstance 提供了几种触发渲染并消耗结果的方法。最简单的途径由 TemplateInstance.render() 表示。此方法触发同步渲染,即在渲染完成之前当前线程都会被阻塞,并且会返回输出。相反,TemplateInstance.renderAsync() 返回一个 CompletionStage<String>,在渲染完成时,它就会完成。

TemplateInstance provides several ways to trigger the rendering and consume the result. The most straightforward approach is represented by TemplateInstance.render(). This method triggers a synchronous rendering, i.e. the current thread is blocked until the rendering is finished, and returns the output. By contrast, TemplateInstance.renderAsync() returns a CompletionStage<String> which is completed when the rendering is finished.

TemplateInstance.renderAsync() Example
template.data(foo).renderAsync().whenComplete((result, failure) -> { 1
   if (failure == null) {
      // consume the output...
   } else {
      // process failure...
   }
};
1 Register a callback that is executed once the rendering is finished.

另外还有返回 Mutiny 类型的两种方法。TemplateInstance.createUni() 返回一个新的 Uni<String> 对象。如果您调用 createUni(),模板不会立即呈现出来。相反,每次调用 Uni.subscribe() 都会触发模板的新渲染。

There are also two methods that return Mutiny types. TemplateInstance.createUni() returns a new Uni<String> object. If you call createUni() the template is not rendered right away. Instead, every time Uni.subscribe() is called a new rendering of the template is triggered.

TemplateInstance.createUni() Example
template.data(foo).createUni().subscribe().with(System.out::println);

TemplateInstance.createMulti() 返回一个新的 Multi<String> 对象。每个项目表示呈现模板的一部分/块。同样,createMulti() 不会触发渲染。相反,每次订阅者触发计算时,模板将再次呈现。

TemplateInstance.createMulti() returns a new Multi<String> object. Each item represents a part/chunk of the rendered template. Again, createMulti() does not trigger rendering. Instead, every time a computation is triggered by a subscriber, the template is rendered again.

TemplateInstance.createMulti() Example
template.data(foo).createMulti().subscribe().with(buffer:append,buffer::flush);

模板呈现分为两个阶段。在第一阶段(异步阶段),模板中的所有表达式都得到解析并且 result tree 已建立。在第二阶段(同步阶段),结果树是 materialized,即,结果节点逐个发出被特定消耗者消耗/缓冲的块。

The template rendering is divided in two phases. During the first phase, which is asynchronous, all expressions in the template are resolved and a result tree is built. In the second phase, which is synchronous, the result tree is materialized, i.e. one by one the result nodes emit chunks that are consumed/buffered by the specific consumer.

Engine Configuration

Value Resolvers

值解析器在估算表达式时使用。可以通过 EngineBuilder.addValueResolver() 以编程方式注册一个自定义的 io.quarkus.qute.ValueResolver

Value resolvers are used when evaluating expressions. A custom io.quarkus.qute.ValueResolver can be registered programmatically via EngineBuilder.addValueResolver().

ValueResolver Builder Example
engineBuilder.addValueResolver(ValueResolver.builder()
    .appliesTo(ctx -> ctx.getBase() instanceof Long && ctx.getName().equals("tenTimes"))
    .resolveSync(ctx -> (Long) ctx.getBase() * 10)
    .build());

Template Locator

可以通过模板定位器手动或自动注册一个模板。只要调用 Engine.getTemplate() 方法并且引擎没有存储在缓存中的给定 id 的模板时,就会使用这些定位器。定位器在读取模板内容时负责使用正确的字符编码。

A template can be either registered manually or automatically via a template locator. The locators are used whenever the Engine.getTemplate() method is called, and the engine has no template for a given id stored in the cache. The locator is responsible for using the correct character encoding when reading the contents of a template.

在 Quarkus 中,来自 src/main/resources/templates 的所有模板都会自动定位,并且使用通过 quarkus.qute.default-charset 指定的编码集(默认情况下使用 UTF-8)。可以通过使用 @Locate 注释来 registered 自定义定位器。

In Quarkus, all templates from the src/main/resources/templates are located automatically and the encoding set via quarkus.qute.default-charset (UTF-8 by default) is used. Custom locators can be template-locator-registration by using the @Locate annotation.

Content Filters

内容过滤器可用于在解析前修改模板内容。

Content filters can be used to modify the template contents before parsing.

Content Filter Example
engineBuilder.addParserHook(new ParserHook() {
    @Override
    public void beforeParsing(ParserHelper parserHelper) {
        parserHelper.addContentFilter(contents -> contents.replace("${", "$\\{")); 1
    }
});
1 Escape all occurrences of ${.

Strict Rendering

严格渲染模式使开发人员能够捕获由错字和无效表达式引起的隐秘错误。如果启用,则任何无法解析(即被估算为 io.quarkus.qute.Results.NotFound 的实例)的表达式始终会导致 TemplateException,并且渲染将被中止。NotFound 值被视为错误,因为它基本上意味着没有值解析器能够正确解析表达式。

The strict rendering enables the developers to catch insidious errors caused by typos and invalid expressions. If enabled then any expression that cannot be resolved, i.e. is evaluated to an instance of io.quarkus.qute.Results.NotFound, will always result in a TemplateException and the rendering is aborted. A NotFound value is considered an error because it basically means that no value resolver was able to resolve the expression correctly.

不过,null 是有效的数值。它被视为 falsy,如 If Section 中所述且不产生任何输出。

null is a valid value though. It is considered falsy as described in If Section and does not produce any output.

严格渲染模式默认启用。然而,您可以通过 io.quarkus.qute.EngineBuilder.strictRendering(boolean) 禁用此功能。

Strict rendering is enabled by default. However, you can disable this functionality via io.quarkus.qute.EngineBuilder.strictRendering(boolean).

相反,在 Quarkus 中可以使用专用配置属性:quarkus.qute.strict-rendering

In Quarkus, a dedicated config property can be used instead: quarkus.qute.strict-rendering.

如果您确实需要使用可能导致“找不到”错误的表达式,则可以使用 default valuessafe expressions 来压制错误。如果表达式的前面部分无法解析或解析为 null,则会使用默认值。可以使用埃尔维斯运算符来输出默认值:{foo.bar ?: 'baz'},它实际等同于以下虚拟方法:{foo.bar.or('baz')}。安全表达式的结尾带有 ?? 后缀,并且如果表达式无法解析,则产生 null。它可能非常有用,例如在 {#if} 部分:{#if valueNotFound??}Only rendered if valueNotFound is truthy!{/if}。事实上,?? 只是 .or(null) 的简写形式,即 {#if valueNotFound??} 将变成 {#if valueNotFound.or(null)}

If you really need to use an expression which can potentially lead to a "not found" error, you can use default values and safe expressions in order to suppress the error. A default value is used if the previous part of an expression cannot be resolved or resolves to null. You can use the elvis operator to output the default value: {foo.bar ?: 'baz'}, which is effectively the same as the following virtual method: {foo.bar.or('baz')}. A safe expression ends with the ?? suffix and results in null if the expression cannot be resolved. It can be very useful e.g. in {#if} sections: {#if valueNotFound??}Only rendered if valueNotFound is truthy!{/if}. In fact, ?? is just a shorthand notation for .or(null), i.e. {#if valueNotFound??} becomes {#if valueNotFound.or(null)}.

Quarkus Integration

如果您要在 Quarkus 应用程序中使用 Qute,请将以下依赖项添加到您的项目中:

If you want to use Qute in your Quarkus application, add the following dependency to your project:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-qute</artifactId>
</dependency>

在 Quarkus 中,提供了预先配置好的引擎实例,并且能够注入 - @ApplicationScoped 范围的 bean、io.quarkus.qute.Engine 类型 bean 和 @Default 限定符都已自动注册。此外,位于 src/main/resources/templates 目录中的所有模板都经过验证,可以轻松注入。

In Quarkus, a preconfigured engine instance is provided and available for injection - a bean with scope @ApplicationScoped, bean type io.quarkus.qute.Engine and qualifier @Default is registered automatically. Moreover, all templates located in the src/main/resources/templates directory are validated and can be easily injected.

import io.quarkus.qute.Engine;
import io.quarkus.qute.Template;
import io.quarkus.qute.Location;

class MyBean {

    @Inject
    Template items; 1

    @Location("detail/items2_v1.html") 2
    Template items2;

    @Inject
    Engine engine; 3
}
1 If there is no Location qualifier provided, the field name is used to locate the template. In this particular case, the container will attempt to locate a template with path src/main/resources/templates/items.html.
2 The Location qualifier instructs the container to inject a template from a path relative from src/main/resources/templates. In this case, the full path is src/main/resources/templates/detail/items2_v1.html.
3 Inject the configured Engine instance.

Engine Customization

可以通过运行时的 CDI 观察者方法中的 EngineBuilder 方法手动注册附加组件:

Additional components can be registered manually via EngineBuilder methods in a CDI observer method at runtime:

import io.quarkus.qute.EngineBuilder;

class MyBean {

    void configureEngine(@Observes EngineBuilder builder) {
       // Add a custom section helper
       builder.addSectionHelper(new CustomSectionFactory());
       // Add a custom value resolver
       builder.addValueResolver(ValueResolver.builder()
                .appliesTo(ctx -> ctx.getBase() instanceof Long && ctx.getName().equals("tenTimes"))
                .resolveSync(ctx -> (Long) ec.getBase() * 10)
                .build());
    }
}

但在此特定情况下,在构建期间进行验证时会忽略章节帮助程序工厂。如果您想注册参与构建期间模板验证的章节,那就使用方便的 @EngineConfiguration 注解:

However, in this particular case the section helper factory is ignored during validation at build time. If you want to register a section that participates in validation of templates at build time then use the convenient @EngineConfiguration annotation:

import io.quarkus.qute.EngineConfiguration;
import io.quarkus.qute.SectionHelper;
import io.quarkus.qute.SectionHelperFactory;

@EngineConfiguration 1
public class CustomSectionFactory implements SectionHelperFactory<CustomSectionFactory.CustomSectionHelper> {

    @Inject
    Service service; 2

    @Override
    public List<String> getDefaultAliases() {
        return List.of("custom");
    }

    @Override
    public ParametersInfo getParameters() {
        // Param "foo" is required
        return ParametersInfo.builder().addParameter("foo").build(); 3
    }

    @Override
    public Scope initializeBlock(Scope outerScope, BlockInfo block) {
        block.addExpression("foo", block.getParameter("foo"));
        return outerScope;
    }


    @Override
    public CustomSectionHelper initialize(SectionInitContext context) {
        return new CustomSectionHelper();
    }

    class CustomSectionHelper implements SectionHelper {

        private final Expression foo;

        public CustomSectionHelper(Expression foo) {
            this.foo = foo;
        }

        @Override
        public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {
            return context.evaluate(foo).thenApply(fooVal -> new SingleResultNode(service.getValueForFoo(fooVal))); 4
        }
    }
}
1 A SectionHelperFactory annotated with @EngineConfiguration is used during validation of templates at build time and automatically registered at runtime (a) as a section factory and (b) as a CDI bean.
2 A CDI bean instance is used at runtime - this means that the factory can define injection points
3 Validate that foo parameter is always present; e.g. {#custom foo='bar' /} is ok but {#custom /} results in a build failure.
4 Use the injected Service during rendering.

@EngineConfiguration 注解还可用于注册 ValueResolverNamespaceResolverParserHook 组件。

The @EngineConfiguration annotation can be also used to register ValueResolver, NamespaceResolver and ParserHook components.

Template Locator Registration

注册 template locators 的最简单方法是使它们成为 CDI 豆。由于在模板验证完成后构建时间内不存在自定义定位器,您需要通过 @Locate 注解禁用验证。

The easiest way to register template-locator is to make them CDI beans. As the custom locator is not available during the build time when a template validation is done, you need to disable the validation via the @Locate annotation.

Custom Locator Example
@Locate("bar.html") 1
@Locate("foo.*") 2
public class CustomLocator implements TemplateLocator {

    @Inject 3
    MyLocationService myLocationService;

    @Override
    public Optional<TemplateLocation> locate(String templateId) {

        return myLocationService.getTemplateLocation(templateId);
    }

}
1 A template named bar.html is located by the custom locator at runtime.
2 A regular expression foo.* disables validation for templates whose name is starting with foo.
3 Injection fields are resolved as template locators annotated with @Locate are registered as singleton session beans.

Template Variants

根据内容协商渲染特定模板的变体有时很有用。这可以通过设置一个通过 TemplateInstance.setVariant() 的特殊属性来完成:

Sometimes it’s useful to render a specific variant of the template based on the content negotiation. This can be done by setting a special attribute via TemplateInstance.setVariant():

class MyService {

    @Inject
    Template items; 1

    @Inject
    ItemManager manager;

    String renderItems() {
       return items.data("items", manager.findItems())
                   .setVariant(new Variant(Locale.getDefault(), "text/html", "UTF-8"))
                   .render();
    }
}

使用 quarkus-rest-qutequarkus-resteasy-qute 时,内容协商将自动执行。有关详细信息,请参阅 [id="resteasy_integration"] REST Integration 部分。

When using quarkus-rest-qute or quarkus-resteasy-qute the content negotiation is performed automatically. For more information, see the [id="resteasy_integration"] REST Integration section.

Injecting Beans Directly In Templates

加有 @Named 注解的 CDI 豆可以通过 cdi 和/或 inject 命名空间引用到任何模板中:

A CDI bean annotated with @Named can be referenced in any template through cdi and/or inject namespaces:

{cdi:personService.findPerson(10).name} 1
{inject:foo.price} 2
1 First, a bean with name personService is found and then used as the base object.
2 First, a bean with name foo is found and then used as the base object.

@Named @Dependent 豆在单个渲染操作的模板中的所有表达式中共享,并在渲染完成后销毁。

@Named @Dependent beans are shared across all expressions in a template for a single rendering operation, and destroyed after the rendering finished.

在构建期间验证具有 cdiinject 命名空间的所有表达式。

All expressions with cdi and inject namespaces are validated during build.

对于表达式 cdi:personService.findPerson(10).name,注入 bean 的实现类必须声明 findPerson 方法或必须存在匹配的 template extension method

For the expression cdi:personService.findPerson(10).name, the implementation class of the injected bean must either declare the findPerson method or a matching template_extension_methods must exist.

对于表达式 inject:foo.price,注入 bean 的实现类必须具有 price 属性(例如 getPrice() 方法)或必须存在匹配的 template extension method

For the expression inject:foo.price, the implementation class of the injected bean must either have the price property (e.g. a getPrice() method) or a matching template_extension_methods must exist.

对所有标注有 @Named 的 bean 还将生成 ValueResolver,以便可以无需反射地访问它的属性。

A ValueResolver is also generated for all beans annotated with @Named so that it’s possible to access its properties without reflection.

如果你的应用程序提供 HTTP requests,还可以通过 inject 命名空间(例如 {inject:vertxRequest.getParam('foo')})注入 io.vertx.core.http.HttpServerRequest

If your application serves HTTP requests you can also inject the current io.vertx.core.http.HttpServerRequest via the inject namespace, e.g. {inject:vertxRequest.getParam('foo')}.

Type-safe Expressions

模板表达式可以选择类型安全。这意味着某个表达式将针对现有 Java 类型和模板扩展方法进行验证。如果找到无效/不正确的表达式,则构建失败。

Template expressions can be optionally type-safe. Which means that an expression is validated against the existing Java types and template extension methods. If an invalid/incorrect expression is found then the build fails.

例如,如果有一个表达式 item.name,其中 item 映射到 org.acme.Item,则 Item 必须具有属性 name 或必须存在匹配的模板扩展方法。

For example, if there is an expression item.name where item maps to org.acme.Item then Item must have a property name or a matching template extension method must exist.

可选 parameter declaration 用于将 Java 类型绑定到其第一部分与参数名称匹配的表达式。参数声明直接在模板中指定。

An optional parameter declaration is used to bind a Java type to expressions whose first part matches the parameter name. Parameter declarations are specified directly in a template.

Java 类型应始终用 fully qualified name 标识,除非它是来自 java.lang 包的 JDK 类型 - 如果是这样,则包名称是可选的。支持参数化类型,但始终忽略通配符 - 仅考虑上限/下限。例如,参数声明 {@java.util.List<? extends org.acme.Foo> list} 被识别为 {@java.util.List<org.acme.Foo> list}。类型变量不会以特殊方式处理,并且不应使用。

A Java type should be always identified with a fully qualified name unless it’s a JDK type from the java.lang package - in this case, the package name is optional. Parameterized types are supported, however wildcards are always ignored - only the upper/lower bound is taken into account. For example, the parameter declaration {@java.util.List<? extends org.acme.Foo> list} is recognized as {@java.util.List<org.acme.Foo> list}. Type variables are not handled in a special way and should never be used.

Parameter Declaration Example
{@org.acme.Foo foo} 1
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Qute Hello</title>
</head>
<body>
  <h1>{title}</h1> 2
  Hello {foo.message.toLowerCase}! 3 4
</body>
</html>
1 Parameter declaration - maps foo to org.acme.Foo.
2 Not validated - not matching a param declaration.
3 This expression is validated. org.acme.Foo must have a property message or a matching template extension method must exist.
4 Likewise, the Java type of the object resolved from foo.message must have a property toLowerCase or a matching template extension method must exist.

将自动为参数声明中使用的所有类型生成一个值解析器,以便可以无需反射地访问它的属性。

A value resolver is automatically generated for all types used in parameter declarations so that it’s possible to access its properties without reflection.

type-safe templates 的方法参数会自动变成参数声明。

Method parameters of typesafe_templates are automatically turned into parameter declarations.

请注意,区段可以覆盖那些原本将匹配参数声明的名称:

Note that sections can override names that would otherwise match a parameter declaration:

{@org.acme.Foo foo}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Qute Hello</title>
</head>
<body>
  <h1>{foo.message}</h1> 1
  {#for foo in baz.foos}
    <p>Hello {foo.message}!</p> 2
  {/for}
</body>
</html>
1 Validated against org.acme.Foo.
2 Not validated - foo is overridden in the loop section.

参数声明可以在键之后指定 default value。键和默认值用等号分隔: {@int age=10}。如果参数键解析为 null 或未找到,模板中将使用默认值。

A parameter declaration may specify the default value after the key. The key and the default value are separated by an equals sign: {@int age=10}. The default value is used in the template if the parameter key resolves to null or is not found.

例如,如果有一个参数声明 {@String foo="Ping"},并且未找到 foo,则可以使用 {foo},并且输出将是 Ping。另一方面,如果设置了该值(例如通过 TemplateInstance.data("foo", "Pong")),则 {foo} 的输出将是 Pong

For example, if there’s a parameter declaration {@String foo="Ping"} and foo is not found then you can use {foo} and the output will be Ping. On the other hand, if the value is set (e.g. via TemplateInstance.data("foo", "Pong")) then the output of {foo} will be Pong.

默认值类型必须可以分配给参数声明类型。例如,请参见导致构建失败的不正确参数声明: {@org.acme.Foo foo=1}

The type of a default value must be assignable to the type of the parameter declaration. For example, see the incorrect parameter declaration that results in a build failure: {@org.acme.Foo foo=1}.

默认值实际上是一个 expression。因此,默认值不一定要是文字(例如 42true)。例如,您可以利用 @TemplateEnum 并将枚举常量指定为参数声明的默认值: {@org.acme.MyEnum myEnum=MyEnum:FOO}。但是,默认值不支持中缀符号,除非圆括号用于分组,例如 {@org.acme.Foo foo=(foo1 ?: foo2)}

The default value is actually an expressions. So the default value does not have to be a literal (such as 42 or true). For example, you can leverage the @TemplateEnum and specify an enum constant as a default value of a parameter declaration: {@org.acme.MyEnum myEnum=MyEnum:FOO}. However, the infix notation is not supported in default values unless the parentheses are used for grouping, e.g. {@org.acme.Foo foo=(foo1 ?: foo2)}.

默认值类型不在 Qute standalone 中验证。

The type of a default value is not validated in standalone.

More Parameter Declarations Examples
{@int pages} 1
{@java.util.List<String> strings} 2
{@java.util.Map<String,? extends Number> numbers} 3
{@java.util.Optional<?> param} 4
{@String name="Quarkus"} 5
1 A primitive type.
2 String is replaced with java.lang.String: {@java.util.List<java.lang.String> strings}
3 The wildcard is ignored and the upper bound is used instead: {@java.util.Map<String,Number>}
4 The wildcard is ignored and the java.lang.Object is used instead: {@java.util.Optional<java.lang.Object>}
5 The type is java.lang.String, the key is name and the default value is Quarkus.

Type-safe Templates

可以在 Java 代码中定义类型安全模板。类型安全模板的参数会自动转换为用于绑定 Type-safe Expressionsparameter declarations。然后在构建时验证类型安全表达式。

You can define type-safe templates in your Java code. Parameters of type-safe templates are automatically turned into parameter declarations that are used to bind Type-safe Expressions. The type-safe expressions are then validated at build time.

有两种方法可以定义类型安全模板:

There are two ways to define a type-safe template:

  1. Annotate a class with @io.quarkus.qute.CheckedTemplate and all its static native methods will be used to define type-safe templates and the list of parameters they require.

  2. Use a Java record that implements io.quarkus.qute.TemplateInstance; the record components represent the template parameters and @io.quarkus.qute.CheckedTemplate can be optionally used to configure the template.

Nested Type-safe Templates

如果使用 templates in Jakarta REST resources,可以依赖以下约定:

If using rest_integration, you can rely on the following convention:

  • Organise your template files in the /src/main/resources/templates directory, by grouping them into one directory per resource class. So, if your ItemResource class references two templates hello and goodbye, place them at /src/main/resources/templates/ItemResource/hello.txt and /src/main/resources/templates/ItemResource/goodbye.txt. Grouping templates per resource class makes it easier to navigate to them.

  • In each of your resource class, declare a @CheckedTemplate static class Template {} class within your resource class.

  • Declare one public static native TemplateInstance method(); per template file for your resource.

  • Use those static methods to build your template instances.

ItemResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import io.quarkus.qute.CheckedTemplate;

@Path("item")
public class ItemResource {

    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance item(Item item); 1 2
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(Integer id) {
        return Templates.item(service.findItem(id)); 3
    }
}
1 Declare a method that gives us a TemplateInstance for templates/ItemResource/item.html and declare its Item item parameter so we can validate the template.
2 The item parameter is automatically turned into a typesafe_expressions and so all expressions that reference this name will be validated.
3 Make the Item object accessible in the template.

默认情况下,使用 @CheckedTemplate 注释的类中定义的模板只能包含类型安全表达式,即可以在构建时进行验证的表达式。可以使用 @CheckedTemplate(requireTypeSafeExpressions = false) 放宽此要求。

By default, the templates defined in a class annotated with @CheckedTemplate can only contain type-safe expressions, i.e. expressions that can be validated at build time. You can use @CheckedTemplate(requireTypeSafeExpressions = false) to relax this requirement.

Top-level Type-safe Templates

还可以声明使用 @CheckedTemplate 注释的顶级 Java 类:

You can also declare a top-level Java class annotated with @CheckedTemplate:

Top-level checked templates
package org.acme.quarkus.sample;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import io.quarkus.qute.CheckedTemplate;

@CheckedTemplate
public class Templates {
    public static native TemplateInstance hello(String name); 1
}
1 This declares a template with path templates/hello.txt. The name parameter is automatically turned into a typesafe_expressions, so that all expressions referencing this name will be validated.

然后为每个模板文件声明一个 public static native TemplateInstance method();。使用这些静态方法构建模板实例:

Then declare one public static native TemplateInstance method(); per template file. Use those static methods to build your template instances:

HelloResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;

@Path("hello")
public class HelloResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return Templates.hello(name);
    }
}

Template Records

实现 io.quarkus.qute.TemplateInstance 的 Java 记录表示类型安全模板。记录组件表示模板参数,可以将 @io.quarkus.qute.CheckedTemplate 用于配置模板(这是可选操作)。

A Java record that implements io.quarkus.qute.TemplateInstance denotes a type-safe template. The record components represent the template parameters and @io.quarkus.qute.CheckedTemplate can be optionally used to configure the template.

HelloResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;

@Path("hello")
public class HelloResource {

    record Hello(String name) implements TemplateInstance {} 1

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return new Hello(name); 2
    }
}
1 Declares a type-safe template with the Java record.
2 Instantiate the record and use it as an ordinary TemplateInstance.

Customized Template Path

@CheckedTemplate 方法的模板路径包含 base pathdefaulted namebase path@CheckedTemplate#basePath() 提供。默认情况下,使用嵌套静态类的声明类的简单名称或者使用顶级类的空字符串。defaulted name 根据 @CheckedTemplate#defaultName() 中指定策略派生。默认情况下,使用 @CheckedTemplate 方法的名称。

The template path of a @CheckedTemplate method consists of the base path and a defaulted name. The base path is supplied by the @CheckedTemplate#basePath(). By default, the simple name of the declaring class for a nested static class or an empty string for a top level class is used. The defaulted name is derived by the strategy specified in @CheckedTemplate#defaultName(). By default, the name of the @CheckedTemplate method is used as is.

Customized Template Path Example
package org.acme.quarkus.sample;

import jakarta.ws.rs.Path;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.CheckedTemplate;

@Path("item")
public class ItemResource {

    @CheckedTemplate(basePath = "items", defaultName = CheckedTemplate.HYPHENATED_ELEMENT_NAME)
    static class Templates {
        static native TemplateInstance itemAndOrder(Item item); 1
    }
}
1 The template path for this method will be items/item-and-order.

Type-safe Fragments

你也可以在 Java 代码中定义类型安全 fragment。带美元符号 $ 的方法名称 _native static_表示一个表示类型安全模板片段的方法。片段名称衍生自带注释方法名称。在美元符号 $`最后一次出现之前的内容是相关类型安全模板的方法名称。美元符号最后一次出现之后的部分是片段标识符。构建默认名称时会遵从由相关 `CheckedTemplate#defaultName() 定义的策略。

You can also define a type-safe fragments in your Java code. A native static method with the name that contains a dollar sign $ denotes a method that represents a fragment of a type-safe template. The name of the fragment is derived from the annotated method name. The part before the last occurence of a dollar sign $ is the method name of the related type-safe template. The part after the last occurence of a dollar sign is the fragment identifier. The strategy defined by the relevant CheckedTemplate#defaultName() is honored when constructing the defaulted names.

Type-safe Fragment Example
import io.quarkus.qute.CheckedTemplate;
import org.acme.Item;

@CheckedTemplate
class Templates {

  // defines a type-safe template
  static native TemplateInstance items(List<Item> items);

  // defines a fragment of Templates#items() with identifier "item"
  static native TemplateInstance items$item(Item item); 1
}
1 Quarkus validates at build time that each template that corresponds to the Templates#items() contains a fragment with identifier item. Moreover, the parameters of the fragment method are validated too. In general, all type-safe expressions that are found in the fragment and that reference some data from the original/outer template require a specific parameter to be present.
Fragment Definition in items.html
<h1>Items</h1>
<ol>
    {#for item in items}
    {#fragment id=item}   1
    <li>{item.name}</li>  2
    {/fragment}
    {/for}
</ol>
1 Defines a fragment with identifier item.
2 The {item.name} expression implies that the Templates#items$item() method must declare a parameter of name item and type org.acme.Item.
Type-safe Fragment Call Site Example
class ItemService {

  String renderItem(Item item) {
     // this would return something like "<li>Foo</li>"
     return Templates.items$item(item).render();
  }
}

你可以指定 @CheckedTemplate#ignoreFragments=true 来禁用此特性,即方法名称中的美元符号 $ 不会导致复选片段方法。

You can specify @CheckedTemplate#ignoreFragments=true in order to disable this feature, i.e. a dollar sign $ in the method name will not result in a checked fragment method.

Template Extension Methods

可以使用扩展方法来使用新功能(扩展可访问属性和方法的集合)扩展数据类,或为特定 namespace 解析表达式。例如,可以添加 computed propertiesvirtual methods

Extension methods can be used to extend the data classes with new functionality (to extend the set of accessible properties and methods) or to resolve expressions for a specific namespace_extension_methods. For example, it is possible to add computed properties and virtual methods.

为注释有 @TemplateExtension 的方法自动生成了一个值解析器。如果类注释有 @TemplateExtension,则为该类上声明的每个 non-private static method 生成了一个值解析器。方法级注释会覆盖在类上定义的行为。不满足以下条件的方法将被忽略。

A value resolver is automatically generated for a method annotated with @TemplateExtension. If a class is annotated with @TemplateExtension then a value resolver is generated for every non-private static method declared on the class. Method-level annotations override the behavior defined on the class. Methods that do not meet the following requirements are ignored.

模板扩展方法:

A template extension method:

  • must not be private

  • must be static,

  • must not return void.

如果没有定义名称空间,则未注释有 @TemplateAttribute 的第一个参数的类用来匹配基础对象。否则,名称空间用来匹配表达式。

If there is no namespace defined the class of the first parameter that is not annotated with @TemplateAttribute is used to match the base object. Otherwise, the namespace is used to match an expression.

Matching by Name

默认情况下,方法名称用于匹配属性名称。

The method name is used to match the property name by default.

Extension Method Example
package org.acme;

class Item {

    public final BigDecimal price;

    public Item(BigDecimal price) {
        this.price = price;
    }
}

@TemplateExtension
class MyExtensions {

    static BigDecimal discountedPrice(Item item) { 1
        return item.getPrice().multiply(new BigDecimal("0.9"));
    }
}
1 This method matches an expression with base object of the type Item.class and the discountedPrice property name.

此模板扩展方法让呈示以下模板成为可能:

This template extension method makes it possible to render the following template:

{item.discountedPrice} 1
1 item is resolved to an instance of org.acme.Item.

然而,可以使用 matchName() 指定匹配名称。

However, it is possible to specify the matching name with matchName().

TemplateExtension#matchName() Example
@TemplateExtension(matchName = "discounted")
static BigDecimal discountedPrice(Item item) {
   // this method matches {item.discounted} if "item" resolves to an object assignable to "Item"
   return item.getPrice().multiply(new BigDecimal("0.9"));
}

可以用特殊常量 - TemplateExtension#ANY/* - 指定扩展方法匹配任何名称。

A special constant - TemplateExtension#ANY/* - can be used to specify that the extension method matches any name.

TemplateExtension#ANY Example
@TemplateExtension(matchName = "*")
static String itemProperty(Item item, String name) { 1
   // this method matches {item.foo} if "item" resolves to an object assignable to "Item"
   // the value of the "name" argument is "foo"
}
1 A additional string method parameter is used to pass the actual property name.

也可以匹配 matchRegex() 中指定的正则表达式中的名称。

It’s also possible to match the name against a regular expression specified in matchRegex().

TemplateExtension#matchRegex() Example
@TemplateExtension(matchRegex = "foo|bar")
static String itemProperty(Item item, String name) { 1
   // this method matches {item.foo} and {item.bar} if "item" resolves to an object assignable to "Item"
   // the value of the "name" argument is "foo" or "bar"
}
1 A additional string method parameter is used to pass the actual property name.

最后,可以使用 matchNames() 指定匹配名称的集合。此外,还需要附加字符串方法参数。

Finally, matchNames() can be used to specify a collection of matching names. An additional string method parameter is mandatory as well.

TemplateExtension#matchNames() Example
@TemplateExtension(matchNames = {"foo", "bar"})
static String itemProperty(Item item, String name) {
   // this method matches {item.foo} and {item.bar} if "item" resolves to an object assignable to "Item"
   // the value of the "name" argument is "foo" or "bar"
}

冗余匹配条件会忽略。按优先级从高到低排列条件为: matchRegex()matchNames()matchName()

Superfluous matching conditions are ignored. The conditions sorted by priority in descending order are: matchRegex(), matchNames() and matchName().

Method Parameters

扩展方法也可以声明参数。如果未指定命名空间,则使用第一个未标注有 @TemplateAttribute 的参数来传递基础对象,即第一个示例中的 org.acme.Item。如果匹配任何名称或使用正则表达式,则需要使用字符串方法参数来传递属性名称。标注有 @TemplateAttribute 的参数通过 TemplateInstance#getAttribute() 获取。渲染模板时会解析所有其他参数,并将它们传递给扩展方法。

An extension method may declare parameters. If no namespace is specified then the first parameter that is not annotated with @TemplateAttribute is used to pass the base object, i.e. org.acme.Item in the first example. If matching any name or using a regular expression, then a string method parameter needs to be used to pass the property name. Parameters annotated with @TemplateAttribute are obtained via TemplateInstance#getAttribute(). All other parameters are resolved when rendering the template and passed to the extension method.

Multiple Parameters Example
@TemplateExtension
class BigDecimalExtensions {

    static BigDecimal scale(BigDecimal val, int scale, RoundingMode mode) { 1
        return val.setScale(scale, mode);
    }
}
1 This method matches an expression with base object of the type BigDecimal.class, with the scale virtual method name and two virtual method parameters.
{item.discountedPrice.scale(2,mode)} 1
1 item.discountedPrice is resolved to an instance of BigDecimal.

Namespace Extension Methods

如果指定了 TemplateExtension#namespace(),那么将使用扩展方法解析具有给定 namespace 的表达式。共享相同命名空间的模板扩展方法按照 TemplateExtension#priority() 排序并分组在一个解析器中。将使用第一个匹配的扩展方法解析表达式。

If TemplateExtension#namespace() is specified then the extension method is used to resolve expressions with the given expressions. Template extension methods that share the same namespace are grouped in one resolver ordered by TemplateExtension#priority(). The first matching extension method is used to resolve an expression.

Namespace Extension Method Example
@TemplateExtension(namespace = "str")
public class StringExtensions {

   static String format(String fmt, Object... args) {
      return String.format(fmt, args);
   }

   static String reverse(String val) {
      return new StringBuilder(val).reverse().toString();
   }
}

这些扩展方法可用以下方式使用。

These extension methods can be used as follows.

{str:format('%s %s!','Hello', 'world')} 1
{str:reverse('hello')} 2
1 The output is Hello world!
2 The output is olleh

Built-in Template Extensions

Quarkus 提供了一组内置扩展方法。

Quarkus provides a set of built-in extension methods.

Maps
  • keys or keySet: Returns a Set view of the keys contained in a map

    • {#for key in map.keySet}

  • values: Returns a Collection view of the values contained in a map

    • {#for value in map.values}

  • size: Returns the number of key-value mappings in a map

    • {map.size}

  • isEmpty: Returns true if a map contains no key-value mappings

    • {#if map.isEmpty}

  • get(key): Returns the value to which the specified key is mapped

    • {map.get('foo')}

还可以直接访问某个地图的值:{map.myKey}。对不是合法标识符的键使用方括号表示法:{map['my key']}

A map value can be also accessed directly: {map.myKey}. Use the bracket notation for keys that are not legal identifiers: {map['my key']}.

Lists

  • get(index): Returns the element at the specified position in a list

    • {list.get(0)}

  • reversed: Returns a reversed iterator over a list

    • {#for r in recordsList.reversed}

  • take: Returns the first n elements from the given list; throws an IndexOutOfBoundsException if n is out of range

    • {#for r in recordsList.take(3)}

  • takeLast: Returns the last n elements from the given list; throws an IndexOutOfBoundsException if n is out of range

    • {#for r in recordsList.takeLast(3)}

  • first: Returns the first element of the given list; throws an NoSuchElementException if the list is empty

    • {recordsList.first}

  • last: Returns the last element of the given list; throws an NoSuchElementException if the list is empty

    • {recordsList.last}

列表元素可以通过索引直接访问:{list.10}`或甚至{list[10]}`。

A list element can be accessed directly via an index: {list.10} or even {list[10]}.

Integer Numbers
  • mod: Modulo operation

    • {#if counter.mod(5) == 0}

  • plus or +: Addition

    • {counter + 1}

    • {age plus 10}

    • {age.plus(10)}

  • minus or -: Subtraction

    • {counter - 1}

    • {age minus 10}

    • {age.minus(10)}

Strings
  • fmt or format: Formats the string instance via java.lang.String.format()

    • {myStr.fmt("arg1","arg2")}

    • {myStr.format(locale,arg1)}

  • str:fmt or str:format: Formats the supplied string value via java.lang.String.format()

    • {str:format("Hello %s!",name)}

    • {str:fmt(locale,'%tA',now)}

  • +: Concatenation

    • {item.name + '_' + mySuffix}

    • {name + 10}

Config
  • config:<name> or config:[<name>]: Returns the config value for the given property name

    • {config:foo} or {config:['property.with.dot.in.name']}

  • config:property(name): Returns the config value for the given property name; the name can be obtained dynamically by an expression

    • {config:property('quarkus.foo')}

    • {config:property(foo.getPropertyName())}

  • config:boolean(name): Returns the config value for the given property name as a boolean; the name can be obtained dynamically by an expression

    • {config:boolean('quarkus.foo.boolean') ?: 'Not Found'}

    • {config:boolean(foo.getPropertyName()) ?: 'property is false'}

  • config:integer(name): Returns the config value for the given property name as an integer; the name can be obtained dynamically by an expression

    • {config:integer('quarkus.foo')}

    • {config:integer(foo.getPropertyName())}

Time
  • format(pattern): Formats temporal objects from the java.time package

    • {dateTime.format('d MMM uuuu')}

  • format(pattern,locale): Formats temporal objects from the java.time package

    • {dateTime.format('d MMM uuuu',myLocale)}

  • format(pattern,locale,timeZone): Formats temporal objects from the java.time package

    • {dateTime.format('d MMM uuuu',myLocale,myTimeZoneId)}

  • time:format(dateTime,pattern): Formats temporal objects from the java.time package, java.util.Date, java.util.Calendar and java.lang.Number

    • {time:format(myDate,'d MMM uuuu')}

  • time:format(dateTime,pattern,locale): Formats temporal objects from the java.time package, java.util.Date, java.util.Calendar and java.lang.Number

    • {time:format(myDate,'d MMM uuuu', myLocale)}

  • time:format(dateTime,pattern,locale,timeZone): Formats temporal objects from the java.time package, java.util.Date, java.util.Calendar and java.lang.Number

    • {time:format(myDate,'d MMM uuuu',myLocale,myTimeZoneId)}

@TemplateData

为使用 `@TemplateData`注解的类型自动生成一个值解析器。这允许 Quarkus 在运行时避免使用反射来访问数据。

A value resolver is automatically generated for a type annotated with @TemplateData. This allows Quarkus to avoid using reflection to access the data at runtime.

始终忽略非公共成员、构造函数、静态初始值设定项、静态函数、合成函数和无效函数。

Non-public members, constructors, static initializers, static, synthetic and void methods are always ignored.

package org.acme;

@TemplateData
class Item {

    public final BigDecimal price;

    public Item(BigDecimal price) {
        this.price = price;
    }

    public BigDecimal getDiscountedPrice() {
        return price.multiply(new BigDecimal("0.9"));
    }
}

`Item`的任何实例都可以在模板中直接使用:

Any instance of Item can be used directly in the template:

{#each items} 1
  {it.price} / {it.discountedPrice}
{/each}
1 items is resolved to a list of org.acme.Item instances.

此外,@TemplateData.properties()@TemplateData.ignore() 可用于微调所生成的解析器。最后,还可以指定注解的“目标” - 这对于应用程序无法控制的第三方类很有用:

Furthermore, @TemplateData.properties() and @TemplateData.ignore() can be used to fine-tune the generated resolver. Finally, it is also possible to specify the "target" of the annotation - this could be useful for third-party classes not controlled by the application:

@TemplateData(target = BigDecimal.class)
@TemplateData
class Item {

    public final BigDecimal price;

    public Item(BigDecimal price) {
        this.price = price;
    }
}
{#each items}
  {it.price.setScale(2, rounding)} 1
{/each}
1 The generated value resolver knows how to invoke the BigDecimal.setScale() method.

Accessing Static Fields and Methods

如果将 @TemplateData#namespace() 设置为非空值,则自动生成一个名称空间解析器,以访问目标类的公共静态字段和方法。默认情况下,名称空间是目标类的 FQCN,其中点和美元符号被下划线替换。例如,名为 org.acme.Foo 的类的名称空间是 org_acme_Foo。静态字段 Foo.AGE 可通过 {org_acme_Foo:AGE} 访问。静态方法 Foo.computeValue(int number) 可通过 {org_acme_Foo:computeValue(10)} 访问。

If @TemplateData#namespace() is set to a non-empty value then a namespace resolver is automatically generated to access the public static fields and methods of the target class. By default, the namespace is the FQCN of the target class where dots and dollar signs are replaced by underscores. For example, the namespace for a class with name org.acme.Foo is org_acme_Foo. The static field Foo.AGE can be accessed via {org_acme_Foo:AGE}. The static method Foo.computeValue(int number) can be accessed via {org_acme_Foo:computeValue(10)}.

命名空间只能由字母数字字符和下划线组成。

A namespace can only consist of alphanumeric characters and underscores.

Class Annotated With @TemplateData
package model;

@TemplateData 1
public class Statuses {
    public static final String ON = "on";
    public static final String OFF = "off";
}
1 A name resolver with the namespace model_Status is generated automatically.
Template Accessing Class Constants
{#if machine.status == model_Status:ON}
  The machine is ON!
{/if}

Convenient Annotation For Enums

还有一个方便的注释用于访问枚举常量:@io.quarkus.qute.TemplateEnum。此注释在功能上等效于 @TemplateData(namespace = TemplateData.SIMPLENAME),即,自动为目标枚举生成一个命名空间解析器,并且目标枚举的简单名称用作命名空间。

There’s also a convenient annotation to access enum constants: @io.quarkus.qute.TemplateEnum. This annotation is functionally equivalent to @TemplateData(namespace = TemplateData.SIMPLENAME), i.e. a namespace resolver is automatically generated for the target enum and the simple name of the target enum is used as the namespace.

Enum Annotated With @TemplateEnum
package model;

@TemplateEnum 1
public enum Status {
    ON,
    OFF
}
1 A name resolver with the namespace Status is generated automatically.

在非枚举类上声明的 @TemplateEnum 将被忽略。此外,如果一个枚举也声明了 @TemplateData 注释,则会忽略 @TemplateEnum 注释。

@TemplateEnum declared on a non-enum class is ignored. Also, if an enum also declares the @TemplateData annotation, then the @TemplateEnum annotation is ignored.

Template Accessing Enum Constants
{#if machine.status == Status:ON}
  The machine is ON!
{/if}

Quarkus 会检测可能的命名空间冲突,如果一个特定的命名空间由多个 @TemplateData 和/或 @TemplateEnum 注释定义,则构建失败。

Quarkus detects possible namespace collisions and fails the build if a specific namespace is defined by multiple @TemplateData and/or @TemplateEnum annotations.

Global Variables

io.quarkus.qute.TemplateGlobal 注释可用于表示静态字段和方法,它们提供可在任何模板中访问的 global variables

The io.quarkus.qute.TemplateGlobal annotation can be used to denote static fields and methods that supply global variables which are accessible in any template.

全局变量是:

Global variables are:

  • added as computed data of any TemplateInstance during initialization,

  • accessible with the global: namespace.

使用 TemplateInstance#computedData(String, Function<String, Object>) 时,一个映射函数与一个特定的键相关联,并且每次请求给定键的值时都会使用此函数。在全局变量的情况下,会在映射函数中调用一个静态方法或读取一个静态字段。

When using TemplateInstance#computedData(String, Function<String, Object>) a mapping function is associated with a specific key and this function is used each time a value for the given key is requested. In case of global variables, a static method is called or a static field is read in the mapping function.

Global Variables Definition
enum Color { RED, GREEN, BLUE }

@TemplateGlobal 1
public class Globals {

    static int age = 40;

    static Color[] myColors() {
      return new Color[] { Color.RED, Color.BLUE };
    }

    @TemplateGlobal(name = "currentUser") 2
    static String user() {
       return "Mia";
    }
}
1 If a class is annotated with @TemplateGlobal then every non-void non-private static method that declares no parameters and every non-private static field is considered a global variable. The name is defaulted, i.e. the name of the field/method is used.
2 Method-level annotations override the class-level annotation. In this particular case, the name is not defaulted but selected explicitly.
A Template Accessing Global Variables
User: {currentUser} 1
Age:  {global:age} 2
Colors: {#each myColors}{it}{#if it_hasNext}, {/if}{/each} 3
1 currentUser resolves to Globals#user().
2 The global: namespace is used; age resolves to Globals#age.
3 myColors resolves to Globals#myColors().

请注意,全局变量隐式地将 parameter declarations 添加到所有模板中,因此引用全局变量的任何表达式都会在构建期间得到验证。

Note that global variables implicitly add typesafe_expressions to all templates and so any expression that references a global variable is validated during build.

The Output
User: Mia
Age:  40
Colors: RED, BLUE

Resolving Conflicts

如果未通过 global: 命名空间访问,全局变量可能会与常规数据对象冲突。Type-safe templates 会自动覆盖全局变量。例如,以下定义覆盖了 Globals#user() 方法提供的全局变量:

If not accessed via the global: namespace the global variables may conflict with regular data objects. typesafe_templates override the global variables automatically. For example, the following definition overrides the global variable supplied by the Globals#user() method:

Type-safe Template Definition
import org.acme.User;

@CheckedTemplate
public class Templates {
    static native TemplateInstance hello(User currentUser); 1
}
1 currentUser conflicts with the global variable supplied by Globals#user().

因此,相应的模板不会导致验证错误,即使 Globals#user()`方法返回不含 `name`属性的 `java.lang.String

So the corresponding template does not result in a validation error even though the Globals#user() method returns java.lang.String which does not have the name property:

templates/hello.txt
User name: {currentUser.name} 1
1 org.acme.User has the name property.

对于其他模板,需要显式参数声明:

For other templates an explicit parameter declaration is needed:

{@org.acme.User currentUser} 1

User name: {currentUser.name}
1 This parameter declaration overrides the declaration added by the global variable supplied by the Globals#user() method.

Native Executables

在 JVM 模式中,基于反射的值解析器可以用于访问模型类属性和调用方法。但是,这对于 a native executable来说并不好用。因此,即使 Foo`类声明了一个相关 getter 方法,您也可能会遇到这样的模板异常: `Property "name" not found on the base object "org.acme.Foo" in expression {foo.name} in template hello.html

In the JVM mode a reflection-based value resolver may be used to access properties and call methods of the model classes. But this does not work for a native executable out of the box. As a result, you may encounter template exceptions like Property "name" not found on the base object "org.acme.Foo" in expression {foo.name} in template hello.html even if the Foo class declares a relevant getter method.

可以有以下几种方法来解决此问题:

There are several ways to solve this problem:

  • Make use of typesafe_templates or typesafe_expressions

    • In this case, an optimized value resolver is generated automatically and used at runtime

    • This is the preferred solution

  • Annotate the model class with <<`@TemplateData`,template_data>> - a specialized value resolver is generated and used at runtime

  • Annotate the model class with @io.quarkus.runtime.annotations.RegisterForReflection to make the reflection-based value resolver work. More details about the @RegisterForReflection annotation can be found on the native application tips page.

[id="resteasy_integration"] REST Integration

如果您想在 Jakarta REST 应用程序中使用 Qute,那么您需要根据使用的 Jakarta REST 栈,首先注册正确的扩展。

If you want to use Qute in your Jakarta REST application, then depending on which Jakarta REST stack you are using, you’ll need to register the proper extension first.

如果您通过 `quarkus-rest`扩展使用 Quarkus REST(以前称为 RESTEasy Reactive),那么在 `pom.xml`文件中添加:

If you are using Quarkus REST (formerly RESTEasy Reactive) via the quarkus-rest extension, then in your pom.xml file, add:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-qute</artifactId>
</dependency>

否则,如果您正在使用基于 RESTEasy Classic 的 `quarkus-resteasy`扩展,那么在 `pom.xml`文件中添加:

If instead you are using the legacy RESTEasy Classic-based quarkus-resteasy extension, then in your pom.xml file, add:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-qute</artifactId>
</dependency>

这两个扩展都注册了一个特殊响应过滤器,它允许资源方法返回 TemplateInstance,从而无需用户负责完成所有必要的内部步骤。

Both of these extensions register a special response filter which enables resource methods to return a TemplateInstance, thus freeing users of having to take care of all necessary internal steps.

如果使用 Quarkus REST,那么返回 `TemplateInstance`的资源方法会被认为是非阻塞的。您需要使用 `io.smallrye.common.annotation.Blocking`注解该方法,以将该方法标记为阻塞的。例如,如果它也用 `@RunOnVirtualThread`进行了注解。

If using Quarkus REST, a resource method that returns TemplateInstance is considered non-blocking. You need to annotate the method with io.smallrye.common.annotation.Blocking in order to mark the method as blocking. For example if it’s also annotated with @RunOnVirtualThread.

最终结果就是,在 Jakarta REST 资源中使用 Qute 可能就像以下内容一样简单:

The end result is that a using Qute within a Jakarta REST resource may look as simple as:

HelloResource.java
package org.acme.quarkus.sample;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;

@Path("hello")
public class HelloResource {

    @Inject
    Template hello; 1

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return hello.data("name", name); 2 3
    }
}
1 If there is no @Location qualifier provided, the field name is used to locate the template. In this particular case, we’re injecting a template with path templates/hello.txt.
2 Template.data() returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key name. The data map is accessible during rendering.
3 Note that we don’t trigger the rendering - this is done automatically by a special ContainerResponseFilter implementation.

鼓励用户使用 Type-safe templates,这有助于安排特定 Jakarta REST 资源的模板,并自动启用 type-safe expressions

Users are encouraged to use typesafe_templates that help to organize the templates for a specific Jakarta REST resource and enable typesafe_expressions automatically.

内容协商自动执行。结果输出取决于从客户端收到的 Accept 标头。

The content negotiation is performed automatically. The resulting output depends on the Accept header received from the client.

@Path("/detail")
class DetailResource {

    @Inject
    Template item; 1

    @GET
    @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
    public TemplateInstance item() {
        return item.data("myItem", new Item("Alpha", 1000)); 2
    }
}
1 Inject a variant template with base path derived from the injected field - src/main/resources/templates/item.
2 For text/plain the src/main/resources/templates/item.txt template is used. For text/html the META-INF/resources/templates/item.html template is used.

可以使用 RestTemplate util 类从 Jakarta REST 资源方法的主体获取模板实例:

The RestTemplate util class can be used to obtain a template instance from a body of a Jakarta REST resource method:

RestTemplate Example
@Path("/detail")
class DetailResource {

    @GET
    @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
    public TemplateInstance item() {
        return RestTemplate.data("myItem", new Item("Alpha", 1000)); 1
    }
}
1 The name of the template is derived from the resource class and method name; DetailResource/item in this particular case.

@Inject 不同,通过 RestTemplate 获取的模板未通过验证,即如果模板不存在,则构建不会失败。

Unlike with @Inject the templates obtained via RestTemplate are not validated, i.e. the build does not fail if a template does not exist.

Development Mode

在开发模式下,会监视 src/main/resources/templates 中位于的所有文件,以进行更改。默认情况下,模板修改会导致应用程序重启,而应用程序重启也会触发构建时验证。

In the development mode, all files located in src/main/resources/templates are watched for changes. By default, a template modification results in an application restart that also triggers build-time validations.

但是,可以使用 quarkus.qute.dev-mode.no-restart-templates 配置属性指定不重新启动应用程序的模板。配置值是一个正则表达式,用于匹配相对于 templates 目录的模板路径,而 / 则用作路径分隔符。例如,quarkus.qute.dev-mode.no-restart-templates=templates/foo.html 匹配模板 src/main/resources/templates/foo.html。将重新加载匹配的模板,仅执行运行时验证。

However, it’s possible to use the quarkus.qute.dev-mode.no-restart-templates configuration property to specify the templates for which the application is not restarted. The configration value is a regular expression that matches the template path relative from the templates directory and / is used as a path separator. For example, quarkus.qute.dev-mode.no-restart-templates=templates/foo.html matches the template src/main/resources/templates/foo.html. The matching templates are reloaded and only runtime validations are performed.

Testing

在测试模式下,已注入和类型安全的模板的渲染结果被记录在受管的 io.quarkus.qute.RenderedResults 中,该 io.quarkus.qute.RenderedResults 已注册为 CDI bean。您可以在测试或任何其他 CDI bean 中注入 RenderedResults,并断言结果。但是,可以将 quarkus.qute.test-mode.record-rendered-results 配置属性设置为 false 以禁用此功能。

In the test mode, the rendering results of injected and type-safe templates are recorded in the managed io.quarkus.qute.RenderedResults which is registered as a CDI bean. You can inject RenderedResults in a test or any other CDI bean and assert the results. However, it’s possible to set the quarkus.qute.test-mode.record-rendered-results configuration property to false to disable this feature.

Type-safe Message Bundles

Basic Concepts

基本思想是,每条消息都可能是一个非常简单的模板。为了防止类型错误,消息被定义为 message bundle interface 的注释方法。Quarkus 在构建时生成 message bundle implementation

The basic idea is that every message is potentially a very simple template. In order to prevent type errors, a message is defined as an annotated method of a message bundle interface. Quarkus generates the message bundle implementation at build time.

Message Bundle Interface Example
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;

@MessageBundle 1
public interface AppMessages {

    @Message("Hello {name}!") 2
    String hello_name(String name); 3
}
1 Denotes a message bundle interface. The bundle name is defaulted to msg and is used as a namespace in templates expressions, e.g. {msg:hello_name}.
2 Each method must be annotated with @Message. The value is a qute template. If no value is provided, then a corresponding value from a localized file is taken. If no such file exists, an exception is thrown and the build fails.
3 The method parameters can be used in the template.

消息包可以在运行时使用:

The message bundles can be used at runtime:

  1. Directly in your code via io.quarkus.qute.i18n.MessageBundles#get(); e.g. MessageBundles.get(AppMessages.class).hello_name("Lucie")

  2. Injected in your beans via @Inject; e.g. @Inject AppMessages

  3. Referenced in the templates via the message bundle namespace:[source, html]

 {msg:hello_name('Lucie')} 1 2 3
 {msg:message(myKey,'Lu')} 4
1 msg is the default namespace.
2 hello_name is the message key.
3 Lucie is the parameter of the message bundle interface method.
4 It is also possible to obtain a localized message for a key resolved at runtime using a reserved key message. The validation is skipped in this case though.

Default Bundle Name

包名称默认为,除非使用 `@MessageBundle#value()`指定。对于顶级类,默认使用 `msg`值。对于嵌套类,名称由层次结构中所有封闭类的简单名称(首先是顶级类)组成,然后是消息包接口的简单名称。名称之间用下划线分隔。

The bundle name is defaulted unless it’s specified with @MessageBundle#value(). For a top-level class the msg value is used by default. For a nested class the name consists of the simple names of all enclosing classes in the hierarchy (top-level class goes first), followed by the simple name of the message bundle interface. Names are separated by underscores.

例如,以下消息包的名称将默认为 Controller_index

For example, the name of the following message bundle will be defaulted to Controller_index:

class Controller {

    @MessageBundle
    interface index {

        @Message("Hello {name}!")
        String hello(String name); 1
   }
}
1 This message could be used in a template via {Controller_index:hello(name)}.

包名称也用作本地化文件名的一部分,例如 Controller_index`中的 `Controller_index_de.properties

The bundle name is also used as a part of the name of a localized file, e.g. Controller_index in the Controller_index_de.properties.

Bundle Name and Message Keys

消息键直接用于模板中。包名称用作模板表达式中的名称空间。可以使用 @MessageBundle`来定义用于从方法名称生成消息键的默认策略。然而,@Message`可以覆盖此策略,甚至定义自定义键。默认情况下,注释元素的名称按原样使用。还有以下其他可能性:

Message keys are used directly in templates. The bundle name is used as a namespace in template expressions. The @MessageBundle can be used to define the default strategy used to generate message keys from method names. However, the @Message can override this strategy and even define a custom key. By default, the annotated element’s name is used as-is. Other possibilities are:

  1. De-camel-cased and hyphenated; e.g. helloName()hello-name

  2. De-camel-cased and parts separated by underscores; e.g. helloName()hello_name.

Validation

  • All message bundle templates are validated:

    • All expressions without a namespace must map to a parameter; e.g. Hello {foo} → the method must have a param of name foo

    • All expressions are validated against the types of the parameters; e.g. Hello {foo.bar} where the parameter foo is of type org.acme.Fooorg.acme.Foo must have a property of name bar

针对每个 _unused_参数记录一条警告消息。

A warning message is logged for each unused parameter.

  • Expressions that reference a message bundle method, such as {msg:hello(item.name)}, are validated too.

Localization

默认情况下,通过 `quarkus.default-locale`配置属性指定的默认语言环境用于 `@MessageBundle`接口。然而,可以 `io.quarkus.qute.i18n.MessageBundle#locale()`来指定一个自定义语言环境。此外,有两种方法来定义本地化包:

The default locale specified via the quarkus.default-locale config property is used for the @MessageBundle interface by default. However, the io.quarkus.qute.i18n.MessageBundle#locale() can be used to specify a custom locale. Additionally, there are two ways to define a localized bundle:

  1. Create an interface that extends the default interface that is annotated with @Localized

  2. Create an UTF-8 encoded file located in the src/main/resources/messages directory of an application archive; e.g. msg_de.properties.

虽然本地化界面能够轻松重构,但外部文件在许多情况下可能更加方便。

While a localized interface enables easy refactoring, an external file might be more convenient in many situations.

Localized Interface Example
import io.quarkus.qute.i18n.Localized;
import io.quarkus.qute.i18n.Message;

@Localized("de") 1
public interface GermanAppMessages extends AppMessages {

    @Override
    @Message("Hallo {name}!") 2
    String hello_name(String name);
}
1 The value is the locale tag string (IETF).
2 The value is the localized template.

消息包文件必须用 UTF-8_进行编码。文件名由相关包名(例如 msg)组成,后跟下划线和语言标记(IETF;例如 en-US)。语言标记可以省略,在这种情况下,将使用默认包区域设置的语言标记。例如,如果包 msg`具有默认区域设置 `en,那么 msg.properties`将被视为 `msg_en.properties。如果同时检测到 msg.properties`和 `msg_en.properties,则会抛出异常并导致构建失败。文件格式非常简单:每一行都代表一对键值,等号用作分隔符,或一个注释(行以 `#`开头)。空行会被忽略。键是对应 Message 包接口中的 _mapped to method names。值通常由 `io.quarkus.qute.i18n.Message#value()`定义,表示模板。一个值可以跨多个相邻的普通行分散。在这种情况下,行终止符必须用反斜杠字符 `\`转义。此行为与 `java.util.Properties.load(Reader)`方法的行为非常相似。

Message bundle files must be encoded in UTF-8. The file name consists of the relevant bundle name (e.g. msg) and underscore followed by a language tag (IETF; e.g. en-US). The language tag may be omitted, in which case the language tag of the default bundle locale is used. For example, if bundle msg has default locale en, then msg.properties is going to be treated as msg_en.properties. If both msg.properties and msg_en.properties are detected, an exception is thrown and build fails. The file format is very simple: each line represents either a key/value pair with the equals sign used as a separator or a comment (line starts with #). Blank lines are ignored. Keys are mapped to method names from the corresponding message bundle interface. Values represent the templates normally defined by io.quarkus.qute.i18n.Message#value(). A value may be spread out across several adjacent normal lines. In such case, the line terminator must be escaped with a backslash character \. The behavior is very similar to the behavior of the java.util.Properties.load(Reader) method.

Localized File Example - msg_de.properties
# This comment is ignored
hello_name=Hallo {name}! 1 2
1 Each line in a localized file represents a key/value pair. The key must correspond to a method declared on the message bundle interface. The value is the message template.
2 Keys and values are separated by the equals sign.

在示例中,我们使用 `.properties`后缀,因为大多数 IDE 和文本编辑器都支持 `.properties`文件的语法高亮显示。但实际上,后缀可以是任何内容——它将被忽略。

We use the .properties suffix in our example because most IDEs and text editors support syntax highlighting of .properties files. But in fact, the suffix could be anything - it is just ignored.

一个示例属性文件会为每个消息包接口自动生成到目标目录中。例如,如果未为 @MessageBundle`指定名称,那么当通过 `mvn clean package`构建应用程序时,将生成 `target/qute-i18n-examples/msg.properties`文件。你可以将此文件用作特定区域设置的基础。只需重命名文件——例如 `msg_fr.properties,更改消息模板并将其移至 `src/main/resources/messages`目录中。

An example properties file is generated into the target directory for each message bundle interface automatically. For example, by default if no name is specified for @MessageBundle the file target/qute-i18n-examples/msg.properties is generated when the application is build via mvn clean package. You can use this file as a base for a specific locale. Just rename the file - e.g. msg_fr.properties, change the message templates and move it in the src/main/resources/messages directory.

Value Spread Out Across Several Adjacent Lines
hello=Hello \
   {name} and \
   good morning!

请注意,行终止符用反斜杠字符 \`转义,并且忽略了下一行开始处的空格。即 `{msg:hello('Edgar')}`将呈现为 `Hello Edgar and good morning!

Note that the line terminator is escaped with a backslash character \ and white space at the start of the following line is ignored. I.e. {msg:hello('Edgar')} would be rendered as Hello Edgar and good morning!.

一旦我们定义了本地化包,我们需要一种 _select_特定模板实例的正确包的方法,即为模板中所有消息包表达式指定区域设置。默认情况下,使用通过 `quarkus.default-locale`配置属性指定区域设置来选择包。或者,你可以指定模板实例的 `locale`属性。

Once we have the localized bundles defined, we need a way to select the correct bundle for a specific template instance, i.e. to specify the locale for all message bundle expressions in the template. By default, the locale specified via the quarkus.default-locale configuration property is used to select the bundle. Alternatively, you can specify the locale attribute of a template instance.

locale Attribute Example
@Singleton
public class MyBean {

    @Inject
    Template hello;

    String render() {
       return hello.instance().setLocale("cs").render(); 1
    }
}
1 You can set a Locale instance or a locale tag string (IETF).

在使用 <<`quarkus-rest-qute`,rest_integration>>(或 quarkus-resteasy-qute)时,如果用户未设置 `locale`属性,那么此属性将从 `Accept-Language`标头派生。

When using <<`quarkus-rest-qute`,rest_integration>> (or quarkus-resteasy-qute) the locale attribute is derived from the Accept-Language header if not set by a user.

`@Localized`限定符可用于注入本地化消息包接口。

The @Localized qualifier can be used to inject a localized message bundle interface.

Injected Localized Message Bundle Example
@Singleton
public class MyBean {

    @Localized("cs") 1
    AppMessages msg;

    String render() {
       return msg.hello_name("Jachym");
    }
}
1 The annotation value is a locale tag string (IETF).
Enums

有一种地方化枚举的便捷方法。如果有一个消息包方法接受枚举类型的单个参数,并且没有定义消息模板:

There is a convenient way to localize enums. If there is a message bundle method that accepts a single parameter of an enum type and has no message template defined:

@Message 1
String methodName(MyEnum enum);
1 The value is intentionally not provided. There’s also no key for the method in a localized file.

然后它会收到一个生成的模板:

Then it receives a generated template:

{#when enumParamName}
  {#is CONSTANT1}{msg:methodName_CONSTANT1}
  {#is CONSTANT2}{msg:methodName_CONSTANT2}
{/when}

此外,为每个枚举常量生成一个特殊的消息方法。最后,每个本地化文件必须包含所有常量消息键的键和值:

Furthermore, a special message method is generated for each enum constant. Finally, each localized file must contain keys and values for all constant message keys:

methodName_CONSTANT1=Value 1
methodName_CONSTANT2=Value 2

在模板中,可以使用消息包方法,如 {msg:methodName(enumConstant)},将枚举常量本地化。

In a template, an enum constant can be localized with a message bundle method like {msg:methodName(enumConstant)}.

同时还有 <<`@TemplateEnum`,便捷枚举注释>> —— 在模板中访问枚举常量的便捷注释。

There is also <<`@TemplateEnum`,convenient-annotation-for-enums>> - a convenient annotation to access enum constants in a template.

Message Templates

消息包界面的每个方法都必须定义一条消息模板。该值通常由 io.quarkus.qute.i18n.Message#value() 定义,但出于方便,还可以在本地化文件中可选地定义该值。

Every method of a message bundle interface must define a message template. The value is normally defined by io.quarkus.qute.i18n.Message#value(), but for convenience, there is also an option to define the value in a localized file.

Example of the Message Bundle Interface without the value
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;

@MessageBundle
public interface AppMessages {

    @Message 1
    String hello_name(String name);

    @Message("Goodbye {name}!") 2
    String goodbye(String name);
}
1 The annotation value is not defined. In such a case, the value from supplementary localized file is taken.
2 The annotation value is defined and preferred to the value defined in the localized file.
Supplementary localized file
hello_name=Hello \
   {name} and \
   good morning!
goodbye=Best regards, {name} 1
1 The value is ignored as io.quarkus.qute.i18n.Message#value() is always prioritized.

在构建期间验证消息模板。如果检测到消息模板缺失,则会抛出异常并导致构建失败。

Message templates are validated during the build. If a missing message template is detected, an exception is thrown and build fails.

Configuration Reference

Unresolved directive in qute-reference.adoc - include::{generated-dir}/config/quarkus-qute.adoc[]

Qute Used as a Standalone Library

Qute 主要设计为 Quarkus 扩展。但是,也可将其用作“独立”库。在这种情况下,某些功能不可用且需要一些其他配置。

Qute is primarily designed as a Quarkus extension. However. it is possible to use it as a "standalone" library. In this case, some features are not available and some additional configuration is needed.

Engine
  • First, no managed Engine instance is available out of the box. You’ll need to configure a new instance via Engine.builder().

Template locators
  • By default, no template-locator are registered, i.e. Engine.getTemplate(String) will not work.

  • You can register a custom template locator using EngineBuilder.addLocator() or parse a template manually and put the result in the cache via Engine.putTemplate(String, Template).

Template initializers
  • No TemplateInstance.Initializer is registered by default, therefore <<`@TemplateGlobal`,global_variables>> annotations are ignored.

  • A custom TemplateInstance.Initializer can be registered with EngineBuilder#addTemplateInstanceInitializer() and initialize a template instance with any data and attributes.

Sections
  • No section helpers are registered by default.

  • The default set of value resolvers can be registered via the convenient EngineBuilder.addDefaultSectionHelpers() method and the EngineBuilder.addDefaults() method respectively.

Value resolvers
  • No <<`ValueResolver`s,value-resolvers>> are generated automatically.

    • <<`@TemplateExtension` methods,template_extension_methods>> will not work.

    • <<`@TemplateData`,template_data>> and <<`@TemplateEnum`,convenient-annotation-for-enums>> annotations are ignored.

  • The default set of value resolvers can be registered via the convenient EngineBuilder.addDefaultValueResolvers() method and the EngineBuilder.addDefaults() method respectively.

并非所有内置扩展方法提供的功能都能被默认值解决器覆盖。但是,可以通过 ValueResolver.builder() 轻松构建自定义值解决器。

Not all functionality provided by the built-in extension methods is covered by the default value resolvers. However, a custom value resolver can be easily built via the ValueResolver.builder().

  • It’s recommended to register a ReflectionValueResolver instance via Engine.addValueResolver(new ReflectionValueResolver()) so that Qute can access object properties and call public methods.

请记住,反射可能无法在某些受限环境中正确工作,或者可能需要额外的配置,例如在 GraalVM 原生镜像的情况下注册。

Keep in mind that reflection may not work correctly in some restricted environments or may require additional configuration, e.g. registration in case of a GraalVM native image.

User-defined Tags
  • No user-defined tags are registered automatically.

  • A tag can be registered manually via Engine.builder().addSectionHelper(new UserTagSectionHelper.Factory("tagName","tagTemplate.html")).build()

Type-safety
Injection

It is not possible to inject a Template instance and vice versa - a template cannot inject a @Named CDI bean via the inject: and cdi: namespace.