diff --git a/README.md b/README.md index 6dc8351..4ac8c85 100644 --- a/README.md +++ b/README.md @@ -92,12 +92,29 @@ Result: ``` +A variable expansion can go through multiple variables until one is found. + +file.html + +``` +@!var1,var2,var3@ +@=var2 value@ +``` + +Result: + +``` +value +``` + +var1 doesn't exist, so ncdg moves on to var2, which does exist, and skips var3. + ### Automatic escaping ``` -
%\
+
@\
 #include 
-%
+@
``` Result: @@ -112,15 +129,17 @@ Note that text inside of escaped sections are not minified. ### Excluding minification ``` -%& +@& & -% +this text isn't minified +@ ``` Result: ``` & +this text isn't minified ``` Used for legacy web pages on my site that I don't want to update diff --git a/src/' b/src/' new file mode 100644 index 0000000..925b556 --- /dev/null +++ b/src/' @@ -0,0 +1,297 @@ +#include +#include +#include + +#include +#include +#include + +#include + +struct var { + struct string *var; + struct string *value; +}; + +struct expandfile { + struct string *data; + struct vector *vars; + /* This is a vector of struct var */ +}; + +struct minstate { + int ignore; + /* If this is set, then ignore the current whitespace group */ + int isspace; + /* If this is set, then we've seen whitespace on the previous char. */ +}; + +static int expandfile(struct expandfile *ret, char *filename, int level); +static int writefile(struct expandfile *file, FILE *out); +static struct string *getstring(FILE *file, char end); +static void initminstate(struct minstate *state); +static void mputs(struct minstate *state, char *s, FILE *file); +static void mputc(struct minstate *state, char c, FILE *file); +static int putvar(int i, const struct string *file, const struct vector *vars); + +int parsefile(char *template, FILE *out) { + struct expandfile expanded; + int ret; + expanded.data = newstring(); + if (expanded.data == NULL) { + ret = 1; + goto error1; + } + expanded.vars = newvector(struct var); + if (expanded.vars == NULL) { + ret = 1; + goto error2; + } + if (expandfile(&expanded, template, 0)) { + ret = 1; + goto error3; + } + ret = writefile(&expanded, out); +error3: + { + int i; + for (i = 0; i < expanded.vars->len; ++i) { + struct var var; + var = getvector(expanded.vars, struct var, i); + freestring(var.var); + freestring(var.value); + } + } + freevector(expanded.vars); +error2: + freestring(expanded.data); +error1: + return ret; +} + +static int writefile(struct expandfile *file, FILE *out) { + long i; + struct minstate s; + initminstate(&s); + for (i = 0; i < file->data->len; ++i) { + if (file->data->data[i] == ESCAPE_CHAR) { + const struct string *data = file->data; + const struct vector *vars = file->vars; + switch (data->data[++i]) { + case ESCAPE_CHAR: + mputc(&s, ESCAPE_CHAR, out); + break; + case VAR_CHAR: { + i = putvar(i, data, vars); + if (i < 0) + goto error; + long start; + int j; + char *varname; + start = ++i; + while (data->data[i] != ESCAPE_CHAR && + i < data->len) + ++i; + data->data[i] = '\0'; + varname = data->data + start; + vars = file->vars; + for (j = 0; j < vars->len; ++j) { + struct var var; + var = getvector(vars, + struct var, j); + if (strcmp(var.var->data, + varname) == 0) { + mputs(&s, var.value->data, + out); + } + } + break; + } + case AUTOESCAPE_CHAR: + for (++i; i < data->len; ++i) { + switch (data->data[i]) { + case '&': + fputs("&", out); + break; + case ';': + fputs(";", out); + case '<': + fputs("<", out); + break; + case '>': + fputs(">", out); + break; + case ESCAPE_CHAR: + if (data->data[i + 1] != ESCAPE_CHAR) + goto autoescapeend; + ++i; + /* fallthrough */ + default: + fputc(data->data[i], out); + break; + } + } +autoescapeend: + break; + case NOMINIFY_CHAR: + for (++i; data->data[i] != ESCAPE_CHAR && + i < data->len; ++i) { + if (data->data[i] == ESCAPE_CHAR) { + if (data->data[i + 1] != ESCAPE_CHAR) + break; + ++i; + } + fputc(data->data[i], out); + } + break; + } + } + else + mputc(&s, file->data->data[i], out); + } + return 0; +} + +static int expandfile(struct expandfile *ret, char *filename, int level) { + FILE *file; + int c, linenum; + file = fopen(filename, "r"); + if (file == NULL) { + fprintf(stderr, "Failed to open file %s\n", filename); + return 1; + } + linenum = 1; + if (level >= MAX_INCLUDE_DEPTH) { + + fprintf(stderr, "The include depth has reached %d, quitting\n", + level); + fclose(file); + return 1; + } + for (;;) { + c = fgetc(file); + switch (c) { + case ESCAPE_CHAR: + c = fgetc(file); + switch (c) { + case ESCAPE_CHAR: + if (appendchar(ret->data, ESCAPE_CHAR)) + goto error; + if (appendchar(ret->data, ESCAPE_CHAR)) + goto error; + break; + case VAR_CHAR: case AUTOESCAPE_CHAR: case NOMINIFY_CHAR: + if (appendchar(ret->data, ESCAPE_CHAR)) + goto error; + for (;;) { + if (c == EOF) + goto error; + if (appendchar(ret->data, c)) + goto error; + if (c == '\n') + ++linenum; + if (c == ESCAPE_CHAR) { + c = fgetc(file); + if (c == ESCAPE_CHAR) { + if (appendchar( + ret->data, c)) + goto error; + if (appendchar( + ret->data, c)) + goto error; + } + else { + ungetc(c, file); + break; + } + } + c = fgetc(file); + } + break; + case SET_CHAR: { + struct var var; + var.var = getstring(file, ' '); + var.value = getstring(file, ESCAPE_CHAR); + addvector(ret->vars, &var); + break; + } + case INCLUDE_CHAR: { + struct string *inclname; + inclname = getstring(file, ESCAPE_CHAR); + if (inclname == NULL) + goto error; + if (expandfile(ret, inclname->data, + level + 1)) + return 1; + freestring(inclname); + break; + } + default: + fprintf(stderr, "Line %d: Invalid escape %c\n", + linenum, c); + goto error; + } + break; + case '\n': + ++linenum; + goto casedefault; + case EOF: + goto end; + default: casedefault: + if (appendchar(ret->data, c)) + goto error; + break; + } + } +end: + fclose(file); + return 0; +error: + fclose(file); + return 1; +} + +static struct string *getstring(FILE *file, char end) { + struct string *ret; + int c; + ret = newstring(); + if (ret == NULL) + return NULL; + for (;;) { + c = fgetc(file); + if (c == EOF) + goto error; + if (c == end) { + if (appendchar(ret, '\0')) + goto error; + return ret; + } + if (appendchar(ret, c)) + goto error; + } +error: + freestring(ret); + return NULL; +} + +static void initminstate(struct minstate *state) { + memset(state, 0, sizeof *state); +} + +static void mputs(struct minstate *state, char *s, FILE *file) { + int i; + for (i = 0; s[i] != '\0'; ++i) + mputc(state, s[i], file); +} + +static void mputc(struct minstate *state, char c, FILE *file) { + if (isspace(c)) + state->isspace = 1; + else { + if (!state->ignore && state->isspace && c != '>') + fputc(' ', file); + fputc(c, file); + state->ignore = c == '<'; + state->isspace = 0; + } +} diff --git a/src/include/config.h b/src/include/config.h index 6dfd872..a5904c8 100644 --- a/src/include/config.h +++ b/src/include/config.h @@ -4,5 +4,6 @@ #define SET_CHAR '=' #define AUTOESCAPE_CHAR '\\' #define NOMINIFY_CHAR '&' +#define SEPARATOR_CHAR ',' #define MAX_INCLUDE_DEPTH 10 diff --git a/src/parse.c b/src/parse.c index 9ed2ea8..4149440 100644 --- a/src/parse.c +++ b/src/parse.c @@ -32,6 +32,8 @@ static struct string *getstring(FILE *file, char end); static void initminstate(struct minstate *state); static void mputs(struct minstate *state, char *s, FILE *file); static void mputc(struct minstate *state, char c, FILE *file); +static long putvar(long i, struct minstate *s, FILE *out, + const struct string *file, const struct vector *vars); int parsefile(char *template, FILE *out) { struct expandfile expanded; @@ -80,29 +82,11 @@ static int writefile(struct expandfile *file, FILE *out) { case ESCAPE_CHAR: mputc(&s, ESCAPE_CHAR, out); break; - case VAR_CHAR: { - long start; - int j; - char *varname; - start = ++i; - while (data->data[i] != ESCAPE_CHAR && - i < data->len) - ++i; - data->data[i] = '\0'; - varname = data->data + start; - vars = file->vars; - for (j = 0; j < vars->len; ++j) { - struct var var; - var = getvector(vars, - struct var, j); - if (strcmp(var.var->data, - varname) == 0) { - mputs(&s, var.value->data, - out); - } - } + case VAR_CHAR: + i = putvar(i, &s, out, data, vars); + if (i < 0) + return 1; break; - } case AUTOESCAPE_CHAR: for (++i; i < data->len; ++i) { switch (data->data[i]) { @@ -130,7 +114,8 @@ static int writefile(struct expandfile *file, FILE *out) { autoescapeend: break; case NOMINIFY_CHAR: - for (++i; data->data[i] != ESCAPE_CHAR && i < data->len; ++i) { + for (++i; data->data[i] != ESCAPE_CHAR && + i < data->len; ++i) { if (data->data[i] == ESCAPE_CHAR) { if (data->data[i + 1] != ESCAPE_CHAR) break; @@ -290,3 +275,35 @@ static void mputc(struct minstate *state, char c, FILE *file) { state->isspace = 0; } } + +static long putvar(long i, struct minstate *s, FILE *out, + const struct string *file, const struct vector *vars) { + long start; + for (;;) { + int j; + start = ++i; + while (file->data[i] != SEPARATOR_CHAR && + file->data[i] != ESCAPE_CHAR && + i < file->len) + ++i; + if (i >= file->len) + return -1; + for (j = 0; j < vars->len; ++j) { + const struct var var = getvector(vars, struct var, j); + if (var.var->len - 1 != i - start) + continue; + /* -1 because var.var->len includes the \0. */ + if (memcmp(var.var->data, file->data + start, + i - start) == 0) { + mputs(s, var.value->data, out); + goto end; + } + } + } +end: + while (file->data[i] != ESCAPE_CHAR && i < file->len) + ++i; + if (i == file->len) + return -1; + return i; +}