# 模板 Deft 的服务端模板构建在块字面量(block literal)之上 —— `[html ... \html]`、`[sql ... \sql]`、`[codeblock ... \codeblock]`。块是一种多行字面量,其内部会被词法化:字面文本、`$var` 插值和嵌套的 `[...]` 调用可以自由混合。编译器把内部内容打包成一个带标签的映射,并调用与块同名的函数,由它应用相应领域的转义并返回渲染结果。 ## 块如何工作 `[name ... \name]` 脱糖为一次普通的调用:
[name %{ :parts @[ part1 part2 ... ] }]
- 块名通过常规命名空间链解析 —— 它就是一个函数。`html` 函数来自 `from "deft/stdext/html" import @{ html html-raw }`;你可以用同样的方式定义自己的块处理器。 - `:parts` 是一个由已求值部分组成的向量: | 部分 | 形态 | 含义 | |---|---|---| | string | `"text"` | 字面块文本 —— 原样输出 | | `$var` | `%__escape{:value ...}` | 插值 —— 应用领域转义 | | trusted | `%__HtmlSafe{...}` / `%__SqlSafe{...}` | 已预转义 —— 直接通过 | | list | `@[ ... ]` | 收集到的结果(例如 `for` 循环体)—— 递归并拼接 | 处理器的职责:遍历各部分,按各自领域转义并拼接。这就是全部契约 —— 参见定义自己的模板。 ## `[html ... \html]` 主力工具。返回一个 `%__HtmlSafe{:value ...}` 带标签映射(一种知道自身可安全嵌入的普通字符串)。
from "deft/stdext/html" import @{ html html-raw html-escape }

def render-user {| user^map |
    [html
        <div class="user">
            <h1>$user~name</h1>
            <p>Age: $user~age</p>
        </div>
    \html]
}

def handler {|req res|
    http/html $res [render-user $alice]
}
### 插值形式 字符串插值中的任何形式都可以在块内使用: | 形式 | 示例 | 说明 | |---|---|---| | `$var` | `$name` | 变量或 `$user~field` 链 | | `$var(args)` | `$xs(len)` | 后缀调用 | | `$(expr)` | `$($price * $quantity)` | 中缀表达式 | | `$[cmd]` | `$[upper $name]` | 前缀调用 —— 结果原样使用 | | `[cmd]` | `[upper $name]` | 裸方括号调用同样会插值 | 字面量 `[` 需转义为 `\[`。 ### 转义是显式的 块是原始模板:插值的 `$var` 值不转义直接通过。请显式转义动态文本: - `[html-escape $text]` —— 实体转义(`&` `<` `>` `"` `'`),用于安全的文本插值。 - `[html-raw $markup]` —— 将字符串标记为受信任的 HTML(不转义)。
set bio "<b>bold</b>"

[html
    <p>[html-escape $bio]</p>   # &lt;b&gt;bold&lt;/b&gt;
    <p>[html-raw $bio]</p>      # <b>bold</b>
\html]
任何被 `__HtmlSafe` 包装的内容(`[html ... \html]` 或 `[html-raw ...]` 的结果)都会原样通过嵌套块 —— 片段正是这样组合的。 ### 片段与嵌套 返回 `[html ... \html]` 的函数可以直接嵌入外层块中:
def render-user {| user^map |
    [html <h1>$user~name</h1> \html]
}

[html
    <div class="card">
        [render-user $user]
    </div>
\html]
### 循环 块中带方括号的 `[for ...]` 会把每次迭代的循环体结果收集为一个列表部分,由处理器拼接。为每一项输出一个嵌套的 `[html ... \html]` 块:
def user-list {| users^list |
    [html
        <ul>
        [for u $users {
            [html <li>[html-escape $u~name]</li> \html]
        }]
        </ul>
    \html]
}
### 原始块 `[*name ... \name]` 逐字捕获内部内容 —— 完全不做 `$var` 或 `[cmd]` 插值。适用于源码列表:
[*codeblock
    set x $who
    [str $x]
\codeblock]        # $who stays literal; the block is syntax-highlighted
## 定义自己的模板 任何接受一个参数的函数都可以作为块处理器。块映射以带块名标签的 `%{ :parts @[...] }` 形式传入;按类型解包每个部分并应用你自己的转义规则。
def block-part-str {| part |
    if ([type $part] == "__escape") {
        # $var interpolation — apply your own escaping here
        [str [get $part :value]]
    } else if ([type $part] == "__HtmlSafe") or ([type $part] == "__SqlSafe") {
        # pre-escaped content — trust it
        [str [get $part :value]]
    } else if [is? "list" $part] {
        # collected loop bodies — recurse and join
        [join [collect [map $part {[block-part-str $it]}]] ""]
    } else {
        # literal text
        [str $part]
    }
}

def my-pill {| block^map |
    set body [join [collect [map [get $block :parts] {[block-part-str $it]}]] ""]
    [html <span class="pill">$body</span> \html]
}

[my-pill
    hi $who
\my-pill]        # → <span class="pill">hi world</span>
返回 `__HtmlSafe` 值(通过 `[html ... \html]` 或 `[html-raw ...]`),这样你的模板就能安全地嵌套在其他块中。stdext 包的 `html.dft` 和 `sql.dft` 是参考实现: - `packages/stdext/src/html.dft` — `html`, `html-raw`, `html-escape` - `packages/stdext/src/sql.dft` — `sql`, `sql-raw`, `sql-escape-value` ## `[sql ... \sql]` —— 另一个示例 同样的块机制,但带有 SQL 感知的转义。`html` 基于字符串,而 `sql` 按 Deft 类型转义值: | 值 | SQL 字面量 | |---|---| | string | `'O''Brien'`(单引号包围,引号翻倍) | | number | `42`, `3.14` | | boolean | `TRUE` / `FALSE` | | nil | `NULL` | | list | `(1, 2, 'text')` | | map | `"col" = 'val' AND ...`(WHERE 风格) |
from "deft/stdext/sql" import @{ sql sql-raw }

set name "O'Brien"
set id 42
set active true

set query [sql
    SELECT * FROM users
    WHERE name = $name AND id = $id AND active = $active
\sql]

# SELECT * FROM users
# WHERE name = 'O''Brien' AND id = 42 AND active = TRUE
对于类型感知转义器会破坏的受信任片段 —— 函数调用、标识符、运算符 —— 请使用 `[sql-raw ...]`:
[sql
    SELECT * FROM users
    WHERE updated_at > [sql-raw "NOW() - INTERVAL '1 day'"]
\sql]
与 `sqlite/query` 自然搭配:
set rows [sqlite/query $db [sql
    SELECT * FROM users WHERE name = $name
\sql]]
## 快速参考 | 形式 | 用途 | |---|---| | `[html ... \html]` | 渲染 HTML 片段;返回 `__HtmlSafe` | | `[html-raw $x]` | 将字符串标记为受信任的 HTML | | `[html-escape $x]` | 为安全插值对文本做实体转义 | | `[sql ... \sql]` | 构建带类型感知值转义的 SQL 字符串 | | `[sql-raw $x]` | 将片段标记为受信任的 SQL | | `[*name ... \name]` | 任意块,逐字捕获(无插值) | | `[myname ... \myname]` | 你自己的块处理器 —— 任意单参数函数 |