lorid

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

commit 4c38fdb59098d2183a8ced3cc3b42ada169cd508
parent 8f902ca4ceced15f3e194cf40d7d7cc89fe5e7f6
Author: nibo <nibo@relim.de>
Date:   Fri, 31 Jan 2025 12:00:10 +0100

Improve line continuation with a backslash

Put a backslash as the last character in the line
to continue that line. This should work on every
line in the file except for a comment line. But
there are probably still cases in doesn't work yet.

Diffstat:
Mchordpro.c | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mchordpro.h | 3++-
2 files changed, 158 insertions(+), 62 deletions(-)

diff --git a/chordpro.c b/chordpro.c @@ -21,14 +21,15 @@ static const char *chord_qualifiers[] = { "m", "", "+", "°" }; static const char *state_enums[] = { "STATE_LYRICS", + "STATE_BACKSLASH", "STATE_DIRECTIVE_NAME", "STATE_DIRECTIVE_VALUE", "STATE_CHORD", "STATE_ANNOTATION", "STATE_TAB", + "STATE_MARKUP_TAG", "STATE_MARKUP_TAG_START", "STATE_MARKUP_TAG_END", - "STATE_MARKUP_TAG", "STATE_MARKUP_ATTR_NAME", "STATE_MARKUP_ATTR_VALUE", "STATE_COMMENT" @@ -3803,7 +3804,9 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) char custom_directive[64]; char *label, *metadata_value, *stripped_directive_value; enum State state = STATE_LYRICS; - enum State prev_state = STATE_LYRICS; + enum State state_before_comment = STATE_LYRICS; + enum State state_before_tag = STATE_LYRICS; + enum State state_before_backslash = STATE_LYRICS; int dn = 0; int dv = 0; int ch = 0; @@ -3856,15 +3859,14 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) if (buf == '\n') { g_line_number++; } - // printf("state: %s, prev_state: %s, prev_buf: %c, buf: %c\n", state_enums[state], state_enums[prev_state], prev_buf, buf); // printf("state: %s, buf: %c\n", state_enums[state], buf); if (buf == '\r') { continue; } switch (state) { - case STATE_LYRICS: + case STATE_LYRICS: { if (prev_buf == '\n' && buf == '#') { - prev_state = STATE_LYRICS; + state_before_comment = STATE_LYRICS; state = STATE_COMMENT; break; } @@ -3891,14 +3893,15 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) te = 0; (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); (*lines)[li]->items[ly] = cho_line_item_new(); - prev_state = STATE_LYRICS; + state_before_tag = STATE_LYRICS; state = STATE_MARKUP_TAG; break; } if (buf == '\n') { if (prev_buf == '\\') { - // INFO: This will later overwrite the backslash - te--; + state_before_backslash = STATE_LYRICS; + state = STATE_BACKSLASH; + te--; // INFO: This will later overwrite the backslash break; } if (ta > -1 && !tags[ta]->is_closed && strcmp(tags[ta]->name, "img")) { @@ -3948,7 +3951,20 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) (*lines)[li]->items[ly]->u.text->text[te] = buf; te++; break; - case STATE_DIRECTIVE_NAME: + } + case STATE_BACKSLASH: { + if (!is_whitespace(buf)) { + if (fseek(fp, -1, SEEK_CUR)) { + LOG_DEBUG("fseek failed."); + return NULL; + } + printf("Hopefully not losing byte 0x%02X|%c\n", buf, buf); + state = state_before_backslash; + break; + } + break; + } + case STATE_DIRECTIVE_NAME: { if (buf == '}') { directive_name[dn] = 0; dn = 0; @@ -3958,10 +3974,10 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) directive_name, cho_debug_dtype(directive->dtype), cho_debug_the_stype(directive->stype), cho_debug_the_pos(directive->position) ); */ switch (directive->dtype) { - case DT_ENVIRONMENT: + case DT_ENVIRONMENT: { g_current_ttype = directive->ttype; switch (directive->position) { - case POS_START: + case POS_START: { if (directive->stype == ST_CUSTOM) { memset(custom_directive, 0, sizeof(custom_directive)); strcpy(custom_directive, &directive_name[9]); @@ -3983,7 +3999,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) (*lines)[li]->items[ly] = cho_line_item_new(); songs[so]->present_text_types[directive->ttype] = true; break; - case POS_END: + } + case POS_END: { if (directive->stype == songs[so]->sections[se]->type) { if (directive->stype == ST_CUSTOM) { if (strcmp(custom_directive, &directive_name[7]) != 0) { @@ -4006,7 +4023,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) (*lines)[li]->items[ly] = cho_line_item_new(); } break; - case POS_NO: + } + case POS_NO: { /* INFO: {chorus} */ chorus = cho_find_previous_chorus(songs[so]->sections, se); if (chorus) { @@ -4087,11 +4105,13 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) te += strlen(label); } break; + } default: cho_log(LOG_ERR, "Invalid position value '%d'.", directive->position); return NULL; } break; + } case DT_METADATA: cho_log(LOG_WARN, "Ignoring metadata directive '%s' because it has no value.", directive_name); break; @@ -4101,7 +4121,7 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) case DT_IMAGE: cho_log(LOG_ERR, "Directive 'image' has no value."); return NULL; - case DT_PREAMBLE: + case DT_PREAMBLE: { // INFO: The only preamble directive is 'new_song' cho_line_item_free((*lines)[li]->items[ly]); free((*lines)[li]->items); @@ -4134,7 +4154,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) (*lines)[li]->items = emalloc(sizeof(struct ChoLineItem *)); (*lines)[li]->items[ly] = cho_line_item_new(); break; - case DT_FONT: + } + case DT_FONT: { sprop.ttype = directive->ttype; sprop.type = directive->sprop; switch (directive->sprop) { @@ -4156,7 +4177,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) return NULL; } break; - case DT_CHORD: + } + case DT_CHORD: { switch (directive->ctype) { case TRANSPOSE: g_transpose--; @@ -4167,6 +4189,7 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) break; } break; + } case DT_OUTPUT: if (directive->btype != -1) { (*lines)[li]->btype = directive->btype; @@ -4196,6 +4219,12 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) return NULL; } if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_DIRECTIVE_NAME; + state = STATE_BACKSLASH; + dn--; + break; + } cho_log(LOG_ERR, "Can't have a newline in a directive name."); return NULL; } @@ -4208,7 +4237,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) directive_name[dn] = buf; dn++; break; - case STATE_DIRECTIVE_VALUE: + } + case STATE_DIRECTIVE_VALUE: { if (buf == '}') { directive_value[dv] = 0; dv = 0; @@ -4219,13 +4249,13 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) directive_name, dtype(directive->dtype), the_stype(directive->stype), pos(directive->position) ); */ switch (directive->dtype) { - case DT_ENVIRONMENT: + case DT_ENVIRONMENT: { if (strlen(stripped_directive_value) > 0) { songs[so]->present_text_types[TT_LABEL] = true; } g_current_ttype = directive->ttype; switch (directive->position) { - case POS_START: + case POS_START: { if (directive->stype == ST_CUSTOM) { memset(custom_directive, 0, sizeof(custom_directive)); strcpy(custom_directive, &directive_name[9]); @@ -4264,7 +4294,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) (*lines)[li]->items[ly] = cho_line_item_new(); songs[so]->present_text_types[directive->ttype] = true; break; - case POS_END: + } + case POS_END: { if (directive->stype == songs[so]->sections[se]->type) { cho_line_item_free((*lines)[li]->items[ly]); free((*lines)[li]->items); @@ -4282,7 +4313,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) (*lines)[li]->items[ly] = cho_line_item_new(); } break; - case POS_NO: + } + case POS_NO: { /* INFO: {chorus: ...} */ label = strdup(stripped_directive_value); chorus = cho_find_previous_chorus(songs[so]->sections, se); @@ -4378,12 +4410,14 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) te += strlen(label); } break; + } default: cho_log(LOG_ERR, "Invalid position value '%d'.", directive->position); return NULL; } break; - case DT_METADATA: + } + case DT_METADATA: { metadata_value = strdup(stripped_directive_value); if (strlen(metadata_value) == 0) { cho_log(LOG_WARN, "Ignoring metadata directive '%s' because it has no value.", directive_name); @@ -4432,7 +4466,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) } m++; break; - case DT_FORMATTING: + } + case DT_FORMATTING: { if ((*lines)[li]->items[ly]->is_text) { (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); (*lines)[li]->items[ly]->u.text->text[te] = 0; @@ -4456,7 +4491,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) te += strlen(stripped_directive_value); songs[so]->present_text_types[directive->ttype] = true; break; - case DT_IMAGE: + } + case DT_IMAGE: { if (strstr(directive_value, "=")) { image = cho_image_directive_parse(directive_value); if (!image) { @@ -4485,10 +4521,11 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) (*lines)[li]->items[ly]->u.image = image; } break; + } case DT_PREAMBLE: cho_log(LOG_ERR, "Preamble directive '%s' can't have a value.", directive_name); return NULL; - case DT_FONT: + case DT_FONT: { sprop.ttype = directive->ttype; char *dir_value = strdup(stripped_directive_value); switch (directive->sprop) { @@ -4528,7 +4565,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) } free(dir_value); break; - case DT_CHORD: + } + case DT_CHORD: { switch (directive->ctype) { case TRANSPOSE: if (!transposition_parse(directive_value, &transpose)) { @@ -4555,6 +4593,7 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) break; } break; + } case DT_OUTPUT: cho_log(LOG_ERR, "Directive '%s' can't have a value.", directive_name); return NULL; @@ -4576,7 +4615,7 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) break; } if (buf == '<') { - prev_state = state; + state_before_tag = STATE_DIRECTIVE_VALUE; state = STATE_MARKUP_TAG; break; } @@ -4585,6 +4624,12 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) return NULL; } if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_DIRECTIVE_VALUE; + state = STATE_BACKSLASH; + dv--; + break; + } cho_log(LOG_ERR, "Newline character inside a directive value is invalid."); return NULL; } @@ -4595,7 +4640,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) directive_value[dv] = buf; dv++; break; - case STATE_CHORD: + } + case STATE_CHORD: { if (buf == ']') { chord[ch] = 0; ch = 0; @@ -4642,6 +4688,12 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) break; } if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_CHORD; + state = STATE_BACKSLASH; + ch--; + break; + } cho_log(LOG_ERR, "Newline character inside a chord is invalid."); return NULL; } @@ -4657,14 +4709,15 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) (*lines)[li]->text_above[c]->u.chord = cho_chord_new(); is_chord_already_initialized = true; } - prev_state = STATE_CHORD; + state_before_tag = STATE_CHORD; state = STATE_MARKUP_TAG; break; } chord[ch] = buf; ch++; break; - case STATE_ANNOTATION: + } + case STATE_ANNOTATION: { if (buf == ']') { (*lines)[li]->text_above[c]->u.annot->text = erealloc((*lines)[li]->text_above[c]->u.annot->text, (ann+1) * sizeof(char)); (*lines)[li]->text_above[c]->u.annot->text[ann] = 0; @@ -4676,11 +4729,17 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) break; } if (buf == '<') { - prev_state = STATE_ANNOTATION; + state_before_tag = STATE_ANNOTATION; state = STATE_MARKUP_TAG; break; } if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_ANNOTATION; + state = STATE_BACKSLASH; + ann--; + break; + } cho_log(LOG_ERR, "Newline character inside an annotation is invalid."); return NULL; } @@ -4688,10 +4747,11 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) (*lines)[li]->text_above[c]->u.annot->text[ann] = buf; ann++; break; - case STATE_TAB: + } + case STATE_TAB: { // INFO: similar to STATE_LYRICS but without markup and directives if (prev_buf == '\n' && buf == '#') { - prev_state = STATE_TAB; + state_before_comment = STATE_TAB; state = STATE_COMMENT; break; } @@ -4760,6 +4820,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) } if (buf == '\n') { if (prev_buf == '\\') { + state_before_backslash = STATE_TAB; + state = STATE_BACKSLASH; // INFO: This will later overwrite the backslash te--; break; @@ -4811,8 +4873,23 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) (*lines)[li]->items[ly]->u.text->text[te] = buf; te++; break; - case STATE_MARKUP_TAG_START: - MARKUP_TAG_START: + } + case STATE_MARKUP_TAG: { + if (buf == '/') { + state = STATE_MARKUP_TAG_END; + break; + } + ta++; + tags = erealloc(tags, (ta+1) * sizeof(struct Tag *)); + tags[ta] = cho_tag_new(); + state = STATE_MARKUP_TAG_START; + if (fseek(fp, -1, SEEK_CUR)) { + LOG_DEBUG("fseek failed."); + return NULL; + } + break; + } + case STATE_MARKUP_TAG_START: { if (buf == '>') { tag_start[t] = 0; t = 0; @@ -4827,7 +4904,7 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) return NULL; } tags[ta]->style = tag_style; - switch (prev_state) { + switch (state_before_tag) { case STATE_LYRICS: cho_style_free((*lines)[li]->items[ly]->u.text->style); (*lines)[li]->items[ly]->u.text->style = cho_style_copy(tag_style); @@ -4855,11 +4932,11 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) directive_has_tag = true; break; default: - cho_log(LOG_ERR, "Invalid prev_state '%s'.", state_enums[prev_state]); + cho_log(LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[state_before_tag]); return NULL; } memset(tag_start, 0, strlen(tag_start)); - state = prev_state; + state = state_before_tag; break; } if (is_whitespace(buf)) { @@ -4872,6 +4949,12 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) break; } if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_MARKUP_TAG_START; + state = STATE_BACKSLASH; + t--; + break; + } cho_log(LOG_ERR, "Newline character inside a tag name is invalid."); return NULL; } @@ -4882,18 +4965,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) tag_start[t] = buf; t++; break; - case STATE_MARKUP_TAG: - if (buf == '/') { - state = STATE_MARKUP_TAG_END; - break; - } - ta++; - tags = erealloc(tags, (ta+1) * sizeof(struct Tag *)); - tags[ta] = cho_tag_new(); - state = STATE_MARKUP_TAG_START; - // INFO: If we don't use 'goto' we loose the first character of the start tag name - goto MARKUP_TAG_START; - case STATE_MARKUP_TAG_END: + } + case STATE_MARKUP_TAG_END: { if (buf == '>') { tag_end[t] = 0; t = 0; @@ -4902,10 +4975,16 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) return NULL; } memset(tag_end, 0, strlen(tag_end)); - state = prev_state; + state = state_before_tag; break; } if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_MARKUP_TAG_END; + state = STATE_BACKSLASH; + t--; + break; + } cho_log(LOG_ERR, "Newline character inside a tag name is invalid."); return NULL; } @@ -4916,7 +4995,8 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) tag_end[t] = buf; t++; break; - case STATE_MARKUP_ATTR_NAME: + } + case STATE_MARKUP_ATTR_NAME: { if (buf == '=') { tags[ta]->attrs[at]->name = erealloc(tags[ta]->attrs[at]->name, (atn+1) * sizeof(char)); tags[ta]->attrs[at]->name[atn] = 0; @@ -4970,7 +5050,7 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) return NULL; } tags[ta]->style = tag_style; - switch (prev_state) { + switch (state_before_tag) { case STATE_LYRICS: cho_style_free((*lines)[li]->items[ly]->u.text->style); (*lines)[li]->items[ly]->u.text->style = cho_style_copy(tag_style); @@ -4987,13 +5067,13 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) directive_has_tag = true; break; default: - cho_log(LOG_ERR, "Invalid prev_state '%s'.", state_enums[prev_state]); + cho_log(LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[state_before_tag]); return NULL; } } at = 0; memset(tag_start, 0, strlen(tag_start)); - state = prev_state; + state = state_before_tag; break; } else { tags[ta]->attrs[at]->name = erealloc(tags[ta]->attrs[at]->name, (atn+1) * sizeof(char)); @@ -5004,6 +5084,12 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) } } if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_MARKUP_ATTR_NAME; + state = STATE_BACKSLASH; + atn--; + break; + } cho_log(LOG_ERR, "Newline character inside an tag attribute name is invalid."); return NULL; } @@ -5011,8 +5097,15 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) tags[ta]->attrs[at]->name[atn] = buf; atn++; break; - case STATE_MARKUP_ATTR_VALUE: + } + case STATE_MARKUP_ATTR_VALUE: { if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_MARKUP_ATTR_VALUE; + state = STATE_BACKSLASH; + atv--; + break; + } cho_log(LOG_ERR, "Newline character inside an attribute value is invalid."); return NULL; } @@ -5063,7 +5156,7 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) return NULL; } tags[ta]->style = tag_style; - switch (prev_state) { + switch (state_before_tag) { case STATE_LYRICS: cho_style_free((*lines)[li]->items[ly]->u.text->style); (*lines)[li]->items[ly]->u.text->style = cho_style_copy(tag_style); @@ -5080,14 +5173,14 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) directive_has_tag = true; break; default: - cho_log(LOG_ERR, "Invalid prev_state '%s'.", state_enums[prev_state]); + cho_log(LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[state_before_tag]); return NULL; } } at = 0; avs = -1; memset(tag_start, 0, strlen(tag_start)); - state = prev_state; + state = state_before_tag; break; } if ( @@ -5109,13 +5202,15 @@ cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) tags[ta]->attrs[at]->value[atv] = buf; atv++; break; - case STATE_COMMENT: + } + case STATE_COMMENT: { if (buf == '\n') { - state = prev_state; + state = state_before_comment; break; } break; } + } prev_buf = buf; } else { break; diff --git a/chordpro.h b/chordpro.h @@ -174,14 +174,15 @@ enum SectionType { enum State { STATE_LYRICS, + STATE_BACKSLASH, STATE_DIRECTIVE_NAME, STATE_DIRECTIVE_VALUE, STATE_CHORD, STATE_ANNOTATION, STATE_TAB, + STATE_MARKUP_TAG, STATE_MARKUP_TAG_START, STATE_MARKUP_TAG_END, - STATE_MARKUP_TAG, STATE_MARKUP_ATTR_NAME, STATE_MARKUP_ATTR_VALUE, STATE_COMMENT