Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions api_test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,104 @@ static void test_front_matter(test_batch_runner *runner) {
#undef PARSE
}

static void test_html_block_blank_lines(test_batch_runner *runner) {
#define PARSE(str, opts) cmark_parse_document(str, sizeof(str) - 1, opts)

static const char nested_html[] =
"<div>\n"
"\t<p>one</p>\n"
"\n"
" <p>two</p>\n"
"</div>\n";

cmark_node *doc = PARSE(nested_html, CMARK_OPT_DEFAULT);
cmark_node *html = doc->first_child;
INT_EQ(runner, cmark_node_get_type(html), CMARK_NODE_HTML_BLOCK,
"indented HTML: default first node is HTML");
OK(runner, html->next != NULL,
"indented HTML: default parsing ends at the blank line");
cmark_node_free(doc);

doc = PARSE(nested_html, CMARK_OPT_HTML_BLOCK_BLANK_LINES);
html = doc->first_child;
INT_EQ(runner, cmark_node_get_type(html), CMARK_NODE_HTML_BLOCK,
"indented HTML: flagged node is HTML");
STR_EQ(runner, cmark_node_get_literal(html), nested_html,
"indented HTML: consistent indentation retains blank lines");
OK(runner, html->next == NULL,
"indented HTML: consistent content remains one block");
cmark_node_free(doc);

static const char initially_blank[] =
"<div>\n"
"\n"
"\t<p>content</p>\n"
"</div>\n";

doc = PARSE(initially_blank, CMARK_OPT_HTML_BLOCK_BLANK_LINES);
html = doc->first_child;
STR_EQ(runner, cmark_node_get_literal(html), initially_blank,
"indented HTML: indentation may be established after a blank line");
OK(runner, html->next == NULL,
"indented HTML: initially blank content remains one block");
cmark_node_free(doc);

static const char dedented[] =
"<div>\n"
"\tcontent\n"
"\n"
"outside\n";

doc = PARSE(dedented, CMARK_OPT_HTML_BLOCK_BLANK_LINES);
html = doc->first_child;
STR_EQ(runner, cmark_node_get_literal(html), "<div>\n\tcontent\n\n",
"indented HTML: retained blank line belongs to preceding HTML");
INT_EQ(runner, cmark_node_get_type(html->next), CMARK_NODE_PARAGRAPH,
"indented HTML: dedented content terminates the block");
cmark_node_free(doc);

static const char inconsistent[] =
"<div>\n"
"\tcontent\n"
"\n"
" less indented\n";

doc = PARSE(inconsistent, CMARK_OPT_HTML_BLOCK_BLANK_LINES);
html = doc->first_child;
INT_EQ(runner, cmark_node_get_type(html->next), CMARK_NODE_PARAGRAPH,
"indented HTML: shallower indentation terminates the block");
cmark_node_free(doc);

static const char unindented[] =
"<div>\n"
"content\n"
"\n"
"more\n";

doc = PARSE(unindented, CMARK_OPT_HTML_BLOCK_BLANK_LINES);
html = doc->first_child;
OK(runner, html->next != NULL,
"indented HTML: unindented content still ends at a blank line");
cmark_node_free(doc);

static const char custom_element[] =
"<slide-diagram>\n"
" first\n"
"\n"
" nested\n"
"</slide-diagram>\n";

doc = PARSE(custom_element, CMARK_OPT_HTML_BLOCK_BLANK_LINES);
html = doc->first_child;
STR_EQ(runner, cmark_node_get_literal(html), custom_element,
"indented HTML: type 7 blocks and deeper indentation are supported");
OK(runner, html->next == NULL,
"indented HTML: custom element remains one block");
cmark_node_free(doc);

#undef PARSE
}

