GRMustache源码解析

GRMustache源码解析

Intro

GRMustache是一个第三方开源框架,用于支持XML(HTML)文件动态生成,提供模版定义。

具体支持特性:http://mustache.github.io/mustache.5.html

Demo

GRMustache模板如下:

{{header}}

{{#bug}} {{/bug}} {{#items}} {{#first}}
  • {{name}}
  • {{/first}} {{#link}}
  • {{name}}
  • {{/link}} {{/items}} {{#empty}}

    The list is empty.

    {{/empty}}

    对应的数据源为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为:

    Colors

  • red
  • green
  • blue
  • 源码分析

    GRMustache中,由模板和JSON对象到XML文档的过程可以粗略概括如下两个步骤:

    1. 首先从Client/Server获取模板文件TemplateDocName.mustache生成GRMustacheTemplate实例对象。

    2. GRMustacheTemplate实例对象负责读取JSON对象,然后渲染成XML字符串返回给Caller。

    Generating Template


    Template.png
    1. GRMustacheTemplate只是一个容器类,里面包含了一个重要的类Repository,保存在一个栈中,栈顶是模板当前环境对应的Repository。

    2. Repository是执行具体将抽象模板实例化的类,此外还负责加载模板。这里面封装了TemplateAST这个类,这个类就是保存模板信息的模型类,一个TemplateAST对应一个TemplateID,内部可以根据ID对AST进行索引查找和缓存。


    - (GRMustacheTemplateAST *)templateASTFromString:(NSString *)templateString contentType:(GRMustacheContentType)contentType templateID:(id)templateID error:(NSError **)error

    在Repository的上述方法中进行了TemplateAST的具体生成工作:

    1. 实例化GRMustacheCompiler和GRMustacheTemplateParser。

    2. 将complier设置为parser的delegate。

    3. 执行parser的parseTemplateString方法。

    4. 从compiler中获取TemplateAST。

    parseTemplateString方法

    parseTemplateString方法是这个部分的关键执行方法,使用了一个基于状态机的字符串模式匹配算法(?)来进行。这个算法设置了五个状态机:

    State Machine Internal State Description
    stateStart 未知状态,根据下一个字符来判断状态去向
    stateText 正在遍历纯文本
    stateTag 正在遍历{{tag}}
    stateUnescapedTag 正在遍历{{{tag}}}
    stateSetDelimitersTag 正在遍历{{=tag=}}

    接下来从模板文本的第一个字符开始进行字符串的遍历,起始状态为stateStart。

    如图:


    state machine.png

    其中stateText状态转移到其他状态的条件与stateStart的转移条件一致,不再在图中描绘。重点关注红色箭头,当状态机运转到红色箭头的条件发生时,说明了一个tag已经被完全遍历完,如:{{name}},此时需要进行事务逻辑的处理。这个时候引入了一个新的对象叫GRMustacheToken来记录此时的状态,包括:range, innerRange和type等。其中type是一个用于记录当前tag类型的enum类型变量。然后parser通过delegate的回调方法将token传给代理(compiler)来对已标识的字符串进行处理。

    Compiler的shouldContinueAfterParsingToken方法

    上一部分中的parser已经完成了模板文本中对每一个结点(标签)的标识工作——给文本和标签作类型判断并且记录位置,这些信息被保存在Token中传入了Comipiler的shouldContinueAfterParsingToken方法中进行处理。接下来介绍一个重要的数据结构——GRMustacheTemplateASTNode。

    ASTNodes.png

    GRMustacheTemplateASTNode是一个抽象类,也就是一个协议,其子类包含了TextNode和Tag,其中Tag又包含了VariableTag和SectionTag。GRMustacheVariableTag和GRMustacheTextNode都是作为最小单位结点不可再分地存在,而GRMustacheSectionTag对应的是{{#Tag}}这种类型的结点,里面可以嵌套其他结点和列表,如果递归的看待这个过程,那就可以把SectionTag里面的内容设计为另一个TemplateAST类的一个实例(子树)。综上所述,VariableTag和TextNode可以看为AST的叶子结点,而SectionTag则可以看为AST的一颗子树。

    在Compiler中生成的一颗完整的TemplateAST树可以描述为下图:

    完整AST树.png

    在compiler内部执行的是一颗树的生成过程,内部保存了三个栈结构: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中的元素生成一棵树作为解析结果返回,整个过程完成。


    Binding Template With Object

    GRMustache通过实现模版和数据的绑定来实现XML文件的生成。调用者通过调用GRMustacheTemplate的:

    - (NSString *)renderObject:(id)object error:(NSError **)error

    方法来实现这个过程,返回的NSString就是最后生成的布局文件字符串。这个方法中调用了:

    - (NSString *)renderContentWithContext:(GRMustacheContext *)context HTMLSafe:(BOOL *)HTMLSafe error:(NSError **)error

    来执行下一步的操作。在这个方法中根据当前templateRepository初始化一个GRMustacheRenderingEngine,这个实例来负责具体的事务执行,类似上文中的parser和compiler的职责。在RenderingEngine中,首先作了以下几件事情(粗略):

    1. 申请了一个能够容纳1024个Unicode字符的结构体作为buffer存储解析结果。

    2. 判断当前入参的templateAST的contentType是否和engine的contentType一致,若不一致则根据入参的contentType初始化一个新的renderingEngine来处理解析事务;如果一致则进入3。

    3. 遍历当前templateAST的子结点数组,并对每一个子结点执行以下操作:

    4. 处理partialNode

    5. 对每个结点执行GRMustacheTemplateASTNode协议中的acceptTemplateASTVisitor方法

    6. acceptTemplateASTVistior调用visitor也就是engine的visit方法解析对应结点的数据


    首先看Section和Variable对相关代理方法的实现:

    对于Section/Variable Tag而言,最后这个协议方法会回到visitor,也就是RenderingEngine的下面方法中执行具体的解析过程:

    - (BOOL)visitTag:(GRMustacheTag *)tag expression:(GRMustacheExpression *)expression escapesHTML:(BOOL)escapesHTML error:(NSError **)error

    其中GRMustacheExpression是GRMustacheTag内部的一个属性,里面保存了Token。接着看源码:

        // Render value
                //value 就是传入的需要绑定的数据 通常是一个json序列化出来的NSDictionary的实例
                id 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 先判断 对应的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对应着入参的Object,在Objective-C环境下,通常会是以下几种类型:NSString,NSObject,NSNumber。renderingObject调用了协议规定的成员方法renderForMustacheTag:,并且把tag当前对应的tag和context传入。在[GRMustacheRendering initialize]方法中使用runtime给以上的类型都绑定上这个协议方法。

    renderForMustacheTag:

    接下来已NSObject和NSString两个常见的类型为例来看下在runtime绑定的render方法中的实现:

    • 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方法中根据tag.type来作出不同的逻辑判断:如果是Variable类型,也就是已经是叶子结点了,直接返回自身的描述,如{{name}} 对应的object为 {"name" : "tom"} 渲染结果直接为 tom到对应的标签位;如果是一个子树的的父节点,也就是Section类型,调用GRMustacheTag的renderContentWithContext方法返回渲染的结果,方法的实现如下:

    - (NSString *)renderContentWithContext:(GRMustacheContext *)context HTMLSafe:(BOOL *)HTMLSafe error:(NSError **)error
    {
        if (HTMLSafe) {
            *HTMLSafe = (_contentType == GRMustacheContentTypeHTML);
        }
        return @"";
    }
    
    

    可以看到最后直接返回空字符串,这样符合预期:当section字段的值非空的时候,section字段的标签位置:{{#name}}不需要做渲染。如Demo中的items标签是不需要渲染的。


    • 对于textNode的解析更为直接,textNode的acceptTemplateASTVisitor方法会直接调用engine的以下成员方法:
    - (BOOL)visitTextNode:(GRMustacheTextNode *)textNode error:(NSError **)error
    {
        GRMustacheBufferAppendString(&_buffer, textNode.text);
        return YES;
    }
    
    

    直接Append textNode.text的内容在渲染结果字符串后面。于是整个将模版和数据绑定的流程就打通了,可以总结为以下流程图:
    图片挂掉了。。传不上去。

    你可能感兴趣的:(GRMustache源码解析)