GRMustache.swift
Swift 的 Mustache 模板
最新发布:2016年10月23日 • 版本 2.0.0 • 变更日志
要求:iOS 8.0+ / OSX 10.9+ / tvOS 9.0+ • Xcode 8+ • Swift 3
在 Twitter 上关注 @groue 以获取发布公告和使用技巧。
特性
GRMustache扩展了真正的Mustache语言,内置了一些好用的功能和扩展插件,使得在需要的时候,您可以使用它来避免Mustache的严格简洁性。
- 支持完整的Mustache语法
- 过滤器,例如:
{{ uppercase(name) }}
- 模板继承,例如在hogan.js、mustache.java和mustache.php中。
- 内置的好用的功能
- GRMustache.swift不依赖于Objective-C运行时。它允许您用临时值或现有的模型填充模板,而无需强迫您将Swift代码重构为Objective-C对象。
使用方法
库围绕以下两个主要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
CocoaPods是Xcode项目的依赖管理器。
要在Podfile中使用GRMustache.swift,请指定
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
pod 'GRMustache.swift'
Carthage
Carthage是另一个用于Xcode项目的依赖管理器。
要使用GRMustache.swift与Carthage一起使用,请指定Cartfile中的
github "groue/GRMustache.swift"
Swift Package Manager
Swift Package Manager是管理Swift代码分发的一个开源工具。
要在 Swift 包管理器中使用 GRMustache.swift,请将 https://github.com/groue/GRMustache.swift 添加到你的包依赖列表中。
import PackageDescription
let package = Package(
name: "MyPackage",
targets: [],
dependencies: [
.Package(url: "https://github.com/groue/GRMustache.swift", majorVersion: 2, minor: 0),
]
)
查看 groue/GRMustacheSPM 以获取使用 GRMustache.swift 的示例 Swift 包。
手动
-
下载 GRMustache.swift 的副本。
-
检出最新的 GRMustache.swift 版本
cd [GRMustache.swift directory] git checkout 2.0.0
-
将
Mustache.xcodeproj
项目嵌入到自己的项目中。 -
在你的应用程序目标的 构建阶段 选项卡的 目标依赖 部分添加
MustacheOSX
、MustacheiOS
或MustacheWatchOS
目标。 -
将来自目标平台的
Mustache.framework
添加到目标 一般 选项卡的 嵌入的二进制文件 部分。
查看 MustacheDemoiOS,了解此类集成的示例。
文档
要修改库,打开 Xcode/Mustache.xcworkspace
工作空间:它包含文件列表顶部的 Mustache-enabled 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标签参考
Mustache基于标签:{{name}}
、{{#registered}}...{{/registered}}
、{{>include}}
等。
每个标签都执行其自己的小任务。
- 变量标签
{{name}}
渲染值。 - 分区标签
{{#items}}...{{/items}}
执行条件判断、循环和对象作用域。 - 倒置分区标签
{{^items}}...{{/items}}
是常规分区标签的姐妹,在另一个标签不渲染时渲染。 - 部分标签
{{>partial}}
允许您在另一个模板中包含一个模板。 - 部分覆盖标签
{{
提供模板继承。 - 设置定界符标签
{{=<% %>=}}
允许您更改标签定界符。 - 注释标签 允许您注释:
{{!Wow. Such comment. }}
- Pragma标签 触发特定的实现功能。
变量标签
一个变量标签 {{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
关联的值触发的。
假值
如果值是假值,则不会渲染该部分。假值包括:
- 缺失值
- 假的布尔值
- 零数值
- 空字符串
- 空集合
- NSNull
例如:
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
其他值
如果值不是假值,也不是集合,则该部分只渲染一次,内部标签可以直接访问值的键。
模板
{{# 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([链接](http://twitter.github.com/hogan.js/))、[mustache.java](https://github.com/spullara/mustache.java)、[mustache.php](https://github.com/bobthecow/mustache.php) 一样的模板继承。
一个局部覆盖标签 {{< layout }}...{{/ layout }}
在渲染的模板中包含另一个模板,就像一个普通的部分标签 {{> partial}}
。
不过,这次,包含的模板可以包含块,并且渲染的模板可以覆盖它们。块看起来像部分,但使用美元符号:{{$ 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 标签通常由 "mustache" 《`{{`》和大括号《`}}` 包围。一个 Set Delimiters Tag 可以改变这个规则,直接在模板中。
Default tags: {{ name }}
{{=<% %>=}}
ERB-styled tags: <% name %>
<%={{ }}=%>
Default tags again: {{ name }}
也有设置这些定界符的API。检查 Configuration.tagDelimiterPair
在 Configuration.swift(《[链接](http://cocoadocs.org/docsets/GRMustache.swift/2.0.0/Structs/Configuration.html)》)。
评论标签
{{! Comment tags }}
实际上完全不会被渲染。
预处理指令标签
多个 Mustache 的实现使用 预处理指令标签。它们以一个百分号 %
开头,不会被渲染,而是触发表达式特定的功能。
GRMustache.swift 解释了两个设置模板内容类型的预处理指令标签
{{% 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
协议的值,例如、 Int
、NSObject
及其子类(参见< -
数组、集合和字典(Swift数组、集合、字典和Foundation集合)。这不包括其他集合,如Swift范围。
-
小玩意儿,如Foundation格式化器。
标准 Swift 类型参考
GRMustache.swift 支持以下标准 Swift 类型
Bool
{{bool}}
渲染 "0" 或 "1"。{{#bool}}...{{/bool}}
仅当 bool 为真时渲染。{{^bool}}...{{/bool}}
仅当 bool 为假时渲染。
数值类型
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}}
渲染 string,HTML 转义。{{{string}}}
渲染 string,不进行 HTML 转义。{{#string}}...{{/string}}
仅当 string 不为空时渲染。{{^string}}...{{/string}}
仅当 string 为空时渲染。
公开的键
string.length
:字符串的长度。
集合
{{集合}}
渲染集合元素的渲染拼接。{{#集合}}...{{/集合}}
根据集合中元素的个数渲染,并将它们推送到 上下文堆栈。{{^集合}}...{{/集合}}
只有在集合为空时才渲染。
公开的键
set.first
:第一个元素。set.count
:集合中的元素数量。
数组
{{数组}}
渲染数组元素的渲染拼接。{{#数组}}...{{/数组}}
根据数组中元素的个数渲染,并将它们推送到 上下文堆栈。{{^数组}}...{{/数组}}
只有在数组为空时才渲染。
公开的键
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])
字典
{{字典}}
渲染字典的标准 Swift 字符串插值(不太有用)。{{#字典}}...{{/字典}}
一次渲染,并将字典推送到 上下文堆栈。{{^字典}}...{{/字典}}
不渲染。
为了遍历字典中的键值对,请使用标准库中的 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 的渲染取决于实际类
-
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 子类
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 枚举、结构体和类,无论是否有 @objc
或 dynamic
修饰符。但是,Swift 值仍可以借助一些帮助提供给模板。
// Define a pure Swift object:
struct Person {
let name: String
}
为了让 Mustache 模板从人员中提取 name
键以渲染 {{ name }}
标签,我们需要显式帮助 Mustache 引擎,通过实现 MustacheBoxable
协议。
extension Person : MustacheBoxable {
// Feed templates with a dictionary:
var mustacheBox: MustacheBox {
return Box(["name": self.name])
}
}
您的 mustacheBox
实现通常会调用一个常规的(如 String
或 Int
)、采用 MustacheBoxable
协议的值(如数组、集合或字典)上的 Box
函数。
现在我们可以渲染人员、人员数组、人员字典等。
// 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)
将字典装箱是构建箱子的简单方法。但是,有各种类型的箱子:检查本说明书中的其余部分。
显式表达式
mustache显式表达式函数允许您进行自定义渲染。显式表达式有两种类型:处理节标记的显式表达式和渲染变量标记的显式表达式。
// `{{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)
显式表达式是自定义渲染函数的特殊情况。原始的RenderFunction
类型在您需要执行自定义渲染时提供了额外的灵活性。请参阅CoreFunctions.swift (在cocoadocs.org上阅读)。
☝️ 注意:Mustache显式表达式与动态部分略有重叠。显式表达式是Mustache规范所要求的。动态部分更有效,因为它们可以避免对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一样。检查CoreFunctions.swift中的RenderFunction
类型,了解更多有关RenderingInfo
和Rendering
类型的信息(在cocoadocs.org上阅读)。
高级过滤器
上述所有过滤器都是FilterFunction
的特例。"值过滤器"、"预渲染过滤器"和"自定义渲染过滤器"都是常见的用例,它们提供了特定的API。
然而,该库提供了一些内置的过滤器,它们并不完全适合上述任何类别。查看它们的文档。由于它们都是使用公共GRMustache.swift API编写的,因此也可以查看它们的源代码以获取灵感。有关FilterFunction
的详细信息,请参阅CoreFunctions.swift(在cocoadocs.org上阅读)。
高级盒子
为模板提供值的对象能够执行许多不同的行为。让我们回顾一些行为
-
布尔值可以触发或阻止段落的渲染
{{# isVerified }}VERIFIED{{/ isVerified }} {{^ isVerified }}NOT VERIFIED{{/ isVerified }}
-
数组多次渲染段落,并公开
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
类型实现的。每当一个值、数组、过滤器等向模板馈送数据时,它会被转换成一个与渲染引擎交互的盒子。
让我们详细描述一下 {{ F(A) }}
标签的渲染过程,并对可用的自定义功能进行说明
-
将评估表达式
A
和F
,渲染引擎将在 上下文栈 中查找返回非空盒子作为“A”和“F”键的盒子。键提取服务由可定制的KeyedSubscriptFunction
提供。这就是 NSObject 暴露其属性,以及 Dictionary 的键的方法。
-
将使用 A 盒子作为参数来评估 F 盒子的可定制
FilterFunction
。结果盒子可能取决于 A 盒子的可定制值,但 A 盒子的所有其他方面都可能参与其中。这就是为什么有各种类型的 过滤器。
-
然后,渲染引擎将在上下文栈中查找所有具有定制
WillRenderFunction
的盒子。例如,这种方式可以使一个封装的 DateFormatter 在一个区域格式化所有日期:其
WillRenderFunction
将日期格式化为字符串。 -
生成的盒子准备好渲染。对于常规和反转的区域标签,渲染引擎查询盒子的可定制布尔值,以确保
{{# F(A) }}...{{/}}
和{{^ F(A) }}...{{/}}
不能同时渲染。显然,布尔类型有一个布尔值,但字符串也有,因此空字符串被认为是 falsey。
-
最终生成的盒子将被渲染:它的可定制
RenderFunction
被执行。它的Rendering
结果会根据其内容类型进行 HTML 转义,并追加到最后模板渲染中。 -
最后,渲染引擎会在上下文栈中查找所有具有定制
DidRenderFunction
的盒子。
所有这些可定制的属性都在低层的 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 参数给出封装的值。当盒子被渲染时使用此值(除非您提供自定义的 RenderFunction)。它也由 MustacheBox 的
value
属性返回。let aBox = MustacheBox(value: 1) // Renders "1" let template = try Template(string: "{{a}}") try template.render(["a": aBox])
-
boolValue
可选的 boolValue 参数指示盒子是否应该触发或阻止渲染常规的
{{#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 引擎从盒子中提取键。例如,{{a}}
标签会使用"a"
作为参数调用子脚本函数,并渲染返回的盒子。默认值是 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 引擎评估涉及盒子的过滤表达式。默认值是 nil,表示该盒子不能用作过滤器。请在 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 参数是一个
RenderFunction
,在渲染盒子的过程中会被评估。默认值是 nil,这使得盒子执行默认的 Mustache 渲染。
{{box}}
渲染内置的 Swift 字符串插值功能,并进行了 HTML 转义。{{{box}}}
渲染内置的 Swift 字符串插值功能,未进行 HTML 转义。{{#box}}...{{/box}}
将盒子推送到上下文堆栈的顶部,并渲染该部分一次。
请在 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
,会在盒子的上下文堆栈上评估所有标签。请在 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)
通过混合所有这些参数,您可以精细调整盒子的行为。
内置功能
该库提供了内置的 功能,有助于您渲染模板:格式化值、渲染数组索引、本地化模板等。