All formats

cmark-gfm

LanguageC (usable from C++)
LicenseBSD-2-Clause (like cmark)
Repositorygithub.com/github/cmark-gfm
Documentationcmark-gfm.h, GFM spec
Depends onnothing
Read / writeparses 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#

Shell
# 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#

C
#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:

C
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:

C
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#

Shell
cmark-gfm -e table -e strikethrough -e autolink -e tagfilter -e tasklist README.md > README.html
cmark-gfm --list-extensions

Tips#