The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

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.

In this guide, you will find an introductory example, the description of the core features and Quarkus integration details.

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との統合 section is missing. You can find more information about the limitations and possibilities in the Quteをスタンドアロンのライブラリとして使用 section.

1. 簡単な例

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>&lt;h1&gt;Header&lt;/h1&gt;</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 building block in the template. In this case, the Ifセクション is used to render the appropriate part of the message based on the input data.
In Quarkus, 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 テンプレート拡張メソッド, テンプレートに直接Beansを注入 or even タイプセーフメッセージバンドル.

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
Please read the javadoc of the io.quarkus.qute.Qute class for more details.

2. Hello Worldの例

この例では、Quteのテンプレートを扱う際の基本的なワークフローをデモしたいと思います。まず、簡単なhello worldの例から始めましょう。 テンプレートのコンテンツ は必ず必要になります。

hello.html
<html>
  <p>Hello {name}! (1)
</html>
1 {name} は、テンプレートがレンダリングされたときに評価される値の式です。

そして、その内容を テンプレート定義 Javaオブジェクトにパースする必要があります。テンプレート定義は、 io.quarkus.qute.Template のインスタンスです。

Quteを"スタンドアロン"で使用する場合は、まず io.quarkus.qute.Engine のインスタンスを作成する必要があります。 Engine は、専用の設定でテンプレートを管理するための中心点を表しています。便利なビルダーを使ってみましょう。

Engine engine = Engine.builder().addDefaults().build();
Quarkusでは、あらかじめ設定された Engine がインジェクション用に利用可能です - quarkus_integration を参照してください。

Engine のインスタンスがあれば、テンプレートの内容を解析することができます。

Template hello = engine.parse(helloHtmlContent);
Quarkusでは、テンプレートの定義を単純に注入することができます。テンプレートは自動的に解析され、キャッシュされます - quarkus_integration を参照してください。

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) は、ワンステップでテンプレートのインスタンスを作成してデータを設定する便利なメソッドです。
2 TemplateInstance.render() は同期レンダリングをトリガーします。つまり、レンダリングが終了するまで現在のスレッドはブロックされます。しかし、レンダリングをトリガーして結果を消費する非同期のメソッドもあります。 例えば、 CompletionStage<String> を返す TemplateInstance.renderAsync() メソッドや、Mutiny の Multi<String> を返す TemplateInstance.createMulti() です。

なので、ワークフローはシンプルです。

  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.

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.

3. コア機能

3.1. 基本的なビルディングブロック

テンプレートの動的な部分には、コメント、式、セクション、パースされていない文字データなどがあります。

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.

An expression 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}.

セクション