static void test_feed_across_line_ending(test_batch_runner *runner) {
// See #117
cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT);
Expand Down Expand Up @@ -1487,6 +1585,7 @@ int main() {
test_cplusplus(runner);
test_safe(runner);
test_front_matter(runner);
test_html_block_blank_lines(runner);
test_feed_across_line_ending(runner);
test_pathological_regressions(runner);
source_pos(runner);
Expand Down
4 changes: 4 additions & 0 deletions man/man1/cmark-gfm.1
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ be rendered as curly quotes, depending on their position.
Parse language prefixes on inline code spans, such as
\f[C]ruby:`Object.new`\f[], and expose the language identifier as code info.
.TP 12n
.B \-\-html-block-blank-lines
Allow indented content in type 6 and 7 HTML blocks to continue across blank
lines while its established indentation is preserved.
.TP 12n
.B \-\-unsafe
Render raw HTML and potentially dangerous URLs.
(Raw HTML is not replaced by a placeholder comment; potentially
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Compared with GitHub's `cmark-gfm`, CMarkly:
as `CMARK_NODE_FRONT_MATTER` nodes.
- Supports opt-in language prefixes for inline code, such as `` ruby:`Object.new` ``,
through `CMARK_OPT_INLINE_CODE_INFO` and the code-info accessors.
- Supports opt-in blank lines within consistently indented HTML content through
`CMARK_OPT_HTML_BLOCK_BLANK_LINES`.
- Provides `cmark_node_clone` for independently cloning complete node trees,
including supported extension metadata and footnote relationships. User data
is intentionally not copied.
Expand Down
37 changes: 31 additions & 6 deletions src/blocks.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ static CMARK_INLINE bool S_ends_on_current_line(cmark_parser *parser, cmark_node
// similar to fenced code blocks.
// Types 6-7 end at a blank line, so their last content line is
// the previous line and they should NOT match here.
(S_type(b) == CMARK_NODE_HTML_BLOCK && b->as.html_block_type >= 1 &&
b->as.html_block_type <= 5) ||
(S_type(b) == CMARK_NODE_HTML_BLOCK && b->as.html_block.type >= 1 &&
b->as.html_block.type <= 5) ||
// Single-line blocks: finalized on same line they started
b->start_line == parser->line_number;
}
Expand Down Expand Up @@ -1037,7 +1037,7 @@ static bool parse_code_block_prefix(cmark_parser *parser, cmark_chunk *input,
static bool parse_html_block_prefix(cmark_parser *parser,
cmark_node *container) {
bool res = false;
int html_block_type = container->as.html_block_type;
int html_block_type = container->as.html_block.type;

assert(html_block_type >= 1 && html_block_type <= 7);
switch (html_block_type) {
Expand All @@ -1051,7 +1051,32 @@ static bool parse_html_block_prefix(cmark_parser *parser,
break;
case 6:
case 7:
res = !parser->blank;
if (!(parser->options & CMARK_OPT_HTML_BLOCK_BLANK_LINES)) {
res = !parser->blank;
} else if (parser->blank) {
// Tentatively retain blank lines. The next nonblank line determines
// whether the HTML block continues:
res = true;
} else if (S_last_line_blank(container)) {
// Establish the content indentation lazily so a blank line may follow
// the opening tag. A non-indented line still terminates the block:
if (container->as.html_block.indent == 0 && parser->indent > 0) {
container->as.html_block.indent = parser->indent;
}

res = container->as.html_block.indent > 0 &&
parser->indent >= container->as.html_block.indent;
} else {
// Record the shallowest positive content indentation before a blank
// line. Deeper nested HTML may then continue without changing it:
if (parser->indent > 0 &&
(container->as.html_block.indent == 0 ||
parser->indent < container->as.html_block.indent)) {
container->as.html_block.indent = parser->indent;
}

res = true;
}
break;
}

Expand Down Expand Up @@ -1224,7 +1249,7 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
input, parser->first_nonspace))))) {
*container = add_child(parser, *container, CMARK_NODE_HTML_BLOCK,
parser->first_nonspace + 1);
(*container)->as.html_block_type = matched;
(*container)->as.html_block.type = matched;
// note, we don't adjust parser->offset because the tag is part of the
// text
} else if (!indented && cont_type == CMARK_NODE_PARAGRAPH &&
Expand Down Expand Up @@ -1421,7 +1446,7 @@ static void add_text_to_container(cmark_parser *parser, cmark_node *container,
add_line(container, input, parser);

int matches_end_condition;
switch (container->as.html_block_type) {
switch (container->as.html_block.type) {
case 1:
// </script>, </style>, </pre>
matches_end_condition =
Expand Down
6 changes: 6 additions & 0 deletions src/cmark-gfm.h
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,12 @@ char *cmark_render_latex_with_mem(cmark_node *root, int options, int width, cmar
*/
#define CMARK_OPT_INLINE_CODE_INFO (1 << 19)

/** Allow indented content in type 6 and 7 HTML blocks to continue across
* blank lines. The indentation established by the HTML content must be
* preserved after each blank line.
*/
#define CMARK_OPT_HTML_BLOCK_BLANK_LINES (1 << 20)

/**
* ## Version information
*/
Expand Down
3 changes: 3 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ void print_usage() {
printf(" --full-info-string Include remainder of code block info\n"
" string in a separate attribute.\n");
printf(" --inline-code-info Parse inline code language prefixes\n");
printf(" --html-block-blank-lines Continue indented HTML across blank lines\n");
printf(" --help, -h Print usage information\n");
printf(" --version Print version\n");
}
Expand Down Expand Up @@ -168,6 +169,8 @@ int main(int argc, char *argv[]) {
options |= CMARK_OPT_FULL_INFO_STRING;
} else if (strcmp(argv[i], "--inline-code-info") == 0) {
options |= CMARK_OPT_INLINE_CODE_INFO;
} else if (strcmp(argv[i], "--html-block-blank-lines") == 0) {
options |= CMARK_OPT_HTML_BLOCK_BLANK_LINES;
} else if (strcmp(argv[i], "--table-prefer-style-attributes") == 0) {
options |= CMARK_OPT_TABLE_PREFER_STYLE_ATTRIBUTES;
} else if (strcmp(argv[i], "--strikethrough-double-tilde") == 0) {
Expand Down
7 changes: 6 additions & 1 deletion src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ typedef struct {
int8_t fenced;
} cmark_code;

typedef struct {
int type;
int indent;
} cmark_html_block;

typedef struct {
int level;
bool setext;
Expand Down Expand Up @@ -104,7 +109,7 @@ struct cmark_node {
cmark_heading heading;
cmark_link link;
cmark_custom custom;
int html_block_type;
cmark_html_block html_block;
int cell_index; // For keeping track of TABLE_CELL table alignments
void *opaque;
} as;
Expand Down
Loading