一、前言

上篇文章(设计模式学习(三):生成器(Builder)模式)记录了 Builder 模式的具体内容,这次使用C语言来实现一个实际的例子——基于Builder模式的歌词解析器。

本文的示例来自李先静老师基于AWTK实现的多媒体播放器项目(awtk-media-player),可前往 GitHub开源仓库 下载。

二、示例介绍

歌词文件(.lrc)是一种文本文件,用来描述歌曲的歌词。在该文件的帮助下,音乐播放器可以根据相应时间同步显示歌词。歌词文件由时间标签、ID标签和歌词组成。

  • 时间标签,例如:[00:23.25]
  • ID标签,例如:[ar:谭咏麟]
  • 歌词,例如:凄雨冷风中 多少繁华如梦

下面是谭咏麟先生的歌曲 水中花 歌词的截取部分:

[ti:水中花]
[ar:谭咏麟]
[al:心手相连]
[by:孟德良]
[00:00.00]《水中花》
[00:02.00]演唱:谭咏麟
[00:04.00]作词:娃娃
[00:05.50]作曲:简宁
[00:07.00]
[00:09.03]凄雨冷风中  多少繁华如梦
[00:15.25]曾经万紫千红  随风吹落
[00:23.25]蓦然回首中  欢爱宛如烟云
[00:29.57]似水年华流走  不留影踪
[00:36.30]
[00:37.18]我看见  水中的花朵
[00:40.31]强要留住一抹红
[00:44.50]奈何辗转在风尘
[00:48.07]不再有往日颜色
[00:50.84]
......
......
[02:46.85]感怀飘零的花朵
[02:50.01]城市中无从寄托
[02:54.10]任那雨打风吹  也沉默
[02:57.90]仿佛是我
[03:00.12]
[03:01.72]啦…啦…啦…啦…
[03:16.09]啦…啦…啦…啦…

三、示例代码(C)

以下歌词解析器的代码参考了李先静老师基于AWTK实现的多媒体播放器项目(awtk-media-player),可前往 GitHub开源仓库 下载,其中歌词解析器位于awtk-media-player\src\media_player\lrc目录中。

3.1 Builder接口(lrc_builder.h)

#ifndef TK_LRC_BUILDER_H
#define TK_LRC_BUILDER_H#include "tkc/types_def.h"BEGIN_C_DECLSstruct _lrc_builder_t;
typedef struct _lrc_builder_t lrc_builder_t;typedef ret_t (*lrc_builder_on_id_tag_t)(lrc_builder_t* builder, const char* key,const char* value);
typedef ret_t (*lrc_builder_on_time_tag_t)(lrc_builder_t* builder, uint32_t start_time);
typedef ret_t (*lrc_builder_on_text_t)(lrc_builder_t* builder, const char* text);
typedef ret_t (*lrc_builder_on_error_t)(lrc_builder_t* builder, const char* error);
typedef ret_t (*lrc_builder_destroy_t)(lrc_builder_t* builder);typedef struct _lrc_builder_vtable_t {lrc_builder_on_text_t on_text;lrc_builder_on_error_t on_error;lrc_builder_on_id_tag_t on_id_tag;lrc_builder_on_time_tag_t on_time_tag;lrc_builder_destroy_t destroy;
} lrc_builder_vtable_t;/*** @class lrc_builder_t* lrc builder*/
struct _lrc_builder_t {const lrc_builder_vtable_t* vt;
};/*** @method lrc_builder_on_id_tag* 处理id标签。** @param {lrc_builder_t*} builder lrc_builder对象。* @param {const char*} id 名称。* @param {const char*} value 值。** @return {ret_t} 返回RET_OK表示成功,否则表示失败。*/
ret_t lrc_builder_on_id_tag(lrc_builder_t* builder, const char* id, const char* value);/*** @method lrc_builder_on_time_tag* 处理time标签。** @param {lrc_builder_t*} builder lrc_builder对象。* @param {uint32_t} timestamp 时间。** @return {ret_t} 返回RET_OK表示成功,否则表示失败。*/
ret_t lrc_builder_on_time_tag(lrc_builder_t* builder, uint32_t timestamp);/*** @method lrc_builder_on_text* 处理歌词。** @param {lrc_builder_t*} builder lrc_builder对象。* @param {const char*} text 歌词。** @return {ret_t} 返回RET_OK表示成功,否则表示失败。*/
ret_t lrc_builder_on_text(lrc_builder_t* builder, const char* text);/*** @method lrc_builder_on_error* 处理错误。** @param {lrc_builder_t*} builder lrc_builder对象。* @param {const char*} error 错误。** @return {ret_t} 返回RET_OK表示成功,否则表示失败。*/
ret_t lrc_builder_on_error(lrc_builder_t* builder, const char* error);/*** @method lrc_builder_destroy* 销毁lrc builder对象。** @param {lrc_builder_t*} builder lrc_builder对象。** @return {ret_t} 返回RET_OK表示成功,否则表示失败。*/
ret_t lrc_builder_destroy(lrc_builder_t* builder);END_C_DECLS#endif /*TK_LRC_BUILDER_H*/

