Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 26578d3ed0
Fetching contributors…

Cannot retrieve contributors at this time

182 lines (159 sloc) 7.126 kb
markdown_lib.c - markdown in C using a PEG grammar.
(c) 2008 John MacFarlane (jgm at berkeley dot edu).
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "markdown_peg.h"
#define TABSTOP 4
/* preformat_text - allocate and copy text buffer while
* performing tab expansion. */
static GString *preformat_text(char *text) {
GString *buf;
char next_char;
int charstotab;
int len = 0;
buf = g_string_new("");
charstotab = TABSTOP;
while ((next_char = *text++) != '\0') {
switch (next_char) {
case '\t':
while (charstotab > 0)
g_string_append_c(buf, ' '), len++, charstotab--;
case '\n':
g_string_append_c(buf, '\n'), len++, charstotab = TABSTOP;
g_string_append_c(buf, next_char), len++, charstotab--;
if (charstotab == 0)
charstotab = TABSTOP;
g_string_append(buf, "\n\n");
/* print_tree - print tree of elements, for debugging only. */
static void print_tree(element * elt, int indent) {
int i;
char * key;
while (elt != NULL) {
for (i = 0; i < indent; i++)
fputc(' ', stderr);
switch (elt->key) {
case LIST: key = "LIST"; break;
case RAW: key = "RAW"; break;
case SPACE: key = "SPACE"; break;
case LINEBREAK: key = "LINEBREAK"; break;
case ELLIPSIS: key = "ELLIPSIS"; break;
case EMDASH: key = "EMDASH"; break;
case ENDASH: key = "ENDASH"; break;
case APOSTROPHE: key = "APOSTROPHE"; break;
case STR: key = "STR"; break;
case LINK: key = "LINK"; break;
case IMAGE: key = "IMAGE"; break;
case CODE: key = "CODE"; break;
case HTML: key = "HTML"; break;
case EMPH: key = "EMPH"; break;
case STRONG: key = "STRONG"; break;
case PLAIN: key = "PLAIN"; break;
case PARA: key = "PARA"; break;
case LISTITEM: key = "LISTITEM"; break;
case BULLETLIST: key = "BULLETLIST"; break;
case ORDEREDLIST: key = "ORDEREDLIST"; break;
case H1: key = "H1"; break;
case H2: key = "H2"; break;
case H3: key = "H3"; break;
case H4: key = "H4"; break;
case H5: key = "H5"; break;
case H6: key = "H6"; break;
case BLOCKQUOTE: key = "BLOCKQUOTE"; break;
case VERBATIM: key = "VERBATIM"; break;
case HTMLBLOCK: key = "HTMLBLOCK"; break;
case HRULE: key = "HRULE"; break;
case REFERENCE: key = "REFERENCE"; break;
case NOTE: key = "NOTE"; break;
default: key = "?";
if ( elt->key == STR ) {
fprintf(stderr, "0x%x: %s '%s'\n", (int)elt, key, elt->contents.str);
} else {
fprintf(stderr, "0x%x: %s\n", (int)elt, key);
if (elt->children)
print_tree(elt->children, indent + 4);
elt = elt->next;
/* process_raw_blocks - traverses an element list, replacing any RAW elements with
* the result of parsing them as markdown text, and recursing into the children
* of parent elements. The result should be a tree of elements without any RAWs. */
static element * process_raw_blocks(element *input, int extensions, element *references, element *notes) {
element *current = NULL;
element *last_child = NULL;
char *contents;
current = input;
while (current != NULL) {
if (current->key == RAW) {
/* \001 is used to indicate boundaries between nested lists when there
* is no blank line. We split the string by \001 and parse
* each chunk separately. */
contents = strtok(current->contents.str, "\001");
current->key = LIST;
current->children = parse_markdown(contents, extensions, references, notes);
last_child = current->children;
while ((contents = strtok(NULL, "\001"))) {
while (last_child->next != NULL)
last_child = last_child->next;
last_child->next = parse_markdown(contents, extensions, references, notes);
current->contents.str = NULL;
if (current->children != NULL)
current->children = process_raw_blocks(current->children, extensions, references, notes);
current = current->next;
return input;
/* markdown_to_gstring - convert markdown text to the output format specified.
* Returns a GString, which must be freed after use using g_string_free(). */
GString * markdown_to_g_string(char *text, int extensions, int output_format) {
element *result;
element *references;
element *notes;
GString *formatted_text;
GString *out;
out = g_string_new("");
formatted_text = preformat_text(text);
references = parse_references(formatted_text->str, extensions);
notes = parse_notes(formatted_text->str, extensions, references);
result = parse_markdown(formatted_text->str, extensions, references, notes);
result = process_raw_blocks(result, extensions, references, notes);
g_string_free(formatted_text, TRUE);
print_element_list(out, result, output_format, extensions);
return out;
/* markdown_to_string - convert markdown text to the output format specified.
* Returns a null-terminated string, which must be freed after use. */
char * markdown_to_string(char *text, int extensions, int output_format) {
GString *out;
char *char_out;
out = markdown_to_g_string(text, extensions, output_format);
char_out = out->str;
g_string_free(out, FALSE);
return char_out;
/* vim:set ts=4 sw=4: */
Jump to Line
Something went wrong with that request. Please try again.