Implement thematic breaks (<hr>).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
diff --git a/README.md b/README.md
index 5b8c15b..559f8f4 100644
--- a/README.md
+++ b/README.md
@@ -81,7 +81,7 @@ more or less forms our to do list.
- [ ] 3.2 Container blocks and leaf blocks
- **Leaf Blocks:**
- - [ ] 4.1 Thematic breaks
+ - [x] 4.1 Thematic breaks
- [ ] 4.2 ATX headings
- [ ] 4.3 Setext headings
- [ ] 4.4 Indented code blocks
diff --git a/md2html/md2html.c b/md2html/md2html.c
index 5a631e0..c5718dd 100644
--- a/md2html/md2html.c
+++ b/md2html/md2html.c
@@ -132,6 +132,7 @@ enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
switch(type) {
case MD_BLOCK_DOC: /* noop */ break;
+ case MD_BLOCK_HR: MEMBUF_APPEND_LITERAL(out, "<hr>\n"); break;
case MD_BLOCK_P: MEMBUF_APPEND_LITERAL(out, "<p>"); break;
}
@@ -145,6 +146,7 @@ leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
switch(type) {
case MD_BLOCK_DOC: /*noop*/ break;
+ case MD_BLOCK_HR: /*noop*/ break;
case MD_BLOCK_P: MEMBUF_APPEND_LITERAL(out, "</p>\n"); break;
}
diff --git a/md4c/md4c.c b/md4c/md4c.c
index 906f36e..ef741fd 100644
--- a/md4c/md4c.c
+++ b/md4c/md4c.c
@@ -72,6 +72,7 @@ struct MD_CTX_tag {
typedef enum MD_LINETYPE_tag MD_LINETYPE;
enum MD_LINETYPE_tag {
MD_LINE_BLANK,
+ MD_LINE_HR,
MD_LINE_TEXT
};
@@ -148,6 +149,7 @@ md_log(MD_CTX* ctx, const char* fmt, ...)
#define ISDIGIT_(ch) (_T('0') <= (ch) && (ch) <= _T('9'))
#define ISXDIGIT_(ch) (ISDIGIT_(ch) || (_T('a') < (ch) && (ch) <= _T('f') || (_T('A') < (ch) && (ch) <= _T('F'))
#define ISALNUM_(ch) (ISALPHA_(ch) || ISDIGIT_(ch))
+#define ISANYOF_(ch, palette) (md_strchr((palette), (ch)) != NULL)
#define ISASCII(off) ISASCII_(CH(off))
#define ISBLANK(off) ISBLANK_(CH(off))
@@ -161,6 +163,19 @@ md_log(MD_CTX* ctx, const char* fmt, ...)
#define ISDIGIT(off) ISDIGIT_(CH(off))
#define ISXDIGIT(off) ISXDIGIT_(CH(off))
#define ISALNUM(off) ISALNUM_(CH(off))
+#define ISANYOF(off, palette) ISANYOF_(CH(off), (palette))
+
+
+static inline const CHAR*
+md_strchr(const CHAR* str, CHAR ch)
+{
+ OFF i;
+ for(i = 0; str[i] != _T('\0'); i++) {
+ if(ch == str[i])
+ return (str + i);
+ }
+ return NULL;
+}
#define MD_ENTER_BLOCK(type, arg) \
@@ -235,6 +250,25 @@ abort:
*** Breaking Document into Blocks ***
***************************************/
+static int
+md_is_hr_line(MD_CTX* ctx, OFF beg, OFF* p_end)
+{
+ OFF off = beg + 1;
+ int n = 1;
+
+ while(off < ctx->size && (CH(off) == CH(beg) || CH(off) == _T(' '))) {
+ if(CH(off) == CH(beg))
+ n++;
+ off++;
+ }
+
+ if(n < 3)
+ return -1;
+
+ *p_end = off;
+ return 0;
+}
+
/* Analyze type of the line and find some its properties. This serves as a
* main input for determining type and boundaries of a block. */
static void
@@ -258,6 +292,14 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end, const MD_LINE* pivot_line, MD_
goto done;
}
+ /* Check whether we are thematic break line. */
+ if(ISANYOF(off, _T("-_*"))) {
+ if(md_is_hr_line(ctx, off, &off) == 0) {
+ line->type = MD_LINE_HR;
+ goto done;
+ }
+ }
+
/* By default, we are normal text line. */
line->type = MD_LINE_TEXT;
@@ -293,17 +335,17 @@ md_process_block(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
/* Derive block type from type of the first line. */
switch(lines[0].type) {
- case MD_LINE_BLANK:
- return 0;
-
- case MD_LINE_TEXT:
- block_type = MD_BLOCK_P;
- break;
+ case MD_LINE_BLANK: return 0;
+ case MD_LINE_HR: block_type = MD_BLOCK_HR; break;
+ case MD_LINE_TEXT: block_type = MD_BLOCK_P; break;
}
/* Process the block accordingly to is type. */
MD_ENTER_BLOCK(block_type, NULL);
- ret = md_process_normal_block(ctx, lines, n_lines);
+ switch(block_type) {
+ case MD_BLOCK_HR: /* Noop. */ break;
+ default: ret = md_process_normal_block(ctx, lines, n_lines); break;
+ }
if(ret != 0)
goto abort;
MD_LEAVE_BLOCK(block_type, NULL);
@@ -350,6 +392,10 @@ md_process_doc(MD_CTX *ctx)
/* The same block continues as long lines are of the same type. */
if(line->type == pivot_line->type) {
+ /* But not so thematic break. */
+ if(line->type == MD_LINE_HR)
+ goto force_block_end;
+
/* Do not grow the 'lines' because of blank lines. Semantically
* one blank line is equivalent to many. */
if(line->type != MD_LINE_BLANK)
@@ -358,6 +404,7 @@ md_process_doc(MD_CTX *ctx)
continue;
}
+force_block_end:
/* Otherwise the old block is complete and we have to process it. */
ret = md_process_block(ctx, lines, n_lines);
if(ret != 0)
diff --git a/md4c/md4c.h b/md4c/md4c.h
index 0715046..3065c28 100644
--- a/md4c/md4c.h
+++ b/md4c/md4c.h
@@ -61,6 +61,9 @@ enum MD_BLOCKTYPE_tag {
/* <body>...</body> */
MD_BLOCK_DOC = 0,
+ /* <hr> */
+ MD_BLOCK_HR,
+
/* <p>...</p> */
MD_BLOCK_P
};