lorid

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

commit c1276b3a52cf3c46b6a6efcf7e8dead5bb721890
parent e9915c1e0bb9e1a6960dcb194876b593a887c26f
Author: nibo <nibo@relim.de>
Date:   Fri,  7 Mar 2025 19:02:12 +0100

Support multiple metadata values

Diffstat:
Msrc/chordpro.c | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/chordpro.h | 4+++-
Msrc/config.c | 11+++++++++--
Msrc/out_pdf.c | 85++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/types.h | 1+
5 files changed, 162 insertions(+), 62 deletions(-)

diff --git a/src/chordpro.c b/src/chordpro.c @@ -1624,16 +1624,40 @@ cho_metadata_free(struct ChoMetadata *meta) free(meta); } -const char * -cho_metadata_get(struct ChoMetadata **metadata, const char *name) +bool +cho_metadata_value( + struct ChoMetadata **metadata, + const char *name, + const char *separator, + char **value, + struct ChoStyle **value_style +) { - int m; - for (m = 0; metadata[m]; m++) { - if (!strcmp(metadata[m]->name, name)) { - return metadata[m]->value; + *value = NULL; + const char *c; + struct ChoMetadata **m; + int v = 0; + for (m = metadata; *m; m++) { + if (!strcmp((*m)->name, name)) { + if (*value) { + for (c = separator; *c; c++, v++) { + *value = erealloc(*value, (v+1) * sizeof(char)); + (*value)[v] = *c; + } + } + for (c = (*m)->value; *c; c++, v++) { + *value = erealloc(*value, (v+1) * sizeof(char)); + (*value)[v] = *c; + } + *value_style = (*m)->style; } } - return NULL; + if (*value) { + *value = erealloc(*value, (v+1) * sizeof(char)); + (*value)[v] = 0; + return true; + } + return false; } static struct ChoMetadata * @@ -1646,20 +1670,20 @@ cho_metadata_split(struct ChoContext *ctx, const char *directive_value) int v = 0; int i; for (i = 0; value[i]; i++) { - if (value[i] == ' ') { - meta->name = erealloc(meta->name, (n+1) * sizeof(char)); - meta->name[n] = 0; - is_name = false; - } else { - if (is_name) { + if (is_name) { + if (value[i] == ' ') { + meta->name = erealloc(meta->name, (n+1) * sizeof(char)); + meta->name[n] = 0; + is_name = false; + } else { meta->name = erealloc(meta->name, (n+1) * sizeof(char)); meta->name[n] = value[i]; n++; - } else { - meta->value = erealloc(meta->value, (v+1) * sizeof(char)); - meta->value[v] = value[i]; - v++; } + } else { + meta->value = erealloc(meta->value, (v+1) * sizeof(char)); + meta->value[v] = value[i]; + v++; } } meta->value = erealloc(meta->value, (v+1) * sizeof(char)); @@ -1779,7 +1803,47 @@ cho_metadata_substitution_replace( struct ChoMetadata *m; int n = 0; int i; - if (index < 0) { + if (index == 0) { + char *c; + char *out = NULL; + int o = 0; + if (value[0] != 0) { + for (i = 0; i<ctx->m; i++) { + m = ctx->songs[ctx->so]->metadata[i]; + if (!strcmp(m->name, name) && !strcmp(m->value, value)) { + if (out) { + for (c = ctx->config->metadata_separator; *c; c++, o++) { + out = erealloc(out, (o+1) * sizeof(char)); + out[o] = *c; + } + } + for (c = m->value; *c; c++, o++) { + out = erealloc(out, (o+1) * sizeof(char)); + out[o] = *c; + } + } + } + } else { + for (i = 0; i<ctx->m; i++) { + m = ctx->songs[ctx->so]->metadata[i]; + if (!strcmp(m->name, name)) { + if (out) { + for (c = ctx->config->metadata_separator; *c; c++, o++) { + out = erealloc(out, (o+1) * sizeof(char)); + out[o] = *c; + } + } + for (c = m->value; *c; c++, o++) { + out = erealloc(out, (o+1) * sizeof(char)); + out[o] = *c; + } + } + } + } + out = erealloc(out, (o+1) * sizeof(char)); + out[o] = 0; + return out; + } else if (index < 0) { if (value[0] != 0) { for (i = ctx->m-1; i >= 0; i--) { m = ctx->songs[ctx->so]->metadata[i]; @@ -2044,7 +2108,7 @@ cho_metadata_substitution_parse( default: } char *replaced, *substituted, *ch; - int index = 1; + int index = 0; if (name_index[0] != 0) { index = atoi(name_index); if (index == 0) { @@ -2081,7 +2145,11 @@ cho_metadata_substitution_parse( } free(substituted); } else { - cho_log(ctx, LOG_WARN, "There is no metadata item named '%s'.", name); + if (index != 0) { + cho_log(ctx, LOG_WARN, "There is no metadata item named '%s.%d'.", name, index); + } else { + cho_log(ctx, LOG_WARN, "There is no metadata item named '%s'.", name); + } } } } else { @@ -2089,7 +2157,7 @@ cho_metadata_substitution_parse( cho_log(ctx, LOG_ERR, "An empty metadata substitution can only be used inside of another metadata substitution."); return NULL; } - replaced = cho_metadata_substitution_replace(ctx, parent_name, "", 1); + replaced = cho_metadata_substitution_replace(ctx, parent_name, "", 0); if (replaced) { for (ch = replaced; *ch; ch++) { out = erealloc(out, (o+1) * sizeof(char)); @@ -3709,6 +3777,19 @@ cho_text_free(struct ChoText *text) free(text); } +void +cho_texts_free(struct ChoText **texts) +{ + if (!texts) { + return; + } + struct ChoText **t; + for (t = texts; *t; t++) { + cho_text_free(*t); + } + free(texts); +} + static struct ChoText * cho_text_copy(struct ChoText *text) { diff --git a/src/chordpro.h b/src/chordpro.h @@ -199,7 +199,9 @@ size_t cho_chord_count(struct ChoChord **chords); int cho_chord_compare(const void *a, const void *b); void cho_chords_free(struct ChoChord **chords); -const char *cho_metadata_get(struct ChoMetadata **metadata, const char *name); +void cho_texts_free(struct ChoText **texts); + +bool cho_metadata_value(struct ChoMetadata **metadata, const char *name, const char *separator, char **value, struct ChoStyle **value_style); struct ChoStyle *cho_style_new(void); void cho_style_free(struct ChoStyle *style); diff --git a/src/config.c b/src/config.c @@ -450,6 +450,7 @@ static struct Config * config_load_default(void) { struct Config *config = emalloc(sizeof(struct Config)); + config->metadata_separator = strdup("; "); config->output = emalloc(sizeof(struct ConfigOutput)); config->output->toc = emalloc(sizeof(struct ConfigToc)); config->output->toc->show = false; @@ -526,6 +527,7 @@ void config_print_default(void) { struct Config *config = config_load_default(); + printf("metadata_separator = \"%s\"\n\n", config->metadata_separator); printf("[notation_systems]\n"); config_notes_print_as_toml(NS_COMMON); config_notes_print_as_toml(NS_GERMAN); @@ -921,10 +923,15 @@ config_load(const char *filepath) util_log(NULL, 0, LOG_ERR, "Config file '%s' is not a valid toml file: %s.", filepath, (char *)&errbuf); return NULL; } + toml_value_t value; + value = toml_table_string(table, "metadata_separator"); + if (value.ok) { + free(config->metadata_separator); + config->metadata_separator = value.u.s; + } toml_table_t *output = toml_table_table(table, "output"); if (output) { toml_table_t *styles, *notes, *chorus, *diagram, *toc, *page_no; - toml_value_t value; enum NotationSystem notation_system; enum Instrument instrument; enum Alignment align; @@ -1049,7 +1056,6 @@ config_load(const char *filepath) toml_table_t *chords = toml_table_table(parser, "chords"); if (chords) { toml_table_t *notes; - toml_value_t value; enum NotationSystem notation_system; struct Note **custom_notes; value = toml_table_string(chords, "notation_system"); @@ -1095,6 +1101,7 @@ config_load(const char *filepath) void config_free(struct Config *config) { + free(config->metadata_separator); free(config->output->toc->title); free(config->output->toc); free(config->output->chorus->label); diff --git a/src/out_pdf.c b/src/out_pdf.c @@ -447,12 +447,16 @@ pdf_filename_generate_from_songs(struct ChoSong **songs) { char *filename; char *normalized_title; - const char *title; + char *title; + struct ChoStyle *unused; int len = cho_song_count(songs); if (len == 0) return NULL; if (len == 1) { - title = cho_metadata_get(songs[0]->metadata, "title"); + if (!cho_metadata_value(songs[0]->metadata, "title", g_config->metadata_separator, &title, &unused)) { + LOG_DEBUG("cho_metadata_value failed."); + return NULL; + } if (!title) { /* INFO: unreachable because the parser already checks the presence of the 'title' directive */ util_log(NULL, 0, LOG_ERR, "Song has no title."); @@ -461,6 +465,7 @@ pdf_filename_generate_from_songs(struct ChoSong **songs) normalized_title = str_normalize(title); filename = file_extension_replace_or_add(normalized_title, "pdf"); free(normalized_title); + free(title); return filename; } return strdup("collection-of-songs.pdf"); @@ -888,10 +893,15 @@ pdf_set_title(pdfio_file_t *pdf, struct ChoSong **songs) { // INFO: Set pdf title only if a single song exist if (songs[0] && !songs[1]) { - const char *title; - title = cho_metadata_get(songs[0]->metadata, "title"); + char *title; + struct ChoStyle *unused; + if (!cho_metadata_value(songs[0]->metadata, "title", g_config->metadata_separator, &title, &unused)) { + LOG_DEBUG("cho_metadata_value failed."); + return false; + } if (title) { pdfioFileSetTitle(pdf, title); + free(title); return true; } return false; @@ -2268,7 +2278,6 @@ pdf_content_create( struct Obj **img_objs ) { - struct ChoMetadata **m; struct ChoSection **se; struct ChoLine **li; struct PDFText ***texts; @@ -2315,41 +2324,41 @@ pdf_content_create( } cho_chords_free(chords); } - for (m = songs[s]->metadata; *m; m++) { - if (!strcmp((*m)->name, "title")) { - ctx.content->toc = erealloc(ctx.content->toc, (ctx.toc_entry+1) * sizeof(struct TocEntry *)); - ctx.content->toc[ctx.toc_entry] = emalloc(sizeof(struct TocEntry)); - ctx.content->toc[ctx.toc_entry]->title = strdup((*m)->value); - ctx.content->toc[ctx.toc_entry]->page_index = ctx.page; - ctx.content->toc[ctx.toc_entry]->page_y = ctx.y; - ctx.toc_entry++; - if (!pdf_texts_add_text(&ctx, (*m)->value, (*m)->style, A_CENTER, NUS_WESTERN_ARABIC)) { - LOG_DEBUG("pdf_texts_add_text failed."); - return false; - } - texts = &ctx.content->pages[ctx.page]->texts; - imgs = &ctx.content->pages[ctx.page]->images; - diagrams = &ctx.content->pages[ctx.page]->diagrams; - } + char *value; + struct ChoStyle *value_style; + if (!cho_metadata_value(songs[s]->metadata, "title", config->metadata_separator, &value, &value_style)) { + LOG_DEBUG("cho_metadata_value failed."); + return false; } - for (m = songs[s]->metadata; *m; m++) { - if (!strcmp((*m)->name, "subtitle")) { - /* - INFO: (*m)->style will be ignored and the config style will be - used because the subtitle style can only be manipulated from the - config file - */ - struct ChoStyle *output_style; - output_style = config->output->styles[TT_SUBTITLE]; - if (!pdf_texts_add_text(&ctx, (*m)->value, output_style, A_CENTER, NUS_WESTERN_ARABIC)) { - LOG_DEBUG("pdf_texts_add_text failed."); - return false; - } - texts = &ctx.content->pages[ctx.page]->texts; - imgs = &ctx.content->pages[ctx.page]->images; - diagrams = &ctx.content->pages[ctx.page]->diagrams; - } + ctx.content->toc = erealloc(ctx.content->toc, (ctx.toc_entry+1) * sizeof(struct TocEntry *)); + ctx.content->toc[ctx.toc_entry] = emalloc(sizeof(struct TocEntry)); + ctx.content->toc[ctx.toc_entry]->title = strdup(value); + ctx.content->toc[ctx.toc_entry]->page_index = ctx.page; + ctx.content->toc[ctx.toc_entry]->page_y = ctx.y; + ctx.toc_entry++; + if (!pdf_texts_add_text(&ctx, value, value_style, A_CENTER, NUS_WESTERN_ARABIC)) { + LOG_DEBUG("pdf_texts_add_text failed."); + return false; + } + free(value); + /* + INFO: (*m)->style will be ignored and the config style will be + used because the subtitle style can only be manipulated from the + config file + */ + // output_style = config->output->styles[TT_SUBTITLE]; + if (!cho_metadata_value(songs[s]->metadata, "subtitle", config->metadata_separator, &value, &value_style)) { + LOG_DEBUG("pdf_texts_add_text failed."); + return false; + } + if (!pdf_texts_add_text(&ctx, value, value_style, A_CENTER, NUS_WESTERN_ARABIC)) { + LOG_DEBUG("pdf_texts_add_text failed."); + return false; } + free(value); + texts = &ctx.content->pages[ctx.page]->texts; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; ctx.y -= 30.0; for (se = songs[s]->sections; *se; se++) { if ((*se)->label) { diff --git a/src/types.h b/src/types.h @@ -348,6 +348,7 @@ struct ConfigOutput { }; struct Config { + char *metadata_separator; struct ConfigOutput *output; struct ConfigParser *parser; };