GRMustache源碼解析

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文檔的過程可以粗略概括如下兩個步驟:

  1. 首先從Client/Server獲取模板文件TemplateDocName.mustache生成GRMustacheTemplate實例對象耀态。

  2. GRMustacheTemplate實例對象負責(zé)讀取JSON對象,然后渲染成XML字符串返回給Caller轧粟。

Generating Template


Template.png
  1. GRMustacheTemplate只是一個容器類策治,里面包含了一個重要的類Repository,保存在一個棧中逃延,棧頂是模板當(dāng)前環(huán)境對應(yīng)的Repository览妖。

  2. 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的具體生成工作:

  1. 實例化GRMustacheCompiler和GRMustacheTemplateParser。

  2. 將complier設(shè)置為parser的delegate料按。

  3. 執(zhí)行parser的parseTemplateString方法奄侠。

  4. 從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旅急。

如圖:


state machine.png

其中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卓起。

ASTNodes.png

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樹可以描述為下圖:

完整AST樹.png

在compiler內(nèi)部執(zhí)行的是一顆樹的生成過程旬盯,內(nèi)部保存了三個棧結(jié)構(gòu):tagValueStack,openingTokenStack翎猛,ASTNodesStack胖翰。解析過程如下:

  1. parser將帶有文本信息的token通過代理方法傳遞給compiler。

  2. 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中,首先作了以下幾件事情(粗略):

  1. 申請了一個能夠容納1024個Unicode字符的結(jié)構(gòu)體作為buffer存儲解析結(jié)果碎绎。

  2. 判斷當(dāng)前入?yún)⒌膖emplateAST的contentType是否和engine的contentType一致螃壤,若不一致則根據(jù)入?yún)⒌腸ontentType初始化一個新的renderingEngine來處理解析事務(wù);如果一致則進入3筋帖。

  3. 遍歷當(dāng)前templateAST的子結(jié)點數(shù)組奸晴,并對每一個子結(jié)點執(zhí)行以下操作:

  4. 處理partialNode

  5. 對每個結(jié)點執(zhí)行GRMustacheTemplateASTNode協(xié)議中的acceptTemplateASTVisitor方法

  6. 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é)為以下流程圖:
簡書圖片掛掉了求摇。射沟。傳不上去。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末与境,一起剝皮案震驚了整個濱河市验夯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摔刁,老刑警劉巖挥转,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異共屈,居然都是意外死亡绑谣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門拗引,熙熙樓的掌柜王于貴愁眉苦臉地迎上來借宵,“玉大人,你說我怎么就攤上這事矾削∪烂担” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵哼凯,是天一觀的道長欲间。 經(jīng)常有香客問我,道長挡逼,這世上最難降的妖魔是什么括改? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮家坎,結(jié)果婚禮上嘱能,老公的妹妹穿的比我還像新娘。我一直安慰自己虱疏,他們只是感情好惹骂,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著做瞪,像睡著了一般对粪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上装蓬,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天著拭,我揣著相機與錄音,去河邊找鬼牍帚。 笑死儡遮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的暗赶。 我是一名探鬼主播鄙币,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼肃叶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了十嘿?” 一聲冷哼從身側(cè)響起因惭,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绩衷,沒想到半個月后蹦魔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡唇聘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年版姑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迟郎。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖聪蘸,靈堂內(nèi)的尸體忽然破棺而出宪肖,到底是詐尸還是另有隱情,我是刑警寧澤健爬,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布控乾,位于F島的核電站,受9級特大地震影響娜遵,放射性物質(zhì)發(fā)生泄漏蜕衡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一设拟、第九天 我趴在偏房一處隱蔽的房頂上張望慨仿。 院中可真熱鬧,春花似錦纳胧、人聲如沸镰吆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽万皿。三九已至,卻和暖如春核行,著一層夾襖步出監(jiān)牢的瞬間牢硅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工芝雪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留减余,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓绵脯,卻偏偏與公主長得像佳励,于是被迫代替她去往敵國和親休里。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 一赃承、iOS 4種JSON數(shù)據(jù)解析方法詳解 2013年09月10日? 綜合? 共 9124字? 字號小中大?評論關(guān)閉...
    lilinjianshu閱讀 2,043評論 0 2
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法妙黍,類相關(guān)的語法,內(nèi)部類的語法瞧剖,繼承相關(guān)的語法拭嫁,異常的語法,線程的語...
    子非魚_t_閱讀 31,597評論 18 399
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹 對...
    cosWriter閱讀 11,090評論 1 32
  • 經(jīng)過大約一周時間“分身術(shù)訓(xùn)練營”的學(xué)習(xí)加實踐,分享一下我的收獲和喜悅捉撮。 我認為的“積極主動” 自09年畢業(yè)怕品,轉(zhuǎn)眼已...
    易仁勇哥閱讀 678評論 3 5
  • 現(xiàn)在做的事情就從投資方面談?wù)劊@一年是我學(xué)習(xí)投資的開始巾遭,稱的上元年肉康,買黃金買基金買p2p理財產(chǎn)品買票據(jù),自己也不敢...
    Thehrdertheluck閱讀 357評論 0 0