zoukankan      html  css  js  c++  java
  • 系统程序员成长计划-文本处理(二)

    Thursday, June 25th, 2009 | Author: admin | » Edit «

    转载时请注明出处和作者联系方式
    文章出处:http://www.limodev.cn/blog
    作者联系方式:李先静 <xianjimli at hotmail dot com>

    Builder模式

    前面我们学习了状态机,并利用它来解析各种格式的文本数据。解析过程把线性的文本数据转换成一些基本的逻辑单元,但这通常只是任务的一部分,接下来 我们还要对这些解析出来的数据进一步处理。对于特定格式的文本数据,它的解析过程是一样的,但是对解析出来的数据的处理却是多种多样的。为了让解析过程能 被重用,就需要把数据的解析和数据的处理分开。

    现在我们回过头来看一下前面写的函数parse_token,这个函数把用分隔符分隔的文本数据,分离出一个一个的token。

    parse_token的函数原型如下:

    typedef void (*OnTokenFunc)(void* ctx, int index, const char* token);
    int parse_token(const char* text, const char* delims, OnTokenFunc on_token, void* ctx)

    parse_token负责解析数据,但它并不关心数据代表的意义及用途。对数据的进一步处理由调用者提供的回调函数来完成,函数 parse_token每解析到一个token,就调用这个回调函数。parse_token负责数据的解析,回调函数负责数据的处理,这样一来,数据的 解析和数据的处理就分开了。

    parse_token可以认为是Builder模式最朴素的应用。现在我们看看Builder 模式:

    Builder 模式的意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。“构建”其实就是前面的解析过程,而“表示”就是前面说的对数据的处理。

    对象关系:
    对象关系
    上面的parse_token与这里的Director对应。

    上面的回调函数与这里的Builder对应。

    具体的回调函数与这里的ConcreteBuilder对应。

    对数据处理的结果就是Product。

    对象协作:
    对象协作
    Client是parse_token的调用者。

    由于parse_token是按面向过程的方式设计的,所以ConcreteBuilder和Director的创建只是对应于一些初始化代码。

    调用parse_token相当于调用aDirector的Construct函数。

    调用回调函数相当于调用aConcreteBuilder的BuildPart函数。

    回调函数可能把处理结果存在它的参数ctx中,GetResult是从里面获取结果,这是可选的过程,依赖于具体回调函数所做的工作。

    parse_token的例子简单直接,对于理解Builder模式有较大的帮助,不过毕竟它是面向过程的。现在我们以前面的XML解析器为例来说 明Builder模式,虽然我们的代码是用C写的,但完全是用面向对象的思想来设计的。Builder是一个接口,我们先把它定义出来:

    struct _XmlBuilder;
    typedef struct _XmlBuilder XmlBuilder;

    typedef void (*XmlBuilderOnStartElementFunc)(XmlBuilder* thiz, const char* tag, const char** attrs);
    typedef void (*XmlBuilderOnEndElementFunc)(XmlBuilder* thiz, const char* tag);
    typedef void (*XmlBuilderOnTextFunc)(XmlBuilder* thiz, const char* text, size_t length);
    typedef void (*XmlBuilderOnCommentFunc)(XmlBuilder* thiz, const char* text, size_t length);
    typedef void (*XmlBuilderOnPiElementFunc)(XmlBuilder* thiz, const char* tag, const char** attrs);
    typedef void (*XmlBuilderOnErrorFunc)(XmlBuilder* thiz, int line, int row, const char* message);
    typedef void (*XmlBuilderDestroyFunc)(XmlBuilder* thiz);

    struct _XmlBuilder
    {
    XmlBuilderOnStartElementFunc on_start_element;
    XmlBuilderOnEndElementFunc on_end_element;
    XmlBuilderOnTextFunc on_text;
    XmlBuilderOnCommentFunc on_comment;
    XmlBuilderOnPiElementFunc on_pi_element;
    XmlBuilderOnErrorFunc on_error;
    XmlBuilderDestroyFunc destroy;

    char priv[1];
    };

    static inline void xml_builder_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)

    {

    return_if_fail(thiz != NULL && thiz->on_start_element != NULL);

    thiz->on_start_element(thiz, tag, attrs);

    return;

    }

    static inline void xml_builder_on_end_element(XmlBuilder* thiz, const char* tag)

    {

    return_if_fail(thiz != NULL && thiz->on_end_element != NULL);

    thiz->on_end_element(thiz, tag);

    return;

    }

    ...
    (其它inline函数不列在这里了)

    XmlBuilder接口要求实现下列函数:

    on_start_element:解析器解析到一个起始TAG时调用它。
    on_end_element:解析器解析到一个结束TAG时调用它。
    on_text:解析器解析到一段文本时调用它。
    on_comment:解析器解析到一个注释时调用它。
    on_pi_element:解析器解析到一个处理指令时调用它。
    on_error:解析器遇到错误时调用它。
    destroy:用销毁Builder对象。

    on_start_element和on_end_element等函数相当于Builder模式中的BuildPartX函数。

    XML解析器相当于Director,在前面我们已经写好了,不过它对解析出来的数据没有做任何处理。现在我们对它做些修改,让它调用XmlBuilder的函数。

    XML解析器对外提供下面几个函数:

    o 构造函数。

    XmlParser* xml_parser_create(void);

    o 为xmlParser设置builder对象。

    void       xml_parser_set_builder(XmlParser* thiz, XmlBuilder* builder);

    o 解析XML

    void       xml_parser_parse(XmlParser* thiz, const char* xml);

    o 析构函数

    void       xml_parser_destroy(XmlParser* thiz);

    在解析时,解析到相应的tag,就调用XmlBuilder相应的函数:

    o 解析到起始tag时调用xml_builder_on_start_element

    static void xml_parser_parse_start_tag(XmlParser* thiz)
    {
    enum _State
    {
    STAT_NAME,
    STAT_ATTR,
    STAT_END,
    }state = STAT_NAME;

    char* tag_name = NULL;
    const char* start = thiz->read_ptr - 1;

    for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
    {
    char c = *thiz->read_ptr;

    switch(state)
    {
    case STAT_NAME:
    {
    if(isspace(c) || c == '>' || c == '/')
    {
    tag_name = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
    state = (c != '>' && c != '/') ? STAT_ATTR : STAT_END;
    }
    break;
    }
    case STAT_ATTR:
    {
    xml_parser_parse_attrs(thiz, '/');
    state = STAT_END;

    break;
    }
    default:break;
    }

    if(state == STAT_END)
    {
    break;
    }
    }

    tag_name = thiz->buffer + (size_t)tag_name;
    /*解析完成,调用builder的函数xml_builder_on_start_element。*/
    xml_builder_on_start_element(thiz->builder, tag_name, (const char**)thiz->attrs);

    if(thiz->read_ptr[0] == '/')
    {
    /*如果tag以'/'结束,调用builder的函数xml_builder_on_end_element。*/
    xml_builder_on_end_element(thiz->builder, tag_name);
    }

    for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '/0'; thiz->read_ptr++);

    return;
    }

    o 解析到结束tag时调用xml_builder_on_end_element

    static void xml_parser_parse_end_tag(XmlParser* thiz)
    {
    char* tag_name = NULL;
    const char* start = thiz->read_ptr;
    for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
    {
    if(*thiz->read_ptr == '>')
    {
    tag_name = thiz->buffer + xml_parser_strdup(thiz, start, thiz->read_ptr-start);
    /*解析完成,调用builder的函数xml_builder_on_end_element。*/
    xml_builder_on_end_element(thiz->builder, tag_name);

    break;
    }
    }

    return;
    }

    o 解析到文本时调用xml_builder_on_text

    static void xml_parser_parse_text(XmlParser* thiz)
    {
    const char* start = thiz->read_ptr - 1;
    for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
    {
    char c = *thiz->read_ptr;

    if(c == '<')
    {
    if(thiz->read_ptr > start)
    {
    /*解析完成,调用builder的函数xml_builder_on_text。*/
    xml_builder_on_text(thiz->builder, start, thiz->read_ptr-start);
    }
    thiz->read_ptr--;
    return;
    }
    else if(c == '&')
    {
    xml_parser_parse_entity(thiz);
    }
    }

    return;
    }

    o 解析到注释时调用xml_builder_on_comment

    static void xml_parser_parse_comment(XmlParser* thiz)
    {
    enum _State
    {
    STAT_COMMENT,
    STAT_MINUS1,
    STAT_MINUS2,
    }state = STAT_COMMENT;

    const char* start = ++thiz->read_ptr;
    for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
    {
    char c = *thiz->read_ptr;

    switch(state)
    {
    case STAT_COMMENT:
    {
    if(c == '-')
    {
    state = STAT_MINUS1;
    }
    break;
    }
    case STAT_MINUS1:
    {
    if(c == '-')
    {
    state = STAT_MINUS2;
    }
    else
    {
    state = STAT_COMMENT;
    }
    break;
    }
    case STAT_MINUS2:
    {
    if(c == '>')
    {
    /*解析完成,调用builder的函数xml_builder_on_comment。*/
    xml_builder_on_comment(thiz->builder, start, thiz->read_ptr-start-2);
    return;
    }
    }
    default:break;
    }
    }

    return;
    }

    o 解析到处理指令时调用xml_builder_on_pi_element

    static void xml_parser_parse_pi(XmlParser* thiz)
    {
    enum _State
    {
    STAT_NAME,
    STAT_ATTR,
    STAT_END
    }state = STAT_NAME;

    char* tag_name = NULL;
    const char* start = thiz->read_ptr;

    for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
    {
    char c = *thiz->read_ptr;

    switch(state)
    {
    case STAT_NAME:
    {
    if(isspace(c) || c == '>')
    {
    tag_name = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
    state = c != '>' ? STAT_ATTR : STAT_END;
    }

    break;
    }
    case STAT_ATTR:
    {
    xml_parser_parse_attrs(thiz, '?');
    state = STAT_END;
    break;
    }
    default:break;
    }

    if(state == STAT_END)
    {
    break;
    }
    }

    tag_name = thiz->buffer + (size_t)tag_name;
    /*解析完成,调用builder的函数xml_builder_on_pi_element。*/
    xml_builder_on_pi_element(thiz->builder, tag_name, (const char**)thiz->attrs);

    for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '/0'; thiz->read_ptr++);

    return;
    }

    从上面的代码可以看出,XmlParser在适当的时候调用了XmlBuilder的接口函数,至于XmlBuilder在这些函数里做什么,要看具体的Builder实现了。

    先看一个最简单的XmlBuilder实现,它只是在屏幕上打印出传递给它的数据:

    o 创建函数

    XmlBuilder* xml_builder_dump_create(FILE* fp)
    {
    XmlBuilder* thiz = (XmlBuilder*)calloc(1, sizeof(XmlBuilder));

    if(thiz != NULL)
    {
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    thiz->on_start_element = xml_builder_dump_on_start_element;
    thiz->on_end_element = xml_builder_dump_on_end_element;
    thiz->on_text = xml_builder_dump_on_text;
    thiz->on_comment = xml_builder_dump_on_comment;
    thiz->on_pi_element = xml_builder_dump_on_pi_element;
    thiz->on_error = xml_builder_dump_on_error;
    thiz->destroy = xml_builder_dump_destroy;

    priv->fp = fp != NULL ? fp : stdout;
    }

    return thiz;
    }

    和其它接口的创建函数一样,它只是把接口要求的函数指针指到具体的实现函数上。

    o 实现 on_start_element

    static void xml_builder_dump_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)
    {
    int i = 0;
    PrivInfo* priv = (PrivInfo*)thiz->priv;
    fprintf(priv->fp, "<%s", tag);

    for(i = 0; attrs != NULL && attrs[i] != NULL && attrs[i + 1] != NULL; i += 2)
    {
    fprintf(priv->fp, " %s=/"%s/"", attrs[i], attrs[i + 1]);
    }
    fprintf(priv->fp, ">");

    return;
    }

    o 实现on_end_element

    static void xml_builder_dump_on_end_element(XmlBuilder* thiz, const char* tag)
    {
    PrivInfo* priv = (PrivInfo*)thiz->priv;
    fprintf(priv->fp, "/n", tag);

    return;
    }

    o 实现on_text

    static void xml_builder_dump_on_text(XmlBuilder* thiz, const char* text, size_t length)
    {
    PrivInfo* priv = (PrivInfo*)thiz->priv;
    fwrite(text, length, 1, priv->fp);

    return;
    }

    o 实现on_comment

    static void xml_builder_dump_on_comment(XmlBuilder* thiz, const char* text, size_t length)
    {
    PrivInfo* priv = (PrivInfo*)thiz->priv;
    fprintf(priv->fp, "/n");

    return;
    }

    o 实现on_pi_element

    static void xml_builder_dump_on_pi_element(XmlBuilder* thiz, const char* tag, const char** attrs)
    {
    int i = 0;
    PrivInfo* priv = (PrivInfo*)thiz->priv;
    fprintf(priv->fp, "fp, " %s=/"%s/"", attrs[i], attrs[i + 1]);
    }
    fprintf(priv->fp, "?>/n");

    return;
    }

    o 实现on_error

    static void xml_builder_dump_on_error(XmlBuilder* thiz, int line, int row, const char* message)
    {
    fprintf(stderr, "(%d,%d) %s/n", line, row, message);

    return;
    }

    上面的XmlBuilder实现简单,而且有一定的实用价值,我一般都会先写这样一个Builder。它不但对于调试程序有不小的帮助,而且只要稍 做修改,就可以把它改进成一个美化数据格式的小工具,不管原始数据的格式(当然要合符相应的语法规则)有多乱,你都能以一种比较好看的方式打印出来。

    下面我们再看一个比较复杂的XmlBuilder的实现,它根据接收的数据构建一棵XML树。

    o 创建函数

    XmlBuilder* xml_builder_tree_create(void)
    {
    XmlBuilder* thiz = (XmlBuilder*)calloc(1, sizeof(XmlBuilder));

    if(thiz != NULL)
    {
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    thiz->on_start_element = xml_builder_tree_on_start_element;
    thiz->on_end_element = xml_builder_tree_on_end_element;
    thiz->on_text = xml_builder_tree_on_text;
    thiz->on_comment = xml_builder_tree_on_comment;
    thiz->on_pi_element = xml_builder_tree_on_pi_element;
    thiz->on_error = xml_builder_tree_on_error;
    thiz->destroy = xml_builder_tree_destroy;

    priv->root = xml_node_create_normal("__root__", NULL);
    priv->current = priv->root;
    }

    return thiz;
    }

    和其它接口的创建函数一样,它只是把接口要求的函数指针指到具体的实现函数上。这里还创建了一个根结点__root__,以保证整棵树只有一个根结点。

    o 实现 on_start_element

    static void xml_builder_tree_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)
    {
    XmlNode* new_node = NULL;
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    new_node = xml_node_create_normal(tag, attrs);
    xml_node_append_child(priv->current, new_node);
    priv->current = new_node;

    return;
    }

    这里创建了一个新的结点,并追加为priv->current的子结点,然后让priv->current指向新的结点。

    o 实现 on_end_element

    static void xml_builder_tree_on_end_element(XmlBuilder* thiz, const char* tag)
    {
    PrivInfo* priv = (PrivInfo*)thiz->priv;
    priv->current = priv->current->parent;
    assert(priv->current != NULL);

    return;
    }

    这里只是让priv->current指向它的父结点。

    o 实现 on_text

    static void xml_builder_tree_on_text(XmlBuilder* thiz, const char* text, size_t length)
    {
    XmlNode* new_node = NULL;
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    new_node = xml_node_create_text(text);
    xml_node_append_child(priv->current, new_node);

    return;
    }

    这里创建一个文本结点, 并追加为priv->current的子结点。

    o 实现 on_comment

    static void xml_builder_tree_on_comment(XmlBuilder* thiz, const char* text, size_t length)
    {
    XmlNode* new_node = NULL;
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    new_node = xml_node_create_comment(text);
    xml_node_append_child(priv->current, new_node);

    return;
    }

    这里创建一个注释结点, 并追加为priv->current的子结点。

    o 实现 on_pi_element

    static void xml_builder_tree_on_pi_element(XmlBuilder* thiz, const char* tag, const char** attrs)
    {
    XmlNode* new_node = NULL;
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    new_node = xml_node_create_pi(tag, attrs);
    xml_node_append_child(priv->current, new_node);

    return;
    }

    这里创建一个处理指令结点, 并追加为priv->current的子结点。

    o 实现on_error

    static void xml_builder_tree_on_error(XmlBuilder* thiz, int line, int row, const char* message)
    {
    fprintf(stderr, "(%d,%d) %s/n", line, row, message);

    return;
    }

    下面我们再看XmlNode的数据结构和主要函数:

    o 数据结构

    typedef struct _XmlNode
    {
    XmlNodeType type;
    union
    {
    char* text;
    char* comment;
    XmlNodePi pi;
    XmlNodeNormal normal;
    }u;
    struct _XmlNode* parent;
    struct _XmlNode* children;
    struct _XmlNode* sibling;
    }XmlNode;

    type决定了结点的类型,可以是处理指令(XML_NODE_PI)、文本(XML_NODE_TEXT)、注释(XML_NODE_COMMENT)或普通TAG(XML_NODE_NORMAL)。

    联合体用于存放具体结点信息。

    parent指向父结点。

    children指向第一个子结点。

    sibling指向下一个兄弟结点。

    o 创建普通TAG结点

    XmlNode* xml_node_create_normal(const char* name, const char** attrs)
    {
    XmlNode* node = NULL;
    return_val_if_fail(name != NULL, NULL);

    if((node = calloc(1, sizeof(XmlNode))) != NULL)
    {
    int i = 0;
    node->type = XML_NODE_NORMAL;
    node->u.normal.name = strdup(name);

    if(attrs != NULL)
    {
    for(i = 0; attrs[i] != NULL && attrs[i+1] != NULL; i += 2)
    {
    xml_node_append_attr(node, attrs[i], attrs[i+1]);
    }
    }
    }

    return node;
    }

    o 创建处理指令结点

    XmlNode* xml_node_create_pi(const char* name, const char** attrs)
    {
    XmlNode* node = NULL;
    return_val_if_fail(name != NULL, NULL);

    if((node = calloc(1, sizeof(XmlNode))) != NULL)
    {
    int i = 0;
    node->type = XML_NODE_PI;
    node->u.pi.name = strdup(name);
    if(attrs != NULL)
    {
    for(i = 0; attrs[i] != NULL && attrs[i+1] != NULL; i += 2)
    {
    xml_node_append_attr(node, attrs[i], attrs[i+1]);
    }
    }
    }

    return node;
    }

    o 创建文本结点

    XmlNode* xml_node_create_text(const char* text)
    {
    XmlNode* node = NULL;
    return_val_if_fail(text != NULL, NULL);

    if((node = calloc(1, sizeof(XmlNode))) != NULL)
    {
    node->type = XML_NODE_TEXT;
    node->u.text = strdup(text);
    }

    return node;
    }

    o 创建注释结点

    XmlNode* xml_node_create_comment(const char* comment)
    {
    XmlNode* node = NULL;
    return_val_if_fail(comment != NULL, NULL);

    if((node = calloc(1, sizeof(XmlNode))) != NULL)
    {
    node->type = XML_NODE_COMMENT;
    node->u.comment = strdup(comment);
    }

    return node;
    }

    o 追加一个兄弟结点

    XmlNode* xml_node_append_sibling(XmlNode* node, XmlNode* sibling)
    {
    return_val_if_fail(node != NULL && sibling != NULL, NULL);

    if(node->sibling == NULL)
    {
    /*没有兄弟结点,让兄弟结点指向sibling */
    node->sibling = sibling;
    }
    else
    {
    /*否则,把sibling追加为最后一个兄弟结点*/
    XmlNode* iter = node->sibling;
    while(iter->sibling != NULL) iter = iter->sibling;
    iter->sibling = sibling;
    }
    /*让兄弟结点的父结点指向自己的父结点*/

    sibling->parent = node->parent;

    return sibling;
    }

    o 追加一个子结点

    XmlNode* xml_node_append_child(XmlNode* node, XmlNode* child)
    {
    return_val_if_fail(node != NULL && child != NULL, NULL);

    if(node->children == NULL)
    {
    /*没有子结点,让子结点指向child */
    node->children = child;
    }
    else
    {
    /*否则,把child 追加为最后一个子结点*/
    XmlNode* iter = node->children;
    while(iter->sibling != NULL) iter = iter->sibling;
    iter->sibling = child;
    }
    /*让子结点的父结点指向自己*/

    child->parent = node;

    return child;
    }

    回头再看一下XmlParser,XmlBuilder及几个具体的XmlBuilder的实现,我们可以看到,它们的实现都非常简单,其实这完全 得益于Builder模式的设计方法。它利用分而治之的思想,把数据的解析和数据的处理分开,降低了实现的复杂度。其次它利用了抽象的思想,从而数据的解 析只关心处理数据处理的接口,而不关心的它的实现,使得数据解析和数据处理可以独立变化。

    分而治之和抽象是降低复杂度最有效的手段之一,它们在Builder模式里得到了很好的体现。初学者应该多花些时间去体会。

    本节示例代码请到这里 下载。

  • 相关阅读:
    最长不重复子串
    add two nums
    logistic 回归
    threesum
    KNN算法思想与实现
    Python的易错点
    ccf 目录格式转换
    Azure 带宽
    Office 365 如何使用powershell查询邮件追踪
    Azure AD Connect 手动同步
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167526.html
Copyright © 2011-2022 走看看