設(shè)計模式學(xué)習(xí)(四):基于Builder模式的歌詞解析器

一、前言

上篇文章(設(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;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容