3.2 Builder的实现一(lrc_builder_dump.c)

lrc_builder_dump:直接把解析的内容保存为文本,方便打印调试 以及 美化格式较乱的 lrc 文件,其主要代码如下:

#include "tkc/mem.h"
#include "tkc/utils.h"
#include "media_player/lrc/lrc_builder_dump.h"static ret_t lrc_builder_dump_on_id_tag(lrc_builder_t* builder, const char* id, const char* value) {lrc_builder_dump_t* dump = (lrc_builder_dump_t*)builder;str_append(&(dump->result), "[");str_append(&(dump->result), id);str_append(&(dump->result), ":");str_append(&(dump->result), value);str_append(&(dump->result), "]");return RET_OK;
}static ret_t lrc_builder_dump_on_time_tag(lrc_builder_t* builder, uint32_t timestamp) {char buff[64];uint32_t m = timestamp / (1000 * 60);double s = (timestamp % (1000 * 60)) / 1000.0f;lrc_builder_dump_t* dump = (lrc_builder_dump_t*)builder;tk_snprintf(buff, sizeof(buff), "[%02d:%2.2f]", m, s);str_append(&(dump->result), buff);return RET_OK;
}static ret_t lrc_builder_dump_on_text(lrc_builder_t* builder, const char* text) {lrc_builder_dump_t* dump = (lrc_builder_dump_t*)builder;str_append(&(dump->result), text);return RET_OK;
}static ret_t lrc_builder_dump_on_error(lrc_builder_t* builder, const char* error) {lrc_builder_dump_t* dump = (lrc_builder_dump_t*)builder;str_append(&(dump->result), error);return RET_OK;
}static ret_t lrc_builder_dump_destroy(lrc_builder_t* builder) {lrc_builder_dump_t* dump = (lrc_builder_dump_t*)builder;str_reset(&(dump->result));TKMEM_FREE(builder);return RET_OK;
}static const lrc_builder_vtable_t s_lrc_builder_dump_vtable = {.on_text = lrc_builder_dump_on_text,.on_id_tag = lrc_builder_dump_on_id_tag,.on_time_tag = lrc_builder_dump_on_time_tag,.on_error = lrc_builder_dump_on_error,.destroy = lrc_builder_dump_destroy,
};lrc_builder_t* lrc_builder_dump_create(void) {lrc_builder_dump_t* dump = TKMEM_ZALLOC(lrc_builder_dump_t);return_value_if_fail(dump != NULL, NULL);str_init(&(dump->result), 0);dump->lrc_builder.vt = &s_lrc_builder_dump_vtable;return (lrc_builder_t*)dump;
}

3.3 Builder的实现二(lrc.c)

lrc:这是默认的builder,它负责把lrc文件构建成内存中的结构,以便查询,其主要代码如下:

