md4c

C Markdown parser. Fast. SAX-like interface. Compliant to CommonMark specification.
git clone https://noulin.net/git/md4c.git
Log | Files | Refs | README | LICENSE

commit 33258e68bd5a0c7170b0badbcd271423f6decc7b
parent 279ec8f6d52e9edc4b608cc694edf2ad7e69cc33
Author: Martin Mitas <mity@morous.org>
Date:   Tue,  4 Oct 2016 21:18:30 +0200

Implement block quotes.

Diffstat:
MREADME.md | 2+-
Mmd2html/md2html.c | 2++
Mmd4c/md4c.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mmd4c/md4c.h | 3+++
4 files changed, 62 insertions(+), 3 deletions(-)

diff --git 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 @@ -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 @@ -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 @@ -61,6 +61,9 @@ enum MD_BLOCKTYPE_tag { /* <body>...</body> */ MD_BLOCK_DOC = 0, + /* <blockquote>...</blockquote> */ + MD_BLOCK_QUOTE, + /* <hr> */ MD_BLOCK_HR,