Qute reference guide

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

このガイドでは、introductory examplecore features の説明、Quarkus integration の詳細について説明しています。

Qute は、主に Quarkus のエクステンションとして設計されています。 「スタンドアロン」のライブラリーとして使用することも可能です。 しかし、その場合、いくつかの機能は使用できません。 一般的に、 Quarkus の統合 のセクションで言及されている機能は使えません。 制限と可能性については、 スタンドアローンライブラリーとして使用する場合のQute セクションに詳細が記載されています。

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 テンプレートには、任意の ビルディングブロック を使用することができます。この場合、セクションの場合 は、入力データに基づいてメッセージの適切な部分をレンダリングするために使用されます。
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 タイプセーフメッセージバンドル.

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

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();
In Quarkus, there is a preconfigured Engine available for injection - see Quarkus の統合.

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

Template hello = engine.parse(helloHtmlContent);
In Quarkus, you can simply inject the template definition. The template is automatically parsed and cached - see Quarkus の統合.

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

// 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> を返す `CompletionStage<String> があります。

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

  1. テンプレートコンテンツを作成する ( hello.html )

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

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

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

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

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

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

セクション

section には、静的テキスト、式、ネストされたセクションを含めることができます ({#if foo.active}{foo.name}{/if})。 クロージングタグ内の名前はオプションです ({#if active}ACTIVE!{/})。 セクションは空にできます ({#myTag image=true /})。 一部のセクションでは、オプションの終了タグがサポートされます。つまり、終了タグがなければ、セクションは親セクションの終了位置で終了します。 セクションは、ネストしたセクションブロックを宣言することもでき ({#if item.valid} Valid. {#else} Invalid. {/if})、どのブロックをレンダリングするかを決定します。

パース対象外文字データ

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.

The number of | (pipe) characters in the opening delimiter can be increased to avoid conflicts with content that itself contains |\}. The closing delimiter must use the same number of pipes as the opening delimiter. For example, \{|||| starts an unparsed block that only ends with ||||\}, making it safe to include |\} in the content: \{|||| content with |\} inside ||||\}.

Consecutive | characters immediately after \{| are now counted as part of the opening delimiter. For example, \{||foo|\} was previously interpreted as single-pipe unparsed character data with content |foo, but is now interpreted as a double-pipe opening that requires ||\} to close.

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

3.2. 識別子とタグ

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

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

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

  • 数字、または

  • アルファベット文字、または

  • アンダースコア、または

  • 組み込みコマンド ( #!@/ )

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

Tag Examples
<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})、またはコメントを含むけれど、式を含まず、空白以外の文字を含まない行のことになります。 言い換えれば、セクションタグやパラメーター宣言を含まない行は、スタンドアロン行ではありません。 同様に、 または 非空白文字列 を含む行も スタンドアロン行ではありません