#include "tkc/mem.h"
#include "media_player/lrc/lrc.h"
#include "media_player/lrc/lrc_parser.h"
#include "media_player/lrc/lrc_builder.h"typedef struct _lrc_builder_default_t {lrc_builder_t lrc_builder;lrc_t* lrc;char* p;char* strs;uint32_t size;
} lrc_builder_default_t;#define lrc_isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r')static const char* lrc_builder_default_dup(lrc_builder_default_t* b, const char* text) {char* p = b->p;uint32_t size = strlen(text);const char* start = text;const char* end = start + size - 1;while (*start && lrc_isspace(*start)) start++;while (end > start && lrc_isspace(*end)) end--;size = end - start + 1;memcpy(p, start, size);p[size] = '\0';b->p += size + 1;return p;
}#define DUP(text) lrc_builder_default_dup(b, text)static ret_t lrc_builder_default_on_id_tag(lrc_builder_t* builder, const char* id,const char* value) {lrc_builder_default_t* b = (lrc_builder_default_t*)builder;lrc_id_tag_list_append(b->lrc->id_tags, DUP(id), DUP(value));return RET_OK;
}static ret_t lrc_builder_default_on_time_tag(lrc_builder_t* builder, uint32_t timestamp) {lrc_builder_default_t* b = (lrc_builder_default_t*)builder;lrc_time_tag_list_append(b->lrc->time_tags, timestamp);return RET_OK;
}static ret_t lrc_builder_default_on_text(lrc_builder_t* builder, const char* text) {lrc_builder_default_t* b = (lrc_builder_default_t*)builder;lrc_time_tag_list_set_text(b->lrc->time_tags, DUP(text));return RET_OK;
}static ret_t lrc_builder_default_on_error(lrc_builder_t* builder, const char* error) {log_debug("error:%s\n", error);return RET_OK;
}static ret_t lrc_builder_default_destroy(lrc_builder_t* builder) {lrc_builder_default_t* b = (lrc_builder_default_t*)builder;b->lrc->strs = b->strs;return RET_OK;
}static const lrc_builder_vtable_t s_lrc_builder_default_vtable = {.on_text = lrc_builder_default_on_text,.on_id_tag = lrc_builder_default_on_id_tag,.on_time_tag = lrc_builder_default_on_time_tag,.on_error = lrc_builder_default_on_error,.destroy = lrc_builder_default_destroy,
};lrc_builder_t* lrc_builder_default_init(lrc_builder_default_t* b, lrc_t* lrc, char* strs,uint32_t size) {return_value_if_fail(strs != NULL, NULL);b->lrc = lrc;b->p = strs;b->strs = strs;b->size = size;memset(strs, 0x00, size);b->lrc_builder.vt = &s_lrc_builder_default_vtable;return (lrc_builder_t*)b;
}static lrc_t* lrc_parse(lrc_t* lrc, const char* text) {ret_t ret = RET_OK;lrc_builder_default_t builder;uint32_t size = strlen(text) + 1;char* strs = TKMEM_ALLOC(size);lrc_builder_t* b = lrc_builder_default_init(&builder, lrc, strs, size);ret = lrc_parser_parse(b, text);lrc_time_tag_list_sort(lrc->time_tags);lrc_builder_destroy(&builder);return ret == RET_OK ? lrc : NULL;
}lrc_t* lrc_create(const char* text) {lrc_t* lrc = NULL;return_value_if_fail(text != NULL, NULL);lrc = TKMEM_ZALLOC(lrc_t);return_value_if_fail(lrc != NULL, NULL);lrc->id_tags = lrc_id_tag_list_create();lrc->time_tags = lrc_time_tag_list_create();if (lrc->id_tags == NULL || lrc->time_tags == NULL) {lrc_destroy(lrc);lrc = NULL;}return_value_if_fail(lrc != NULL, NULL);if (lrc_parse(lrc, text) == NULL) {lrc_destroy(lrc);lrc = NULL;}return lrc;
}ret_t lrc_destroy(lrc_t* lrc) {return_value_if_fail(lrc != NULL, RET_BAD_PARAMS);lrc_id_tag_list_destroy(lrc->id_tags);lrc_time_tag_list_destroy(lrc->time_tags);TKMEM_FREE(lrc->strs);TKMEM_FREE(lrc);return RET_OK;
}

3.4 产品(Product)

根据上篇 文章 中的描述,Product 就是 ConcreteBuilder 产生的结果,不同的 ConcreteBuilder 所产生的 Product 是不同的。

