#include "website.h" #include #define TAG_P 0 #define TAG_H1 1 #define TAG_H2 2 #define TAG_H3 3 #define TAG_H4 4 #define TAG_H5 5 #define TAG_H6 6 #define TAG_LI 7 #define TAG_TR 8 #define TAG_TD 9 static const char *closing_tag_strings[] = { "

\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n" }; static const char *months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; Space produce_markdown_html(Filesystem& fs, String& html, const char *input, int in_sz, Markdown_Params& md_params) { Space title = {0}; html.reserve(html.len + in_sz); int tag_levels[16] = {0}; int tag_cursor = 0; int tag_of_prev_line = 0; int header_level = 0; int quote_level = 0; int new_quote_level = 0; int code_level = 0; int list_level = 0; int new_list_level = 0; int consq_backticks = 0; int consq_tildes = 0; int consq_asterisks = 0; int level_asterisks = 0; int consq_underscores = 0; int level_underscores = 0; int leading_spaces = 0; int nl_count = 0; int prev_line_len = 0; int prev_nl_pos = 0; int code_type = 0; int table_mode = 0; int new_table_mode = 0; int table_col_idx = 0; int table_row_idx = 0; bool uses_canvas_js = false; bool ignore_underscores = false; bool should_not_open_tag = false; bool started_line = false; bool is_strikethrough = false; bool was_esc = false; char prev = 0; char chbuf[8]; for (int i = 0; i < in_sz; i++) { char c = input[i]; bool should_process = true; bool is_list_asterisk = false; chbuf[0] = c; chbuf[1] = 0; if (!started_line && code_type != 1) { should_process = false; if (c == ' ') { leading_spaces++; } else if (c == '\t') { leading_spaces += 4; } else if (code_type == 0) { if (c == '>') { new_quote_level++; } else if (c == '#') { header_level++; if (header_level > 6) started_line = true; } else if (c == '|') { should_process = false; bool is_sep = false; int j = i; for (; j < in_sz; j++) { if (input[j] == '\n') break; if (input[j] != '|' && input[j] != ' ' && input[j] != '-' && input[j] != '+' && input[j] != ':') break; if (input[j] == '-') is_sep = true; } if (is_sep || j >= in_sz) { i = j; c = '\n'; table_row_idx = 0; should_not_open_tag = true; } new_table_mode = table_mode ? table_mode : 1; new_table_mode = is_sep ? 2 : new_table_mode; started_line = true; } else if (c == '-' || c == '+' || c == '*') { if (i < in_sz-1 && input[i+1] == ' ') { new_list_level = (leading_spaces / 4) + 1; is_list_asterisk = c == '*'; } else if (i < in_sz-2 && input[i+1] == c && input[i+2] == c) { html.add("
"); should_process = false; while (i < in_sz && input[i] != '\n') i++; c = '\n'; } started_line = true; } else if (c != '\n') { started_line = true; should_process = true; } } else if (c != '\n') { started_line = true; should_process = true; } if (started_line) { if (tag_cursor > 0) { int prev_cursor = tag_cursor; int tag = tag_levels[tag_cursor]; if (tag == TAG_P && tag_of_prev_line == TAG_P && !header_level && !new_list_level && !new_table_mode && prev_line_len > 1) { html.add(" "); should_not_open_tag = true; } else { const char *tag_str = closing_tag_strings[tag]; tag_cursor--; if (tag_cursor < 0) { tag_cursor = 0; tag_levels[0] = 0; } bool is_block = code_level || quote_level; html.add(tag_str, strlen(tag_str) - is_block); } } if (new_list_level > list_level) { for (int j = 0; j < new_list_level - list_level; j++) { html.add(""); } } list_level = new_list_level; if (new_quote_level != quote_level) { if (!quote_level) { html.add("
"); } else if (!new_quote_level) { html.add("
"); } quote_level = new_quote_level; } int prev_code_type = code_type; if (quote_level == 0 && list_level == 0 && leading_spaces >= 4) { code_type = 2; } else if (code_type == 2) { code_type = 0; } if (!prev_code_type && code_type) { html.add(""); } else if (prev_code_type && !code_type) { html.add(""); } if (new_table_mode != table_mode) { if (table_mode == 0) { html.add(""); html.add(new_table_mode == 1 ? "" : ""); } else { if (table_mode == 1) { html.add(""); if (new_table_mode) html.add(""); } else { if (new_table_mode == 1) html.add(""); else if (!new_table_mode) html.add(""); } if (!new_table_mode) html.add("
"); } table_row_idx = 0; table_mode = new_table_mode; } if (!should_not_open_tag) { if (tag_cursor < 15) tag_cursor++; if (header_level > 0 && header_level <= 6) { char tag[8]; tag[0] = '<'; tag[1] = 'h'; tag[2] = '0' + header_level; tag[3] = '>'; tag[4] = 0; html.add(tag); if (!title.offset) title.offset = i; tag_levels[tag_cursor] = header_level; } else { if (table_mode) { tag_levels[tag_cursor] = TAG_TR; html.add(""); html.data()[html.len - 3] = '0' + (table_row_idx % 2); html.add(""); } else if (list_level > 0) { tag_levels[tag_cursor] = TAG_LI; html.add("
  • "); } else { tag_levels[tag_cursor] = TAG_P; html.add("

    "); } } } if (header_level > 6) html.add("#######"); header_level = 0; } } if (code_type == 0) { int prev_em = level_asterisks + level_underscores; if (c == '*' && !was_esc && !is_list_asterisk) { consq_asterisks++; should_process = false; } else if (consq_asterisks) { level_asterisks = level_asterisks ? 0 : consq_asterisks; consq_asterisks = 0; } if (c == '_' && !was_esc) { if ((prev >= '0' && prev <= '9') || (prev >= 'A' && prev <= 'Z') || (prev >= 'a' && prev <= 'z')) ignore_underscores = !level_underscores; if (!ignore_underscores) { consq_underscores++; should_process = false; } } else { if (consq_underscores) { level_underscores = level_underscores ? 0 : consq_underscores; consq_underscores = 0; } ignore_underscores = false; } int diff_em = (level_asterisks + level_underscores) - prev_em; if (diff_em < -2) diff_em = -2; if (diff_em > 2) diff_em = 2; const char *em_tags[] = { "", "", "", "", "" }; html.add(em_tags[diff_em + 2]); if (c == '~' && !was_esc) { consq_tildes++; should_process = false; } else { if (consq_tildes == 2) { is_strikethrough = !is_strikethrough; html.add(is_strikethrough ? "" : ""); } consq_tildes = 0; } if (c == '|' && table_mode) { if (table_col_idx) html.add(""); table_col_idx++; should_process = false; } } int prev_backticks = consq_backticks; if (c == '`' && !was_esc) { // this avoids the case where a code block is formed using leading spaces, since code_level would equal 0 consq_backticks++; should_process = false; } else { if (code_type == 1 && consq_backticks == code_level) { code_level = 0; html.add(consq_backticks >= 3 ? "" : ""); code_type = 0; } else if (code_type == 0 && consq_backticks) { code_level = consq_backticks; html.add(consq_backticks >= 3 ? "

    " : ""); code_type = 1; } consq_backticks = 0; } if (code_type != 0) { should_process = false; if (c != '`' && !(prev_backticks > 0 && c == '\n')) { for (int j = 0; j < consq_backticks; j++) html.add("`"); if (NEEDS_ESCAPE(c)) write_escaped_byte(c, chbuf); html.add(chbuf); } } else if (!was_esc) { if (c == '!' || c == '?' || c == '$') { if (i < in_sz-1 && input[i+1] == '[') should_process = false; } else if (c == '[') { should_process = false; if (prev == '!') html.add("j; link) html.add_and_escape(&input[link], link_end - link); } if (link <= alt) { html.add("\">"); if (prev != '!' && prev != '?' && prev != '$') html.add_and_escape(&input[link], link_end - link); } else if (prev == '$') { if (alt_end > alt) { html.add("\" data-params=\""); html.add_and_escape(&input[alt], alt_end - alt); } html.add("\">"); } else if (prev == '?') { if (input[alt] == '=' && alt_end - alt > 1) { html.add("class=\""); alt++; } else if (alt_contains_colon) { html.add("style=\""); } html.add_and_escape(&input[alt], alt_end - alt); html.add("\">"); html.add_and_escape(&input[link], link_end - link); } else { if (prev == '!') html.add("\" alt=\""); else html.add("\">"); if (alt_end > alt) { html.add_and_escape(&input[alt], alt_end - alt); } if (prev == '!') { if (input[alt] == '=' && alt_end - alt > 1) { html.add("\" class=\""); alt++; html.add_and_escape(&input[alt], alt_end - alt); } else if (alt_contains_colon) { html.add("\" style=\""); html.add_and_escape(&input[alt], alt_end - alt); } html.add("\">"); } } if (prev == '$') { html.add(""); uses_canvas_js = true; } else if (prev == '?') { html.add(""); } else if (prev != '!') { html.add(md_params.disable_anchors ? "" : ""); } i = j; } } if (should_process) { bool should_add_c = true; if (!was_esc) { should_add_c = c != '\\' && c != '\n'; } if (should_add_c) { if (NEEDS_ESCAPE(c)) write_escaped_byte(c, chbuf); html.add(chbuf); } } if (c == '\n') { if (header_level > 0) { while (tag_cursor > 0) { int tag = tag_levels[tag_cursor]; const char *tag_str = closing_tag_strings[tag]; tag_cursor--; bool is_block = code_level || quote_level; html.add(tag_str, strlen(tag_str) - is_block); if (tag >= 1 && tag <= 6) break; } } if (header_level == 1 && !title.size) { title.size = i - title.offset; } else if (header_level == 2 && md_params.created_time) { struct tm *date = localtime(&md_params.created_time); int day_kind = 0; int day_mod10 = date->tm_mday % 10; if (day_mod10 < 4 && (date->tm_mday < 4 || date->tm_mday > 20)) day_kind = day_mod10; const char *kinds[] = {"th", "st", "nd", "rd"}; char datebuf[128]; String date_str(datebuf, 128); date_str.reformat( "{d}{s} {s}, {d}", date->tm_mday, kinds[day_kind], months[date->tm_mon], 1900 + date->tm_year ); html.add(datebuf); md_params.created_time = 0; } if (table_mode) { while (tag_cursor > 0) { int tag = tag_levels[tag_cursor]; const char *tag_str = closing_tag_strings[tag]; tag_cursor--; if (tag == TAG_TR) { html.add(""); break; } bool is_block = code_level || quote_level; html.add(tag_str, strlen(tag_str) - is_block); } if (!should_not_open_tag) table_row_idx++; } header_level = 0; tag_of_prev_line = tag_levels[tag_cursor]; started_line = false; should_not_open_tag = false; leading_spaces = 0; new_quote_level = 0; new_list_level = 0; new_table_mode = 0; table_col_idx = 0; prev_line_len = i - prev_nl_pos - 1; prev_nl_pos = i; nl_count++; if (md_params.line_limit > 0 && nl_count >= md_params.line_limit) break; } if (!code_type) was_esc = c == '\\' && !was_esc; else was_esc = false; prev = c; } if (code_type) html.add("\n"); if (is_strikethrough) html.add("\n"); int em_level = level_asterisks + level_underscores; if (em_level == 1 || em_level == 3) html.add("\n"); if (em_level == 2 || em_level == 3) html.add("\n"); while (tag_cursor > 0) { const char *tag = closing_tag_strings[tag_levels[tag_cursor]]; tag_cursor--; html.add(tag); } if (table_mode) { html.add(table_mode == 1 ? "" : ""); html.add(""); } if (uses_canvas_js) { html.add(""); } return title; } void serve_markdown_tester(Filesystem& fs, Request& request, Response& response) { bool allow_html = false; if (request.accept > 0) { char *p = request.str.data() + request.accept; while (*p && *p != '\r' && *p != '\n') { if (p[0] == 'h' && p[1] == 't' && p[2] == 'm' && p[3] == 'l') { allow_html = true; break; } p++; } } if (!allow_html) { const char *md_text = request.str.data() + request.header_size; int md_len = request.str.len - request.header_size; response.mime = "text/plain"; Markdown_Params md_params = {0}; produce_markdown_html(fs, response.html, md_text, md_len, md_params); //log_info("{S}", response.html.data(), response.html.len); return; } String *html = &response.html; html->add( "" HTML_METAS "Markdown Editor" "" "
    " ); add_banner(fs, html, NAV_IDX_NULL); html->add( "
    " "
    " "

    Editor

    " "
    " "
    " "
    " "
    " "

    Preview

    " "
    " "
    " "
    " "
    " "
    " ); html->add("
    "); }