lorid

convert chordpro to pdf
git clone git://git.relim.de/lorid.git
Log | Files | Refs | README | LICENSE

commit b10b5d93fcbe7ce68f596db38e98b0a7527a6068
parent c8d2989df01e732224ee06a152bf90ed134b2963
Author: nibo <nibo@relim.de>
Date:   Tue, 22 Jul 2025 20:27:31 +0200

Improve freeing of memory in case of error

Also correct a misunderstanding I had when working with fonts.
sans, serif and monospace are aliases of a group of font families.

Diffstat:
Msrc/chordpro.c | 151++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/chordpro.h | 2--
Msrc/config.c | 58+++++++++++++++++++---------------------------------------
Msrc/core.c | 6+++---
Msrc/core.h | 12++----------
Msrc/lorid.c | 60++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/out_pdf.c | 143+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Asrc/some.c | 1715+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 1924 insertions(+), 223 deletions(-)

diff --git a/src/chordpro.c b/src/chordpro.c @@ -14,7 +14,6 @@ #include "chord_diagram.h" #include "config.h" -static const char *font_families[] = { "normal", "sans", "serif", "monospace" }; static const char *font_styles[] = { "normal", "oblique", "italic" }; static const char *font_weights[] = { "normal", "bold" }; static const char *line_styles[] = { "single", "double", "none" }; @@ -548,8 +547,7 @@ static struct Font * cho_font_new(void) { struct Font *font = emalloc(sizeof(struct Font)); - font->name = NULL; - font->family = FONT_FAMILY_NORMAL; + font->family = NULL; font->style = FONT_STYLE_ROMAN; font->weight = FONT_WEIGHT_REGULAR; font->size = DEFAULT_FONT_SIZE; @@ -560,8 +558,7 @@ struct Font * cho_font_copy(struct Font *font) { struct Font *copy = emalloc(sizeof(struct Font)); - copy->name = font->name ? strdup(font->name) : NULL; - copy->family = font->family; + copy->family = font->family ? strdup(font->family) : NULL; copy->style = font->style; copy->weight = font->weight; copy->size = font->size; @@ -574,7 +571,7 @@ cho_font_free(struct Font *font) if (!font) { return; } - free(font->name); + free(font->family); free(font); } @@ -591,29 +588,6 @@ cho_fonts_free(struct Font **fonts) free(fonts); } -enum FontFamily -cho_font_family_parse(const char *str, bool *error) -{ - *error = false; - if (!strcmp(str, font_families[FONT_FAMILY_SANS])) { - return FONT_FAMILY_SANS; - } else if (!strcmp(str, font_families[FONT_FAMILY_SERIF])) { - return FONT_FAMILY_SERIF; - } else if (!strcmp(str, font_families[FONT_FAMILY_MONOSPACE])) { - return FONT_FAMILY_MONOSPACE; - } else if (!strcmp(str, font_families[FONT_FAMILY_NORMAL])) { - return FONT_FAMILY_NORMAL; - } - *error = true; - return FONT_FAMILY_SANS; // unused -} - -const char * -cho_font_family_to_config_string(enum FontFamily font_family) -{ - return font_families[font_family]; -} - enum FontStyle cho_font_style_parse(const char *str, bool *error) { @@ -658,8 +632,7 @@ static void cho_font_print_as_toml(struct Font *font, const char *section) { printf("[output.styles.%s.font]\n", section); - printf("name = \"%s\"\n", font->name); - printf("family = \"%s\"\n", cho_font_family_to_config_string(font->family)); + printf("family = \"%s\"\n", font->family); printf("style = \"%s\"\n", cho_font_style_to_config_string(font->style)); printf("weight = \"%s\"\n", cho_font_weight_to_config_string(font->weight)); printf("size = %.1f\n\n", font->size); @@ -669,12 +642,11 @@ void cho_font_print(struct Font *font) { printf("---- BEGIN FONT ----\n"); - if (font->name) { - printf("font name: %s\n", font->name); + if (font->family) { + printf("font family: %s\n", font->family); } else { - printf("font name: NULL\n"); + printf("font family: NULL\n"); } - printf("font family: %s\n", cho_font_family_to_config_string(font->family)); printf("font style: %s\n", cho_font_style_to_config_string(font->style)); printf("font weight: %s\n", cho_font_weight_to_config_string(font->weight)); printf("font size: %f\n", font->size); @@ -777,8 +749,8 @@ cho_style_property_apply_default( switch (ptype) { case STYLE_PROPERTY_TYPE_FONT: if (default_style_properties[i].u.font_name) { - free(style->font->name); - style->font->name = strdup(default_style_properties[i].u.font_name); + free(style->font->family); + style->font->family = strdup(default_style_properties[i].u.font_name); return true; } else { return false; @@ -874,12 +846,9 @@ cho_style_complement( struct ChoStylePresence *presence ) { - if (presence->font.name) { - free(old->font->name); - old->font->name = strdup(new->font->name); - } if (presence->font.family) { - old->font->family = new->font->family; + free(old->font->family); + old->font->family = strdup(new->font->family); } if (presence->font.style) { old->font->style = new->font->style; @@ -1046,13 +1015,13 @@ cho_style_font_desc_parse(const char *font_desc, struct ChoStylePresence *presen } else if (strcasecmp(words[w], "serif") == 0) { font->family = FONT_FAMILY_SERIF; if (name_until == EMPTY_INT) - name_until = w; */ + name_until = w; } else if (!strcasecmp(words[w], "monospace")) { font->family = FONT_FAMILY_MONOSPACE; presence->font.family = true; if (name_until == EMPTY_INT) { name_until = w; - } + } */ } else { size = strtod(words[w], NULL); if (size == 0.0) { @@ -1071,15 +1040,15 @@ cho_style_font_desc_parse(const char *font_desc, struct ChoStylePresence *presen if (name_until > 0) { for (w = 0, n = 0; w<name_until; w++) { len = strlen(words[w]); - font->name = erealloc(font->name, (n+len+1) * sizeof(char)); - memcpy(&font->name[n], words[w], len); + font->family = erealloc(font->family, (n+len+1) * sizeof(char)); + memcpy(&font->family[n], words[w], len); n += len; - font->name[n] = ' '; + font->family[n] = ' '; n++; } n--; - font->name[n] = '\0'; - presence->font.name = true; + font->family[n] = '\0'; + presence->font.family = true; } free(words); return font; @@ -1111,8 +1080,8 @@ cho_style_parse( if (!strcmp((*a)->name, "font_desc")) { font = cho_style_font_desc_parse((*a)->value, presence); if (font) { - if (!font->name) { - font->name = strdup(style->font->name); + if (!font->family) { + font->family = strdup(style->font->family); } cho_font_free(style->font); style->font = font; @@ -1121,22 +1090,8 @@ cho_style_parse( !strcmp((*a)->name, "font_family") || !strcmp((*a)->name, "face") ) { - if (!strcmp((*a)->value, "normal")) { - style->font->family = FONT_FAMILY_NORMAL; - presence->font.family = true; - } else if (!strcmp((*a)->value, "sans")) { - style->font->family = FONT_FAMILY_SANS; - presence->font.family = true; - } else if (!strcmp((*a)->value, "serif")) { - style->font->family = FONT_FAMILY_SERIF; - presence->font.family = true; - } else if (!strcmp((*a)->value, "monospace")) { - style->font->family = FONT_FAMILY_MONOSPACE; - presence->font.family = true; - } else { - cho_log(ctx, LOG_ERR, "Attribute 'font_family/face' of markup tag '%s' has an invalid value '%s'.", tag_name, (*a)->value); - goto ERR; - } + free(style->font->family); + style->font->family = strdup((*a)->value); } else if (!strcmp((*a)->name, "size")) { value_len = strlen((*a)->value); last_char = value_len - 1; @@ -1413,7 +1368,7 @@ cho_style_parse( style->font->size *= 0.8; presence->font.size = true; } else if (!strcmp(tag_name, "tt")) { - style->font->family = FONT_FAMILY_MONOSPACE; + style->font->family = strdup("monospace"); presence->font.family = true; } else if (!strcmp(tag_name, "u")) { style->underline_style = LINE_STYLE_SINGLE; @@ -3948,6 +3903,7 @@ cho_section_new(void) section->type = SECTION_TYPE_UNINITIALIZED; section->label = NULL; section->lines = NULL; + section->is_closed = false; return section; } @@ -4779,10 +4735,9 @@ cho_songs_close(struct ChoContext *ctx, struct ChoLine ***lines) (*lines) && (*lines)[ctx->li] && (*lines)[ctx->li]->items && - (*lines)[ctx->li]->items[ctx->lii] && - (*lines)[ctx->li]->items[ctx->lii]->is_text + (*lines)[ctx->li]->items[ctx->lii] ) { - if ((*lines)[ctx->li]->items[ctx->lii]->u.text->text) { + if ((*lines)[ctx->li]->items[ctx->lii]->is_text && (*lines)[ctx->li]->items[ctx->lii]->u.text->text) { (*lines)[ctx->li]->items[ctx->lii]->u.text->text = erealloc((*lines)[ctx->li]->items[ctx->lii]->u.text->text, (ctx->te+1) * sizeof(char)); (*lines)[ctx->li]->items[ctx->lii]->u.text->text[ctx->te] = 0; ctx->lii++; @@ -4877,6 +4832,8 @@ cho_context_init( ctx->th++; ctx->songs[ctx->so]->sections = emalloc((ctx->se+1) * sizeof(struct ChoSection *)); ctx->songs[ctx->so]->sections[ctx->se] = cho_section_new(); + // INFO: The first section has no start and end directive + ctx->songs[ctx->so]->sections[ctx->se]->is_closed = true; ctx->songs[ctx->so]->sections[ctx->se]->lines = emalloc(sizeof(struct ChoLine *)); ctx->songs[ctx->so]->sections[ctx->se]->lines[ctx->li] = cho_line_new(); return true; @@ -4898,6 +4855,20 @@ cho_context_cleanup(struct ChoContext *ctx) free(ctx->image_assets); } +static bool +is_previous_section_closed(struct ChoContext *ctx) +{ + int i; + + for (i = ctx->se; i>=0; i--) { + if (ctx->songs[ctx->so]->sections[i]->type == SECTION_TYPE_UNINITIALIZED) { + continue; + } + return ctx->songs[ctx->so]->sections[i]->is_closed ? true : false; + } + return true; +} + struct ChoSong ** cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *config) { @@ -4995,7 +4966,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c if (ctx.ta > -1 && !ctx.tags[ctx.ta]->is_closed && strcmp(ctx.tags[ctx.ta]->name, "img")) { // TODO: This seems to be unreachable cho_log(&ctx, LOG_ERR, "Tag has to be closed on same line."); - return NULL; + goto ERR; } if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); @@ -5063,6 +5034,10 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c ctx.current_ttype = directive->ttype; switch (directive->position) { case POSITION_START: { + if (!is_previous_section_closed(&ctx)) { + cho_log(&ctx, LOG_ERR, "Can't start a new section when the previous one is not yet closed."); + goto ERR; + } if (directive->stype == SECTION_TYPE_CUSTOM) { memset(custom_directive, 0, sizeof(custom_directive)); strcpy(custom_directive, &directive_name[9]); @@ -5112,6 +5087,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c ctx.lii = 0; free((*lines)[ctx.li]); (*lines)[ctx.li] = NULL; + ctx.songs[ctx.so]->sections[ctx.se]->is_closed = true; ctx.se++; ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *)); ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new(); @@ -5123,12 +5099,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); ctx.state = STATE_LYRICS; } else { - const char *section_type = section_types[directive->stype]; - if (section_type[0] == 0) { - cho_log(&ctx, LOG_ERR, "Can't close a section that wasn't opened earlier."); - } else { - cho_log(&ctx, LOG_ERR, "Can't close a %s section that wasn't opened earlier.", section_type); - } + cho_log(&ctx, LOG_ERR, "Can't close a %s section that wasn't opened earlier.", section_types[directive->stype]); goto ERR; } break; @@ -5366,6 +5337,10 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c ctx.current_ttype = directive->ttype; switch (directive->position) { case POSITION_START: { + if (!is_previous_section_closed(&ctx)) { + cho_log(&ctx, LOG_ERR, "Can't start a new section when the previous one is not yet closed."); + goto ERR; + } if (directive->stype == SECTION_TYPE_CUSTOM) { memset(custom_directive, 0, sizeof(custom_directive)); strcpy(custom_directive, &directive_name[9]); @@ -5650,32 +5625,28 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c goto ERR; case DIRECTIVE_TYPE_FONT: { sprop.ttype = directive->ttype; - char *dir_value = strdup(stripped_directive_value); switch (directive->sprop) { case STYLE_PROPERTY_TYPE_FONT: - sprop.u.font_name = emalloc((strlen(dir_value)+1) * sizeof(char)); - strcpy(sprop.u.font_name, dir_value); + sprop.u.font_name = emalloc((strlen(stripped_directive_value)+1) * sizeof(char)); + strcpy(sprop.u.font_name, stripped_directive_value); sprop.type = STYLE_PROPERTY_TYPE_FONT; break; case STYLE_PROPERTY_TYPE_SIZE: - sprop.u.font_size = strtod(dir_value, NULL); - if (sprop.u.font_size == 0.0) { - cho_log(&ctx, LOG_ERR, "Font directive '%s' has an invalid value.", directive_name); + sprop.u.font_size = strtod(stripped_directive_value, NULL); + if (sprop.u.font_size <= 0.0) { + cho_log(&ctx, LOG_ERR, "Font directive '%s' has to contain a positive number.", directive_name); goto ERR; } sprop.type = STYLE_PROPERTY_TYPE_SIZE; break; case STYLE_PROPERTY_TYPE_COLOR: - sprop.u.foreground_color = cho_color_parse(dir_value); + sprop.u.foreground_color = cho_color_parse(stripped_directive_value); if (sprop.u.foreground_color == NULL) { cho_log(&ctx, LOG_ERR, "Font directive '%s' has an invalid value.", directive_name); goto ERR; } sprop.type = STYLE_PROPERTY_TYPE_COLOR; break; - default: - cho_log(&ctx, LOG_ERR, "Invalid style property type '%d'.", directive->sprop); - goto ERR; } if (!cho_style_change_default(sprop)) { LOG_DEBUG("cho_style_change_default failed."); @@ -5686,7 +5657,6 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c } else if (sprop.type == STYLE_PROPERTY_TYPE_COLOR) { free(sprop.u.foreground_color); } - free(dir_value); break; } case DIRECTIVE_TYPE_CHORD: { @@ -5738,6 +5708,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c } memset(directive_value, 0, strlen(directive_value)); free(stripped_directive_value); + stripped_directive_value = NULL; cho_directive_free(directive); directive = NULL; break; @@ -5881,6 +5852,7 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c break; } cho_log(&ctx, LOG_ERR, "Newline character inside an annotation is invalid."); + ctx.lia++; goto ERR; } (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text = erealloc((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text, (ctx.ann+1) * sizeof(char)); @@ -6612,7 +6584,6 @@ cho_songs_parse(const char *str, const char *chordpro_filepath, struct Config *c free(stripped_directive_value); cho_directive_free(directive); cho_tag_attrs_free(directive_attrs); - cho_metadata_free(metadata); return NULL; } diff --git a/src/chordpro.h b/src/chordpro.h @@ -247,8 +247,6 @@ void cho_font_print(struct Font *font); struct Font *cho_font_copy(struct Font *font); void cho_fonts_free(struct Font **fonts); char *cho_font_name_normalize(const char *name); -enum FontFamily cho_font_family_parse(const char *str, bool *error); -const char *cho_font_family_to_config_string(enum FontFamily font_family); enum FontStyle cho_font_style_parse(const char *str, bool *error); const char *cho_font_style_to_config_string(enum FontStyle style); enum FontWeight cho_font_weight_parse(const char *str, bool *error); diff --git a/src/config.c b/src/config.c @@ -481,32 +481,16 @@ config_load_font( char (*err_buf)[25] ) { - enum FontFamily family; enum FontStyle style; enum FontWeight weight; toml_value_t value; bool error; - value = toml_table_string(table, "name"); - if (value.ok) { - presence->font.name = true; - free(font->name); - font->name = value.u.s; - } value = toml_table_string(table, "family"); if (value.ok) { presence->font.family = true; - family = cho_font_family_parse(value.u.s, &error); - if (error) { - if (err_buf) { - strcpy((char *)err_buf, "family value is invalid."); - } - free(value.u.s); - return false; - } else { - font->family = family; - } - free(value.u.s); + free(font->family); + font->family = value.u.s; } value = toml_table_string(table, "style"); if (value.ok) { @@ -709,7 +693,6 @@ debug_presence_print(const char *name, struct ChoStylePresence *presence) { printf("---- BEGIN PRESENCE ----\n"); printf("style '%s'\n", name); - printf("font.name %d\n", presence->font.name); printf("font.family %d\n", presence->font.family); printf("font.style %d\n", presence->font.style); printf("font.weight %d\n", presence->font.weight); @@ -740,12 +723,9 @@ set_text_style( ) { - if (!presence->font.name && text_presence->font.name) { - free(style->font->name); - style->font->name = strdup(text_style->font->name); - } if (!presence->font.family && text_presence->font.family) { - style->font->family = text_style->font->family; + free(style->font->family); + style->font->family = strdup(text_style->font->family); } if (!presence->font.style && text_presence->font.style) { style->font->style = text_style->font->style; @@ -854,50 +834,50 @@ config_load_default(void) config->output->toc->title = strdup("Table Of Contents"); config->output->styles[TEXT_TYPE_CHORD] = cho_style_new(); - config->output->styles[TEXT_TYPE_CHORD]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_CHORD]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_CHORD]->font->weight = FONT_WEIGHT_BOLD; config->output->styles[TEXT_TYPE_ANNOT] = cho_style_new(); - config->output->styles[TEXT_TYPE_ANNOT]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_ANNOT]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_ANNOT]->font->style = FONT_STYLE_ITALIC; config->output->styles[TEXT_TYPE_CHORUS] = cho_style_new(); - config->output->styles[TEXT_TYPE_CHORUS]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_CHORUS]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_FOOTER] = cho_style_new(); - config->output->styles[TEXT_TYPE_FOOTER]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_FOOTER]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_GRID] = cho_style_new(); - config->output->styles[TEXT_TYPE_GRID]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_GRID]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_GRID]->font->weight = FONT_WEIGHT_BOLD; config->output->styles[TEXT_TYPE_TAB] = cho_style_new(); - config->output->styles[TEXT_TYPE_TAB]->font->name = strdup("Courier"); + config->output->styles[TEXT_TYPE_TAB]->font->family = strdup("Courier"); // config->output->styles[TEXT_TYPE_TAB]->font->family = FONT_FAMILY_MONOSPACE; config->output->styles[TEXT_TYPE_TOC] = cho_style_new(); - config->output->styles[TEXT_TYPE_TOC]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_TOC]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_TOC]->font->size = 12.0; config->output->styles[TEXT_TYPE_TOC_TITLE] = cho_style_new(); - config->output->styles[TEXT_TYPE_TOC_TITLE]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_TOC_TITLE]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_TOC_TITLE]->font->weight = FONT_WEIGHT_BOLD; config->output->styles[TEXT_TYPE_TOC_TITLE]->font->size = 18.0; config->output->styles[TEXT_TYPE_TEXT] = cho_style_new(); - config->output->styles[TEXT_TYPE_TEXT]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_TEXT]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_TITLE] = cho_style_new(); - config->output->styles[TEXT_TYPE_TITLE]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_TITLE]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_TITLE]->font->weight = FONT_WEIGHT_BOLD; config->output->styles[TEXT_TYPE_TITLE]->font->size = 18.0; config->output->styles[TEXT_TYPE_SUBTITLE] = cho_style_new(); - config->output->styles[TEXT_TYPE_SUBTITLE]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_SUBTITLE]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_SUBTITLE]->font->size = 12.0; config->output->styles[TEXT_TYPE_LABEL] = cho_style_new(); - config->output->styles[TEXT_TYPE_LABEL]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_LABEL]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_LABEL]->font->style = FONT_STYLE_ITALIC; config->output->styles[TEXT_TYPE_COMMENT] = cho_style_new(); - config->output->styles[TEXT_TYPE_COMMENT]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_COMMENT]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_COMMENT]->background_color->red = 228; config->output->styles[TEXT_TYPE_COMMENT]->background_color->green = 228; config->output->styles[TEXT_TYPE_COMMENT]->background_color->blue = 228; config->output->styles[TEXT_TYPE_COMMENT_ITALIC] = cho_style_new(); - config->output->styles[TEXT_TYPE_COMMENT_ITALIC]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_COMMENT_ITALIC]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_COMMENT_ITALIC]->font->style = FONT_STYLE_ITALIC; config->output->styles[TEXT_TYPE_COMMENT_BOX] = cho_style_new(); - config->output->styles[TEXT_TYPE_COMMENT_BOX]->font->name = strdup(DEFAULT_FONT); + config->output->styles[TEXT_TYPE_COMMENT_BOX]->font->family = strdup(DEFAULT_FONT); config->output->styles[TEXT_TYPE_COMMENT_BOX]->boxed = true; return config; } diff --git a/src/core.c b/src/core.c @@ -622,7 +622,7 @@ size_to_string(struct Size *size) const char * is_base_font(struct Font *font) { - if (!strcmp(font->name, "Courier")) { + if (!strcmp(font->family, "Courier")) { if (font->style == FONT_STYLE_ITALIC && font->weight == FONT_WEIGHT_BOLD) { return "Courier-BoldItalic"; } else @@ -634,7 +634,7 @@ is_base_font(struct Font *font) } return "Courier"; } else - if (!strcmp(font->name, "Helvetica")) { + if (!strcmp(font->family, "Helvetica")) { if (font->style == FONT_STYLE_OBLIQUE && font->weight == FONT_WEIGHT_BOLD) { return "Helvetica-BoldOblique"; } else @@ -646,7 +646,7 @@ is_base_font(struct Font *font) } return "Helvetica"; } else - if (!strcmp(font->name, "Times")) { + if (!strcmp(font->family, "Times")) { if (font->style == FONT_STYLE_ITALIC && font->weight == FONT_WEIGHT_BOLD) { return "Times-BoldItalic"; } else diff --git a/src/core.h b/src/core.h @@ -61,13 +61,6 @@ enum FileType { FILE_TYPE_OTHER }; -enum FontFamily { - FONT_FAMILY_NORMAL, - FONT_FAMILY_SANS, - FONT_FAMILY_SERIF, - FONT_FAMILY_MONOSPACE -}; - enum FontStyle { FONT_STYLE_ROMAN, FONT_STYLE_OBLIQUE, @@ -158,7 +151,6 @@ enum TextType { }; struct FontPresence { - bool name; bool family; bool style; bool weight; @@ -182,8 +174,7 @@ struct ChoStylePresence { }; struct Font { - char *name; // INFO: Exact font family name, e.g. from `fc-list : family` - enum FontFamily family; + char *family; // INFO: Exact font family name, e.g. from `fc-list : family` enum FontStyle style; enum FontWeight weight; double size; @@ -260,6 +251,7 @@ struct ChoSection { enum SectionType type; struct ChoText *label; struct ChoLine **lines; + bool is_closed; }; struct ChoSong { diff --git a/src/lorid.c b/src/lorid.c @@ -3,6 +3,7 @@ #include <stdbool.h> #include <string.h> #include <getopt.h> +#include <fontconfig/fontconfig.h> #include "core.h" #include "chordpro.h" #include "config.h" @@ -24,13 +25,14 @@ main(int argc, char *argv[]) int s = 0; const char *chordpro_filepath = NULL; char *config_filepath = NULL; + char *input = NULL; char *output = NULL; - char *input, *pdf_filename; + char *pdf_filename = NULL; struct ChoSong **all_songs = NULL; struct ChoSong **songs = NULL; struct ChoSong **so; - struct Config *config; - FILE *fp; + struct Config *config = NULL; + FILE *fp = NULL; while ((o = getopt_long(argc, argv, "pc:o:Vvh", long_options, &option_index)) != -1) { switch(o) { @@ -60,14 +62,14 @@ main(int argc, char *argv[]) config = config_load_from_file(config_filepath); if (!config) { util_log(NULL, 0, LOG_ERR, "Failed to load the config file '%s'.", config_filepath); - return 1; + goto ERR; } } else { char *home = getenv("HOME"); if (!home) { LOG_DEBUG("getenv failed."); util_log(NULL, 0, LOG_ERR, "Failed to read the environment variable 'HOME'."); - return 1; + goto ERR; } size_t size = 26 + strlen(home) + 1; char default_config_path[size]; @@ -78,31 +80,27 @@ main(int argc, char *argv[]) config = config_load_default(); } } - free(config_filepath); if (argc == optind) { fp = stdin; input = file_read(fp); all_songs = cho_songs_parse(input, NULL, config); if (!all_songs) { LOG_DEBUG("cho_songs_parse failed."); - return 1; + goto ERR; } - free(input); } else if (argc == optind+1) { fp = fopen(argv[argc-1], "r"); if (!fp) { LOG_DEBUG("fopen failed."); - return 1; + goto ERR; } input = file_read(fp); chordpro_filepath = argv[argc-1]; all_songs = cho_songs_parse(input, chordpro_filepath, config); if (!all_songs) { LOG_DEBUG("cho_songs_parse failed."); - return 1; + goto ERR; } - free(input); - fclose(fp); } else { int file_count = argc - optind; int i; @@ -110,35 +108,61 @@ main(int argc, char *argv[]) fp = fopen(argv[i], "r"); if (!fp) { LOG_DEBUG("fopen failed."); - return 1; + goto ERR; } input = file_read(fp); songs = cho_songs_parse(input, argv[i], config); if (!songs) { LOG_DEBUG("cho_songs_parse failed."); - return 1; + goto ERR; } - free(input); - fclose(fp); for (so = songs; *so; s++, so++) { all_songs = erealloc(all_songs, (s+1) * sizeof(struct ChoSong *)); all_songs[s] = *so; } + free(input); + fclose(fp); free(songs); + input = NULL; + fp = NULL; + songs = NULL; } all_songs = erealloc(all_songs, (s+1) * sizeof(struct ChoSong *)); all_songs[s] = NULL; + printf("ending at index %d\n", s); } qsort(all_songs, cho_song_count(all_songs), sizeof(struct ChoSong *), cho_song_compare); pdf_filename = out_pdf_create(chordpro_filepath, output, all_songs, config); if (!pdf_filename) { LOG_DEBUG("out_pdf_create failed."); - return 1; + goto ERR; } util_log(NULL, 0, LOG_INFO, "Writing pdf to file: '%s'.", pdf_filename); - free(pdf_filename); + free(config_filepath); + free(input); free(output); + free(pdf_filename); cho_songs_free(all_songs); + cho_songs_free(songs); config_free(config); + if (fp) { + fclose(fp); + } return 0; + ERR: + if (s > 0 && all_songs[s-1]) { + all_songs = erealloc(all_songs, (s+1) * sizeof(struct ChoSong *)); + all_songs[s] = NULL; + } + free(config_filepath); + free(input); + free(output); + free(pdf_filename); + cho_songs_free(all_songs); + cho_songs_free(songs); + config_free(config); + if (fp) { + fclose(fp); + } + return 1; } diff --git a/src/out_pdf.c b/src/out_pdf.c @@ -91,19 +91,13 @@ fnt_name_create(struct Font *font) { const char *c; char *f; - char *name = str_normalize(font->name); - const char *family = cho_font_family_to_config_string(font->family); + char *family = str_normalize(font->family); const char *style = cho_font_style_to_config_string(font->style); const char *weight = cho_font_weight_to_config_string(font->weight); - size_t len = strlen(name) + strlen(family) + strlen(style) + strlen(weight); + size_t len = strlen(family) + strlen(style) + strlen(weight); char *fnt_name = emalloc((len + 4) * sizeof(char)); f = fnt_name; - for (c = name; *c; c++, f++) { - *f = *c; - } - *f = '-'; - f++; for (c = family; *c; c++, f++) { *f = *c; } @@ -118,7 +112,7 @@ fnt_name_create(struct Font *font) *f = *c; } *f = '\0'; - free(name); + free(family); return fnt_name; } @@ -134,8 +128,7 @@ fonts_add_if_not_in(struct Font ***array, struct Font *font) int a; for (a = 0; (*array)[a]; a++) { if ( - !strcmp((*array)[a]->name, font->name) && - (*array)[a]->family == font->family && + !strcmp((*array)[a]->family, font->family) && (*array)[a]->style == font->style && (*array)[a]->weight == font->weight ) { @@ -182,7 +175,7 @@ fonts_get_all(struct ChoSong **songs, struct Config *config) } } for (se = (*so)->sections; *se; se++) { - if ((*se)->label && (*se)->label->style->font->name) { + if ((*se)->label && (*se)->label->style->font->family) { font = cho_font_copy((*se)->label->style->font); added = fonts_add_if_not_in(&fonts, font); if (!added) { @@ -196,7 +189,7 @@ fonts_get_all(struct ChoSong **songs, struct Config *config) } else { style = (*above)->u.annot->style; } - if (style->font->name) { + if (style->font->family) { font = cho_font_copy(style->font); added = fonts_add_if_not_in(&fonts, font); if (!added) { @@ -207,7 +200,7 @@ fonts_get_all(struct ChoSong **songs, struct Config *config) for (it = (*li)->items; *it; it++) { if ((*it)->is_text) { style = (*it)->u.text->style; - if (style->font->name) { + if (style->font->family) { font = cho_font_copy(style->font); added = fonts_add_if_not_in(&fonts, font); if (!added) { @@ -239,36 +232,27 @@ fontpath_count_fonts(FcChar8 *path) return count; } -static char * -fontpath_find(struct Font *font, enum FontType font_type) +static FcPattern * +fontconfig_pattern_create(struct Font *font, enum FontType font_type) { - FcObjectSet *obj; FcPattern *pattern; - char *filepath = NULL; - obj = FcObjectSetBuild(FC_FAMILY, FC_SLANT, FC_WIDTH, FC_WEIGHT, FC_FONTFORMAT, FC_FONT_WRAPPER, FC_FILE, NULL); + FcValue family, wrapper, variable, width, type, style, weight; + pattern = FcPatternCreate(); - FcValue family = { .type = FcTypeString, .u.s = (FcChar8 *)font->name }; - FcPatternAdd(pattern, FC_FAMILY, family, FcFalse); - FcValue font_wrapper = { .type = FcTypeString, .u.s = (FcChar8 *)"SFNT" }; - FcPatternAdd(pattern, FC_FONT_WRAPPER, font_wrapper, FcFalse); - FcValue variable = { .type = FcTypeBool, .u.b = FcFalse }; - FcPatternAdd(pattern, FC_VARIABLE, variable, FcFalse); - FcValue width = { .type = FcTypeInteger, .u.i = FC_WIDTH_NORMAL }; - FcPatternAdd(pattern, FC_WIDTH, width, FcFalse); - /* TODO: Also handle FONT_FAMILY_NORMAL, FONT_FAMILY_SANS and FONT_FAMILY_SERIF, but how? */ - if (font->family == FONT_FAMILY_MONOSPACE) { - FcValue spacing = { .type = FcTypeInteger, .u.i = FC_MONO }; - FcPatternAdd(pattern, FC_SPACING, spacing, FcFalse); + if (!pattern) { + LOG_DEBUG("FcPatternCreate failed.\n"); + return NULL; } - FcValue type; + family.type = FcTypeString; + family.u.s = (FcChar8 *)font->family; + wrapper.type = FcTypeString; + wrapper.u.s = (FcChar8 *)"SFNT"; + variable.type = FcTypeBool; + variable.u.b = FcFalse; + width.type = FcTypeInteger; + width.u.i = FC_WIDTH_NORMAL; type.type = FcTypeString; - if (font_type == FONT_TYPE_OTF) { - type.u.s = (FcChar8 *)"CFF"; - } else { - type.u.s = (FcChar8 *)"TrueType"; - } - FcPatternAdd(pattern, FC_FONTFORMAT, type, FcFalse); - FcValue style; + type.u.s = (FcChar8 *)(font_type == FONT_TYPE_OTF ? "CFF" : "TrueType"); style.type = FcTypeInteger; switch (font->style) { case FONT_STYLE_ROMAN: @@ -281,35 +265,70 @@ fontpath_find(struct Font *font, enum FontType font_type) style.u.i = FC_SLANT_ITALIC; break; } - FcPatternAdd(pattern, FC_SLANT, style, FcFalse); - FcValue weight; weight.type = FcTypeInteger; - switch (font->weight) { - case FONT_WEIGHT_REGULAR: - weight.u.i = FC_WEIGHT_REGULAR; - break; - case FONT_WEIGHT_BOLD: - weight.u.i = FC_WEIGHT_BOLD; - break; - } + weight.u.i = font->weight == FONT_WEIGHT_REGULAR ? FC_WEIGHT_REGULAR : FC_WEIGHT_BOLD; + FcPatternAdd(pattern, FC_FAMILY, family, FcFalse); + FcPatternAdd(pattern, FC_FONT_WRAPPER, wrapper, FcFalse); + FcPatternAdd(pattern, FC_VARIABLE, variable, FcFalse); + FcPatternAdd(pattern, FC_WIDTH, width, FcFalse); + FcPatternAdd(pattern, FC_FONTFORMAT, type, FcFalse); + FcPatternAdd(pattern, FC_SLANT, style, FcFalse); FcPatternAdd(pattern, FC_WEIGHT, weight, FcFalse); - FcFontSet *set = FcFontList(NULL, pattern, obj); + return pattern; +} + +static char * +fontpath_find(struct Font *font, enum FontType font_type) +{ FcChar8 *file; + FcFontSet *set; + FcObjectSet *obj; + FcPattern *pattern, *match; + FcResult result; int i; - for (i = 0; i<set->nfont; i++) { - if (FcPatternGetString(set->fonts[i], FC_FILE, 0, &file) == FcResultMatch) { + char *filepath = NULL; + + pattern = fontconfig_pattern_create(font, font_type); + if (!pattern) { + LOG_DEBUG("fontconfig_pattern_create failed.\n"); + return NULL; + } + if ( + !strcmp(font->family, "sans") || + !strcmp(font->family, "serif") || + !strcmp(font->family, "monospace") + ) { + // pattern = FcNameParse((const FcChar8 *)font->family); + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + match = FcFontMatch(NULL, pattern, &result); + if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch) { if ( !file_extension_equals((const char *)file, "ttc") && fontpath_count_fonts(file) == 1 ) { filepath = strdup((const char *)file); - break; } } + FcPatternDestroy(match); + } else { + obj = FcObjectSetBuild(FC_FAMILY, FC_SLANT, FC_WIDTH, FC_WEIGHT, FC_FONTFORMAT, FC_FONT_WRAPPER, FC_FILE, NULL); + set = FcFontList(NULL, pattern, obj); + for (i = 0; i<set->nfont; i++) { + if (FcPatternGetString(set->fonts[i], FC_FILE, 0, &file) == FcResultMatch) { + if ( + !file_extension_equals((const char *)file, "ttc") && + fontpath_count_fonts(file) == 1 + ) { + filepath = strdup((const char *)file); + break; + } + } + } + FcObjectSetDestroy(obj); + FcFontSetDestroy(set); } - FcObjectSetDestroy(obj); FcPatternDestroy(pattern); - FcFontSetDestroy(set); return filepath; } @@ -320,8 +339,7 @@ pdf_load_chord_diagram_fonts(struct PDFContext *ctx) const char *font_name; struct Obj *fnt; struct Font font = { - .name = DEFAULT_FONT, - .family = FONT_FAMILY_NORMAL, + .family = DEFAULT_FONT, .style = FONT_STYLE_ROMAN, .weight = FONT_WEIGHT_REGULAR }; @@ -375,8 +393,8 @@ pdf_load_fonts(struct PDFContext *ctx, struct Font **needed_fonts) struct Obj *fnt; for (f = needed_fonts; *f; f++) { - if (font_name_is_path((*f)->name)) { - fontpath = filepath_resolve_tilde((*f)->name); + if (font_name_is_path((*f)->family)) { + fontpath = filepath_resolve_tilde((*f)->family); fnt = obj_new(); fnt->name = fnt_name_create(*f); index = strs_get_index_if_in(fontpaths, fontpath); @@ -387,7 +405,7 @@ pdf_load_fonts(struct PDFContext *ctx, struct Font **needed_fonts) goto ERR; } strs_add(&fontpaths, fontpath); - util_log(NULL, 0, LOG_INFO, "Loaded font from '%s'.", (*f)->name); + util_log(NULL, 0, LOG_INFO, "Loaded font from '%s'.", (*f)->family); } else { fnt->value = ctx->fonts[index]->value; } @@ -1760,7 +1778,7 @@ pdf_page_add_page_no( double width, x; style = cho_style_new(); - style->font->name = strdup(DEFAULT_FONT); + style->font->family = strdup(DEFAULT_FONT); texts = &c_ctx->content->pages[c_ctx->page]->texts; page_no = numeral_system_number_to_str(numeral_system, c_ctx->page+1); if (!page_no) { @@ -2902,6 +2920,7 @@ out_pdf_create( char *pdf_filepath = NULL; char *pdf_dot_filepath = NULL; + FcInit(); ctx.diagram_font_is_base_font = false; ctx.fonts = NULL; ctx.config = config; @@ -2979,6 +2998,7 @@ out_pdf_create( ); } free(pdf_dot_filepath); + FcFini(); return pdf_filepath; ERR: objs_free(img_objs); @@ -2993,5 +3013,6 @@ out_pdf_create( LOG_DEBUG("unlink failed."); } free(pdf_dot_filepath); + FcFini(); return NULL; } diff --git a/src/some.c b/src/some.c @@ -0,0 +1,1715 @@ +{ + enum AttrValueSyntax avs = ATTRIBUTE_VALUE_SYNTAX_UNINITIALIZED; + enum GridToken token; + struct Attr **directive_attrs = NULL; + struct ChoStyle *tag_style; + struct StyleProperty sprop; + struct ChoChord *tmp_chord; + struct ChoSection *chorus; + struct ChoImage *image; + struct ChordDiagram *diagram; + struct ChoDirective *directive = NULL; + struct ChoMetadata *metadata = NULL; + struct ChoLine ***lines; + struct ChoContext ctx; + bool err; + char *metadata_value, *shape; + char *stripped_directive_value = NULL; + char *label = NULL; + char directive_name[128]; + char directive_value[4096]; + char chord[CHORD_LEN]; + char tag_start[6]; + char tag_end[6]; + char custom_directive[64]; + char metadata_substitution[4096]; + char grid_token[CHORD_LEN]; + char c = 0; + char prev_c = '\n'; + int transpose; + + if (!cho_context_init(&ctx, config, chordpro_filepath)) { + LOG_DEBUG("cho_context_init failed."); + return NULL; + } + + lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines; + (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_TOC] = config->output->toc->show; + for (; *str; str++) { + c = *str; + // printf("state: %s, c: %c\n", state_enums[ctx.state], c); + if (c == '\r') { + continue; + } + switch (ctx.state) { + case STATE_LYRICS: { + if (prev_c == '\n' && c == '#') { + ctx.state_before_comment = STATE_LYRICS; + ctx.state = STATE_COMMENT; + break; + } + if (prev_c == '\n' && c == '{') { + ctx.state = STATE_DIRECTIVE_NAME; + break; + } + if (c == '[') { + ctx.state = STATE_CHORD; + break; + } + if (c == '<') { + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) { + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + } else { + ctx.lii++; + } + } else { + ctx.lii++; + } + ctx.te = 0; + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + ctx.state_before_tag = STATE_LYRICS; + ctx.state = STATE_MARKUP_TAG; + break; + } + if (c == '%') { + ctx.state_before_metadata_substitution = STATE_LYRICS; + ctx.state = STATE_MAYBE_METADATA_SUBSTITUTION; + break; + } + if (c == '\n') { + ctx.line_no++; + if (prev_c == '\\') { + ctx.state_before_backslash = STATE_LYRICS; + ctx.state = STATE_BACKSLASH; + ctx.te--; // INFO: This will later overwrite the backslash + break; + } + if (ctx.ta > -1 && !ctx.tags[ctx.ta]->is_closed && strcmp(ctx.tags[ctx.ta]->name, "img")) { + // TODO: This seems to be unreachable + cho_log(&ctx, LOG_ERR, "Tag has to be closed on same line."); + return NULL; + } + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) { + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + if (ctx.lii == 0) { + if ( + !(*lines)[ctx.li]->text_above && + (*lines)[ctx.li]->btype == BREAK_TYPE_LINE + ) { + free((*lines)[ctx.li]->items); + free((*lines)[ctx.li]); + *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + break; + } + } + } else { + ctx.lii++; + } + } else { + ctx.lii++; + } + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = NULL; + ctx.lii = 0; + ctx.te = 0; + (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[ctx.li]->text_above[ctx.lia] = NULL; + ctx.lia = 0; + ctx.li++; + *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + break; + } + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = c; + ctx.te++; + break; + } + case STATE_BACKSLASH: { + if (!is_whitespace(c)) { + str--; + ctx.state = ctx.state_before_backslash; + break; + } + break; + } + case STATE_DIRECTIVE_NAME: { + if (c == '}') { + directive_name[ctx.dn] = 0; + ctx.dn = 0; + directive = cho_directive_parse(&ctx, directive_name); + /* printf( + "directive: '%s'\ndtype: %s, stype: %s, position: %s\n", + directive_name, cho_debug_dtype(directive->dtype), cho_debug_the_stype(directive->stype), cho_debug_the_pos(directive->position) + ); */ + switch (directive->dtype) { + case DIRECTIVE_TYPE_ENVIRONMENT: { + ctx.current_ttype = directive->ttype; + switch (directive->position) { + case POSITION_START: { + if (directive->stype == SECTION_TYPE_CUSTOM) { + memset(custom_directive, 0, sizeof(custom_directive)); + strcpy(custom_directive, &directive_name[9]); + } + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + free((*lines)[ctx.li]->items); + ctx.lii = 0; + free((*lines)[ctx.li]); + (*lines)[ctx.li] = NULL; + ctx.se++; + ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *)); + ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new(); + ctx.songs[ctx.so]->sections[ctx.se]->type = directive->stype; + ctx.li = 0; + lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + ctx.songs[ctx.so]->present_text_types[directive->ttype] = true; + switch (directive->stype) { + case SECTION_TYPE_TAB: + ctx.state = STATE_TAB; + break; + case SECTION_TYPE_GRID: + ctx.state = STATE_GRID; + break; + default: + ctx.state = STATE_LYRICS; + } + break; + } + case POSITION_END: { + if (directive->stype == ctx.songs[ctx.so]->sections[ctx.se]->type) { + if (directive->stype == SECTION_TYPE_CUSTOM) { + if (strcmp(custom_directive, &directive_name[7]) != 0) { + break; + } + } + if (directive->stype == SECTION_TYPE_GRID) { + ctx.grid.bar_line_symbol_count = 0; + ctx.grid.tokens_per_cell = 0; + ctx.grid.expected_tokens_per_cell = 0; + } + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + free((*lines)[ctx.li]->items); + ctx.lii = 0; + free((*lines)[ctx.li]); + (*lines)[ctx.li] = NULL; + ctx.se++; + ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *)); + ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new(); + ctx.li = 0; + lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + ctx.state = STATE_LYRICS; + } else { + const char *section_type = section_types[directive->stype]; + if (section_type[0] == 0) { + cho_log(&ctx, LOG_ERR, "Can't close a section that wasn't opened earlier."); + } else { + cho_log(&ctx, LOG_ERR, "Can't close a %s section that wasn't opened earlier.", section_type); + } + goto ERR; + } + break; + } + case POSITION_NO: { + /* INFO: {chorus} */ + chorus = cho_find_previous_chorus(ctx.songs[ctx.so]->sections, ctx.se); + if (chorus) { + if (config->output->chorus->quote) { + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + } + ctx.lii++; + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = NULL; + ctx.lii = 0; + ctx.te = 0; + (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[ctx.li]->text_above[ctx.lia] = NULL; + ctx.lia = 0; + cho_line_free((*lines)[ctx.li]); + (*lines)[ctx.li] = NULL; + ctx.li = 0; + ctx.se++; + ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *)); + ctx.songs[ctx.so]->sections[ctx.se] = cho_section_copy(chorus); + ctx.se++; + ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *)); + ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new(); + lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + } else { + if (chorus->label) { + label = strdup(chorus->label->text); + } else { + label = strdup(config->output->chorus->label); + } + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) { + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + } else { + ctx.lii++; + } + } else { + ctx.lii++; + } + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style); + ctx.current_ttype = TEXT_TYPE_LABEL; + (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = label; + ctx.te += strlen(label); + } + } else { + if (config->output->chorus->quote) { + cho_log(&ctx, LOG_WARN, "Can't quote chorus because it's not defined previously."); + } + label = strdup(config->output->chorus->label); + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) { + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + } else { + ctx.lii++; + } + } else { + ctx.lii++; + } + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style); + ctx.current_ttype = TEXT_TYPE_LABEL; + (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = label; + ctx.te += strlen(label); + } + break; + } + } + break; + } + case DIRECTIVE_TYPE_METADATA: + cho_log(&ctx, LOG_WARN, "Ignoring metadata directive '%s' because it has no value.", directive_name); + break; + case DIRECTIVE_TYPE_FORMATTING: + cho_log(&ctx, LOG_WARN, "Formatting directive '%s' has no value.", directive_name); + break; + case DIRECTIVE_TYPE_IMAGE: + cho_log(&ctx, LOG_ERR, "Directive 'image' has no value."); + goto ERR; + case DIRECTIVE_TYPE_PREAMBLE: { + // INFO: The only preamble directive is 'new_song' + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + free((*lines)[ctx.li]->items); + free((*lines)[ctx.li]); + (*lines)[ctx.li] = NULL; + ctx.songs[ctx.so]->metadata = erealloc(ctx.songs[ctx.so]->metadata, (ctx.m+1) * sizeof(struct ChoMetadata *)); + ctx.songs[ctx.so]->metadata[ctx.m] = NULL; + ctx.se++; + ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *)); + ctx.songs[ctx.so]->sections[ctx.se] = NULL; + ctx.songs[ctx.so]->diagrams = erealloc(ctx.songs[ctx.so]->diagrams, (ctx.dia+1) * sizeof(struct ChordDiagram *)); + ctx.songs[ctx.so]->diagrams[ctx.dia] = NULL; + if (!cho_style_reset_default()) { + LOG_DEBUG("cho_style_reset_default failed."); + goto ERR; + } + for (int e = 0; e<ctx.ia; e++) { + cho_image_free(ctx.image_assets[e]); + } + free(ctx.image_assets); + ctx.image_assets = NULL; + ctx.ia = 0; + ctx.so++; + ctx.songs = erealloc(ctx.songs, (ctx.so+1) * sizeof(struct ChoSong *)); + ctx.songs[ctx.so] = cho_song_new(&ctx); + if (!ctx.songs[ctx.so]) { + LOG_DEBUG("cho_song_new failed."); + goto ERR; + } + free(ctx.transpose_history); + ctx.th = 0; + ctx.transpose_history = emalloc((ctx.th+1) * sizeof(int *)); + ctx.transpose_history[ctx.th] = 0; + ctx.transpose = &ctx.transpose_history[ctx.th]; + ctx.th++; + ctx.se = 0; + ctx.li = 0; + ctx.lii = 0; + // ctx.m = 0; + ctx.dia = 0; + ctx.songs[ctx.so]->sections = emalloc((ctx.se+1) * sizeof(struct ChoSection *)); + ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new(); + lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines; + *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + break; + } + case DIRECTIVE_TYPE_FONT: { + sprop.ttype = directive->ttype; + sprop.type = directive->sprop; + switch (directive->sprop) { + case STYLE_PROPERTY_TYPE_FONT: + sprop.u.font_name = NULL; + break; + case STYLE_PROPERTY_TYPE_SIZE: + sprop.u.font_size = EMPTY_DOUBLE; + break; + case STYLE_PROPERTY_TYPE_COLOR: + sprop.u.foreground_color = NULL; + break; + } + if (!cho_style_change_default(sprop)) { + LOG_DEBUG("cho_style_change_default failed."); + goto ERR; + } + break; + } + case DIRECTIVE_TYPE_CHORD: { + switch (directive->ctype) { + case CHORD_DIRECTIVE_TRANSPOSE: + ctx.transpose--; + ctx.th--; + break; + case CHORD_DIRECTIVE_DEFINE: + cho_log(&ctx, LOG_WARN, "Ignoring chord directive '%s' because it has no value.", directive_name); + break; + } + break; + } + case DIRECTIVE_TYPE_OUTPUT: + (*lines)[ctx.li]->btype = directive->btype; + break; + case DIRECTIVE_TYPE_EXTENSION: + // INFO: Such a directive should not be logged. + break; + case DIRECTIVE_TYPE_CUSTOM: + cho_log(&ctx, LOG_INFO, "Ignoring custom directive '%s'.", directive_name); + break; + } + cho_directive_free(directive); + directive = NULL; + break; + } + if (c == '{') { + cho_log(&ctx, LOG_ERR, "Can't start a new directive if the previous one is not yet closed."); + goto ERR; + } + if (c == '\n') { + ctx.line_no++; + if (prev_c == '\\') { + ctx.state_before_backslash = STATE_DIRECTIVE_NAME; + ctx.state = STATE_BACKSLASH; + ctx.dn--; + break; + } + cho_log(&ctx, LOG_ERR, "Can't have a newline in a directive name."); + goto ERR; + } + if (c == ':' || c == ' ') { + directive_name[ctx.dn] = 0; + ctx.dn = 0; + ctx.state = STATE_DIRECTIVE_VALUE; + break; + } + directive_name[ctx.dn] = c; + ctx.dn++; + break; + } + case STATE_DIRECTIVE_VALUE: { + if (c == '}') { + directive_value[ctx.dv] = 0; + ctx.dv = 0; + stripped_directive_value = str_remove_leading_whitespace(directive_value); + directive = cho_directive_parse(&ctx, directive_name); + /* printf( + "directive: '%s'\ndtype: %s, stype: %s, position: %s\n", + directive_name, dtype(directive->dtype), the_stype(directive->stype), pos(directive->position) + ); */ + switch (directive->dtype) { + case DIRECTIVE_TYPE_ENVIRONMENT: { + if (strlen(stripped_directive_value) > 0) { + ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_LABEL] = true; + } + ctx.current_ttype = directive->ttype; + switch (directive->position) { + case POSITION_START: { + if (directive->stype == SECTION_TYPE_CUSTOM) { + memset(custom_directive, 0, sizeof(custom_directive)); + strcpy(custom_directive, &directive_name[9]); + } + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + free((*lines)[ctx.li]->items); + ctx.lii = 0; + free((*lines)[ctx.li]); + (*lines)[ctx.li] = NULL; + ctx.se++; + ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *)); + ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new(); + ctx.songs[ctx.so]->sections[ctx.se]->type = directive->stype; + + if (strchr(stripped_directive_value, '=')) { + directive_attrs = cho_attrs_parse(&ctx, stripped_directive_value, directive_name); + if (!directive_attrs) { + LOG_DEBUG("cho_attrs_parse failed."); + goto ERR; + } + label = cho_attrs_get(directive_attrs, "label"); + if (directive->stype == SECTION_TYPE_GRID) { + shape = cho_attrs_get(directive_attrs, "shape"); + if (shape) { + if (!cho_grid_shape_parse_and_set(&ctx, shape)) { + LOG_DEBUG("cho_grid_parse_and_set_shape failed."); + goto ERR; + } + } + } + } else { + if (directive->stype == SECTION_TYPE_GRID) { + int index = str_index_of(stripped_directive_value, ' '); + if (index != -1) { + stripped_directive_value[index] = 0; + label = &stripped_directive_value[index+1]; + } + if (!cho_grid_shape_parse_and_set(&ctx, stripped_directive_value)) { + LOG_DEBUG("cho_grid_parse_and_set_shape failed."); + goto ERR; + } + } else { + label = stripped_directive_value; + } + } + if (label) { + ctx.songs[ctx.so]->sections[ctx.se]->label = emalloc(sizeof(struct ChoText)); + ctx.songs[ctx.so]->sections[ctx.se]->label->text = strdup(label); + ctx.prev_ttype = ctx.current_ttype; + ctx.current_ttype = TEXT_TYPE_LABEL; + ctx.songs[ctx.so]->sections[ctx.se]->label->style = cho_style_new_default(&ctx); + ctx.current_ttype = ctx.prev_ttype; + label = NULL; + } + cho_tag_attrs_free(directive_attrs); + directive_attrs = NULL; + if (ctx.directive_has_tag) { + cho_style_complement(ctx.songs[ctx.so]->sections[ctx.se]->label->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence); + ctx.directive_has_tag = false; + } + ctx.li = 0; + lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + ctx.songs[ctx.so]->present_text_types[directive->ttype] = true; + break; + } + case POSITION_END: { + cho_log(&ctx, LOG_ERR, "A directive that closes a section can't have arguments."); + goto ERR; + } + case POSITION_NO: { + /* INFO: {chorus: ...} */ + label = strdup(stripped_directive_value); + chorus = cho_find_previous_chorus(ctx.songs[ctx.so]->sections, ctx.se); + if (chorus) { + if (config->output->chorus->quote) { + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + } + ctx.lii++; + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = NULL; + ctx.lii = 0; + ctx.te = 0; + (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[ctx.li]->text_above[ctx.lia] = NULL; + ctx.lia = 0; + cho_line_free((*lines)[ctx.li]); + // songs[so]->sections[se]->lines = erealloc(songs[so]->sections[se]->lines, (li+1) * sizeof(struct ChoLine *)); + (*lines)[ctx.li] = NULL; + ctx.li = 0; + ctx.se++; + ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *)); + ctx.songs[ctx.so]->sections[ctx.se] = cho_section_copy(chorus); + if (ctx.songs[ctx.so]->sections[ctx.se]->label) { + free(ctx.songs[ctx.so]->sections[ctx.se]->label->text); + ctx.songs[ctx.so]->sections[ctx.se]->label->text = label; + } else { + ctx.current_ttype = TEXT_TYPE_LABEL; + ctx.songs[ctx.so]->sections[ctx.se]->label = cho_text_new(&ctx); + ctx.songs[ctx.so]->sections[ctx.se]->label->text = label; + } + if (ctx.directive_has_tag) { + cho_style_complement(ctx.songs[ctx.so]->sections[ctx.se]->label->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence); + ctx.directive_has_tag = false; + } + ctx.se++; + ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *)); + ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new(); + lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + } else { + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) { + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + } else { + ctx.lii++; + } + } else { + ctx.lii++; + } + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style); + ctx.current_ttype = TEXT_TYPE_LABEL; + (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = label; + if (ctx.directive_has_tag) { + cho_style_complement((*lines)[ctx.li]->items[ctx.lii]->u.text->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence); + ctx.directive_has_tag = false; + } + ctx.te += strlen(label); + } + } else { + if (config->output->chorus->quote) { + cho_log(&ctx, LOG_WARN, "Can't quote chorus because it's not defined previously."); + } + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) { + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + } else { + ctx.lii++; + } + } else { + ctx.lii++; + } + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style); + ctx.current_ttype = TEXT_TYPE_LABEL; + (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = label; + if (ctx.directive_has_tag) { + cho_style_complement((*lines)[ctx.li]->items[ctx.lii]->u.text->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence); + } + ctx.te += strlen(label); + } + break; + } + } + break; + } + case DIRECTIVE_TYPE_METADATA: { + metadata_value = strdup(stripped_directive_value); + if (strlen(metadata_value) == 0) { + cho_log(&ctx, LOG_WARN, "Ignoring metadata directive '%s' because it has no value.", directive_name); + free(metadata_value); + break; + } + switch (directive->meta) { + case METADATA_DIRECTIVE_TITLE: + ctx.songs[ctx.so]->metadata = erealloc(ctx.songs[ctx.so]->metadata, (ctx.m+1) * sizeof(struct ChoMetadata *)); + ctx.songs[ctx.so]->metadata[ctx.m] = cho_metadata_new(&ctx); + ctx.songs[ctx.so]->metadata[ctx.m]->name = strdup("title"); + ctx.songs[ctx.so]->metadata[ctx.m]->value = metadata_value; + cho_style_free(ctx.songs[ctx.so]->metadata[ctx.m]->style); + ctx.songs[ctx.so]->metadata[ctx.m]->style = cho_style_copy(directive->style); + ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_TITLE] = true; + break; + case METADATA_DIRECTIVE_SUBTITLE: + ctx.songs[ctx.so]->metadata = erealloc(ctx.songs[ctx.so]->metadata, (ctx.m+1) * sizeof(struct ChoMetadata *)); + ctx.songs[ctx.so]->metadata[ctx.m] = cho_metadata_new(&ctx); + ctx.songs[ctx.so]->metadata[ctx.m]->name = strdup("subtitle"); + ctx.songs[ctx.so]->metadata[ctx.m]->value = metadata_value; + cho_style_free(ctx.songs[ctx.so]->metadata[ctx.m]->style); + ctx.songs[ctx.so]->metadata[ctx.m]->style = cho_style_copy(directive->style); + ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_SUBTITLE] = true; + break; + case METADATA_DIRECTIVE_OTHER: { + if (!strcmp(directive_name, "meta")) { + metadata = cho_metadata_split(&ctx, directive_value); + if (!metadata) { + LOG_DEBUG("cho_metadata_split failed."); + goto ERR; + } + ctx.songs[ctx.so]->metadata = erealloc(ctx.songs[ctx.so]->metadata, (ctx.m+1) * sizeof(struct ChoMetadata *)); + ctx.songs[ctx.so]->metadata[ctx.m] = metadata; + free(metadata_value); + } else { + ctx.songs[ctx.so]->metadata = erealloc(ctx.songs[ctx.so]->metadata, (ctx.m+1) * sizeof(struct ChoMetadata *)); + ctx.songs[ctx.so]->metadata[ctx.m] = cho_metadata_new(&ctx); + ctx.songs[ctx.so]->metadata[ctx.m]->name = strdup(directive_name); + ctx.songs[ctx.so]->metadata[ctx.m]->value = metadata_value; + } + } + } + if (ctx.directive_has_tag) { + cho_style_complement(ctx.songs[ctx.so]->metadata[ctx.m]->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence); + ctx.directive_has_tag = false; + } + ctx.m++; + break; + } + case DIRECTIVE_TYPE_FORMATTING: { + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) { + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + } else { + ctx.lii++; + } + } else { + ctx.lii++; + } + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style); + (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_copy(directive->style); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(stripped_directive_value); + if (ctx.directive_has_tag) { + cho_style_complement((*lines)[ctx.li]->items[ctx.lii]->u.text->style, ctx.tags[ctx.ta]->style, &ctx.tags[ctx.ta]->style_presence); + ctx.directive_has_tag = false; + } + ctx.te += strlen(stripped_directive_value); + ctx.songs[ctx.so]->present_text_types[directive->ttype] = true; + break; + } + case DIRECTIVE_TYPE_IMAGE: { + if (strchr(directive_value, '=')) { + image = cho_image_directive_parse(&ctx, directive_value); + if (!image) { + LOG_DEBUG("cho_image_directive_parse failed."); + goto ERR; + } + } else { + image = cho_image_new(); + image->src = strdup(stripped_directive_value); + } + if (image->is_asset) { + ctx.image_assets = erealloc(ctx.image_assets, (ctx.ia+1) * sizeof(struct ChoImage *)); + ctx.image_assets[ctx.ia] = image; + ctx.ia++; + } else { + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + ctx.te = 0; + } + ctx.lii++; + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + cho_text_free((*lines)[ctx.li]->items[ctx.lii]->u.text); + (*lines)[ctx.li]->items[ctx.lii]->is_text = false; + (*lines)[ctx.li]->items[ctx.lii]->u.image = image; + } + break; + } + case DIRECTIVE_TYPE_PREAMBLE: + cho_log(&ctx, LOG_ERR, "Preamble directive '%s' can't have a value.", directive_name); + goto ERR; + case DIRECTIVE_TYPE_FONT: { + sprop.ttype = directive->ttype; + char *dir_value = strdup(stripped_directive_value); + switch (directive->sprop) { + case STYLE_PROPERTY_TYPE_FONT: + sprop.u.font_name = emalloc((strlen(dir_value)+1) * sizeof(char)); + strcpy(sprop.u.font_name, dir_value); + sprop.type = STYLE_PROPERTY_TYPE_FONT; + break; + case STYLE_PROPERTY_TYPE_SIZE: + sprop.u.font_size = strtod(dir_value, NULL); + if (sprop.u.font_size == 0.0) { + cho_log(&ctx, LOG_ERR, "Font directive '%s' has an invalid value.", directive_name); + goto ERR; + } + sprop.type = STYLE_PROPERTY_TYPE_SIZE; + break; + case STYLE_PROPERTY_TYPE_COLOR: + sprop.u.foreground_color = cho_color_parse(dir_value); + if (sprop.u.foreground_color == NULL) { + cho_log(&ctx, LOG_ERR, "Font directive '%s' has an invalid value.", directive_name); + goto ERR; + } + sprop.type = STYLE_PROPERTY_TYPE_COLOR; + break; + default: + cho_log(&ctx, LOG_ERR, "Invalid style property type '%d'.", directive->sprop); + goto ERR; + } + if (!cho_style_change_default(sprop)) { + LOG_DEBUG("cho_style_change_default failed."); + goto ERR; + } + if (sprop.type == STYLE_PROPERTY_TYPE_FONT) { + free(sprop.u.font_name); + } else if (sprop.type == STYLE_PROPERTY_TYPE_COLOR) { + free(sprop.u.foreground_color); + } + free(dir_value); + break; + } + case DIRECTIVE_TYPE_CHORD: { + switch (directive->ctype) { + case CHORD_DIRECTIVE_TRANSPOSE: + if (!transposition_parse(directive_value, &transpose)) { + LOG_DEBUG("transposition_parse failed."); + cho_log(&ctx, LOG_ERR, "Directive 'transpose' has an invalid value."); + goto ERR; + } + ctx.transpose_history = erealloc(ctx.transpose_history, (ctx.th+1) * sizeof(int *)); + ctx.transpose_history[ctx.th] = ctx.transpose_history[ctx.th-1] + transpose; + ctx.transpose = &ctx.transpose_history[ctx.th]; + ctx.th++; + break; + case CHORD_DIRECTIVE_DEFINE: + diagram = cho_chord_diagram_parse(&ctx, directive_value, ctx.songs[ctx.so]->diagrams, ctx.dia); + if (!diagram) { + LOG_DEBUG("cho_chord_diagram_parse failed."); + goto ERR; + } + // debug_chord_diagram_print(diagram); + ctx.songs[ctx.so]->diagrams = erealloc(ctx.songs[ctx.so]->diagrams, (ctx.dia+1) * sizeof(struct ChordDiagram *)); + ctx.songs[ctx.so]->diagrams[ctx.dia] = diagram; + ctx.dia++; + break; + } + break; + } + case DIRECTIVE_TYPE_OUTPUT: + cho_log(&ctx, LOG_ERR, "Directive '%s' can't have a value.", directive_name); + goto ERR; + case DIRECTIVE_TYPE_EXTENSION: + // INFO: Such a directive should not be logged. + break; + case DIRECTIVE_TYPE_CUSTOM: + cho_log(&ctx, LOG_INFO, "Ignoring custom directive '%s'.", directive_name); + break; + } + switch (directive->stype) { + case SECTION_TYPE_TAB: + ctx.state = STATE_TAB; + break; + case SECTION_TYPE_GRID: + ctx.state = STATE_GRID; + break; + default: + ctx.state = STATE_LYRICS; + } + memset(directive_value, 0, strlen(directive_value)); + free(stripped_directive_value); + stripped_directive_value = NULL; + cho_directive_free(directive); + directive = NULL; + break; + } + if (c == '%') { + ctx.state_before_metadata_substitution = STATE_DIRECTIVE_VALUE; + ctx.state = STATE_MAYBE_METADATA_SUBSTITUTION; + break; + } + if (c == '<') { + ctx.state_before_tag = STATE_DIRECTIVE_VALUE; + ctx.state = STATE_MARKUP_TAG; + break; + } + if (c == '{') { + cho_log(&ctx, LOG_ERR, "Can't start a new directive if the previous one is not yet closed."); + goto ERR; + } + if (c == '\n') { + ctx.line_no++; + if (prev_c == '\\') { + ctx.state_before_backslash = STATE_DIRECTIVE_VALUE; + ctx.state = STATE_BACKSLASH; + ctx.dv--; + break; + } + cho_log(&ctx, LOG_ERR, "Newline character inside a directive value is invalid."); + goto ERR; + } + if (ctx.dv > 4094) { + cho_log(&ctx, LOG_ERR, "Directive value can't be greater than 4095 bytes."); + goto ERR; + } + directive_value[ctx.dv] = c; + ctx.dv++; + break; + } + case STATE_CHORD: { + if (c == ']') { + chord[ctx.ch] = 0; + ctx.ch = 0; + ctx.prev_ttype = ctx.current_ttype; + ctx.current_ttype = TEXT_TYPE_CHORD; + if (ctx.is_chord_already_initialized) { + ctx.text_above_pos = cho_line_compute_text_above_position(ctx.songs[ctx.so]->sections[ctx.se]->lines[ctx.li], ctx.lii, ctx.te); + (*lines)[ctx.li]->text_above[ctx.lia]->position = ctx.text_above_pos; + tmp_chord = cho_chord_parse(&ctx, chord); + cho_chord_complete((*lines)[ctx.li]->text_above[ctx.lia]->u.chord, tmp_chord); + if (!(*lines)[ctx.li]->text_above[ctx.lia]->u.chord->is_canonical) { + cho_log(&ctx, LOG_INFO, "Didn't recognize the chord '%s'.", (*lines)[ctx.li]->text_above[ctx.lia]->u.chord->name); + } + cho_chord_free(tmp_chord); + ctx.is_chord_already_initialized = false; + } else { + (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[ctx.li]->text_above[ctx.lia] = emalloc(sizeof(struct ChoLineItemAbove)); + (*lines)[ctx.li]->text_above[ctx.lia]->is_chord = true; + ctx.text_above_pos = cho_line_compute_text_above_position((*lines)[ctx.li], ctx.lii, ctx.te); + (*lines)[ctx.li]->text_above[ctx.lia]->position = ctx.text_above_pos; + (*lines)[ctx.li]->text_above[ctx.lia]->u.chord = cho_chord_parse(&ctx, chord); + if (!(*lines)[ctx.li]->text_above[ctx.lia]->u.chord->is_canonical) { + cho_log(&ctx, LOG_INFO, "Didn't recognize the chord '%s'.", (*lines)[ctx.li]->text_above[ctx.lia]->u.chord->name); + } + } + ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_CHORD] = true; + memset(chord, 0, strlen(chord)); + ctx.lia++; + ctx.current_ttype = ctx.prev_ttype; + ctx.state = STATE_LYRICS; + break; + } + if (prev_c == '[' && c == '*') { + ctx.prev_ttype = ctx.current_ttype; + ctx.current_ttype = TEXT_TYPE_ANNOT; + (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[ctx.li]->text_above[ctx.lia] = emalloc(sizeof(struct ChoLineItemAbove)); + (*lines)[ctx.li]->text_above[ctx.lia]->is_chord = false; + ctx.text_above_pos = cho_line_compute_text_above_position((*lines)[ctx.li], ctx.lii, ctx.te); + (*lines)[ctx.li]->text_above[ctx.lia]->position = ctx.text_above_pos; + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot = cho_text_new(&ctx); + ctx.state = STATE_ANNOTATION; + break; + } + if (c == '\n') { + ctx.line_no++; + if (prev_c == '\\') { + ctx.state_before_backslash = STATE_CHORD; + ctx.state = STATE_BACKSLASH; + ctx.ch--; + break; + } + cho_log(&ctx, LOG_ERR, "Newline character inside a chord is invalid."); + goto ERR; + } + if (c == '[') { + cho_log(&ctx, LOG_ERR, "Can't start a new chord/annotation if the previous one is not yet closed."); + goto ERR; + } + if (c == '<') { + if (prev_c == '[') { + (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[ctx.li]->text_above[ctx.lia] = emalloc(sizeof(struct ChoLineItemAbove)); + (*lines)[ctx.li]->text_above[ctx.lia]->is_chord = true; + (*lines)[ctx.li]->text_above[ctx.lia]->u.chord = cho_chord_new(&ctx); + ctx.is_chord_already_initialized = true; + } + ctx.state_before_tag = STATE_CHORD; + ctx.state = STATE_MARKUP_TAG; + break; + } + if (ctx.ch > CHORD_LEN-2) { + cho_log(&ctx, LOG_ERR, "Chord can't be greater than %d bytes.", CHORD_LEN-1); + goto ERR; + } + chord[ctx.ch] = c; + ctx.ch++; + break; + } + case STATE_ANNOTATION: { + if (c == ']') { + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text = erealloc((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text, (ctx.ann+1) * sizeof(char)); + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text[ctx.ann] = 0; + ctx.songs[ctx.so]->present_text_types[TEXT_TYPE_ANNOT] = true; + ctx.ann = 0; + ctx.lia++; + ctx.current_ttype = ctx.prev_ttype; + ctx.state = STATE_LYRICS; + break; + } + if (c == '<') { + ctx.state_before_tag = STATE_ANNOTATION; + ctx.state = STATE_MARKUP_TAG; + break; + } + if (c == '\n') { + ctx.line_no++; + if (prev_c == '\\') { + ctx.state_before_backslash = STATE_ANNOTATION; + ctx.state = STATE_BACKSLASH; + ctx.ann--; + break; + } + cho_log(&ctx, LOG_ERR, "Newline character inside an annotation is invalid."); + goto ERR; + } + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text = erealloc((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text, (ctx.ann+1) * sizeof(char)); + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text[ctx.ann] = c; + ctx.ann++; + break; + } + case STATE_TAB: { + // INFO: similar to STATE_LYRICS but without markup and directives + if (prev_c == '\n' && c == '#') { + ctx.state_before_comment = STATE_TAB; + ctx.state = STATE_COMMENT; + break; + } + if (ctx.is_maybe_end_of_tab_directive) { + if (c == '}') { + directive_name[ctx.dn] = 0; + ctx.dn = 0; + ctx.is_maybe_end_of_tab_directive = false; + if (!strcmp(directive_name, "end_of_tab") || !strcmp(directive_name, "eot")) { + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + free((*lines)[ctx.li]->items); + ctx.lii = 0; + free((*lines)[ctx.li]); + (*lines)[ctx.li] = NULL; + ctx.se++; + ctx.songs[ctx.so]->sections = erealloc(ctx.songs[ctx.so]->sections, (ctx.se+1) * sizeof(struct ChoSection *)); + ctx.songs[ctx.so]->sections[ctx.se] = cho_section_new(); + ctx.li = 0; + lines = &ctx.songs[ctx.so]->sections[ctx.se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + ctx.current_ttype = TEXT_TYPE_TEXT; + ctx.state = STATE_LYRICS; + break; + } else { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = '{'; + ctx.te++; + char *k; + for (k = (char *)&directive_name; *k; k++, ctx.te++) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = *k; + } + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = '}'; + ctx.te++; + } + break; + } + if (c == ' ' || c == ':') { + directive_name[ctx.dn] = 0; + ctx.dn = 0; + ctx.is_maybe_end_of_tab_directive = false; + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = '{'; + ctx.te++; + char *k; + for (k = (char *)&directive_name; *k; k++, ctx.te++) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = *k; + } + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = c; + ctx.te++; + break; + } + directive_name[ctx.dn] = c; + ctx.dn++; + break; + } + if (prev_c == '\n' && c == '{') { + ctx.is_maybe_end_of_tab_directive = true; + break; + } + if (c == '\n') { + ctx.line_no++; + if (prev_c == '\\') { + ctx.state_before_backslash = STATE_TAB; + ctx.state = STATE_BACKSLASH; + // INFO: This will later overwrite the backslash + ctx.te--; + break; + } + if (ctx.ta > -1 && !ctx.tags[ctx.ta]->is_closed && strcmp(ctx.tags[ctx.ta]->name, "img")) { + cho_log(&ctx, LOG_ERR, "Tag has to be closed on same line."); + goto ERR; + } + if ((*lines)[ctx.li]->items[ctx.lii]->is_text) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = 0; + if (strlen((*lines)[ctx.li]->items[ctx.lii]->u.text->text) == 0) { + cho_line_item_free((*lines)[ctx.li]->items[ctx.lii]); + if (ctx.lii == 0) { + if ( + !(*lines)[ctx.li]->text_above && + (*lines)[ctx.li]->btype == BREAK_TYPE_LINE + ) { + free((*lines)[ctx.li]->items); + free((*lines)[ctx.li]); + *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + break; + } + } + } else { + ctx.lii++; + } + } else { + ctx.lii++; + } + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = NULL; + ctx.lii = 0; + ctx.te = 0; + (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[ctx.li]->text_above[ctx.lia] = NULL; + ctx.lia = 0; + ctx.li++; + *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + break; + } + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = c; + ctx.te++; + break; + } + case STATE_GRID: { + if (prev_c == '\n' && c == '#') { + ctx.state_before_comment = STATE_GRID; + ctx.state = STATE_COMMENT; + break; + } + if (prev_c == '\n' && c == '{') { + ctx.state = STATE_DIRECTIVE_NAME; + break; + } + if (c == '\n') { + ctx.line_no++; + if (prev_c != '}') { + grid_token[ctx.gt] = 0; + ctx.gt = 0; + if (grid_token[0] == 0) { + break; + } + token = cho_grid_token_parse(&ctx, grid_token, &err); + if (err) { + cho_log(&ctx, LOG_ERR, "Invalid token '%s' in grid section.", grid_token); + goto ERR; + } + if (token == GRID_TOKEN_BAR_LINE_SYMBOL) { + ctx.grid.bar_line_symbol_count++; + ctx.grid.bar_line_symbol_in_line_count++; + if (ctx.grid.bar_line_symbol_count == 2) { + ctx.grid.expected_tokens_per_cell = ctx.grid.tokens_per_cell; + } else { + if (ctx.grid.bar_line_symbol_in_line_count > 1 && ctx.grid.expected_tokens_per_cell != ctx.grid.tokens_per_cell) { + cho_log( + &ctx, + LOG_ERR, + "Grid cell no. %d has %d tokens but should have %d tokens.", + ctx.grid.bar_line_symbol_in_line_count - 1, + ctx.grid.tokens_per_cell, + ctx.grid.expected_tokens_per_cell + ); + goto ERR; + } + } + ctx.grid.tokens_per_cell = 0; + } else { + ctx.grid.tokens_per_cell++; + } + if (token == GRID_TOKEN_PLACEHOLDER) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(" "); + } else { + ctx.prev_ttype = ctx.current_ttype; + ctx.current_ttype = TEXT_TYPE_GRID; + cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style); + (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(grid_token); + ctx.current_ttype = ctx.prev_ttype; + } + ctx.lii++; + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = NULL; + ctx.lii = 0; + (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->text_above[ctx.lia] = NULL; + ctx.lia = 0; + ctx.li++; + *lines = erealloc(*lines, (ctx.li+1) * sizeof(struct ChoLine *)); + (*lines)[ctx.li] = cho_line_new(); + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + ctx.grid.bar_line_symbol_in_line_count = 0; + } + break; + } + if (c == ' ') { + grid_token[ctx.gt] = 0; + ctx.gt = 0; + if (grid_token[0] == 0) { + break; + } + token = cho_grid_token_parse(&ctx, grid_token, &err); + if (err) { + cho_log(&ctx, LOG_ERR, "Invalid token '%s' in grid section.", grid_token); + goto ERR; + } + if (token == GRID_TOKEN_BAR_LINE_SYMBOL) { + ctx.grid.bar_line_symbol_count++; + ctx.grid.bar_line_symbol_in_line_count++; + if (ctx.grid.bar_line_symbol_count == 2) { + ctx.grid.expected_tokens_per_cell = ctx.grid.tokens_per_cell; + } else { + if (ctx.grid.bar_line_symbol_in_line_count > 1 && ctx.grid.expected_tokens_per_cell != ctx.grid.tokens_per_cell) { + cho_log( + &ctx, + LOG_ERR, + "Grid cell no. %d has %d tokens but should have %d tokens.", + ctx.grid.bar_line_symbol_in_line_count - 1, + ctx.grid.tokens_per_cell, + ctx.grid.expected_tokens_per_cell + ); + goto ERR; + } + } + ctx.grid.tokens_per_cell = 0; + } else { + ctx.grid.tokens_per_cell++; + } + if (token == GRID_TOKEN_PLACEHOLDER) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(" "); + } else { + ctx.prev_ttype = ctx.current_ttype; + ctx.current_ttype = TEXT_TYPE_GRID; + cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style); + (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_new_default(&ctx); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = strdup(grid_token); + ctx.current_ttype = ctx.prev_ttype; + } + ctx.lii++; + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + break; + } + grid_token[ctx.gt] = c; + ctx.gt++; + break; + } + case STATE_MARKUP_TAG: { + if (c == '/') { + ctx.state = STATE_MARKUP_TAG_END; + break; + } + ctx.ta++; + ctx.tags = erealloc(ctx.tags, (ctx.ta+1) * sizeof(struct Tag *)); + ctx.tags[ctx.ta] = cho_tag_new(); + ctx.state = STATE_MARKUP_TAG_START; + str--; + break; + } + case STATE_MARKUP_TAG_START: { + if (c == '>') { + tag_start[ctx.t] = 0; + ctx.t = 0; + if (!strcmp(tag_start, "img")) { + cho_log(&ctx, LOG_ERR, "'img' tag has to have at least the 'src' attribute."); + goto ERR; + } + ctx.tags[ctx.ta]->name = strdup(tag_start); + tag_style = cho_style_parse(&ctx, tag_start, NULL, cho_tag_style_inherit(ctx.tags, ctx.ta-1), &ctx.tags[ctx.ta]->style_presence); + if (!tag_style) { + LOG_DEBUG("cho_style_parse failed."); + goto ERR; + } + ctx.tags[ctx.ta]->style = tag_style; + switch (ctx.state_before_tag) { + case STATE_LYRICS: + cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style); + (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_copy(tag_style); + break; + case STATE_CHORD: + cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style); + (*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style = cho_style_copy(tag_style); + break; + case STATE_ANNOTATION: + if (ctx.ann > 0) { + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text = erealloc((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text, (ctx.ann+1) * sizeof(char)); + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->text[ctx.ann] = 0; + ctx.ann = 0; + ctx.lia++; + (*lines)[ctx.li]->text_above = erealloc((*lines)[ctx.li]->text_above, (ctx.lia+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[ctx.li]->text_above[ctx.lia] = emalloc(sizeof(struct ChoLineItemAbove)); + (*lines)[ctx.li]->text_above[ctx.lia]->is_chord = false; + (*lines)[ctx.li]->text_above[ctx.lia]->position = ctx.text_above_pos; + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot = cho_text_new(&ctx); + } + cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style); + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style = cho_style_copy(tag_style); + break; + case STATE_DIRECTIVE_VALUE: + ctx.directive_has_tag = true; + break; + default: + cho_log(&ctx, LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[ctx.state_before_tag]); + goto ERR; + } + memset(tag_start, 0, strlen(tag_start)); + ctx.state = ctx.state_before_tag; + break; + } + if (is_whitespace(c)) { + tag_start[ctx.t] = 0; + ctx.t = 0; + ctx.tags[ctx.ta]->name = strdup(tag_start); + ctx.tags[ctx.ta]->attrs = erealloc(ctx.tags[ctx.ta]->attrs, (ctx.at+1) * sizeof(struct Attr *)); + ctx.tags[ctx.ta]->attrs[ctx.at] = cho_tag_attr_new(); + ctx.state = STATE_MARKUP_ATTR_NAME; + break; + } + if (c == '\n') { + ctx.line_no++; + if (prev_c == '\\') { + ctx.state_before_backslash = STATE_MARKUP_TAG_START; + ctx.state = STATE_BACKSLASH; + ctx.t--; + break; + } + cho_log(&ctx, LOG_ERR, "Newline character inside a tag name is invalid."); + goto ERR; + } + if (ctx.t == 5) { + cho_log(&ctx, LOG_ERR, "Start tag name is too long."); + goto ERR; + } + tag_start[ctx.t] = c; + ctx.t++; + break; + } + case STATE_MARKUP_TAG_END: { + if (c == '>') { + tag_end[ctx.t] = 0; + ctx.t = 0; + if (!cho_tag_close_last_unclosed(&ctx, tag_end, ctx.tags, ctx.ta)) { + LOG_DEBUG("cho_tag_close_last_unclosed failed."); + goto ERR; + } + memset(tag_end, 0, strlen(tag_end)); + ctx.state = ctx.state_before_tag; + break; + } + if (c == '\n') { + ctx.line_no++; + if (prev_c == '\\') { + ctx.state_before_backslash = STATE_MARKUP_TAG_END; + ctx.state = STATE_BACKSLASH; + ctx.t--; + break; + } + cho_log(&ctx, LOG_ERR, "Newline character inside a tag name is invalid."); + goto ERR; + } + if (ctx.t == 5) { + cho_log(&ctx, LOG_ERR, "End tag name is too long."); + goto ERR; + } + tag_end[ctx.t] = c; + ctx.t++; + break; + } + case STATE_MARKUP_ATTR_NAME: { + if (c == '=') { + ctx.tags[ctx.ta]->attrs[ctx.at]->name = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->name, (ctx.atn+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->name[ctx.atn] = 0; + ctx.atn = 0; + ctx.state = STATE_MARKUP_ATTR_VALUE; + break; + } + if (is_whitespace(c)) { + if (ctx.at == 0) { + if (!ctx.tags[ctx.ta]->attrs[ctx.at]->name) { + break; + } else { + ctx.tags[ctx.ta]->attrs[ctx.at]->name = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->name, (ctx.atn+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->name[ctx.atn] = 0; + cho_log(&ctx, LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", ctx.tags[ctx.ta]->attrs[ctx.at]->name, tag_start); + goto ERR; + } + } + if (ctx.tags[ctx.ta]->attrs[ctx.at-1]->name && ctx.tags[ctx.ta]->attrs[ctx.at-1]->value) { + break; + } + if (!ctx.tags[ctx.ta]->attrs[ctx.at-1]->name && !ctx.tags[ctx.ta]->attrs[ctx.at-1]->value) { + break; + } + ctx.tags[ctx.ta]->attrs[ctx.at]->name = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->name, (ctx.atn+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->name[ctx.atn] = 0; + cho_log(&ctx, LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", ctx.tags[ctx.ta]->attrs[ctx.at]->name, tag_start); + goto ERR; + } + if (c == '>') { + if (ctx.tags[ctx.ta]->attrs[ctx.at-1]->value) { + cho_tag_attr_free(ctx.tags[ctx.ta]->attrs[ctx.at]); + ctx.tags[ctx.ta]->attrs[ctx.at] = NULL; + ctx.atn = 0; + if (!strcmp(ctx.tags[ctx.ta]->name, "img")) { + cho_text_free((*lines)[ctx.li]->items[ctx.lii]->u.text); + (*lines)[ctx.li]->items[ctx.lii]->is_text = false; + image = cho_image_tag_parse(&ctx, ctx.tags[ctx.ta]->attrs); + if (!image) { + LOG_DEBUG("cho_image_tag_parse failed."); + goto ERR; + } + (*lines)[ctx.li]->items[ctx.lii]->u.image = image; + ctx.lii++; + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + } else { + tag_style = cho_style_parse(&ctx, tag_start, ctx.tags[ctx.ta]->attrs, cho_tag_style_inherit(ctx.tags, ctx.ta-1), &ctx.tags[ctx.ta]->style_presence); + if (!tag_style) { + LOG_DEBUG("cho_style_parse failed."); + goto ERR; + } + ctx.tags[ctx.ta]->style = tag_style; + switch (ctx.state_before_tag) { + case STATE_LYRICS: + cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style); + (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_copy(tag_style); + break; + case STATE_CHORD: + cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style); + (*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style = cho_style_copy(tag_style); + break; + case STATE_ANNOTATION: + cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style); + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style = cho_style_copy(tag_style); + break; + case STATE_DIRECTIVE_VALUE: + ctx.directive_has_tag = true; + break; + default: + cho_log(&ctx, LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[ctx.state_before_tag]); + goto ERR; + } + } + ctx.at = 0; + memset(tag_start, 0, strlen(tag_start)); + ctx.state = ctx.state_before_tag; + break; + } else { + ctx.tags[ctx.ta]->attrs[ctx.at]->name = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->name, (ctx.atn+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->name[ctx.atn] = 0; + ctx.atn = 0; + cho_log(&ctx, LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", ctx.tags[ctx.ta]->attrs[ctx.at]->name, tag_start); + goto ERR; + } + } + if (c == '\n') { + ctx.line_no++; + if (prev_c == '\\') { + ctx.state_before_backslash = STATE_MARKUP_ATTR_NAME; + ctx.state = STATE_BACKSLASH; + ctx.atn--; + break; + } + cho_log(&ctx, LOG_ERR, "Newline character inside an tag attribute name is invalid."); + goto ERR; + } + ctx.tags[ctx.ta]->attrs[ctx.at]->name = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->name, (ctx.atn+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->name[ctx.atn] = c; + ctx.atn++; + break; + } + case STATE_MARKUP_ATTR_VALUE: { + if (c == '\n') { + ctx.line_no++; + if (prev_c == '\\') { + ctx.state_before_backslash = STATE_MARKUP_ATTR_VALUE; + ctx.state = STATE_BACKSLASH; + ctx.atv--; + break; + } + cho_log(&ctx, LOG_ERR, "Newline character inside an attribute value is invalid."); + goto ERR; + } + if (avs == ATTRIBUTE_VALUE_SYNTAX_UNINITIALIZED) { + if (is_whitespace(c)) { + cho_log(&ctx, LOG_ERR, "Whitespace character after equals sign is invalid."); + goto ERR; + } + if (c == '>') { + cho_log(&ctx, LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", ctx.tags[ctx.ta]->attrs[ctx.at]->name, tag_start); + goto ERR; + } + if (c == '\'') { + avs = ATTRIBUTE_VALUE_SYNTAX_APOSTROPHE; + } else if (c == '"') { + avs = ATTRIBUTE_VALUE_SYNTAX_QUOTATION_MARK; + } else { + avs = ATTRIBUTE_VALUE_SYNTAX_UNQUOTED; + if (c == '%') { + ctx.state_before_metadata_substitution = STATE_MARKUP_ATTR_VALUE; + ctx.state = STATE_MAYBE_METADATA_SUBSTITUTION; + break; + } + ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = c; + ctx.atv++; + } + break; + } + if (avs == ATTRIBUTE_VALUE_SYNTAX_UNQUOTED && c == '>') { + ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = 0; + ctx.atv = 0; + ctx.at++; + ctx.tags[ctx.ta]->attrs = erealloc(ctx.tags[ctx.ta]->attrs, (ctx.at+1) * sizeof(struct Attr *)); + ctx.tags[ctx.ta]->attrs[ctx.at] = NULL; + if (!strcmp(ctx.tags[ctx.ta]->name, "img")) { + cho_text_free((*lines)[ctx.li]->items[ctx.lii]->u.text); + (*lines)[ctx.li]->items[ctx.lii]->is_text = false; + image = cho_image_tag_parse(&ctx, ctx.tags[ctx.ta]->attrs); + if (!image) { + LOG_DEBUG("cho_image_tag_parse failed."); + goto ERR; + } + (*lines)[ctx.li]->items[ctx.lii]->u.image = image; + ctx.lii++; + (*lines)[ctx.li]->items = erealloc((*lines)[ctx.li]->items, (ctx.lii+1) * sizeof(struct ChoLineItem *)); + (*lines)[ctx.li]->items[ctx.lii] = cho_line_item_new(&ctx); + } else { + tag_style = cho_style_parse(&ctx, tag_start, ctx.tags[ctx.ta]->attrs, cho_tag_style_inherit(ctx.tags, ctx.ta-1), &ctx.tags[ctx.ta]->style_presence); + if (!tag_style) { + LOG_DEBUG("cho_style_parse failed."); + goto ERR; + } + ctx.tags[ctx.ta]->style = tag_style; + switch (ctx.state_before_tag) { + case STATE_LYRICS: + cho_style_free((*lines)[ctx.li]->items[ctx.lii]->u.text->style); + (*lines)[ctx.li]->items[ctx.lii]->u.text->style = cho_style_copy(tag_style); + break; + case STATE_CHORD: + cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style); + (*lines)[ctx.li]->text_above[ctx.lia]->u.chord->style = cho_style_copy(tag_style); + break; + case STATE_ANNOTATION: + cho_style_free((*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style); + (*lines)[ctx.li]->text_above[ctx.lia]->u.annot->style = cho_style_copy(tag_style); + break; + case STATE_DIRECTIVE_VALUE: + ctx.directive_has_tag = true; + break; + default: + cho_log(&ctx, LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[ctx.state_before_tag]); + goto ERR; + } + } + ctx.at = 0; + avs = ATTRIBUTE_VALUE_SYNTAX_UNINITIALIZED; + memset(tag_start, 0, strlen(tag_start)); + ctx.state = ctx.state_before_tag; + break; + } + if ( + (avs == ATTRIBUTE_VALUE_SYNTAX_APOSTROPHE && c == '\'') || + (avs == ATTRIBUTE_VALUE_SYNTAX_QUOTATION_MARK && c == '"') || + (avs == ATTRIBUTE_VALUE_SYNTAX_UNQUOTED && (c == ' ' || c == '\t')) + ) { + ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = 0; + ctx.atv = 0; + ctx.at++; + ctx.tags[ctx.ta]->attrs = erealloc(ctx.tags[ctx.ta]->attrs, (ctx.at+1) * sizeof(struct Attr *)); + ctx.tags[ctx.ta]->attrs[ctx.at] = cho_tag_attr_new(); + avs = ATTRIBUTE_VALUE_SYNTAX_UNINITIALIZED; + ctx.state = STATE_MARKUP_ATTR_NAME; + break; + } + if (c == '%') { + ctx.state_before_metadata_substitution = STATE_MARKUP_ATTR_VALUE; + ctx.state = STATE_MAYBE_METADATA_SUBSTITUTION; + break; + } + ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = c; + ctx.atv++; + break; + } + case STATE_COMMENT: { + if (c == '\n') { + ctx.line_no++; + ctx.state = ctx.state_before_comment; + break; + } + break; + } + case STATE_MAYBE_METADATA_SUBSTITUTION: { + if (c == '{') { + metadata_substitution[ctx.ms] = '%'; + ctx.ms++; + metadata_substitution[ctx.ms] = '{'; + ctx.ms++; + ctx.state = STATE_METADATA_SUBSTITUTION; + break; + } + switch (ctx.state_before_metadata_substitution) { + case STATE_LYRICS: + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = '%'; + ctx.te++; + break; + case STATE_DIRECTIVE_VALUE: + directive_value[ctx.dv] = '%'; + ctx.dv++; + break; + case STATE_MARKUP_ATTR_VALUE: + ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = '%'; + break; + default: + } + ctx.state = ctx.state_before_metadata_substitution; + str--; + break; + } + case STATE_METADATA_SUBSTITUTION: { + if (prev_c != '\\' && c == '}') { + if (ctx.nested_level == 0) { + metadata_substitution[ctx.ms] = '}'; + ctx.ms++; + metadata_substitution[ctx.ms] = 0; + ctx.ms = 0; + char *substituted = cho_metadata_substitution_parse( + &ctx, + metadata_substitution, + NULL, + ctx.state_before_metadata_substitution + ); + if (!substituted) { + LOG_DEBUG("cho_metadata_substitution_parse failed."); + goto ERR; + } + char *ch; + switch (ctx.state_before_metadata_substitution) { + case STATE_LYRICS: + for (ch = substituted; *ch; ch++) { + (*lines)[ctx.li]->items[ctx.lii]->u.text->text = erealloc((*lines)[ctx.li]->items[ctx.lii]->u.text->text, (ctx.te+1) * sizeof(char)); + (*lines)[ctx.li]->items[ctx.lii]->u.text->text[ctx.te] = *ch; + ctx.te++; + } + break; + case STATE_DIRECTIVE_VALUE: + for (ch = substituted; *ch; ch++) { + directive_value[ctx.dv] = *ch; + ctx.dv++; + } + break; + case STATE_MARKUP_ATTR_VALUE: + for (ch = substituted; *ch; ch++) { + ctx.tags[ctx.ta]->attrs[ctx.at]->value = erealloc(ctx.tags[ctx.ta]->attrs[ctx.at]->value, (ctx.atv+1) * sizeof(char)); + ctx.tags[ctx.ta]->attrs[ctx.at]->value[ctx.atv] = *ch; + ctx.atv++; + } + break; + default: + } + free(substituted); + ctx.state = ctx.state_before_metadata_substitution; + break; + } + ctx.nested_level--; + } + if (prev_c == '%' && c == '{') { + ctx.nested_level++; + } + if (ctx.ms > 4094) { + cho_log(&ctx, LOG_ERR, "Metadata substitution can't be greater than 4095 bytes."); + goto ERR; + } + metadata_substitution[ctx.ms] = c; + ctx.ms++; + break; + } + } + prev_c = c; + } + if (!cho_style_reset_default()) { + LOG_DEBUG("cho_style_reset_default failed."); + goto ERR; + } + cho_songs_close(&ctx, lines); + cho_context_cleanup(&ctx); + bool exist_title = false; + for (ctx.so = 0; ctx.songs[ctx.so]; ctx.so++) { + for (ctx.m = 0; ctx.songs[ctx.so]->metadata[ctx.m]; ctx.m++) { + if ( + !strcmp(ctx.songs[ctx.so]->metadata[ctx.m]->name, "title") && + ctx.songs[ctx.so]->metadata[ctx.m]->value && + strcmp(ctx.songs[ctx.so]->metadata[ctx.m]->value, "") != 0 + ) { + exist_title = true; + } + } + if (!exist_title) { + /* INFO: This cho_log() is not line specific. It's a workaround. */ + ctx.line_no = 0; + cho_log(&ctx, LOG_ERR, "Song has no title."); + goto ERR; + } + exist_title = false; + } + return ctx.songs; + ERR: + cho_songs_close(&ctx, lines); + cho_context_cleanup(&ctx); + for (int e = 0; e<=ctx.so; e++) { + cho_song_free(ctx.songs[e]); + } + free(ctx.songs); + free(stripped_directive_value); + cho_directive_free(directive); + cho_tag_attrs_free(directive_attrs); + return NULL; +}