在上面的例子中,lrc_builder_dump 产生的 Product 是一段文本。而Builder默认的实现(lrc)产生的 Product 是一个数据结构,如下:

/*** @class lrc_t* lrc*/
typedef struct _lrc_t {/*** @property {lrc_id_tag_list_t*} id_tags* @annotation ["readable"]* id tags。*/lrc_id_tag_list_t* id_tags;/*** @property {lrc_time_tag_list_t*} time_tags* @annotation ["readable"]* time tags。*/lrc_time_tag_list_t* time_tags;/*private*/char* strs;
} lrc_t;

3.5 解析器(Director)的工作过程

解析器的任务就是解析出 lrc 文件最基本的元素:时间标签、ID标签和歌词,然后调用builder相应的函数表示出来,此处仅做例子,完整代码请参考 lrc_parser.c 。

static ret_t lrc_parser_parse_tag(lrc_parser_t* parser) {lrc_parser_skip_chars(parser, "\t \r\n");if (parser->p[0] == '\0') {return RET_OK;}if (isdigit(parser->p[0])) {return lrc_parser_parse_time_tag(parser);} else {return lrc_parser_parse_id_tag(parser);}
}static ret_t lrc_parser_parse_impl(lrc_parser_t* parser) {lrc_parser_skip_text(parser);while (TRUE) {char c = parser->p[0];if (c == '\0') {break;}if (c == '[') {parser->p++;lrc_parser_parse_tag(parser);if (parser->p[0] == ']') {parser->p++;}} else {lrc_parser_parse_text(parser);}}return RET_OK;
}ret_t lrc_parser_parse(lrc_builder_t* builder, const char* str) {lrc_parser_t p;ret_t ret = RET_OK;return_value_if_fail(lrc_parser_init(&p, builder, str) == RET_OK, RET_BAD_PARAMS);ret = lrc_parser_parse_impl(&p);lrc_parser_deinit(&p);return ret;
}

3.6 调用者(Client)

有了 解析器(Parser)和 相应的Builder 后,调用者需要把它们组合起来。解析完成时,调用者还希望从 Builder 取出 Product,以便后面使用。例如此处调用 lrc_create() 函数解析歌词文本(text参数)即可,代码如下:

static lrc_t* lrc_parse(lrc_t* lrc, const char* text) {ret_t ret = RET_OK;lrc_builder_default_t builder;uint32_t size = strlen(text) + 1;char* strs = TKMEM_ALLOC(size);lrc_builder_t* b = lrc_builder_default_init(&builder, lrc, strs, size);ret = lrc_parser_parse(b, text);lrc_time_tag_list_sort(lrc->time_tags);lrc_builder_destroy(&builder);return ret == RET_OK ? lrc : NULL;
}lrc_t* lrc_create(const char* text) {lrc_t* lrc = NULL;return_value_if_fail(text != NULL, NULL);lrc = TKMEM_ZALLOC(lrc_t);return_value_if_fail(lrc != NULL, NULL);lrc->id_tags = lrc_id_tag_list_create();lrc->time_tags = lrc_time_tag_list_create();if (lrc->id_tags == NULL || lrc->time_tags == NULL) {lrc_destroy(lrc);lrc = NULL;}return_value_if_fail(lrc != NULL, NULL);if (lrc_parse(lrc, text) == NULL) {lrc_destroy(lrc);lrc = NULL;}return lrc;
}

