static void md_block_external_images(cmark_iter *const iter) { for(;;) { cmark_event_type const event = cmark_iter_next(iter); if(CMARK_EVENT_DONE == event) break; if(CMARK_EVENT_EXIT != event) continue; cmark_node *const node = cmark_iter_get_node(iter); if(CMARK_NODE_IMAGE != cmark_node_get_type(node)) continue; char const *const URI = cmark_node_get_url(node); if(URI) { if(0 == strncasecmp(URI, STR_LEN("hash:"))) continue; if(0 == strncasecmp(URI, STR_LEN("data:"))) continue; } cmark_node *link = cmark_node_new(CMARK_NODE_LINK); cmark_node *text = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_url(link, URI); for(;;) { cmark_node *child = cmark_node_first_child(node); if(!child) break; cmark_node_append_child(link, child); } if(cmark_node_first_child(link)) { cmark_node_set_literal(text, " (external image)"); } else { cmark_node_set_literal(text, "(external image)"); } cmark_node_append_child(link, text); cmark_node_insert_before(node, link); cmark_node_free(node); } }
static cmark_node *param_ref_match(cmark_syntax_extension *self, cmark_parser *parser, cmark_node *parent, cmark_inline_parser *inline_parser) { cmark_node *emph, *text_node; char *param_name; char prev_char; ParsingContext context; context.parser = inline_parser; context.allow_dashes = 0; prev_char = cmark_inline_parser_peek_at( inline_parser, cmark_inline_parser_get_offset(inline_parser) - 1); if (prev_char && prev_char != ' ' && prev_char != '\t' && prev_char != '\n') return NULL; cmark_inline_parser_advance_offset(inline_parser); param_name = cmark_inline_parser_take_while(inline_parser, (CMarkInlinePredicate) is_valid_c, &context); if (!param_name) return NULL; emph = cmark_node_new(CMARK_NODE_EMPH); text_node = cmark_node_new(CMARK_NODE_TEXT); cmark_node_append_child(emph, text_node); cmark_node_set_literal(text_node, param_name); free(param_name); return emph; }
static CMARK_INLINE cmark_node *make_autolink(cmark_mem *mem, cmark_chunk url, int is_email) { cmark_node *link = make_simple(mem, CMARK_NODE_LINK); link->as.link.url = cmark_clean_autolink(mem, &url, is_email); link->as.link.title = cmark_chunk_literal(""); cmark_node_append_child(link, make_str_with_entities(mem, &url)); return link; }
static void md_autolink(cmark_iter *const iter) { regex_t linkify[1]; // <http://daringfireball.net/2010/07/improved_regex_for_matching_urls> // Painstakingly ported to POSIX int rc = regcomp(linkify, "([a-z][a-z0-9_-]+:(/{1,3}|[a-z0-9%])|www[0-9]{0,3}[.]|[a-z0-9.-]+[.][a-z]{2,4}/)([^[:space:]()<>]+|\\(([^[:space:]()<>]+|(\\([^[:space:]()<>]+\\)))*\\))+(\\(([^[:space:]()<>]+|(\\([^[:space:]()<>]+\\)))*\\)|[^][[:space:]`!(){};:'\".,<>?«»“”‘’])", REG_ICASE | REG_EXTENDED); assert(0 == rc); for(;;) { cmark_event_type const event = cmark_iter_next(iter); if(CMARK_EVENT_DONE == event) break; if(CMARK_EVENT_ENTER != event) continue; cmark_node *const node = cmark_iter_get_node(iter); if(CMARK_NODE_TEXT != cmark_node_get_type(node)) continue; char const *const str = cmark_node_get_literal(node); char const *pos = str; regmatch_t match; while(0 == regexec(linkify, pos, 1, &match, 0)) { regoff_t const loc = match.rm_so; regoff_t const len = match.rm_eo - match.rm_so; char *pfx = strndup(pos, loc); char *link_abs = strndup(pos+loc, len); char *link_rel = aasprintf("/history/%s", link_abs); assert(pfx); assert(link_abs); assert(link_rel); cmark_node *text = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(text, pfx); cmark_node *link = cmark_node_new(CMARK_NODE_LINK); cmark_node_set_url(link, link_rel); cmark_node *sup = superscript("^", "", link_abs); cmark_node *face = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(face, link_abs); cmark_node_append_child(link, face); cmark_node_insert_before(node, text); cmark_node_insert_before(node, link); cmark_node_insert_before(node, sup); free(pfx); pfx = NULL; free(link_abs); link_abs = NULL; free(link_rel); link_rel = NULL; pos += loc+len; } if(str != pos) { cmark_node *text = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(text, pos); cmark_node_insert_before(node, text); cmark_node_free(node); } } regfree(linkify); }
static delimiter *S_insert_emph(subject *subj, delimiter *opener, delimiter *closer) { delimiter *delim, *tmp_delim; bufsize_t use_delims; cmark_node *opener_inl = opener->inl_text; cmark_node *closer_inl = closer->inl_text; bufsize_t opener_num_chars = opener_inl->as.literal.len; bufsize_t closer_num_chars = closer_inl->as.literal.len; cmark_node *tmp, *tmpnext, *emph; // calculate the actual number of characters used from this closer use_delims = (closer_num_chars >= 2 && opener_num_chars >=2) ? 2 : 1; // remove used characters from associated inlines. opener_num_chars -= use_delims; closer_num_chars -= use_delims; opener_inl->as.literal.len = opener_num_chars; closer_inl->as.literal.len = closer_num_chars; // free delimiters between opener and closer delim = closer->previous; while (delim != NULL && delim != opener) { tmp_delim = delim->previous; remove_delimiter(subj, delim); delim = tmp_delim; } // create new emph or strong, and splice it in to our inlines // between the opener and closer emph = use_delims == 1 ? make_emph(subj->mem) : make_strong(subj->mem); tmp = opener_inl->next; while (tmp && tmp != closer_inl) { tmpnext = tmp->next; cmark_node_append_child(emph, tmp); tmp = tmpnext; } cmark_node_insert_after(opener_inl, emph); // if opener has 0 characters, remove it and its associated inline if (opener_num_chars == 0) { cmark_node_free(opener_inl); remove_delimiter(subj, opener); } // if closer has 0 characters, remove it and its associated inline if (closer_num_chars == 0) { // remove empty closer inline cmark_node_free(closer_inl); // remove closer from list tmp_delim = closer->next; remove_delimiter(subj, closer); closer = tmp_delim; } return closer; }
static cmark_node *www_match(cmark_parser *parser, cmark_node *parent, cmark_inline_parser *inline_parser) { cmark_chunk *chunk = cmark_inline_parser_get_chunk(inline_parser); size_t max_rewind = cmark_inline_parser_get_offset(inline_parser); uint8_t *data = chunk->data + max_rewind; size_t size = chunk->len - max_rewind; int start = cmark_inline_parser_get_column(inline_parser); size_t link_end; if (max_rewind > 0 && strchr("*_~(", data[-1]) == NULL && !cmark_isspace(data[-1])) return 0; if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0) return 0; link_end = check_domain(data, size, 0); if (link_end == 0) return NULL; while (link_end < size && !cmark_isspace(data[link_end])) link_end++; link_end = autolink_delim(data, link_end); if (link_end == 0) return NULL; cmark_inline_parser_set_offset(inline_parser, (int)(max_rewind + link_end)); cmark_node *node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem); cmark_strbuf buf; cmark_strbuf_init(parser->mem, &buf, 10); cmark_strbuf_puts(&buf, "http://"); cmark_strbuf_put(&buf, data, (bufsize_t)link_end); node->as.link.url = cmark_chunk_buf_detach(&buf); cmark_node *text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); text->as.literal = cmark_chunk_dup(chunk, (bufsize_t)max_rewind, (bufsize_t)link_end); cmark_node_append_child(node, text); node->start_line = text->start_line = node->end_line = text->end_line = cmark_inline_parser_get_line(inline_parser); node->start_column = text->start_column = start - 1; node->end_column = text->end_column = cmark_inline_parser_get_column(inline_parser) - 1; return node; }
static cmark_node *superscript(char const *const label, char const *const title, char const *const link) { cmark_node *node = cmark_node_new(CMARK_NODE_CUSTOM_INLINE); cmark_node *a = cmark_node_new(CMARK_NODE_LINK); cmark_node_set_url(a, link); cmark_node_set_title(a, title); cmark_node *op = cmark_node_new(CMARK_NODE_INLINE_HTML); cmark_node_set_literal(op, "<sup>["); cmark_node *ed = cmark_node_new(CMARK_NODE_INLINE_HTML); cmark_node_set_literal(ed, "]</sup>"); cmark_node *face = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(face, label); cmark_node_append_child(a, face); cmark_node_append_child(node, op); cmark_node_append_child(node, a); cmark_node_append_child(node, ed); return node; }
static cmark_node *url_match(cmark_parser *parser, cmark_node *parent, cmark_inline_parser *inline_parser) { size_t link_end, domain_len; int rewind = 0; cmark_chunk *chunk = cmark_inline_parser_get_chunk(inline_parser); int max_rewind = cmark_inline_parser_get_offset(inline_parser); uint8_t *data = chunk->data + max_rewind; size_t size = chunk->len - max_rewind; if (size < 4 || data[1] != '/' || data[2] != '/') return 0; while (rewind < max_rewind && cmark_isalpha(data[-rewind - 1])) rewind++; if (!sd_autolink_issafe(data - rewind, size + rewind)) return 0; link_end = strlen("://"); domain_len = check_domain(data + link_end, size - link_end, 1); if (domain_len == 0) return 0; link_end += domain_len; while (link_end < size && !cmark_isspace(data[link_end])) link_end++; link_end = autolink_delim(data, link_end); if (link_end == 0) return NULL; cmark_inline_parser_set_offset(inline_parser, (int)(max_rewind + link_end)); cmark_node_unput(parent, rewind); cmark_node *node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem); cmark_chunk url = cmark_chunk_dup(chunk, max_rewind - rewind, (bufsize_t)(link_end + rewind)); node->as.link.url = url; cmark_node *text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); text->as.literal = url; cmark_node_append_child(node, text); return node; }
static void md_escape(cmark_iter *const iter) { for(;;) { cmark_event_type const event = cmark_iter_next(iter); if(CMARK_EVENT_DONE == event) break; if(CMARK_EVENT_ENTER != event) continue; cmark_node *const node = cmark_iter_get_node(iter); if(CMARK_NODE_HTML != cmark_node_get_type(node)) continue; char const *const str = cmark_node_get_literal(node); cmark_node *p = cmark_node_new(CMARK_NODE_PARAGRAPH); cmark_node *text = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(text, str); cmark_node_append_child(p, text); cmark_node_insert_before(node, p); cmark_node_free(node); } }
static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser, cmark_inline_parser *inline_parser, delimiter *opener, delimiter *closer) { cmark_node *strikethrough; cmark_node *tmp, *next; delimiter *delim, *tmp_delim; delimiter *res = closer->next; strikethrough = opener->inl_text; if (!cmark_node_set_type(strikethrough, CMARK_NODE_STRIKETHROUGH)) goto done; cmark_node_set_syntax_extension(strikethrough, self); cmark_node_set_string_content(strikethrough, "~"); tmp = cmark_node_next(opener->inl_text); while (tmp) { if (tmp == closer->inl_text) break; next = cmark_node_next(tmp); cmark_node_append_child(strikethrough, tmp); tmp = next; } strikethrough->end_column = closer->inl_text->start_column + closer->inl_text->as.literal.len - 1; cmark_node_free(closer->inl_text); delim = closer; while (delim != NULL && delim != opener) { tmp_delim = delim->previous; cmark_inline_parser_remove_delimiter(inline_parser, delim); delim = tmp_delim; } cmark_inline_parser_remove_delimiter(inline_parser, opener); done: return res; }
static void test_content(test_batch_runner *runner, cmark_node_type type, int allowed_content) { cmark_node *node = cmark_node_new(type); for (int i = 0; i < num_node_types; ++i) { cmark_node_type child_type = node_types[i]; cmark_node *child = cmark_node_new(child_type); int got = cmark_node_append_child(node, child); int expected = (allowed_content >> child_type) & 1; INT_EQ(runner, got, expected, "add %d as child of %d", child_type, type); cmark_node_free(child); } cmark_node_free(node); }
// Parse an inline, advancing subject, and add it as a child of parent. // Return 0 if no inline can be parsed, 1 otherwise. static int parse_inline(subject* subj, cmark_node * parent, int options) { cmark_node* new_inl = NULL; cmark_chunk contents; unsigned char c; int endpos; c = peek_char(subj); if (c == 0) { return 0; } switch(c) { case '\n': new_inl = handle_newline(subj); break; case '`': new_inl = handle_backticks(subj); break; case '\\': new_inl = handle_backslash(subj); break; case '&': new_inl = handle_entity(subj); break; case '<': new_inl = handle_pointy_brace(subj); break; case '*': case '_': case '\'': case '"': new_inl = handle_delim(subj, c, options & CMARK_OPT_SMART); break; case '-': new_inl = handle_hyphen(subj, options & CMARK_OPT_SMART); break; case '.': new_inl = handle_period(subj, options & CMARK_OPT_SMART); break; case '[': advance(subj); new_inl = make_str(cmark_chunk_literal("[")); push_delimiter(subj, '[', true, false, new_inl); break; case ']': new_inl = handle_close_bracket(subj, parent); break; case '!': advance(subj); if (peek_char(subj) == '[') { advance(subj); new_inl = make_str(cmark_chunk_literal("![")); push_delimiter(subj, '!', false, true, new_inl); } else { new_inl = make_str(cmark_chunk_literal("!")); } break; default: endpos = subject_find_special_char(subj, options); contents = cmark_chunk_dup(&subj->input, subj->pos, endpos - subj->pos); subj->pos = endpos; // if we're at a newline, strip trailing spaces. if (peek_char(subj) == '\n') { cmark_chunk_rtrim(&contents); } new_inl = make_str(contents); } if (new_inl != NULL) { cmark_node_append_child(parent, new_inl); } return 1; }
static void postprocess_text(cmark_parser *parser, cmark_node *text, int offset) { size_t link_end; uint8_t *data = text->as.literal.data, *at; size_t size = text->as.literal.len; int rewind, max_rewind, nb = 0, np = 0, ns = 0; if (offset < 0 || (size_t)offset >= size) return; data += offset; size -= offset; at = (uint8_t *)memchr(data, '@', size); if (!at) return; max_rewind = (int)(at - data); data += max_rewind; size -= max_rewind; for (rewind = 0; rewind < max_rewind; ++rewind) { uint8_t c = data[-rewind - 1]; if (cmark_isalnum(c)) continue; if (strchr(".+-_", c) != NULL) continue; if (c == '/') ns++; break; } if (rewind == 0 || ns > 0) { postprocess_text(parser, text, max_rewind + 1 + offset); return; } for (link_end = 0; link_end < size; ++link_end) { uint8_t c = data[link_end]; if (cmark_isalnum(c)) continue; if (c == '@') nb++; else if (c == '.' && link_end < size - 1 && cmark_isalnum(data[link_end + 1])) np++; else if (c != '-' && c != '_') break; } if (link_end < 2 || nb != 1 || np == 0 || (!cmark_isalpha(data[link_end - 1]) && data[link_end - 1] != '.')) { postprocess_text(parser, text, max_rewind + 1 + offset); return; } link_end = autolink_delim(data, link_end); if (link_end == 0) { postprocess_text(parser, text, max_rewind + 1 + offset); return; } cmark_chunk_to_cstr(parser->mem, &text->as.literal); cmark_node *link_node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem); cmark_strbuf buf; cmark_strbuf_init(parser->mem, &buf, 10); cmark_strbuf_puts(&buf, "mailto:"); cmark_strbuf_put(&buf, data - rewind, (bufsize_t)(link_end + rewind)); link_node->as.link.url = cmark_chunk_buf_detach(&buf); cmark_node *link_text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); cmark_chunk email = cmark_chunk_dup( &text->as.literal, offset + max_rewind - rewind, (bufsize_t)(link_end + rewind)); cmark_chunk_to_cstr(parser->mem, &email); link_text->as.literal = email; cmark_node_append_child(link_node, link_text); cmark_node_insert_after(text, link_node); cmark_node *post = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); post->as.literal = cmark_chunk_dup(&text->as.literal, (bufsize_t)(offset + max_rewind + link_end), (bufsize_t)(size - link_end)); cmark_chunk_to_cstr(parser->mem, &post->as.literal); cmark_node_insert_after(link_node, post); text->as.literal.len = offset + max_rewind - rewind; text->as.literal.data[text->as.literal.len] = 0; postprocess_text(parser, post, 0); }
// Return a link, an image, or a literal close bracket. static cmark_node *handle_close_bracket(subject *subj) { bufsize_t initial_pos, after_link_text_pos; bufsize_t endurl, starttitle, endtitle, endall; bufsize_t sps, n; cmark_reference *ref = NULL; cmark_chunk url_chunk, title_chunk; cmark_chunk url, title; bracket *opener; cmark_node *inl; cmark_chunk raw_label; int found_label; cmark_node *tmp, *tmpnext; bool is_image; advance(subj); // advance past ] initial_pos = subj->pos; // get last [ or ![ opener = subj->last_bracket; if (opener == NULL) { return make_str(subj->mem, cmark_chunk_literal("]")); } if (!opener->active) { // take delimiter off stack pop_bracket(subj); return make_str(subj->mem, cmark_chunk_literal("]")); } // If we got here, we matched a potential link/image text. // Now we check to see if it's a link/image. is_image = opener->image; after_link_text_pos = subj->pos; // First, look for an inline link. if (peek_char(subj) == '(' && ((sps = scan_spacechars(&subj->input, subj->pos + 1)) > -1) && ((n = manual_scan_link_url(&subj->input, subj->pos + 1 + sps, &url_chunk)) > -1)) { // try to parse an explicit link: endurl = subj->pos + 1 + sps + n; starttitle = endurl + scan_spacechars(&subj->input, endurl); // ensure there are spaces btw url and title endtitle = (starttitle == endurl) ? starttitle : starttitle + scan_link_title(&subj->input, starttitle); endall = endtitle + scan_spacechars(&subj->input, endtitle); if (peek_at(subj, endall) == ')') { subj->pos = endall + 1; title_chunk = cmark_chunk_dup(&subj->input, starttitle, endtitle - starttitle); url = cmark_clean_url(subj->mem, &url_chunk); title = cmark_clean_title(subj->mem, &title_chunk); cmark_chunk_free(subj->mem, &url_chunk); cmark_chunk_free(subj->mem, &title_chunk); goto match; } else { // it could still be a shortcut reference link subj->pos = after_link_text_pos; } } // Next, look for a following [link label] that matches in refmap. // skip spaces raw_label = cmark_chunk_literal(""); found_label = link_label(subj, &raw_label); if (!found_label) { // If we have a shortcut reference link, back up // to before the spacse we skipped. subj->pos = initial_pos; } if ((!found_label || raw_label.len == 0) && !opener->bracket_after) { cmark_chunk_free(subj->mem, &raw_label); raw_label = cmark_chunk_dup(&subj->input, opener->position, initial_pos - opener->position - 1); found_label = true; } if (found_label) { ref = cmark_reference_lookup(subj->refmap, &raw_label); cmark_chunk_free(subj->mem, &raw_label); } if (ref != NULL) { // found url = chunk_clone(subj->mem, &ref->url); title = chunk_clone(subj->mem, &ref->title); goto match; } else { goto noMatch; } noMatch: // If we fall through to here, it means we didn't match a link: pop_bracket(subj); // remove this opener from delimiter list subj->pos = initial_pos; return make_str(subj->mem, cmark_chunk_literal("]")); match: inl = make_simple(subj->mem, is_image ? CMARK_NODE_IMAGE : CMARK_NODE_LINK); inl->as.link.url = url; inl->as.link.title = title; cmark_node_insert_before(opener->inl_text, inl); // Add link text: tmp = opener->inl_text->next; while (tmp) { tmpnext = tmp->next; cmark_node_append_child(inl, tmp); tmp = tmpnext; } // Free the bracket [: cmark_node_free(opener->inl_text); process_emphasis(subj, opener->previous_delimiter); pop_bracket(subj); // Now, if we have a link, we also want to deactivate earlier link // delimiters. (This code can be removed if we decide to allow links // inside links.) if (!is_image) { opener = subj->last_bracket; while (opener != NULL) { if (!opener->image) { if (!opener->active) { break; } else { opener->active = false; } } opener = opener->previous; } } return NULL; }
void hierarchy(test_batch_runner *runner) { cmark_node *bquote1 = cmark_node_new(CMARK_NODE_BLOCK_QUOTE); cmark_node *bquote2 = cmark_node_new(CMARK_NODE_BLOCK_QUOTE); cmark_node *bquote3 = cmark_node_new(CMARK_NODE_BLOCK_QUOTE); OK(runner, cmark_node_append_child(bquote1, bquote2), "append bquote2"); OK(runner, cmark_node_append_child(bquote2, bquote3), "append bquote3"); OK(runner, !cmark_node_append_child(bquote3, bquote3), "adding a node as child of itself fails"); OK(runner, !cmark_node_append_child(bquote3, bquote1), "adding a parent as child fails"); cmark_node_free(bquote1); int max_node_type = CMARK_NODE_LAST_BLOCK > CMARK_NODE_LAST_INLINE ? CMARK_NODE_LAST_BLOCK : CMARK_NODE_LAST_INLINE; OK(runner, max_node_type < 32, "all node types < 32"); int list_item_flag = 1 << CMARK_NODE_ITEM; int top_level_blocks = (1 << CMARK_NODE_BLOCK_QUOTE) | (1 << CMARK_NODE_LIST) | (1 << CMARK_NODE_CODE_BLOCK) | (1 << CMARK_NODE_HTML) | (1 << CMARK_NODE_PARAGRAPH) | (1 << CMARK_NODE_HEADER) | (1 << CMARK_NODE_HRULE); int all_inlines = (1 << CMARK_NODE_TEXT) | (1 << CMARK_NODE_SOFTBREAK) | (1 << CMARK_NODE_LINEBREAK) | (1 << CMARK_NODE_CODE) | (1 << CMARK_NODE_INLINE_HTML) | (1 << CMARK_NODE_EMPH) | (1 << CMARK_NODE_STRONG) | (1 << CMARK_NODE_LINK) | (1 << CMARK_NODE_IMAGE); test_content(runner, CMARK_NODE_DOCUMENT, top_level_blocks); test_content(runner, CMARK_NODE_BLOCK_QUOTE, top_level_blocks); test_content(runner, CMARK_NODE_LIST, list_item_flag); test_content(runner, CMARK_NODE_ITEM, top_level_blocks); test_content(runner, CMARK_NODE_CODE_BLOCK , 0); test_content(runner, CMARK_NODE_HTML, 0); test_content(runner, CMARK_NODE_PARAGRAPH, all_inlines); test_content(runner, CMARK_NODE_HEADER, all_inlines); test_content(runner, CMARK_NODE_HRULE, 0); test_content(runner, CMARK_NODE_TEXT, 0); test_content(runner, CMARK_NODE_SOFTBREAK, 0); test_content(runner, CMARK_NODE_LINEBREAK, 0); test_content(runner, CMARK_NODE_CODE, 0); test_content(runner, CMARK_NODE_INLINE_HTML, 0); test_content(runner, CMARK_NODE_EMPH, all_inlines); test_content(runner, CMARK_NODE_STRONG, all_inlines); test_content(runner, CMARK_NODE_LINK, all_inlines); test_content(runner, CMARK_NODE_IMAGE, all_inlines); }
static void create_tree(test_batch_runner *runner) { char *html; cmark_node *doc = cmark_node_new(CMARK_NODE_DOCUMENT); cmark_node *p = cmark_node_new(CMARK_NODE_PARAGRAPH); OK(runner, !cmark_node_insert_before(doc, p), "insert before root fails"); OK(runner, !cmark_node_insert_after(doc, p), "insert after root fails"); OK(runner, cmark_node_append_child(doc, p), "append1"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "append1 consistent"); OK(runner, cmark_node_parent(p) == doc, "node_parent"); cmark_node *emph = cmark_node_new(CMARK_NODE_EMPH); OK(runner, cmark_node_prepend_child(p, emph), "prepend1"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "prepend1 consistent"); cmark_node *str1 = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(str1, "Hello, "); OK(runner, cmark_node_prepend_child(p, str1), "prepend2"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "prepend2 consistent"); cmark_node *str3 = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(str3, "!"); OK(runner, cmark_node_append_child(p, str3), "append2"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "append2 consistent"); cmark_node *str2 = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(str2, "world"); OK(runner, cmark_node_append_child(emph, str2), "append3"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "append3 consistent"); html = cmark_render_html(doc, CMARK_OPT_DEFAULT); STR_EQ(runner, html, "<p>Hello, <em>world</em>!</p>\n", "render_html"); free(html); OK(runner, cmark_node_insert_before(str1, str3), "ins before1"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins before1 consistent"); // 31e OK(runner, cmark_node_first_child(p) == str3, "ins before1 works"); OK(runner, cmark_node_insert_before(str1, emph), "ins before2"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins before2 consistent"); // 3e1 OK(runner, cmark_node_last_child(p) == str1, "ins before2 works"); OK(runner, cmark_node_insert_after(str1, str3), "ins after1"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins after1 consistent"); // e13 OK(runner, cmark_node_next(str1) == str3, "ins after1 works"); OK(runner, cmark_node_insert_after(str1, emph), "ins after2"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins after2 consistent"); // 1e3 OK(runner, cmark_node_previous(emph) == str1, "ins after2 works"); cmark_node_unlink(emph); html = cmark_render_html(doc, CMARK_OPT_DEFAULT); STR_EQ(runner, html, "<p>Hello, !</p>\n", "render_html after shuffling"); free(html); cmark_node_free(doc); // TODO: Test that the contents of an unlinked inline are valid // after the parent block was destroyed. This doesn't work so far. cmark_node_free(emph); }