| #include <stdlib.h> |
| #include <string.h> |
| |
| #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 NODE_CODE_BLOCK: |
| cmark_chunk_free(&e->as.code.info); |
| cmark_chunk_free(&e->as.code.literal); |
| break; |
| case NODE_TEXT: |
| case NODE_INLINE_HTML: |
| case NODE_CODE: |
| case NODE_HTML: |
| cmark_chunk_free(&e->as.literal); |
| break; |
| case NODE_LINK: |
| case NODE_IMAGE: |
| cmark_chunk_free(&e->as.link.url); |
| cmark_chunk_free(&e->as.link.title); |
| 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_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_EMPH: |
| return "emph"; |
| case CMARK_NODE_STRONG: |
| return "strong"; |
| case CMARK_NODE_LINK: |
| return "link"; |
| case CMARK_NODE_IMAGE: |
| return "image"; |
| } |
| |
| return "<unknown>"; |
| } |
| |
| 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 NODE_HTML: |
| case NODE_TEXT: |
| case NODE_INLINE_HTML: |
| case NODE_CODE: |
| return cmark_chunk_to_cstr(&node->as.literal); |
| |
| case 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 NODE_HTML: |
| case NODE_TEXT: |
| case NODE_INLINE_HTML: |
| case NODE_CODE: |
| cmark_chunk_set_cstr(&node->as.literal, content); |
| return 1; |
| |
| case 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 == 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 == 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 NODE_LINK: |
| case 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 NODE_LINK: |
| case 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 NODE_LINK: |
| case 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 NODE_LINK: |
| case NODE_IMAGE: |
| cmark_chunk_set_cstr(&node->as.link.title, title); |
| 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; |
| } |