Current section
Files
Jump to
Current section
Files
c_src/src/node.c
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "node.h"
static void
S_node_unlink(cmark_node *node);
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) {
strbuf_free(&e->string_content);
switch (e->type){
case NODE_CODE_BLOCK:
strbuf_free(&e->as.code.info);
break;
case NODE_TEXT:
case NODE_INLINE_HTML:
case NODE_INLINE_CODE:
cmark_chunk_free(&e->as.literal);
break;
case NODE_LINK:
case NODE_IMAGE:
free(e->as.link.url);
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)
{
return node->type;
}
static const char*
S_type_string(cmark_node *node)
{
switch (node->type) {
case CMARK_NODE_DOCUMENT: return "DOCUMENT";
case CMARK_NODE_BLOCK_QUOTE: return "BLOCK_QUOTE";
case CMARK_NODE_LIST: return "LIST";
case CMARK_NODE_LIST_ITEM: return "LIST_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_REFERENCE_DEF: return "REFERENCE_DEF";
case CMARK_NODE_TEXT: return "TEXT";
case CMARK_NODE_SOFTBREAK: return "SOFTBREAK";
case CMARK_NODE_LINEBREAK: return "LINEBREAK";
case CMARK_NODE_INLINE_CODE: return "INLINE_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)
{
return node->next;
}
cmark_node*
cmark_node_previous(cmark_node *node)
{
return node->prev;
}
cmark_node*
cmark_node_parent(cmark_node *node)
{
return node->parent;
}
cmark_node*
cmark_node_first_child(cmark_node *node)
{
return node->first_child;
}
cmark_node*
cmark_node_last_child(cmark_node *node)
{
return node->last_child;
}
static char*
S_strdup(const char *str) {
size_t size = strlen(str) + 1;
char *dup = (char *)malloc(size);
memcpy(dup, str, size);
return dup;
}
const char*
cmark_node_get_string_content(cmark_node *node) {
switch (node->type) {
case NODE_CODE_BLOCK:
case NODE_HTML:
return cmark_strbuf_cstr(&node->string_content);
case NODE_TEXT:
case NODE_INLINE_HTML:
case NODE_INLINE_CODE:
return cmark_chunk_to_cstr(&node->as.literal);
default:
break;
}
return NULL;
}
int
cmark_node_set_string_content(cmark_node *node, const char *content) {
switch (node->type) {
case NODE_CODE_BLOCK:
case NODE_HTML:
cmark_strbuf_sets(&node->string_content, content);
return 1;
case NODE_TEXT:
case NODE_INLINE_HTML:
case NODE_INLINE_CODE:
cmark_chunk_set_cstr(&node->as.literal, content);
return 1;
default:
break;
}
return 0;
}
int
cmark_node_get_header_level(cmark_node *node) {
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 (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->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->type == CMARK_NODE_LIST) {
node->as.list.list_type = type;
return 1;
}
else {
return 0;
}
}
int
cmark_node_get_list_start(cmark_node *node) {
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 (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->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->type == CMARK_NODE_LIST) {
node->as.list.tight = tight;
return 1;
}
else {
return 0;
}
}
const char*
cmark_node_get_fence_info(cmark_node *node) {
if (node->type == NODE_CODE_BLOCK) {
return cmark_strbuf_cstr(&node->as.code.info);
}
else {
return NULL;
}
}
int
cmark_node_set_fence_info(cmark_node *node, const char *info) {
if (node->type == NODE_CODE_BLOCK) {
cmark_strbuf_sets(&node->as.code.info, info);
return 1;
}
else {
return 0;
}
}
const char*
cmark_node_get_url(cmark_node *node) {
switch (node->type) {
case NODE_LINK:
case NODE_IMAGE:
return (char *)node->as.link.url;
default:
break;
}
return NULL;
}
int
cmark_node_set_url(cmark_node *node, const char *url) {
switch (node->type) {
case NODE_LINK:
case NODE_IMAGE:
free(node->as.link.url);
node->as.link.url = (unsigned char *)S_strdup(url);
return 1;
default:
break;
}
return 0;
}
const char*
cmark_node_get_title(cmark_node *node) {
switch (node->type) {
case NODE_LINK:
case NODE_IMAGE:
return (char *)node->as.link.title;
default:
break;
}
return NULL;
}
int
cmark_node_set_title(cmark_node *node, const char *title) {
switch (node->type) {
case NODE_LINK:
case NODE_IMAGE:
free(node->as.link.title);
node->as.link.title = (unsigned char *)S_strdup(title);
return 1;
default:
break;
}
return 0;
}
int
cmark_node_get_start_line(cmark_node *node) {
return node->start_line;
}
int
cmark_node_get_start_column(cmark_node *node) {
return node->start_column;
}
int
cmark_node_get_end_line(cmark_node *node) {
return node->end_line;
}
static inline bool
S_is_block(cmark_node *node) {
return node->type >= CMARK_NODE_FIRST_BLOCK
&& node->type <= CMARK_NODE_LAST_BLOCK;
}
static inline bool
S_is_inline(cmark_node *node) {
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;
// 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_LIST_ITEM:
return S_is_block(child)
&& child->type != CMARK_NODE_LIST_ITEM;
case CMARK_NODE_LIST:
return child->type == CMARK_NODE_LIST_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;
}
// Unlink a node without adjusting its next, prev, and parent pointers.
static void
S_node_unlink(cmark_node *node)
{
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->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->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,
S_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;
while (true) {
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;
}