diff markdown_converter/markdown_to_html.c @ 195:f8f5004a920a

Merging back hg-web-tip
author MrJuneJune <me@mrjunejune.com>
date Tue, 27 Jan 2026 06:51:44 -0800
parents a2725419f988
children
line wrap: on
line diff
--- a/markdown_converter/markdown_to_html.c	Sat Jan 24 06:37:43 2026 -0800
+++ b/markdown_converter/markdown_to_html.c	Tue Jan 27 06:51:44 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);