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:
| M | chordpro.c | | | 217 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------- |
| M | chordpro.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