#include #include #include #include #include /* macros */ #define BODY_START_SIZE 26624 /* 26 x 1024 byte = 26 KiB */ #define CLS_MAX 16 #define DATE_LEN 10 #define FAVICON "data:image/gif;base64,R0lGODlhEAAQAKEAAAAAcP///wAAcAAAcCH5BAEKAAIALAAAAAAQABAAQAIwlBWZxxwAQWjtTQRvlZhTnGSfYT3eZ3WaE4mjGa1UasoH7c6tzYanfqmhJMKXglcAADs=" #define FIGCAPTION_TAG_LEN 12 #define INDEX_START 16 #define LANG_LEN 2 #define LINE_MAX 1000 #define LINE_START 80 #define NOTES_START 8 #define NOTES_MAX_DIGITS 3 typedef struct { int indentation; char *name; } closure; typedef struct { int index; int size; closure *tags; } closures; typedef enum { tag_content = 0, tag_attribute = 1 } html_val_type; typedef struct { size_t size; char *str; } string; typedef struct { char *desc; char *id; char heading; } index_title; typedef struct { int index; int size; index_title *titles; } index; typedef struct { char *desc; char *href; } note; typedef struct { int index; note *notes; int size; } references; /* function declarations */ static void add_closure(closures *dst, const char *tag, const int ind); static void add_h_to_index(const char heading, const char *id); static int add_note(const char *href, const char *desc); static void cmd(char *line); static void cmd_close(void); static void cmd_a(const char *line); static void cmd_au_de_ti(char **str, const char *line); static void cmd_br(void); static void cmd_dt(const char *line); static void cmd_em(const char *line); static void cmd_fi(const char *line); static void cmd_h(const char *line); static void cmd_hr(void); static void cmd_i(const char *line); static void cmd_in(const char *line); static void cmd_la(const char *line); static void cmd_le(void); static void cmd_ls(void); static void cmd_no(const char *line); static void cmd_p(void); static void cmd_pd(const char *line); static void cmd_q(const char *line); static void cmd_st(const char *line); static void cmd_tm(const char *line); static void cmd_ur(const char *line); static void findent(const int ind, FILE *f); static void footer(void); static void footnotes(void); static int fputs_html(const char *str, FILE *f, const html_val_type t); static size_t fread_line(FILE *file, char **str, size_t *len); static void free_closures(closures *dst); static void indent_str(char **str, const int ind); static void nav_index(FILE *f); static void page_head(void); static void pop_closure(closures *dst); static void print_help(void); static void print_usage(void); static void print_version(void); static void *safe_malloc(const size_t size); static void *safe_realloc(void *ptr, const size_t size); static size_t stread_param(const char *src, char **str); static char *string_cat(string *dst, const char *src); static char *string_cat_html(string *dst, const char *src, const html_val_type t); static void string_enlarge(string *old, const size_t size); static int string_putc(string *dst, const char c); /* variables */ char *author = NULL; string body; char *description = NULL; int figcaption_presence = 0; string head; size_t heading_position = 0; int img_presence = 0; int indentation = 2; char *index_heading = NULL; char *index_heading_id = NULL; size_t index_position = 0; char *language = NULL; int licence_container = 0; index page_index; char *pub_date = NULL; references sitography; int space = 0; char tag_closure[CLS_MAX]; char *title = NULL; /* function implementations */ void add_closure(closures *dst, const char *name, const int ind) { closure *tag; if (dst->index == dst->size) { dst->size *= 2; dst->tags = safe_realloc(dst->tags, sizeof(closure) * dst->size); } tag = dst->tags + dst->index; tag->indentation = ind; tag->name = safe_malloc(sizeof(char) * (strlen(name) + 1)); strcpy(tag->name, name); dst->index++; } void add_h_to_index(const char heading, const char *id) { index_title *new; if (page_index.index == page_index.size) { page_index.size *= 2; page_index.titles = safe_realloc(page_index.titles, sizeof(index_title) * page_index.size); } new = page_index.titles + page_index.index; new->desc = NULL; new->heading = heading; new->id = safe_malloc(sizeof(char) * (strlen(id) + 1)); strcpy(new->id, id); heading_position = strlen(body.str); } int add_note(const char *href, const char *desc) { note *new; if (sitography.index == sitography.size) { sitography.size *= 2; sitography.notes = safe_realloc(sitography.notes, sizeof(note) * sitography.size); } new = sitography.notes + sitography.index; new->href = safe_malloc(sizeof(char) * (strlen(href) + 1)); strcpy(new->href, href); new->desc = safe_malloc(sizeof(char) * (strlen(desc) + 1)); strcpy(new->desc, desc); sitography.index++; return sitography.index; } void cmd(char *line) { char *end; size_t cmdlen; if (!(end = strchr(line, ' '))) end = line + strlen(line); cmdlen = end - ++line; if (!strncmp(line, "A", cmdlen)) cmd_a(line); else if (!strncmp(line, "AU", cmdlen)) cmd_au_de_ti(&author, line); else if (!strncmp(line, "BR", cmdlen)) cmd_br(); else if (!strncmp(line, "DE", cmdlen)) cmd_au_de_ti(&description, line); else if (!strncmp(line, "DT", cmdlen)) cmd_dt(line); else if (!strncmp(line, "EM", cmdlen)) cmd_em(line); else if (!strncmp(line, "FI", cmdlen)) cmd_fi(line); else if (!strncmp(line, "H1", cmdlen)) cmd_h(line); else if (!strncmp(line, "H2", cmdlen)) cmd_h(line); else if (!strncmp(line, "H3", cmdlen)) cmd_h(line); else if (!strncmp(line, "H4", cmdlen)) cmd_h(line); else if (!strncmp(line, "H5", cmdlen)) cmd_h(line); else if (!strncmp(line, "H6", cmdlen)) cmd_h(line); else if (!strncmp(line, "HR", cmdlen)) cmd_hr(); else if (!strncmp(line, "I", cmdlen)) cmd_i(line); else if (!strncmp(line, "IN", cmdlen)) cmd_in(line); else if (!strncmp(line, "LA", cmdlen)) cmd_la(line); else if (!strncmp(line, "LE", cmdlen)) cmd_le(); else if (!strncmp(line, "LS", cmdlen)) cmd_ls(); else if (!strncmp(line, "NO", cmdlen)) cmd_no(line); else if (!strncmp(line, "P", cmdlen)) cmd_p(); else if (!strncmp(line, "PD", cmdlen)) cmd_pd(line); else if (!strncmp(line, "Q", cmdlen)) cmd_q(line); else if (!strncmp(line, "ST", cmdlen)) cmd_st(line); else if (!strncmp(line, "TI", cmdlen)) cmd_au_de_ti(&title, line); else if (!strncmp(line, "TM", cmdlen)) cmd_tm(line); else if (!strncmp(line, "UR", cmdlen)) cmd_ur(line); } void cmd_a(const char *line) { char *href, *in, *title; href = NULL; in = NULL; title = NULL; /* move past "A" */ line += 1; /* move past arguments, once found them */ line += stread_param(line, &href); line += stread_param(line, &in); line += stread_param(line, &title); if (space) string_cat(&body, " "); string_cat(&body, ""); string_cat_html(&body, in, tag_attribute); string_cat(&body, ""); free(href); free(in); free(title); } void cmd_au_de_ti(char **str, const char *line) { /* move past command */ line += 2; /* move past ' ', if present */ if (line[0] == ' ') line++; if (*str != NULL) free(*str); *str = safe_malloc(sizeof(char) * (strlen(line) + 1)); strcpy(*str, line); } void cmd_br(void) { string_cat(&body, "
"); space = 0; } void cmd_close(void) { char *ind, *line; index_title *title; size_t len; ind = NULL; len = 0; line = NULL; title = NULL; if (!strcmp(tag_closure, "")) { len = strlen(body.str); if (len >= FIGCAPTION_TAG_LEN + indentation) line = body.str + len - FIGCAPTION_TAG_LEN; if (line != NULL && !strcmp(line, "
")) { *(line - indentation) = '\0'; } else { figcaption_presence = 1; string_cat(&body, "
\n"); } indent_str(&ind, --indentation); string_cat(&body, ind); free(ind); } if ((!strcmp(tag_closure, "") || !strcmp(tag_closure, "") || !strcmp(tag_closure, "") || !strcmp(tag_closure, "")) && index_position && heading_position) { title = page_index.titles + page_index.index; title->desc = safe_malloc(sizeof(char) * (strlen(body.str + heading_position) + 1)); strcpy(title->desc, body.str + heading_position); page_index.index++; heading_position = 0; } string_cat(&body, tag_closure); string_cat(&body, "\n"); strcpy(tag_closure, ""); space = 0; } void cmd_dt(const char *line) { char *datetime, *in; datetime = NULL; in = NULL; /* move past "DT" */ line += 2; /* move past arguments, once found them */ line += stread_param(line, &datetime); line += stread_param(line, &in); if (space) string_cat(&body, " "); string_cat(&body, ""); free(datetime); free(in); } void cmd_em(const char *line) { /* move past "EM" */ line += 2; /* move past ' ', if present */ if (line[0] == ' ') line++; if (space) string_cat(&body, " "); string_cat(&body, ""); string_cat(&body, line); string_cat(&body, ""); } void cmd_fi(const char *line) { char *ind, *src; ind = NULL; src = NULL; /* move past "FI" */ line += 2; /* move past argument, once found it */ line += stread_param(line, &src); indent_str(&ind, indentation); string_cat(&body, ind); string_cat(&body, "
\n"); indent_str(&ind, ++indentation); string_cat(&body, ind); string_cat(&body, "\"\"\n"); img_presence = 1; string_cat(&body, ind); string_cat(&body, "
"); strcpy(tag_closure, "
"); free(ind); free(src); } void cmd_h(const char *line) { const char *id; id = NULL; string_cat(&body, "\t\t 4 && line[2] == ' ' && line[3] != '\0') { id = line + 3; string_cat(&body, " id=\""); string_cat_html(&body, id, tag_attribute); string_cat(&body, "\""); } string_cat(&body, ">"); strcpy(tag_closure, ""); } void cmd_i(const char *line) { /* move past "I" */ line += 1; /* move past ' ', if present */ if (line[0] == ' ') line++; if (space) string_cat(&body, " "); string_cat(&body, ""); string_cat(&body, line); string_cat(&body, ""); space = 1; } void cmd_in(const char *line) { /* move past "IN" */ line += 2; /* move past arguments, once found them */ line += stread_param(line, &index_heading); line += stread_param(line, &index_heading_id); if (!strcmp(index_heading, "")) { free(index_heading); index_heading = NULL; } if (!strcmp(index_heading_id, "")) { free(index_heading_id); index_heading_id = NULL; } index_position = strlen(body.str); } void cmd_la(const char *line) { /* move past "LA" */ line += 2; /* move past ' ', if present */ if (line[0] == ' ') line++; if (strlen(line) != LANG_LEN) return; if (language == NULL) { language = safe_malloc(sizeof(char) * (LANG_LEN + 1)); language[LANG_LEN] = '\0'; } strncpy(language, line, LANG_LEN); } void cmd_le(void) { char *ind; ind = NULL; indentation--; indent_str(&ind, indentation); string_cat(&body, ind); string_cat(&body, "\n"); free(ind); } void cmd_ls(void) { char *ind; ind = NULL; licence_container = 1; indent_str(&ind, indentation); string_cat(&body, ind); string_cat(&body, "
\n"); indentation++; free(ind); ind = NULL; indent_str(&ind, indentation); string_cat(&body, ind); string_cat(&body, "
\n"); free(ind); } void cmd_no(const char *line) { char *desc, *href, *title; char index[NOTES_MAX_DIGITS + 1]; desc = NULL; href = NULL; title = NULL; /* move past "NO" */ line += 2; /* move past arguments, once found them */ line += stread_param(line, &href); line += stread_param(line, &desc); line += stread_param(line, &title); if (space) string_cat(&body, " "); sprintf(index, "%u", add_note(href, desc)); string_cat(&body, "["); string_cat(&body, index); string_cat(&body, "]"); free(desc); free(href); free(title); } void cmd_p(void) { char *ind; ind = NULL; indent_str(&ind, indentation); string_cat(&body, ind); string_cat(&body, "

"); strcpy(tag_closure, "

"); free(ind); } void cmd_pd(const char *line) { /* move past "PD" */ line += 2; /* move past ' ', if present */ if (line[0] == ' ') line++; if (strlen(line) != DATE_LEN) return; if (pub_date == NULL) { pub_date = safe_malloc(sizeof(char) * (DATE_LEN + 1)); pub_date[DATE_LEN] = '\0'; } strncpy(pub_date, line, DATE_LEN); } void cmd_q(const char *line) { char *cite; cite = NULL; /* move past "Q" */ line += 1; /* move past argument, if found it */ line += stread_param(line, &cite); if (space) string_cat(&body, " "); string_cat(&body, ""); space = 0; strcpy(tag_closure, "

"); free(cite); } void cmd_st(const char *line) { size_t len; len = strlen(body.str); /* move past "ST" */ line += 2; /* move past space, if present */ if (line[0] == ' ') line++; if (space && len > 0 && body.str[len - 1] != '\'') string_cat(&body, " "); string_cat(&body, ""); string_cat(&body, line); string_cat(&body, ""); } void cmd_tm(const char *line) { char *date, *hours, *in; /* move past "TM" */ line += 2; /* move past arguments, once found them */ line += stread_param(line, &date); line += stread_param(line, &hours); line += stread_param(line, &in); if (space) string_cat(&body, " "); string_cat(&body, ""); free(date); free(hours); free(in); } void cmd_ur(const char *line) { /* move past "UR" */ line += 2; /* move past ' ', if present */ if (line[0] == ' ') line++; if (space) string_cat(&body, " "); string_cat(&body, "<"); string_cat_html(&body, line, tag_content); string_cat(&body, ">"); } void findent(const int ind, FILE *f) { char *str; str = NULL; indent_str(&str, ind); fputs(str, f); free(str); } void footer(void) { size_t max; struct tm *now; char str[16]; time_t s; int tm_mday, tm_mon, tm_year; struct tm tm_pd = { 0 }; max = 16; string_cat(&body, "\t\t
\n"); if (pub_date != NULL) { string_cat(&body, "\t\t\t

Pubblicazione:

\n"); free(pub_date); } s = time(NULL); now = localtime(&s); string_cat(&body, "\t\t\t

Revisione:

\n"); string_cat(&body, "\t\t
\n"); } void footnotes(void) { note *cur; int i; size_t dig_num, x; char id[NOTES_MAX_DIGITS + 1]; char pad[6 * (NOTES_MAX_DIGITS - 1) + 1]; if (sitography.index == 0) return; string_cat(&body, "\t\t
\n"); string_cat(&body, "\t\t
\n"); string_cat(&body, "\t\t\t

Sitografia

\n"); sprintf(id, "%u", sitography.index); dig_num = strlen(id); for (i = 0; i < sitography.index; i++) { cur = sitography.notes + i; sprintf(id, "%u", i + 1); string_cat(&body, "\t\t\t

"); strcpy(pad, ""); for (x = strlen(id); x < dig_num; x++) strcat(pad, " "); string_cat(&body, pad); string_cat(&body, ""); string_cat(&body, id); string_cat(&body, " "); if (strcmp(cur->desc, "")) { string_cat_html(&body, cur->desc, tag_content); string_cat(&body, " "); } string_cat(&body, "<href); string_cat(&body, "\">"); string_cat_html(&body, cur->href, tag_content); string_cat(&body, ">

\n"); free(cur->desc); free(cur->href); } free(sitography.notes); string_cat(&body, "\t\t
\n"); } int fputs_html(const char *str, FILE *f, const html_val_type t) { size_t i, len; int res; len = strlen(str); res = 0; for (i = 0; i < len; i++) { if (res == EOF) return EOF; if (t == tag_attribute && str[i] == '"') res = fputs(""", f); else if (str[i] == '&') res = fputs("&", f); else if (str[i] == '<') res = fputs("<", f); else if (str[i] == '>') res = fputs(">", f); else res = fputc(str[i], f); } return 1; } size_t fread_line(FILE *file, char **str, size_t *len) { int c; size_t i; if (*str == NULL) { *len = LINE_START + 1; *str = safe_malloc(sizeof(char) * *len); } i = 0; while ((c = fgetc(file)) && c != '\n' && c != EOF && i < LINE_MAX) { if (i >= *len - 1) { *len *= 2; *str = safe_realloc(*str, sizeof(char) * *len); } (*str)[i] = c; i++; } (*str)[i] = '\0'; *len = i; return c == EOF ? EOF : i; } void free_closures(closures *dst) { int i; for (i = 0; i < dst->index; i++) free(dst->tags[i].name); } void indent_str(char **str, const int ind) { int i; if (*str == NULL || strlen(*str) <= ind) { free(*str); *str = safe_malloc(sizeof(char) * (ind + 1)); } for (i = 0; i < ind; i++) (*str)[i] = '\t'; (*str)[ind] = '\0'; } int main (int argc, char *argv[]) { FILE *in, *out; size_t len; char *line; if (argc == 2) { if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) print_help(); if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) print_version(); } if (argc > 3) { print_usage(); return 1; } setlocale(LC_ALL, ""); if (argc >= 2) in = fopen(argv[1], "r"); else in = stdin; if (in == NULL) { fprintf(stderr, BIN ": Can't open %s.\n", argv[1]); return 1; } head.size = BODY_START_SIZE / 4; head.str = safe_malloc(sizeof(char) * head.size); strcpy(head.str, ""); body.size = BODY_START_SIZE; body.str = safe_malloc(sizeof(char) * body.size); strcpy(body.str, ""); page_index.index = 0; page_index.size = INDEX_START; page_index.titles = safe_malloc(sizeof(index_title) * page_index.size); sitography.index = 0; sitography.size = NOTES_START; sitography.notes = malloc(sizeof(note) * sitography.size); string_cat(&body, "\t\n"); len = 0; line = NULL; while (fread_line(in, &line, &len) != EOF) { if (!len) cmd_close(); else if (len >= 2 && line[0] == '.') cmd(line); else if (len >= 2 && line[0] == '\\' && line[1] == '"') continue; else { if (space && line[0] != '!' && line[0] != '\'' && line[0] != ',' && line[0] != '.' && line[0] != '.' && line[0] != ';' && line[0] != '?') string_cat(&body, " "); string_cat_html(&body, line, tag_content); space = 1; } free(line); len = 0; line = NULL; } free(line); if (in != stdin) fclose(in); if (strcmp(tag_closure, "")) cmd_close(); page_head(); footnotes(); footer(); string_cat(&body, "\t\n\n"); if (argc == 3) out = fopen(argv[2], "w"); else out = stdout; if (out == NULL) { fprintf(stderr, BIN ": Can't open %s.\n", argv[2]); return 1; } fputs(head.str, out); { char c; c = body.str[index_position]; body.str[index_position] = '\0'; fputs(body.str, out); if (index_position) nav_index(out); body.str[index_position] = c; fputs(body.str + index_position, out); } fclose(out); return 0; } void nav_index(FILE *f) { int i, n; int ind; closure *tag; closures nav_cls; index_title *next_title, *title; ind = 2; nav_cls.index = 0; nav_cls.size = 4; nav_cls.tags = safe_malloc(sizeof(closure) * nav_cls.size); tag = NULL; findent(ind++, f); fputs("\n", f); free_closures(&nav_cls); free(nav_cls.tags); free(page_index.titles); } void page_head(void) { string_cat(&head, "\n"); string_cat(&head, "\n"); string_cat(&head, "\t\n"); string_cat(&head, "\t\t\n"); string_cat(&head, "\t\t\n"); if (author != NULL) { string_cat(&head, "\t\t\n"); free(author); } if (description != NULL) { string_cat(&head, "\t\t\n"); free(description); } string_cat(&head, "\t\t\n"); string_cat(&head, "\t\t\n"); if (title != NULL) { string_cat(&head, "\t\t"); string_cat_html(&head, title, tag_content); string_cat(&head, "\n"); free(title); } string_cat(&head, "\t\n"); } void print_help(void) { print_usage(); fprintf(stdout, "\n"); fprintf(stdout, "Without INPUT, the roff source code will be read from standard input.\n"); fprintf(stdout, "Without OUTPUT, the resulting HTML will be sent to standard output.\n"); fprintf(stdout, "To use a different output from stdout, you must specify the input first,\n"); fprintf(stdout, "or use shell redirection.\n"); fprintf(stdout, "\n"); fprintf(stdout, "\"" BIN " -h\" or \"--help\" prints this help text.\n"); fprintf(stdout, "\"" BIN " -v\" or \"--version\" prints version and license information.\n"); fprintf(stdout, "\n"); fprintf(stdout, BIN " exits with 0 only when it converts the input into HTML,\n"); fprintf(stdout, "otherwise it exits with 1.\n"); exit(1); } void print_usage(void) { fprintf(stdout, "Usage: " BIN " [INPUT [OUTPUT]]\n"); } void print_version(void) { fprintf(stdout, BIN " " VERSION "\n"); fprintf(stdout, "Copyright (C) 2024 Matteo Bini\n"); fprintf(stdout, "License: GPLv3+ .\n"); fprintf(stdout, "This is free software: you are free to change and redistribute it.\n"); fprintf(stdout, "There is NO WARRANTY, to the extent permitted by law.\n"); fprintf(stdout, "\n"); fprintf(stdout, "Written by Matteo Bini.\n"); exit(1); } void pop_closure(closures *dst) { closure *tag; if (dst->index <= 0) return; tag = dst->tags + (dst->index - 1); free(tag->name); dst->index--; } void * safe_malloc(const size_t size) { void *ptr; ptr = malloc(size); if (ptr == NULL) { fprintf(stderr, BIN ": Can't allocate %lu bytes.\n", size); exit(1); } return ptr; } void * safe_realloc(void *ptr, const size_t size) { void *new; new = realloc(ptr, size); if (new == NULL) { fprintf(stderr, BIN ": Can't allocate %lu bytes.\n", size); exit(1); } return new; } size_t stread_param(const char *src, char **str) { char c; char *end; size_t i, len; int p; c = ' '; p = 0; if (src[0] == ' ') { src++; p++; } if (src[0] == '"' || src[0] == '\'') { c = src[0]; src++; p += 2; } end = strchr(src, c); if (end) len = end - src; else len = strlen(src); *str = safe_malloc(len + 1); for (i = 0; i <= len; i++) (*str)[i] = '\0'; strncpy(*str, src, len); return len + p; } char * string_cat(string *dst, const char *src) { size_t len; len = strlen(src); if (len >= (dst->size - strlen(dst->str))) string_enlarge(dst, (dst->size + len) * 2 + 1); strcat(dst->str, src); return dst->str; } char * string_cat_html(string *dst, const char *src, const html_val_type t) { size_t i, len; len = strlen(src); for (i = 0; i < len; i++) { if (t == tag_attribute && src[i] == '"') string_cat(dst, """); else if (src[i] == '&') string_cat(dst, "&"); else if (src[i] == '<') string_cat(dst, "<"); else if (src[i] == '>') string_cat(dst, ">"); else string_putc(dst, src[i]); } return dst->str; } void string_enlarge(string *old, const size_t size) { old->str = safe_realloc(old->str, sizeof(char) * size); old->size = size; } int string_putc(string *dst, const char c) { size_t len; len = strlen(dst->str); if ((dst->size - len) <= 1) string_enlarge(dst, (dst->size + 1) * 2 + 1); dst->str[len] = c; dst->str[len + 1] = '\0'; return (int) c; }