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

Qute リファレンスガイド

Qute は、Quarkus のニーズを満たすために特別に設計されたテンプレートエンジンです。ネイティブイメージのサイズを小さくするために、リフレクションの使用は最小限に抑えられています。API は、命令型とノンブロッキングリアクティブ型の両方のスタイルのコーディングを組み合わせています。開発モードでは、 src/main/resources/templates フォルダーにあるすべてのファイルが変更があるかどうか監視され、変更はアプリケーションにすぐに表示されます。さらに、ビルド時にテンプレートの問題のほとんどの検出を試み、即座に失敗します。

このガイドでは、 入門編のサンプルコア機能 の説明、 Quarkus 統合 の詳細について説明しています。

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. Find more information about the limitations and possibilities in the スタンドアローンライブラリーとして使用する場合のQute section.

1. 最も簡単な例

Qute を試す最も簡単な方法は、便利な io.quarkus.qute.Qute クラスを使用し、簡単なメッセージをフォーマットするために使用できる fmt() 静的メソッドの 1 つを呼び出すことです。

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 空の式 {} はプレースホルダーであり、インデックスベースの配列アクセスサー (例えば {data[0]}) に置き換えられます。
2 代わりにデータマップを提供することができます。
3 より複雑なフォーマットを必要とする場合には、ビルダーライクな API が利用可能です。
4 "text/html" テンプレートでは、デフォルトで特殊な char が html エンティティーに置き換えられることに注意してください。
5 テンプレートには、任意の ビルディングブロック を使用することができます。この場合、If セクション は、入力データに基づいてメッセージの適切な部分をレンダリングするために使用されます。
Quarkus では、メッセージのフォーマットに使用されるエンジンは @Inject Engine によって注入されるものと同じです。したがって、テンプレート拡張メソッドテンプレートに直接Beansを注入 または タイプセーフメッセージバンドル のような Quarkus 固有の統合機能を利用することができます。

Qute.fmt(String) メソッドが返すフォーマットオブジェクトは、遅延評価され、たとえば、ログメッセージとして使用することができます。

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 をお読みください。

2. ハローワールドの例

この例では、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 を参照してください。

最後に テンプレートインスタンス を作成し、データをセットして出力をレンダリングします。

// 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. テンプレートコンテンツを作成する ( hello.html )

  2. テンプレートの定義を解析する ( io.quarkus.qute.Template )

  3. テンプレートインスタンスを作成する ( io.quarkus.qute.TemplateInstance )

  4. 出力をレンダリングする

Engine はテンプレート定義をキャッシュすることができるので、何度も内容をパースする必要がありません。Quarkus では、キャッシュは自動的に行われます。

3. コア機能

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

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

コメント

