#include #include #include "config.h" #include "node.h" static void S_node_unlink(cmark_node *node); static inline bool S_is_block(cmark_node *node) { if (node == NULL) { return false; } return node->type >= CMARK_NODE_FIRST_BLOCK && node->type <= CMARK_NODE_LAST_BLOCK; } static inline bool S_is_inline(cmark_node *node) { if (node == NULL) { return false; } return node->type >= CMARK_NODE_FIRST_INLINE && node->type <= CMARK_NODE_LAST_INLINE; } static bool S_can_contain(cmark_node *node, cmark_node *child) { cmark_node *cur; if (node == NULL || child == NULL) { return false; } // Verify that child is not an ancestor of node or equal to node. cur = node; do { if (cur == child) { return false; } cur = cur->parent; } while (cur != NULL); if (child->type == CMARK_NODE_DOCUMENT) { return false; } switch (node->type) { case CMARK_NODE_DOCUMENT: case CMARK_NODE_BLOCK_QUOTE: case CMARK_NODE_ITEM: return S_is_block(child) && child->type != CMARK_NODE_ITEM; case CMARK_NODE_LIST: return child->type == CMARK_NODE_ITEM; case CMARK_NODE_PARAGRAPH: case CMARK_NODE_HEADER: case CMARK_NODE_EMPH: case CMARK_NODE_STRONG: case CMARK_NODE_LINK: case CMARK_NODE_IMAGE: return S_is_inline(child); default: break; } return false; } cmark_node *cmark_node_new(cmark_node_type type) { cmark_node *node = (cmark_node *)calloc(1, sizeof(*node)); node->type = type; switch (node->type) { case CMARK_NODE_HEADER: node->as.header.level = 1; break; case CMARK_NODE_LIST: { cmark_list *list = &node->as.list; list->list_type = CMARK_BULLET_LIST; list->start = 1; list->tight = false; break; } default: break; } return node; } // Free a cmark_node list and any children. static void S_free_nodes(cmark_node *e) { cmark_node *next; while (e != NULL) { if (S_is_block(e)) { cmark_strbuf_free(&e->string_content); } switch (e->type) { case CMARK_NODE_CODE_BLOCK: cmark_chunk_free(&e->as.code.info); cmark_chunk_free(&e->as.code.literal); break; case CMARK_NODE_TEXT: case CMARK_NODE_INLINE_HTML: case CMARK_NODE_CODE: case CMARK_NODE_HTML: cmark_chunk_free(&e->as.literal); break; case CMARK_NODE_LINK: case CMARK_NODE_IMAGE: cmark_chunk_free(&e->as.link.url); cmark_chunk_free(&e->as.link.title); break; case CMARK_NODE_CUSTOM_BLOCK: case CMARK_NODE_CUSTOM_INLINE: cmark_chunk_free(&e->as.custom.on_enter); cmark_chunk_free(&e->as.custom.on_exit); break; default: break; } if (e->last_child) { // Splice children into list e->last_child->next = e->next; e->next = e->first_child; } next = e->next; free(e); e = next; } } void cmark_node_free(cmark_node *node) { S_node_unlink(node); node->next = NULL; S_free_nodes(node); } cmark_node_type cmark_node_get_type(cmark_node *node) { if (node == NULL) { return CMARK_NODE_NONE; } else { return node->type; } } const char *cmark_node_get_type_string(cmark_node *node) { if (node == NULL) { return "NONE"; } switch (node->type) { case CMARK_NODE_NONE: return "none"; case CMARK_NODE_DOCUMENT: return "document"; case CMARK_NODE_BLOCK_QUOTE: return "block_quote"; case CMARK_NODE_LIST: return "list"; case CMARK_NODE_ITEM: return "item"; case CMARK_NODE_CODE_BLOCK: return "code_block"; case CMARK_NODE_HTML: return "html"; case CMARK_NODE_CUSTOM_BLOCK: return "raw_block"; case CMARK_NODE_PARAGRAPH: return "paragraph"; case CMARK_NODE_HEADER: return "header"; case CMARK_NODE_HRULE: return "hrule"; case CMARK_NODE_TEXT: return "text"; case CMARK_NODE_SOFTBREAK: return "softbreak"; case CMARK_NODE_LINEBREAK: return "linebreak"; case CMARK_NODE_CODE: return "code"; case CMARK_NODE_INLINE_HTML: return "inline_html"; case CMARK_NODE_CUSTOM_INLINE: return "raw_inline"; case CMARK_NODE_EMPH: return "emph"; case CMARK_NODE_STRONG: return "strong"; case CMARK_NODE_LINK: return "link"; case CMARK_NODE_IMAGE: return "image"; } return ""; } cmark_node *cmark_node_next(cmark_node *node) { if (node == NULL) { return NULL; } else { return node->next; } } cmark_node *cmark_node_previous(cmark_node *node) { if (node == NULL) { return NULL; } else { return node->prev; } } cmark_node *cmark_node_parent(cmark_node *node) { if (node == NULL) { return NULL; } else { return node->parent; } } cmark_node *cmark_node_first_child(cmark_node *node) { if (node == NULL) { return NULL; } else { return node->first_child; } } cmark_node *cmark_node_last_child(cmark_node *node) { if (node == NULL) { return NULL; } else { return node->last_child; } } void *cmark_node_get_user_data(cmark_node *node) { if (node == NULL) { return NULL; } else { return node->user_data; } } int cmark_node_set_user_data(cmark_node *node, void *user_data) { if (node == NULL) { return 0; } node->user_data = user_data; return 1; } const char *cmark_node_get_literal(cmark_node *node) { if (node == NULL) { return NULL; } switch (node->type) { case CMARK_NODE_HTML: case CMARK_NODE_TEXT: case CMARK_NODE_INLINE_HTML: case CMARK_NODE_CODE: return cmark_chunk_to_cstr(&node->as.literal); case CMARK_NODE_CODE_BLOCK: return cmark_chunk_to_cstr(&node->as.code.literal); default: break; } return NULL; } int cmark_node_set_literal(cmark_node *node, const char *content) { if (node == NULL) { return 0; } switch (node->type) { case CMARK_NODE_HTML: case CMARK_NODE_TEXT: case CMARK_NODE_INLINE_HTML: case CMARK_NODE_CODE: cmark_chunk_set_cstr(&node->as.literal, content); return 1; case CMARK_NODE_CODE_BLOCK: cmark_chunk_set_cstr(&node->as.code.literal, content); return 1; default: break; } return 0; } int cmark_node_get_header_level(cmark_node *node) { if (node == NULL) { return 0; } switch (node->type) { case CMARK_NODE_HEADER: return node->as.header.level; default: break; } return 0; } int cmark_node_set_header_level(cmark_node *node, int level) { if (node == NULL || level < 1 || level > 6) { return 0; } switch (node->type) { case CMARK_NODE_HEADER: node->as.header.level = level; return 1; default: break; } return 0; } cmark_list_type cmark_node_get_list_type(cmark_node *node) { if (node == NULL) { return CMARK_NO_LIST; } if (node->type == CMARK_NODE_LIST) { return node->as.list.list_type; } else { return CMARK_NO_LIST; } } int cmark_node_set_list_type(cmark_node *node, cmark_list_type type) { if (!(type == CMARK_BULLET_LIST || type == CMARK_ORDERED_LIST)) { return 0; } if (node == NULL) { return 0; } if (node->type == CMARK_NODE_LIST) { node->as.list.list_type = type; return 1; } else { return 0; } } cmark_delim_type cmark_node_get_list_delim(cmark_node *node) { if (node == NULL) { return CMARK_NO_DELIM; } if (node->type == CMARK_NODE_LIST) { return node->as.list.delimiter; } else { return CMARK_NO_DELIM; } } int cmark_node_set_list_delim(cmark_node *node, cmark_delim_type delim) { if (!(delim == CMARK_PERIOD_DELIM || delim == CMARK_PAREN_DELIM)) { return 0; } if (node == NULL) { return 0; } if (node->type == CMARK_NODE_LIST) { node->as.list.delimiter = delim; return 1; } else { return 0; } } int cmark_node_get_list_start(cmark_node *node) { if (node == NULL) { return 0; } if (node->type == CMARK_NODE_LIST) { return node->as.list.start; } else { return 0; } } int cmark_node_set_list_start(cmark_node *node, int start) { if (node == NULL || start < 0) { return 0; } if (node->type == CMARK_NODE_LIST) { node->as.list.start = start; return 1; } else { return 0; } } int cmark_node_get_list_tight(cmark_node *node) { if (node == NULL) { return 0; } if (node->type == CMARK_NODE_LIST) { return node->as.list.tight; } else { return 0; } } int cmark_node_set_list_tight(cmark_node *node, int tight) { if (node == NULL) { return 0; } if (node->type == CMARK_NODE_LIST) { node->as.list.tight = tight == 1; return 1; } else { return 0; } } const char *cmark_node_get_fence_info(cmark_node *node) { if (node == NULL) { return NULL; } if (node->type == CMARK_NODE_CODE_BLOCK) { return cmark_chunk_to_cstr(&node->as.code.info); } else { return NULL; } } int cmark_node_set_fence_info(cmark_node *node, const char *info) { if (node == NULL) { return 0; } if (node->type == CMARK_NODE_CODE_BLOCK) { cmark_chunk_set_cstr(&node->as.code.info, info); return 1; } else { return 0; } } const char *cmark_node_get_url(cmark_node *node) { if (node == NULL) { return NULL; } switch (node->type) { case CMARK_NODE_LINK: case CMARK_NODE_IMAGE: return cmark_chunk_to_cstr(&node->as.link.url); default: break; } return NULL; } int cmark_node_set_url(cmark_node *node, const char *url) { if (node == NULL) { return 0; } switch (node->type) { case CMARK_NODE_LINK: case CMARK_NODE_IMAGE: cmark_chunk_set_cstr(&node->as.link.url, url); return 1; default: break; } return 0; } const char *cmark_node_get_title(cmark_node *node) { if (node == NULL) { return NULL; } switch (node->type) { case CMARK_NODE_LINK: case CMARK_NODE_IMAGE: return cmark_chunk_to_cstr(&node->as.link.title); default: break; } return NULL; } int cmark_node_set_title(cmark_node *node, const char *title) { if (node == NULL) { return 0; } switch (node->type) { case CMARK_NODE_LINK: case CMARK_NODE_IMAGE: cmark_chunk_set_cstr(&node->as.link.title, title); return 1; default: break; } return 0; } const char *cmark_node_get_on_enter(cmark_node *node) { if (node == NULL) { return NULL; } switch (node->type) { case CMARK_NODE_CUSTOM_INLINE: case CMARK_NODE_CUSTOM_BLOCK: return cmark_chunk_to_cstr(&node->as.custom.on_enter); default: break; } return NULL; } int cmark_node_set_on_enter(cmark_node *node, const char *on_enter) { if (node == NULL) { return 0; } switch (node->type) { case CMARK_NODE_CUSTOM_INLINE: case CMARK_NODE_CUSTOM_BLOCK: cmark_chunk_set_cstr(&node->as.custom.on_enter, on_enter); return 1; default: break; } return 0; } const char *cmark_node_get_on_exit(cmark_node *node) { if (node == NULL) { return NULL; } switch (node->type) { case CMARK_NODE_CUSTOM_INLINE: case CMARK_NODE_CUSTOM_BLOCK: return cmark_chunk_to_cstr(&node->as.custom.on_exit); default: break; } return NULL; } int cmark_node_set_on_exit(cmark_node *node, const char *on_exit) { if (node == NULL) { return 0; } switch (node->type) { case CMARK_NODE_CUSTOM_INLINE: case CMARK_NODE_CUSTOM_BLOCK: cmark_chunk_set_cstr(&node->as.custom.on_exit, on_exit); return 1; default: break; } return 0; } int cmark_node_get_start_line(cmark_node *node) { if (node == NULL) { return 0; } return node->start_line; } int cmark_node_get_start_column(cmark_node *node) { if (node == NULL) { return 0; } return node->start_column; } int cmark_node_get_end_line(cmark_node *node) { if (node == NULL) { return 0; } return node->end_line; } int cmark_node_get_end_column(cmark_node *node) { if (node == NULL) { return 0; } return node->end_column; } // Unlink a node without adjusting its next, prev, and parent pointers. static void S_node_unlink(cmark_node *node) { if (node == NULL) { return; } if (node->prev) { node->prev->next = node->next; } if (node->next) { node->next->prev = node->prev; } // Adjust first_child and last_child of parent. cmark_node *parent = node->parent; if (parent) { if (parent->first_child == node) { parent->first_child = node->next; } if (parent->last_child == node) { parent->last_child = node->prev; } } } void cmark_node_unlink(cmark_node *node) { S_node_unlink(node); node->next = NULL; node->prev = NULL; node->parent = NULL; } int cmark_node_insert_before(cmark_node *node, cmark_node *sibling) { if (node == NULL || sibling == NULL) { return 0; } if (!node->parent || !S_can_contain(node->parent, sibling)) { return 0; } S_node_unlink(sibling); cmark_node *old_prev = node->prev; // Insert 'sibling' between 'old_prev' and 'node'. if (old_prev) { old_prev->next = sibling; } sibling->prev = old_prev; sibling->next = node; node->prev = sibling; // Set new parent. cmark_node *parent = node->parent; sibling->parent = parent; // Adjust first_child of parent if inserted as first child. if (parent && !old_prev) { parent->first_child = sibling; } return 1; } int cmark_node_insert_after(cmark_node *node, cmark_node *sibling) { if (node == NULL || sibling == NULL) { return 0; } if (!node->parent || !S_can_contain(node->parent, sibling)) { return 0; } S_node_unlink(sibling); cmark_node *old_next = node->next; // Insert 'sibling' between 'node' and 'old_next'. if (old_next) { old_next->prev = sibling; } sibling->next = old_next; sibling->prev = node; node->next = sibling; // Set new parent. cmark_node *parent = node->parent; sibling->parent = parent; // Adjust last_child of parent if inserted as last child. if (parent && !old_next) { parent->last_child = sibling; } return 1; } int cmark_node_prepend_child(cmark_node *node, cmark_node *child) { if (!S_can_contain(node, child)) { return 0; } S_node_unlink(child); cmark_node *old_first_child = node->first_child; child->next = old_first_child; child->prev = NULL; child->parent = node; node->first_child = child; if (old_first_child) { old_first_child->prev = child; } else { // Also set last_child if node previously had no children. node->last_child = child; } return 1; } int cmark_node_append_child(cmark_node *node, cmark_node *child) { if (!S_can_contain(node, child)) { return 0; } S_node_unlink(child); cmark_node *old_last_child = node->last_child; child->next = NULL; child->prev = old_last_child; child->parent = node; node->last_child = child; if (old_last_child) { old_last_child->next = child; } else { // Also set first_child if node previously had no children. node->first_child = child; } return 1; } static void S_print_error(FILE *out, cmark_node *node, const char *elem) { if (out == NULL) { return; } fprintf(out, "Invalid '%s' in node type %s at %d:%d\n", elem, cmark_node_get_type_string(node), node->start_line, node->start_column); } int cmark_node_check(cmark_node *node, FILE *out) { cmark_node *cur; int errors = 0; if (!node) { return 0; } cur = node; for (;;) { if (cur->first_child) { if (cur->first_child->prev != NULL) { S_print_error(out, cur->first_child, "prev"); cur->first_child->prev = NULL; ++errors; } if (cur->first_child->parent != cur) { S_print_error(out, cur->first_child, "parent"); cur->first_child->parent = cur; ++errors; } cur = cur->first_child; continue; } next_sibling: if (cur == node) { break; } if (cur->next) { if (cur->next->prev != cur) { S_print_error(out, cur->next, "prev"); cur->next->prev = cur; ++errors; } if (cur->next->parent != cur->parent) { S_print_error(out, cur->next, "parent"); cur->next->parent = cur->parent; ++errors; } cur = cur->next; continue; } if (cur->parent->last_child != cur) { S_print_error(out, cur->parent, "last_child"); cur->parent->last_child = cur; ++errors; } cur = cur->parent; goto next_sibling; } return errors; }