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
113 changes: 113 additions & 0 deletions api_test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,117 @@ static void inline_code_info(test_batch_runner *runner) {
cmark_node_free(doc);
}

static void node_clone(test_batch_runner *runner) {
static const char markdown[] =
"## Heading\n\nruby:`Object.new` and [link](url 'title')\n";
cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1,
CMARK_OPT_INLINE_CODE_INFO);
cmark_node *clone = cmark_node_clone(doc);

OK(runner, clone != NULL, "clone a document");
OK(runner, clone != doc, "clone is independent");

cmark_node *paragraph = cmark_node_next(cmark_node_first_child(clone));
cmark_node *code = cmark_node_first_child(paragraph);
STR_EQ(runner, cmark_node_get_code_info(code), "ruby",
"clone inline code info");
STR_EQ(runner, cmark_node_get_literal(code), "Object.new",
"clone inline code literal");

cmark_node_set_literal(code, "Class.new");
cmark_node *original_paragraph = cmark_node_next(cmark_node_first_child(doc));
cmark_node *original_code = cmark_node_first_child(original_paragraph);
STR_EQ(runner, cmark_node_get_literal(original_code), "Object.new",
"mutating clone does not change source");

cmark_node *paragraph_clone = cmark_node_clone(original_paragraph);
OK(runner, paragraph_clone != NULL, "clone child node");

cmark_node_free(doc);
char *html = cmark_render_html(clone, CMARK_OPT_DEFAULT, NULL);
STR_EQ(runner, html,
"<h2>Heading</h2>\n"
"<p><code class=\"language-ruby\">Class.new</code> and "
"<a href=\"url\" title=\"title\">link</a></p>\n",
"clone remains valid after source is freed");
free(html);
cmark_node_free(clone);

html = cmark_render_html(paragraph_clone, CMARK_OPT_DEFAULT, NULL);
STR_EQ(runner, html,
"<p><code class=\"language-ruby\">Object.new</code> and "
"<a href=\"url\" title=\"title\">link</a></p>\n",
"render cloned child node");
free(html);
cmark_node_free(paragraph_clone);

OK(runner, cmark_node_clone(NULL) == NULL, "cloning null returns null");
}

static void node_clone_extensions(test_batch_runner *runner) {
static const char markdown[] =
"| Left | Right |\n| :--- | ---: |\n| A | B |\n";

cmark_gfm_core_extensions_ensure_registered();
cmark_syntax_extension *table_extension =
cmark_find_syntax_extension("table");
cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT);
cmark_parser_attach_syntax_extension(parser, table_extension);
cmark_parser_feed(parser, markdown, sizeof(markdown) - 1);
cmark_node *doc = cmark_parser_finish(parser);
cmark_parser_free(parser);

cmark_node *clone = cmark_node_clone(doc);
OK(runner, clone != NULL, "clone extension nodes");

cmark_node *table = cmark_node_first_child(doc);
cmark_node *cloned_table = cmark_node_first_child(clone);
INT_EQ(runner, cmark_gfm_extensions_get_table_columns(cloned_table), 2,
"clone table column count");

uint8_t *alignments = cmark_gfm_extensions_get_table_alignments(table);
uint8_t *cloned_alignments =
cmark_gfm_extensions_get_table_alignments(cloned_table);
OK(runner, cloned_alignments != alignments,
"clone table alignments independently");
INT_EQ(runner, cloned_alignments[0], alignments[0],
"clone left table alignment");
INT_EQ(runner, cloned_alignments[1], alignments[1],
"clone right table alignment");

cmark_node *header = cmark_node_first_child(cloned_table);
OK(runner, cmark_gfm_extensions_get_table_row_is_header(header),
"clone table header metadata");

cmark_node_free(doc);
INT_EQ(runner, cmark_gfm_extensions_get_table_columns(cloned_table), 2,
"cloned table remains valid after source is freed");

cmark_mem *mem = cmark_get_default_mem_allocator();
cmark_llist *extensions = NULL;
extensions = cmark_llist_append(mem, extensions, table_extension);
char *html = cmark_render_html(clone, CMARK_OPT_DEFAULT, extensions);
STR_EQ(runner, html,
"<table>\n"
"<thead>\n"
"<tr>\n"
"<th align=\"left\">Left</th>\n"
"<th align=\"right\">Right</th>\n"
"</tr>\n"
"</thead>\n"
"<tbody>\n"
"<tr>\n"
"<td align=\"left\">A</td>\n"
"<td align=\"right\">B</td>\n"
"</tr>\n"
"</tbody>\n"
"</table>\n",
"render cloned table");
free(html);
cmark_llist_free(mem, extensions);
cmark_node_free(clone);
}