コメントは {! というシーケンスで始まり、 !} というシーケンスで終わり、例えば {! This is a comment !} のようになります。 複数行にすることができ、 {! {#if true} !} のように式やセクションを含むことができます。 コメントの内容は、出力をレンダリングする際に完全に無視されます。

は評価値を出力します。これは 1つ以上のパーツから構成されます。パーツは {foo}{item.name} のような単純なプロパティーや、 {item.get(name)}{name ?: 'John'} のような仮想メソッドを表わすことができます。また、式は名前空間 {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 /}. 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.

パース対象外文字データ

レンダリングは行うが パースは行わない という内容をマークするために使用されます。 これは {| というシーケンスで始まり、 |} というシーケンスで終わり( {|<script>if(true){alert('Qute is cute!')};</script>|} 、複数行になる可能性もあります。

以前はパース対象外文字データを {[ and end with ]} から始めることができました。この構文は、他の言語の構文とよく衝突するため、現在では削除されています。

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 無視対象: " で開始
エスケープシーケンス \{ and \} を使ってテキストにデリミタを挿入することも可能です。 実際には、エスケープシーケンスは通常、開始デリミタのためにのみ必要とされます。すなわち、\{foo}{foo} として描画されます(パース、評価は行われません)。

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

式が評価され、値が出力されます。これには 1 つ以上のパーツがあり、各パーツはプロパティーアクセサー (別名 Field Access Expression) または仮想メソッド呼び出し (別名 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 consists of alphanumeric characters and underscores. Namespace expressions are resolved differently - see also 解決.

プロパティーアクセサーの例
{name} (1)
{item.name} (2)
{item['name']} (3)
{global:colors} (4)
1 名前空間がなし、1 つの部分: name
2 名前空間なし、2つの部分: item . name
3 {item.name} と同じですが、ブラケット表記を使います。
4 名前空間 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. サポートされているリテラル

リテラル

ブーリアン

true, false

null

null

string

'value', "string"

integer

1, -5

1l, -5L

double

1D, -5d

float

1f, -5F

3.4.2. 解決

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 NamespaceResolvers. 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 ValueResolvers against the result of the previous resolution.

例えば、式 {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. 組込リゾルバ

Name Description

エルビス演算子

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

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

orEmpty

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

{pets.orEmpty.size} は、pets が解決可能でないか、null であれば 0 を出力します。

Ternary Operator

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

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

論理 AND 演算子

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

{person.isActive && person.hasStyle}

論理 OR 演算子

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

{person.isActive || person.hasStyle}

The condition in a ternary operator evaluates to true if the value is not considered falsy as described in If セクション.
実際、オペレーターは 1 つのパラメーターを消費する "仮想メソッド" として実装されており、infix 記法で使用することができます。例えば、{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 ループセクション. 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 配列の最初の 2 要素を出力します。

3.4.6. 文字エスケープ

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

Quarkusでは、 src/main/resources/templates にあるテンプレートに対してバリアントが自動的に設定されます。デフォルトでは、テンプレートファイルのコンテンツタイプを決定するために java.net.URLConnection#getFileNameMap() が使用されます。コンテンツタイプへのサフィックスの追加マップは、 quarkus.qute.content-types を通じて設定できます。

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

  1. java.lang.Object の拡張メソッドとして実装されている raw または safe プロパティーを使用します。

  2. String の値を io.quarkus.qute.RawString でラップします。

<html>
<h1>{title}</h1> (1)
{paragraph.raw} (2)
</html>
1 titleExpressions & Escapes に解決した場合は、 Expressions &amp; Escapes としてレンダリングされます。
2 <p>My text!</p> に解決する paragraph は、<p>My text!</p> としてレンダリングされます。
デフォルトでは、text/htmltext/xmlapplication/xml および application/xhtml+xml コンテンツタイプのいずれかを持つテンプレートがエスケープされます。しかし、quarkus.qute.escape-content-types 設定プロパティーで、このリストを拡張することができます。

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 から最初に作成され、その後上記のように評価されます。

Uni#subscribeAsCompletionStage() は新しいサブスクリプションになることに注目してください。 Uni の項目や障害がテンプレートデータとして使用される前に、メモ化を設定する必要があるかもしれません。つまり、 myUni.memoize().indefinitely() .

CompletionStage が完了しなかったり、あるいは Uni がアイテム/失敗を発しないということが起こり得ます。この場合、レンダリングメソッド (TemplateInstance#render() および TemplateInstance#createUni()) は、特定のタイムアウトを過ぎると失敗します。このタイムアウトは、テンプレートインスタンスの timeout 属性として指定することができます。timeout 属性が設定されていない場合、グローバルレンダリングタイムアウトが使用されます。

Quarkus では、デフォルトのタイムアウトは io.quarkus.qute.timeout 設定プロパティーで設定することができます。Qute をスタンドアロンで使用する場合は、EngineBuilder#timeout() メソッドを使用することができます。
以前のバージョンでは、TemplateInstance#render() メソッドだけがタイムアウト属性を尊重していました。たとえば、templateInstance.createUtni().ifNoItem().after(Duration.ofMillis(500)).fail() のように、io.quarkus.qute.useAsyncTimeout=false config プロパティーを使って、古い動作を保持しタイムアウトを自分で処理することが可能です。
3.4.8.1. テンプレートの問題個所の特定方法

タイムアウトが発生したときに、テンプレートの問題のあるパーツを見つけるのは簡単ではありません。ロガー io.quarkus.qute.nodeResolveTRACE レベルを設定し、その後ログ出力の解析を試みることができます。

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

テンプレートで使用されるすべての式とセクションについて、次のような一対のログメッセージが表示されるはずです。

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 のログメッセージがない場合は、調査すべき良い候補があります。

3.4.9. 不明なプロパティ

式が実行時に評価されない場合があります。たとえば、{person.age} という式があり、Person クラスに age というプロパティーが宣言されていない場合です。strict レンダリング が有効かどうかによって、動作が異なってきます。

これを有効にすると、プロパティーが見つからない場合、常に TemplateException が発生し、レンダリングが中断されます。このエラーを抑制するために、default valuessafe expressions を使用することができます。

無効な場合、デフォルトで特別な定数 NOT_FOUND が出力に書き込まれます。

Quarkus では、[設定リファレンス] で説明しているように、quarkus.qute.property-not-found-strategy を使用して、デフォルトのストラテジーを変更することが可能です。
タイプセーフな式タイプセーフテンプレート を使用した場合、ビルド時に同様のエラーが検出されます。

3.5. セクション

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}

ユーザー定義タグ

{#fragment}

{#cached}

3.5.1. Parameters

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 expression and evaluated before use.

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

`#if`セクションの例
{#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 これがメインブロックです。
2 追加ブロック。
3 追加ブロック。

3.5.2. ループセクション

ループセクションでは、IterableIterator、array、Map (要素は Map.Entry)、StreamIntegerint (プリミティブ値) のインスタンスに対して反復処理することができます。パラメーター値に null を指定すると、no-op となります。

このセクションには 2 つのフレーバーがあります。最初のものは each という名前を使い、it は反復処理要素の暗黙のエイリアスです。

{#each items}
  {it.name} (1)
{/each}
1 name は現在の反復要素に対して解決されます。

もう一つの形式は、for という名前を使い、反復要素を参照するために使われるエイリアスを指定します。

{#for item in items} (1)
  {item.name}
{/for}
1 item は、反復処理要素に使用されるエイリアスです。

また、ループ内の反復処理メタデータには、以下のキーでアクセスすることが可能です。

  • count - 1 を基準としたインデックス

  • index - ゼロ基準のインデックス

  • hasNext - イテレーションにさらに要素がある場合は true とります。

  • isLast - true if hasNext == false

  • isFirst - true if count == 1

  • odd - 要素のカウントが奇数である場合 true

  • hasNext - イテレーションにさらに要素がある場合に true

  • indexParity - は、カウント値に応じて odd または even を出力します

しかし、キーを直接使うことはできません。その代わり、外部スコープの変数と競合する可能性を避けるために、接頭辞が使用されます。デフォルトでは、反復処理された要素のエイリアスの後にアンダースコアを付けたものが接頭辞として使用されます。たとえば、{#each} セクション ({it_hasNext}) の中では、hasNext キーは it_ を接頭辞として使用する必要があります。

each イテレーションのメタデータの例
{#each items}
  {it_count}. {it.name} (1)
  {#if it_hasNext}<br>{/if} (2)
{/each}
1 it_count は 1 を基準としたインデックスを表します。
2 <br> は、反復処理がより多くの要素を持っている場合にのみレンダリングされます。

また、{#for} セクションの中で {item_hasNext} の形式で、item 要素のエイリアスと使用する必要があります。

for イテレーションのメタデータの例
{#for item in items}
  {item_count}. {item.name} (1)
  {#if item_hasNext}<br>{/if} (2)
{/for}
1 item_count は 1 を基準としたインデックスを表します。
2 <br> は、反復処理がより多くの要素を持っている場合にのみレンダリングされます。

反復処理メタデータの接頭辞は、スタンドアロンの Qute では EngineBuilder.iterationMetadataPrefix() によって、Quarkus アプリケーションでは quarkus.qute.iteration-metadata-prefix 設定プロパティーによって設定することができます。以下の 3 つの特別な定数を使用することができます。

  1. <alias_> - アンダースコアを接尾辞とする反復子要素のエイリアスを使用します (デフォルト)

  2. <alias?> - クエスチョンマークを接尾辞とする反復処理された要素のエイリアスを使用します。

  3. <none> - 接頭辞は使用しません。

for 文は、1 から始まる整数でも動作します。 以下の例では、 total = 3 です。

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

出力は、以下のようになります。

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

ループセクションは、反復する項目がないときに実行される {#else} ブロックも定義することができます。

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

3.5.3. 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.4. When セクション

このセクションは、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 It is possible to use an operator to specify the matching logic. Unlike in the If セクション nested operators are not supported.
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") の場合に実行されます
enum 定数は、テストされた値が利用可能な型情報を持ち、列挙型に解決された場合に検証されます。

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

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

{#let myParent=order.item.parent isActive=false age=10 price=(order.price + 10)} (1)(2)
  <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 literal, i.e. isActive=false and age=10.
2 中置記法は、括弧を使用してグループ化する場合にのみサポートされます。例えば、 price=(order.price + 10)price=order.price.plus(10) と同等です。
3 この変数は、let セクションを定義している外では利用できないことに留意してください。

ローカル変数名などのセクションパラメータのキーが ? で終わっている場合、 ? を除いたキーが null または "not found" に解決される場合にのみ、ローカル変数が設定されます:

{#let enabled?=true} (1) (2)
  {#if enabled}ON{/if}
{/let}
1 true は事実上 default value で、親スコープが enabled をすでに定義していない場合にのみ使用されます。
2 enabled?=trueenabled=enabled.or(true) の短縮版です。

このセクションタグも set のエイリアスで登録されています。

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

3.5.6. 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, replace it 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.7. Include セクション

このセクションは、別のテンプレートをインクルードし、場合によってはテンプレートの一部をオーバーライドするために使用することができます (下記の テンプレートの継承 を参照)。

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

テンプレートの継承 により、テンプレートレイアウトの再利用が可能になります。

テンプレート "base"
<html>
<head>
<meta charset="UTF-8">
<title>{#insert title}Default Title{/}</title> (1)
</head>
<body>
  {#insert}No body!{/} (2)
</body>
</html>
1 insert セクションは、指定されたテンプレートをインクルードするテンプレートでオーバーライドできるパーツを指定するために使われます。
2 insert セクションは、オーバーライドされない場合にレンダリングされるデフォルトのコンテンツを定義することができます。もし名前が与えられていない場合は、関連する {#include} セクションのメインブロックが使用されます。
テンプレート "detail"
{#include base} (1)
  {#title}My Title{/title} (2)
  <div> (3)
    My body.
  </div>
{/include}
1 include セクションは、拡張テンプレートを指定するために使用されます。
2 入れ子になったブロックは、オーバーライドするパーツを指定するために使用されます。
3 メインブロックのコンテンツは、name パラメーターが指定されていない {#insert} セクションに使用されます。
セクションブロックは、オプションのエンドタグ ({/title}) を定義することもできます。

3.5.8. ユーザー定義タグ

ユーザー定義タグは、タグテンプレート をインクルードし、オプションでいくつかの引数を渡し、場合によってはテンプレートの一部をオーバーライドするために使用することができます。たとえば、itemDetail.html というタグテンプレートがあるとします。

{#if showImage} (1)
  {it.image} (2)
  {nested-content} (3)
{/if}
1 showImage は名前付きパラメーターです。
2 it は、タグの最初の名前のないパラメーターに置き換えられる特別なキーです。
3 (オプション) nested-content は、タグの内容に置き換えられる特別なキーです。

Quarkus では、src/main/resources/templates/tags にあるすべてのファイルが、自動的に登録および監視されます。Qute スタンドアロンでは、解析されたテンプレートを itemDetail.html という名前で置き、関連する UserTagSectionHelper をエンジンに登録する必要があります。

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

そして、以下のようなタグを呼び出すことができます。

<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, 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 /}.

3.5.8.1. Arguments

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.

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" /}.

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 is also iterable of java.util.Map.Entry: {#each _args}{it.key}={it.value}{/each}.

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"
3.5.8.2. Inheritance

ユーザータグも、通常の {#include} セクションと同じように、テンプレートの継承を利用することができます。

タグ myTag
This is {#insert title}my title{/title}! (1)
1 insert セクションは、指定されたテンプレートをインクルードするテンプレートでオーバーライドできるパーツを指定するために使われます。
タグ呼び出しサイト
<p>
  {#myTag}
    {#title}my custom title{/title} (1)
  {/myTag}
</p>
1 結果は、<p>これは私のカスタムタイトル!</p> のようになります。

3.5.9. フラグメント

フラグメントは、別個のテンプレートとして扱える、つまり別々にレンダリングできるテンプレートの一部分を表します。この機能を導入した主な動機の1つは、 htmxフラグメント のようなユースケースをサポートするためでした。

フラグメントは {#fragment} セクションで定義することができます。 各フラグメントは、英数字とアンダースコアのみからなる識別子を持ちます。

フラグメント識別子は、テンプレート内で一意でなければならないことに注意してください。
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 識別子 item_aliases を持つフラグメントを定義します。識別子には、英数字とアンダースコアしか使用できないことに注意してください。

io.quarkus.qute.Template.getFragment(String) メソッドにより、プログラム的にフラグメントを取得することができます。

フラグメントの取得
@Inject
Template item;

String useTheFragment() {
   return item.getFragment("item_aliases") (1)
            .data("aliases", List.of("Foo","Bar")) (2)
            .render();
}
1 識別子 item_aliases を持つテンプレートフラグメントを取得します。
2 データが正しく設定されているようにします。

上記のスニペットは、次のようにレンダリングされるはずです:

<h2>Aliases</h2>
<ol>
    <li>Foo</li>
    <li>Bar</li>
</ol>
In Quarkus, it is also possible to define a type-safe fragment.

また、 {#include} セクションを持つフラグメントを、別のテンプレートやフラグメントを定義するテンプレートの中に含めることもできます。

`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 /} (1)(2)
1 ドル記号 $ を含むテンプレート識別子は、フラグメントを表します。 item$item_aliases の値は、次のように訳されます: テンプレート item からフラグメント item_aliases を使用します。
2 aliases パラメーターは、関連するデータを渡すために使用されます。データが正しく設定されていることを確認する必要があります。この特定のケースでは、フラグメントは {#for alias in aliases} セクションの aliases の値として式 user.aliases を使用します。
同じテンプレートからフラグメントを参照する場合は、 $ の前の部分、つまり {#include $item_aliases /} のような部分をスキップしてください。
この機能を無効にするために、 {#include item$item_aliases _ignoreFragments=true /} を指定することができます。つまり、テンプレート識別子にドル記号 $ を指定しても、フラグメント検索は行われません。
3.5.9.1. 非表示 (hidden) フラグメント

デフォルトでは、フラグメントは通常、オリジナルテンプレートの一部としてレンダリングされます。しかし、 rendered=false を使って、フラグメントを 非表示 にするのが便利な場合もあります。興味深い使用例として、フラグメントを定義したテンプレートの中で複数回使用できるフラグメントがあります。

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 フラグメント strong をインクルードし、値を渡します。構文 $strong に注意してください。これは、現在のテンプレートからフラグメント strong をインクルードするように処理されます。
3 フラグメント strong をインクルードし、値を渡す。

上のスニペットは、次のようにレンダリングされます:

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

3.5.10. Eval セクション

This section can be used to parse and evaluate a template dynamically. The behavior is very similar to Include セクション but:

  1. テンプレートの内容は、io.quarkus.qute.TemplateLocator を介して取得されるのではなく、直接渡されます。

  2. 評価済みテンプレートの一部をオーバーライドすることはできません。

{#eval myData.template name='Mia' /} (1)(2)(3)
1 myData.template の結果がテンプレートとして使用されます。テンプレートは カレントコンテキスト で実行されます。つまり、インクルードされているテンプレートのデータを参照することができます。
2 また、評価済みテンプレートで使用可能なオプションのパラメーターを定義することも可能です。
3 セクションの内容は常に無視されます。
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.

3.5.11. キャッシュされたセクション

めったに変更されないテンプレートの一部をキャッシュすることが実用的な場合もあります。キャッシュ機能を使うには、組み込みの 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();
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.

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

3.6. レンダリング出力

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

TemplateInstance.renderAsync() Example
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() Example
template.data(foo).createUni().subscribe().with(System.out::println);

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);
テンプレートのレンダリングは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. テンプレートロケーター

テンプレートは、手動で登録するか、テンプレートロケータを介して自動的に登録することができます。ロケーターは、 Engine.getTemplate() メソッドが呼び出されたときに、エンジンがキャッシュに保存している所定の ID のテンプレートがない場合に使用されます。ロケーターは、テンプレートの内容を読み取る際に、正しい文字エンコーディングを使用する責任があります。

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 by using the @Locate annotation.

3.7.3. コンテンツフィルタ

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

コンテンツフィルター例
engineBuilder.addParserHook(new ParserHook() {
    @Override
    public void beforeParsing(ParserHelper parserHelper) {
        parserHelper.addContentFilter(contents -> contents.replace("${", "$\\{")); (1)
    }
});
1 すべての ${ との一致をエスケープします。

3.7.4. strict レンダリング

strict レンダリングにより、開発者は、タイプミスや無効な式によって引き起こされるわかりにくいエラーをキャッチすることができます。この機能を有効にすると、解決できない式、つまり io.quarkus.qute.Results.NotFound のインスタンスとして評価される式は、常に TemplateException となり、レンダリングは中断されます。NotFound 値は基本的に、値リゾルバーが式を正しく解決できなかったことを意味するため、エラーとみなされます。

null is a valid value though. It is considered falsy as described in If セクション and does not produce any output.

strict レンダリングはデフォルトで有効になっています。しかし、io.quarkus.qute.EngineBuilder.strictRendering(boolean) によって、この機能を無効にすることができます。

Quarkus では、専用の config プロパティーである quarkus.qute.strict-rendering を代わりに使用することができます。

"not found" エラーを引き起こす可能性のある式をどうしても使いたい場合は、デフォルト値 および 安全な式 を使ってエラーを抑制することができます。デフォルト値は、式の前のパーツが解決できないか、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)} となります。

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 インスタンスを注入します。

4.1. エンジンのカスタマイズ

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

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 CDI Beanインスタンスは実行時に使用されます - これはファクトリーが注入ポイントを定義できることを意味します
3 Validate that foo parameter is always present; e.g. {#custom foo='bar' /} is ok but {#custom /} results in a build failure.
4 レンダリング時に注入された Service を使用します。

The @EngineConfiguration annotation can be also used to register ValueResolvers and NamespaceResolvers.

4.1.1. テンプレートロケーターの登録

テンプレートロケーター を登録する最も簡単な方法は、これらを CDI Bean にすることです。テンプレート検証を行うビルド時にはカスタムロケーターは利用できないので、@Locate アノテーションで検証を無効化する必要があります。

カスタムロケーターの例
@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 実行時に、カスタムロケーターによって bar.html という名前のテンプレートが配置されます。
2 正規表現 foo.* は、名前が foo で始まるテンプレートのバリデーションを無効にします。
3 注入フィールドは @Locate でアノテーションされたテンプレートロケーターとして解決され、シングルトンセッション Bean として登録されます。

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();
    }
}
When using quarkus-rest-qute or quarkus-resteasy-qute the content negotiation is performed automatically. For more information, see the REST Integration section.

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

@Named でアノテーションされた CDI Bean は、cdiinject 名前空間を通して、任意のテンプレートで参照することができます。

{cdi:personService.findPerson(10).name} (1)
{inject:foo.price} (2)
1 まず、personService という名前の Bean が見つかり、それをベースオブジェクトとして使用します。
2 まず、 foo という名前のBeanを見つけ、それをベースオブジェクトとして使用します。
@Named @Dependent Bean は、1 つのレンダリング操作用にテンプレート内のすべての式で共有され、レンダリングが終了した後に破棄されます。

cdi および inject の名前空間を持つすべての式は、ビルド時に検証されます。

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 も生成され、そのプロパティーにリフレクションなしでアクセスできるようになります。
アプリケーションが HTTP リクエスト を提供する場合、inject 名前空間を介して現在の io.vertx.core.http.HttpServerRequest を注入することもできます (例: {inject:vertxRequest.getParam('foo')})。

4.4. タイプセーフな式

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

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

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

Java タイプは、java.lang パッケージの JDK タイプでない限り、常に fully qualified name で識別される必要があります。この場合、パッケージ名はオプションになります。パラメーター化されたタイプもサポートされていますが、ワイルドカードは常に無視され、上限と下限のみが考慮されます。たとえば、パラメーター宣言 {@java.util.List<? extends org.acme.Foo> list}{@java.util.List<org.acme.Foo> list} として認識されます。タイプ変数は特別な方法で扱われることはないため、決して使用しないでください。

パラメーター宣言の例
{@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 はループセクションでオーバーライドされています。

パラメーター宣言では、キーの後に default value を指定することができます。キーとデフォルト値は等号で区切られます ({@int age=10})。 デフォルト値は、パラメーターのキーが null に解決されるか、見つからない場合に、テンプレート内で使用されます。

たとえば、パラメーター宣言 {@String foo="Ping"} があり、foo が見つからない場合、{foo} を使用すると Ping が出力されます。一方、(たとえば TemplateInstance.data("foo", "Pong") を介して) 値が設定されている場合は、{foo} の出力は Pong となります。

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

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 unless the parentheses are used for grouping, e.g. {@org.acme.Foo foo=(foo1 ?: foo2)}.
The type of a default value is not validated in Qute standalone.
その他のパラメーター宣言の例
{@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 Java プリミティブタイプ。
2 String` は java.lang.String ( {@java.util.List<java.lang.String> strings}) に置き換えることができます。
3 ワイルドカードは無視され、代わりに上限値が使用されます: {@java.util.Map<String,Number>}
4 ワイルドカードは無視され、代わりに上限値が使用されます: {@java.util.Optional<java.lang.Object>}
5 タイプは java.lang.String で、キーは name、デフォルト値は Quarkus です。

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

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 タイプセーフな式. 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.

4.5.1. Nested Type-safe Templates

If using templates in Jakarta REST 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 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 templates/ItemResource/item.html に対して TemplateInstance を与えるメソッドを宣言し、その Item item パラメーターを宣言することで、テンプレートを検証することができます。
2 item パラメーターは自動的に parameter declaration に変換されるので、この名前を参照するすべての式が検証されます。
3 テンプレート内で Item オブジェクトにアクセスできるようにします。
デフォルトでは、@CheckedTemplate でアノテーションされたクラスで定義されたテンプレートは、タイプセーフな式、つまり、ビルド時に検証可能な式のみを含むことができます。この要件を緩和するために、@CheckedTemplate(requireTypeSafeExpressions = false) を使用することができます。

4.5.2. トップレベルタイプセーフテンプレート

また、 @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 This declares a template with path templates/hello.txt. The name parameter is automatically turned into a parameter declaration, so that all expressions referencing this name will be validated.

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

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

4.5.3. Template Records

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.

4.5.4. カスタマイズされたテンプレート・パス

@CheckedTemplate メソッドのテンプレートパスは、 ベースパスデフォルトの名前 から構成されます。 ベースパス@CheckedTemplate#basePath() で指定されます. デフォルトでは,ネストした静的クラスの場合は宣言クラスのsimple name,トップレベルクラスの場合は空文字列が使用されます. デフォルトの名前 は, @CheckedTemplate#defaultName() で指定された戦略によって導出されます。 デフォルトでは、 @CheckedTemplate のメソッドの名前がそのまま使われます。

カスタマイズされたTemplate Pathの例
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 このメソッドのテンプレートパスは、 items/item-and-order になります。

4.5.5. タイプセーフフラグメント

You can also define a type-safe fragment 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.

タイプセーフフラグメントの例
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では、ビルド時に、 Templates#items() に対応する各テンプレートに、識別子 item を持つフラグメントが含まれているかどうかが検証されます。さらに、フラグメントメソッドのパラメーターも検証されます。一般に、フラグメントに含まれ、オリジナル/アウターテンプレートのデータを参照するすべてのタイプセーフ 式には、特定のパラメータが必要です。
items.html におけるフラグメントの定義
<h1>Items</h1>
<ol>
    {#for item in items}
    {#fragment id=item}   (1)
    <li>{item.name}</li>  (2)
    {/fragment}
    {/for}
</ol>
1 識別子 item を持つフラグメントを定義します
2 {item.name} 式は、 Templates#items$item() メソッドが、名前 item 、型 org.acme.Item のパラメータを宣言しなければならないことを意味します。
タイプセーフフラグメントコールサイト例
class ItemService {

  String renderItem(Item item) {
     // this would return something like "<li>Foo</li>"
     return Templates.items$item(item).render();
  }
}
この機能を無効にするために、 @CheckedTemplate#ignoreFragments=true を指定することができます。つまり、メソッド名にドル記号 $ を指定しても、チェックされたフラグメントメソッドになることはありません。

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

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

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

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

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

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

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

名前空間が定義されていない限り、@TemplateAttribute でアノテーションされた最初のパラメーターのクラスがベース・オブジェクトにマッチするように使用されます。そうでなければ、名前空間が式をマッチするのに使用されます。

4.6.1. 名前によるマッチング

メソッド名は、デフォルトでプロパティ名と一致するように使用されます。

拡張メソッドの例
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 のインスタンスに解決されます。

ただし、 matchName() でマッチング名を指定することは可能です。

TemplateInstance.createUni() の例
@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 / * - を使用すると、拡張メソッドが任意の名前にマッチするように指定することができます。

TemplateExtension#ANY の例
@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 追加の文字列メソッドパラメータは、実際のプロパティ名を渡すために使用されます。

また、 matchRegex() で指定された正規表現と名前を照合することも可能です。

TemplateInstance.createUni() の例
@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 追加の文字列メソッドパラメータは、実際のプロパティ名を渡すために使用されます。

最後に、 matchNames() を使用して、一致する名前のコレクションを指定することができます。追加の文字列メソッド・パラメータも必須です。

TemplateInstance.createUni() の例
@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()

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

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.

複数のパラメーターの例
@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. リスト

  • 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: 指定されたリストの最初の要素を返します。リストが空の場合、NoSuchElementException をスローします。

    • {recordsList.first}

  • last: 指定されたリストの最後の要素を返します。リストが空の場合、NoSuchElementException をスローします。

    • {recordsList.last}

リストの要素にはインデックス ({list.10} または {list[10]}) を介して直接アクセスすることができます。
4.6.5.1. 整数値
  • mod : モデューロ演算

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

  • plus または +: 加算

    • {counter + 1}

    • {age plus 10}

    • {age.plus(10)}

  • minus または -: 引き算

    • {counter - 1}

    • {age minus 10}

    • {age.minus(10)}

4.6.5.2. Strings
  • fmt または format: java.lang.String.format() によって文字列のインスタンスをフォーマットします。

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

    • {myStr.format(locale,arg1)}

  • str:fmt または str:format: 与えられた文字列値を java.lang.String.format() でフォーマットします。

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

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

  • +: 連結

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

    • {name + 10}

4.6.5.3. 設定
  • config:<name> または config:[<name>]: 与えられたプロパティー名に対する config 値を返します。

    • {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.Calendar 、 `java.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. 静的なフィールドとメソッドへのアクセス

もし @TemplateData#namespace() が空でない値に設定されている場合、ターゲットクラスのパブリック静的フィールドとメソッドにアクセスするための名前空間リゾルバーが自動的に生成されます。デフォルトでは、名前空間はターゲットクラスの FQCN で、ドットやドル記号はアンダースコアに置き換えられます。たとえば、名前 org.acme.Foo を持つクラスの名前空間は org_acme_Foo となります。静的フィールド Foo.AGE には、{org_acme_Foo:AGE} からアクセスすることができます。静的メソッド Foo.computeValue(int number) には、{org_acme_Foo:computeValue(10)} を介してアクセスすることができます。

名前空間は、英数字とアンダースコアだけで構成されます。
@TemplateData でアノテーションされたクラス
package model;

@TemplateData (1)
public class Statuses {
    public static final String ON = "on";
    public static final String OFF = "off";
}
1 名前空間 model_Status を持つネームリゾルバーが自動的に生成されます。
クラス定数にアクセスするテンプレート
{#if machine.status == model_Status:ON}
  The machine is ON!
{/if}

4.7.2. enum の便利なアノテーション

また、enum 定数にアクセスするための便利なアノテーションとして、@io.quarkus.qute.TemplateEnum があります。このアノテーションは、機能的には @TemplateData(namespace = TemplateData.SIMPLENAME) と同等です。つまり、対象の enum に対して自動的に名前空間リゾルバーが生成され、対象の enum の単純名が名前空間として使用されます。

@TemplateEnum でアノテーションされた enum
package model;

@TemplateEnum (1)
public enum Status {
    ON,
    OFF
}
1 名前空間 Status を持つネームリゾルバーが自動的に生成される。
enum 以外のクラスで宣言された @TemplateEnum は無視されます。また、enum が @TemplateData アノテーションを宣言する場合は、@TemplateEnum アノテーションは無視されます。
enum 定数にアクセスするテンプレート
{#if machine.status == Status:ON}
  The machine is ON!
{/if}
Quarkus は、名前空間の競合の可能性を検出し、特定の名前空間が複数の @TemplateData@TemplateEnum アノテーションによって定義されている場合、ビルドを失敗させます。

4.8. グローバル変数

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 to the data map of any TemplateInstance during initialization,

  • accessible with the global: namespace.

グローバル変数の定義
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 クラスが @TemplateGlobal でアノテーションが付けられている場合、パラメーターを宣言しないすべての非 void 非プライベート静的メソッドとすべての非プライベート静的フィールドは、グローバル変数と見なされます。名前はデフォルトです。つまり、フィールド/メソッドの名前が使用されます。
2 メソッドレベルのアノテーションは、クラスレベルのアノテーションをオーバーライドします。この特別なケースでは、名前はデフォルトではなく、明示的に選択されます。
グローバル変数にアクセスするテンプレート
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().
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. 競合の解消

If not accessed via the global: namespace the 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 currentUserGlobals#user() が提供するグローバル変数と競合しています。

そのため、Globals#user() メソッドが java.lang.String を返し、それが name プロパティーを持たないにもかかわらず、対応するテンプレートはバリデーションエラーにはなりません。

templates/hello.txt
User name: {currentUser.name} (1)
1 org.acme.User には name プロパティーがあります。

その他のテンプレートでは、明示的なパラメーター宣言が必要です。

{@org.acme.User currentUser} (1)

User name: {currentUser.name}
1 このパラメーター宣言は、Globals#user() メソッドが提供するグローバル変数によって追加される宣言を上書きします。

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

JVM モードでは、リフレクションベースの値リゾルバーが、プロパティーにアクセスし、モデルクラスのメソッドを呼び出すために使われる場合があります。しかし、これは ネイティブ実行可能ファイル に対しては、そのままではうまくいきません。その結果、たとえ Foo クラスが関連するゲッターメソッドを宣言していたとしても、Property "name" not found on the base object "org.acme.Foo" in expression {foo.name} in template hello.html といったテンプレート例外に遭遇するかも知れません。

この問題を解決するには、いくつかの方法があります。

  • タイプセーフテンプレート または タイプセーフ式 をご利用ください。

    • この場合、最適化されたバリューリゾルバーが自動的に生成され、実行時に使用されます。

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

  • モデルクラスに @TemplateData のアノテーションを付ける - 実行時に専用の値リゾルバーが生成され、使用されます。

  • モデルクラスに @io.quarkus.runtime.annotations.RegisterForReflection のアノテーションを付けて、リフレクションベースの値リゾルバーを動作させます。

4.10. REST Integration

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.

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>

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>

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.

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.

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 @Location の修飾子が指定されていない場合は、フィールド名がテンプレートの場所を特定するために使用されます。この特定のケースでは、パスが templates/hello.txt であるテンプレートを注入しています。
2 Template.data() は、実際のレンダリングがトリガーされる前にカスタマイズできる新しいテンプレートインスタンスを返します。この場合、名前の値をキー name の下に置きます。データマップはレンダリング中にアクセス可能です。
3 レンダリングをトリガーしないことに注意してください - これは特別な ContainerResponseFilter の実装によって自動的に行われます。
Users are encouraged to use Type-safe templates that help to organize the templates for a specific Jakarta REST resource and enable type-safe expressions automatically.

コンテンツネゴシエーションは自動的に実行されます。結果の出力はクライアントから受け取った 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 クラスを使用すると、Jakarta REST リソースメソッドのボディからテンプレートインスタンスを取得することができます:

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. 開発モード

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.

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.

4.12. テスト

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.

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

4.13.1. 基本概念

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.

メッセージバンドルインターフェイスの例
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 メソッドのパラメーターはテンプレートで使用することができます。

メッセージバンドルは、実行時に使用することができます:

  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 を使用して、実行時に解決されたキーのローカライズされたメッセージを取得することも可能である。この場合、検証はスキップされます。

4.13.2. デフォルトのバンドル名

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 になります:

class Controller {

    @MessageBundle
    interface index {

        @Message("Hello {name}!")
        String hello(String name); (1)
   }
}
1 このメッセージは、 {Controller_index:hello(name)} を介してテンプレートで使用することができます。
バンドル名は、ローカライズされたファイル名の一部としても使用されます。例えば、 Controller_index は、 Controller_index_de.properties となります。

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

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. キャメルケースを止め、ハイフン化したもの。 例: helloName()hello-name

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

4.13.4. バリデーション

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

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

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

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

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

デフォルトでは、@MessageBundle インターフェイスには quarkus.default-locale 設定プロパティーで指定したデフォルトロケールが使用されます。しかし、io.quarkus.qute.i18n.MessageBundle#locale() を使用して、カスタムのロケールを指定することができます。さらに、ローカライズされたバンドルを定義するには、以下の 2 つの方法があります。

  1. @Localized でアノテーションされたデフォルトのインターフェイスを拡張するインターフェイスを作成します。

  2. アプリケーションアーカイブの src/main/resources/messages ディレクトリーにある UTF-8 エンコードファイルを作成します (例: msg_de.properties)。

ローカライズされたインターフェイスはリファクタリングを容易にしますが、外部ファイルの方が便利な場面も多いかもしれません。
ローカライズされたインターフェースの例
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 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.

ローカライズされたファイルの例 - msg_de.properties
# This comment is ignored
hello_name=Hallo {name}! (1) (2)
1 ローカライズされたファイルの各行は、キーと値のペアを表します。キーは、メッセージバンドルインターフェイスで宣言されたメソッドに対応しなければなりません。値はメッセージテンプレートになります。
2 キーと値は等号で区切られています。
この例では .properties という接尾辞を使用していますが、これはほとんどの IDE やテキストエディターが .properties ファイルのシンタックスハイライトをサポートしているからです。しかし、実際には接尾辞は何でもよいため、単に無視されます。
メッセージバンドルインターフェイスごとに、ターゲットディレクトリーにサンプルのプロパティーファイルが自動的に生成されます。たとえば、デフォルトで @MessageBundle に名前が指定されない場合、mvn clean package を用いてアプリケーションをビルドすると、target/qute-i18n-examples/msg.properties というファイルが生成されます。このファイルは、特定のロケールのベースとして使用することができます。単にファイル名を変更 (例: msg_fr.properties) して メッセージのテンプレートを変更し、これを src/main/resources/messages ディレクトリーに移動します。
複数の隣接する行にまたがる値
hello=Hello \
   {name} and \
   good morning!

行の終端はバックスラッシュ文字 \ でエスケープされ、次の行の先頭の空白は無視されることに注意してください。つまり、{msg:hello('Edgar')}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-rest-qute (or 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.13.6. メッセージテンプレート

メッセージバンドルインターフェースの全てのメソッドは、メッセージテンプレートを定義しなければなりません。通常は io.quarkus.qute.i18n.Message#value() で定義しますが、利便性のため、ローカライズしたファイルに値を定義するオプションも用意されています。

値のない Message Bundle Interface の例
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 アノテーションの値が定義されていない。この場合、ローカライズされた補足ファイルの値が使用される。
2 アノテーションの値は、ローカライズされたファイルに定義された値を優先して定義される。
ローカライズされた補足ファイル
hello_name=Hello \
   {name} and \
   good morning!
goodbye=Best regards, {name} (1)
1 io.quarkus.qute.i18n.Message#value() は常に優先されるため、この値は無視されます。

メッセージテンプレートはビルド時に検証されます。メッセージテンプレートの欠落が検出された場合、例外がスローされ、ビルドに失敗します。

4.14. 設定リファレンス

ビルド時に固定される構成プロパティ - 他のすべての構成プロパティは実行時にオーバーライド可能

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

Show more

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

Show more

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

Show more

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

Show more

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

Show more

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

Show more

Charset

UTF-8

By default, a template modification results in an application restart that triggers build-time validations.

This regular expression can be used to specify the templates for which the application is not restarted. I.e. the templates are reloaded and only runtime validations are performed.

The matched input is the template path that starts with a template root, and the / is used as a path separator. For example, templates/foo.html.

Environment variable: QUARKUS_QUTE_DEV_MODE_NO_RESTART_TEMPLATES

Show more

Pattern

By default, the rendering results of injected and type-safe templates are recorded in the managed RenderedResults which is registered as a CDI bean.

Environment variable: QUARKUS_QUTE_TEST_MODE_RECORD_RENDERED_RESULTS

Show more

boolean

true

The strategy used when a standalone expression evaluates to a "not found" value at runtime and the 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

Show more

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

Show more

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

Show more

boolean

true

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

Environment variable: QUARKUS_QUTE_TIMEOUT

Show more

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

Show more

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

Show more

Map<String,String>

5. スタンドアローンライブラリーとして使用する場合のQute

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

エンジン
  • まず第一に、すぐに使用できるマネージド Engine インスタンスはありません。新しいインスタンスを Engine.builder() で設定する必要があります。

Template locators
  • デフォルトでは、テンプレートロケーター は登録されていません。つまり、Engine.getTemplate(String) は機能しません。

  • カスタムテンプレートロケーターは EngineBuilder.addLocator() を使って登録するか、テンプレートを手動で解析して、Engine.putTemplate(String, Template) で結果をキャッシュに格納することができます。

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

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

セクション
  • デフォルトでは、セクションヘルパーは登録されていません。

  • デフォルトの値リゾルバーのセットは、便利な EngineBuilder.addDefaultSectionHelpers() メソッドと EngineBuilder.addDefaults() メソッドを介して、それぞれ登録することができます。

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

  • デフォルトの値リゾルバーのセットは、便利な EngineBuilder.addDefaultValueResolvers() メソッドと EngineBuilder.addDefaults() メソッドを介して、それぞれ登録することができます。

    組み込みの拡張メソッドによって提供されるすべての機能が、デフォルトの値リゾルバーによってカバーされているわけではありません。ただし、カスタムの値リゾルバーは ValueResolver.builder() を使って簡単に構築することができます。
  • Qute がオブジェクトのプロパティーにアクセスし、パブリックメソッドを呼び出せるように、Engine.addValueResolver (new ReflectionValueResolver()) を介して ReflectionValueResolver インスタンスを登録することが推奨されます。

    リフレクションは、一部の制限された環境では正しく動作しないか、GraalVM ネイティブイメージの場合の登録など、追加の設定が必要な場合があることに留意してください。
ユーザー定義タグ
  • ユーザー定義のタグは自動的に登録されません。

  • タグは、Engine.builder().addSectionHelper(new UserTagSectionHelper.Factory("tagName","tagTemplate.html")).build() を介して手動で登録することができます。

タイプセーフ
インジェクション

Template インスタンスを注入することはできませんし、その逆もできません。テンプレートは、inject: および cdi: の名前空間を介して @Named CDI Bean を注入することはできません。

関連コンテンツ

同一エクステンション