设计模式学习(四):基于Builder模式的歌词解析器相关推荐

  1. 设计模式学习笔记——建造者(Builder)模式

    设计模式学习笔记--建造者(Builder)模式 @(设计模式)[设计模式, 建造者模式, builder] 设计模式学习笔记建造者Builder模式 基本介绍 建造者案例 类图 实现代码 Build ...

  2. 设计模式(四)--代理模式

    设计模式(四)–代理模式 文章目录 设计模式(四)--代理模式 其他链接 代理模式 1.简介 2.静态代理 角色 代码实现 好处 3.动态代理 代码实现 解析 其他链接 JVM学习笔记(一) JVM学 ...

  3. 肝一肝设计模式【四】-- 建造者模式

    系列文章目录 肝一肝设计模式[一]-- 单例模式 传送门 肝一肝设计模式[二]-- 工厂模式 传送门 肝一肝设计模式[三]-- 原型模式 传送门 肝一肝设计模式[四]-- 建造者模式 传送门 肝一肝设 ...

  4. 设计模式学习笔记——装饰(Decorator)模式

    设计模式学习笔记--装饰(Decorator)模式 @(设计模式)[设计模式, 装饰模式, decorator] 设计模式学习笔记装饰Decorator模式 基本介绍 装饰案例 类图 实现代码 Dis ...

  5. 设计模式学习笔记——适配器(Adapter)模式

    设计模式学习笔记--适配器(Adapter)模式 @(设计模式)[设计模式, 适配器模式, adapter, 适配器] 设计模式学习笔记适配器Adapter模式 基本介绍 适配器案例 类适配器模式 类 ...

  6. 设计模式学习笔记——解释器(Interpreter)模式

    设计模式学习笔记--解释器(Interpreter)模式 @(设计模式)[设计模式, 解释器模式, Interpreter] 设计模式学习笔记解释器Interpreter模式 基本介绍 解释器案例 类 ...

  7. 设计模式学习笔记——命令(Command)模式

    设计模式学习笔记--命令(Command)模式 @(设计模式)[设计模式, 命令模式, command] 设计模式学习笔记命令Command模式 基本介绍 命令案例 类图 实现代码 Command接口 ...

  8. 设计模式学习笔记——代理(Proxy)模式

    设计模式学习笔记--代理(Proxy)模式 @(设计模式)[设计模式, 代理模式, proxy] 设计模式学习笔记代理Proxy模式 基本介绍 代理案例 类图 实现代码 Printable接口 Pri ...

  9. 设计模式学习笔记——状态(State)模式框架

    设计模式学习笔记--状态(State)模式框架 @(设计模式)[设计模式, 状态模式, State] 设计模式学习笔记状态State模式框架 基本介绍 状态案例 类图 实现代码 State接口 Day ...

最新文章

  1. Ajax实现无刷新三联动下拉框
  2. 8天后,有份独家小程序数据分析报告重磅来袭
  3. @Autowired
  4. sqlplus中调用shell_(转)shell 调用sqlplus各种情况示例
  5. 【转】自然语言系列学习之表示学习与知识获取(四)TransE
  6. window 任务管理器
  7. [BZOJ] 1637: [Usaco2007 Mar]Balanced Lineup
  8. 通过命令行界面使用AWS ElasticMapReduce
  9. 为什么配置了vsftpd.conf文件,用户还是可以访问上级目录?
  10. CompletableFuture详解~thenCombine
  11. [CQOI2014]通配符匹配
  12. ThinkPHP中的路由是什么意思?
  13. UVA 11732 - strcmp() Anyone?(Trie)
  14. 【转】JS判断SWF,JPG加载完毕、兼容(Activex,plugIn)所有浏览器
  15. gmm中隐变量是什么的_机器学习-隐变量模型和期望最大算法
  16. 转:Ajax与CustomErrors的尴尬
  17. 【C/C++】size_t 数据类型
  18. jquery 获取指定元素
  19. 微博html5到桌面,微博正文-微博HTML5版
  20. 动软代码生成器 模板

热门文章

  1. 老吴的 Xmind / 网络是怎样连接的 / Web浏览器发出请求
  2. 动漫配音是怎么制作出来的?除了自己配,还有一个小方法
  3. wincc通过vb如何读取mysql_wincc如何通过VB脚本读取报警数据库归档内容-工业支持中心-西门子中国...
  4. Windows7系统下安装Ubuntu实现双系统
  5. Python小游戏:滑雪大冒险,庆祝冬奥会完美闭幕
  6. 微信支付 支付验证签名失败
  7. Android Studio Build Variants 使用
  8. ros(25):发布一个静态的里程计odom信息
  9. 全球各国研发支出占GDP比例(1996-2018年)
  10. Unity 蓝湖 关于UI工作流优化的思考