static void node_check(test_batch_runner *runner) {
// Construct an incomplete tree.
cmark_node *doc = cmark_node_new(CMARK_NODE_DOCUMENT);
Expand Down Expand Up @@ -1264,6 +1375,8 @@ int main() {
constructor(runner);
accessors(runner);
inline_code_info(runner);
node_clone(runner);
node_clone_extensions(runner);
node_check(runner);
iterator(runner);
iterator_delete(runner);
Expand Down
24 changes: 24 additions & 0 deletions extensions/table.c
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,29 @@ static void opaque_free(cmark_syntax_extension *self, cmark_mem *mem, cmark_node
}
}

static void opaque_copy(cmark_syntax_extension *self, cmark_mem *mem,
cmark_node *node, cmark_node *source) {
if (node->type == CMARK_NODE_TABLE) {
node_table *table = (node_table *)node->as.opaque;
node_table *source_table = (node_table *)source->as.opaque;

table->n_columns = source_table->n_columns;
table->n_rows = source_table->n_rows;
table->n_nonempty_cells = source_table->n_nonempty_cells;

if (source_table->alignments) {
table->alignments = mem->calloc(source_table->n_columns, sizeof(uint8_t));
memcpy(table->alignments, source_table->alignments,
source_table->n_columns * sizeof(uint8_t));
}
} else if (node->type == CMARK_NODE_TABLE_ROW) {
*(node_table_row *)node->as.opaque = *(node_table_row *)source->as.opaque;
} else if (node->type == CMARK_NODE_TABLE_CELL) {
mem->free(node->as.opaque);
node->as.cell_index = source->as.cell_index;
}
}

static int escape(cmark_syntax_extension *self, cmark_node *node, int c) {
return
node->type != CMARK_NODE_TABLE &&
Expand All @@ -867,6 +890,7 @@ cmark_syntax_extension *create_table_extension(void) {
cmark_syntax_extension_set_html_render_func(self, html_render);
cmark_syntax_extension_set_opaque_alloc_func(self, opaque_alloc);
cmark_syntax_extension_set_opaque_free_func(self, opaque_free);
cmark_syntax_extension_set_opaque_copy_func(self, opaque_copy);
cmark_syntax_extension_set_commonmark_escape_func(self, escape);
CMARK_NODE_TABLE = cmark_syntax_extension_add_node(0);
CMARK_NODE_TABLE_ROW = cmark_syntax_extension_add_node(0);
Expand Down
10 changes: 10 additions & 0 deletions src/cmark-gfm-extension_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ typedef void (*cmark_opaque_free_func) (cmark_syntax_extension *extension,
cmark_mem *mem,
cmark_node *node);

typedef void (*cmark_opaque_copy_func)(cmark_syntax_extension *extension,
cmark_mem *mem, cmark_node *node,
cmark_node *source);

/** Free a cmark_syntax_extension.
*/
CMARK_GFM_EXPORT
Expand Down Expand Up @@ -406,6 +410,12 @@ CMARK_GFM_EXPORT
void cmark_syntax_extension_set_opaque_free_func(cmark_syntax_extension *extension,
cmark_opaque_free_func func);

/** See the documentation for 'cmark_syntax_extension'
*/
CMARK_GFM_EXPORT
void cmark_syntax_extension_set_opaque_copy_func(
cmark_syntax_extension *extension, cmark_opaque_copy_func func);

/** See the documentation for 'cmark_syntax_extension'
*/
CMARK_GFM_EXPORT
Expand Down
6 changes: 6 additions & 0 deletions src/cmark-gfm.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ CMARK_GFM_EXPORT cmark_node *cmark_node_new_with_mem_and_ext(cmark_node_type typ
cmark_mem *mem,
cmark_syntax_extension *extension);

/** Creates an independent deep copy of 'node' and all its children.
* User data is not copied. Returns NULL when extension-specific node data
* cannot be copied.
*/
CMARK_GFM_EXPORT cmark_node *cmark_node_clone(cmark_node *node);

/** Frees the memory allocated for a node and any children.
*/
CMARK_GFM_EXPORT void cmark_node_free(cmark_node *node);
Expand Down
5 changes: 3 additions & 2 deletions src/html.c
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,

case CMARK_NODE_PARAGRAPH:
parent = cmark_node_parent(node);
grandparent = cmark_node_parent(parent);
grandparent = parent ? cmark_node_parent(parent) : NULL;
if (grandparent != NULL && grandparent->type == CMARK_NODE_LIST) {
tight = grandparent->as.list.tight;
} else {
Expand All @@ -316,7 +316,8 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
cmark_html_render_sourcepos(node, html, options);
cmark_strbuf_putc(html, '>');
} else {
if (parent->type == CMARK_NODE_FOOTNOTE_DEFINITION && node->next == NULL) {
if (parent && parent->type == CMARK_NODE_FOOTNOTE_DEFINITION &&
node->next == NULL) {
cmark_strbuf_putc(html, ' ');
S_put_footnote_backref(renderer, html, parent);
}
Expand Down
138 changes: 138 additions & 0 deletions src/node.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,144 @@ cmark_node *cmark_node_new(cmark_node_type type) {
return cmark_node_new_with_ext(type, NULL);
}

static cmark_chunk S_clone_chunk(cmark_mem *mem, const cmark_chunk *source) {
cmark_chunk clone = {NULL, source->len, 1};
clone.data = (unsigned char *)mem->calloc(source->len + 1, 1);
if (source->len > 0) {
memcpy(clone.data, source->data, source->len);
}
return clone;
}

static cmark_node *S_clone_node(cmark_node *node) {
cmark_mem *mem = NODE_MEM(node);
cmark_node *clone = cmark_node_new_with_mem_and_ext(
(cmark_node_type)node->type, mem, node->extension);

if (!clone) {
return NULL;
}

cmark_strbuf_set(&clone->content, node->content.ptr, node->content.size);
clone->start_line = node->start_line;
clone->start_column = node->start_column;
clone->end_line = node->end_line;
clone->end_column = node->end_column;
clone->internal_offset = node->internal_offset;
clone->flags = node->flags;
clone->footnote = node->footnote;

switch (node->type) {
case CMARK_NODE_HEADING:
clone->as.heading = node->as.heading;
break;
case CMARK_NODE_LIST:
case CMARK_NODE_ITEM:
clone->as.list = node->as.list;
break;
case CMARK_NODE_CODE_BLOCK:
case CMARK_NODE_FRONT_MATTER:
case CMARK_NODE_CODE:
clone->as.code = node->as.code;
clone->as.code.info = S_clone_chunk(mem, &node->as.code.info);
clone->as.code.literal = S_clone_chunk(mem, &node->as.code.literal);
break;
case CMARK_NODE_TEXT:
case CMARK_NODE_HTML_INLINE:
case CMARK_NODE_HTML_BLOCK:
case CMARK_NODE_FOOTNOTE_REFERENCE:
case CMARK_NODE_FOOTNOTE_DEFINITION:
clone->as.literal = S_clone_chunk(mem, &node->as.literal);
break;
case CMARK_NODE_LINK:
case CMARK_NODE_IMAGE:
clone->as.link.url = S_clone_chunk(mem, &node->as.link.url);
clone->as.link.title = S_clone_chunk(mem, &node->as.link.title);
break;
case CMARK_NODE_CUSTOM_BLOCK:
case CMARK_NODE_CUSTOM_INLINE:
clone->as.custom.on_enter = S_clone_chunk(mem, &node->as.custom.on_enter);
clone->as.custom.on_exit = S_clone_chunk(mem, &node->as.custom.on_exit);
break;
default:
break;
}

if (node->extension && node->extension->opaque_alloc_func) {
if (!node->extension->opaque_copy_func) {
cmark_node_free(clone);
return NULL;
}

node->extension->opaque_copy_func(node->extension, mem, clone, node);
}

for (cmark_node *child = node->first_child; child; child = child->next) {
cmark_node *child_clone = S_clone_node(child);
if (!child_clone) {
cmark_node_free(clone);
return NULL;
}

if (!cmark_node_append_child(clone, child_clone)) {
cmark_node_free(child_clone);
cmark_node_free(clone);
return NULL;
}
}

return clone;
}

static cmark_node *S_find_clone(cmark_node *source, cmark_node *clone,
cmark_node *target) {
if (source == target) {
return clone;
}

cmark_node *source_child = source->first_child;
cmark_node *clone_child = clone->first_child;
while (source_child && clone_child) {
cmark_node *result = S_find_clone(source_child, clone_child, target);
if (result) {
return result;
}
source_child = source_child->next;
clone_child = clone_child->next;
}

return NULL;
}

static void S_clone_footnote_links(cmark_node *source, cmark_node *clone,
cmark_node *source_root,
cmark_node *clone_root) {
if (source->parent_footnote_def) {
clone->parent_footnote_def =
S_find_clone(source_root, clone_root, source->parent_footnote_def);
}

cmark_node *source_child = source->first_child;
cmark_node *clone_child = clone->first_child;
while (source_child && clone_child) {
S_clone_footnote_links(source_child, clone_child, source_root, clone_root);
source_child = source_child->next;
clone_child = clone_child->next;
}
}

cmark_node *cmark_node_clone(cmark_node *node) {
if (!node) {
return NULL;
}

cmark_node *clone = S_clone_node(node);
if (clone) {
S_clone_footnote_links(node, clone, node, clone);
}
return clone;
}

static void free_node_as(cmark_node *node) {
switch (node->type) {
case CMARK_NODE_CODE_BLOCK:
Expand Down
5 changes: 5 additions & 0 deletions src/syntax_extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ void cmark_syntax_extension_set_opaque_free_func(cmark_syntax_extension *extensi
extension->opaque_free_func = func;
}

void cmark_syntax_extension_set_opaque_copy_func(
cmark_syntax_extension *extension, cmark_opaque_copy_func func) {
extension->opaque_copy_func = func;
}

void cmark_syntax_extension_set_commonmark_escape_func(cmark_syntax_extension *extension,
cmark_commonmark_escape_func func) {
extension->commonmark_escape_func = func;
Expand Down
1 change: 1 addition & 0 deletions src/syntax_extension.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct cmark_syntax_extension {
cmark_postprocess_func postprocess_func;
cmark_opaque_alloc_func opaque_alloc_func;
cmark_opaque_free_func opaque_free_func;
cmark_opaque_copy_func opaque_copy_func;
cmark_commonmark_escape_func commonmark_escape_func;
};

Expand Down
Loading