cmark-gfm
| Language | C (usable from C++) |
|---|---|
| License | BSD-2-Clause (like cmark) |
| Repository | github.com/github/cmark-gfm |
| Documentation | cmark-gfm.h, GFM spec |
| Depends on | nothing |
| Read / write | parses CommonMark + GFM; renders HTML, XML, LaTeX, man, CommonMark and plain text |
cmark-gfm is the fork of cmark that GitHub uses to render Markdown on github.com. It keeps cmark’s API (functions are still named cmark_*) and adds a syntax extension mechanism with the GFM extensions: table, strikethrough, autolink, tagfilter (escapes dangerous HTML tags like <script>), tasklist, plus footnotes as an option. It’s the GFM reference implementation; the GFM spec tests run against it.
Extensions live in a second library (libcmark-gfm-extensions) and must be registered and attached to the parser one by one. Development is slow; GitHub mostly fixes security issues.
Installation#
# Debian / Ubuntu
sudo apt install libcmark-gfm-dev libcmark-gfm-extensions-dev cmark-gfm
# macOS
brew install cmark-gfm
# Windows (vcpkg)
vcpkg install cmark-gfm
Link with -lcmark-gfm-extensions -lcmark-gfm.
Basic usage: render GitHub Flavored Markdown to HTML#
#include <cmark-gfm.h>
#include <cmark-gfm-core-extensions.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char *gfm_to_html(const char *md, size_t len) {
const char *names[] = {"table", "strikethrough", "autolink", "tagfilter", "tasklist"};
int options = CMARK_OPT_DEFAULT | CMARK_OPT_FOOTNOTES;
cmark_gfm_core_extensions_ensure_registered();
cmark_parser *parser = cmark_parser_new(options);
for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
cmark_syntax_extension *ext = cmark_find_syntax_extension(names[i]);
if (ext) cmark_parser_attach_syntax_extension(parser, ext);
}
cmark_parser_feed(parser, md, len);
cmark_node *doc = cmark_parser_finish(parser);
/* pass the parser's extensions so the renderer handles tables etc. */
char *html = cmark_render_html(doc, options, cmark_parser_get_syntax_extensions(parser));
cmark_node_free(doc);
cmark_parser_free(parser);
return html; /* free() when done */
}
int main(void) {
const char *md =
"| Name | Done |\n|------|:----:|\n| docs | yes |\n\n"
"- [x] write\n- [ ] review\n\n~~old~~ new, see https://github.com\n";
char *html = gfm_to_html(md, strlen(md));
printf("%s", html);
free(html);
return 0;
}
How to allow raw HTML#
By default raw HTML is replaced by <!-- raw HTML omitted -->. With CMARK_OPT_UNSAFE it passes through; keep the tagfilter extension attached so <script>, <iframe>, <style> and similar tags are still escaped, as on GitHub:
int options = CMARK_OPT_DEFAULT | CMARK_OPT_UNSAFE;
How to check whether a table was found#
GFM nodes have their own types, reported as strings by cmark_node_get_type_string:
cmark_iter *iter = cmark_iter_new(doc);
cmark_event_type ev;
while ((ev = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
cmark_node *node = cmark_iter_get_node(iter);
if (ev == CMARK_EVENT_ENTER && strcmp(cmark_node_get_type_string(node), "table") == 0)
printf("table at line %d\n", cmark_node_get_start_line(node));
}
cmark_iter_free(iter);
How to render GFM from the command line#
cmark-gfm -e table -e strikethrough -e autolink -e tagfilter -e tasklist README.md > README.html
cmark-gfm --list-extensions
Tips#
cmark_render_commonmarkandcmark_render_plaintextwork with extensions too, which lets you normalize GFM files or strip them to text.CMARK_OPT_GITHUB_PRE_LANGrenders fenced code as<pre lang="x">like GitHub does, instead ofclass="language-x".- Don’t link both cmark and cmark-gfm into one program: the function names collide.