最新版本:2024 年 8 月 19 日 • 5.0.0 版本 • 更新日志
要求: iOS 8.0+ / OSX 10.10+ / tvOS 9.0+ • Xcode 16+ • Swift 5
在 Twitter 上关注 @groue,获取版本发布公告和使用技巧。
GRMustache 扩展了真正的 Mustache 语言,提供了内置的实用工具和可扩展的钩子,让您在需要时避免 Mustache 的严格极简主义。
{{ uppercase(name) }}
该库围绕两个主要 API 构建
Template(...)
初始化方法。Template.render(...)
方法。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 是 Xcode 项目的依赖项管理器。
要将 GRMustache.swift 与 CocoaPods 一起使用,请在您的 Podfile 中指定
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
pod 'GRMustache.swift'
Carthage 是 Xcode 项目的另一个依赖项管理器。
要将 GRMustache.swift 与 Carthage 一起使用,请在您的 Cartfile 中指定
github "groue/GRMustache.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"])
]
下载 GRMustache.swift 的副本。
检出最新的 GRMustache.swift 版本
cd [GRMustache.swift directory]
git checkout 6.0.0
将 Mustache.xcodeproj
项目嵌入到您自己的项目中。
在应用程序目标的构建阶段选项卡的目标依赖项部分中,添加 MustacheOSX
、MustacheiOS
或 MustacheWatchOS
目标。
将目标平台的 Mustache.framework
添加到目标常规选项卡的嵌入式二进制文件部分。
有关此类集成的示例,请参见 MustacheDemoiOS。
要摆弄该库,请打开 Xcode/Mustache.xcworkspace
工作区:它包含文件列表顶部的启用了 Mustache 的 Playground。
外部链接
渲染模板
填充模板
其他
模板可能来自各种来源
原始 Swift 字符串
let template = try Template(string: "Hello {{name}}")
捆绑资源
// Loads the "document.mustache" resource of the main bundle:
let template = try Template(named: "document")
文件和 URL
let template = try Template(path: "/path/to/document.mustache")
let template = try Template(URL: templateURL)
模板存储库
模板存储库表示一组模板。 它们可以独立配置,并提供整洁的功能,例如模板缓存。 例如
// The repository of Bash templates, with extension ".sh":
let repo = TemplateRepository(bundle: Bundle.main, templateExtension: "sh")
// Disable HTML escaping for Bash scripts:
repo.configuration.contentType = .text
// Load the "script.sh" resource:
let template = repo.template(named: "script")!
有关更多信息,请查看
不好玩,但它们确实会发生。 只要库需要访问文件系统或其他系统资源,就可能抛出域 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 基于标签:{{name}}
、{{#registered}}...{{/registered}}
、{{>include}}
等。
它们中的每一个都执行自己的小任务
{{name}}
渲染值。{{#items}}...{{/items}}
执行条件判断、循环和对象作用域。{{^items}}...{{/items}}
是常规段落标签的姊妹标签,并在另一个不渲染时渲染。{{>partial}}
允许您在一个模板中包含另一个模板。 {{<layout}}...{{/layout}}
提供模板继承。 {{=<% %>=}}
允许您更改标签分隔符。{{! Wow. Such comment. }}
变量标签 {{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 值包括
例如
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.
一般来说,局部模板名称始终由模板存储库解释
Template(named:...)
使用基于捆绑的模板存储库:局部模板名称是资源名称。Template(path:...)
使用基于文件的模板存储库:局部模板名称是相对路径。Template(URL:...)
使用基于 URL 的模板存储库:局部模板名称是相对 URL。Template(string:...)
使用无法加载任何局部模板的模板存储库。templateRepository.template(named:...)
使用模板存储库的局部模板加载机制。有关更多信息,请查看 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.java 和 mustache.php。
局部模板覆盖标签 {{< layout }}...{{/ layout }}
在渲染的模板中包含另一个模板,就像常规的局部模板标签 {{> partial}}
一样。
但是,这一次,包含的模板可以包含块 (blocks),并且渲染的模板可以覆盖它们。 块看起来像段落,但使用美元符号:{{$ overrideMe }}...{{/ overrideMe }}
。
包含的模板 layout.mustache
如下所示,它具有 title
和 content
块,渲染后的模板可以覆盖它们。
<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>
一些需要了解的事项
一个块 {{$ title }}...{{/ title }}
总是会被渲染,并且只渲染一次。没有布尔检查,没有集合迭代。“title”标识符是一个允许其他模板覆盖该块的名称,而不是渲染数据中的键。
一个模板可以包含多个局部覆盖标签。
一个模板可以覆盖一个局部模板,该局部模板本身又覆盖另一个局部模板。递归是可能的,但您的数据应该避免无限循环。
一般来说,模板的任何部分都可以使用局部模板和局部覆盖标签进行重构,而无需在其他地方(在依赖它的其他模板或您的代码中)进行任何修改。
像常规局部模板标签一样,局部覆盖标签 {{< 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 }}
根本不会被渲染。
一些 Mustache 实现使用 *Pragma 标签*。它们以百分号 %
开头,并且根本不会被渲染。相反,它们会触发特定于实现的功能。
GRMustache.swift 解释了两个用于设置模板内容类型的 pragma 标签
{{% CONTENT_TYPE:TEXT }}
{{% CONTENT_TYPE:HTML }}
HTML 模板是默认值。它们会对变量标签 {{name}}
渲染的值进行 HTML 转义。
在文本模板中,没有 HTML 转义。{{name}}
和 {{{name}}}
具有相同的渲染效果。文本模板在包含在 HTML 模板中时会被全局进行 HTML 转义。
有关更完整的讨论,请参阅 Configuration.swift 中 Configuration.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.title
和 format(article.modificationDate)
也是。当一个标签渲染时,它会评估它的表达式,并渲染结果。
有四种表达式
点 .
又名 Mustache 语言中的“隐式迭代器”
隐式迭代器评估为上下文堆栈的顶部,即由最后一个进入的节推送的值。
它允许您迭代字符串集合,例如。例如,当给定 [1,2,3] 时,{{#items}}<{{.}}>{{/items}}
渲染 <1><2><3>
。
标识符,如 name
标识符(如 name
)的评估会遍历上下文堆栈,直到找到提供 name
键的值。
标识符不能包含空格、点、括号和逗号。它们不能以以下任何字符开头:{}&$#^/<>
。
复合表达式,如 article.title
,通常为 <expression>.<identifier>
这一次没有遍历上下文堆栈:article.title
评估为文章的标题,而不管封闭上下文定义的 title
键如何。
.title
(带前导点)是一个基于隐式迭代器的复合表达式:它在上下文堆栈的顶部查找 title
。
比较以下三个模板
...{{# article }}{{ title }}{{/ article }}...
...{{# article }}{{ .title }}{{/ article }}...
...{{ article.title }}...
第一个将在上下文堆栈中的任何位置查找 title
,从 article
对象开始。
另外两个是相同的:它们确保 title
键来自 article
对象本身。
过滤器表达式,如 format(date)
,通常为 <expression>(<expression>, ...)
过滤器 在下面介绍。
模板渲染值
template.render(["name": "Luigi"])
template.render(Person(name: "Luigi"))
您可以使用以下内容来填充模板:
采用 MustacheBoxable
协议的值,例如 String
、Int
、NSObject
及其子类(请参阅 标准 Swift 类型参考 和 自定义类型)
数组、集合和字典(Swift 数组、集合、字典和 Foundation 集合)。这不包括其他集合,例如 Swift 范围。
Goodies,例如 Foundation 的格式化程序。
GRMustache.swift 内置支持以下标准 Swift 类型
{{bool}}
渲染 “0” 或 “1”。{{#bool}}...{{/bool}}
仅当 *bool* 为 true 时才渲染。{{^bool}}...{{/bool}}
仅当 *bool* 为 false 时才渲染。GRMustache 支持 Int
、UInt
、Int64
、UInt64
、Float
、Double
和 CGFloat
{{number}}
渲染 *number* 的标准 Swift 字符串插值。{{#number}}...{{/number}}
仅当 *number* 不为 0(零)时才渲染。{{^number}}...{{/number}}
仅当 *number* 为 0(零)时才渲染。Swift 类型 Int8
、UInt8
等没有内置支持:在将它们注入模板之前,将它们转换为三种通用类型之一。
要格式化数字,可以使用 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)
{{string}}
渲染 *string*,已进行 HTML 转义。{{{string}}}
渲染 *string*,未进行 HTML 转义。{{#string}}...{{/string}}
仅当 *string* 不为空时才渲染。{{^string}}...{{/string}}
仅当 *string* 为空时才渲染。公开的键
string.length
:字符串的长度。{{set}}
渲染集合元素的渲染结果的连接。{{#set}}...{{/set}}
渲染的次数与集合中的元素数量相同,并将它们推送到 上下文堆栈 的顶部。{{^set}}...{{/set}}
仅当集合为空时才渲染。公开的键
set.first
:第一个元素。set.count
:集合中元素的数量。{{array}}
渲染数组元素的渲染结果的连接。{{#array}}...{{/array}}
渲染的次数与数组中的元素数量相同,并将它们推送到 上下文堆栈 的顶部。{{^array}}...{{/array}}
仅当数组为空时才渲染。公开的键
array.first
:第一个元素。array.last
:最后一个元素。array.count
:数组中元素的数量。为了渲染数组索引,或根据元素在数组中的位置改变渲染效果,请使用标准库中的 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}}
渲染 *dictionary* 的标准 Swift 字符串插值(不是很实用)。{{#dictionary}}...{{/dictionary}}
渲染一次,将字典推送到 上下文堆栈 的顶部。{{^dictionary}}...{{/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 的渲染取决于实际的类
NSFastEnumeration
当一个对象符合 NSFastEnumeration 协议时,如 NSArray,它的渲染方式与 Swift Array 相同。NSSet 是一个例外,渲染为 Swift Set。NSDictionary,另一个例外,渲染为 Swift Dictionary。
根据其值,NSNumber 渲染为 Swift Bool、Int、UInt、Int64、UInt64、Float 或 Double。
NSString 渲染为 String
NSNull 渲染为
{{null}}
不渲染。{{#null}}...{{/null}}
不渲染。{{^null}}...{{/null}}
渲染。对于其他 NSObject,应用这些默认规则
{{object}}
渲染 description
方法,已进行 HTML 转义。{{{object}}}
渲染 description
方法,未进行 HTML 转义。{{#object}}...{{/object}}
渲染一次,将对象推送到 上下文堆栈 的顶部。{{^object}}...{{/object}}
不渲染。在对 Objective-C 运行时提供支持的情况下,模板可以渲染对象属性:{{ user.name }}
。
子类可以通过覆盖 MustacheBoxable
协议的 mustacheBox
方法来更改此行为。有关更多信息,请查看下面 自定义类型 的渲染。
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 枚举、结构和类,无论最终的 @objc
或 dynamic
修饰符如何。但是,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
协议(例如 String
或 Int
),或者是一个数组、一个集合或一个字典。
现在我们可以渲染人员、人员数组、人员字典等等。
// 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:请查看本文档的其余部分。
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(radius) }}
{{ uppercase(...) }}
{{# pluralize(cats.count) }}cat{{/}}
值过滤器转换任何类型的输入。 它们也可以返回任何内容。
例如,这是一个 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 实体
// >lmth<
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 一样。 有关 RenderingInfo
和 Rendering
类型的更多信息,请查看 CoreFunctions.swift 中的 RenderFunction
类型(在 cocoadocs.org 上阅读)。
上面看到的所有过滤器都是 FilterFunction
的特定情况。 “值过滤器”、“预渲染过滤器”和“自定义渲染过滤器”是常见的使用案例,它们被授予特定的 API。
然而,该库附带了一些内置过滤器,这些过滤器不太适合这些类别中的任何一个。 请查看它们的 文档。 并且由于它们都是使用公共 GRMustache.swift API 编写的,因此还可以查看它们的 源代码,以获取灵感。 常规的 FilterFunction
本身在 CoreFunctions.swift 中进行了详细说明(在 cocoadocs.org 上阅读)。
为模板提供数据的值能够表现出许多不同的行为。 让我们回顾其中的一些
Bool 可以触发或阻止 section 的渲染
{{# isVerified }}VERIFIED{{/ isVerified }}
{{^ isVerified }}NOT VERIFIED{{/ isVerified }}
数组多次渲染 section,并公开 count
、first
和 last
键
You see {{ objects.count }} objects:
{{# objects }}
- {{ name }}
{{/ objects }}
字典公开其所有键
{{# user }}
- {{ name }}
- {{ age }}
{{/ user }}
NSObject 公开其所有属性
{{# user }}
- {{ name }}
- {{ age }}
{{/ user }}
Foundation 的 Formatter 能够格式化值 (更多信息)
{{ format(date) }}
StandardLibrary.each
是一个过滤器,用于在迭代数组时定义一些额外的键 (更多信息)
{{# each(items) }}
- {{ @indexPlusOne }}: {{ name }}
{{/}}
这种行为的多样性是由 MustacheBox
类型实现的。 无论何时,值、数组、过滤器等为模板提供数据时,它都会转换为一个 box,该 box 与渲染引擎交互。
让我们详细描述 {{ F(A) }}
标签的渲染,并阐明一些可用的自定义设置
评估 A
和 F
表达式:渲染引擎在 上下文堆栈 中查找 box,这些 box 为键“A”和“F”返回非空 box。 键提取服务由可自定义的 KeyedSubscriptFunction
提供。
这就是 NSObject 如何公开其属性,以及 Dictionary 如何公开其键。
F box 的可自定义 FilterFunction
使用 A box 作为参数进行评估。
结果 box 可能很好地取决于 A box 的可自定义值,但 A box 的所有其他方面都可能涉及。 这就是为什么有各种类型的 过滤器。
然后,渲染引擎在上下文堆栈中查找所有具有自定义 WillRenderFunction
的 box。 这些函数有机会处理结果 box,并最终返回另一个 box。
例如,这就是盒装的 DateFormatter 如何格式化 section 中的所有日期:其 WillRenderFunction
将日期格式化为字符串。
生成的 box 准备好进行渲染。 对于常规和反向 section 标签,渲染引擎查询 box 的可自定义布尔值,以便 {{# F(A) }}...{{/}}
和 {{^ F(A) }}...{{/}}
不能同时渲染。
Bool 类型显然具有布尔值,但 String 类型也具有布尔值,因此空字符串被认为是 虚假 的。
最终渲染生成的 box:执行其可自定义的 RenderFunction
。 根据其内容类型,其 Rendering
结果被 HTML 转义,并附加到最终模板渲染。
最后,渲染引擎在上下文堆栈中查找所有具有自定义 DidRenderFunction
的 box。
所有这些可自定义的属性都在低级别的 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)
我们将在下面分别描述它们中的每一个,即使您可以同时提供多个
value
可选的 *value* 参数给出盒装的值。 该值在渲染 box 时使用(除非您提供自定义 RenderFunction)。 它也由 MustacheBox 的 value
属性返回。
let aBox = MustacheBox(value: 1)
// Renders "1"
let template = try Template(string: "{{a}}")
try template.render(["a": aBox])
boolValue
可选的 *boolValue* 参数告诉 Box 是否应该触发或阻止常规 {{#section}}...{{/}}
和反向 {{^section}}...{{/}}
标签的渲染。 默认值为 true。
// Render "true", "false"
let template = try Template(string:"{{#.}}true{{/.}}{{^.}}false{{/.}}")
try template.render(MustacheBox(boolValue: true))
try template.render(MustacheBox(boolValue: false))
keyedSubscript
可选的 *keyedSubscript* 参数是一个 KeyedSubscriptFunction
,它允许 Mustache 引擎从 box 中提取键。 例如,{{a}}
标签将使用 "a"
作为参数调用下标函数,并渲染返回的 box。
默认值为 nil,这意味着无法提取任何键。
有关更多信息,请查看 CoreFunctions.swift 中的 KeyedSubscriptFunction
类型(在 cocoadocs.org 上阅读)。
let box = MustacheBox(keyedSubscript: { (key: String) in
return Box("key:\(key)")
})
// Renders "key:a"
let template = try Template(string:"{{a}}")
try template.render(box)
filter
可选的 *filter* 参数是一个 FilterFunction
,它允许 Mustache 引擎评估涉及该 box 的过滤表达式。 默认值为 nil,这意味着该 box 不能用作过滤器。
有关更多信息,请查看 CoreFunctions.swift 中的 FilterFunction
类型(在 cocoadocs.org 上阅读)。
let box = MustacheBox(filter: Filter { (x: Int?) in
return x! * x!
})
// Renders "100"
let template = try Template(string:"{{square(x)}}")
try template.render(["square": box, "x": Box(10)])
render
可选的 *render* 参数是一个 RenderFunction
,它在渲染 Box 时进行评估。
默认值为 nil,这使得 box 执行默认的 Mustache 渲染
{{box}}
渲染该值的内置 Swift 字符串插值,并进行 HTML 转义。{{{box}}}
渲染该值的内置 Swift 字符串插值,并且不进行 HTML 转义。{{#box}}...{{/box}}
将 box 推送到上下文堆栈的顶部,并渲染该 section 一次。有关更多信息,请查看 CoreFunctions.swift 中的 RenderFunction
类型(在 cocoadocs.org 上阅读)。
let box = MustacheBox(render: { (info: RenderingInfo) in
return Rendering("foo")
})
// Renders "foo"
let template = try Template(string:"{{.}}")
try template.render(box)
willRender
& didRender
可选的 willRender 和 didRender 参数分别是 WillRenderFunction
和 DidRenderFunction
,只要 box 位于上下文堆栈中,它们就会针对所有标签进行评估。
有关更多信息,请查看 CoreFunctions.swift 中的 WillRenderFunction
和 DidRenderFunction
类型(在 cocoadocs.org 上阅读)。
let box = MustacheBox(willRender: { (tag: Tag, box: MustacheBox) in
return "baz"
})
// Renders "baz baz"
let template = try Template(string:"{{#.}}{{foo}} {{bar}}{{/.}}")
try template.render(box)
通过混合所有这些参数,您可以精细地调整 box 的行为。
该库附带内置的 goodies,可以帮助您渲染模板:格式化值、渲染数组索引、本地化模板等。