static void md_convert_hashes(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); cmark_node_type const type = cmark_node_get_type(node); if(CMARK_NODE_LINK != type && CMARK_NODE_IMAGE != type) continue; char const *const URI = cmark_node_get_url(node); if(!URI) continue; if(0 != strncasecmp(URI, STR_LEN("hash:"))) continue; cmark_node *sup = superscript("#", HASH_INFO_MSG, URI); cmark_node_insert_after(node, sup); cmark_iter_reset(iter, sup, CMARK_EVENT_EXIT); char *escaped = QSEscape(URI, strlen(URI), true); size_t const elen = strlen(escaped); cmark_strbuf rel[1]; char const qpfx[] = "/sources/"; cmark_strbuf_init(&DEFAULT_MEM_ALLOCATOR, rel, sizeof(qpfx)-1+elen); cmark_strbuf_put(rel, (unsigned char const *)qpfx, sizeof(qpfx)-1); cmark_strbuf_put(rel, (unsigned char const *)escaped, elen); free(escaped); escaped = NULL; cmark_node_set_url(node, cmark_strbuf_cstr(rel)); cmark_strbuf_free(rel); } }
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 *get_first_parent_block(cmark_node *node) { cmark_node *parent = node; while (cmark_node_get_type(parent) > CMARK_NODE_LAST_BLOCK) { parent = cmark_node_parent(parent); } return parent; }
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 md_escape_inline(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_INLINE_HTML != cmark_node_get_type(node)) continue; char const *const str = cmark_node_get_literal(node); cmark_node *text = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_literal(text, str); cmark_node_insert_before(node, text); cmark_node_free(node); } }
char* CFCPerlPod_md_doc_to_pod(const char *module, const char *md) { int options = CMARK_OPT_SMART | CMARK_OPT_VALIDATE_UTF8; cmark_node *doc = cmark_parse_document(md, strlen(md), options); cmark_node *maybe_header = cmark_node_first_child(doc); char *name; char *desc; if (maybe_header && cmark_node_get_type(maybe_header) == CMARK_NODE_HEADER ) { cmark_node *header_child = cmark_node_first_child(maybe_header); char *short_desc = S_nodes_to_pod(header_child, NULL, 1); name = CFCUtil_sprintf("%s - %s", module, short_desc); FREEMEM(short_desc); cmark_node *remaining = cmark_node_next(maybe_header); desc = S_nodes_to_pod(remaining, NULL, 1); } else { // No header found. name = CFCUtil_strdup(module); desc = S_node_to_pod(doc, NULL, 1); } const char *pattern = "=head1 NAME\n" "\n" "%s\n" "\n" "=head1 DESCRIPTION\n" "\n" "%s"; char *retval = CFCUtil_sprintf(pattern, name, desc); FREEMEM(name); FREEMEM(desc); cmark_node_free(doc); return retval; }
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); } }
// Convert a single node. static char* S_node_to_pod(cmark_node *node, CFCClass *klass, int header_level) { char *result = CFCUtil_strdup(""); if (node == NULL) { return result; } int found_matching_code_block = false; cmark_iter *iter = cmark_iter_new(node); cmark_event_type ev_type; while (CMARK_EVENT_DONE != (ev_type = cmark_iter_next(iter))) { cmark_node *node = cmark_iter_get_node(iter); cmark_node_type type = cmark_node_get_type(node); switch (type) { case CMARK_NODE_DOCUMENT: break; case CMARK_NODE_PARAGRAPH: if (ev_type == CMARK_EVENT_EXIT) { result = CFCUtil_cat(result, "\n\n", NULL); } break; case CMARK_NODE_BLOCK_QUOTE: case CMARK_NODE_LIST: if (ev_type == CMARK_EVENT_ENTER) { result = CFCUtil_cat(result, "=over\n\n", NULL); } else { result = CFCUtil_cat(result, "=back\n\n", NULL); } break; case CMARK_NODE_ITEM: // TODO: Ordered lists. if (ev_type == CMARK_EVENT_ENTER) { result = CFCUtil_cat(result, "=item *\n\n", NULL); } break; case CMARK_NODE_HEADER: if (ev_type == CMARK_EVENT_ENTER) { int extra_level = cmark_node_get_header_level(node) - 1; char *header = CFCUtil_sprintf("=head%d ", header_level + extra_level); result = CFCUtil_cat(result, header, NULL); FREEMEM(header); } else { result = CFCUtil_cat(result, "\n\n", NULL); } break; case CMARK_NODE_CODE_BLOCK: { int is_host = CFCMarkdown_code_block_is_host(node, "perl"); if (is_host) { found_matching_code_block = true; const char *content = cmark_node_get_literal(node); char *copy = CFCUtil_strdup(content); // Chomp trailing newline. size_t len = strlen(copy); if (len > 0 && copy[len-1] == '\n') { copy[len-1] = '\0'; } char *indented = CFCUtil_global_replace(copy, "\n", "\n "); result = CFCUtil_cat(result, " ", indented, "\n\n", NULL); FREEMEM(indented); FREEMEM(copy); } if (CFCMarkdown_code_block_is_last(node)) { if (!found_matching_code_block) { result = CFCUtil_cat(result, " Code example for Perl is missing\n\n"); } else { // Reset. found_matching_code_block = false; } } break; } case CMARK_NODE_HTML: { const char *html = cmark_node_get_literal(node); result = CFCUtil_cat(result, "=begin html\n\n", html, "\n=end\n\n", NULL); break; } case CMARK_NODE_HRULE: break; case CMARK_NODE_TEXT: { const char *content = cmark_node_get_literal(node); char *escaped = S_pod_escape(content); result = CFCUtil_cat(result, escaped, NULL); FREEMEM(escaped); break; } case CMARK_NODE_LINEBREAK: // POD doesn't support line breaks. Start a new paragraph. result = CFCUtil_cat(result, "\n\n", NULL); break; case CMARK_NODE_SOFTBREAK: result = CFCUtil_cat(result, "\n", NULL); break; case CMARK_NODE_CODE: { const char *content = cmark_node_get_literal(node); char *escaped = S_pod_escape(content); result = CFCUtil_cat(result, "C<", escaped, ">", NULL); FREEMEM(escaped); break; } case CMARK_NODE_INLINE_HTML: { const char *html = cmark_node_get_literal(node); CFCUtil_warn("Inline HTML not supported in POD: %s", html); break; } case CMARK_NODE_LINK: if (ev_type == CMARK_EVENT_ENTER) { char *pod = S_convert_link(node, klass, header_level); result = CFCUtil_cat(result, pod, NULL); FREEMEM(pod); cmark_iter_reset(iter, node, CMARK_EVENT_EXIT); } break; case CMARK_NODE_IMAGE: CFCUtil_warn("Images not supported in POD"); break; case CMARK_NODE_STRONG: if (ev_type == CMARK_EVENT_ENTER) { result = CFCUtil_cat(result, "B<", NULL); } else { result = CFCUtil_cat(result, ">", NULL); } break; case CMARK_NODE_EMPH: if (ev_type == CMARK_EVENT_ENTER) { result = CFCUtil_cat(result, "I<", NULL); } else { result = CFCUtil_cat(result, ">", NULL); } break; default: CFCUtil_die("Invalid cmark node type: %d", (int)type); break; } } cmark_iter_free(iter); return result; }
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; }