GRMustache.swift Swift Platforms License

Swift 的 Mustache 模板

最新版本:2024 年 8 月 19 日 • 5.0.0 版本 • 更新日志

要求: iOS 8.0+ / OSX 10.10+ / tvOS 9.0+ • Xcode 16+ • Swift 5

在 Twitter 上关注 @groue,获取版本发布公告和使用技巧。


特性用法安装文档


特性

GRMustache 扩展了真正的 Mustache 语言,提供了内置的实用工具和可扩展的钩子,让您在需要时避免 Mustache 的严格极简主义。

用法

该库围绕两个主要 API 构建

document.mustache:

Hello {{name}}
Your beard trimmer will arrive on {{format(date)}}.
{{#late}}
Well, on {{format(realDate)}} because of a Martian attack.
{{/late}}
import Mustache

// Load the `document.mustache` resource of the main bundle
let template = try Template(named: "document")

// Let template format dates with `{{format(...)}}`
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
template.register(dateFormatter, forKey: "format")

// The rendered data
let data: [String: Any] = [
    "name": "Arthur",
    "date": Date(),
    "realDate": Date().addingTimeInterval(60*60*24*3),
    "late": true
]

// The rendering: "Hello Arthur..."
let rendering = try template.render(data)

安装

CocoaPods

CocoaPods 是 Xcode 项目的依赖项管理器。

要将 GRMustache.swift 与 CocoaPods 一起使用,请在您的 Podfile 中指定

source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!

pod 'GRMustache.swift'

Carthage

Carthage 是 Xcode 项目的另一个依赖项管理器。

要将 GRMustache.swift 与 Carthage 一起使用,请在您的 Cartfile 中指定

github "groue/GRMustache.swift"

Swift 包管理器

Swift 包管理器是用于管理 Swift 代码分发的开源工具。

要将 GRMustache.swift 与 Swift 包管理器一起使用,请将 https://github.com/groue/GRMustache.swift 添加到包依赖项列表中

dependencies: [
    .package(url: "https://github.com/groue/GRMustache.swift", from: "6.0.0")
]

Mustache 添加到您的目标

targets: [
    .target(
        name: "MyTool",
        dependencies: ["Mustache"])
]

手动

  1. 下载 GRMustache.swift 的副本。

  2. 检出最新的 GRMustache.swift 版本

    cd [GRMustache.swift directory]
    git checkout 6.0.0
  3. Mustache.xcodeproj 项目嵌入到您自己的项目中。

  4. 在应用程序目标的构建阶段选项卡的目标依赖项部分中,添加 MustacheOSXMustacheiOSMustacheWatchOS 目标。

  5. 将目标平台的 Mustache.framework 添加到目标常规选项卡的嵌入式二进制文件部分。

有关此类集成的示例,请参见 MustacheDemoiOS

文档

要摆弄该库,请打开 Xcode/Mustache.xcworkspace 工作区:它包含文件列表顶部的启用了 Mustache 的 Playground。

外部链接

渲染模板

填充模板

其他

加载模板

模板可能来自各种来源

有关更多信息,请查看

错误

不好玩,但它们确实会发生。 只要库需要访问文件系统或其他系统资源,就可能抛出域 NSCocoaErrorDomain 等的标准错误。 Mustache 特有的错误类型为 MustacheError

do {
    let template = try Template(named: "Document")
    let rendering = try template.render(data)
} catch let error as MustacheError {
    // Parse error at line 2 of template /path/to/template.mustache:
    // Unclosed Mustache tag.
    error.description
    
    // templateNotFound, parseError, or renderError
    error.kind
    
    // The eventual template at the source of the error. Can be a path, a URL,
    // a resource name, depending on the repository data source.
    error.templateID
    
    // The eventual faulty line.
    error.lineNumber
    
    // The eventual underlying error.
    error.underlyingError
}

Mustache 标签参考

Mustache 基于标签:{{name}}{{#registered}}...{{/registered}}{{>include}} 等。

它们中的每一个都执行自己的小任务

变量标签

变量标签 {{value}} 渲染与键 value 相关联的值,并进行 HTML 转义。 要避免 HTML 转义,请使用三重 Mustache 标签 {{{value}}}

let template = try Template(string: "{{value}} - {{{value}}}")

// Mario & Luigi - Mario & Luigi
let data = ["value": "Mario & Luigi"]
let rendering = try template.render(data)

段落标签

段落标签 {{#value}}...{{/value}} 是三种不同用法的常见语法

这些行为由与 value 关联的值触发

Falsey 值

如果该值是 falsey,则不渲染该段落。 Falsey 值包括

例如

let template = try Template(string: "<{{#value}}Truthy{{/value}}>")

// "<Truthy>"
try template.render(["value": true])
// "<>"
try template.render([:])                  // missing value
try template.render(["value": false])     // false boolean

集合

如果该值是集合(数组或集合),则该段落会渲染与集合中元素数量相同的次数,并且内部标签可以直接访问元素的键

模板

{{# friends }}
- {{ name }}
{{/ friends }}

数据

[
  "friends": [
    [ "name": "Hulk Hogan" ],
    [ "name": "Albert Einstein" ],
    [ "name": "Tom Selleck" ],
  ]
]

渲染

- Hulk Hogan
- Albert Einstein
- Tom Selleck

其他值

如果该值不是 falsey,也不是集合,则该段落渲染一次,并且内部标签可以直接访问该值的键

模板

{{# user }}
- {{ name }}
- {{ score }}
{{/ user }}

数据

[
  "user": [
    "name": "Mario"
    "score": 1500
  ]
]

渲染

- Mario
- 1500

反向段落标签

当常规段落 {{#value}}...{{/value}} 不会渲染时,会渲染反向段落标签 {{^value}}...{{/value}}。 您可以将其视为 Mustache 的 “else” 或 “unless”。

模板

{{# persons }}
- {{name}} is {{#alive}}alive{{/alive}}{{^alive}}dead{{/alive}}.
{{/ persons }}
{{^ persons }}
Nobody
{{/ persons }}

数据

[
  "persons": []
]

渲染

Nobody

数据

[
  "persons": [
    ["name": "Errol Flynn", "alive": false],
    ["name": "Sacha Baron Cohen", "alive": true]
  ]
]

渲染

- Errol Flynn is dead.
- Sacha Baron Cohen is alive.

局部模板标签

局部模板标签 {{> partial }} 包含另一个模板,由其名称标识。 包含的模板可以访问当前可用的数据

document.mustache:

Guests:
{{# guests }}
  {{> person }}
{{/ guests }}

person.mustache:

{{ name }}

数据

[
  "guests": [
    ["name": "Frank Zappa"],
    ["name": "Lionel Richie"]
  ]
]

渲染

Guests:
- Frank Zappa
- Lionel Richie

支持递归局部模板,但您的数据应避免无限循环。

局部模板查找取决于主模板的来源

文件系统

当模板来自文件系统(通过路径或 URL)时,局部模板名称是相对路径

// Load /path/document.mustache
let template = Template(path: "/path/document.mustache")

// {{> partial }} includes /path/partial.mustache.
// {{> shared/partial }} includes /path/shared/partial.mustache.

局部模板的文件扩展名与主模板相同。

// Loads /path/document.html
let template = Template(path: "/path/document.html")

// {{> partial }} includes /path/partial.html.

当您的模板存储在目录层次结构中时,可以使用绝对路径到局部模板,带前导斜杠。 为此,您需要一个模板存储库,它将定义绝对局部模板路径的根

let repository = TemplateRepository(directoryPath: "/path")
let template = repository.template(named: ...)

// {{> /shared/partial }} includes /path/shared/partial.mustache.

捆绑资源

当模板是捆绑资源时,局部模板名称被解释为资源名称

// Load the document.mustache resource from the main bundle
let template = Template(named: "document")

// {{> partial }} includes the partial.mustache resource.

局部模板的文件扩展名与主模板相同。

// Load the document.html resource from the main bundle
let template = Template(named: "document", templateExtension: "html")

// {{> partial }} includes the partial.html resource.

一般情况

一般来说,局部模板名称始终由模板存储库解释

有关更多信息,请查看 TemplateRepository.swift在 cocoadocs.org 上阅读)。

动态局部模板

标签 {{> partial }} 包含一个模板,即名为 "partial" 的模板。 可以说它是静态地确定的,因为该局部模板已经在渲染模板之前加载。

let repo = TemplateRepository(bundle: Bundle.main)
let template = try repo.template(string: "{{#user}}{{>partial}}{{/user}}")

// Now the `partial.mustache` resource has been loaded. It will be used when
// the template is rendered. Nothing can change that.

您还可以包含动态局部模板。 为此,请使用常规变量标签 {{ partial }},并在渲染数据中为键 “partial” 提供您选择的模板

// A template that delegates the rendering of a user to a partial.
// No partial has been loaded yet.
let template = try Template(string: "{{#user}}{{partial}}{{/user}}")

// The user
let user = ["firstName": "Georges", "lastName": "Brassens", "occupation": "Singer"]

// Two different partials:
let partial1 = try Template(string: "{{firstName}} {{lastName}}")
let partial2 = try Template(string: "{{occupation}}")

// Two different renderings of the same template:
// "Georges Brassens"
try template.render(["user": user, "partial": partial1])
// "Singer"
try template.render(["user": user, "partial": partial2])

局部模板覆盖标签

GRMustache.swift 支持模板继承,如同 hogan.js, mustache.javamustache.php

局部模板覆盖标签 {{< layout }}...{{/ layout }} 在渲染的模板中包含另一个模板,就像常规的局部模板标签 {{> partial}} 一样。

但是,这一次,包含的模板可以包含块 (blocks),并且渲染的模板可以覆盖它们。 块看起来像段落,但使用美元符号:{{$ overrideMe }}...{{/ overrideMe }}

包含的模板 layout.mustache 如下所示,它具有 titlecontent 块,渲染后的模板可以覆盖它们。

<html>
<head>
    <title>{{$ title }}Default title{{/ title }}</title>
</head>
<body>
    <h1>{{$ title }}Default title{{/ title }}</h1>
    {{$ content }}
        Default content
    {{/ content }}}
</body>
</html>

渲染后的模板 article.mustache

{{< layout }}

    {{$ title }}{{ article.title }}{{/ title }}
    
    {{$ content }}
        {{{ article.html_body }}}
        <p>by {{ article.author }}</p>
    {{/ content }}
    
{{/ layout }}
let template = try Template(named: "article")
let data = [
    "article": [
        "title": "The 10 most amazing handlebars",
        "html_body": "<p>...</p>",
        "author": "John Doe"
    ]
]
let rendering = try template.render(data)

渲染结果是一个完整的 HTML 页面

<html>
<head>
    <title>The 10 most amazing handlebars</title>
</head>
<body>
    <h1>The 10 most amazing handlebars</h1>
    <p>...</p>
    <p>by John Doe</p>
</body>
</html>

一些需要了解的事项

动态局部模板覆盖

像常规局部模板标签一样,局部覆盖标签 {{< layout }}...{{/ layout }} 包含一个静态确定的模板,即名为“layout”的模板。

要覆盖动态局部模板,请使用常规节标签 {{# layout }}...{{/ layout }},并在渲染数据中为键“layout”提供您选择的模板。

设置分隔符标签

Mustache 标签通常由“mustaches” {{}} 括起来。*设置分隔符标签*可以在模板中直接更改它。

Default tags: {{ name }}
{{=<% %>=}}
ERB-styled tags: <% name %>
<%={{ }}=%>
Default tags again: {{ name }}

还有一些 API 用于设置这些分隔符。请查看 Configuration.swift 中的 Configuration.tagDelimiterPair (在 cocoadocs.org 上阅读)。

注释标签

{{! Comment tags }} 根本不会被渲染。

pragma 标签

一些 Mustache 实现使用 *Pragma 标签*。它们以百分号 % 开头,并且根本不会被渲染。相反,它们会触发特定于实现的功能。

GRMustache.swift 解释了两个用于设置模板内容类型的 pragma 标签

HTML 模板是默认值。它们会对变量标签 {{name}} 渲染的值进行 HTML 转义。

文本模板中,没有 HTML 转义。{{name}}{{{name}}} 具有相同的渲染效果。文本模板在包含在 HTML 模板中时会被全局进行 HTML 转义。

有关更完整的讨论,请参阅 Configuration.swiftConfiguration.contentType 的文档。

上下文堆栈和表达式

上下文堆栈

变量和节标签在您提供给模板的数据中获取值:{{name}} 在您的输入数据中查找键“name”,或者更准确地说,在*上下文堆栈*中查找。

当渲染引擎进入节时,该上下文堆栈会增长,并在离开时缩小。它的顶部值由最后一个进入的节推送,{{name}} 标签从那里开始查找“name”标识符。如果此顶部值不提供该键,则标签会进一步向下挖掘堆栈,直到找到它正在查找的名称。

例如,给定模板

{{#family}}
- {{firstName}} {{lastName}}
{{/family}}

数据

[
    "lastName": "Johnson",
    "family": [
        ["firstName": "Peter"],
        ["firstName": "Barbara"],
        ["firstName": "Emily", "lastName": "Scott"],
    ]
]

渲染结果是

- Peter Johnson
- Barbara Johnson
- Emily Scott

上下文堆栈通常使用您渲染模板的数据进行初始化

// The rendering starts with a context stack containing `data`
template.render(data)

确切地说,模板具有一个*基本上下文堆栈*,渲染后的数据会添加到该堆栈的顶部。无论渲染的数据是什么,此基本上下文始终可用。例如

// The base context contains `baseData`
template.extendBaseContext(baseData)

// The rendering starts with a context stack containing `baseData` and `data`
template.render(data)

基本上下文通常是注册 过滤器 的好地方

template.extendBaseContext(["each": StandardLibrary.each])

但是您通常会使用 register(:forKey:) 方法注册过滤器,因为它可防止渲染的数据覆盖过滤器的名称

template.register(StandardLibrary.each, forKey: "each")

有关基本上下文的更多信息,请参阅 Template

表达式

变量和节标签包含*表达式*。name 是一个表达式,但 article.titleformat(article.modificationDate) 也是。当一个标签渲染时,它会评估它的表达式,并渲染结果。

有四种表达式

模板渲染值

template.render(["name": "Luigi"])
template.render(Person(name: "Luigi"))

您可以使用以下内容来填充模板:

标准 Swift 类型参考

GRMustache.swift 内置支持以下标准 Swift 类型

Bool

数值类型

GRMustache 支持 IntUIntInt64UInt64FloatDoubleCGFloat

Swift 类型 Int8UInt8 等没有内置支持:在将它们注入模板之前,将它们转换为三种通用类型之一。

要格式化数字,可以使用 NumberFormatter

let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent

let template = try Template(string: "{{ percent(x) }}")
template.register(percentFormatter, forKey: "percent")

// Rendering: 50%
let data = ["x": 0.5]
let rendering = try template.render(data)

有关 Formatter 的更多信息.

String

公开的键

Set

公开的键

Array

公开的键

为了渲染数组索引,或根据元素在数组中的位置改变渲染效果,请使用标准库中的 each 过滤器

document.mustache:

Users with their positions:
{{# each(users) }}
- {{ @indexPlusOne }}: {{ name }}
{{/}}

Comma-separated user names:
{{# each(users) }}{{ name }}{{^ @last }}, {{/}}{{/}}.
let template = try! Template(named: "document")

// Register StandardLibrary.each for the key "each":
template.register(StandardLibrary.each, forKey: "each")

// Users with their positions:
// - 1: Alice
// - 2: Bob
// - 3: Craig
// 
// Comma-separated user names: Alice, Bob, Craig.
let users = [["name": "Alice"], ["name": "Bob"], ["name": "Craig"]]
let rendering = try! template.render(["users": users])

Dictionary

为了迭代字典的键/值对,请使用标准库中的 each 过滤器

document.mustache:

{{# each(dictionary) }}
    key: {{ @key }}, value: {{.}}
{{/}}
let template = try! Template(named: "document")

// Register StandardLibrary.each for the key "each":
template.register(StandardLibrary.each, forKey: "each")

// Renders "key: name, value: Freddy Mercury"
let dictionary = ["name": "Freddy Mercury"]
let rendering = try! template.render(["dictionary": dictionary])

NSObject

NSObject 的渲染取决于实际的类

自定义类型

NSObject 子类

NSObject 子类可以很容易地填充您的模板

// An NSObject subclass
class Person : NSObject {
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

// Charlie Chaplin has a mustache.
let person = Person(name: "Charlie Chaplin")
let template = try Template(string: "{{name}} has a mustache.")
let rendering = try template.render(person)

当从您的 NSObject 子类中提取值时,GRMustache.swift 使用 键-值编码 方法 valueForKey:,只要该键是“安全的”(安全键是声明属性的名称,包括 NSManagedObject 属性)。

子类可以通过覆盖 MustacheBoxable 协议的 mustacheBox 方法来更改此默认行为,如下所述

纯 Swift 值和 MustacheBoxable

键-值编码不适用于 Swift 枚举、结构和类,无论最终的 @objcdynamic 修饰符如何。但是,Swift 值仍然可以通过一些帮助来填充模板。

// Define a pure Swift object:
struct Person {
    let name: String
}

为了让 Mustache 模板从一个人身上提取 name 键,以便它们可以渲染 {{ name }} 标签,我们需要通过遵循 MustacheBoxable 协议来显式地帮助 Mustache 引擎。

extension Person : MustacheBoxable {
    
    // Feed templates with a dictionary:
    var mustacheBox: MustacheBox {
        return Box(["name": self.name])
    }
}

你的 mustacheBox 实现通常会在一个常规的 上调用 Box 函数,该值本身采用了 MustacheBoxable 协议(例如 StringInt),或者是一个数组、一个集合或一个字典。

现在我们可以渲染人员、人员数组、人员字典等等。

// Freddy Mercury has a mustache.
let person = Person(name: "Freddy Mercury")
let template = try Template(string: "{{name}} has a mustache.")
let rendering = try template.render(person)

将字典装箱是构建 box 的一种简单方法。但是,有很多种 box:请查看本文档的其余部分。

Lambdas

Mustache Lambdas 是允许您执行自定义渲染的函数。 有两种 Lambdas:处理 section 标签的 Lambdas 和渲染变量标签的 Lambdas。

// `{{fullName}}` renders just as `{{firstName}} {{lastName}}.`
let fullName = Lambda { "{{firstName}} {{lastName}}" }

// `{{#wrapped}}...{{/wrapped}}` renders the content of the section, wrapped in
// a <b> HTML tag.
let wrapped = Lambda { (string) in "<b>\(string)</b>" }

// <b>Frank Zappa is awesome.</b>
let templateString = "{{#wrapped}}{{fullName}} is awesome.{{/wrapped}}"
let template = try Template(string: templateString)
let data: [String: Any] = [
    "firstName": "Frank",
    "lastName": "Zappa",
    "fullName": fullName,
    "wrapped": wrapped]
let rendering = try template.render(data)

Lambdas 是自定义渲染函数的一种特殊情况。当您需要执行自定义渲染时,原始的 RenderFunction 类型为您提供了额外的灵活性。请参阅 CoreFunctions.swift (在 cocoadocs.org 上阅读)。

☝️ 注意:Mustache lambdas 与 动态局部模板 有些重叠。Mustache 规范要求使用 Lambdas。动态局部模板效率更高,因为它们避免了重复解析 lambda 字符串。

过滤器

过滤器像函数一样应用,带有括号:{{ uppercase(name) }}

一般来说,使用过滤器是一个三步过程

// 1. Define the filter using the `Filter()` function:
let uppercase = Filter(...)

// 2. Assign a name to your filter, and register it in a template:
template.register(uppercase, forKey: "uppercase")

// 3. Render
template.render(...)

可以将过滤器分为四种来考虑

值过滤器

值过滤器转换任何类型的输入。 它们也可以返回任何内容。

例如,这是一个 square 过滤器,用于对整数求平方

// Define the `square` filter.
//
// square(n) evaluates to the square of the provided integer.
let square = Filter { (n: Int?) in
    guard let n = n else {
        // No value, or not an integer: return nil.
        // We could throw an error as well.
        return nil
    }
    
    // Return the result
    return n * n
}

// Register the square filter in our template:
let template = try Template(string: "{{n}} × {{n}} = {{square(n)}}")
template.register(square, forKey:"square")

// 10 × 10 = 100
let rendering = try template.render(["n": 10])

过滤器可以像上面一样接受精确类型的参数。 您可能更喜欢自己管理值类型

// Define the `abs` filter.
//
// abs(x) evaluates to the absolute value of x (Int or Double):
let absFilter = Filter { (box: MustacheBox) in
    switch box.value {
    case let int as Int:
        return abs(int)
    case let double as Double:
        return abs(double)
    default:
        return nil
    }
}

您也可以处理集合和字典,并返回新的集合和字典

// Define the `oneEveryTwoItems` filter.
//
// oneEveryTwoItems(collection) returns the array of even items in the input
// collection.
let oneEveryTwoItems = Filter { (box: MustacheBox) in
    // `box.arrayValue` returns a `[MustacheBox]` for all boxed collections
    // (Array, Set, NSArray, etc.).
    guard let boxes = box.arrayValue else {
        // No value, or not a collection: return the empty box
        return nil
    }
    
    // Rebuild another array with even indexes:
    var result: [MustacheBox] = []
    for (index, box) in boxes.enumerated() where index % 2 == 0 {
        result.append(box)
    }
    
    return result
}

// A template where the filter is used in a section, so that the items in the
// filtered array are iterated:
let templateString = "{{# oneEveryTwoItems(items) }}<{{.}}>{{/ oneEveryTwoItems(items) }}"
let template = try Template(string: templateString)

// Register the oneEveryTwoItems filter in our template:
template.register(oneEveryTwoItems, forKey: "oneEveryTwoItems")

// <1><3><5><7><9>
let rendering = try template.render(["items": Array(1..<10)])

多参数过滤器也可以,但这次您需要使用 VariadicFilter() 函数

// Define the `sum` filter.
//
// sum(x, ...) evaluates to the sum of provided integers
let sum = VariadicFilter { (boxes: [MustacheBox]) in
    var sum = 0
    for box in boxes {
        sum += (box.value as? Int) ?? 0
    }
    return sum
}

// Register the sum filter in our template:
let template = try Template(string: "{{a}} + {{b}} + {{c}} = {{ sum(a,b,c) }}")
template.register(sum, forKey: "sum")

// 1 + 2 + 3 = 6
let rendering = try template.render(["a": 1, "b": 2, "c": 3])

过滤器可以链接,通常是更复杂表达式的一部分

Circle area is {{ format(product(PI, circle.radius, circle.radius)) }} cm².

当您想要格式化值时,只需使用 NumberFormatter、DateFormatter 或通常任何 Foundation 的 Formatter。 它们是现成的过滤器

let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent

let template = try Template(string: "{{ percent(x) }}")
template.register(percentFormatter, forKey: "percent")

// Rendering: 50%
let data = ["x": 0.5]
let rendering = try template.render(data)

有关格式化器的更多信息.

预渲染过滤器

如上所述,值过滤器处理输入值,这些值可以是任何类型(布尔值、整数、集合等)。 无论输入值是什么,预渲染过滤器总是处理字符串。 它们有机会在这些字符串实际包含在最终模板渲染中之前更改它们。

例如,您可以反转渲染

// Define the `reverse` filter.
//
// reverse(x) renders the reversed rendering of its argument:
let reverse = Filter { (rendering: Rendering) in
    let reversedString = String(rendering.string.characters.reversed())
    return Rendering(reversedString, rendering.contentType)
}

// Register the reverse filter in our template:
let template = try Template(string: "{{reverse(value)}}")
template.register(reverse, forKey: "reverse")

// ohcuorG
try template.render(["value": "Groucho"])

// 321
try template.render(["value": 123])

正如您所看到的,这样的过滤器并不能完全处理原始字符串。 它处理的是一个 Rendering,它是一种带有其 contentType(文本或 HTML)的风味字符串。

此渲染通常是文本:简单值(整数、字符串等)呈现为文本。 我们的反转过滤器保留此内容类型,并且不会破坏 HTML 实体

// &gt;lmth&lt;
try template.render(["value": "<html>"])

自定义渲染过滤器

一个例子将展示它们如何使用

// Define the `pluralize` filter.
//
// {{# pluralize(count) }}...{{/ }} renders the plural form of the
// section content if the `count` argument is greater than 1.
let pluralize = Filter { (count: Int?, info: RenderingInfo) in
    
    // The inner content of the section tag:
    var string = info.tag.innerTemplateString
    
    // Pluralize if needed:
    if let count = count, count > 1 {
        string += "s"  // naive
    }
    
    return Rendering(string)
}

// Register the pluralize filter in our template:
let templateString = "I have {{ cats.count }} {{# pluralize(cats.count) }}cat{{/ }}."
let template = try Template(string: templateString)
template.register(pluralize, forKey: "pluralize")

// I have 3 cats.
let data = ["cats": ["Kitty", "Pussy", "Melba"]]
let rendering = try template.render(data)

由于这些过滤器执行自定义渲染,因此它们基于 RenderFunction,就像 Lambdas 一样。 有关 RenderingInfoRendering 类型的更多信息,请查看 CoreFunctions.swift 中的 RenderFunction 类型(在 cocoadocs.org 上阅读)。

高级过滤器

上面看到的所有过滤器都是 FilterFunction 的特定情况。 “值过滤器”、“预渲染过滤器”和“自定义渲染过滤器”是常见的使用案例,它们被授予特定的 API。

然而,该库附带了一些内置过滤器,这些过滤器不太适合这些类别中的任何一个。 请查看它们的 文档。 并且由于它们都是使用公共 GRMustache.swift API 编写的,因此还可以查看它们的 源代码,以获取灵感。 常规的 FilterFunction 本身在 CoreFunctions.swift 中进行了详细说明(在 cocoadocs.org 上阅读)。

高级盒子 (Boxes)

为模板提供数据的值能够表现出许多不同的行为。 让我们回顾其中的一些

这种行为的多样性是由 MustacheBox 类型实现的。 无论何时,值、数组、过滤器等为模板提供数据时,它都会转换为一个 box,该 box 与渲染引擎交互。

让我们详细描述 {{ F(A) }} 标签的渲染,并阐明一些可用的自定义设置

  1. 评估 AF 表达式:渲染引擎在 上下文堆栈 中查找 box,这些 box 为键“A”和“F”返回非空 box。 键提取服务由可自定义的 KeyedSubscriptFunction 提供。

    这就是 NSObject 如何公开其属性,以及 Dictionary 如何公开其键。

  2. F box 的可自定义 FilterFunction 使用 A box 作为参数进行评估。

    结果 box 可能很好地取决于 A box 的可自定义值,但 A box 的所有其他方面都可能涉及。 这就是为什么有各种类型的 过滤器

  3. 然后,渲染引擎在上下文堆栈中查找所有具有自定义 WillRenderFunction 的 box。 这些函数有机会处理结果 box,并最终返回另一个 box。

    例如,这就是盒装的 DateFormatter 如何格式化 section 中的所有日期:其 WillRenderFunction 将日期格式化为字符串。

  4. 生成的 box 准备好进行渲染。 对于常规和反向 section 标签,渲染引擎查询 box 的可自定义布尔值,以便 {{# F(A) }}...{{/}}{{^ F(A) }}...{{/}} 不能同时渲染。

    Bool 类型显然具有布尔值,但 String 类型也具有布尔值,因此空字符串被认为是 虚假 的。

  5. 最终渲染生成的 box:执行其可自定义的 RenderFunction。 根据其内容类型,其 Rendering 结果被 HTML 转义,并附加到最终模板渲染。

    Lambdas 使用这样的 RenderFunction预渲染过滤器自定义渲染过滤器 也是如此。

  6. 最后,渲染引擎在上下文堆栈中查找所有具有自定义 DidRenderFunction 的 box。

    这个由 LocalizerLogger 小工具使用。

所有这些可自定义的属性都在低级别的 MustacheBox 初始化器中公开

// MustacheBox initializer
init(
    value value: Any? = nil,
    boolValue: Bool? = nil,
    keyedSubscript: KeyedSubscriptFunction? = nil,
    filter: FilterFunction? = nil,
    render: RenderFunction? = nil,
    willRender: WillRenderFunction? = nil,
    didRender: DidRenderFunction? = nil)

我们将在下面分别描述它们中的每一个,即使您可以同时提供多个

通过混合所有这些参数,您可以精细地调整 box 的行为。

内置的实用工具

该库附带内置的 goodies,可以帮助您渲染模板:格式化值、渲染数组索引、本地化模板等。