A section 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 /}. A section may also declare nested section blocks: {#if item.valid} Valid. {#else} Invalid. {/if} and decide which block to render.

パース対象外文字データ

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.

3.2. 識別子とタグ

識別子は、式やセクションタグで使用されます。有効な識別子は、空白文字以外の文字列です。ただし、式の中では有効な Java 識別子のみを使用することが推奨されます。

ドットを含む識別子を指定する必要がある場合は、大括弧表記を使用することができます。例:{map['my.key']}

テンプレート文書を解析する際、パーサはすべての タグ を識別します。タグは中括弧で始まり、中括弧で終わります。例: {foo} タグの内容は次から始まる必要があります:

  • 数字、もしくは

  • アルファベット文字、もしくは

  • アンダースコア、もしくは

  • 組み込みコマンド: # , ! , @ , / .

上記のいずれかで始まらない場合は、パーサによって無視されます。

タグの例
<html>
   <body>
   {_foo.bar}   (1)
   {! comment !}(2)
   {  foo}      (3)
   {{foo}}      (4)
   {"foo":true} (5)
   </body>
</html>
1 パース対象: アンダースコアで始まる式
2 パース対象: コメント
3 無視対象: 空白で開始
4 無視対象: { で開始
5 無視対象: " で開始
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).

3.3. テンプレートから独立した行を削除する

デフォルトでは、パーサはテンプレート出力からスタンドアロン行を削除します。スタンドアロン とは、少なくとも1つのセクションタグ(例: {#each}{/each} )、パラメーター宣言(例: {@org.acme.Foo foo} )、コメントを含むが、式を含まず、空白以外の文字を含まない行のことです。言い換えれば、セクションタグやパラメーター宣言を含まない行は、独立した行ではあり ません 。同様に、 空白以外の文字 を含む行もスタンドアロン行ではあり ません

テンプレート例
<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 これはスタンドアロン行なので削除されます。
2 スタンドアロン行ではない - 式と非空白文字を含む
3 スタンドアロン行ではない - セクションタグ/パラメーター宣言を含まない
4 これはスタンドアロン行です。
デフォルト出力
<html>
  <body>
     <ul>
       <li>Foo 100</li>

     </ul>
   <body>
</html>
Quarkusでは、プロパティー( quarkus.qute.remove-standalone-lines )を false に設定することで、デフォルトの動作を無効にすることができます。この場合、スタンドアロン行のすべての空白文字が出力されます。
quarkus.qute.remove-standalone-lines=false での出力
<html>
  <body>
     <ul>

       <li>Foo 100</li>


     </ul>
   <body>
</html>

3.4. 式

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).

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 valid identifier. In the object[property_name] (bracket notation) syntax, the property_name has to be a non-null literal value.

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

Property Accessor Examples
{name} (1)
{item.name} (2)
{item['name']} (3)
{global:colors} (4)
1 名前空間なし、1つのパーツ: name
2 名前空間なし、2つのパーツ: item . name
3 {item.name} と同等ですが、括弧表記を使用しています。
4 namespace global , 1つのパーツ: colors

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 literal 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 following section.

仮想メソッドの例
{item.getLabels(1)} (1)
{name or 'John'} (2)
1 名前空間なし、2つのパーツ - item , getLabels(1) 、2番目のパーツは、名前 getLabels と パラメーター 1 を持つ仮想メソッドです。
2 name.or('John') に翻訳される、単一のパラメーターを持つ仮想メソッドに使用できる 中置 (infix) 記法。名前空間なし、2 つのパーツ - name . or('John')

3.4.1. サポートされているリテラル

リテラル

boolean

true, false

null

null

string

'value', "string"

integer

1, -5

long

1l, -5L

double

1D, -5d

float

1f, -5F

3.4.2. 解決

式の最初のパーツは、常に カレントコンテキストオブジェクト に対して解決されます。最初のパーツの結果が見つからない場合は、(利用可能な場合) 親コンテキストオブジェクトに対して解決されます。名前空間で始まる式の場合、カレントコンテキストオブジェクトは、利用可能なすべての NamespaceResolver を使用して検出されます。名前空間で始まらない式の場合、現在のコンテキストオブジェクトはタグの 位置から派生し ます。式の他のすべてのパーツは、前の解決結果に対してすべての ValueResolver を使用して解決されます。

例えば、式 {name} には名前空間がなく、単一のパーツ - name です。"name"は、カレントコンテキストオブジェクトに対して利用可能なすべての値リゾルバを使用して解決されます。しかし、式 {global:colors} には、名前空間 global と単一パーツ - colors があります。まず、現在のコンテキストオブジェクトを見つけるために、利用可能なすべての NamespaceResolver s が使用されます。その後、見つかったコンテキストオブジェクトに対して"colors"を解決するために値リゾルバが使用されます。

テンプレート・インスタンスに渡されたデータは、常に data 名前空間を使用してアクセスできます。これは、キーがオーバーライドされているデータにアクセスするのに便利かもしれません。

<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 はデータオブジェクトとしてテンプレートインスタンスに渡されます。
2 派生アイテムのリストを繰り返し処理します。
3 item は反復要素のエイリアスです。
4 data 名前空間を使用して、 item データ・オブジェクトにアクセスします。

3.4.3. カレントコンテキスト

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 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 セクション内の現在のコンテキストオブジェクトは、解決されたパラメーターのマップです。
カレントコンテキストは、暗黙のバインディング this を通してアクセスできます。

3.4.4. 組込Resolver

名前 説明

Elvis演算子

前のパーツが解決できなかったり、 null に解決された場合のデフォルト値を出力します。

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

orEmpty

前のパーツが解決できなかったり、 null に解決された場合に空のリストを出力します。

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

三項演算子

if-then-else文の省略形。 Ifセクション とは異なり、入れ子になった演算子はサポートされていません。

{item.isActive ? item.name : 'Inactive item'} は、 item.isActivetrue に解決した場合に item.name の値を出力します。

論理 AND 演算子

Ifセクション で説明したように、両方のパーツが falsy でない場合に true を出力します。このパラメーターは必要な場合にのみ評価されます。

{person.isActive && person.hasStyle}

論理 OR 演算子

Ifセクション で説明したように、いずれかのパーツが falsy でない場合に true を出力します。このパラメーターは必要な場合のみ評価されます。

{person.isActive || person.hasStyle}

三項演算子の条件は、 Ifセクション で説明したように、値が falsy とみなされない場合は true と評価されます。
実際には、演算子は1つのパラメーターを消費する「仮想メソッド」として実装されており、中置記法で使用することができます。例えば、{person.name or 'John'}{person.name.or('John')} に翻訳され、{item.isActive ? item.name : 'Inactive item'}{item.isActive.ifTruthy(item.name).or('Inactive item')} に翻訳されます。

3.4.5. 配列

You can iterate over elements of an array with the ループセクション. 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.

配列の例
<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 配列の長さを出力します。
2 配列の最初の要素を出力します。
3 括弧表記で配列の2つ目の要素を出力します。
4 配列の 3 番目の要素を仮想メソッド get() で出力します。
5 Outputs the first two elements of the array.

3.4.6. 文字エスケープ

HTML と XML テンプレートの場合、 ' , " , < , > , & の文字は、テンプレートバリアントが設定されている場合はデフォルトでエスケープされます。

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.

エスケープされていない値をレンダリングする必要がある場合:

  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 titleExpressions & Escapes に解決した場合は、 Expressions & Escapes としてレンダリングされます。
2 paragraph<p>My text!</p> に解決された場合は、 <p>My text!</p> としてレンダリングされます。
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.

3.4.7. 仮想メソッド

仮想メソッドとは、通常のJavaメソッド呼び出しのように見える 式のパーツ です。Javaクラスの実際のメソッドと一致する必要がないため、「仮想」と呼ばれています。実際には、通常のプロパティーと同様に、仮想メソッドも値リゾルバによって処理されます。唯一の違いは、仮想メソッドの場合、値リゾルバが式でもあるパラメーターを消費することです。

仮想メソッドの例
<html>
<h1>{item.buildName(item.name,5)}</h1> (1)
</html>
1 buildName(item.name,5) は、名前 buildName と 2 つのパラメーター item.name5 を持つ仮想メソッドを表します。この仮想メソッドは、以下の Java クラスに対して生成された値リゾルバによって評価されます。
class Item {
   String buildName(String name, int age) {
      return name + ":" + age;
   }
}
仮想メソッドは通常、 @TemplateExtension methods@TemplateData 、または parameter declarations で使用されるクラスのために生成された値リゾルバによって評価されます。しかし、Javaのクラス/メソッドに基づかないカスタムの値リゾルバも登録できます。

単一のパラメーターを持つ仮想メソッドは 中置記法を使用して呼び出すことができます。

中置表記例
<html>
<p>{item.price or 5}</p>  (1)
</html>
1 item.price or 5item.price.or(5) に翻訳されます。

仮想メソッドのパラメーターは、「入れ子」仮想メソッドの呼び出しにすることができます。

入れ子になった仮想メソッドの例
<html>
<p>{item.subtractPrice(item.calculateDiscount(10))}</p>  (1)
</html>
1 item.calculateDiscount(10) が最初に評価され、その後 item.subtractPrice() への引数として渡されます。

3.4.8. CompletionStageUni オブジェクトの評価

java.util.concurrent.CompletionStage および io.smallrye.mutiny.Uni を実装したオブジェクトは、特別な方法で評価されます。式の一部が CompletionStage に解決された場合、このステージが完了すると解決が継続され、式の次の部分(もしあれば)が完了したステージの結果に対して評価されます。たとえば、式 {foo.size} があり、 fooCompletionStage<List<String>> に解決された場合、 size は完了した結果、すなわち List<String> に対して解決されます。式の一部が Uni に解決される場合、 CompletionStageUni#subscribeAsCompletionStage() を使用して Uni から最初に作成され、その後上記のように評価されます。

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.

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.
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().
3.4.8.1. How to Identify a Problematic Part of the Template

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

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

3.4.9. 不明なプロパティ

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.

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.

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

In Quarkus, it’s possible to change the default strategy via the quarkus.qute.property-not-found-strategy as described in the [configuration-reference].
型安全な式タイプセーフテンプレート を使用すると、ビルド時に同様のエラーが検出されます。

3.5. セクション

セクションは:

  • スタートタグを持ちます

    • # で始まり、 {#if}{#each} などのセクションの名前が続きます。

  • 空の場合もあります

    • タグは / で終わります。例: {#emptySection /}

  • 他の表現、セクションなどを含む場合があります。

    • 終了タグは / で始まり、セクションの名前(オプション)を含みます: {#if foo}Foo!{/if} または {#if foo}Foo!{/} .

startタグはパラメーターを定義することもできます。パラメーターにはオプションの名前を付けます。1つのセクションには、いくつかのコンテンツ ブロック を含めることができます。"main" ブロックは常に存在します。追加/入れ子になったブロックも # で始まり、パラメーターを持つことができます - {#else if item.isActive} 。セクションのロジックを定義するセクションヘルパーは、任意のブロックを「実行」し、パラメーターを評価することができます。

{#if item.name is 'sword'}
  It's a sword!
{#else if item.name is 'shield'}
  It's a shield!
{#else}
  Item is neither a sword nor a shield.
{/if}

3.5.1. ループセクション

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

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.

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 zero-based index is odd

  • even - true if the zero-based index is even

  • indexParity - outputs odd or even based on the zero-based index value

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.

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)
{/each}
1 item_count represents one-based index.
2 <br> is only rendered if the iteration has more elements.

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 .

{#for i in total}
  {i}:
{/for}

And the output will be:

1:2:3:

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}

3.5.2. Ifセクション

if セクションは、基本的な制御フローセクションを表しています。最も単純なバージョンでは、単一のパラメーターを受け取り、条件が true と評価された場合にコンテンツをレンダリングします。演算子なしの条件は値が falsy (つまり、nullfalse 、空のコレクション、空のマップ、空の配列、空の文字列/文字列シーケンス、またはゼロに等しい数) ではない場合、 true と評価されます。

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

条件では、以下の演算子を使うこともできます:

オペレーター エイリアス 優先順位 (大優先)

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)

||, or

1

簡単な演算子の例
{#if item.age > 10}
  This item is very old.
{/if}

複数の条件にも対応しています。

複数条件の例
{#if item.age > 10 && item.price > 500}
  This item is very old and expensive.
{/if}

優先順位の規則は、カッコで上書きすることができます。

括弧の例
{#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 ブロックを何個でも追加することができます。

{#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}

3.5.3. When Section

このセクションは、Java の switch や Kotlin の when に似ています。条件が満たされるまで、 テストされた値 をすべてのブロックに対して順次マッチさせます。最初にマッチしたブロックが実行されます。他のすべてのブロックは無視されます(この動作は、 break 文が必要な Java switch とは異なります)。

when / is の名前のエイリアスを使用した例
{#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 パラメーターが1つだけの場合、それは等価かがテストされます。
2 演算子 を使ってマッチングロジックを指定することができます。 Ifセクション とは異なり、入れ子になった演算子はサポートされていません。
3 else は、他のブロックが値に一致しない場合に実行されます。
switch / case の名前エイリアスを使用した例
{#switch person.name}
  {#case 'John'} (1)
    Hey John!
  {#case 'Mary'}
    Hey Mary!
{/switch}
1 caseis の別名です。

enum に解決されるテスト値は特別な扱いを受けます。 is / case ブロックのパラメーターは式として評価されるのではなく、テストされた値に対して toString() を呼び出した結果と比較されます。

{#when machine.status}
  {#is ON}
    It's running. (1)
  {#is in OFF BROKEN}
    It's broken or OFF. (2)
{/when}
1 このブロックは machine.status.toString().equals("ON") の場合に実行されます
2 このブロックは、 machine.status.toString().equals("OFF") または machine.status.toString().equals("BROKEN") の場合に実行されます
列挙型定数は、テストされた値が利用可能な型情報を持ち、列挙型に解決された場合に検証されます。

is / case ブロック条件では、以下の演算子がサポートされています。

オペレーター エイリアス

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}

3.5.4. Let Section

このセクションでは、名前付きローカル変数を定義することができます。

{#let myParent=order.item.parent isActive=false age=10} (1)
  <h1>{myParent.name}</h1>
  Is active: {isActive}
  Age: {age}
{/let} (2)
1 ローカル変数は、 リテラルを表すこともできる式で初期化されます。
2 Keep in mind that the variable is not available outside the let section that defines it.

If a key of a section parameter (aka 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).

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}

3.5.5. With セクション

このセクションは、現在のコンテキストオブジェクトを設定するために使用することができます。これはテンプレート構造を単純化するのに便利かもしれません。

{#with item.parent}
  <h1>{name}</h1>  (1)
  <p>{description}</p> (2)
{/with}
1 nameitem.parent に対して解決されます。
2 descriptionitem.parent に対して解決されます。

Note that the with section should not be used in タイプセーフテンプレート or templates that define 型安全な式. The reason is that it prevents Qute from validating the nested expressions. If possible it should be replaced with the {#let} section which declares an explicit binding:

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

このセクションは、複数の高価な呼び出しを避けたいときにも便利かもしれません。

{#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 thisitem.callExpensiveLogicToGetTheValue(1,'foo',bazinga) の結果です。結果が複数の式で使用されても、このメソッドは一度しか呼び出されません。

3.5.6. Include セクション

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

簡単な例
<html>
<head>
<meta charset="UTF-8">
<title>Simple Include</title>
</head>
<body>
  {#include foo limit=10 /} (1)(2)
</body>
</html>
1 id foo でテンプレートをインクルードします。インクルードされたテンプレートは、現在のコンテキストからデータを参照することができます。
2 また、インクルードされたテンプレートで使用できるオプションのパラメーターを定義することも可能です。

Template inheritance makes it possible to reuse template layouts.

テンプレート「ベース」
<html>
<head>
<meta charset="UTF-8">
<title>{#insert title}Default Title{/}</title> (1)
</head>
<body>
  {#insert}No body!{/} (2)
</body>
</html>
1 insert セクションは、指定されたテンプレートをインクルードするテンプレートでオーバーライドできる部分を指定するために使われます。
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.
テンプレート「詳細」
{#include base} (1)
  {#title}My Title{/title} (2)
  <div> (3)
    My body.
  </div>
{/include}
1 include セクションは、拡張テンプレートを指定するために使用されます。
2 入れ子になったブロックは、オーバーライドする部分を指定するために使用されます。
3 The content of the main block is used for an {#insert} section with no name parameter specified.
セクションブロックはオプションのエンドタグを定義することもできます - {/title} .

3.5.7. ユーザー定義タグ

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 は名前付きパラメーターです。
2 it is a special key that is replaced with the first unnamed parameter of the tag.
3 (オプション) nested-content は、タグの内容に置き換えられる特別なキーです。

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 は反復要素に解決され、タグテンプレートで it キーを使用して参照することができます。
2 タグテンプレートで nested-content キーを使用して注入されたタグコンテンツ。

By default, the tag template can reference data from the parent context. For example, the tag above could use the following expression {items.size}. However, sometimes it might be useful to disable this behavior and execute the tag as an isolated template, i.e. without access to the context of the template that calls the tag. In this case, just add _isolated or _isolated=true argument to the call site, e.g. {#itemDetail item showImage=true _isolated /}.

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 セクションは、指定されたテンプレートをインクルードするテンプレートでオーバーライドできる部分を指定するために使われます。
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>.

3.5.8. Eval Section

This section can be used to parse and evaluate a template dynamically. The behavior is very similar to the Include セクション 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' /} (1)(2)(3)
1 The result of myData.template will be used as the template. The template is executed with the カレントコンテキスト, 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’s not possible to cache the parsed value to conserve resources and optimize the performance.

3.6. レンダリング出力

TemplateInstance は、レンダリングをトリガーして結果を消費するためのいくつかの方法を提供しています。最も簡単な方法は TemplateInstance.render() です。このメソッドは同期レンダリングをトリガーし、レンダリングが終了するまで現在のスレッドをブロックして出力を返します。対照的に、 TemplateInstance.renderAsync()CompletionStage<String> を返し、レンダリングが終了すると完了します。

TemplateInstance.renderAsync()
template.data(foo).renderAsync().whenComplete((result, failure) -> { (1)
   if (failure == null) {
      // consume the output...
   } else {
      // process failure...
   }
};
1 レンダリング終了後に実行されるコールバックを登録します。

また、 Mutiny 型を返す2つのメソッドがあります。 TemplateInstance.createUni() は新しい Uni<String> オブジェクトを返します。 createUni() を呼び出しても、テンプレートはすぐにレンダリングされません。その代わり、 Uni.subscribe() が呼び出されるたびに、テンプレートの新しいレンダリングがトリガーされます。

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

TemplateInstance.createMulti() は新しい Multi<String> オブジェクトを返します。各アイテムはレンダリングされたテンプレートの一部/チャンクを表します。繰り返しになりますが、 createMulti() はレンダリングをトリガしません。代わりに、サブスクライバによって実行がトリガされるたびに、テンプレートが再びレンダリングされます。

TemplateInstance.createMulti()
template.data(foo).createMulti().subscribe().with(buffer:append,buffer::flush);
テンプレートのレンダリングは2つのフェーズに分かれています。最初のフェーズ(非同期)では、テンプレート内のすべての式が解決され、 結果ツリー が構築されます。同期的な第2フェーズでは、結果ツリーが マテリアライズされ 、結果ノードが1つずつ、特定のコンシューマによって消費/バッファリングされるチャンクを出力します。

3.7. エンジン設定

3.7.1. 値リゾルバ

式を評価する際に値リゾルバを使用します。カスタム io.quarkus.qute.ValueResolverEngineBuilder.addValueResolver() からプログラムで登録することができます。

ValueResolver ビルダーの例
engineBuilder.addValueResolver(ValueResolver.builder()
    .appliesTo(ctx -> ctx.getBase() instanceof Long && ctx.getName().equals("tenTimes"))
    .resolveSync(ctx -> (Long) ctx.getBase() * 10)
    .build());

3.7.2. テンプレートロケーター

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 to use the correct character encoding when reading the contents of a template.

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 registered via the @Locate annotation.

3.7.3. コンテンツフィルタ

コンテンツフィルタを使用して、解析前にテンプレートコンテンツを変更することができます。

コンテンツフィルター例
engineBuilder.addParserHook(new ParserHook() {
    @Override
    public void beforeParsing(ParserHelper parserHelper) {
        parserHelper.addContentFilter(contents -> contents.replace("${", "$\\{")); (1)
    }
});
1 Escape all occurrences of ${.

3.7.4. Strict Rendering

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 is a valid value though. It is considered falsy as described in the Ifセクション and does not produce any output.

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

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

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)}.

4. Quarkusとの統合

QuarkusアプリケーションでQuteを使用する場合は、以下の依存関係をプロジェクトに追加してください。

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

Quarkusでは、事前に設定されたエンジンインスタンスが提供され、インジェクションに利用できます。@ApplicationScoped スコープのBean、 io.quarkus.qute.Engine 型のBean、 @Default 修飾子が自動的に登録されます。さらに、 src/main/resources/templates ディレクトリーにあるすべてのテンプレートが検証され、簡単にインジェクションすることができます。

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 Location 修飾子が与えられていない場合は、フィールド名がテンプレートを特定するために使われます。この特定のケースでは、コンテナーはパス src/main/resources/templates/items.html でテンプレートを見つけようとします。
2 Location 修飾子は、 src/main/resources/templates からの相対パスからテンプレートを注入するようにコンテナーに指示します。この場合のフルパスは src/main/resources/templates/detail/items2_v1.html です。
3 設定された Engine インスタンスを注入します。

CDI observerメソッドを通じてエンジン設定に貢献することも可能です。

EngineBuilder オブザーバの例
import io.quarkus.qute.EngineBuilder;

class MyBean {

    void configureEngine(@Observes EngineBuilder builder) {
       builder.addValueResolver(ValueResolver.builder()
                .appliesTo(ctx -> ctx.getBase() instanceof Long && ctx.getName().equals("tenTimes"))
                .resolveSync(ctx -> (Long) ec.getBase() * 10)
                .build());
    }
}

4.1. Template Locator Registration

The easiest way to register template locators 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.

4.2. テンプレートバリアント

コンテンツネゴシエーションに基づいてテンプレートの特定のバリアントをレンダリングすることが有用な場合があります。これは TemplateInstance.setAttribute() を通じて特別な属性を設定することで行うことができます。

class MyService {

    @Inject
    Template items; (1)

    @Inject
    ItemManager manager;

    String renderItems() {
       return items.data("items",manager.findItems()).setAttribute(TemplateInstance.SELECTED_VARIANT, new Variant(Locale.getDefault(),"text/html","UTF-8")).render();
    }
}
quarkus-resteasy-qute を使用している場合、コンテンツネゴシエーションは自動的に行われます。 RESTEasyの統合 を参照してください。

4.3. テンプレートに直接Beansを注入

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 まず、 foo という名前のBeanを見つけ、それをベースオブジェクトとして使用します。
@Named @Dependent beans are shared across all expressions in a template for a single rendering operation, and destroyed after the rendering finished.

All expressions with cdi and inject namespaces are validated during build. 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 method must exist. 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 method must exist.

@Named でアノテーションされたすべてのBeanに対して ValueResolver も生成され、そのプロパティーにリフレクションなしでアクセスできるようになります。
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')}.

4.4. 型安全な式

テンプレート式は、オプションでタイプセーフにすることができます。これは、式が既存のJavaの型とテンプレート拡張メソッドに対して検証されることを意味します。無効な/不正確な式が見つかった場合、ビルドは失敗します。

例えば、 item.name という式があり、 itemorg.acme.Item にマップされている場合、 Item には name というプロパティーがあるか、一致するテンプレート拡張メソッドが存在しなければなりません。

オプションの パラメーター宣言 は、最初のパートがパラメーター名と一致する式に Java 型をバインドするために使用されます。パラメーター宣言はテンプレートで直接指定します。

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.

パラメーター宣言の例
{@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 パラメーター宣言 - fooorg.acme.Foo にマップします。
2 検証されていません - パラメーター宣言にマッチしません。
3 この式は検証されています。org.acme.Foo はプロパティー message を持つか、一致するテンプレート拡張メソッドが存在しなければなりません。
4 同様に、 foo.message から解決されたオブジェクトの Java 型は、プロパティー toLowerCase を持つか、一致するテンプレート拡張メソッドが存在しなければなりません。
パラメーター宣言で使用されるすべての型に対して値リゾルバが自動的に生成され、そのプロパティーにリフレクションなしでアクセスできるようになります。
type-safe templates のメソッドパラメーターは、自動的にパラメーター宣言に変換されます。

セクションは、パラメーター宣言にマッチする名前をオーバーライドできることに注意してください。

{@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 org.acme.Foo に対して検証されています。
2 検証されていません - foo はループセクションでオーバーライドされています。

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.

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.

The type of a default value must be assignable to the type of the parameter declaration, i.e. the following parameter declaration is incorrect and results in a build failure: {@org.acme.Foo foo=1}.

The default value is actually an expression. 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.
The type of a default value is not validated in Qute 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.

4.5. タイプセーフテンプレート

You can also define type-safe templates in your Java code. If using templates in JAX-RS resources, you can rely on the following convention:

  • テンプレートファイルを /src/main/resources/templates ディレクトリーに整理し、リソースクラスごとに 1 つのディレクトリーにグループ化します。つまり、 ItemResource クラスが hellogoodbye の 2 つのテンプレートを参照している場合は、 /src/main/resources/templates/ItemResource/hello.txt/src/main/resources/templates/ItemResource/goodbye.txt に配置します。リソースクラスごとにテンプレートをグループ化することで、テンプレートへのナビゲートが容易になります。

  • 各リソースクラスで、@CheckedTemplate static class Template {} クラスを宣言します。

  • リ ソ ース のテ ン プ レー ト フ ァ イ ルごとに public static native TemplateInstance method(); を 1 つずつ宣言 し ます。

  • テンプレートインスタンスを構築するには、これらの静的メソッドを使用します。

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

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.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 テンプレートを検証する為に、templates/ItemResource/item.htmlTemplateInstance を用意するメソッドと、その Item item パラメーターを宣言します。
2 item パラメーターは自動的に parameter declaration に変換されるので、この名前を参照するすべての式が検証されます。
3 テンプレート内で Item オブジェクトにアクセスできるようにします。
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.

また、 @CheckedTemplate でアノテーションされたトップレベルの Java クラスを宣言することもできます。

トップレベルチェック済テンプレート
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 これは、パス templates/hello.txt を持つテンプレートを宣言します。 name パラメーターは自動的に パラメーター宣言 に変換されるので、この名前を参照するすべての式が検証されます。

そして、テンプレートファイルごとに public static native TemplateInstance method(); を宣言します。これらの静的メソッドを使用してテンプレートインスタンスを構築します。

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

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
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);
    }
}

4.6. テンプレート拡張メソッド

拡張メソッドは、データクラスを新しい機能で拡張するため(アクセス可能なプロパティーとメソッドのセットを拡張するため)、または特定の 名前空間 のための式を解決するために使用することができます。例えば、 計算されたプロパティー仮想メソッド を追加することができます。

値リゾルバは、 @TemplateExtension でアノテーションされたメソッドに対して自動的に生成されます。クラスが @TemplateExtension でアノテーションされている場合、そのクラスで宣言されている プライベートではないスタティック・メソッド ごとに値リゾルバが生成されます。メソッドレベルのアノテーションは、クラスで定義された動作をオーバーライドします。以下の要件を満たさないメソッドは無視されます。

テンプレート拡張メソッドは:

  • private であってはなりません

  • static でなければなりません

  • void を返してはいけません

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.

4.6.1. Matching by Name

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

拡張メソッドの例
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 このメソッドは、 Item.class 型のベース・オブジェクトと discountedPrice プロパティー名を持つ式にマッチします。

このテンプレート拡張メソッドを使うと、以下のテンプレートをレンダリングすることができます。

{item.discountedPrice} (1)
1 itemorg.acme.Item のインスタンスに解決されます。

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"));
}

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.

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.

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"
}
Superfluous matching conditions are ignored. The conditions sorted by priority in descending order are: matchRegex(), matchNames() and matchName().

4.6.2. メソッドパラメーター

拡張メソッドはパラメーターを宣言することができます。名前空間が指定されていない場合、@TemplateAttribute でアノテーションされていない最初のパラメーターが基底オブジェクトを渡すために使用されます。例:最初の例における org.acme.Item 。任意の名前に一致する場合、または正規表現を使用する場合は、文字列メソッドパラメータを使用してプロパティ名を渡す必要があります。@ TemplateAttribute でアノテーションされたパラメーターは、TemplateInstance#getAttribute() を介して取得されます。 他のすべてのパラメーターは、テンプレートをレンダリングするときに解決され、拡張メソッドに渡されます。

複数のパラメーターの例
@TemplateExtension
class BigDecimalExtensions {

    static BigDecimal scale(BigDecimal val, int scale, RoundingMode mode) { (1)
        return val.setScale(scale, mode);
    }
}
1 このメソッドは、 BigDecimal.class 型の基底オブジェクトと、 scale の仮想メソッド名と 2 つの仮想メソッドパラメーターを持つ式にマッチします。
{item.discountedPrice.scale(2,mode)} (1)
1 item.discountedPriceBigDecimal のインスタンスに解決されます。

4.6.3. 名前空間拡張メソッド

TemplateExtension#namespace() が指定された場合、拡張メソッドが、指定された namespace を持つ式を解決するために使用されます。同じ名前空間を共有するテンプレート拡張メソッドは、 TemplateExtension#priority() で順に 1 つのリゾルバにグループ化されます。最初にマッチする拡張メソッドが式の解決に使用されます。

名前空間拡張メソッド例
@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();
   }
}

これらの拡張メソッドは、以下のように使用することができます。

{str:format('%s %s!','Hello', 'world')} (1)
{str:reverse('hello')} (2)
1 出力は Hello world! です
2 出力は olleh です

4.6.4. 組み込みテンプレートエクステンション

Quarkusは、組み込みの拡張メソッドのセットを提供しています。

4.6.4.1. マップ
  • keys または keySet : マップに含まれるキーのセットビューを返します。

    • {#for key in map.keySet}

  • values : マップに含まれる値のコレクションビューを返します。

    • {#for value in map.values}

  • size : マップ内のキーと値の対応付けの個数を返します。

    • {map.size}

  • isEmpty : マップにキーと値のマッピングが含まれていない場合に true を返します。

    • {#if map.isEmpty}

  • get(key) : 指定したキーがマッピングされた値を返します。

    • {map.get('foo')}

マップ値は、直接アクセスすることもできます : {map.myKey} 。正規の識別子ではないキーには大括弧表記を使用します: {map['my key']}

4.6.5. Lists

  • get(index) : リストの指定した位置の要素を返す

    • {list.get(0)}

  • reversed: リストを逆にしたイテレータを返す

    • {#for r in recordsList.reversed}

  • take: 与えられたリストから最初の n 要素を返します。 n が範囲外の場合は IndexOutOfBoundsException を投げます。

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

  • takeLast: 与えられたリストから最後の n 要素を返します。 n が範囲外の場合は IndexOutOfBoundsException を投げます。

    • {#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}

A list element can be accessed directly via an index: {list.10} or even {list[10]}.
4.6.5.1. 数値
  • mod : モデューロ演算

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

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

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

    • {myStr.format(locale,arg1)}

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

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

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

4.6.5.3. 設定
  • config:<name> 又は config:[<name>] : 指定したプロパティー名の設定値を返します。

    • {config:foo} または {config:['property.with.dot.in.name']}

  • config:property(name) : 指定したプロパティー名の設定値を返します。名前は式で動的に取得することが可能です。

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

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

  • config:boolean(name): 指定したプロパティー名の設定値をbooleanとして返します。名前は式で動的に取得することが可能です。

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

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

  • config:integer(name): 指定したプロパティー名の設定値をintegerで返します。名前は式で動的に取得することが可能です。

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

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

4.6.5.4. 時間
  • format(pattern) : java.time パッケージの一時オブジェクトをフォーマットします。

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

  • format(pattern,locale) : java.time パッケージの一時オブジェクトをフォーマットします。

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

  • format(pattern,locale,timeZone) : java.time パッケージの一時オブジェクトをフォーマットします。

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

  • time:format(dateTime,pattern) : java.time パッケージ、 java.util.Datejava.util.Calendarjava.lang.Number の一時オブジェクトをフォーマットします。

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

  • time:format(dateTime,pattern,locale) : java.time パッケージ、 java.util.Datejava.util.Calendarjava.lang.Number の一時オブジェクトをフォーマットします。

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

  • time:format(dateTime,pattern,locale,timeZone) : java.time パッケージ、 java.util.Datejava.util.Calendarjava.lang.Number の一時オブジェクトをフォーマットします。

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

4.7. @TemplateData

値リゾルバは、 @TemplateData でアノテーションされた型に対して自動的に生成されます。これにより、Quarkusでは、実行時にデータにアクセスするためのリフレクションの使用を避けることができます。

非publicのメンバー、コンストラクタ、静的イニシャライザ、静的メソッド、合成メソッド、ボイドメソッドは常に無視されます。
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 の任意のインスタンスをテンプレート内で直接使用することができます。

{#each items} (1)
  {it.price} / {it.discountedPrice}
{/each}
1 itemsorg.acme.Item インスタンスのリストに解決されます。

さらに、 @TemplateData.properties()@TemplateData.ignore() を使用して、生成されるリゾルバを微調整することができます。最後に、アノテーションの「ターゲット」を指定することも可能です。これは、アプリケーションによって管理されないサードパーティーのクラスに便利です。

@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 生成された値リゾルバは、 BigDecimal.setScale() メソッドを呼び出す方法を知っています。

4.7.1. Accessing Static Fields and Methods

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}

4.7.2. Convenient Annotation For Enums

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 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 detects possible namespace collisions and fails the build if a specific namespace is defined by multiple @TemplateData and/or @TemplateEnum annotations.

4.8. 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. Internally, each global variable is added to the data map of any TemplateInstance via the TemplateInstance#data(String, Object) method.

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:  {age} (2)
Colors: {#each myColors}{it}{#if it_hasNext}, {/if}{/each} (3)
1 currentUser resolves to Globals#user().
2 age resolves to Globals#age.
3 myColors resolves to Globals#myColors().
Note that global variables implicitly add parameter declarations to all templates and so any expression that references a global variable is validated during build.
出力
User: Mia
Age:  40
Colors: RED, BLUE

4.8.1. Resolving Conflicts

Global variables may conflict with regular data objects. Type-safe templates override the global variables automatically. For example, the following definition overrides the global variable supplied by the Globals#user() method:

タイプセーフテンプレート定義
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().

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.

4.9. ネイティブ実行可能ファイル

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 type-safe templates or type-safe expressions

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

    • これが望ましい解決策です

  • Annotate the model class with @TemplateData - 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

4.10. RESTEasyの統合

JAX-RSアプリケーションでQuteを使用したい場合、使用しているJAX-RSスタックによっては、まず適切なエクステンションを登録する必要があります。トラディショナルな quakus-resteasy のエクステンションを使用している場合は、 以下を pom.xml ファイルに追加してください。

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

代わりに quarkus-resteasy-reactive エクステンションを介して RESTEasy Reactive を使用している場合は、 次を pom.xml ファイルに追加してください。

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

これらのエクステンションはどちらも、リソースメソッドが TemplateInstance を返すことを可能にする特別な ContainerResponseFilter 実装を登録しています。そのため、ユーザーは必要なすべての内部ステップを処理する必要がなくなります。

最終的には、JAX-RSリソース内でQuteを使用すると、次のようにシンプルに見えるかもしれません。

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

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

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 @Location の修飾子が指定されていない場合は、フィールド名がテンプレートの場所を特定するために使用されます。この特定のケースでは、パスが templates/hello.txt であるテンプレートを注入しています。
2 Template.data() は、実際のレンダリングがトリガーされる前にカスタマイズできる新しいテンプレートインスタンスを返します。この場合、nameの値をキー name にセットします。データマップはレンダリング中にアクセス可能です。
3 レンダリングをトリガーしないことに注意してください - これは特別な ContainerResponseFilter の実装によって自動的に行われます。
ユーザーは、特定のJAX-RSリソースのテンプレートを整理し、自動的に タイプセーフ表現 を有効にするのに役立つ タイプセーフテンプレート を使用することが推奨されます。

コンテンツネゴシエーションは自動的に実行されます。結果の出力はクライアントから受け取った Accept ヘッダーに依存します。

@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 注入されたフィールドから派生したベースパスを持つバリアントテンプレートを注入します - src/main/resources/templates/item
2 text/plain では src/main/resources/templates/item.txt のテンプレートが使用されます。 text/html では META-INF/resources/templates/item.html のテンプレートが使用されます。

RestTemplate utilクラスは、JAX-RSリソースメソッドのボディからテンプレートインスタンスを取得するために使用することができます。

RestTemplateの例
@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 テンプレートの名前は、リソースクラスとメソッド名から派生しています。このケースでは、DetailResource/item です。
@Inject とは異なり、 RestTemplate 経由で取得したテンプレートは検証されません。即ち、テンプレートが存在しなくてもビルドは失敗しません。

4.11. 開発モード

開発モードでは、 src/main/resources/templates にあるすべてのファイルの変更が監視され、変更はすぐに表示されます。

4.12. タイプセーフメッセージバンドル

4.12.1. 基本概念

基本的な考え方は、すべてのメッセージは潜在的に非常にシンプルなテンプレートであるということです。型エラーを防ぐために、メッセージは メッセージバンドルインターフェース のアノテーションメソッドとして定義されます。Quarkusは、ビルド時に メッセージバンドルの実装を 生成します。そのため、実行時にバンドルを使用することができます。

  1. io.quarkus.qute.i18n.MessageBundles#get() を経由し、直接コードの中で使用。例: MessageBundles.get(AppMessages.class).hello_name("Lucie")

  2. @Inject であなたのBeanに注入。 例: @Inject AppMessages

  3. メッセージ・バンドル名前空間を介してテンプレート内で参照。

     {msg:hello_name('Lucie')} (1) (2) (3)
     {msg:message(myKey,'Lu')} (4)
    1 msg がデフォルトの名前空間です。
    2 hello_name がメッセージキーです。
    3 Lucie はメッセージバンドルインターフェースメソッドのパラメーターです。
    4 また、予約されたキー message を使用して、実行時に解決されたキーのローカライズされたメッセージを取得することも可能である。この場合、検証はスキップされます。
Message Bundle インタフェース例
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 メッセージバンドルインターフェイスを表します。 バンドル名のデフォルトは msg で、テンプレート式の名前空間として使用されます。例: {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 メソッドのパラメーターはテンプレートで使用することができます。

4.12.2. バンドル名とメッセージキー

キーはテンプレート内で直接使用されます。バンドル名はテンプレート式の名前空間として使用されます。 @MessageBundle は、メソッド名からメッセージキーを生成するために使用されるデフォルトのストラテジーを定義するために使用することができます。しかし、 @Message はこのストラテジーをオーバーライドし、カスタム・キーを定義することもできます。デフォルトでは、アノテーションされた要素の名前がそのまま使用されます。他の可能性としては、以下のようなものがあります。

  1. キャメルケースを止め、ハイフン化したもの。 例: helloName()hello-name

  2. キャメルケースを止め、パーツをアンダースコア区切りとしたもの。例: helloName()hello_name

4.12.3. バリデーション

  • すべてのメッセージバンドルテンプレートは検証されます。

    • 名前空間を持たない全ての式はパラメーターにマッピングする必要があります。例: Hello {foo} の場合、メソッドは foo という名前のパラメータを持つ必要があります。

    • すべての式はパラメーターの型に対して検証されます。例: Hello {foo.bar} で、パラメーター foo の型が org.acme.Foo の場合、 org.acme.Foobar という名前のプロパティーを持つ必要があります。

      未使用の パラメーターごとに警告メッセージが記録されます。
  • {msg:hello(item.name)} のようなメッセージバンドルメソッドを参照する式も検証されます。

4.12.4. ローカライゼーション

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. @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.
ローカライズされたインターフェースの例
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 値はロケールタグ文字列(IETF)です。
2 値はローカライズされたテンプレートです。

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 the locate tag (IETF). 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.

ローカライズされたファイルの例 - 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 キーと値は等号で区切られています。
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.
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!

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!.

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 属性例
@Singleton
public class MyBean {

    @Inject
    Template hello;

    String render() {
       return hello.instance().setAttribute("locale", Locale.forLanguageTag("cs")).render(); (1)
    }
}
1 Locale インスタンスかロケールタグ文字列 (IETF) をセットすることが出来ます。
When using quarkus-resteasy-qute the locale attribute is derived from the Accept-Language header if not set by a user.

@Localized 修飾子を使用して、ローカライズされたMessage Bundleインタフェースを注入することができます。

注入されたローカライズされた Message Bundle例
@Singleton
public class MyBean {

    @Localized("cs") (1)
    AppMessages msg;

    String render() {
       return msg.hello_name("Jachym");
    }
}
1 アノテーションの値はロケールタグ文字列(IETF)です。

4.12.5. Message Templates

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.

4.13. 設定リファレンス

ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能

Configuration property

タイプ

デフォルト

The list of suffixes used when attempting to locate a template file.

By default, engine.getTemplate("foo") would result in several lookups: foo, foo.html, foo.txt, etc.

Environment variable: QUARKUS_QUTE_SUFFIXES

list of string

qute.html,qute.txt,html,txt

The list of exclude rules used to intentionally ignore some parts of an expression when performing type-safe validation. An element value must have at least two parts separated by dot. The last part is used to match the property/method name. The prepended parts are used to match the class name. The value * can be used to match any name. Examples: - org.acme.Foo.name - exclude the property/method name on the org.acme.Foo class - org.acme.Foo.* - exclude any property/method on the org.acme.Foo class - *.age - exclude the property/method age on any class

Environment variable: QUARKUS_QUTE_TYPE_CHECK_EXCLUDES

list of string

This regular expression is used to exclude template files from the templates directory. Excluded templates are neither parsed nor validated during build and are not available at runtime. The matched input is the file path relative from the templates directory and the / is used as a path separator. By default, the hidden files are excluded. The name of a hidden file starts with a dot.

Environment variable: QUARKUS_QUTE_TEMPLATE_PATH_EXCLUDE

Pattern

^\..|.\/\..*$

The prefix is used to access the iteration metadata inside a loop section. A valid prefix consists of alphanumeric characters and underscores. Three special constants can be used: - <alias_> - the alias of an iterated element suffixed with an underscore is used, e.g. item_hasNext and it_count - <alias?> - the alias of an iterated element suffixed with a question mark is used, e.g. item?hasNext and it?count - <none> - no prefix is used, e.g. hasNext and count By default, the <alias_> constant is set.

Environment variable: QUARKUS_QUTE_ITERATION_METADATA_PREFIX

string

<alias_>

The list of content types for which the ', ", <, > and & characters are escaped if a template variant is set.

Environment variable: QUARKUS_QUTE_ESCAPE_CONTENT_TYPES

list of string

text/html,text/xml,application/xml,application/xhtml+xml

The default charset of the templates files.

Environment variable: QUARKUS_QUTE_DEFAULT_CHARSET

Charset

UTF-8

The strategy used when a standalone expression evaluates to a "not found" value at runtime and the io.quarkus.qute.strict-rendering config property is set to false This strategy is never used when evaluating section parameters, e.g. {#if foo.name}. In such case, it’s the responsibility of the section to handle this situation appropriately. By default, the NOT_FOUND constant is written to the output. However, in the development mode the PropertyNotFoundStrategy#THROW_EXCEPTION is used by default, i.e. when the strategy is not specified.

Environment variable: QUARKUS_QUTE_PROPERTY_NOT_FOUND_STRATEGY

defaultOutput the NOT_FOUND constant., noopNo operation - no output., throw-exceptionThrow a TemplateException., output-originalOutput the original expression string, e.g. {foo.name}.

Specify whether the parser should remove standalone lines from the output. A standalone line is a line that contains at least one section tag, parameter declaration, or comment but no expression and no non-whitespace character.

Environment variable: QUARKUS_QUTE_REMOVE_STANDALONE_LINES

boolean

true

If set to true then any expression that is evaluated to a Results.NotFound value will always result in a TemplateException and the rendering is aborted. Note that the quarkus.qute.property-not-found-strategy config property is completely ignored if strict rendering is enabled.

Environment variable: QUARKUS_QUTE_STRICT_RENDERING

boolean

true

The global rendering timeout in milliseconds. It is used if no timeout template instance attribute is set.

Environment variable: QUARKUS_QUTE_TIMEOUT

long

10000

If set to true then the timeout should also be used for asynchronous rendering methods, such as TemplateInstance#createUni() and TemplateInstance#renderAsync().

Environment variable: QUARKUS_QUTE_USE_ASYNC_TIMEOUT

boolean

true

The additional map of suffixes to content types. This map is used when working with template variants. By default, the java.net.URLConnection#getFileNameMap() is used to determine the content type of a template file.

Environment variable: QUARKUS_QUTE_CONTENT_TYPES

Map<String,String>

5. Quteをスタンドアロンのライブラリとして使用

Quteは、主にQuarkusのエクステンションとして設計されています。しかし、「スタンドアロン」のライブラリとして使用することも可能です。この場合、いくつかの機能は利用できず、いくつかの追加設定が必要となります。

エンジン

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

テンプレート
  • By default, no template locators 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).

セクション
  • 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.

値リゾルバー
  • ValueResolvers は自動的に生成されません。

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

    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.

    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.
ユーザー定義タグ
  • No user-defined tags are registered automatically.

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

型安全
注入

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.