一、前言
上篇文章(设计模式学习(三):生成器(Builder)模式)记录了 Builder 模式的具体内容,这次使用C语言来实现一个实际的例子——基于Builder模式的歌词解析器。
二、示例介绍
歌词文件(.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_DECLS
struct _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;
}