GRMustache源碼解析
Intro
GRMustache是一個第三方開源框架古戴,用于支持XML(HTML)文件動態(tài)生成搁廓,提供模版定義。
具體支持特性:http://mustache.github.io/mustache.5.html
Demo
GRMustache模板如下:
<h1>{{header}}</h1>
{{#bug}}
{{/bug}}
{{#items}}
{{#first}}
<li><strong>{{name}}</strong></li>
{{/first}}
{{#link}}
<li><a href="{{url}}">{{name}}</a></li>
{{/link}}
{{/items}}
{{#empty}}
<p>The list is empty.</p>
{{/empty}}
對應(yīng)的數(shù)據(jù)源為JSON對象:
{
"header": "Colors",
"items": [
{"name": "red", "first": true, "url": "#Red"},
{"name": "green", "link": true, "url": "#Green"},
{"name": "blue", "link": true, "url": "#Blue"}
],
"empty": false
}
通過GRMustache生成出來的HTML為:
<h1>Colors</h1><li><strong>red</strong></li><li><a href="#Green">green</a></li><li><a href="#Blue">blue</a></li>
源碼分析
GRMustache中税朴,由模板和JSON對象到XML文檔的過程可以粗略概括如下兩個步驟:
首先從Client/Server獲取模板文件TemplateDocName.mustache生成GRMustacheTemplate實例對象耀态。
GRMustacheTemplate實例對象負責(zé)讀取JSON對象,然后渲染成XML字符串返回給Caller轧粟。
Generating Template
GRMustacheTemplate只是一個容器類策治,里面包含了一個重要的類Repository,保存在一個棧中逃延,棧頂是模板當(dāng)前環(huán)境對應(yīng)的Repository览妖。
Repository是執(zhí)行具體將抽象模板實例化的類,此外還負責(zé)加載模板揽祥。這里面封裝了TemplateAST這個類讽膏,這個類就是保存模板信息的模型類,一個TemplateAST對應(yīng)一個TemplateID拄丰,內(nèi)部可以根據(jù)ID對AST進行索引查找和緩存府树。
- (GRMustacheTemplateAST *)templateASTFromString:(NSString *)templateString contentType:(GRMustacheContentType)contentType templateID:(id)templateID error:(NSError **)error
在Repository的上述方法中進行了TemplateAST的具體生成工作:
實例化GRMustacheCompiler和GRMustacheTemplateParser。
將complier設(shè)置為parser的delegate料按。
執(zhí)行parser的parseTemplateString方法奄侠。
從compiler中獲取TemplateAST。
parseTemplateString方法
parseTemplateString方法是這個部分的關(guān)鍵執(zhí)行方法载矿,使用了一個基于狀態(tài)機的字符串模式匹配算法(垄潮?)來進行。這個算法設(shè)置了五個狀態(tài)機:
State Machine Internal State | Description |
---|---|
stateStart | 未知狀態(tài),根據(jù)下一個字符來判斷狀態(tài)去向 |
stateText | 正在遍歷純文本 |
stateTag | 正在遍歷{{tag}} |
stateUnescapedTag | 正在遍歷{{{tag}}} |
stateSetDelimitersTag | 正在遍歷{{=tag=}} |
接下來從模板文本的第一個字符開始進行字符串的遍歷弯洗,起始狀態(tài)為stateStart旅急。
如圖:
其中stateText狀態(tài)轉(zhuǎn)移到其他狀態(tài)的條件與stateStart的轉(zhuǎn)移條件一致,不再在圖中描繪牡整。重點關(guān)注紅色箭頭藐吮,當(dāng)狀態(tài)機運轉(zhuǎn)到紅色箭頭的條件發(fā)生時,說明了一個tag已經(jīng)被完全遍歷完逃贝,如:{{name}}谣辞,此時需要進行事務(wù)邏輯的處理。這個時候引入了一個新的對象叫GRMustacheToken來記錄此時的狀態(tài)沐扳,包括:range, innerRange和type等泥从。其中type是一個用于記錄當(dāng)前tag類型的enum類型變量。然后parser通過delegate的回調(diào)方法將token傳給代理(compiler)來對已標(biāo)識的字符串進行處理迫皱。
Compiler的shouldContinueAfterParsingToken方法
上一部分中的parser已經(jīng)完成了模板文本中對每一個結(jié)點(標(biāo)簽)的標(biāo)識工作——給文本和標(biāo)簽作類型判斷并且記錄位置歉闰,這些信息被保存在Token中傳入了Comipiler的shouldContinueAfterParsingToken方法中進行處理。接下來介紹一個重要的數(shù)據(jù)結(jié)構(gòu)——GRMustacheTemplateASTNode卓起。
GRMustacheTemplateASTNode是一個抽象類和敬,也就是一個協(xié)議,其子類包含了TextNode和Tag戏阅,其中Tag又包含了VariableTag和SectionTag昼弟。GRMustacheVariableTag和GRMustacheTextNode都是作為最小單位結(jié)點不可再分地存在,而GRMustacheSectionTag對應(yīng)的是{{#Tag}}這種類型的結(jié)點奕筐,里面可以嵌套其他結(jié)點和列表舱痘,如果遞歸的看待這個過程,那就可以把SectionTag里面的內(nèi)容設(shè)計為另一個TemplateAST類的一個實例(子樹)离赫。綜上所述芭逝,VariableTag和TextNode可以看為AST的葉子結(jié)點,而SectionTag則可以看為AST的一顆子樹渊胸。
在Compiler中生成的一顆完整的TemplateAST樹可以描述為下圖:
在compiler內(nèi)部執(zhí)行的是一顆樹的生成過程旬盯,內(nèi)部保存了三個棧結(jié)構(gòu):tagValueStack,openingTokenStack翎猛,ASTNodesStack胖翰。解析過程如下:
parser將帶有文本信息的token通過代理方法傳遞給compiler。
compiler做了以下事情切厘,用偽代碼描述一下:
switch token.type:
case sectionOpening:
this.currentOpeningToken = token;
tagValueStack.push(token.value);
openingTokenStack.push(token.value);
this.nodes = new Nodes();
ASTNodesStack.push(nodes);
case Text:
this.currentNodes.append(new TextNode(token));
case Variable:
this.currentNodes.append(new Variable(token));
case Closing:
TemplateAST tree = TemplateAST(this.currentNodes);
this.tagValueStack.pop();
this.openingTokenStack.pop();
this.ASTNodesStack.pop();
this.currentxxx = xxxxStack.top(); //取棧頂元素
this.currentNodes.append(new ASTNodes(tree));
最后compiler的除了ASTNodesStack(因為這個棧在初始化的時候就被先壓入一個元素)之外的兩個棧的元素將全部被彈出萨咳,然后使用最后nodeStack中的元素生成一棵樹作為解析結(jié)果返回,整個過程完成疫稿。
Binding Template With Object
GRMustache通過實現(xiàn)模版和數(shù)據(jù)的綁定來實現(xiàn)XML文件的生成培他。調(diào)用者通過調(diào)用GRMustacheTemplate的:
- (NSString *)renderObject:(id)object error:(NSError **)error
方法來實現(xiàn)這個過程鹃两,返回的NSString就是最后生成的布局文件字符串。這個方法中調(diào)用了:
- (NSString *)renderContentWithContext:(GRMustacheContext *)context HTMLSafe:(BOOL *)HTMLSafe error:(NSError **)error
來執(zhí)行下一步的操作靶壮。在這個方法中根據(jù)當(dāng)前templateRepository初始化一個GRMustacheRenderingEngine怔毛,這個實例來負責(zé)具體的事務(wù)執(zhí)行,類似上文中的parser和compiler的職責(zé)腾降。在RenderingEngine中,首先作了以下幾件事情(粗略):
申請了一個能夠容納1024個Unicode字符的結(jié)構(gòu)體作為buffer存儲解析結(jié)果碎绎。
判斷當(dāng)前入?yún)⒌膖emplateAST的contentType是否和engine的contentType一致螃壤,若不一致則根據(jù)入?yún)⒌腸ontentType初始化一個新的renderingEngine來處理解析事務(wù);如果一致則進入3筋帖。
遍歷當(dāng)前templateAST的子結(jié)點數(shù)組奸晴,并對每一個子結(jié)點執(zhí)行以下操作:
處理partialNode
對每個結(jié)點執(zhí)行GRMustacheTemplateASTNode協(xié)議中的acceptTemplateASTVisitor方法
acceptTemplateASTVistior調(diào)用visitor也就是engine的visit方法解析對應(yīng)結(jié)點的數(shù)據(jù)
首先看Section和Variable對相關(guān)代理方法的實現(xiàn):
對于Section/Variable Tag而言,最后這個協(xié)議方法會回到visitor日麸,也就是RenderingEngine的下面方法中執(zhí)行具體的解析過程:
- (BOOL)visitTag:(GRMustacheTag *)tag expression:(GRMustacheExpression *)expression escapesHTML:(BOOL)escapesHTML error:(NSError **)error
其中GRMustacheExpression是GRMustacheTag內(nèi)部的一個屬性寄啼,里面保存了Token。接著看源碼:
// Render value
//value 就是傳入的需要綁定的數(shù)據(jù) 通常是一個json序列化出來的NSDictionary的實例
id<GRMustacheRendering> renderingObject = [GRMustacheRendering renderingObjectForObject:value];
NSString *rendering = nil;
NSError *renderingError = nil; // Default nil, so that we can help lazy coders who return nil as a valid rendering.
BOOL HTMLSafe = NO; // Default NO, so that we assume unsafe rendering from lazy coders who do not explicitly set it.
switch (tag.type) {
// 通過type 走不同邏輯
case GRMustacheTagTypeVariable:
rendering = [renderingObject renderForMustacheTag:tag context:context HTMLSafe:&HTMLSafe error:&renderingError];
break;
case GRMustacheTagTypeSection: {
// section 先判斷 對應(yīng)的value是否有值
BOOL boolValue = [renderingObject mustacheBoolValue];
if (!tag.isInverted != !boolValue) {
// important calling
rendering = [renderingObject renderForMustacheTag:tag context:context HTMLSafe:&HTMLSafe error:&renderingError];
} else {
rendering = @"";
}
} break;
}
renderingObject對應(yīng)著入?yún)⒌腛bject代箭,在Objective-C環(huán)境下墩划,通常會是以下幾種類型:NSString,NSObject嗡综,NSNumber乙帮。renderingObject調(diào)用了協(xié)議規(guī)定的成員方法renderForMustacheTag:,并且把tag當(dāng)前對應(yīng)的tag和context傳入极景。在[GRMustacheRendering initialize]方法中使用runtime給以上的類型都綁定上這個協(xié)議方法察净。
renderForMustacheTag:
接下來已NSObject和NSString兩個常見的類型為例來看下在runtime綁定的render方法中的實現(xiàn):
- NSObject:
static NSString *GRMustacheRenderWithIterationSupportNSObject(NSObject *self, SEL _cmd, GRMustacheTag *tag, BOOL enumerationItem, GRMustacheContext *context, BOOL *HTMLSafe, NSError **error)
static NSString *GRMustacheRenderWithIterationSupportNSObject(NSObject *self, SEL _cmd, GRMustacheTag *tag, BOOL enumerationItem, GRMustacheContext *context, BOOL *HTMLSafe, NSError **error)
{
switch (tag.type) {
case GRMustacheTagTypeVariable:
// {{ object }}
if (HTMLSafe != NULL) {
*HTMLSafe = NO;
}
return [self description];
case GRMustacheTagTypeSection:
// {{# object }}...{{/}}
// {{^ object }}...{{/}}
context = [context newContextByAddingObject:self];
NSString *rendering = [tag renderContentWithContext:context HTMLSafe:HTMLSafe error:error];
[context release];
return rendering;
}
}
- NSString:
static NSString *GRMustacheRenderWithIterationSupportNSString(NSString *self, SEL _cmd, GRMustacheTag *tag, BOOL enumerationItem, GRMustacheContext *context, BOOL *HTMLSafe, NSError **error)
{
switch (tag.type) {
case GRMustacheTagTypeVariable:
// {{ string }}
if (HTMLSafe != NULL) {
*HTMLSafe = NO;
}
return self;
case GRMustacheTagTypeSection:
if (tag.isInverted) {
// {{^ number }}...{{/}}
return [tag renderContentWithContext:context HTMLSafe:HTMLSafe error:error];
} else {
// {{# string }}...{{/}}
context = [context newContextByAddingObject:self];
NSString *rendering = [tag renderContentWithContext:context HTMLSafe:HTMLSafe error:error];
[context release];
return rendering;
}
}
}
在Rendering方法中根據(jù)tag.type來作出不同的邏輯判斷:如果是Variable類型,也就是已經(jīng)是葉子結(jié)點了盼樟,直接返回自身的描述氢卡,如{{name}} 對應(yīng)的object為 {"name" : "tom"} 渲染結(jié)果直接為 tom到對應(yīng)的標(biāo)簽位;如果是一個子樹的的父節(jié)點晨缴,也就是Section類型译秦,調(diào)用GRMustacheTag的renderContentWithContext方法返回渲染的結(jié)果,方法的實現(xiàn)如下:
- (NSString *)renderContentWithContext:(GRMustacheContext *)context HTMLSafe:(BOOL *)HTMLSafe error:(NSError **)error
{
if (HTMLSafe) {
*HTMLSafe = (_contentType == GRMustacheContentTypeHTML);
}
return @"";
}
可以看到最后直接返回空字符串喜庞,這樣符合預(yù)期:當(dāng)section字段的值非空的時候诀浪,section字段的標(biāo)簽位置:{{#name}}不需要做渲染。如Demo中的items標(biāo)簽是不需要渲染的延都。
- 對于textNode的解析更為直接雷猪,textNode的acceptTemplateASTVisitor方法會直接調(diào)用engine的以下成員方法:
- (BOOL)visitTextNode:(GRMustacheTextNode *)textNode error:(NSError **)error
{
GRMustacheBufferAppendString(&_buffer, textNode.text);
return YES;
}
直接Append textNode.text的內(nèi)容在渲染結(jié)果字符串后面。于是整個將模版和數(shù)據(jù)綁定的流程就打通了晰房,可以總結(jié)為以下流程圖:
簡書圖片掛掉了求摇。射沟。傳不上去。