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 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 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 cmark_node *symbol_link_match(cmark_syntax_extension *self, cmark_parser *parser, cmark_node *parent, cmark_inline_parser *inline_parser) { cmark_node *link = NULL; char *symbol_name = NULL; NamedLink *named_link = NULL; int start_offset = cmark_inline_parser_get_offset(inline_parser); ParsingContext context; context.parser = inline_parser; context.allow_dashes = 0; if (start_offset > 0) { char prev_char = cmark_inline_parser_peek_at( inline_parser, start_offset - 1); if (prev_char && prev_char != ' ' && prev_char != '\t' && prev_char != '\n') return NULL; } cmark_inline_parser_advance_offset(inline_parser); symbol_name = cmark_inline_parser_take_while(inline_parser, (CMarkInlinePredicate) is_valid_symbol_name, &context); if (!symbol_name) goto done; named_link = PRIV(self)->link_resolve_func(symbol_name); if (!named_link || !named_link->ref) { int actual_line, actual_col; translate_sourcepos(get_first_parent_block(parent), start_offset, &actual_line, &actual_col); cmark_strbuf *message = cmark_strbuf_new(0); cmark_strbuf_puts(message, "Trying to link to non-existing symbol ‘"); cmark_strbuf_puts(message, symbol_name); cmark_strbuf_puts(message, "’"); diagnose("gtk-doc-bad-link", cmark_strbuf_get(message), actual_line - 1, actual_col - 1); cmark_strbuf_free(message); link = cmark_node_new (CMARK_NODE_TEXT); cmark_node_set_literal (link, symbol_name); } else { link = cmark_node_new(CMARK_NODE_LINK); } cmark_node_set_url(link, symbol_name); done: free(symbol_name); free_named_link(named_link); return link; }
static void node_check(test_batch_runner *runner) { // Construct an incomplete tree. cmark_node *doc = cmark_node_new(CMARK_NODE_DOCUMENT); cmark_node *p1 = cmark_node_new(CMARK_NODE_PARAGRAPH); cmark_node *p2 = cmark_node_new(CMARK_NODE_PARAGRAPH); doc->first_child = p1; p1->next = p2; INT_EQ(runner, cmark_node_check(doc, NULL), 4, "node_check works"); INT_EQ(runner, cmark_node_check(doc, NULL), 0, "node_check fixes tree"); cmark_node_free(doc); }
static cmark_node *symbol_link_match(cmark_syntax_extension *self, cmark_parser *parser, cmark_node *parent, cmark_inline_parser *inline_parser) { cmark_node *link; char *symbol_name; if (cmark_inline_parser_get_offset(inline_parser) > 0) { char 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); symbol_name = cmark_inline_parser_take_while(inline_parser, (CMarkInlinePredicate) is_valid_symbol_name); if (!symbol_name) return NULL; link = cmark_node_new(CMARK_NODE_LINK); cmark_node_set_url(link, symbol_name); free(symbol_name); return link; }
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 cmark_node *fixup_nodes(cmark_inline_parser *inline_parser, cmark_node *parent, int size) { int node_text_len; cmark_node *prev = NULL; cmark_node *tmp; int name_size = size; cmark_strbuf *name; for (prev = cmark_node_last_child(parent); prev; prev = cmark_node_previous(prev)) { if (cmark_node_get_type(prev) == CMARK_NODE_TEXT) { const char *text = cmark_node_get_literal(prev); node_text_len = strlen(text); size -= node_text_len; if (size <= 0) { if (size < 0) { char *split_text = my_strndup(text, size * -1); cmark_node *split = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(split, split_text); free(split_text); split_text = my_strndup(text + (size * - 1), node_text_len - size); cmark_node_set_literal(prev, split_text); free(split_text); cmark_node_insert_before(prev, split); } break; } } else { return NULL; } } name = cmark_strbuf_new(name_size + 1); tmp = prev; while (tmp) { cmark_node *next = cmark_node_next(tmp); cmark_strbuf_puts(name, cmark_node_get_literal(tmp)); if (tmp != prev) cmark_node_free(tmp); tmp = next; } cmark_node_set_type(prev, CMARK_NODE_LINK); cmark_node_set_url(prev, cmark_strbuf_get(name)); cmark_strbuf_free(name); return prev; }
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); }
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 void constructor(test_batch_runner *runner) { for (int i = 0; i < num_node_types; ++i) { cmark_node_type type = node_types[i]; cmark_node *node = cmark_node_new(type); OK(runner, node != NULL, "new type %d", type); INT_EQ(runner, cmark_node_get_type(node), type, "get_type %d", type); switch (node->type) { case CMARK_NODE_HEADER: INT_EQ(runner, cmark_node_get_header_level(node), 1, "default header level is 1"); node->as.header.level = 1; break; case CMARK_NODE_LIST: INT_EQ(runner, cmark_node_get_list_type(node), CMARK_BULLET_LIST, "default is list type is bullet"); INT_EQ(runner, cmark_node_get_list_delim(node), CMARK_NO_DELIM, "default is list delim is NO_DELIM"); INT_EQ(runner, cmark_node_get_list_start(node), 1, "default is list start is 1"); INT_EQ(runner, cmark_node_get_list_tight(node), 0, "default is list is loose"); break; default: break; } cmark_node_free(node); } }
static cmark_node *fixup_nodes(cmark_syntax_extension *self, cmark_parser *parser, cmark_inline_parser *inline_parser, cmark_node *parent, int start_offset, int size) { int node_text_len; cmark_node *prev = NULL; cmark_node *tmp; int name_size = size; cmark_strbuf *name; NamedLink *named_link; for (prev = cmark_node_last_child(parent); prev; prev = cmark_node_previous(prev)) { if (cmark_node_get_type(prev) == CMARK_NODE_TEXT) { const char *text = cmark_node_get_literal(prev); node_text_len = strlen(text); size -= node_text_len; if (size <= 0) { if (size < 0) { char *split_text = my_strndup(text, size * -1); cmark_node *split = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(split, split_text); free(split_text); split_text = my_strndup(text + (size * - 1), node_text_len - size); cmark_node_set_literal(prev, split_text); free(split_text); cmark_node_insert_before(prev, split); } break; } } else { return NULL; } } name = cmark_strbuf_new(name_size + 1); tmp = prev; while (tmp) { cmark_node *next = cmark_node_next(tmp); cmark_strbuf_puts(name, cmark_node_get_literal(tmp)); if (tmp != prev) cmark_node_free(tmp); tmp = next; } named_link = PRIV(self)->link_resolve_func(cmark_strbuf_get(name)); if (!named_link || !named_link->ref) { int actual_line, actual_col; translate_sourcepos(get_first_parent_block(parent), start_offset, &actual_line, &actual_col); cmark_strbuf *message = cmark_strbuf_new(0); cmark_strbuf_puts(message, "Trying to link to non-existing symbol ‘"); cmark_strbuf_puts(message, cmark_strbuf_get(name)); cmark_strbuf_puts(message, "’"); diagnose("gtk-doc-bad-link", cmark_strbuf_get(message), actual_line - 1, actual_col - 1); cmark_strbuf_free(message); cmark_node_set_literal(prev, cmark_strbuf_get(name)); cmark_strbuf_free(name); return prev; } free_named_link(named_link); cmark_node_set_type(prev, CMARK_NODE_LINK); cmark_node_set_url(prev, cmark_strbuf_get(name)); cmark_strbuf_free(name); return prev; }
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); }