commit c1276b3a52cf3c46b6a6efcf7e8dead5bb721890
parent e9915c1e0bb9e1a6960dcb194876b593a887c26f
Author: nibo <nibo@relim.de>
Date: Fri, 7 Mar 2025 19:02:12 +0100
Support multiple metadata values
Diffstat:
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;
};