Template Example
<html>
  <body>
     <ul>
     {#for item in items} (1)
       <li>{item.name} {#if item.active}{item.price}{/if}</li>  (2)
                          (3)
     {/for}               (4)
     </ul>
   </body>
</html>
1 これはスタンドアロン行なので削除されます。
2 スタンドアロン行ではない - 式と非空白文字を含む
3 スタンドアロン行ではない - セクションタグ/パラメーター宣言を含まない
4 これはスタンドアロン行です。
Default Output
<html>
  <body>
     <ul>
       <li>Foo 100</li>

     </ul>
   </body>
</html>
In Quarkus, the default behavior can be disabled by setting the property quarkus.qute.remove-standalone-lines to false. In this case, all whitespace characters from a standalone line will be printed to the output.
Output with quarkus.qute.remove-standalone-lines=false
<html>
  <body>
     <ul>

       <li>Foo 100</li>


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

3.4. 式

式が評価され、値が出力されます。これには 1 つ以上のパーツがあり、各パーツはプロパティーアクセサー (別名 Field Access Expression) または仮想メソッド呼び出し (別名 Method Invocation Expression) のいずれかを表します。

プロパティーにアクセスする際には、ドット表記とブラケット表記のどちらかを使用できます。 object.property (ドット表記) 構文では、propertyvalid identifier でなければなりません。 object[property_name] (ブラケット表記) 構文では、property_name は null 以外の literal 値でなければなりません。

式は、名前空間の後にコロン (:) を付けて開始できます。 有効な名前空間は英数字とアンダースコアで構成されます。 名前空間の式は別の方法で解決されます。解決 も参照してください。

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 名前空間 global , 1つのパーツ: colors

式の一部を 仮想メソッド とすることができ、その場合、名前の後にコンマで区切られたパラメーターのリストを括弧で囲みます。 仮想メソッドのパラメーターは、ネストされた式か、literal 値のどちらかにすることができます。 これらのメソッドを "仮想" と呼ぶのは、実際の Java メソッドによってバックアップされる必要がないためです。 仮想メソッドの詳細については、following section を参照してください。

Virtual Method Example
{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')

An expression can also use a literal value as the base, followed by property accessors or virtual method invocations.

Literal Base Expression Examples
{='foo'.toUpperCase} (1)
{=1.intValue} (2)
{#let name=('foo'.toUpperCase)}{name}{/let} (3)
{name.replace('foo'.toUpperCase)} (4)
1 string literal as the base followed by a property accessor; outputs FOO
2 integer literal as the base followed by a property accessor; outputs 1
3 in a let section parameter, a literal base can be used directly inside parentheses
4 a literal base can also be used in nested expressions passed as parameters of virtual methods
In a top-level output expression, a literal base requires a special syntax prefix (such as =) configured via ParserConfig.

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

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

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

式が名前空間を指定しない場合、カレントコンテキストオブジェクト はタグの位置から導出されます。 デフォルトでは、現在のコンテキストオブジェクトは、テンプレートインスタンスに渡されたデータを表します。 ただし、セクションは現在のコンテキストオブジェクトを変更することがあります。 典型的な例は、名前付きローカル変数を定義するために使用できる let セクションです。

{#let myParent=order.item.parent myPrice=order.price} (1)
  <h1>{myParent.name}</h1>
  <p>Price: {myPrice}</p>
{/let}
1 セクション内の現在のコンテキストオブジェクトは、解決されたパラメーターのマップです。
The current context can be accessed via the implicit binding this.

3.4.4. 組込リゾルバ

名前 説明

エルビス演算子: ?:

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

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

orEmpty

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

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

三項演算子: condition ? ifTrue : ifFalse

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

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

論理 AND 演算子: &&

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

{person.isActive && person.hasStyle}

論理 OR 演算子: ||

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

{person.isActive || person.hasStyle}

等価演算子: ==/eq/is

ベースオブジェクトが引数と等しい場合、true を出力します。

{obj1 is obj2 ? 'Equal' : 'Inequal'}{obj1 == obj2 ? 'Equal' : 'Inequal'}{obj1.eq(obj2) ? 'Equal' : 'Inequal'}

The condition in a ternary operator evaluates to true if the value is not considered falsy as described in セクションの場合.
In fact, the operators are implemented as "virtual methods" that consume one parameter and can be used with infix notation. For example {person.name or 'John'} is translated to {person.name.or('John')} and {item.isActive ? item.name : 'Inactive item'} is translated to {item.isActive.ifTruthy(item.name).or('Inactive item')}

3.4.5. 配列

ループセクション を使用して、配列の要素を反復処理することができます。 また、指定した配列の長さを取得し、インデックス値を用いて直接要素にアクセスすることも可能です。 さらに、take(n)/takeLast(n) メソッドを使用して最初または最後の n 要素にアクセスできます。

Array Examples
<h1>Array of length: {myArray.length}</h1> (1)
<ul>
  <li>First: {myArray.0}</li> (2)
  <li>Second: {myArray[1]}</li> (3)
  <li>Third: {myArray.get(2)}</li> (4)
</ul>
<ol>
 {#for element in myArray}
 <li>{element}</li>
 {/for}
</ol>
First two elements: {#each myArray.take(2)}{it}{/each} (5)
1 配列の長さを出力します。
2 配列の最初の要素を出力します。
3 ブラケット表記で配列の 2 つ目の要素を出力します。
4 配列の 3 番目の要素を仮想メソッド get() で出力します。
5 配列の最初の 2 要素を出力します。

3.4.6. 文字エスケープ

HTML および XML テンプレートでは、対応するテンプレートバリアントが設定されている場合に、', “, `<, >, & 文字はデフォルトでエスケープされます。 JSON テンプレートの場合、対応するテンプレートバリアントが設定されている場合、”`、\、および制御文字 (U+0000 から U+001F) はデフォルトでエスケープされます。

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

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

HTML Example
<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> としてレンダリングされます。
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.
JSON Example
{
  "id": "{valueId.raw}", (1)
  "name": "{valueName}" (2)
}
1 \nA12345 に解決される valueId\nA12345 としてレンダリングされますが、属性 id の文字列値内に新しい行が挿入されるため、無効な JSON オブジェクトになります。
2 \ Expressions \n Escapes に対して解決される valueName\\ Expressions \\n Escapes とレンダリングされます。

3.4.7. 仮想メソッド

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

Virtual Method Example
<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;
   }
}
Virtual methods are usually evaluated by value resolvers generated for @TemplateExtension methods, @TemplateData or classes used in parameter declarations. However, a custom value resolver that is not backed by any Java class/method can be registered as well.

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

Infix Notation Example
<html>
<p>{item.price or 5}</p>  (1)
</html>
1 item.price or 5item.price.or(5) に翻訳されます。

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

Nested Virtual Method Example
<html>
<p>{item.subtractPrice(item.calculateDiscount(10))}</p>  (1)
</html>
1 item.calculateDiscount(10) が最初に評価され、その後 item.subtractPrice() への引数として渡されます。

3.4.8. Evaluation of CompletionStage and Uni Objects

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

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

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

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. テンプレートの問題個所の特定方法

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

application.properties Example
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 が出力に書き込まれます。

In Quarkus, it’s possible to change the default strategy via the quarkus.qute.property-not-found-strategy as described in the 設定リファレンス.
Similar errors are detected at build time if タイプセーフな式 and タイプセーフテンプレート are used.

3.5. セクション

セクションは # で始まる開始タグを持ち、その後に {#if}{#each} のようなセクション名が続きます。 また、空でも構いません。その場合、開始タグは / で終了します ({#myEmptySection /})。 セクションには通常、ネストされた式や他のセクションが含まれています。 終了タグは / で始まり、セクションの名前 (オプション) が含まれます ({#if foo}Foo!{/if}または `{#if foo}Foo!{/})。 一部のセクションではオプションの終了タグがサポートされています。終了タグがない場合、セクションは親セクションの終了位置で終了します。

#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 {#if} セクション内で使用できるローカル変数を定義します。
組み込みセクション オプションの終了タグをサポート

{#for}

{#if}

{#when}

{#let}

{#with}

{#include}

ユーザー定義タグ

{#fragment}

{#cached}

3.5.1. パラメーター

開始タグは、オプションの名前を持つパラメーターを定義できます (例: {#if item.isActive} および {#let foo=1 bar=false})。パラメーターは 1 つ以上の空白で区切られます。名前は等号によって値と区切られます。名前と値には、任意の数の空白を接頭辞および接尾辞として追加できます (例: {#let id='Foo'}{#let id = 'Foo'} は、パラメーター名が id で値が Foo である点で同等です)。値は括弧を使用してグループ化できます (例: {#let id=(item.id ?: 42)} は、名前が id で値が item.id ?: 42 です)。セクションはパラメーター値を任意の方法で (たとえば値をそのまま) 解釈できます。しかし、ほとんどの場合、パラメーター値は として登録され、使用前に評価されます。

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

#if Section Example
{#if item.name is 'sword'}
  It's a sword! (1)
{#else if item.name is 'shield'}
  It's a shield! (2)
{#else}
  Item is neither a sword nor a shield. (3)
{/if}
1 これがメインブロックです。
2 追加ブロック。
3 追加ブロック。

3.5.2. ループセクション

ループセクションでは、IterableIterator、array、Map (要素は Map.Entry)、StreamIntegerLongint および long (プリミティブ値) のインスタンスに対して反復処理できます。 パラメーター値に 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 Iteration Metadata Example
{#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 Iteration Metadata Example
{#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} セクションは、基本的な制御フローセクションを表します。最も単純なバージョンでは、単一のパラメーターを受け取り、条件が true と評価された場合にコンテンツをレンダリングします。演算子なしの条件は、値が falsy (つまり、nullfalse、空のコレクション、空のマップ、空の配列、空の文字列/文字シーケンス、空の java.util.Optional/java.util.OptionalInt/java.util.OptionalLong/java.util.OptionalDouble、またはゼロに等しい数) ではない場合、true と評価されます。

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

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

オペレーター エイリアス 優先度 説明

logical complement

!

4

{#if !item.active}{/if}

評価された値を反転します。

greater than

gt, >

3

{#if item.age > 43}This item is very old.{/if}

value1value2 より大きい場合は true と評価されます。

greater than or equal to

ge, >=

3

{#if item.price >= 100}This item is expensive.{/if}

value1value2 以上の場合、true と評価されます。

less than

lt, <

3

{#if item.price < 100}This item is cheap.{/if}

value1value2 より小さい場合は true と評価されます。

less than or equal to

le, <=

3

{#if item.age ⇐ 43}This item is young.{/if}

value1value2 以下の場合は true と評価されます。

equals

eq, ==, is

2

{#if item.name eq 'Foo'}Foo item!{/if}

value1value2 と等しい場合は true と評価されます。

not equals

ne, !=

2

{#if item.name != 'Bar'}Not a Bar item!{/if}

value1value2 と等しくない場合は true と評価されます。

logical AND (short-circuiting)

&&, and

1

{#if item.price > 100 && item.isActive}Expensive and active item.{/if}

両方のオペランドが true と評価された場合は true と評価されます。

logical OR (short-circuiting)

||, or

1

{#if item.price > 100 || item.isActive}Expensive or active item.{/if}

オペランドの 1 つが true と評価された場合、true と評価されます。

>>=<、および <= には、次のルールが適用されます。

  • どちらのオペランドも null であってはなりません。

  • 両方のオペランドが java.lang.Comparable を実装する同じ型である場合、比較を実行するために Comparable#compareTo (T) メソッドが使用されます。

  • それ以外の場合は、まず両方のオペランドが java.math.BigDecimal に強制変換され、次に BigDecimal#compareTo (BigDecimal) メソッドを使用して比較が実行されます。

Types that support coercion include BigInteger, Integer, Long, Double, Float and String.

== および != には次のルールが適用されます。

  • オペランドは、まず java.util.Objects#equals (Object、Object) メソッドを使用してテストされます。true が返された場合、オペランドは等しいとみなされます。

  • それ以外の場合、両方のオペランドが null ではなく、少なくとも 1 つが java.lang.Number のインスタンスである場合、オペランドは java.math.BigDecimal に強制変換され、BigDecimal#compareTo (BigDecimal) メソッドを使用して比較が実行されます。

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

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

デフォルトの優先順位ルール (優先順位の高いものが優先) は括弧で上書きできます。

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

また、 else ブロックを何個でも追加することができます。

{#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. セクションの際

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

Example using the when/is name aliases
{#when items.size}
  {#is 1} (1)
    There is exactly one item!
  {#is > 10} (2)
    There are more than 10 items!
  {#else} (3)
    There are 2 -10 items!
{/when}
1 パラメーターが1つだけの場合、それは等価かがテストされます。
2 an operator を使用して、一致するロジックを指定できます。 セクションの場合 とは異なり、ネストされた演算子はサポートされません。
3 else は、他のブロックが値に一致しない場合に実行されます。
Example using the switch/case name aliases
{#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") の場合に実行されます
An enum constant is validated if the tested value has a type information available and resolves to an enum type.

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}
  Price: {price}
{/let} (3)
1 ローカル変数は、literal を表すことができる式 (つまり isActive=falseage=10) で初期化されます。
2 The infix notation is only supported if parentheses are used for grouping, e.g. price=(order.price + 10) is equivalent to price=order.price.plus(10). A literal value can also be used as the first operand, e.g. price=('$' + item.price).
3 変数は let セクションの外では利用できません。

変数は、定義している let セクションの外では利用できません。ただし、終了タグはオプションで、省略された場合は親セクションの終了位置でセクションが終了します。

Let with optional end tag
<ul>
{#for item in items}
{#let price=item.price} (1)
   <li>{price}</li>
{! a synthetic {/let} is added here automatically !}
{/for}
</ul>
{price} --> BOOM! (2)
1 ローカル変数 price は、式 item.price で初期化されます。
2 変数 pricelet セクションの外では利用できません。

ローカル変数名などのセクションパラメータのキーが ? で終わっている場合、 ? を除いたキーが 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 item.parent}
  <h1>{name}</h1>  (1)
  <p>{description}</p> (2)
{/with}
1 nameitem.parent に対して解決されます。
2 descriptionitem.parent に対して解決されます。

タイプセーフテンプレート や、タイプセーフな式 を定義するテンプレートでは、with セクションを使用してはならないことに注意してください。 これは、Qute がネストされた式を検証することを妨げるためです。 可能であれば、明示的なバインディングを宣言する {#let} セクションに置き換えてください。

{#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. セクションを含む

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

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

デフォルトでは、最初の名前なしパラメーターは、インクルードされるテンプレートの ID を表します。そして、それはそのまま解釈されます。例えば、{#include bar/foo /} は ID bar/foo のテンプレートをインクルードします。つまり、src/main/resources/templates/bar/foo.html が一致する可能性があります。ただし、テンプレート ID を動的に指定することも可能です。タグに _id パラメーターを追加するだけです。この場合、_id パラメーターの引数値は解決される式を表し、その結果がテンプレート ID を表します。例えば、{#include _id=bar.foo /} は、まず bar.foo が解決され、その結果のテンプレート ID が使用されることを意味します。

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

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

デフォルトでは、タグテンプレートは親コンテキストからデータを参照できません。 Qute はタグを 分離された テンプレートとして実行します。つまり、タグを呼び出すテンプレートのコンテキストにアクセスしません。 ただし、デフォルトの動作を変更して分離を無効にすると便利な場合があります。 その場合、呼び出しサイトに _isolated=false または _unisolated 引数を追加するします (例: {#itemDetail item showImage=true _isolated=false /} または {#itemDetail item showImage=true _unisolated /})。

3.5.8.1. 引数

タグテンプレート内で、名前付き引数に直接アクセスできます。 ただし、最初の引数で名前を定義する必要はなく、it エイリアスを使用してアクセスできます。 さらに、引数に名前が定義されておらず、値が foo など単一の識別子である場合、名前はデフォルトで値の識別子に設定されます (たとえば {#myTag foo /}{#myTag foo=foo /} になります)。 つまり、引数値 foo は解決され、タグテンプレート内で {foo} を使用してアクセスできます。

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

io.quarkus.qute.UserTagSectionHelper.Arguments メタデータは、_args エイリアスを使用してタグ内でアクセスできます。

  • _args.size - タグに渡された引数の実際の数を返します。

  • _args.empty/_args.isEmpty - 引数が渡されない場合は true を返します。

  • _args.get(String name) - 指定された名前の引数値または null を返します。

  • _args.filter(String…​) - 指定された名前に一致する引数を返します。

  • _args.filterIdenticalKeyValue - 値と等しい名前を持つ引数を返します。通常は {#test foo="foo" bar=true} または {#test "foo" bar=true /} から`foo` が返されます。

  • _args.skip(String…​) - 指定された名前と一致しない引数のみを返します。

  • _args.skipIdenticalKeyValue - 値と等しくない名前を持つ引数のみを返します。通常は {#test foo="foo" bar=true /} から bar が返されます。

  • _args.skipIt - 最初の名前なし引数を除くすべての引数を返します。通常は {#test foo bar=true /} から bar が返されます。

  • _args.asHtmlAttributes - 引数を HTML 属性としてレンダリングします (例: foo="true" readonly="readonly")。引数は名前のアルファベット順にソートされ、'"<, >& の文字はエスケープされます。

_args は 反復可能な java.util.Map.Entry でもあります ({#each _args}{it.key}={it.value}{/each})。

たとえば、以下のように定義されたユーザータグを {#test 'Martin' readonly=true /} で呼び出すことができます。

tags/test.html
{it} (1)
{readonly} (2)
{_args.filter('readonly').asHtmlAttributes} (3)
1 it はタグの最初の名前なしパラメーターに置き換えられます。
2 readonly は名前付きパラメーターです。
3 _args は引数のメタデータを表します。

結果は次のようになります。

Martin
true
readonly="true"
3.5.8.2. 継承

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

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 結果は、<p>これは私のカスタムタイトル!</p> のようになります。

3.5.9. フラグメント

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

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

Note that a fragment identifier must be unique in a template.
Fragment Definition in item.html
{@org.acme.Item item}
{@java.util.List<String> aliases}

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

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

{#fragment id=item_aliases} (1)
<h2>Aliases</h2>
<ol>
    {#for alias in aliases}
    <li>{alias}</li>
    {/for}
</ol>
{/fragment}
1 識別子 item_aliases を持つフラグメントを定義します。識別子には英数字とアンダースコアしか使えないことに注意してください。最初のパラメータ名は省略可能で、 {#fragment item_aliases} でもかまいません。

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

Obtaining a Fragment
@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} セクションを持つフラグメントを、別のテンプレートや、フラグメントを定義したテンプレートの中に含めることもできます。 フラグメントは、 frg: / fragment: 名前空間を持つ式でも使用できます。

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

<p>
{#fragment fullname}
{name} <strong>{surname}</strong>
{/fragment}
</p>

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

{#include item$item_aliases aliases=user.aliases /} (1)(2)

{frg:fullname} is a happy user! (3)
1 ドル記号 $ を含むテンプレート識別子は、フラグメントを表します。 item$item_aliases の値は、次のように訳されます。テンプレート item からフラグメント item_aliases を使用します。
2 aliases パラメーターは、関連するデータを渡すために使用されます。データが正しく設定されていることを確認する必要があります。この特定のケースでは、フラグメントは {#for alias in aliases} セクションの aliases の値として式 user.aliases を使用します。
3 {frg:username} 式は、フラグメントの内容を出力します。 frg: は、 fragment: に置き換えることができます。
If you want to reference a fragment from the same template, skip the part before $, i.e. something like {#include $item_aliases /}.
You can specify {#include item$item_aliases _ignoreFragments=true /} in order to disable this feature, i.e. a dollar sign $ in the template identifier does not result in a fragment lookup.
3.5.9.1. 非表示(hidden)フラグメント(キャプチャ)

デフォルトでは、フラグメントは通常オリジナルテンプレートの一部としてレンダリングされます。 しかし、フラグメントを 非表示 にすると便利な場合もあります。 通常のフラグメントセクションは、非表示フラグメントを意味する capture というエイリアスを持ちます。 また、 rendered=false または _hidden パラメータを使用して、フラグメントを「非表示」することもできます。興味深い使用例として、フラグメントを定義するテンプレート内で複数回使用できるフラグメントがあります。

Hidden Fragment Definition in item.html
{#capture strong} (1)
<strong>{val}</strong>
{/capture}

<h1>My page</h1>
<p>This document
{#include $strong val='contains' /} (2)
a lot of
{capture:strong(param:val = 'information')} (3) (4)
!</p>
1 識別子 strong を持つ非表示フラグメントを定義します。 {#capture strong}{#fragment strong rendered=false} または {#fragment strong _hidden} に置き換えることができます。 rendered パラメータには任意の式を使用できます (例: {#fragment strong rendered=config.isRendered})。
2 フラグメント strong をインクルードし、値を渡します。 構文 $strong に注意してください。これは、現在のテンプレートからフラグメント strong をインクルードするように処理されます。
3 名前空間リゾルバを使用して、非表示のフラグメントにアクセスすることもできます。 capture: は、 cap: に置き換えることができます。
4 param:val = 'information' は、名前付きパラメータをフラグメントに渡すために使われます。

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

<h1>My page</h1>
<p>This document
<strong>contains</strong>
a lot of
<strong>information</strong>
!</p>
In Quarkus, the namespace resolvers are automatically registered for namespaces frg, fragment, cap and capture.

3.5.10. Eval セクション

このセクションは、テンプレートを動的に解析および評価するために使用できます。 動作は セクションを含む によく似ています。ただし、

  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.
The {#eval} section helper is not registered automatically. It can be registered manually using engine.addSectionHelper(new EvalSectionHelper.Factory()). See エンジンのカスタマイズ for more details.

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.

その後、{#cached}` セクションをテンプレート内で使用できます。

{#cached} (1)
 Result: {service.findResult} (2)
{/cached}
1 key パラメーターが使用されていない場合は、テンプレートのすべてのクライアントが同じキャッシュ値を共有します。
2 テンプレートのこの部分はキャッシュされ、キャッシュエントリーが見つからない、または無効になっている場合にのみ {service.findResult} 式が評価されます。
{#cached key=currentUser.username} (1)
 User-specific result: {service.findResult(currentUser)}
{/cached}
1 key パラメーターが設定されているため、{currentUser.username} の結果ごとに異なるキャッシュ値が使用されます。
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 レンダリング終了後に実行されるコールバックを登録します。

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

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

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

TemplateInstance.createMulti() Example
template.data(foo).createMulti().subscribe().with(buffer:append,buffer::flush);
The template rendering is divided in two phases. During the first phase, which is asynchronous, all expressions in the template are resolved and a result tree is built. In the second phase, which is synchronous, the result tree is materialized, i.e. one by one the result nodes emit chunks that are consumed/buffered by the specific consumer.

3.7. エンジン設定

3.7.1. 値リゾルバ

値リゾルバーは式を評価するときに使用されます。 まず、指定された EvalContext に適用されるリゾルバーがフィルタリングされます。 次に、_ 優先度が最も高い _ リゾルバーを使用してデータを解決します。 io.quarkus.qute.Results.NotFound オブジェクトが返された場合は、代わりに次の利用可能なリゾルバーが使用されます。 ただし、戻り値 null は有効な結果と見なされます。

カスタム io.quarkus.qute.ValueResolver は、EngineBuilder.addValueResolver() を介してプログラムで登録できます。

ValueResolver Builder Example
engineBuilder.addValueResolver(ValueResolver.builder()
    .appliesTo(ctx -> ctx.getBase() instanceof Long && ctx.getName().equals("tenTimes"))
    .resolveSync(ctx -> (Long) ctx.getBase() * 10)
    .build());
In Quarkus, the @EngineConfiguration annotation can be used to register a ValueResolver implemented as a CDI bean.
Keep in mind that the reflection-based value resolver has priority -1 and the max priority value for resolvers generated from @TemplateData and type-safe expressions is 10.

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. コンテンツフィルタ

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

Content Filter Example
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 セクションの場合 and does not produce any output.

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

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

"not found" エラーを引き起こす可能性のある式をどうしても使いたい場合は、デフォルト値 および 安全な式 を使ってエラーを抑制することができます。 デフォルト値は、式の前のパーツが解決できないか、null に解決される場合に使用されます。 エルビス演算子を使ってデフォルト値 {foo.bar ?: 'baz'} を出力することができます。 これは事実上、仮想メソッド {foo.bar.or('baz')} と同じになります。安全な式は ?? という接尾辞で終わり、式が解決できない場合は null という結果になります。 これは、{#if valueNotFound??}Only rendered if valueNotFound is truthy!{/if} セクションの {#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 ディレクトリーにあるすべてのテンプレートが検証され、簡単にインジェクションすることができます。

A valid template file name is a sequence of non-whitespace characters. For example, a template file named foo and bar.html will be ignored.
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. エンジンのカスタマイズ

追加のコンポーネントは、実行時に CDI オブザーバーメソッドの EngineBuilder メソッドを使用して手動で登録できます。

import io.quarkus.qute.EngineBuilder;

class MyBean {

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

ただし、この特定のケースでは、ビルド時の検証でセクションヘルパーファクトリーは無視されます。 ビルド時のテンプレート検証に参加する登録する場合は、便利な @EngineConfiguration アノテーションを使用します。

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 @EngineConfiguration アノテーションが付けられた SectionHelperFactory は、ビルド時のテンプレート検証で使用され、実行時に (a) セクションファクトリー、および、(b) CDI Bean として自動的に登録されます。
2 CDI Beanインスタンスは実行時に使用されます - これはファクトリーが注入ポイントを定義できることを意味します
3 foo パラメーターが常に存在することを検証します。たとえば、{#custom foo='bar' /} は問題ありませんが、{#custom /} はビルド時に失敗します。
4 レンダリング時に注入された Service を使用します。
The @EngineConfiguration annotation can be also used to register ValueResolver, NamespaceResolver and ParserHook components.

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

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

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

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

場合によっては、コンテンツネゴシエーションに基づき、テンプレートの特定バリアントをレンダリングすると便利です。 これは TemplateInstance.setVariant() を使用して特別な属性を設定することで実行できます。

class MyService {

    @Inject
    Template items; (1)

    @Inject
    ItemManager manager;

    String renderItems() {
       return items.data("items", manager.findItems())
                   .setVariant(new Variant(Locale.getDefault(), "text/html", "UTF-8"))
                   .render();
    }
}
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 beans are shared across all expressions in a template for a single rendering operation, and destroyed after the rendering finished.

名前空間 cdiinject を含むすべての式は、ビルド時に検証されます。 cdi:personService.findPerson(10).name という式では、注入される Bean の実装クラスは findPerson メソッドを宣言しているか、マッチする template extension method が存在しなければなりません。 inject:foo.price という式では、注入される Bean の実装クラスは price プロパティー (例: getPrice() メソッド) を持つか、マッチする template extension method が存在しなければなりません。

A ValueResolver is also generated for all beans annotated with @Named so that it’s possible to access its properties without reflection.
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')}.

場合によっては、@Named アノテーションが付けられていない CDI Bean のパブリックメソッドとプロパティーにアクセスする必要がある場合があります。 ただし、Bean のソースを制御していない場合は、@Named アノテーションを追加できません。 ただし、@Named アノテーションが付けられた中間 CDI Bean を作成することは可能です。 この中間 Bean は、問題の Bean を注入してアクセス可能にできます。 Java レコードは、このような中間 CDI Bean を定義するのに非常に便利な方法です。

@Named (1) (2)
public record UserData(UserInfo info, @LoggedIn String username) { (3)
}
1 value メンバーによって名前が明示的に指定されていない場合は、https://jakarta.ee/specifications/cdi/4.1/jakarta -cdi-spec-4.1#default_name[デフォルト名が割り当てられます] (最初の文字を小文字に変換した後の Bean クラスの単純な名前)。この特定のケースでは、デフォルト名は userData です。
2 @Singleton スコープが自動的に追加されます。
3 標準コンストラクターのパラメーターはすべて注入ポイントです。アクセサーメソッドを使用して、注入された Bean を取得できます。

そしてテンプレートでは {cdi:userData.info} または {cdi:userData.username} です。

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} として認識されます。 タイプ変数は特別な方法で扱われることはないため、決して使用しないでください。

Parameter Declaration Example
{@org.acme.Foo foo} (1)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Qute Hello</title>
</head>
<body>
  <h1>{title}</h1> (2)
  Hello {foo.message.toLowerCase}! (3) (4)
</body>
</html>
1 パラメーター宣言 - fooorg.acme.Foo にマップします。
2 検証されていません - パラメーター宣言にマッチしません。
3 この式は検証されています。org.acme.Foo はプロパティー message を持つか、一致するテンプレート拡張メソッドが存在しなければなりません。
4 同様に、 foo.message から解決されたオブジェクトの Java 型は、プロパティー toLowerCase を持つか、一致するテンプレート拡張メソッドが存在しなければなりません。
A value resolver is automatically generated for all types used in parameter declarations so that it’s possible to access its properties without reflection.
Method parameters of type-safe templates are automatically turned into parameter declarations.

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

{@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 となります。

デフォルト値の型は、パラメーター宣言の型に割り当て可能でなければなりません。これは、ビルドの失敗につながる誤ったパラメーター宣言の例です: {@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.
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 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. タイプセーフテンプレート

Java コードでタイプセーフなテンプレートを定義できます。 タイプセーフなテンプレートのパラメーターは、タイプセーフな式 のバインドに使用される パラメーター宣言 に自動変換されます。 その後、タイプセーフな式はビルド時に検証されます。

タイプセーフなテンプレートは、2 つの方法で定義できます。

  1. クラスにアノテーション @io.quarkus.qute.CheckedTemplate を付けると、そのすべての static native メソッドが、タイプセーフなテンプレートと、それに必要なパラメーターリストを定義するために使用されます。

  2. io.quarkus.qute.TemplateInstance を実装する Java レコードを使用します。レコードコンポーネントはテンプレートパラメーターを表し、オプションで @io.quarkus.qute.CheckedTemplate を使用してテンプレートを設定できます。

4.5.1. ネストされたタイプセーフなテンプレート

templates in Jakarta REST resources を使用する場合、次の規則を適用できます。

  • テンプレートファイルを /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 オブジェクトにアクセスできるようにします。
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.

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

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

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

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

@CheckedTemplate
public class Templates {
    public static native TemplateInstance hello(String name); (1)
}
1 これは、パス templates/hello.txt を持つテンプレートを宣言します。name パラメーターは自動的に parameter declaration に変換されるため、この名前を参照するすべての式が検証されます。

そして、テンプレートファイルごとに 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. テンプレートレコード

io.quarkus.qute.TemplateInstance を実装する Java レコードは、タイプセーフなテンプレートを表します。 レコードコンポーネントはテンプレートパラメーターを表し、オプションで @io.quarkus.qute.CheckedTemplate を使用してテンプレートを設定できます。

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 Java レコードを使用してタイプセーフなテンプレートを宣言します。テンプレートは /src/main/resources/templates/HelloResource/Hello.html にあります。
2 レコードをインスタンス化し、通常の TemplateInstance として使用します。

Kotlin ユーザーは、@JvmRecord でアノテーションされたデータクラスを使用して、同じ結果を得ることができます。

@CheckedTemplate(basePath = "HelloResource")
@JvmRecord
data class Hello(val name: String) : TemplateInstance

@JvmRecord アノテーションは、Kotlin コンパイラーにバイトコードで Java レコードを生成するように指示します。Quarkus はこれをタイプセーフなテンプレートとして認識します。@JvmRecord には Kotlin 1.5 以上と JVM ターゲット 16 以上が必要であることに注意してください。

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

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

A template record that is not annotated with @CheckedTemplate is treated as if it was annotated with @CheckedTemplate with default values.
Customized Template Path Example
package org.acme.quarkus.sample;

import jakarta.ws.rs.Path;

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

@Path("item")
public class ItemResource {

    @CheckedTemplate(basePath = "items", defaultName = CheckedTemplate.HYPHENATED_ELEMENT_NAME)
    static class Templates {
        static native TemplateInstance itemAndOrder(Item item); (1)
    }
}
1 このメソッドのテンプレートパスは、 items/item-and-order になります。

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

Java コードで、タイプセーフなテンプレートのタイプセーフな フラグメント を定義することもできます。タイプセーフなフラグメントを定義するには、2 つの方法があります。

  1. ドル記号 $ を含む名前を持つ、@CheckedTemplate でアノテーションされた native static メソッド。

  2. io.quarkus.qute.TemplateInstance を実装し、その名前にドル記号 $ が含まれる Java レコード。

フラグメントの名前は、アノテーションが付けられたメンバー名から導出されます。最後のドル記号 $ の前の部分は、関連するタイプセーフなテンプレートのメソッド名です。最後のドル記号の後の部分はフラグメント識別子です。関連する CheckedTemplate#defaultName() で定義されたストラテジーは、デフォルト名を構築する際に適用されます。

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

@CheckedTemplate
class Templates {

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

  // defines a fragment of Templates#items() with identifier "item"
  static native TemplateInstance items$item(Item item); (1)

  // type-safe fragment as a Java record - functionally equivalent to the items$item() method above
  record items$item(Item item) implements TemplateInstance {}
}
1 Quarkusでは、ビルド時に、 Templates#items() に対応する各テンプレートに、識別子 item を持つフラグメントが含まれているかどうかが検証されます。さらに、フラグメントメソッドのパラメーターも検証されます。一般に、フラグメントに含まれ、オリジナル/アウターテンプレートのデータを参照するすべてのタイプセーフ 式には、特定のパラメータが必要です。
Fragment Definition in items.html
<h1>Items</h1>
<ol>
    {#for item in items}
    {#fragment id=item}   (1)
    <li>{item.name}</li>  (2)
    {/fragment}
    {/for}
</ol>
1 識別子 item を持つフラグメントを定義します
2 {item.name} `式では、`Templates#items$item() メソッドが、名前 item および型 org.acme.Item のパラメーターを宣言する必要があります。
Type-safe Fragment Call Site Example
class ItemService {

  String renderItem(Item item) {
     // this would return something like "<li>Foo</li>"
     return Templates.items$item(item).render();
  }
}
You can specify @CheckedTemplate#ignoreFragments=true in order to disable this feature, i.e. a dollar sign $ in the method name will not result in a checked fragment method.

4.5.6. テンプレートの内容

型セーフテンプレートの内容を Java コード内で直接指定することもできます。 @CheckedTemplate でアノテーションが付けられたクラスの static native メソッド、または TemplateInstance を実装する Java レコードには、@io.quarkus.qute.TemplateContents でアノテーションが付けられる場合があります。 アノテーション値はテンプレートの内容として使用されます。 テンプレート ID/パスは、タイプセーフテンプレートから派生されます。

Template Contents Example
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.TemplateContents;
import io.quarkus.qute.TemplateInstance;

@Path("hello")
public class HelloResource {

    @TemplateContents("Hello {name}!") (1)
    record Hello(String name) implements TemplateInstance {}

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return new Hello(name);
    }
}
1 Hello レコードによって表される型セーフテンプレートの内容を定義します。派生テンプレート ID は HelloResource/Hello です。

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

エクステンションメソッドは、データクラスを新しい機能で拡張するため (アクセス可能なプロパティーとメソッドのセットを拡張するため)、または特定の namespace のための式を解決するために使用できます。 たとえば、計算済みプロパティ仮想メソッド を追加できます。

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

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

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

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

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

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

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

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

Extension Method Example
package org.acme;

class Item {

    public final BigDecimal price;

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

@TemplateExtension
class MyExtensions {

    static BigDecimal discountedPrice(Item item) { (1)
        return item.getPrice().multiply(new BigDecimal("0.9"));
    }
}
1 このメソッドは、 Item.class 型のベース・オブジェクトと discountedPrice プロパティー名を持つ式にマッチします。

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

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

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

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

特別な定数 - TemplateExtension#ANY - を使用すると、エクステンションメソッドが任意の名前にマッチするように指定することができます。

TemplateExtension#ANY Example
@TemplateExtension(matchName = TemplateExtension.ANY)
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() で指定された正規表現と名前を照合することも可能です。

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

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

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 が付けられていないもの) を使用する必要があります。@TemplateAttribute でアノテーションされたパラメーターは TemplateInstance#getAttribute() を介して取得されます。その他のすべてのパラメーターは、テンプレートのレンダリング時に解決され、エクステンションメソッドに渡されます。

Multiple Parameters Example
@TemplateExtension
class BigDecimalExtensions {

    @TemplateExtension(matchNames = {"scale", "setScale"})
    static BigDecimal scale(BigDecimal val, String ignoredName, int scale, RoundingMode mode) { (1)
        return val.setScale(scale, mode);
    }
}
1 このメソッドは、型 BigDecimal.class のベースオブジェクトを持つ式に一致し、scale()/setScale() 仮想メソッド名と、scale および mode という 2 つの仮想メソッドパラメーターを持ちます。
{item.discountedPrice.scale(2,mode)} (1)
1 item.discountedPriceBigDecimal のインスタンスに解決されます。

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

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

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

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

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

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

{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')}

A map value can be also accessed directly: {map.myKey}. Use the bracket notation for keys that are not legal identifiers: {map['my key']}.
4.6.4.2. リスト
  • 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}

A list element can be accessed directly via an index: {list.10} or even {list[10]}.
4.6.4.3. 整数値
  • 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.4.4. Strings
  • fmt または format: java.lang.String.format() によって文字列のインスタンスをフォーマットします。

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

    • {myStr.format(locale,arg1)}

  • +: 連結のための中置記法。String および StringBuilder 基本オブジェクトで動作します。

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

    • {name + 10}

  • str:['<value>']: 文字列値を返します。たとえば、別の文字列値を簡単に連結できます。

    • {str:['/path/to/'] + fileName}

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

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

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

    • {str:fmt('/path/to/%s', fileName)}

  • str:concat: 指定された引数の文字列表現を連結します。

    • {str:concat("Hello ",name,"!")} nameFoo に解決された場合、Hello Foo! が返されます。

    • {str:concat('/path/to/', fileName)}

  • str:join: 指定された引数の文字列表現を区切り文字で結合します。

    • {str:join('_','Qute','is','cool')}Qute_is_cool を生成します

  • str:builder: 新しい文字列ビルダーを返します。

    • {str:builder('Qute').append("is").append("cool!")}Qute is cool! を生成します。

    • {str:builder('Qute') + "is" + whatisqute + "!"} whatisqutecool に解決される場合、Qute is cool! が生成されます。

  • str:eval:current context で最初の引数の文字列表現をテンプレートとして評価します。

    • {str:eval('Hello {name}!') は、namelovely に解決された場合、Hello lovely! を返します。

    • {str:eval(myTemplate)} myTemplateHello lovely! に解決された場合、Hello{name}!namelovely に対して解決されます。

    • {str:eval('/path/to/{fileName}')} は、fileNamefile.txt に解決される場合、/path/to/file.txt を生成します。

4.6.4.5. 設定
  • 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.4.6. 時間
  • 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では、実行時にデータにアクセスするためのリフレクションの使用を避けることができます。

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

@TemplateData
class Item {

    public final BigDecimal price;

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

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

Item の任意のインスタンスをテンプレート内で直接使用することができます。

{#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)} を介してアクセスできます。

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 名前空間が model_Statuses のネームリゾルバーが自動的に生成されます。
Template Accessing Class Constants
{#if machine.status == model_Statuses:ON}
  The machine is ON!
{/if}

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

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

Enum Annotated With @TemplateEnum
package model;

@TemplateEnum (1)
public enum Status {
    ON,
    OFF
}
1 名前空間 Status を持つネームリゾルバーが自動的に生成される。
@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. グローバル変数

アノテーション io.quarkus.qute.TemplateGlobal は、任意のテンプレートからアクセス可能な グローバル変数 を提供する静的フィールドとメソッドを示すために使用できます。

グローバル変数は次のとおりです。

  • 初期化中に、任意の TemplateInstance計算データ として追加されます。

  • global: 名前空間でアクセス可能です。

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

@TemplateGlobal (1)
public class Globals {

    static int age = 40;

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

    @TemplateGlobal(name = "currentUser") (2)
    static String user() {
       return "Mia";
    }
}
1 クラスが @TemplateGlobal でアノテーションが付けられている場合、パラメーターを宣言しないすべての非 void 非プライベート静的メソッドとすべての非プライベート静的フィールドは、グローバル変数と見なされます。名前はデフォルトです。つまり、フィールド/メソッドの名前が使用されます。
2 メソッドレベルのアノテーションは、クラスレベルのアノテーションをオーバーライドします。この特別なケースでは、名前はデフォルトではなく、明示的に選択されます。
A Template Accessing Global Variables
User: {currentUser} (1)
Age:  {global:age} (2)
Colors: {#each myColors}{it}{#if it_hasNext}, {/if}{/each} (3)
1 currentUser resolves to Globals#user().
2 global: 名前空間が使用され、ageGlobals#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.
The Output
User: Mia
Age:  40
Colors: RED, BLUE

4.8.1. 競合の解消

global: 名前空間経由でアクセスしない場合、グローバル変数は通常のデータオブジェクトと競合する可能性があります。 Type-safe templates は自動的にグローバル変数をオーバーライドします。 たとえば次の定義は、Globals#user() メソッドが提供するグローバル変数をオーバーライドします。

Type-safe Template Definition
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 などのテンプレート例外に遭遇する可能性があります。

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

  • type-safe templates または type-safe expressions をご利用ください。

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

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

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

  • リフレクションベースの値リゾルバーが機能するためには、モデルクラスにアノテーション @io.quarkus.runtime.annotations.RegisterForReflection が必要です。@RegisterForReflection アノテーションの詳細は、ネイティブアプリケーションのヒント ページを参照してください。

4.10. REST Integration

Jakarta REST アプリケーションで Qute を使用する場合は、使用している Jakarta REST スタックに応じて適切なエクステンションを最初に登録する必要があります。

quarkus-rest エクステンションを介して Quarkus REST (旧 RESTEasy Reactive) を使用している場合は、pom.xml ファイルに以下を追加します。

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

従来の RESTEasy Classic ベースの quarkus-resteasy エクステンションを使用している場合は、pom.xml ファイルに以下を追加します。

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

これら両方のエクステンションは、リソースメソッドが TemplateInstance を返せるようにする特別な応答フィルターを登録します。そのため、ユーザーによる内部ステップの処理が不要になります。

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

最終的には、Jakarta REST リソース内で Qute を使用すると、次のようにシンプルになります。

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 Example
@Path("/detail")
class DetailResource {

    @GET
    @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
    public TemplateInstance item() {
        return RestTemplate.data("myItem", new Item("Alpha", 1000)); (1)
    }
}
1 テンプレートの名前は、リソースクラスとメソッド名から派生しています。このケースでは、DetailResource/item です。
Unlike with @Inject the templates obtained via RestTemplate are not validated, i.e. the build does not fail if a template does not exist.

4.11. Vert.x インテグレーション

テンプレートで io.vertx.core.json.JsonObject をデータとして使用する場合、依存関係にまだ含まれていなければ、ビルドファイルに quarkus-vertx エクステンションを追加する必要があります (ほとんどのアプリケーションでは、このエクステンションがデフォルトで使用されます)。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-vertx</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-vertx")

この依存関係を含めると、io.vertx.core.json.JsonObject の特別な値リゾルバーが作成され、テンプレート内の JSON オブジェクトのプロパティーにアクセスできるようになります。

src/main/resources/templates/foo.txt
{tool.name}
{tool.fieldNames}
{tool.fields}
{tool.size}
{tool.empty}
{tool.isEmpty}
{tool.get('name')}
{tool.containsKey('name')}
QuteVertxIntegration.java
import java.util.HashMap;
import jakarta.inject.Inject;
import io.vertx.core.json.JsonObject;
import io.quarkus.qute.Template;

public class QuteVertxIntegration {

    @Inject
    Template foo;

    public String render() {
         HashMap<String, Object> toolMap = new Map<String, Object>();
         toolMap.put("name", "Roq");
         JsonObject jsonObject = new JsonObject(toolMap);
         return foo.data("tool", jsonObject).render();
    }
}

QuteVertxIntegration#render() の出力は次のようになります。

Roq
[name]
[name]
1
false
false
Roq
true

4.12. 開発モード

開発モードでは、src/main/resources/templates にあるすべてのファイルの変更が監視されます。 デフォルトでは、テンプレートを変更するとアプリケーションが再起動され、ビルド時の検証もトリガーされます。

ただし、quarkus.qute.dev-mode.no-restart-templates 設定プロパティーを使用して、アプリケーションを再起動しないテンプレートを指定することは可能です。 設定値は、templates ディレクトリーからの相対テンプレートパスに一致する正規表現であり、パス区切り文字として / が使用されます。 たとえば、quarkus.qute.dev-mode.no-restart-templates=templates/foo.html はテンプレート src/main/resources/templates/foo.html と一致します。 一致するテンプレートがリロードされ、実行時の検証のみが実行されます。

4.13. テスト

テストモードの場合、注入されたタイプセーフテンプレートのレンダリング結果は、CDI Bean として登録されている管理対象の io.quarkus.qute.RenderedResults に記録されます。 テストまたは他の CDI Bean に RenderedResults を注入し、結果をアサートできます。 この機能は、quarkus.qute.test-mode.record-rendered-results 設定プロパティーを false に設定することで無効にできます。

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

4.14.1. 基本概念

基本的な考え方として、すべてのメッセージを非常に単純なテンプレートである可能性があるとみなします。 タイプエラーを防ぐために、メッセージは メッセージバンドルインターフェイス のアノテーション付きメソッドとして定義されます。 Quarkus はビルド時に メッセージバンドル実装 を生成します。

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

@MessageBundle (1)
public interface AppMessages {

    @Message("Hello {name}!") (2)
    String hello_name(String name); (3)
}
1 メッセージバンドルインターフェイスを表します。 バンドル名のデフォルトは msg で、テンプレート式の名前空間として使用されます (例: {msg:hello_name})。
2 各メソッドには @Message のアノテーションが必要です。その値は qute テンプレートです。値が指定されていない場合、ローカライズされたファイルから対応する値が取得されます。該当するファイルがない場合は例外がスローされ、ビルドは失敗します。
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.14.2. デフォルトのバンドル名

@MessageBundle#value() で指定されていない限り、バンドル名がデフォルトになります。 最上位クラスの場合、デフォルトで msg 値が使用されます。 ネストされたクラスの場合、名前は階層内に含まれるすべてのクラスの単純名 (最上位クラスが最初) で設定され、その後にメッセージバンドルインターフェイスの単純名が続きます。 名前はアンダースコアで区切られます。

例えば、以下のメッセージバンドルの名前は、デフォルトで Controller_index になります:

class Controller {

    @MessageBundle
    interface index {

        @Message("Hello {name}!")
        String hello(String name); (1)
   }
}
1 このメッセージは {Controller_index:hello(name)} 経由でテンプレートで使用できます。
The bundle name is also used as a part of the name of a localized file, e.g. Controller_index in the Controller_index_de.properties.

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

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

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

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

4.14.4. バリデーション

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

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

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

      A warning message is logged for each unused parameter.
  • {msg:hello(item.name)} のようなメッセージバンドルメソッドを参照する式も検証されます。

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

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

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

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

While a localized interface enables easy refactoring, an external file might be more convenient in many situations.
Localized Interface Example
import io.quarkus.qute.i18n.Localized;
import io.quarkus.qute.i18n.Message;

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

    @Override
    @Message("Hallo {name}!") (2)
    String hello_name(String name);
}
1 値はロケールタグ文字列(IETF)です。
2 値はローカライズされたテンプレートです。

メッセージバンドルファイルは UTF-8 でエンコードされている必要があります。ファイル名は、関連するバンドル名 (例: msg) とアンダースコア、続いて言語タグ (IETF; 例: en-US) で構成されます。言語タグは省略でき、その場合、デフォルトのバンドルロケールの言語タグが使用されます。例えば、バンドル msg のデフォルトロケールが en の場合、msg.propertiesmsg_en.properties として扱われます。

特定のロケールに対して複数のファイルがある場合、Qute は曖昧さを解決しようとします。アプリケーションルートからのローカライズされたファイルは優先度が高く、依存関係からのローカライズされたファイルよりも優先されます。同じ優先度のファイルが複数存在する場合、ビルドは失敗します。例えば、デフォルトのバンドルロケールが en で、msg.propertiesmsg_en.properties の両方のファイルがアプリケーションルートで見つかった場合、例外がスローされビルドは失敗します。別の例として、2 つの依存関係があり、両方に msg_en.properties ファイルが含まれている場合、ビルドは再び失敗します。一方、アプリケーションルートに msg_en.properties ファイルがあり、依存関係にも msg_en.properties ファイルがある場合、アプリケーションルートからのメッセージが優先され、依存関係からの値を上書きします。

ファイル形式は非常にシンプルで、各行は、等号を区切り文字として使用するキーと値のペア、またはコメント (行頭は #) のいずれかを表します。空白行は無視されます。キーは、対応するメッセージバンドルインターフェースから メソッド名にマッピング されます。値は通常、io.quarkus.qute.i18n.Message#value() で定義されるテンプレートを表します。値は、隣接する複数の通常の行にまたがっていてもかまいません。その場合、行の終端はバックスラッシュ文字 \ でエスケープする必要があります。この動作は、java.util.Properties.load(Reader) メソッドの動作と非常によく似ています。

Localized File Example - msg_de.properties
# This comment is ignored
hello_name=Hallo {name}! (1) (2)
1 ローカライズされたファイルの各行は、キーと値のペアを表します。キーは、メッセージバンドルインターフェイスで宣言されたメソッドに対応しなければなりません。値はメッセージテンプレートになります。
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!

行の終端はバックスラッシュ文字 \ でエスケープされ、次の行の先頭の空白は無視されることに注意してください。つまり、{msg:hello('Edgar')}Hello Edgar and good morning! とレンダリングされます。

ローカライズされたバンドルを定義した後、特定のテンプレートインスタンスに対して正しいバンドルを 選択 する方法、つまりテンプレート内のすべてのメッセージバンドル式に対してロケールを指定する方法が必要です。 デフォルトでは、quarkus.default-locale 設定プロパティーで指定したロケールが、バンドルの選択に使用されます。 また、テンプレートインスタンスの locale 属性を指定することもできます。

locale Attribute Example
@Singleton
public class MyBean {

    @Inject
    Template hello;

    String render() {
       return hello.instance().setLocale("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インタフェースを注入することができます。

Injected Localized Message Bundle Example
@Singleton
public class MyBean {

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

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

列挙は、便利な方法でローカライズできます。 列挙型の単一のパラメーターを受け入れ、メッセージテンプレートが未定義のメッセージバンドルメソッドがある場合:

@Message (1)
String methodName(MyEnum enum);
1 値は意図的に提供されていません。また、ローカライズされたファイルにはこのメソッドのキー/値のペアはありません。

次に、以下のような生成されたテンプレートを受け取ります。

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

さらに、各列挙定数に対して特別なメッセージメソッドが生成されます。 最後に、ローカライズされた各ファイルには、すべての定数メッセージキーのキーと値が含まれている必要があります。

methodName_CONSTANT1=Value 1
methodName_CONSTANT2=Value 2
列挙定数のメッセージキー

デフォルトでは、メッセージキーにはメソッド名、その後に _ 区切り文字、定数名を使用して構成されます。 特定の列挙型の定数名に _ または $ 文字が含まれている場合、この列挙型のすべてのメッセージキーには代わりに _$ 区切り文字を使用する必要があります。 たとえば、methodName_$CONSTANT_1 =Value 1 または methodName_$CONSTANT$1 =Value 1 などです。 ローカライズされた列挙型の定数には、_$ 区切り文字を含めることはできません。

テンプレートでは、列挙定数のローカライズされたメッセージは、{msg:methodName(enumConstant)} のようなメッセージバンドルメソッドで取得できます。。

There is also @TemplateEnum - a convenient annotation to access enum constants in a template.

4.14.6. メッセージテンプレート

メッセージバンドルインターフェースのすべてのメソッドは、メッセージテンプレートを定義する必要があります。 通常、値は io.quarkus.qute.i18n.Message#value() によって定義されますが、利便性のために、ローカライズされたファイル内で値を定義するオプションもあります。 メッセージテンプレートはビルド時に検証されます。 テンプレートが欠落していると検出された場合、例外が出力され、ビルドが失敗します。

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

デフォルトのメッセージテンプレート を定義することも可能です。 デフォルトのテンプレートは、Message#value() が指定されておらず、ローカライズされたファイルにも該当するメッセージテンプレートが定義されていない場合にのみ使用されます。

Example of the Message Bundle Interface with a default value
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;

@MessageBundle
public interface AppMessages {

    @Message(defaultValue = "Goodbye {name}!") (1)
    String goodbye(String name);
}
1 アノテーション値は、ローカライズされたファイルにメッセージテンプレートが定義されていない場合にのみ使用されます。

4.15. 設定リファレンス

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

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

文字列のリスト

qute.html, qute.txt, html, txt

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__FILE_SUFFIX_

Show more

Map<String,String>

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.

例:

  • 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

文字列のリスト

This regular expression is used to exclude template files found in template roots. 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 root 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

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

文字列のリスト

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

The strategy used when multiple templates with the same path are found in the application.

Environment variable: QUARKUS_QUTE_DUPLICIT_TEMPLATES_STRATEGY

Show more

prioritizeIf multiple templates with the same path are found then determine the highest priority value and eliminate all templates with lowest priority. If there is exactly one template remaining then use this template. Otherwise, fail the build. Templates from the root application archive have the priority 30. Templates from other application archives have the priority 10. Templates from build items can define any priority., failFail the build if multiple templates with the same path are found.

prioritizeIf multiple templates with the same path are found then determine the highest priority value and eliminate all templates with lowest priority. If there is exactly one template remaining then use this template. Otherwise, fail the build. Templates from the root application archive have the priority {@code 30}. Templates from other application archives have the priority {@code 10}. Templates from build items can define any priority.

Use the equals character as a command to identify an output expression in all templates from the application template root, i.e. src/main/resources/templates.

By default, no special command is used, i.e. the syntax for an output expression looks like {foo.name}. If set to true then the equals command is used instead. The alternative syntax looks like {=foo.name}.

Environment variable: QUARKUS_QUTE_ALT_EXPR_SYNTAX

Show more

boolean

false

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

Enables or disables the Qute debug mode. This feature is experimental.

When enabled, Qute templates can be debugged directly at runtime. This includes the ability to:

  • Set breakpoints inside templates

  • Inspect the stack trace of visited template nodes during rendering

  • Evaluate expressions in the current Qute context

This mode is intended for development and troubleshooting purposes.

Default value: true

設定例:

quarkus.qute.debug.enabled = false

Environment variable: QUARKUS_QUTE_DEBUG_ENABLED

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

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

Show more

boolean

true

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

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

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

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

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

テンプレートイニシャライザー
  • デフォルトで TemplateInstance.Initializer は登録されていないため、@TemplateGlobal アノテーションは無視されます。

  • カスタム TemplateInstance.Initializer は、EngineBuilder#addTemplateInstanceInitializer() を使用して登録でき、任意のデータと属性を使用してテンプレートインスタンスを初期化できます。

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

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

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

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

    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().
  • Qute がオブジェクトのプロパティーにアクセスし、パブリックメソッドを呼び出せるように、Engine.addValueResolver (new ReflectionValueResolver()) を介して ReflectionValueResolver インスタンスを登録することが推奨されます。

    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.
ユーザー定義タグ
  • ユーザー定義のタグは自動的に登録されません。

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

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

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