一、前言
上篇文章(設(shè)計模式學(xué)習(xí)(三):生成器(Builder)模式)記錄了 Builder 模式的具體內(nèi)容,這次使用C語言來實(shí)現(xiàn)一個實(shí)際的例子——基于Builder模式的歌詞解析器。
二、示例介紹
歌詞文件(.lrc)是一種文本文件,用來描述歌曲的歌詞。在該文件的幫助下,音樂播放器可以根據(jù)相應(yīng)時間同步顯示歌詞。歌詞文件由時間標(biāo)簽、ID標(biāo)簽和歌詞組成。
- 時間標(biāo)簽,例如:[00:23.25]
- ID標(biāo)簽,例如:[ar:譚詠麟]
- 歌詞,例如:凄雨冷風(fēng)中 多少繁華如夢
下面是譚詠麟先生的歌曲 水中花 歌詞的截取部分:
[ti:水中花]
[ar:譚詠麟]
[al:心手相連]
[by:孟德良]
[00:00.00]《水中花》
[00:02.00]演唱:譚詠麟
[00:04.00]作詞:娃娃
[00:05.50]作曲:簡寧
[00:07.00]
[00:09.03]凄雨冷風(fēng)中 多少繁華如夢
[00:15.25]曾經(jīng)萬紫千紅 隨風(fēng)吹落
[00:23.25]驀然回首中 歡愛宛如煙云
[00:29.57]似水年華流走 不留影蹤
[00:36.30]
[00:37.18]我看見 水中的花朵
[00:40.31]強(qiáng)要留住一抹紅
[00:44.50]奈何輾轉(zhuǎn)在風(fēng)塵
[00:48.07]不再有往日顏色
[00:50.84]
......
......
[02:46.85]感懷飄零的花朵
[02:50.01]城市中無從寄托
[02:54.10]任那雨打風(fēng)吹 也沉默
[02:57.90]仿佛是我
[03:00.12]
[03:01.72]啦…啦…啦…啦…
[03:16.09]啦…啦…啦…啦…
三、示例代碼(C)
以下歌詞解析器的代碼參考了李先靜老師基于AWTK實(shí)現(xiàn)的多媒體播放器項(xiàng)目(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標(biāo)簽。
*
* @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標(biāo)簽。
*
* @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的實(shí)現(xiàn)一(lrc_builder_dump.c)
lrc_builder_dump:直接把解析的內(nèi)容保存為文本,方便打印調(diào)試 以及 美化格式較亂的 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的實(shí)現(xiàn)二(lrc.c)
lrc:這是默認(rèn)的builder,它負(fù)責(zé)把lrc文件構(gòu)建成內(nèi)存中的結(jié)構(gòu),以便查詢,其主要代碼如下:
#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 產(chǎn)品(Product)
根據(jù)上篇 文章 中的描述,Product 就是 ConcreteBuilder 產(chǎn)生的結(jié)果,不同的 ConcreteBuilder 所產(chǎn)生的 Product 是不同的。
在上面的例子中,lrc_builder_dump 產(chǎn)生的 Product 是一段文本。而Builder默認(rèn)的實(shí)現(xiàn)(lrc)產(chǎn)生的 Product 是一個數(shù)據(jù)結(jié)構(gòu),如下:
/**
* @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)的工作過程
解析器的任務(wù)就是解析出 lrc 文件最基本的元素:時間標(biāo)簽、ID標(biāo)簽和歌詞,然后調(diào)用builder相應(yīng)的函數(shù)表示出來,此處僅做例子,完整代碼請參考 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 調(diào)用者(Client)
有了 解析器(Parser)和 相應(yīng)的Builder 后,調(diào)用者需要把它們組合起來。解析完成時,調(diào)用者還希望從 Builder 取出 Product,以便后面使用。例如此處調(diào)用 lrc_create() 函數(shù)解析歌詞文本(text參數(shù))即可,代碼如下:
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;
}