初见

GRMustache源码解析

2018-08-06  本文已影响0人  SHY圆圆圈圈圆圆

GRMustache源码解析

Intro

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

具体支持特性: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}}

对应的数据源为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实例对象负责读取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<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 先判断 对应的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方法中的实现:

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;
    }
}

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标签是不需要渲染的。


- (BOOL)visitTextNode:(GRMustacheTextNode *)textNode error:(NSError **)error
{
    GRMustacheBufferAppendString(&_buffer, textNode.text);
    return YES;
}

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

上一篇 下一篇

猜你喜欢

热点阅读