Mercurial
diff markdown_converter/markdown_to_html.c @ 190:a2725419f988 hg-web
Updated so that bun builds will with already existing js files.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sat, 24 Jan 2026 21:06:42 -0800 |
| parents | 8c74204fd362 |
| children |
line wrap: on
line diff
--- a/markdown_converter/markdown_to_html.c Fri Jan 23 22:50:28 2026 -0800 +++ b/markdown_converter/markdown_to_html.c Sat Jan 24 21:06:42 2026 -0800 @@ -84,6 +84,9 @@ } } +// Forward declaration +static void process_inline(StringBuffer *buf, const char *text, size_t len); + // Check if line starts with pattern (after trimming whitespace) static int starts_with(const char *line, const char *pattern) { @@ -187,6 +190,157 @@ return *line == '.' && line[1] == ' '; } +// Check if line could be a table row (contains |) +static int is_table_row(const char *line) +{ + line = skip_whitespace(line); + // Must contain at least one | + return strchr(line, '|') != NULL; +} + +// Check if line is a table separator (|---|---|) +static int is_table_separator(const char *line) +{ + line = skip_whitespace(line); + int has_dash = 0; + int has_pipe = 0; + + while (*line) { + char c = *line; + if (c == '|') has_pipe = 1; + else if (c == '-') has_dash = 1; + else if (c == ':') ; // alignment marker, allowed + else if (isspace((unsigned char)c)) ; // whitespace allowed + else return 0; // invalid character for separator + line++; + } + + return has_dash && has_pipe; +} + +// Parse alignment from separator cell (e.g., ":---:", "---:", ":---") +// Returns: 0 = left (default), 1 = center, 2 = right +static int parse_alignment(const char *cell, size_t len) +{ + // Trim whitespace + while (len > 0 && isspace((unsigned char)*cell)) { cell++; len--; } + while (len > 0 && isspace((unsigned char)cell[len-1])) { len--; } + + if (len == 0) return 0; + + int left_colon = (cell[0] == ':'); + int right_colon = (len > 0 && cell[len-1] == ':'); + + if (left_colon && right_colon) return 1; // center + if (right_colon) return 2; // right + return 0; // left (default) +} + +// Count columns in a table row +static int count_table_columns(const char *line) +{ + int count = 0; + int in_cell = 0; + line = skip_whitespace(line); + + // Skip leading | + if (*line == '|') line++; + + while (*line) { + if (*line == '|') { + count++; + in_cell = 0; + } else if (!isspace((unsigned char)*line)) { + in_cell = 1; + } + line++; + } + + // Count last cell if there was content after last | + if (in_cell) count++; + + return count > 0 ? count : 1; +} + +// Parse table cells and call callback for each +typedef void (*cell_callback)(StringBuffer *buf, const char *cell, size_t len, int align, int is_header); + +static void parse_table_row(StringBuffer *buf, const char *line, int *alignments, int num_cols, int is_header, cell_callback cb) +{ + line = skip_whitespace(line); + + // Skip leading | + if (*line == '|') line++; + + int col = 0; + const char *cell_start = line; + + while (*line && col < num_cols) { + if (*line == '|' || *(line + 1) == '\0') { + // End of cell + size_t cell_len = line - cell_start; + if (*line != '|') cell_len++; // include last char if no trailing | + + // Trim whitespace from cell + while (cell_len > 0 && isspace((unsigned char)*cell_start)) { cell_start++; cell_len--; } + while (cell_len > 0 && isspace((unsigned char)cell_start[cell_len-1])) { cell_len--; } + + int align = (alignments && col < num_cols) ? alignments[col] : 0; + cb(buf, cell_start, cell_len, align, is_header); + + col++; + cell_start = line + 1; + } + line++; + } + + // Fill remaining columns with empty cells + while (col < num_cols) { + cb(buf, "", 0, alignments ? alignments[col] : 0, is_header); + col++; + } +} + +static void emit_table_cell(StringBuffer *buf, const char *cell, size_t len, int align, int is_header) +{ + const char *tag = is_header ? "th" : "td"; + const char *align_attr = ""; + + if (align == 1) align_attr = " style=\"text-align:center\""; + else if (align == 2) align_attr = " style=\"text-align:right\""; + + buffer_append(buf, "<"); + buffer_append(buf, tag); + buffer_append(buf, align_attr); + buffer_append(buf, ">"); + process_inline(buf, cell, len); + buffer_append(buf, "</"); + buffer_append(buf, tag); + buffer_append(buf, ">"); +} + +// Parse alignments from separator row +static void parse_alignments(const char *line, int *alignments, int num_cols) +{ + line = skip_whitespace(line); + if (*line == '|') line++; + + int col = 0; + const char *cell_start = line; + + while (*line && col < num_cols) { + if (*line == '|' || *(line + 1) == '\0') { + size_t cell_len = line - cell_start; + if (*line != '|') cell_len++; + + alignments[col] = parse_alignment(cell_start, cell_len); + col++; + cell_start = line + 1; + } + line++; + } +} + // Process inline markdown (bold, italic, code, links, strikethrough) static void process_inline(StringBuffer *buf, const char *text, size_t len) { @@ -532,6 +686,79 @@ continue; } + // Table: | col1 | col2 | followed by |---|---| + if (is_table_row(line)) { + // Peek at next line to see if it's a separator + const char *peek_ptr = ptr; + if (*peek_ptr == '\n') peek_ptr++; + + const char *next_line_start = peek_ptr; + while (*peek_ptr && *peek_ptr != '\n') peek_ptr++; + size_t next_line_len = peek_ptr - next_line_start; + + char *next_line = (char *)malloc(next_line_len + 1); + if (next_line) { + memcpy(next_line, next_line_start, next_line_len); + next_line[next_line_len] = '\0'; + + if (is_table_separator(next_line)) { + // It's a table! + int num_cols = count_table_columns(line); + int *alignments = (int *)calloc(num_cols, sizeof(int)); + + buffer_append(buf, "<table>"); + + // Header row + buffer_append(buf, "<thead><tr>"); + parse_table_row(buf, line, NULL, num_cols, 1, emit_table_cell); + buffer_append(buf, "</tr></thead>"); + + free(line); + if (*ptr == '\n') ptr++; + + // Parse alignments from separator + parse_alignments(next_line, alignments, num_cols); + free(next_line); + + // Skip separator line + ptr = peek_ptr; + if (*ptr == '\n') ptr++; + + // Body rows + buffer_append(buf, "<tbody>"); + + while (*ptr) { + line_start = ptr; + while (*ptr && *ptr != '\n') ptr++; + line_len = ptr - line_start; + + line = (char *)malloc(line_len + 1); + if (!line) break; + memcpy(line, line_start, line_len); + line[line_len] = '\0'; + + if (!is_table_row(line) || is_empty_line(line)) { + ptr = line_start; + free(line); + break; + } + + buffer_append(buf, "<tr>"); + parse_table_row(buf, line, alignments, num_cols, 0, emit_table_cell); + buffer_append(buf, "</tr>"); + + free(line); + if (*ptr == '\n') ptr++; + } + + buffer_append(buf, "</tbody></table>"); + free(alignments); + continue; + } + free(next_line); + } + } + // HTML block - pass through unchanged if (is_html_block_start(line)) { // Check if it's a script or style tag that needs special handling @@ -607,6 +834,7 @@ is_horizontal_rule(line) || is_unordered_list(line) || is_ordered_list(line) || + is_table_row(line) || is_html_block_start(line)) { ptr = line_start; free(line);