Implement block quotes.
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 173 174
diff --git a/README.md b/README.md
index 8557d4b..270069a 100644
--- a/README.md
+++ b/README.md
@@ -92,7 +92,7 @@ more or less forms our to do list.
- [x] 4.9 Blank lines
- **Container Blocks:**
- - [ ] 5.1 Block quotes
+ - [x] 5.1 Block quotes
- [ ] 5.2 List items
- [ ] 5.3 Lists
diff --git a/md2html/md2html.c b/md2html/md2html.c
index c062e2f..1b79cc5 100644
--- a/md2html/md2html.c
+++ b/md2html/md2html.c
@@ -148,6 +148,7 @@ enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
switch(type) {
case MD_BLOCK_DOC: /* noop */ break;
+ case MD_BLOCK_QUOTE: MEMBUF_APPEND_LITERAL(out, "<blockquote>"); break;
case MD_BLOCK_HR: MEMBUF_APPEND_LITERAL(out, "<hr>\n"); break;
case MD_BLOCK_H: MEMBUF_APPEND_LITERAL(out, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
case MD_BLOCK_CODE: open_code_block(out, (const MD_BLOCK_CODE_DETAIL*) detail); break;
@@ -166,6 +167,7 @@ leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
switch(type) {
case MD_BLOCK_DOC: /*noop*/ break;
+ case MD_BLOCK_QUOTE: MEMBUF_APPEND_LITERAL(out, "</blockquote>"); break;
case MD_BLOCK_HR: /*noop*/ break;
case MD_BLOCK_H: MEMBUF_APPEND_LITERAL(out, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
case MD_BLOCK_CODE: MEMBUF_APPEND_LITERAL(out, "</code></pre>\n"); break;
diff --git a/md4c/md4c.c b/md4c/md4c.c
index 05f8775..c0ac87a 100644
--- a/md4c/md4c.c
+++ b/md4c/md4c.c
@@ -76,6 +76,9 @@ struct MD_CTX_tag {
MD_RENDERER r;
void* userdata;
+ /* For MD_BLOCK_QUOTE */
+ unsigned quote_level; /* Nesting level. */
+
/* Minimal indentation to call the block "indented code". */
unsigned code_indent_offset;
@@ -112,6 +115,7 @@ struct MD_LINE_tag {
MD_LINETYPE type;
OFF beg;
OFF end;
+ unsigned quote_level; /* Level of nesting in <blockquote>. */
unsigned indent; /* Indentation level. */
};
@@ -667,8 +671,10 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end, const MD_LINE* pivot_line, MD_
OFF off = beg;
line->type = MD_LINE_BLANK;
+ line->quote_level = 0;
line->indent = 0;
+redo_indentation_after_blockquote_mark:
/* Eat indentation. */
while(off < ctx->size && ISBLANK(off)) {
if(CH(off) == _T('\t'))
@@ -706,6 +712,16 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end, const MD_LINE* pivot_line, MD_
goto done;
}
+ /* Check blockquote mark. */
+ if(off < ctx->size && CH(off) == _T('>')) {
+ off++;
+ if(off < ctx->size && CH(off) == _T(' '))
+ off++;
+ line->quote_level++;
+ line->indent = 0;
+ goto redo_indentation_after_blockquote_mark;
+ }
+
/* Check whether we are blank line.
* Note blank lines after indented code are treated as part of that block.
* If they are at the end of the block, it is discarded by caller.
@@ -787,6 +803,11 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end, const MD_LINE* pivot_line, MD_
/* By default, we are normal text line. */
line->type = MD_LINE_TEXT;
+ /* Ordinary text line may need to upgrade block quote level because
+ * of its lazy continuation. */
+ if(pivot_line->type == MD_LINE_TEXT && pivot_line->quote_level > line->quote_level)
+ line->quote_level = pivot_line->quote_level;
+
done:
/* Eat rest of the line contents */
while(off < ctx->size && !ISNEWLINE(off))
@@ -819,6 +840,27 @@ done:
*p_end = off;
}
+static int
+md_process_blockquote_nesting(MD_CTX* ctx, unsigned desired_level)
+{
+ int ret = 0;
+
+ /* Bring blockquote nesting to expected level. */
+ if(ctx->quote_level != desired_level) {
+ while(ctx->quote_level < desired_level) {
+ MD_ENTER_BLOCK(MD_BLOCK_QUOTE, NULL);
+ ctx->quote_level++;
+ }
+ while(ctx->quote_level > desired_level) {
+ MD_LEAVE_BLOCK(MD_BLOCK_QUOTE, NULL);
+ ctx->quote_level--;
+ }
+ }
+
+abort:
+ return ret;
+}
+
/* Determine type of the block (from type of its 1st line and some context),
* call block_enter() callback, then appropriate function to parse contents
* of the block, and finally block_leave() callback.
@@ -836,6 +878,12 @@ md_process_block(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
if(n_lines == 0)
return 0;
+ /* Make sure the processed leaf block lives in the proper block quote
+ * nesting level. */
+ ret = md_process_blockquote_nesting(ctx, lines[0].quote_level);
+ if(ret != 0)
+ goto abort;
+
/* Derive block type from type of the first line. */
switch(lines[0].type) {
case MD_LINE_BLANK:
@@ -973,8 +1021,9 @@ md_process_doc(MD_CTX *ctx)
line->type = MD_LINE_BLANK;
}
- /* New block also starts if line type changes. */
- if(line->type != pivot_line->type) {
+ /* New block also starts if line type changes or if block quote nesting
+ * level changes. */
+ if(line->type != pivot_line->type || line->quote_level != pivot_line->quote_level) {
ret = md_process_block(ctx, lines, n_lines);
if(ret != 0)
goto abort;
@@ -1002,6 +1051,11 @@ md_process_doc(MD_CTX *ctx)
goto abort;
}
+ /* Close any dangling parent blocks. */
+ ret = md_process_blockquote_nesting(ctx, 0);
+ if(ret != 0)
+ goto abort;
+
MD_LEAVE_BLOCK(MD_BLOCK_DOC, NULL);
abort:
diff --git a/md4c/md4c.h b/md4c/md4c.h
index 6948adb..b0d577c 100644
--- a/md4c/md4c.h
+++ b/md4c/md4c.h
@@ -61,6 +61,9 @@ enum MD_BLOCKTYPE_tag {
/* <body>...</body> */
MD_BLOCK_DOC = 0,
+ /* <blockquote>...</blockquote> */
+ MD_BLOCK_QUOTE,
+
/* <hr> */
MD_BLOCK_HR,