GRMustache.swift 4.0.1

GRMustache.swift 4.0.1

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布时间最新发布2019年6月
SPM支持SPM

Gwendal RouéGwendal RouéGwendal Rouéfumitoito 维护。



  • 作者:
  • Gwendal Roué

GRMustache.swift Swift Platforms License

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.jsmustache.javamustache.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 包。

手动

  1. 下载 GRMustache.swift 的副本。

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

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

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

  5. 将来自目标平台的 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}}等。

每个标签都执行其自己的小任务。

变量标签

一个变量标签 {{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 包含了 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>

一些需要注意的要点

  • {{$ 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.tagDelimiterPairConfiguration.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.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)。当一个标签被渲染时,它会评估其表达式,并渲染结果。

共有四种类型的表达式。

  • 点号 .,在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协议的值,例如IntNSObject及其子类(参见<

  • 数组、集合和字典(Swift数组、集合、字典和Foundation集合)。这不包括其他集合,如Swift范围。

  • 一些函数类型,例如过滤器函数lambda函数和其他涉及高级盒的函数。

  • 小玩意儿,如Foundation格式化器。

标准 Swift 类型参考

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

Bool

  • {{bool}} 渲染 "0" 或 "1"。
  • {{#bool}}...{{/bool}} 仅当 bool 为真时渲染。
  • {{^bool}}...{{/bool}} 仅当 bool 为假时渲染。

数值类型

GRMustache 支持以下 Int, UInt, Int64, UInt64, Float, DoubleCGFloat

  • {{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 BoolInt, 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 枚举、结构体和类,无论是否有 @objcdynamic 修饰符。但是,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 实现通常会调用一个常规的(如 StringInt)、采用 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过滤器,用于平方整数

// 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一样。检查CoreFunctions.swift中的RenderFunction类型,了解更多有关RenderingInfoRendering类型的信息(在cocoadocs.org上阅读)。

高级过滤器

上述所有过滤器都是FilterFunction的特例。"值过滤器"、"预渲染过滤器"和"自定义渲染过滤器"都是常见的用例,它们提供了特定的API。

然而,该库提供了一些内置的过滤器,它们并不完全适合上述任何类别。查看它们的文档。由于它们都是使用公共GRMustache.swift API编写的,因此也可以查看它们的源代码以获取灵感。有关FilterFunction的详细信息,请参阅CoreFunctions.swift(在cocoadocs.org上阅读)。

高级盒子

为模板提供值的对象能够执行许多不同的行为。让我们回顾一些行为

  • 布尔值可以触发或阻止段落的渲染

    {{# isVerified }}VERIFIED{{/ isVerified }}
    {{^ isVerified }}NOT VERIFIED{{/ isVerified }}
    
  • 数组多次渲染段落,并公开countfirstlast

    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) }} 标签的渲染过程,并对可用的自定义功能进行说明

  1. 将评估表达式 AF,渲染引擎将在 上下文栈 中查找返回非空盒子作为“A”和“F”键的盒子。键提取服务由可定制的 KeyedSubscriptFunction 提供。

    这就是 NSObject 暴露其属性,以及 Dictionary 的键的方法。

  2. 将使用 A 盒子作为参数来评估 F 盒子的可定制 FilterFunction

    结果盒子可能取决于 A 盒子的可定制值,但 A 盒子的所有其他方面都可能参与其中。这就是为什么有各种类型的 过滤器

  3. 然后,渲染引擎将在上下文栈中查找所有具有定制 WillRenderFunction 的盒子。

    例如,这种方式可以使一个封装的 DateFormatter 在一个区域格式化所有日期:其 WillRenderFunction 将日期格式化为字符串。

  4. 生成的盒子准备好渲染。对于常规和反转的区域标签,渲染引擎查询盒子的可定制布尔值,以确保 {{# F(A) }}...{{/}}{{^ F(A) }}...{{/}} 不能同时渲染。

    显然,布尔类型有一个布尔值,但字符串也有,因此空字符串被认为是 falsey

  5. 最终生成的盒子将被渲染:它的可定制 RenderFunction 被执行。它的 Rendering 结果会根据其内容类型进行 HTML 转义,并追加到最后模板渲染中。

    Lambdas 使用这样的 RenderFunction,还有 预渲染过滤器自定义渲染过滤器

  6. 最后,渲染引擎会在上下文栈中查找所有具有定制 DidRenderFunction 的盒子。

    这些由 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)

以下将逐一描述它们,尽管您可以同时提供多个

  • 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)
  • willRenderdidRender

    可选的 willRenderdidRender 参数是 WillRenderFunctionDidRenderFunction,会在盒子的上下文堆栈上评估所有标签。

    请在 CoreFunctions.swift 中检查 WillRenderFunctionDidRenderFunction 类型以获取更多信息(更多信息请参阅 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)

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

内置功能

该库提供了内置的 功能,有助于您渲染模板:格式化值、渲染数组索引、本地化模板等。