Groovy模板引擎下(The MarkupTemplateEngine引擎細節介紹)
模板格式
1.1基礎:
模板包含Groovy程式碼,下面詳細具體的解析一下第一個樣例:
xmlDeclaration() (1)
cars { (2)
cars.each { (3)
car(make: it.make, model: it.model) (4)
} ( 5)
}
1 渲染XML的宣告字串
2 開啟一個cars標籤
3 cars是模板資料模型的一個變數包含了所有的car例項
4 遍歷每一項,從car例項建立car標籤
5 關閉上述的cars標籤
模板可以使用通常的Groovy程式碼,能在list(來自模板資料模型)上使用each為每個car例項生成一個car標籤。
相似的情景下,渲染HTML程式碼非常簡單:
yieldUnescaped '<!DOCTYPE html>' (1)
html(lang:'en') { ( 2)
head { (3)
meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"') (4)
title('My page') (5)
} ( 6)
body { (7)
p('This is an example of HTML contents') (8)
} (9)
}
1 渲染HTML檔案型別的標籤
2 開啟一個帶有屬性的html標籤
3 開啟一個head標籤
4 渲染帶有http-equiv屬性的 meta標籤
5 渲染title標籤
6 關閉head 標籤
7 開啟head標籤
8 渲染P標籤
9 關閉body標籤
10 關閉html標籤
輸出結果如下:
<!DOCTYPE html><html lang='en'><head><meta http-equiv='"Content-Type" content="text/html; charset=utf-8"'/><title>My page</title></head><body><p>This is an example of HTML contents</p></body></html>
通過一些配置,能美化輸出,增加一些換行以及縮排。
1.2支援的方法
上述示例,文件型別通過yieldUnescaped方法渲染。同時我們也見到過xmlDeclaration方法。模板引擎支援多種方法幫助渲染內容。
方法名 | 描述 | 樣例 |
yield | 渲染內容,但是渲染前會轉義 | 模板:yield ‘Some text with <angle brackets>’輸出:Some text with <angle brackets> |
yieldUnescaped | 渲染原始內容,渲染後不進行轉義 | 模板:yieldUnescaped ‘Some text with <angle brackets>’輸出:Some text with <angle brackets> |
xmlDeclaration | 渲染XML宣告字串。如果在配置檔案中編碼進行了宣告,則會寫入XML宣告 | 模板:xmlDeclaration()輸出:<?xml version=’1.0′?>如果TemplateConfiguration#getDeclarationEncoding非空輸出:<?xml version=’1.0′ encoding=’UTF-8′?> |
comment | 渲染原始內容到XML的註釋中 | 模板:comment ‘This is <a href=’http://docs.groovy-lang.org/latest/html/documentation/foo.html’>commented out</a>’輸出:<!–This is <a href=’http://docs.groovy-lang.org/latest/html/documentation/foo.html’>commented out</a>–> |
newLine | 渲染新行同時參考:TemplateConfiguration#setAutoNewLine和TemplateConfiguration#setNewLineString. | 模板:p(‘text’)newLine()p(‘text on new line’)輸出:<p>text</p><p>text on new line</p> |
pi | 渲染XML處理宣告 | 模板:pi(“xml-stylesheet”:[href:”mystyle.css”, type:”text/css”])輸出:<?xml-stylesheet href=’mystyle.css’ type=’text/css’?> |
tryEscape | 如果是物件字串,從實體物件渲染轉義的字串,否則返回實體物件本身 | 模板:yieldUnescaped tryEscape(‘Some text with <angle brackets>’)輸出:Some text with <angle brackets> |
1.3 包含
MarkupTemplateEngine模板支援從其他檔案引入內容,引入的型別可以是:
- 另一個模板
- 原始內容
- 轉義的內容
引入另外的模板如下:
include template: 'other_template.tpl'
引入原始內容檔案(無需轉義)如下:
include unescaped: 'raw.txt'
最終,引入需要渲染前轉義的txt檔案如下:
include escaped: 'to_be_escaped.txt'
選擇性的,可以使用如下方法代替:
includeGroovy(<name>) 引入其他模板
includeEscaped(<name>) 引入其他需要轉義的檔案
includeUnescaped(<name>) 引入其他不需要轉義的檔案
如果引入的檔名是動態的(儲存在變數中)則使用上述的方法替代 include xxx:語法是非常有用的。引入的檔案能在classpath上找到。這是MarkupTemplateEngine接受ClassLoader 作為構造器引數的原因(其他的原因:在模板中引入涉及到其他類的程式碼)
如果你不想設定模板到classpath上,MarkupTemplateEngine可以接受一個方便的構造器方便你定義模板所在的檔案目錄。
1.4 片段
片段是內嵌的模板,他們在單一的模板中內提高複用性。一個碎片包含:字串,內嵌模板和模型 用於渲染模板。參考下面的模板:
ul { pages.each { fragment "li(line)", line:it } }
fragment元素建立內嵌模板,根據指定的模型渲染模板。這裡,我們有li(line)片段, line繫結到it, it對應於pages的遍歷,我們將會為每個page生成單個的li元素.
<ul><li>Page 1</li><li>Page 2</li></ul>
片段對於模板元素的分解非常有意思,對於模板來說,他們需要付出片段編譯的程式碼,並且不能外部化。
1.5 佈局
佈局涉及到其他的模板。通常用於組合模板並且共享通用的結構。如果你的HTML頁面有通用的佈局,並且只想替換body,那麼佈局將是非常有意思的,將簡化操作。首先,需要建立一個佈局模板:
layout-main.tpl
html { head { title(title) (1) } body { bodyContents() (2) } }
1. title變數是佈局變數
2. 呼叫bodyContents渲染body
然後需要做的就是在模板中引入佈局:
layout 'layout-main.tpl', (1) title: 'Layout example', (2) bodyContents: contents { p('This is the body') } (3)
1.使用 main-layout.tpl佈局檔案 2.設定 title變數 3.設定 bodyContents 感謝佈局檔案中對bodyContents()的呼叫,bodyContents將會被渲染到佈局中。結果,模板會被渲染成如下:
Layout example
This is the body
對於contents方法的呼叫通知模板引擎這塊程式碼對於模板是特殊的宣告而不是用於直接渲染的幫助方法。如果沒有在宣告前新增contents方法,那麼內容將直接被渲染,但是你會看到一個隨機生成的字串而不是程式碼塊對應的結果值。
佈局是一個有力的方式共享橫跨多個模板的通用內容。不需要重寫以及引入任何東西。
佈局所用的資料模型和頁面所用的資料模型是獨立的。然後繼承頁面的資料模型也是可行的,設想一下定義的模型如下:
model = new HashMap<String,Object>(); model.put('title','Title from main model');
模板如下:
layout 'layout-main.tpl', true, (1) bodyContents: contents { p('This is the body') }
1. 注意設定成ture開啟模板繼承。
因此,傳遞title的值到先前的例子就變的不是那麼必要。渲染的結果如下:
<html><head><title>Title from main model</title></head><body><p>This is the body</p></body></html>
但是重寫來自父模型的值也是可能的:
layout 'layout-main.tpl', true, (1) title: 'Overriden title', (2) bodyContents: contents { p('This is the body') }
1 true表示從父模型繼承
2 但是title被重寫
輸出的結果如下:
<html><head><title>Overriden title</title></head><body><p>This is the body</p></body></html>
2 渲染內容
2.1建立模板引擎
服務端渲染模板引擎需要groovy.text.markup.MarkupTemplateEngine和groovy.text.markup.TemplateConfiguration的例項
TemplateConfiguration config = new TemplateConfiguration(); (1) MarkupTemplateEngine engine = new MarkupTemplateEngine(config); (2) Template template = engine.createTemplate("p('test template')"); (3) Map<String, Object> model = new HashMap<>(); (4) Writable output = template.make(model); (5) output.writeTo(writer); (6)
1 建立模板配置
2 基於模板配置建立模板引擎
3 基於字串建立模板
4 建立用於模板的資料模型
5 繫結資料模型到模板
6 渲染輸出
下面是解析模板的一些可選擇的項:
- 基於String,使用createTemplate(String)
- 基於Reader, 使用createTemplate(Reader)
- 基於URL, 使用 createTemplate(URL)
- 基於模板名, 使用 createTemplateByPath(String)
通常被建議的最終的版本:
Template template = engine.createTemplateByPath("main.tpl"); Writable output = template.make(model); output.writeTo(writer);
2.2 配置選項
通過TemplateConfiguration的一些配置選項,可以配置引擎,改變引擎的預設行為:
選項 | 預設值 | 描述 | 樣例 |
declarationEncoding | null | 定義呼叫xmlDeclaration時預設的編碼格式,本身不影響輸出 | xmlDeclaration()輸出:
<?xml version=’1.0′?> 如果TemplateConfiguration#getDeclarationEncoding非空: 輸出: <?xml version=’1.0′ encoding=’UTF-8′?> |
expandEmptyElements | false | 如果true,輸出標籤的擴充套件格式 | 模板:p()輸出:
<p/> 如果expandEmptyElements 是true: 輸出: <p></p> |
expandEmptyElements | false | 如果true,屬性的雙引號代替單引號 | 模板:tag(attr:’value’)輸出:
<tag attr=’value’/> 如果useDoubleQuotes是true: 輸出: <tag attr=”value”/> |
newLineString | 系統預設(系統屬性line.separator) | 允許選擇新行渲染所使用的字串 | 模板p(‘foo’)newLine()
p(‘baz’) 如果newLineString=’BAR’: 輸出: <p>foo</p>BAR<p>baz</p> |
autoEscape | false | 如果true,資料模型中的變數會在渲染前自動轉義 | |
baseTemplateClass | groovy.text.markup.BaseTemplate | 設定編譯模板的父類,提供應用所需的特殊模板 |
2.3. 自動格式化
預設的,模板引擎會輸出沒有格式化的文字。參見配置選項,對輸出做必要的格式化
autoIndent
新行插入後自動縮排autoNewLine
基於原始預設的模板內容自動插入新行
通常,推薦設定autoIndent和autoNewLine為true,增加可讀性,程式碼如下:
config.setAutoNewLine(true); config.setAutoIndent(true);
使用下面的模板:
html { head { title('Title') } }
輸出結果:
<html> <head> <title>Title</title> </head> </html>
我們可以稍微的調整模板讓title和heade位於同一行
html { head { title('Title') } }
輸出如下:
<html> <head><title>Title</title> </head> </html>
新行僅僅插入在花括號標籤之後,同時插入對應於新的內嵌內容被發現的地方。這就意味著標籤中包含的標籤不會觸發新行的插入除非使用花括號。
html { head { meta(attr:'value') (1) title('Title') (2) newLine() (3) meta(attr:'value2') (4) } }
1 meta和head不在同一行,新行被出入
2 新行不會被插入,和前面的標籤處於同一深度
3 呼叫newLine強制插入新行
4 在新行被渲染
輸出結果如下:
<html> <head> <meta attr='value'/><title>Title</title> <meta attr='value2'/> </head> </html>
預設的情況下,現在使用4個空格縮排,但是可以通過設定TemplateConfiguration#autoIndentString屬性來定製化。