#define _XOPEN_SOURCE #include #include #include #include #include "viewhtml.h" typedef enum { Inline, Block, TableRow, } ElementType; typedef struct { char *control_sequence; } Style; typedef struct { char *name; ElementType type; const Style *style; } TagInfo; static const Style NullStyle = { "" }; static const Style BrightStyle = { "\033[1m" }; static const Style BlueStyle = { "\033[34m" }; static const Style RedStyle = { "\033[31m" }; #define NUM_TAGS 12 static const TagInfo tags[NUM_TAGS] = { { "p", Block, &NullStyle }, { "h1", Block, &BrightStyle }, { "h2", Block, &BrightStyle }, { "h3", Block, &BrightStyle }, { "h4", Block, &BrightStyle }, { "h5", Block, &NullStyle }, { "h6", Block, &NullStyle }, { "b", Inline, &BrightStyle }, { "i", Inline, &RedStyle }, { "em", Inline, &RedStyle }, { "a", Inline, &BlueStyle }, { "tr", TableRow, &NullStyle }, }; typedef struct { char *code; char *text; } EntityInfo; #define NUM_ENTITIES 6 static const EntityInfo entities[NUM_ENTITIES] = { { "&", "&" }, { "<", "<" }, { ">", ">" }, { """, "\"" }, { " ", "\302\240" }, { "–", "\342\200\223" }, }; static void reset_styles(FILE *file) { fputs("\033[0m", file); } static void activate_style(FILE *file, const Style *style) { fputs(style->control_sequence, file); } static void set_styles(FILE *file, int open_tags) { reset_styles(file); for (int t = 0; t < NUM_TAGS; t++) { if (open_tags & (1 << t)) { activate_style(file, tags[t].style); } } } static void handle_inline_element(FILE *file, int open_tags, bool *near_text) { if (!*near_text) { /* Spacing between block level elements and text */ fputs("\n\n", file); set_styles(file, open_tags); *near_text = true; } } static void handle_tag(FILE *file, int *open_tags, bool *near_text, const char *tag, int taglen, bool open) { /* Look up tag */ int t; for (t = 0; t < NUM_TAGS; t++) { if (!strncmp(tags[t].name, tag, taglen)) break; } if (t == NUM_TAGS) return; /* Special care for some tags */ if (tags[t].type == Block) { #if 0 if (*near_text && open) { /* Start of block in a text */ fputs("\n\n", file); // TODO ignore trailing br tag } #endif *near_text = false; /* if (open) fputs("\n\n", file); else *near_text = false;*/ } else if (tags[t].type == TableRow) { /* A simple hack for tables */ fputs("\n", file); } /* The tag was found. Apply it */ if (open) *open_tags |= (1 << t); else *open_tags &= ~(1 << t); //fprintf(file, "<%s%*s>", open ? "" : "/", taglen, tag); /* Handle some tags specially */ if (!strncmp(tag, "p", taglen)) { fputs("\n\n", file); } else if (!strncmp(tag, "br", taglen)) { fputs("\n", file); } /* Update style */ set_styles(file, *open_tags); } static void print_html(FILE *file, const char *html) { int open_tags = 0; bool near_text = false; const char *p = html; while (*p != '\0') { if (*p == '<') { p++; if (*p == '\0') break; else if (*p == '/') { /* Skip end tag */ const char *tagstart = ++p; while ((*p != '>') && (*p != '\0')) p++; handle_tag(file, &open_tags, &near_text, tagstart, p - tagstart, false); } else { /* Parse start tag */ const char *tagstart = p; while ((*p != '>') && (*p != ' ') && (*p != '/') && (*p != '\0')) p++; handle_tag(file, &open_tags, &near_text, tagstart, p - tagstart, true); // FIXME Hack to display link URLs if (*p == ' ') p++; if (!strncmp("href=\"entry://", p, 14)) { handle_inline_element(file, open_tags, &near_text); const char *url = p+14; fputs("[", file); fwrite(url, 1, strcspn(url, "\""), file); fputs("] ", file); } /* Skip to end of tag */ while ((*p != '>') && (*p != '\0')) p++; } /* Skip trailing > */ if (*p == '>') p++; } else if (*p == '&') { /* TODO parse entities */ for (int i = 0; i < NUM_ENTITIES; i++) { const int len = strlen(entities[i].code); if (!strncmp(entities[i].code, p, len)) { p += len; fputs(entities[i].text, file); goto known_entity; } } /* Print unknown entity as-is */ fputc('&', file); p++; known_entity: ; //while ((*p != '>') && (*p != '\0')) p++; } else if ((*p > 0) && (*p <= ' ')) { /* Group spacing characters */ do p++; while ((*p > 0) && (*p <= ' ')); fputc(' ', file); } else { // TODO: Wrap words here instead of with "fmt", to avoid breaking control sequences handle_inline_element(file, open_tags, &near_text); fputc(*p, file); p++; } } } void view_html(const char *html) { /* FIXME fork and these variables in the forked process */ putenv("LESSHISTFILE=/dev/null"); //putenv("COLUMNS=100"); FILE *lesspipe = popen("fmt -w 100 | less -R -", "w"); //FILE *lesspipe = popen("fmt -w 100 -u | less -", "w"); //FILE *lesspipe = popen("pr -t -J -o 4 -w 100 | less -", "w"); print_html(lesspipe, html); pclose(lesspipe); }