packages feed

sundown (empty) → 0.1

raw patch · 20 files changed

+5144/−0 lines, 20 filesdep +basedep +bytestringsetup-changed

Dependencies added: base, bytestring

Files

+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cbits/array.c view
@@ -0,0 +1,300 @@+/* array.c - automatic dynamic array for pointers */++/*+ * Copyright (c) 2008, Natacha Porté+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++#include "array.h"++#include <string.h>+++/***************************+ * STATIC HELPER FUNCTIONS *+ ***************************/++/* arr_realloc • realloc memory of a struct array */+static int+arr_realloc(struct array* arr, int neosz) {+	void* neo;+	neo = realloc(arr->base, neosz * arr->unit);+	if (neo == 0) return 0;+	arr->base = neo;+	arr->asize = neosz;+	if (arr->size > neosz) arr->size = neosz;+	return 1; }+++/* parr_realloc • realloc memory of a struct parray */+static int+parr_realloc(struct parray* arr, int neosz) {+	void* neo;+	neo = realloc(arr->item, neosz * sizeof (void*));+	if (neo == 0) return 0;+	arr->item = neo;+	arr->asize = neosz;+	if (arr->size > neosz) arr->size = neosz;+	return 1; }++++/***************************+ * GENERIC ARRAY FUNCTIONS *+ ***************************/++/* arr_adjust • shrink the allocated memory to fit exactly the needs */+int+arr_adjust(struct array *arr) {+	return arr_realloc(arr, arr->size); }+++/* arr_free • frees the structure contents (buf NOT the struct itself) */+void+arr_free(struct array *arr) {+	if (!arr) return;+	free(arr->base);+	arr->base = 0;+	arr->size = arr->asize = 0; }+++/* arr_grow • increases the array size to fit the given number of elements */+int+arr_grow(struct array *arr, int need) {+	if (arr->asize >= need) return 1;+	else return arr_realloc(arr, need); }+++/* arr_init • initialization of the contents of the struct */+void+arr_init(struct array *arr, size_t unit) {+	arr->base = 0;+	arr->size = arr->asize = 0;+	arr->unit = unit; }+++/* arr_insert • inserting nb elements before the nth one */+int+arr_insert(struct array *arr, int nb, int n) {+	char *src, *dst;+	size_t len;+	if (!arr || nb <= 0 || n < 0+	|| !arr_grow(arr, arr->size + nb))+		return 0;+	if (n < arr->size) {+		src = arr->base;+		src += n * arr->unit;+		dst = src + nb * arr->unit;+		len = (arr->size - n) * arr->unit;+		memmove(dst, src, len); }+	arr->size += nb;+	return 1; }+++/* arr_item • returns a pointer to the n-th element */+void *+arr_item(struct array *arr, int no) {+	char *ptr;+	if (!arr || no < 0 || no >= arr->size) return 0;+	ptr = arr->base;+	ptr += no * arr->unit;+	return ptr; }+++/* arr_newitem • returns the index of a new element appended to the array */+int+arr_newitem(struct array *arr) {+	if (!arr_grow(arr, arr->size + 1)) return -1;+	arr->size += 1;+	return arr->size - 1; }+++/* arr_remove • removes the n-th elements of the array */+void+arr_remove(struct array *arr, int idx) {+	if (!arr || idx < 0 || idx >= arr->size) return;+	arr->size -= 1;+	if (idx < arr->size) {+		char *dst = arr->base;+		char *src;+		dst += idx * arr->unit;+		src = dst + arr->unit;+		memmove(dst, src, (arr->size - idx) * arr->unit); } }+++/* arr_sorted_find • O(log n) search in a sorted array, returning entry */+void *+arr_sorted_find(struct array *arr, void *key, array_cmp_fn cmp) {+	int mi, ma, cu, ret;+	char *ptr = arr->base;+	mi = -1;+	ma = arr->size;+	while (mi < ma - 1) {+		cu = mi + (ma - mi) / 2;+		ret = cmp(key, ptr + cu * arr->unit);+		if (ret == 0) return ptr + cu * arr->unit;+		else if (ret < 0) ma = cu;+		else /* if (ret > 0) */ mi = cu; }+	return 0; }+++/* arr_sorted_find_i • O(log n) search in a sorted array,+ *      returning index of the smallest element larger than the key */+int+arr_sorted_find_i(struct array *arr, void *key, array_cmp_fn cmp) {+	int mi, ma, cu, ret;+	char *ptr = arr->base;+	mi = -1;+	ma = arr->size;+	while (mi < ma - 1) {+		cu = mi + (ma - mi) / 2;+		ret = cmp(key, ptr + cu * arr->unit);+		if (ret == 0) {+			while (cu < arr->size && ret == 0) {+				cu += 1;+				ret = cmp(key, ptr + cu * arr->unit); }+			return cu; }+		else if (ret < 0) ma = cu;+		else /* if (ret > 0) */ mi = cu; }+	return ma; }++++/***************************+ * POINTER ARRAY FUNCTIONS *+ ***************************/++/* parr_adjust • shrinks the allocated memory to fit exactly the needs */+int+parr_adjust(struct parray* arr) {+	return parr_realloc (arr, arr->size); }+++/* parr_free • frees the structure contents (buf NOT the struct itself) */+void+parr_free(struct parray *arr) {+	if (!arr) return;+	free (arr->item);+	arr->item = 0;+	arr->size = 0;+	arr->asize = 0; }+++/* parr_grow • increases the array size to fit the given number of elements */+int+parr_grow(struct parray *arr, int need) {+	if (arr->asize >= need) return 1;+	else return parr_realloc (arr, need); }+++/* parr_init • initialization of the struct (which is equivalent to zero) */+void+parr_init(struct parray *arr) {+	arr->item = 0;+	arr->size = 0;+	arr->asize = 0; }+++/* parr_insert • inserting nb elements before the nth one */+int+parr_insert(struct parray *parr, int nb, int n) {+	char *src, *dst;+	size_t len, i;+	if (!parr || nb <= 0 || n < 0+	|| !parr_grow(parr, parr->size + nb))+		return 0;+	if (n < parr->size) {+		src = (void *)parr->item;+		src += n * sizeof (void *);+		dst = src + nb * sizeof (void *);+		len = (parr->size - n) * sizeof (void *);+		memmove(dst, src, len);+		for (i = 0; i < (size_t)nb; ++i)+			parr->item[n + i] = 0; }+	parr->size += nb;+	return 1; }+++/* parr_pop • pops the last item of the array and returns it */+void *+parr_pop(struct parray *arr) {+	if (arr->size <= 0) return 0;+	arr->size -= 1;+	return arr->item[arr->size]; }+++/* parr_push • pushes a pointer at the end of the array (= append) */+int+parr_push(struct parray *arr, void *i) {+	if (!parr_grow(arr, arr->size + 1)) return 0;+	arr->item[arr->size] = i;+	arr->size += 1;+	return 1; }+++/* parr_remove • removes the n-th element of the array and returns it */+void *+parr_remove(struct parray *arr, int idx) {+	void* ret;+	int i;+	if (!arr || idx < 0 || idx >= arr->size) return 0;+	ret = arr->item[idx];+	for (i = idx+1; i < arr->size; ++i)+		arr->item[i - 1] = arr->item[i];+	arr->size -= 1;+	return ret; }+++/* parr_sorted_find • O(log n) search in a sorted array, returning entry */+void *+parr_sorted_find(struct parray *arr, void *key, array_cmp_fn cmp) {+	int mi, ma, cu, ret;+	mi = -1;+	ma = arr->size;+	while (mi < ma - 1) {+		cu = mi + (ma - mi) / 2;+		ret = cmp(key, arr->item[cu]);+		if (ret == 0) return arr->item[cu];+		else if (ret < 0) ma = cu;+		else /* if (ret > 0) */ mi = cu; }+	return 0; }+++/* parr_sorted_find_i • O(log n) search in a sorted array,+ *      returning index of the smallest element larger than the key */+int+parr_sorted_find_i(struct parray *arr, void *key, array_cmp_fn cmp) {+	int mi, ma, cu, ret;+	mi = -1;+	ma = arr->size;+	while (mi < ma - 1) {+		cu = mi + (ma - mi) / 2;+		ret = cmp(key, arr->item[cu]);+		if (ret == 0) {+			while (cu < arr->size && ret == 0) {+				cu += 1;+				ret = cmp(key, arr->item[cu]); }+			return cu; }+		else if (ret < 0) ma = cu;+		else /* if (ret > 0) */ mi = cu; }+	return ma; }+++/* parr_top • returns the top the stack (i.e. the last element of the array) */+void *+parr_top(struct parray *arr) {+	if (arr == 0 || arr->size <= 0) return 0;+	else return arr->item[arr->size - 1]; }++/* vim: set filetype=c: */
+ cbits/array.h view
@@ -0,0 +1,147 @@+/* array.h - automatic dynamic array for pointers */++/*+ * Copyright (c) 2008, Natacha Porté+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++#ifndef LITHIUM_ARRAY_H+#define LITHIUM_ARRAY_H++#include <stdlib.h>++/********************+ * TYPE DEFINITIONS *+ ********************/++/* struct array • generic linear array */+struct array {+	void*	base;+	int	size;+	int	asize;+	size_t	unit; };+++/* struct parray • array of pointers */+struct parray {+	void **	item;+	int	size;+	int	asize; };+++/* array_cmp_fn • comparison functions for sorted arrays */+typedef int (*array_cmp_fn)(void *key, void *array_entry);++++/***************************+ * GENERIC ARRAY FUNCTIONS *+ ***************************/++/* arr_adjust • shrink the allocated memory to fit exactly the needs */+int+arr_adjust(struct array *);++/* arr_free • frees the structure contents (buf NOT the struct itself) */+void+arr_free(struct array *);++/* arr_grow • increases the array size to fit the given number of elements */+int+arr_grow(struct array *, int);++/* arr_init • initialization of the contents of the struct */+void+arr_init(struct array *, size_t);++/* arr_insert • inserting elements nb before the nth one */+int+arr_insert(struct array *, int nb, int n);++/* arr_item • returns a pointer to the n-th element */+void *+arr_item(struct array *, int);++/* arr_newitem • returns the index of a new element appended to the array */+int+arr_newitem(struct array *);++/* arr_remove • removes the n-th elements of the array */+void+arr_remove(struct array *, int);++/* arr_sorted_find • O(log n) search in a sorted array, returning entry */+/* equivalent to bsearch(key, arr->base, arr->size, arr->unit, cmp) */+void *+arr_sorted_find(struct array *, void *key, array_cmp_fn cmp);++/* arr_sorted_find_i • O(log n) search in a sorted array,+ *      returning index of the smallest element larger than the key */+int+arr_sorted_find_i(struct array *, void *key, array_cmp_fn cmp);+++/***************************+ * POINTER ARRAY FUNCTIONS *+ ***************************/++/* parr_adjust • shrinks the allocated memory to fit exactly the needs */+int+parr_adjust(struct parray *);++/* parr_free • frees the structure contents (buf NOT the struct itself) */+void+parr_free(struct parray *);++/* parr_grow • increases the array size to fit the given number of elements */+int+parr_grow(struct parray *, int);++/* parr_init • initialization of the struct (which is equivalent to zero) */+void+parr_init(struct parray *);++/* parr_insert • inserting nb elements before the nth one */+int+parr_insert(struct parray *, int nb, int n);++/* parr_pop • pops the last item of the array and returns it */+void *+parr_pop(struct parray *);++/* parr_push • pushes a pointer at the end of the array (= append) */+int+parr_push(struct parray *, void *);++/* parr_remove • removes the n-th element of the array and returns it */+void *+parr_remove(struct parray *, int);++/* parr_sorted_find • O(log n) search in a sorted array, returning entry */+void *+parr_sorted_find(struct parray *, void *key, array_cmp_fn cmp);++/* parr_sorted_find_i • O(log n) search in a sorted array,+ *      returning index of the smallest element larger than the key */+int+parr_sorted_find_i(struct parray *, void *key, array_cmp_fn cmp);++/* parr_top • returns the top the stack (i.e. the last element of the array) */+void *+parr_top(struct parray *);+++#endif /* ndef LITHIUM_ARRAY_H */++/* vim: set filetype=c: */
+ cbits/autolink.c view
@@ -0,0 +1,261 @@+/*+ * Copyright (c) 2011, Vicent Marti+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++#include "buffer.h"++#include <string.h>+#include <stdlib.h>+#include <stdio.h>+#include <ctype.h>++int+sd_autolink_issafe(const char *link, size_t link_len)+{+	static const size_t valid_uris_count = 4;+	static const char *valid_uris[] = {+		"http://", "https://", "ftp://", "mailto://"+	};++	size_t i;++	for (i = 0; i < valid_uris_count; ++i) {+		size_t len = strlen(valid_uris[i]);++		if (link_len > len &&+			strncasecmp(link, valid_uris[i], len) == 0 &&+			isalnum(link[len]))+			return 1;+	}++	return 0;+}++static size_t+autolink_delim(char *data, size_t link_end, size_t offset, size_t size)+{+	char cclose, copen = 0;+	size_t i;++	for (i = 0; i < link_end; ++i)+		if (data[i] == '<') {+			link_end = i;+			break;+		}++	while (link_end > 0) {+		if (strchr("?!.,", data[link_end - 1]) != NULL)+			link_end--;++		else if (data[link_end - 1] == ';') {+			size_t new_end = link_end - 2;++			while (new_end > 0 && isalpha(data[new_end]))+				new_end--;++			if (new_end < link_end - 2 && data[new_end] == '&')+				link_end = new_end;+			else+				link_end--;+		}+		else break;+	}++	if (link_end == 0)+		return 0;++	cclose = data[link_end - 1];++	switch (cclose) {+	case '"':	copen = '"'; break;+	case '\'':	copen = '\''; break;+	case ')':	copen = '('; break;+	case ']':	copen = '['; break;+	case '}':	copen = '{'; break;+	}++	if (copen != 0) {+		size_t closing = 0;+		size_t opening = 0;+		size_t i = 0;++		/* Try to close the final punctuation sign in this same line;+		 * if we managed to close it outside of the URL, that means that it's+		 * not part of the URL. If it closes inside the URL, that means it+		 * is part of the URL.+		 *+		 * Examples:+		 *+		 *	foo http://www.pokemon.com/Pikachu_(Electric) bar+		 *		=> http://www.pokemon.com/Pikachu_(Electric)+		 *+		 *	foo (http://www.pokemon.com/Pikachu_(Electric)) bar+		 *		=> http://www.pokemon.com/Pikachu_(Electric)+		 *+		 *	foo http://www.pokemon.com/Pikachu_(Electric)) bar+		 *		=> http://www.pokemon.com/Pikachu_(Electric))+		 *+		 *	(foo http://www.pokemon.com/Pikachu_(Electric)) bar+		 *		=> foo http://www.pokemon.com/Pikachu_(Electric)+		 */++		while (i < link_end) {+			if (data[i] == copen)+				opening++;+			else if (data[i] == cclose)+				closing++;++			i++;+		}++		if (closing != opening)+			link_end--;+	}++	return link_end;+}++static size_t+check_domain(char *data, size_t size)+{+	size_t i, np = 0;++	if (!isalnum(data[0]))+		return 0;++	for (i = 1; i < size - 1; ++i) {+		if (data[i] == '.') np++;+		else if (!isalnum(data[i]) && data[i] != '-') break;+	}++	if (!isalnum(data[i - 1]) || np == 0)+		return 0;++	return i;+}++size_t+sd_autolink__www(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size)+{+	size_t link_end;++	if (offset > 0 && !ispunct(data[-1]) && !isspace(data[-1]))+		return 0;++	if (size < 4 || memcmp(data, "www.", STRLEN("www.")) != 0)+		return 0;++	link_end = check_domain(data, size);++	if (link_end == 0)+		return 0;++	while (link_end < size && !isspace(data[link_end]))+		link_end++;++	link_end = autolink_delim(data, link_end, offset, size);++	if (link_end == 0)+		return 0;++	bufput(link, data, link_end);+	*rewind_p = 0;++	return (int)link_end;+}++size_t+sd_autolink__email(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size)+{+	size_t link_end, rewind;+	int nb = 0, np = 0;++	for (rewind = 0; rewind < offset; ++rewind) {+		char c = data[-rewind - 1];++		if (isalnum(c))+			continue;++		if (strchr(".+-_", c) != NULL)+			continue;++		break;+	}++	if (rewind == 0)+		return 0;++	for (link_end = 0; link_end < size; ++link_end) {+		char c = data[link_end];++		if (isalnum(c))+			continue;++		if (c == '@')+			nb++;+		else if (c == '.' && link_end < size - 1)+			np++;+		else if (c != '-' && c != '_')+			break;+	}++	if (link_end < 2 || nb != 1 || np == 0)+		return 0;++	link_end = autolink_delim(data, link_end, offset, size);++	if (link_end == 0)+		return 0;++	bufput(link, data - rewind, link_end + rewind);+	*rewind_p = rewind;++	return link_end;+}++size_t+sd_autolink__url(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size)+{+	size_t link_end, rewind = 0, domain_len;++	if (size < 4 || data[1] != '/' || data[2] != '/')+		return 0;++	while (rewind < offset && isalpha(data[-rewind - 1]))+		rewind++;++	if (!sd_autolink_issafe(data - rewind, size + rewind))+		return 0;+	link_end = STRLEN("://");++	domain_len = check_domain(data + link_end, size - link_end);+	if (domain_len == 0)+		return 0;++	link_end += domain_len;+	while (link_end < size && !isspace(data[link_end]))+		link_end++;++	link_end = autolink_delim(data, link_end, offset, size);++	if (link_end == 0)+		return 0;++	bufput(link, data - rewind, link_end + rewind);+	*rewind_p = rewind;++	return link_end;+}+
+ cbits/autolink.h view
@@ -0,0 +1,36 @@+/*+ * Copyright (c) 2011, Vicent Marti+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++#ifndef UPSKIRT_AUTOLINK_H+#define UPSKIRT_AUTOLINK_H++#include "buffer.h"++extern int+sd_autolink_issafe(const char *link, size_t link_len);++extern size_t+sd_autolink__www(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size);++extern size_t+sd_autolink__email(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size);++extern size_t+sd_autolink__url(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size);++#endif++/* vim: set filetype=c: */
+ cbits/buffer.c view
@@ -0,0 +1,323 @@+/* buffer.c - automatic buffer structure */++/*+ * Copyright (c) 2008, Natacha Porté+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++/*+ * COMPILE TIME OPTIONS+ *+ * BUFFER_STATS • if defined, stats are kept about memory usage+ */++#define BUFFER_STDARG+#define BUFFER_MAX_ALLOC_SIZE (1024 * 1024 * 16) //16mb++#include "buffer.h"++#include <stdio.h>+#include <stdlib.h>+#include <string.h>+++/********************+ * GLOBAL VARIABLES *+ ********************/++#ifdef BUFFER_STATS+long buffer_stat_nb = 0;+size_t buffer_stat_alloc_bytes = 0;+#endif+++/***************************+ * STATIC HELPER FUNCTIONS *+ ***************************/++/* lower • retruns the lower-case variant of the input char */+static char+lower(char c) {+	return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') : c; }++++/********************+ * BUFFER FUNCTIONS *+ ********************/++/* bufcasecmp • case-insensitive buffer comparison */+int+bufcasecmp(const struct buf *a, const struct buf *b) {+	size_t i = 0;+	size_t cmplen;+	if (a == b) return 0;+	if (!a) return -1; else if (!b) return 1;+	cmplen = (a->size < b->size) ? a->size : b->size;+	while (i < cmplen && lower(a->data[i]) == lower(b->data[i])) ++i;+	if (i < a->size) {+		if (i < b->size)  return lower(a->data[i]) - lower(b->data[i]);+		else		  return 1; }+	else {	if (i < b->size)  return -1;+		else		  return 0; } }+++/* bufcmp • case-sensitive buffer comparison */+int+bufcmp(const struct buf *a, const struct buf *b) {+	size_t i = 0;+	size_t cmplen;+	if (a == b) return 0;+	if (!a) return -1; else if (!b) return 1;+	cmplen = (a->size < b->size) ? a->size : b->size;+	while (i < cmplen && a->data[i] == b->data[i]) ++i;+	if (i < a->size) {+		if (i < b->size)	return a->data[i] - b->data[i];+		else			return 1; }+	else {	if (i < b->size)	return -1;+		else			return 0; } }+++/* bufcmps • case-sensitive comparison of a string to a buffer */+int+bufcmps(const struct buf *a, const char *b) {+	const size_t len = strlen(b);+	size_t cmplen = len;+	int r;+	if (!a || !a->size) return b ? 0 : -1;+	if (len < a->size) cmplen = a->size;+	r = strncmp(a->data, b, cmplen);+	if (r) return r;+	else if (a->size == len) return 0;+	else if (a->size < len) return -1;+	else return 1; }++int+bufprefix(const struct buf *buf, const char *prefix)+{+	size_t i;++	for (i = 0; i < buf->size; ++i) {+		if (prefix[i] == 0)+			return 0;++		if (buf->data[i] != prefix[i])+			return buf->data[i] - prefix[i];+	}++	return 0;+}+++/* bufdup • buffer duplication */+struct buf *+bufdup(const struct buf *src, size_t dupunit) {+	size_t blocks;+	struct buf *ret;+	if (src == 0) return 0;+	ret = malloc(sizeof (struct buf));+	if (ret == 0) return 0;+	ret->unit = dupunit;+	ret->size = src->size;+	ret->ref = 1;+	if (!src->size) {+		ret->asize = 0;+		ret->data = 0;+		return ret; }+	blocks = (src->size + dupunit - 1) / dupunit;+	ret->asize = blocks * dupunit;+	ret->data = malloc(ret->asize);+	if (ret->data == 0) {+		free(ret);+		return 0; }+	memcpy(ret->data, src->data, src->size);+#ifdef BUFFER_STATS+	buffer_stat_nb += 1;+	buffer_stat_alloc_bytes += ret->asize;+#endif+	return ret; }++/* bufgrow • increasing the allocated size to the given value */+int+bufgrow(struct buf *buf, size_t neosz) {+	size_t neoasz;+	void *neodata;+	if (!buf || !buf->unit || neosz > BUFFER_MAX_ALLOC_SIZE) return 0;+	if (buf->asize >= neosz) return 1;+	neoasz = buf->asize + buf->unit;+	while (neoasz < neosz) neoasz += buf->unit;+	neodata = realloc(buf->data, neoasz);+	if (!neodata) return 0;+#ifdef BUFFER_STATS+	buffer_stat_alloc_bytes += (neoasz - buf->asize);+#endif+	buf->data = neodata;+	buf->asize = neoasz;+	return 1; }+++/* bufnew • allocation of a new buffer */+struct buf *+bufnew(size_t unit) {+	struct buf *ret;+	ret = malloc(sizeof (struct buf));+	if (ret) {+#ifdef BUFFER_STATS+		buffer_stat_nb += 1;+#endif+		ret->data = 0;+		ret->size = ret->asize = 0;+		ret->ref = 1;+		ret->unit = unit; }+	return ret; }+++/* bufnullterm • NUL-termination of the string array (making a C-string) */+void+bufnullterm(struct buf *buf) {+	if (!buf || !buf->unit) return;+	if (buf->size < buf->asize && buf->data[buf->size] == 0) return;+	if (buf->size + 1 <= buf->asize || bufgrow(buf, buf->size + 1))+		buf->data[buf->size] = 0; }+++/* bufprintf • formatted printing to a buffer */+void+bufprintf(struct buf *buf, const char *fmt, ...) {+	va_list ap;+	if (!buf || !buf->unit) return;+	va_start(ap, fmt);+	vbufprintf(buf, fmt, ap);+	va_end(ap); }+++/* bufput • appends raw data to a buffer */+void+bufput(struct buf *buf, const void *data, size_t len) {+	if (!buf) return;+	if (buf->size + len > buf->asize && !bufgrow(buf, buf->size + len))+		return;+	memcpy(buf->data + buf->size, data, len);+	buf->size += len; }+++/* bufputs • appends a NUL-terminated string to a buffer */+void+bufputs(struct buf *buf, const char *str) {+	bufput(buf, str, strlen (str)); }+++/* bufputc • appends a single char to a buffer */+void+bufputc(struct buf *buf, char c) {+	if (!buf) return;+	if (buf->size + 1 > buf->asize && !bufgrow(buf, buf->size + 1))+		return;+	buf->data[buf->size] = c;+	buf->size += 1; }+++/* bufrelease • decrease the reference count and free the buffer if needed */+void+bufrelease(struct buf *buf) {+	if (!buf) return;+	buf->ref -= 1;+	if (buf->ref == 0) {+#ifdef BUFFER_STATS+		buffer_stat_nb -= 1;+		buffer_stat_alloc_bytes -= buf->asize;+#endif+		free(buf->data);+		free(buf); } }+++/* bufreset • frees internal data of the buffer */+void+bufreset(struct buf *buf) {+	if (!buf) return;+#ifdef BUFFER_STATS+	buffer_stat_alloc_bytes -= buf->asize;+#endif+	free(buf->data);+	buf->data = 0;+	buf->size = buf->asize = 0; }+++/* bufset • safely assigns a buffer to another */+void+bufset(struct buf **dest, struct buf *src) {+	if (src) {+		if (!src->asize) src = bufdup(src, 1);+		else src->ref += 1; }+	bufrelease(*dest);+	*dest = src; }+++/* bufslurp • removes a given number of bytes from the head of the array */+void+bufslurp(struct buf *buf, size_t len) {+	if (!buf || !buf->unit || len <= 0) return;+	if (len >= buf->size) {+		buf->size = 0;+		return; }+	buf->size -= len;+	memmove(buf->data, buf->data + len, buf->size); }+++/* buftoi • converts the numbers at the beginning of the buf into an int */+int+buftoi(struct buf *buf, size_t offset_i, size_t *offset_o) {+	int r = 0, neg = 0;+	size_t i = offset_i;+	if (!buf || !buf->size) return 0;+	if (buf->data[i] == '+') i += 1;+	else if (buf->data[i] == '-') {+		neg = 1;+		i += 1; }+	while (i < buf->size && buf->data[i] >= '0' && buf->data[i] <= '9') {+		r = (r * 10) + buf->data[i] - '0';+		i += 1; }+	if (offset_o) *offset_o = i;+	return neg ? -r : r; }++++/* vbufprintf • stdarg variant of formatted printing into a buffer */+void+vbufprintf(struct buf *buf, const char *fmt, va_list ap) {+	int n;+	va_list ap_save;+	if (buf == 0+	|| (buf->size >= buf->asize && !bufgrow (buf, buf->size + 1)))+		return;++	va_copy(ap_save, ap);+	n = vsnprintf(buf->data + buf->size, buf->asize - buf->size, fmt, ap);++	if (n < 0 || (size_t)n >= buf->asize - buf->size) {+		size_t new_size = (n > 0) ? n : buf->size;+		if (!bufgrow (buf, buf->size + new_size + 1))+			return;++		n = vsnprintf(buf->data + buf->size, buf->asize - buf->size, fmt, ap_save);+	}+	va_end(ap_save);++	if (n < 0)+		return;++	buf->size += n;+}++/* vim: set filetype=c: */
+ cbits/buffer.h view
@@ -0,0 +1,154 @@+/* buffer.h - automatic buffer structure */++/*+ * Copyright (c) 2008, Natacha Porté+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++#ifndef LITHIUM_BUFFER_H+#define LITHIUM_BUFFER_H++#include <stddef.h>++#if defined(_MSC_VER)+#define __attribute__(x)+#define inline+#define strncasecmp _strnicmp+#define snprintf _snprintf+#define va_copy(d,s) ((d) = (s))+#endif++/********************+ * TYPE DEFINITIONS *+ ********************/++/* struct buf • character array buffer */+struct buf {+	char *	data;	/* actual character data */+	size_t	size;	/* size of the string */+	size_t	asize;	/* allocated size (0 = volatile buffer) */+	size_t	unit;	/* reallocation unit size (0 = read-only buffer) */+	int	ref; };	/* reference count */++/**********+ * MACROS *+ **********/++#define STRLEN(x) (sizeof(x) - 1)++/* CONST_BUF • global buffer from a string litteral */+#define CONST_BUF(name, string) \+	static struct buf name = { string, sizeof string -1, sizeof string }+++/* VOLATILE_BUF • macro for creating a volatile buffer on the stack */+#define VOLATILE_BUF(name, strname) \+	struct buf name = { strname, strlen(strname) }+++/* BUFPUTSL • optimized bufputs of a string litteral */+#define BUFPUTSL(output, litteral) \+	bufput(output, litteral, sizeof litteral - 1)++++/********************+ * BUFFER FUNCTIONS *+ ********************/++/* bufcasecmp • case-insensitive buffer comparison */+int+bufcasecmp(const struct buf *, const struct buf *);++/* bufcmp • case-sensitive buffer comparison */+int+bufcmp(const struct buf *, const struct buf *);++/* bufcmps • case-sensitive comparison of a string to a buffer */+int+bufcmps(const struct buf *, const char *);++/* bufprefix * compare the beginning of a buffer with a string */+int+bufprefix(const struct buf *buf, const char *prefix);++/* bufdup • buffer duplication */+struct buf *+bufdup(const struct buf *, size_t)+	__attribute__ ((malloc));++/* bufgrow • increasing the allocated size to the given value */+int+bufgrow(struct buf *, size_t);++/* bufnew • allocation of a new buffer */+struct buf *+bufnew(size_t)+	__attribute__ ((malloc));++/* bufnullterm • NUL-termination of the string array (making a C-string) */+void+bufnullterm(struct buf *);++/* bufprintf • formatted printing to a buffer */+void+bufprintf(struct buf *, const char *, ...)+	__attribute__ ((format (printf, 2, 3)));++/* bufput • appends raw data to a buffer */+void+bufput(struct buf *, const void*, size_t);++/* bufputs • appends a NUL-terminated string to a buffer */+void+bufputs(struct buf *, const char*);++/* bufputc • appends a single char to a buffer */+void+bufputc(struct buf *, char);++/* bufrelease • decrease the reference count and free the buffer if needed */+void+bufrelease(struct buf *);++/* bufreset • frees internal data of the buffer */+void+bufreset(struct buf *);++/* bufset • safely assigns a buffer to another */+void+bufset(struct buf **, struct buf *);++/* bufslurp • removes a given number of bytes from the head of the array */+void+bufslurp(struct buf *, size_t);++/* buftoi • converts the numbers at the beginning of the buf into an int */+int+buftoi(struct buf *, size_t, size_t *);++++#ifdef BUFFER_STDARG+#include <stdarg.h>++/* vbufprintf • stdarg variant of formatted printing into a buffer */+void+vbufprintf(struct buf *, const char*, va_list);++#endif /* def BUFFER_STDARG */++#endif /* ndef LITHIUM_BUFFER_H */++/* vim: set filetype=c: */
+ cbits/html.c view
@@ -0,0 +1,674 @@+/*+ * Copyright (c) 2009, Natacha Porté+ * Copyright (c) 2011, Vicent Marti+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++#include "markdown.h"+#include "html.h"++#include <string.h>+#include <stdlib.h>+#include <stdio.h>+#include <ctype.h>++#define USE_XHTML(opt) (opt->flags & HTML_USE_XHTML)++static inline void+put_scaped_char(struct buf *ob, char c)+{+	switch (c) {+		case '<': BUFPUTSL(ob, "&lt;"); break;+		case '>': BUFPUTSL(ob, "&gt;"); break;+		case '&': BUFPUTSL(ob, "&amp;"); break;+		case '"': BUFPUTSL(ob, "&quot;"); break;+		default: bufputc(ob, c); break;+	}+}++/* sdhtml_escape • copy the buffer entity-escaping '<', '>', '&' and '"' */+void+sdhtml_escape(struct buf *ob, const char *src, size_t size)+{+	size_t  i = 0, org;+	while (i < size) {+		/* copying directly unescaped characters */+		org = i;+		while (i < size && src[i] != '<' && src[i] != '>'+		&& src[i] != '&' && src[i] != '"')+			i += 1;+		if (i > org) bufput(ob, src + org, i - org);++		/* escaping */+		if (i >= size) break;++		put_scaped_char(ob, src[i]);+		i++;+	}+}++int+sdhtml_tag(const char *tag_data, size_t tag_size, const char *tagname)+{+	size_t i;+	int closed = 0;++	if (tag_size < 3 || tag_data[0] != '<')+		return HTML_TAG_NONE;++	i = 1;++	if (tag_data[i] == '/') {+		closed = 1;+		i++;+	}++	for (; i < tag_size; ++i, ++tagname) {+		if (*tagname == 0)+			break;++		if (tag_data[i] != *tagname)+			return HTML_TAG_NONE;+	}++	if (i == tag_size)+		return HTML_TAG_NONE;++	if (isspace(tag_data[i]) || tag_data[i] == '>')+		return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN;++	return HTML_TAG_NONE;+}++/********************+ * GENERIC RENDERER *+ ********************/+static int+rndr_autolink(struct buf *ob, struct buf *link, enum mkd_autolink type, void *opaque)+{+	struct html_renderopt *options = opaque;++	if (!link || !link->size)+		return 0;++	if ((options->flags & HTML_SAFELINK) != 0 &&+		!sd_autolink_issafe(link->data, link->size) &&+		type != MKDA_EMAIL)+		return 0;++	BUFPUTSL(ob, "<a href=\"");+	if (type == MKDA_EMAIL)+		BUFPUTSL(ob, "mailto:");+	sdhtml_escape(ob, link->data, link->size);++	if (options->link_attributes) {+		bufputc(ob, '\"');+		options->link_attributes(ob, link, opaque);+		bufputc(ob, '>');+	} else {+		BUFPUTSL(ob, "\">");+	}++	/*+	 * Pretty printing: if we get an email address as+	 * an actual URI, e.g. `mailto:foo@bar.com`, we don't+	 * want to print the `mailto:` prefix+	 */+	if (bufprefix(link, "mailto:") == 0) {+		sdhtml_escape(ob, link->data + 7, link->size - 7);+	} else {+		sdhtml_escape(ob, link->data, link->size);+	}++	BUFPUTSL(ob, "</a>");++	return 1;+}++static void+rndr_blockcode(struct buf *ob, struct buf *text, struct buf *lang, void *opaque)+{+	if (ob->size) bufputc(ob, '\n');++	if (lang && lang->size) {+		size_t i, cls;+		BUFPUTSL(ob, "<pre><code class=\"");++		for (i = 0, cls = 0; i < lang->size; ++i, ++cls) {+			while (i < lang->size && isspace(lang->data[i]))+				i++;++			if (i < lang->size) {+				size_t org = i;+				while (i < lang->size && !isspace(lang->data[i]))+					i++;++				if (lang->data[org] == '.')+					org++;++				if (cls) bufputc(ob, ' ');+				sdhtml_escape(ob, lang->data + org, i - org);+			}+		}++		BUFPUTSL(ob, "\">");+	} else+		BUFPUTSL(ob, "<pre><code>");++	if (text)+		sdhtml_escape(ob, text->data, text->size);++	BUFPUTSL(ob, "</code></pre>\n");+}++/*+ * GitHub style code block:+ *+ *		<pre lang="LANG"><code>+ *		...+ *		</pre></code>+ *+ * Unlike other parsers, we store the language identifier in the <pre>,+ * and don't let the user generate custom classes.+ *+ * The language identifier in the <pre> block gets postprocessed and all+ * the code inside gets syntax highlighted with Pygments. This is much safer+ * than letting the user specify a CSS class for highlighting.+ *+ * Note that we only generate HTML for the first specifier.+ * E.g.+ *		~~~~ {.python .numbered}	=>	<pre lang="python"><code>+ */+static void+rndr_blockcode_github(struct buf *ob, struct buf *text, struct buf *lang, void *opaque)+{+	if (ob->size) bufputc(ob, '\n');++	if (lang && lang->size) {+		size_t i = 0;+		BUFPUTSL(ob, "<pre lang=\"");++		while (i < lang->size && !isspace(lang->data[i]))+			i++;++		if (lang->data[0] == '.')+			sdhtml_escape(ob, lang->data + 1, i - 1);+		else+			sdhtml_escape(ob, lang->data, i);++		BUFPUTSL(ob, "\"><code>");+	} else+		BUFPUTSL(ob, "<pre><code>");++	if (text)+		sdhtml_escape(ob, text->data, text->size);++	BUFPUTSL(ob, "</code></pre>\n");+}++static void+rndr_blockquote(struct buf *ob, struct buf *text, void *opaque)+{+	if (ob->size) bufputc(ob, '\n');+	BUFPUTSL(ob, "<blockquote>\n");+	if (text) bufput(ob, text->data, text->size);+	BUFPUTSL(ob, "</blockquote>\n");+}++static int+rndr_codespan(struct buf *ob, struct buf *text, void *opaque)+{+	BUFPUTSL(ob, "<code>");+	if (text) sdhtml_escape(ob, text->data, text->size);+	BUFPUTSL(ob, "</code>");+	return 1;+}++static int+rndr_strikethrough(struct buf *ob, struct buf *text, void *opaque)+{+	if (!text || !text->size)+		return 0;++	BUFPUTSL(ob, "<del>");+	bufput(ob, text->data, text->size);+	BUFPUTSL(ob, "</del>");+	return 1;+}++static int+rndr_double_emphasis(struct buf *ob, struct buf *text, void *opaque)+{+	if (!text || !text->size)+		return 0;++	BUFPUTSL(ob, "<strong>");+	bufput(ob, text->data, text->size);+	BUFPUTSL(ob, "</strong>");++	return 1;+}++static int+rndr_emphasis(struct buf *ob, struct buf *text, void *opaque)+{+	if (!text || !text->size) return 0;+	BUFPUTSL(ob, "<em>");+	if (text) bufput(ob, text->data, text->size);+	BUFPUTSL(ob, "</em>");+	return 1;+}++static int+rndr_linebreak(struct buf *ob, void *opaque)+{+	struct html_renderopt *options = opaque;	+	bufputs(ob, USE_XHTML(options) ? "<br/>\n" : "<br>\n");+	return 1;+}++static void+rndr_header(struct buf *ob, struct buf *text, int level, void *opaque)+{+	struct html_renderopt *options = opaque;+	+	if (ob->size)+		bufputc(ob, '\n');++	if (options->flags & HTML_TOC)+		bufprintf(ob, "<h%d id=\"toc_%d\">", level, options->toc_data.header_count++);+	else+		bufprintf(ob, "<h%d>", level);++	if (text) bufput(ob, text->data, text->size);+	bufprintf(ob, "</h%d>\n", level);+}++static int+rndr_link(struct buf *ob, struct buf *link, struct buf *title, struct buf *content, void *opaque)+{+	struct html_renderopt *options = opaque;+	+	if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size))+		return 0;++	BUFPUTSL(ob, "<a href=\"");++	if (link && link->size)+		sdhtml_escape(ob, link->data, link->size);++	if (title && title->size) {+		BUFPUTSL(ob, "\" title=\"");+		sdhtml_escape(ob, title->data, title->size);+	}++	if (options->link_attributes) {+		bufputc(ob, '\"');+		options->link_attributes(ob, link, opaque);+		bufputc(ob, '>');+	} else {+		BUFPUTSL(ob, "\">");+	}++	if (content && content->size) bufput(ob, content->data, content->size);+	BUFPUTSL(ob, "</a>");+	return 1;+}++static void+rndr_list(struct buf *ob, struct buf *text, int flags, void *opaque)+{+	if (ob->size) bufputc(ob, '\n');+	bufput(ob, flags & MKD_LIST_ORDERED ? "<ol>\n" : "<ul>\n", 5);+	if (text) bufput(ob, text->data, text->size);+	bufput(ob, flags & MKD_LIST_ORDERED ? "</ol>\n" : "</ul>\n", 6);+}++static void+rndr_listitem(struct buf *ob, struct buf *text, int flags, void *opaque)+{+	BUFPUTSL(ob, "<li>");+	if (text) {+		while (text->size && text->data[text->size - 1] == '\n')+			text->size -= 1;+		bufput(ob, text->data, text->size); }+	BUFPUTSL(ob, "</li>\n");+}++static void+rndr_paragraph(struct buf *ob, struct buf *text, void *opaque)+{+	struct html_renderopt *options = opaque;+	size_t i = 0;++	if (ob->size) bufputc(ob, '\n');++	if (!text || !text->size)+		return;++	while (i < text->size && isspace(text->data[i])) i++;++	if (i == text->size)+		return;++	BUFPUTSL(ob, "<p>");+	if (options->flags & HTML_HARD_WRAP) {+		size_t org;+		while (i < text->size) {+			org = i;+			while (i < text->size && text->data[i] != '\n')+				i++;++			if (i > org)+				bufput(ob, text->data + org, i - org);++			/*+			 * do not insert a line break if this newline+			 * is the last character on the paragraph+			 */+			if (i >= text->size - 1)+				break;+			+			rndr_linebreak(ob, opaque);+			i++;+		}+	} else {+		bufput(ob, &text->data[i], text->size - i);+	}+	BUFPUTSL(ob, "</p>\n");+}++static void+rndr_raw_block(struct buf *ob, struct buf *text, void *opaque)+{+	size_t org, sz;+	if (!text) return;+	sz = text->size;+	while (sz > 0 && text->data[sz - 1] == '\n') sz -= 1;+	org = 0;+	while (org < sz && text->data[org] == '\n') org += 1;+	if (org >= sz) return;+	if (ob->size) bufputc(ob, '\n');+	bufput(ob, text->data + org, sz - org);+	bufputc(ob, '\n');+}++static int+rndr_triple_emphasis(struct buf *ob, struct buf *text, void *opaque)+{+	if (!text || !text->size) return 0;+	BUFPUTSL(ob, "<strong><em>");+	bufput(ob, text->data, text->size);+	BUFPUTSL(ob, "</em></strong>");+	return 1;+}++static void+rndr_hrule(struct buf *ob, void *opaque)+{+	struct html_renderopt *options = opaque;	+	if (ob->size) bufputc(ob, '\n');+	bufputs(ob, USE_XHTML(options) ? "<hr/>\n" : "<hr>\n");+}++static int+rndr_image(struct buf *ob, struct buf *link, struct buf *title, struct buf *alt, void *opaque)+{+	struct html_renderopt *options = opaque;	+	if (!link || !link->size) return 0;+	BUFPUTSL(ob, "<img src=\"");+	sdhtml_escape(ob, link->data, link->size);+	BUFPUTSL(ob, "\" alt=\"");+	if (alt && alt->size)+		sdhtml_escape(ob, alt->data, alt->size);+	if (title && title->size) {+		BUFPUTSL(ob, "\" title=\"");+		sdhtml_escape(ob, title->data, title->size); }++	bufputs(ob, USE_XHTML(options) ? "\"/>" : "\">");+	return 1;+}++static int+rndr_raw_html(struct buf *ob, struct buf *text, void *opaque)+{+	struct html_renderopt *options = opaque;	++	if ((options->flags & HTML_SKIP_HTML) != 0)+		return 1;++	if ((options->flags & HTML_SKIP_STYLE) != 0 && sdhtml_tag(text->data, text->size, "style"))+		return 1;++	if ((options->flags & HTML_SKIP_LINKS) != 0 && sdhtml_tag(text->data, text->size, "a"))+		return 1;++	if ((options->flags & HTML_SKIP_IMAGES) != 0 && sdhtml_tag(text->data, text->size, "img"))+		return 1;++	bufput(ob, text->data, text->size);+	return 1;+}++static void+rndr_table(struct buf *ob, struct buf *header, struct buf *body, void *opaque)+{+	if (ob->size) bufputc(ob, '\n');+	BUFPUTSL(ob, "<table><thead>\n");+	if (header)+		bufput(ob, header->data, header->size);+	BUFPUTSL(ob, "</thead><tbody>\n");+	if (body)+		bufput(ob, body->data, body->size);+	BUFPUTSL(ob, "</tbody></table>\n");+}++static void+rndr_tablerow(struct buf *ob, struct buf *text, void *opaque)+{+	BUFPUTSL(ob, "<tr>\n");+	if (text)+		bufput(ob, text->data, text->size);+	BUFPUTSL(ob, "</tr>\n");+}++static void+rndr_tablecell(struct buf *ob, struct buf *text, int flags, void *opaque)+{+	if (flags & MKD_TABLE_HEADER) {+		BUFPUTSL(ob, "<th");+	} else {+		BUFPUTSL(ob, "<td");+	}++	switch (flags & MKD_TABLE_ALIGNMASK) {+	case MKD_TABLE_ALIGN_CENTER:+		BUFPUTSL(ob, " align=\"center\">");+		break;++	case MKD_TABLE_ALIGN_L:+		BUFPUTSL(ob, " align=\"left\">");+		break;++	case MKD_TABLE_ALIGN_R:+		BUFPUTSL(ob, " align=\"right\">");+		break;++	default:+		BUFPUTSL(ob, ">");+	}++	if (text)+		bufput(ob, text->data, text->size);++	if (flags & MKD_TABLE_HEADER) {+		BUFPUTSL(ob, "</th>\n");+	} else {+		BUFPUTSL(ob, "</td>\n");+	}+}++static int+rndr_superscript(struct buf *ob, struct buf *text, void *opaque)+{+	if (!text || !text->size) return 0;+	BUFPUTSL(ob, "<sup>");+	bufput(ob, text->data, text->size);+	BUFPUTSL(ob, "</sup>");+	return 1;+}++static void+rndr_normal_text(struct buf *ob, struct buf *text, void *opaque)+{+	if (text)+		sdhtml_escape(ob, text->data, text->size);+}++static void+toc_header(struct buf *ob, struct buf *text, int level, void *opaque)+{+	struct html_renderopt *options = opaque;++	while (level > options->toc_data.current_level) {+		if (options->toc_data.current_level > 0)+			BUFPUTSL(ob, "<li>");+		BUFPUTSL(ob, "<ul>\n");+		options->toc_data.current_level++;+	}++	while (level < options->toc_data.current_level) {+		BUFPUTSL(ob, "</ul>");+		if (options->toc_data.current_level > 1)+			BUFPUTSL(ob, "</li>\n");+		options->toc_data.current_level--;+	}++	bufprintf(ob, "<li><a href=\"#toc_%d\">", options->toc_data.header_count++);+	if (text)+		bufput(ob, text->data, text->size);+	BUFPUTSL(ob, "</a></li>\n");+}++static void+toc_finalize(struct buf *ob, void *opaque)+{+	struct html_renderopt *options = opaque;++	while (options->toc_data.current_level > 1) {+		BUFPUTSL(ob, "</ul></li>\n");+		options->toc_data.current_level--;+	}++	if (options->toc_data.current_level)+		BUFPUTSL(ob, "</ul>\n");+}++void+sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options)+{+	static const struct sd_callbacks cb_default = {+		NULL,+		NULL,+		NULL,+		toc_header,+		NULL,+		NULL,+		NULL,+		NULL,+		NULL,+		NULL,+		NULL,++		NULL,+		rndr_codespan,+		rndr_double_emphasis,+		rndr_emphasis,+		NULL,+		NULL,+		NULL,+		NULL,+		rndr_triple_emphasis,+		rndr_strikethrough,+		rndr_superscript,++		NULL,+		NULL,++		NULL,+		toc_finalize,+	};++	memset(options, 0x0, sizeof(struct html_renderopt));+	options->flags = HTML_TOC;++	memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));+}++void+sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options, unsigned int render_flags)+{+	static const struct sd_callbacks cb_default = {+		rndr_blockcode,+		rndr_blockquote,+		rndr_raw_block,+		rndr_header,+		rndr_hrule,+		rndr_list,+		rndr_listitem,+		rndr_paragraph,+		rndr_table,+		rndr_tablerow,+		rndr_tablecell,++		rndr_autolink,+		rndr_codespan,+		rndr_double_emphasis,+		rndr_emphasis,+		rndr_image,+		rndr_linebreak,+		rndr_link,+		rndr_raw_html,+		rndr_triple_emphasis,+		rndr_strikethrough,+		rndr_superscript,++		NULL,+		rndr_normal_text,++		NULL,+		NULL,+	};++	/* Prepare the options pointer */+	memset(options, 0x0, sizeof(struct html_renderopt));+	options->flags = render_flags;++	/* Prepare the callbacks */+	memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));++	if (render_flags & HTML_SKIP_IMAGES)+		callbacks->image = NULL;++	if (render_flags & HTML_SKIP_LINKS) {+		callbacks->link = NULL;+		callbacks->autolink = NULL;+	}++	if (render_flags & HTML_SKIP_HTML)+		callbacks->blockhtml = NULL;++	if (render_flags & HTML_GITHUB_BLOCKCODE)+		callbacks->blockcode = rndr_blockcode_github;+}
+ cbits/html.h view
@@ -0,0 +1,71 @@+/*+ * Copyright (c) 2011, Vicent Marti+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++#ifndef UPSKIRT_HTML_H+#define UPSKIRT_HTML_H++#include "markdown.h"+#include "buffer.h"+#include <stdlib.h>++struct html_renderopt {+	struct {+		int header_count;+		int current_level;+	} toc_data;++	unsigned int flags;++	/* extra callbacks */+	void (*link_attributes)(struct buf *ob, struct buf *url, void *self);+};++typedef enum {+	HTML_SKIP_HTML = (1 << 0),+	HTML_SKIP_STYLE = (1 << 1),+	HTML_SKIP_IMAGES = (1 << 2),+	HTML_SKIP_LINKS = (1 << 3),+	HTML_EXPAND_TABS = (1 << 5),+	HTML_SAFELINK = (1 << 7),+	HTML_TOC = (1 << 8),+	HTML_HARD_WRAP = (1 << 9),+	HTML_GITHUB_BLOCKCODE = (1 << 10),+	HTML_USE_XHTML = (1 << 11),+} html_render_mode;++typedef enum {+	HTML_TAG_NONE = 0,+	HTML_TAG_OPEN,+	HTML_TAG_CLOSE,+} html_tag;++void+sdhtml_escape(struct buf *ob, const char *src, size_t size);++int+sdhtml_tag(const char *tag_data, size_t tag_size, const char *tagname);++extern void+sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr, unsigned int render_flags);++extern void+sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr);++extern void+sdhtml_smartypants(struct buf *ob, struct buf *text);++#endif+
+ cbits/html_smartypants.c view
@@ -0,0 +1,383 @@+/*+ * Copyright (c) 2011, Vicent Marti+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++#include "buffer.h"+#include "html.h"++#include <string.h>+#include <stdlib.h>+#include <stdio.h>+#include <ctype.h>++struct smartypants_data {+	int in_squote;+	int in_dquote;+};++static size_t smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);+static size_t smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);+static size_t smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);+static size_t smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);+static size_t smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);+static size_t smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);+static size_t smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);+static size_t smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);+static size_t smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);+static size_t smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);++static size_t (*smartypants_cb_ptrs[])+	(struct buf *, struct smartypants_data *, char, const char *, size_t) = +{+	NULL,					/* 0 */+	smartypants_cb__dash,	/* 1 */+	smartypants_cb__parens,	/* 2 */+	smartypants_cb__squote, /* 3 */+	smartypants_cb__dquote, /* 4 */+	smartypants_cb__amp,	/* 5 */+	smartypants_cb__period,	/* 6 */+	smartypants_cb__number,	/* 7 */+	smartypants_cb__ltag,	/* 8 */+	smartypants_cb__backtick, /* 9 */+	smartypants_cb__escape, /* 10 */+};++static const char smartypants_cb_chars[] = {+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 4, 0, 0, 0, 5, 3, 2, 0, 0, 0, 0, 1, 6, 0,+	0, 7, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0,+	9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,+};++static inline int+word_boundary(char c)+{+	return c == 0 || isspace(c) || ispunct(c);+}++static int+smartypants_quotes(struct buf *ob, char previous_char, char next_char, char quote, int *is_open)+{+	char ent[8];++	if (*is_open && !word_boundary(next_char))+		return 0;++	if (!(*is_open) && !word_boundary(previous_char))+		return 0;++	snprintf(ent, sizeof(ent), "&%c%cquo;", (*is_open) ? 'r' : 'l', quote);+	*is_open = !(*is_open);+	bufputs(ob, ent);+	return 1;+}++static size_t+smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)+{+	if (size >= 2) {+		char t1 = tolower(text[1]);++		if (t1 == '\'') {+			if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote))+				return 1;+		}++		if ((t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') &&+			(size == 3 || word_boundary(text[2]))) {+			BUFPUTSL(ob, "&rsquo;");+			return 0;+		}++		if (size >= 3) {+			char t2 = tolower(text[2]);++			if (((t1 == 'r' && t2 == 'e') ||+				(t1 == 'l' && t2 == 'l') ||+				(t1 == 'v' && t2 == 'e')) &&+				(size == 4 || word_boundary(text[3]))) {+				BUFPUTSL(ob, "&rsquo;");+				return 0;+			}+		}+	}++	if (smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 's', &smrt->in_squote))+		return 0;++	bufputc(ob, text[0]);+	return 0;+}++static size_t+smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)+{+	if (size >= 3) {+		char t1 = tolower(text[1]);+		char t2 = tolower(text[2]);++		if (t1 == 'c' && t2 == ')') {+			BUFPUTSL(ob, "&copy;");+			return 2;+		}++		if (t1 == 'r' && t2 == ')') {+			BUFPUTSL(ob, "&reg;");+			return 2;+		}++		if (size >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')') {+			BUFPUTSL(ob, "&trade;");+			return 3;+		}+	}+	+	bufputc(ob, text[0]);+	return 0;+}++static size_t+smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)+{+	if (size >= 3 && text[1] == '-' && text[2] == '-') {+		BUFPUTSL(ob, "&mdash;");+		return 2;+	}++	if (size >= 2 && text[1] == '-') {+		BUFPUTSL(ob, "&ndash;");+		return 1;+	}++	bufputc(ob, text[0]);+	return 0;+}++static size_t+smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)+{+	if (size >= 6 && memcmp(text, "&quot;", 6) == 0) {+		if (smartypants_quotes(ob, previous_char, size >= 7 ? text[6] : 0, 'd', &smrt->in_dquote))+			return 5;+	}++	if (size >= 4 && memcmp(text, "&#0;", 4) == 0)+		return 3;++	bufputc(ob, '&');+	return 0;+}++static size_t+smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)+{+	if (size >= 3 && text[1] == '.' && text[2] == '.') {+		BUFPUTSL(ob, "&hellip;");+		return 2;+	}++	if (size >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.') {+		BUFPUTSL(ob, "&hellip;");+		return 4;+	}++	bufputc(ob, text[0]);+	return 0;+}++static size_t+smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)+{+	if (size >= 2 && text[1] == '`') {+		if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote))+			return 1;+	}++	return 0;+}++static size_t+smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)+{+	if (word_boundary(previous_char) && size >= 3) {+		if (text[0] == '1' && text[1] == '/' && text[2] == '2') {+			if (size == 3 || word_boundary(text[3])) {+				BUFPUTSL(ob, "&frac12;");+				return 2;+			}+		}++		if (text[0] == '1' && text[1] == '/' && text[2] == '4') {+			if (size == 3 || word_boundary(text[3]) ||+				(size >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h')) {+				BUFPUTSL(ob, "&frac14;");+				return 2;+			}+		}++		if (text[0] == '3' && text[1] == '/' && text[2] == '4') {+			if (size == 3 || word_boundary(text[3]) ||+				(size >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's')) {+				BUFPUTSL(ob, "&frac34;");+				return 2;+			}+		}+	}++	bufputc(ob, text[0]);+	return 0;+}++static size_t+smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)+{+	if (!smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 'd', &smrt->in_dquote))+		BUFPUTSL(ob, "&quot;");++	return 0;+}++static size_t+smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)+{+	static const char *skip_tags[] = {"pre", "code", "kbd", "script"};+	static const size_t skip_tags_count = 4;++	size_t tag, i = 0;++	while (i < size && text[i] != '>')+		i++;++	for (tag = 0; tag < skip_tags_count; ++tag) {+		if (sdhtml_tag(text, size, skip_tags[tag]) == HTML_TAG_OPEN)+			break;+	}++	if (tag < skip_tags_count) {+		for (;;) {+			while (i < size && text[i] != '<')+				i++;++			if (i == size)+				break;++			if (sdhtml_tag(text + i, size - i, skip_tags[tag]) == HTML_TAG_CLOSE)+				break;++			i++;+		}++		while (i < size && text[i] != '>')+			i++;+	}++	bufput(ob, text, i + 1);+	return i;+}++static size_t+smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)+{+	if (size < 2)+		return 0;++	switch (text[1]) {+	case '\\':+	case '"':+	case '\'':+	case '.':+	case '-':+	case '`':+		bufputc(ob, text[1]);+		return 1;++	default:+		bufputc(ob, '\\');+		return 0;+	}+}++#if 0+static struct {+    char c0;+    const char *pattern;+    const char *entity;+    int skip;+} smartypants_subs[] = {+    { '\'', "'s>",      "&rsquo;",  0 },+    { '\'', "'t>",      "&rsquo;",  0 },+    { '\'', "'re>",     "&rsquo;",  0 },+    { '\'', "'ll>",     "&rsquo;",  0 },+    { '\'', "'ve>",     "&rsquo;",  0 },+    { '\'', "'m>",      "&rsquo;",  0 },+    { '\'', "'d>",      "&rsquo;",  0 },+    { '-',  "--",       "&mdash;",  1 },+    { '-',  "<->",      "&ndash;",  0 },+    { '.',  "...",      "&hellip;", 2 },+    { '.',  ". . .",    "&hellip;", 4 },+    { '(',  "(c)",      "&copy;",   2 },+    { '(',  "(r)",      "&reg;",    2 },+    { '(',  "(tm)",     "&trade;",  3 },+    { '3',  "<3/4>",    "&frac34;", 2 },+    { '3',  "<3/4ths>", "&frac34;", 2 },+    { '1',  "<1/2>",    "&frac12;", 2 },+    { '1',  "<1/4>",    "&frac14;", 2 },+    { '1',  "<1/4th>",  "&frac14;", 2 },+    { '&',  "&#0;",      0,       3 },+};+#endif++void+sdhtml_smartypants(struct buf *ob, struct buf *text)+{+	size_t i;+	struct smartypants_data smrt = {0, 0};++	if (!text)+		return;++	bufgrow(ob, text->size);++	for (i = 0; i < text->size; ++i) {+		size_t org;+		char action = 0;++		org = i;+		while (i < text->size && (action = smartypants_cb_chars[(unsigned char)text->data[i]]) == 0)+			i++;++		if (i > org)+			bufput(ob, text->data + org, i - org);++		if (i < text->size) {+			i += smartypants_cb_ptrs[(int)action]+				(ob, &smrt, i ? text->data[i - 1] : 0, text->data + i, text->size - i);+		}+	}+}++
+ cbits/markdown.c view
@@ -0,0 +1,2328 @@+/* markdown.c - generic markdown parser */++/*+ * Copyright (c) 2009, Natacha Porté+ * Copyright (c) 2011, Vicent Marti+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++#include "markdown.h"+#include "array.h"++#include <assert.h>+#include <string.h>+//#include <strings.h> /* for strncasecmp */+#include <ctype.h>+#include <stdio.h>++#define BUFFER_BLOCK 0+#define BUFFER_SPAN 1++#define MKD_LI_END 8	/* internal list flag */++/***************+ * LOCAL TYPES *+ ***************/++/* link_ref • reference to a link */+struct link_ref {+	struct buf *id;+	struct buf *link;+	struct buf *title;+};++/* char_trigger • function pointer to render active chars */+/*   returns the number of chars taken care of */+/*   data is the pointer of the beginning of the span */+/*   offset is the number of valid chars before data */+struct render;+typedef size_t+(*char_trigger)(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);++static size_t char_emphasis(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);+static size_t char_linebreak(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);+static size_t char_codespan(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);+static size_t char_escape(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);+static size_t char_entity(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);+static size_t char_langle_tag(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);+static size_t char_autolink_url(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);+static size_t char_autolink_email(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);+static size_t char_autolink_www(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);+static size_t char_link(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);+static size_t char_superscript(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size);++enum markdown_char_t {+	MD_CHAR_NONE = 0,+	MD_CHAR_EMPHASIS,+	MD_CHAR_CODESPAN,+	MD_CHAR_LINEBREAK,+	MD_CHAR_LINK,+	MD_CHAR_LANGLE,+	MD_CHAR_ESCAPE,+	MD_CHAR_ENTITITY,+	MD_CHAR_AUTOLINK_URL,+	MD_CHAR_AUTOLINK_EMAIL,+	MD_CHAR_AUTOLINK_WWW,+	MD_CHAR_SUPERSCRIPT,+};++static char_trigger markdown_char_ptrs[] = {+	NULL,+	&char_emphasis,+	&char_codespan,+	&char_linebreak,+	&char_link,+	&char_langle_tag,+	&char_escape,+	&char_entity,+	&char_autolink_url,+	&char_autolink_email,+	&char_autolink_www,+	&char_superscript,+};++/* render • structure containing one particular render */+struct render {+	struct sd_callbacks	cb;+	void *opaque;++	struct array refs;+	char active_char[256];+	struct parray work_bufs[2];+	unsigned int ext_flags;+	size_t max_nesting;+};++/* html_tag • structure for quick HTML tag search (inspired from discount) */+struct html_tag {+	const char *text;+	size_t size;+};++static inline struct buf *+rndr_newbuf(struct render *rndr, int type)+{+	static const size_t buf_size[2] = {256, 64};+	struct buf *work = NULL;+	struct parray *queue = &rndr->work_bufs[type];++	if (queue->size < queue->asize) {+		work = queue->item[queue->size++];+		work->size = 0;+	} else {+		work = bufnew(buf_size[type]);+		parr_push(queue, work);+	}++	return work;+}++static inline void+rndr_popbuf(struct render *rndr, int type)+{+	rndr->work_bufs[type].size--;+}++/********************+ * GLOBAL VARIABLES *+ ********************/++/* block_tags • recognised block tags, sorted by cmp_html_tag */+static struct html_tag block_tags[] = {+/*0*/	{ "p",		1 },+	{ "dl",		2 },+	{ "h1",		2 },+	{ "h2",		2 },+	{ "h3",		2 },+	{ "h4",		2 },+	{ "h5",		2 },+	{ "h6",		2 },+	{ "ol",		2 },+	{ "ul",		2 },+	{ "del",	3 }, /* 10 */+	{ "div",	3 },+	{ "ins",	3 }, /* 12 */+	{ "pre",	3 },+	{ "form",	4 },+	{ "math",	4 },+	{ "table",	5 },+	{ "figure",	6 },+	{ "iframe",	6 },+	{ "script",	6 },+	{ "fieldset",	8 },+	{ "noscript",	8 },+	{ "blockquote",	10 }+};++#define INS_TAG (block_tags + 12)+#define DEL_TAG (block_tags + 10)++/***************************+ * HELPER FUNCTIONS *+ ***************************/+static void+unscape_text(struct buf *ob, struct buf *src)+{+	size_t i = 0, org;+	while (i < src->size) {+		org = i;+		while (i < src->size && src->data[i] != '\\')+			i++;++		if (i > org)+			bufput(ob, src->data + org, i - org);++		if (i + 1 >= src->size)+			break;++		bufputc(ob, src->data[i + 1]);+		i += 2;+	}+}++/* cmp_link_ref • comparison function for link_ref sorted arrays */+static int+cmp_link_ref(void *key, void *array_entry)+{+	struct link_ref *lr = array_entry;+	return bufcasecmp(key, lr->id);+}++/* cmp_link_ref_sort • comparison function for link_ref qsort */+static int+cmp_link_ref_sort(const void *a, const void *b)+{+	const struct link_ref *lra = a;+	const struct link_ref *lrb = b;+	return bufcasecmp(lra->id, lrb->id);+}++/* cmp_html_tag • comparison function for bsearch() (stolen from discount) */+static int+cmp_html_tag(const void *a, const void *b)+{+	const struct html_tag *hta = a;+	const struct html_tag *htb = b;+	if (hta->size != htb->size) return (int)(hta->size - htb->size);+	return strncasecmp(hta->text, htb->text, hta->size);+}+++/* find_block_tag • returns the current block tag */+static struct html_tag *+find_block_tag(char *data, size_t size)+{+	size_t i = 0;+	struct html_tag key;++	/* looking for the word end */+	while (i < size && ((data[i] >= '0' && data[i] <= '9')+				|| (data[i] >= 'A' && data[i] <= 'Z')+				|| (data[i] >= 'a' && data[i] <= 'z')))+		i++;+	if (i >= size) return 0;++	/* binary search of the tag */+	key.text = data;+	key.size = i;+	return bsearch(&key, block_tags,+				sizeof block_tags / sizeof block_tags[0],+				sizeof block_tags[0], cmp_html_tag);+}++/****************************+ * INLINE PARSING FUNCTIONS *+ ****************************/++/* is_mail_autolink • looks for the address part of a mail autolink and '>' */+/* this is less strict than the original markdown e-mail address matching */+static size_t+is_mail_autolink(char *data, size_t size)+{+	size_t i = 0, nb = 0;++	/* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */+	for (i = 0; i < size; ++i) {+		if (isalnum(data[i]))+			continue;++		switch (data[i]) {+			case '@':+				nb++;++			case '-':+			case '.':+			case '_':+				break;++			case '>':+				return (nb == 1) ? i + 1 : 0;++			default:+				return 0;+		}+	}++	return 0;+}++/* tag_length • returns the length of the given tag, or 0 is it's not valid */+static size_t+tag_length(char *data, size_t size, enum mkd_autolink *autolink)+{+	size_t i, j;++	/* a valid tag can't be shorter than 3 chars */+	if (size < 3) return 0;++	/* begins with a '<' optionally followed by '/', followed by letter or number */+	if (data[0] != '<') return 0;+	i = (data[1] == '/') ? 2 : 1;++	if (!isalnum(data[i]))+		return 0;++	/* scheme test */+	*autolink = MKDA_NOT_AUTOLINK;++	/* try to find the beginning of an URI */+	while (i < size && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-'))+		i++;++	if (i > 1 && data[i] == '@') {+		if ((j = is_mail_autolink(data + i, size - i)) != 0) {+			*autolink = MKDA_EMAIL;+			return i + j;+		}+	}++	if (i > 2 && data[i] == ':') {+		*autolink = MKDA_NORMAL;+		i++;+	}++	/* completing autolink test: no whitespace or ' or " */+	if (i >= size)+		*autolink = MKDA_NOT_AUTOLINK;++	else if (*autolink) {+		j = i;++		while (i < size) {+			if (data[i] == '\\') i += 2;+			else if (data[i] == '>' || data[i] == '\'' ||+					data[i] == '"' || isspace(data[i])) break;+			else i++;+		}++		if (i >= size) return 0;+		if (i > j && data[i] == '>') return i + 1;+		/* one of the forbidden chars has been found */+		*autolink = MKDA_NOT_AUTOLINK;+	}++	/* looking for sometinhg looking like a tag end */+	while (i < size && data[i] != '>') i++;+	if (i >= size) return 0;+	return i + 1;+}++/* parse_inline • parses inline markdown elements */+static void+parse_inline(struct buf *ob, struct render *rndr, char *data, size_t size)+{+	size_t i = 0, end = 0;+	char action = 0;+	struct buf work = { 0, 0, 0, 0, 0 };++	if (rndr->work_bufs[BUFFER_SPAN].size ++		rndr->work_bufs[BUFFER_BLOCK].size > (int)rndr->max_nesting)+		return;++	while (i < size) {+		/* copying inactive chars into the output */+		while (end < size && (action = rndr->active_char[(unsigned char)data[end]]) == 0) {+			end++;+		}++		if (rndr->cb.normal_text) {+			work.data = data + i;+			work.size = end - i;+			rndr->cb.normal_text(ob, &work, rndr->opaque);+		}+		else+			bufput(ob, data + i, end - i);++		if (end >= size) break;+		i = end;++		/* calling the trigger */+		end = markdown_char_ptrs[(int)action](ob, rndr, data + i, i, size - i);+		if (!end) /* no action from the callback */+			end = i + 1;+		else { +			i += end;+			end = i;+		} +	}+}++/* find_emph_char • looks for the next emph char, skipping other constructs */+static size_t+find_emph_char(char *data, size_t size, char c)+{+	size_t i = 1;++	while (i < size) {+		while (i < size && data[i] != c && data[i] != '`' && data[i] != '[')+			i++;++		if (i == size)+			return 0;++		if (data[i] == c)+			return i;++		/* not counting escaped chars */+		if (i && data[i - 1] == '\\') {+			i++; continue;+		}++		if (data[i] == '`') {+			size_t span_nb = 0, bt;+			size_t tmp_i = 0;++			/* counting the number of opening backticks */+			while (i < size && data[i] == '`') {+				i++; span_nb++;+			}++			if (i >= size) return 0;++			/* finding the matching closing sequence */+			bt = 0;+			while (i < size && bt < span_nb) {+				if (!tmp_i && data[i] == c) tmp_i = i;+				if (data[i] == '`') bt++;+				else bt = 0;+				i++;+			}++			if (i >= size) return tmp_i;+			i++;+		}+		/* skipping a link */+		else if (data[i] == '[') {+			size_t tmp_i = 0;+			char cc;++			i++;+			while (i < size && data[i] != ']') {+				if (!tmp_i && data[i] == c) tmp_i = i;+				i++;+			}++			i++;+			while (i < size && (data[i] == ' ' || data[i] == '\t' || data[i] == '\n'))+				i++;++			if (i >= size)+				return tmp_i;++			if (data[i] != '[' && data[i] != '(') { /* not a link*/+				if (tmp_i) return tmp_i;+				else continue;+			}++			cc = data[i];+			i++;+			while (i < size && data[i] != cc) {+				if (!tmp_i && data[i] == c) tmp_i = i;+				i++;+			}++			if (i >= size)+				return tmp_i;++			i++;+		}+	}++	return 0;+}++/* parse_emph1 • parsing single emphase */+/* closed by a symbol not preceded by whitespace and not followed by symbol */+static size_t+parse_emph1(struct buf *ob, struct render *rndr, char *data, size_t size, char c)+{+	size_t i = 0, len;+	struct buf *work = 0;+	int r;++	if (!rndr->cb.emphasis) return 0;++	/* skipping one symbol if coming from emph3 */+	if (size > 1 && data[0] == c && data[1] == c) i = 1;++	while (i < size) {+		len = find_emph_char(data + i, size - i, c);+		if (!len) return 0;+		i += len;+		if (i >= size) return 0;++		if (i + 1 < size && data[i + 1] == c) {+			i++;+			continue;+		}++		if (data[i] == c && !isspace(data[i - 1])) {++			if (rndr->ext_flags & MKDEXT_NO_INTRA_EMPHASIS) {+				if (!(i + 1 == size || isspace(data[i + 1]) || ispunct(data[i + 1])))+					continue;+			}++			work = rndr_newbuf(rndr, BUFFER_SPAN);+			parse_inline(work, rndr, data, i);+			r = rndr->cb.emphasis(ob, work, rndr->opaque);+			rndr_popbuf(rndr, BUFFER_SPAN);+			return r ? i + 1 : 0;+		}+	}++	return 0;+}++/* parse_emph2 • parsing single emphase */+static size_t+parse_emph2(struct buf *ob, struct render *rndr, char *data, size_t size, char c)+{+	int (*render_method)(struct buf *ob, struct buf *text, void *opaque);+	size_t i = 0, len;+	struct buf *work = 0;+	int r;++	render_method = (c == '~') ? rndr->cb.strikethrough : rndr->cb.double_emphasis;++	if (!render_method)+		return 0;+	+	while (i < size) {+		len = find_emph_char(data + i, size - i, c);+		if (!len) return 0;+		i += len;++		if (i + 1 < size && data[i] == c && data[i + 1] == c && i && !isspace(data[i - 1])) {+			work = rndr_newbuf(rndr, BUFFER_SPAN);+			parse_inline(work, rndr, data, i);+			r = render_method(ob, work, rndr->opaque);+			rndr_popbuf(rndr, BUFFER_SPAN);+			return r ? i + 2 : 0;+		}+		i++;+	}+	return 0;+}++/* parse_emph3 • parsing single emphase */+/* finds the first closing tag, and delegates to the other emph */+static size_t+parse_emph3(struct buf *ob, struct render *rndr, char *data, size_t size, char c)+{+	size_t i = 0, len;+	int r;++	while (i < size) {+		len = find_emph_char(data + i, size - i, c);+		if (!len) return 0;+		i += len;++		/* skip whitespace preceded symbols */+		if (data[i] != c || isspace(data[i - 1]))+			continue;++		if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && rndr->cb.triple_emphasis) {+			/* triple symbol found */+			struct buf *work = rndr_newbuf(rndr, BUFFER_SPAN);++			parse_inline(work, rndr, data, i);+			r = rndr->cb.triple_emphasis(ob, work, rndr->opaque);+			rndr_popbuf(rndr, BUFFER_SPAN);+			return r ? i + 3 : 0;++		} else if (i + 1 < size && data[i + 1] == c) {+			/* double symbol found, handing over to emph1 */+			len = parse_emph1(ob, rndr, data - 2, size + 2, c);+			if (!len) return 0;+			else return len - 2;++		} else {+			/* single symbol found, handing over to emph2 */+			len = parse_emph2(ob, rndr, data - 1, size + 1, c);+			if (!len) return 0;+			else return len - 1;+		}+	}+	return 0; +}++/* char_emphasis • single and double emphasis parsing */+static size_t+char_emphasis(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	char c = data[0];+	size_t ret;++	if (size > 2 && data[1] != c) {+		/* whitespace cannot follow an opening emphasis;+		 * strikethrough only takes two characters '~~' */+		if (c == '~' || isspace(data[1]) || (ret = parse_emph1(ob, rndr, data + 1, size - 1, c)) == 0)+			return 0;++		return ret + 1;+	}++	if (size > 3 && data[1] == c && data[2] != c) {+		if (isspace(data[2]) || (ret = parse_emph2(ob, rndr, data + 2, size - 2, c)) == 0)+			return 0;++		return ret + 2;+	}++	if (size > 4 && data[1] == c && data[2] == c && data[3] != c) {+		if (c == '~' || isspace(data[3]) || (ret = parse_emph3(ob, rndr, data + 3, size - 3, c)) == 0)+			return 0;++		return ret + 3;+	}++	return 0; +}+++/* char_linebreak • '\n' preceded by two spaces (assuming linebreak != 0) */+static size_t+char_linebreak(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	if (offset < 2 || data[-1] != ' ' || data[-2] != ' ')+		return 0;++	/* removing the last space from ob and rendering */+	while (ob->size && ob->data[ob->size - 1] == ' ')+		ob->size--;++	return rndr->cb.linebreak(ob, rndr->opaque) ? 1 : 0;+}+++/* char_codespan • '`' parsing a code span (assuming codespan != 0) */+static size_t+char_codespan(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	size_t end, nb = 0, i, f_begin, f_end;++	/* counting the number of backticks in the delimiter */+	while (nb < size && data[nb] == '`')+		nb++;++	/* finding the next delimiter */+	i = 0;+	for (end = nb; end < size && i < nb; end++) {+		if (data[end] == '`') i++;+		else i = 0;+	}++	if (i < nb && end >= size)+		return 0; /* no matching delimiter */++	/* trimming outside whitespaces */+	f_begin = nb;+	while (f_begin < end && (data[f_begin] == ' ' || data[f_begin] == '\t'))+		f_begin++;++	f_end = end - nb;+	while (f_end > nb && (data[f_end-1] == ' ' || data[f_end-1] == '\t'))+		f_end--;++	/* real code span */+	if (f_begin < f_end) {+		struct buf work = { data + f_begin, f_end - f_begin, 0, 0, 0 };+		if (!rndr->cb.codespan(ob, &work, rndr->opaque))+			end = 0;+	} else {+		if (!rndr->cb.codespan(ob, 0, rndr->opaque))+			end = 0;+	}++	return end;+}+++/* char_escape • '\\' backslash escape */+static size_t+char_escape(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	static const char *escape_chars = "\\`*_{}[]()#+-.!:|&<>";+	struct buf work = { 0, 0, 0, 0, 0 };++	if (size > 1) {+		if (strchr(escape_chars, data[1]) == NULL)+			return 0;++		if (rndr->cb.normal_text) {+			work.data = data + 1;+			work.size = 1;+			rndr->cb.normal_text(ob, &work, rndr->opaque);+		}+		else bufputc(ob, data[1]);+	}++	return 2;+}++/* char_entity • '&' escaped when it doesn't belong to an entity */+/* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */+static size_t+char_entity(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	size_t end = 1;+	struct buf work;++	if (end < size && data[end] == '#')+		end++;++	while (end < size && isalnum(data[end]))+		end++;++	if (end < size && data[end] == ';')+		end++; /* real entity */+	else+		return 0; /* lone '&' */++	if (rndr->cb.entity) {+		work.data = data;+		work.size = end;+		rndr->cb.entity(ob, &work, rndr->opaque);+	}+	else bufput(ob, data, end);++	return end;+}++/* char_langle_tag • '<' when tags or autolinks are allowed */+static size_t+char_langle_tag(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	enum mkd_autolink altype = MKDA_NOT_AUTOLINK;+	size_t end = tag_length(data, size, &altype);+	struct buf work = { data, end, 0, 0, 0 };+	int ret = 0;++	if (end > 2) {+		if (rndr->cb.autolink && altype != MKDA_NOT_AUTOLINK) {+			struct buf *u_link = rndr_newbuf(rndr, BUFFER_SPAN);+			work.data = data + 1;+			work.size = end - 2;+			unscape_text(u_link, &work);+			ret = rndr->cb.autolink(ob, u_link, altype, rndr->opaque);+			rndr_popbuf(rndr, BUFFER_SPAN);+		}+		else if (rndr->cb.raw_html_tag)+			ret = rndr->cb.raw_html_tag(ob, &work, rndr->opaque);+	}++	if (!ret) return 0;+	else return end;+}++static size_t+char_autolink_www(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	struct buf *link, *link_url;+	size_t link_len, rewind;++	if (!rndr->cb.link)+		return 0;++	link = rndr_newbuf(rndr, BUFFER_SPAN);++	if ((link_len = sd_autolink__www(&rewind, link, data, offset, size)) > 0) {+		link_url = rndr_newbuf(rndr, BUFFER_SPAN);+		BUFPUTSL(link_url, "http://");+		bufput(link_url, link->data, link->size);++		ob->size -= rewind;+		rndr->cb.link(ob, link_url, NULL, link, rndr->opaque);+		rndr_popbuf(rndr, BUFFER_SPAN);+	}++	rndr_popbuf(rndr, BUFFER_SPAN);+	return link_len;+}++static size_t+char_autolink_email(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	struct buf *link;+	size_t link_len, rewind;++	if (!rndr->cb.autolink)+		return 0;++	link = rndr_newbuf(rndr, BUFFER_SPAN);++	if ((link_len = sd_autolink__email(&rewind, link, data, offset, size)) > 0) {+		ob->size -= rewind;+		rndr->cb.autolink(ob, link, MKDA_EMAIL, rndr->opaque);+	}++	rndr_popbuf(rndr, BUFFER_SPAN);+	return link_len;+}++static size_t+char_autolink_url(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	struct buf *link;+	size_t link_len, rewind;++	if (!rndr->cb.autolink)+		return 0;++	link = rndr_newbuf(rndr, BUFFER_SPAN);++	if ((link_len = sd_autolink__url(&rewind, link, data, offset, size)) > 0) {+		ob->size -= rewind;+		rndr->cb.autolink(ob, link, MKDA_NORMAL, rndr->opaque);+	}++	rndr_popbuf(rndr, BUFFER_SPAN);+	return link_len;+}++/* char_link • '[': parsing a link or an image */+static size_t+char_link(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	int is_img = (offset && data[-1] == '!'), level;+	size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0;+	struct buf *content = 0;+	struct buf *link = 0;+	struct buf *title = 0;+	struct buf *u_link = 0;+	size_t org_work_size = rndr->work_bufs[BUFFER_SPAN].size;+	int text_has_nl = 0, ret = 0;++	/* checking whether the correct renderer exists */+	if ((is_img && !rndr->cb.image) || (!is_img && !rndr->cb.link))+		goto cleanup;++	/* looking for the matching closing bracket */+	for (level = 1; i < size; i++) {+		if (data[i] == '\n')+			text_has_nl = 1;++		else if (data[i - 1] == '\\')+			continue;++		else if (data[i] == '[')+			level++;++		else if (data[i] == ']') {+			level--;+			if (level <= 0)+				break;+		}+	}++	if (i >= size)+		goto cleanup;++	txt_e = i;+	i++;++	/* skip any amount of whitespace or newline */+	/* (this is much more laxist than original markdown syntax) */+	while (i < size && isspace(data[i]))+		i++;++	/* inline style link */+	if (i < size && data[i] == '(') {+		/* skipping initial whitespace */+		i++;++		while (i < size && isspace(data[i]))+			i++;++		link_b = i;++		/* looking for link end: ' " ) */+		while (i < size) {+			if (data[i] == '\\') i += 2;+			else if (data[i] == ')' || data[i] == '\'' || data[i] == '"') break;+			else i++;+		}++		if (i >= size) goto cleanup;+		link_e = i;++		/* looking for title end if present */+		if (data[i] == '\'' || data[i] == '"') {+			i++;+			title_b = i;++			while (i < size) {+				if (data[i] == '\\') i += 2;+				else if (data[i] == ')') break;+				else i++;+			}++			if (i >= size) goto cleanup;++			/* skipping whitespaces after title */+			title_e = i - 1;+			while (title_e > title_b && isspace(data[title_e]))+				title_e--;++			/* checking for closing quote presence */+			if (data[title_e] != '\'' &&  data[title_e] != '"') {+				title_b = title_e = 0;+				link_e = i;+			}+		}++		/* remove whitespace at the end of the link */+		while (link_e > link_b && isspace(data[link_e - 1]))+			link_e--;++		/* remove optional angle brackets around the link */+		if (data[link_b] == '<') link_b++;+		if (data[link_e - 1] == '>') link_e--;++		/* building escaped link and title */+		if (link_e > link_b) {+			link = rndr_newbuf(rndr, BUFFER_SPAN);+			bufput(link, data + link_b, link_e - link_b);+		}++		if (title_e > title_b) {+			title = rndr_newbuf(rndr, BUFFER_SPAN);+			bufput(title, data + title_b, title_e - title_b);+		}++		i++;+	}++	/* reference style link */+	else if (i < size && data[i] == '[') {+		struct buf id = { 0, 0, 0, 0, 0 };+		struct link_ref *lr;++		/* looking for the id */+		i++;+		link_b = i;+		while (i < size && data[i] != ']') i++;+		if (i >= size) goto cleanup;+		link_e = i;++		/* finding the link_ref */+		if (link_b == link_e) {+			if (text_has_nl) {+				struct buf *b = rndr_newbuf(rndr, BUFFER_SPAN);+				size_t j;++				for (j = 1; j < txt_e; j++) {+					if (data[j] != '\n')+						bufputc(b, data[j]);+					else if (data[j - 1] != ' ')+						bufputc(b, ' ');+				}++				id.data = b->data;+				id.size = b->size;+			} else {+				id.data = data + 1;+				id.size = txt_e - 1;+			}+		} else {+			id.data = data + link_b;+			id.size = link_e - link_b;+		}++		lr = arr_sorted_find(&rndr->refs, &id, cmp_link_ref);+		if (!lr) goto cleanup;++		/* keeping link and title from link_ref */+		link = lr->link;+		title = lr->title;+		i++;+	}++	/* shortcut reference style link */+	else {+		struct buf id = { 0, 0, 0, 0, 0 };+		struct link_ref *lr;++		/* crafting the id */+		if (text_has_nl) {+			struct buf *b = rndr_newbuf(rndr, BUFFER_SPAN);+			size_t j;++			for (j = 1; j < txt_e; j++) {+				if (data[j] != '\n')+					bufputc(b, data[j]);+				else if (data[j - 1] != ' ')+					bufputc(b, ' ');+			}++			id.data = b->data;+			id.size = b->size;+		} else {+			id.data = data + 1;+			id.size = txt_e - 1;+		}++		/* finding the link_ref */+		lr = arr_sorted_find(&rndr->refs, &id, cmp_link_ref);+		if (!lr) goto cleanup;++		/* keeping link and title from link_ref */+		link = lr->link;+		title = lr->title;++		/* rewinding the whitespace */+		i = txt_e + 1;+	}++	/* building content: img alt is escaped, link content is parsed */+	if (txt_e > 1) {+		content = rndr_newbuf(rndr, BUFFER_SPAN);+		if (is_img) bufput(content, data + 1, txt_e - 1);+		else parse_inline(content, rndr, data + 1, txt_e - 1);+	}++	if (link) {+		u_link = rndr_newbuf(rndr, BUFFER_SPAN);+		unscape_text(u_link, link);+	}++	/* calling the relevant rendering function */+	if (is_img) {+		if (ob->size && ob->data[ob->size - 1] == '!')+			ob->size -= 1;++		ret = rndr->cb.image(ob, u_link, title, content, rndr->opaque);+	} else {+		ret = rndr->cb.link(ob, u_link, title, content, rndr->opaque);+	}++	/* cleanup */+cleanup:+	rndr->work_bufs[BUFFER_SPAN].size = (int)org_work_size;+	return ret ? i : 0;+}++static size_t+char_superscript(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size)+{+	size_t sup_start, sup_len;+	struct buf *sup;++	if (!rndr->cb.superscript)+		return 0;++	if (size < 2)+		return 0;++	if (data[1] == '(') {+		sup_start = sup_len = 2;++		while (sup_len < size && data[sup_len] != ')' && data[sup_len - 1] != '\\')+			sup_len++;++		if (sup_len == size)+			return 0;+	} else {+		sup_start = sup_len = 1;++		while (sup_len < size && !isspace(data[sup_len]))+			sup_len++;+	}++	if (sup_len - sup_start == 0)+		return (sup_start == 2) ? 3 : 0;++	sup = rndr_newbuf(rndr, BUFFER_SPAN);+	parse_inline(sup, rndr, data + sup_start, sup_len - sup_start);+	rndr->cb.superscript(ob, sup, rndr->opaque);+	rndr_popbuf(rndr, BUFFER_SPAN);++	return (sup_start == 2) ? sup_len + 1 : sup_len;+}++/*********************************+ * BLOCK-LEVEL PARSING FUNCTIONS *+ *********************************/++/* is_empty • returns the line length when it is empty, 0 otherwise */+static size_t+is_empty(char *data, size_t size)+{+	size_t i;+	for (i = 0; i < size && data[i] != '\n'; i++)+		if (data[i] != ' ' && data[i] != '\t') return 0;+	return i + 1;+}++/* is_hrule • returns whether a line is a horizontal rule */+static int+is_hrule(char *data, size_t size)+{+	size_t i = 0, n = 0;+	char c;++	/* skipping initial spaces */+	if (size < 3) return 0;+	if (data[0] == ' ') { i++;+	if (data[1] == ' ') { i++;+	if (data[2] == ' ') { i++; } } }++	/* looking at the hrule char */+	if (i + 2 >= size+	|| (data[i] != '*' && data[i] != '-' && data[i] != '_'))+		return 0;+	c = data[i];++	/* the whole line must be the char or whitespace */+	while (i < size && data[i] != '\n') {+		if (data[i] == c) n++;+		else if (data[i] != ' ' && data[i] != '\t')+			return 0;+		i++; }++	return n >= 3;+}++/* check if a line is a code fence; return its size if it is */+static size_t+is_codefence(char *data, size_t size, struct buf *syntax)+{+	size_t i = 0, n = 0;+	char c;++	/* skipping initial spaces */+	if (size < 3) return 0;+	if (data[0] == ' ') { i++;+	if (data[1] == ' ') { i++;+	if (data[2] == ' ') { i++; } } }++	/* looking at the hrule char */+	if (i + 2 >= size || !(data[i] == '~' || data[i] == '`'))+		return 0;++	c = data[i];++	/* the whole line must be the char or whitespace */+	while (i < size && data[i] == c) {+		n++; i++;+	}++	if (n < 3)+		return 0;++	if (syntax != NULL) {+		size_t syn = 0;++		while (i < size && (data[i] == ' ' || data[i] == '\t'))+			i++;++		syntax->data = data + i;++		if (i < size && data[i] == '{') {+			i++; syntax->data++;++			while (i < size && data[i] != '}' && data[i] != '\n') {+				syn++; i++;+			}++			if (i == size || data[i] != '}')+				return 0;++			/* strip all whitespace at the beginning and the end+			 * of the {} block */+			while (syn > 0 && isspace(syntax->data[0])) {+				syntax->data++; syn--;+			}++			while (syn > 0 && isspace(syntax->data[syn - 1]))+				syn--;++			i++;+		} else {+			while (i < size && !isspace(data[i])) {+				syn++; i++;+			}+		}++		syntax->size = syn;+	}++	while (i < size && data[i] != '\n') {+		if (!isspace(data[i]))+			return 0;++		i++;+	}++	return i + 1;+}++/* is_atxheader • returns whether the line is a hash-prefixed header */+static int+is_atxheader(struct render *rndr, char *data, size_t size)+{+	if (data[0] != '#')+		return 0;++	if (rndr->ext_flags & MKDEXT_SPACE_HEADERS) {+		size_t level = 0;++		while (level < size && level < 6 && data[level] == '#')+			level++;++		if (level < size && data[level] != ' ' && data[level] != '\t')+			return 0;+	}++	return 1;+}++/* is_headerline • returns whether the line is a setext-style hdr underline */+static int+is_headerline(char *data, size_t size)+{+	size_t i = 0;++	/* test of level 1 header */+	if (data[i] == '=') {+		for (i = 1; i < size && data[i] == '='; i++);+		while (i < size && (data[i] == ' ' || data[i] == '\t')) i++;+		return (i >= size || data[i] == '\n') ? 1 : 0; }++	/* test of level 2 header */+	if (data[i] == '-') {+		for (i = 1; i < size && data[i] == '-'; i++);+		while (i < size && (data[i] == ' ' || data[i] == '\t')) i++;+		return (i >= size || data[i] == '\n') ? 2 : 0; }++	return 0;+}++/* prefix_quote • returns blockquote prefix length */+static size_t+prefix_quote(char *data, size_t size)+{+	size_t i = 0;+	if (i < size && data[i] == ' ') i++;+	if (i < size && data[i] == ' ') i++;+	if (i < size && data[i] == ' ') i++;+	if (i < size && data[i] == '>') {+		if (i + 1 < size && (data[i + 1] == ' ' || data[i+1] == '\t'))+			return i + 2;+		else return i + 1; }+	else return 0;+}++/* prefix_code • returns prefix length for block code*/+static size_t+prefix_code(char *data, size_t size)+{+	if (size > 0 && data[0] == '\t') return 1;+	if (size > 3 && data[0] == ' ' && data[1] == ' '+			&& data[2] == ' ' && data[3] == ' ') return 4;+	return 0;+}++/* prefix_oli • returns ordered list item prefix */+static size_t+prefix_oli(char *data, size_t size)+{+	size_t i = 0;+	if (i < size && data[i] == ' ') i++;+	if (i < size && data[i] == ' ') i++;+	if (i < size && data[i] == ' ') i++;+	if (i >= size || data[i] < '0' || data[i] > '9') return 0;+	while (i < size && data[i] >= '0' && data[i] <= '9') i++;+	if (i + 1 >= size || data[i] != '.'+	|| (data[i + 1] != ' ' && data[i + 1] != '\t')) return 0;+	return i + 2;+}++/* prefix_uli • returns ordered list item prefix */+static size_t+prefix_uli(char *data, size_t size)+{+	size_t i = 0;+	if (i < size && data[i] == ' ') i++;+	if (i < size && data[i] == ' ') i++;+	if (i < size && data[i] == ' ') i++;+	if (i + 1 >= size+	|| (data[i] != '*' && data[i] != '+' && data[i] != '-')+	|| (data[i + 1] != ' ' && data[i + 1] != '\t'))+		return 0;+	return i + 2;+}+++/* parse_block • parsing of one block, returning next char to parse */+static void parse_block(struct buf *ob, struct render *rndr,+			char *data, size_t size);+++/* parse_blockquote • handles parsing of a blockquote fragment */+static size_t+parse_blockquote(struct buf *ob, struct render *rndr, char *data, size_t size)+{+	size_t beg, end = 0, pre, work_size = 0;+	char *work_data = 0;+	struct buf *out = 0;++	out = rndr_newbuf(rndr, BUFFER_BLOCK);+	beg = 0;+	while (beg < size) {+		for (end = beg + 1; end < size && data[end - 1] != '\n'; end++);++		pre = prefix_quote(data + beg, end - beg);++		if (pre)+			beg += pre; /* skipping prefix */++		/* empty line followed by non-quote line */+		else if (is_empty(data + beg, end - beg) &&+				(end >= size || (prefix_quote(data + end, size - end) == 0 &&+				!is_empty(data + end, size - end))))+			break;++		if (beg < end) { /* copy into the in-place working buffer */+			/* bufput(work, data + beg, end - beg); */+			if (!work_data)+				work_data = data + beg;+			else if (data + beg != work_data + work_size)+				memmove(work_data + work_size, data + beg, end - beg);+			work_size += end - beg;+		}+		beg = end;+	}++	parse_block(out, rndr, work_data, work_size);+	if (rndr->cb.blockquote)+		rndr->cb.blockquote(ob, out, rndr->opaque);+	rndr_popbuf(rndr, BUFFER_BLOCK);+	return end;+}++static size_t+parse_htmlblock(struct buf *ob, struct render *rndr, char *data, size_t size, int do_render);++/* parse_blockquote • handles parsing of a regular paragraph */+static size_t+parse_paragraph(struct buf *ob, struct render *rndr, char *data, size_t size)+{+	size_t i = 0, end = 0;+	int level = 0;+	struct buf work = { data, 0, 0, 0, 0 }; /* volatile working buffer */++	while (i < size) {+		for (end = i + 1; end < size && data[end - 1] != '\n'; end++) /* empty */;++		if (is_empty(data + i, size - i) || (level = is_headerline(data + i, size - i)) != 0)+			break;++		if (rndr->ext_flags & MKDEXT_LAX_HTML_BLOCKS) {+			if (data[i] == '<' && rndr->cb.blockhtml && parse_htmlblock(ob, rndr, data + i, size - i, 0)) {+				end = i;+				break;+			}+		}++		if (is_atxheader(rndr, data + i, size - i) || is_hrule(data + i, size - i)) {+			end = i;+			break;+		}++		i = end;+	}++	work.size = i;+	while (work.size && data[work.size - 1] == '\n')+		work.size--;++	if (!level) {+		struct buf *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);+		parse_inline(tmp, rndr, work.data, work.size);+		if (rndr->cb.paragraph)+			rndr->cb.paragraph(ob, tmp, rndr->opaque);+		rndr_popbuf(rndr, BUFFER_BLOCK);+	} else {+		struct buf *header_work;++		if (work.size) {+			size_t beg;+			i = work.size;+			work.size -= 1;++			while (work.size && data[work.size] != '\n')+				work.size -= 1;++			beg = work.size + 1;+			while (work.size && data[work.size - 1] == '\n')+				work.size -= 1;++			if (work.size > 0) {+				struct buf *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);+				parse_inline(tmp, rndr, work.data, work.size);++				if (rndr->cb.paragraph)+					rndr->cb.paragraph(ob, tmp, rndr->opaque);++				rndr_popbuf(rndr, BUFFER_BLOCK);+				work.data += beg;+				work.size = i - beg;+			}+			else work.size = i;+		}++		header_work = rndr_newbuf(rndr, BUFFER_SPAN);+		parse_inline(header_work, rndr, work.data, work.size);++		if (rndr->cb.header)+			rndr->cb.header(ob, header_work, (int)level, rndr->opaque);++		rndr_popbuf(rndr, BUFFER_SPAN);+	}++	return end;+}++/* parse_fencedcode • handles parsing of a block-level code fragment */+static size_t+parse_fencedcode(struct buf *ob, struct render *rndr, char *data, size_t size)+{+	size_t beg, end;+	struct buf *work = 0;+	struct buf lang = { 0, 0, 0, 0, 0 };++	beg = is_codefence(data, size, &lang);+	if (beg == 0) return 0;++	work = rndr_newbuf(rndr, BUFFER_BLOCK);++	while (beg < size) {+		size_t fence_end;++		fence_end = is_codefence(data + beg, size - beg, NULL);+		if (fence_end != 0) {+			beg += fence_end;+			break;+		}++		for (end = beg + 1; end < size && data[end - 1] != '\n'; end++);++		if (beg < end) {+			/* verbatim copy to the working buffer,+				escaping entities */+			if (is_empty(data + beg, end - beg))+				bufputc(work, '\n');+			else bufput(work, data + beg, end - beg);+		}+		beg = end;+	}++	if (work->size && work->data[work->size - 1] != '\n')+		bufputc(work, '\n');++	if (rndr->cb.blockcode)+		rndr->cb.blockcode(ob, work, lang.size ? &lang : NULL, rndr->opaque);++	rndr_popbuf(rndr, BUFFER_BLOCK);+	return beg;+}++static size_t+parse_blockcode(struct buf *ob, struct render *rndr, char *data, size_t size)+{+	size_t beg, end, pre;+	struct buf *work = 0;++	work = rndr_newbuf(rndr, BUFFER_BLOCK);++	beg = 0;+	while (beg < size) {+		for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {};+		pre = prefix_code(data + beg, end - beg);++		if (pre)+			beg += pre; /* skipping prefix */+		else if (!is_empty(data + beg, end - beg))+			/* non-empty non-prefixed line breaks the pre */+			break;++		if (beg < end) {+			/* verbatim copy to the working buffer,+				escaping entities */+			if (is_empty(data + beg, end - beg))+				bufputc(work, '\n');+			else bufput(work, data + beg, end - beg);+		}+		beg = end;+	}++	while (work->size && work->data[work->size - 1] == '\n')+		work->size -= 1;++	bufputc(work, '\n');++	if (rndr->cb.blockcode)+		rndr->cb.blockcode(ob, work, NULL, rndr->opaque);++	rndr_popbuf(rndr, BUFFER_BLOCK);+	return beg;+}++/* parse_listitem • parsing of a single list item */+/*	assuming initial prefix is already removed */+static size_t+parse_listitem(struct buf *ob, struct render *rndr, char *data, size_t size, int *flags)+{+	struct buf *work = 0, *inter = 0;+	size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i;+	int in_empty = 0, has_inside_empty = 0;++	/* keeping track of the first indentation prefix */+	while (orgpre < 3 && orgpre < size && data[orgpre] == ' ')+		orgpre++;++	beg = prefix_uli(data, size);+	if (!beg)+		beg = prefix_oli(data, size);++	if (!beg)+		return 0;++	/* skipping to the beginning of the following line */+	end = beg;+	while (end < size && data[end - 1] != '\n')+		end++;++	/* getting working buffers */+	work = rndr_newbuf(rndr, BUFFER_SPAN);+	inter = rndr_newbuf(rndr, BUFFER_SPAN);++	/* putting the first line into the working buffer */+	bufput(work, data + beg, end - beg);+	beg = end;++	/* process the following lines */+	while (beg < size) {+		end++;++		while (end < size && data[end - 1] != '\n')+			end++;++		/* process an empty line */+		if (is_empty(data + beg, end - beg)) {+			in_empty = 1;+			beg = end;+			continue;+		}++		/* calculating the indentation */+		i = 0;+		while (i < 4 && beg + i < end && data[beg + i] == ' ')+			i++;++		pre = i;+		if (data[beg] == '\t') { i = 1; pre = 8; }++		/* checking for a new item */+		if ((prefix_uli(data + beg + i, end - beg - i) &&+			!is_hrule(data + beg + i, end - beg - i)) ||+			prefix_oli(data + beg + i, end - beg - i)) {+			if (in_empty)+				has_inside_empty = 1;++			if (pre == orgpre) /* the following item must have */+				break;             /* the same indentation */++			if (!sublist)+				sublist = work->size;+		}+		/* joining only indented stuff after empty lines */+		else if (in_empty && i < 4 && data[beg] != '\t') {+				*flags |= MKD_LI_END;+				break;+		}+		else if (in_empty) {+			bufputc(work, '\n');+			has_inside_empty = 1;+		}++		in_empty = 0;++		/* adding the line without prefix into the working buffer */+		bufput(work, data + beg + i, end - beg - i);+		beg = end;+	}++	/* render of li contents */+	if (has_inside_empty)+		*flags |= MKD_LI_BLOCK;++	if (*flags & MKD_LI_BLOCK) {+		/* intermediate render of block li */+		if (sublist && sublist < work->size) {+			parse_block(inter, rndr, work->data, sublist);+			parse_block(inter, rndr, work->data + sublist, work->size - sublist); +		}+		else+			parse_block(inter, rndr, work->data, work->size);+	} else {+		/* intermediate render of inline li */+		if (sublist && sublist < work->size) {+			parse_inline(inter, rndr, work->data, sublist);+			parse_block(inter, rndr, work->data + sublist, work->size - sublist);+		}+		else+			parse_inline(inter, rndr, work->data, work->size);+	}++	/* render of li itself */+	if (rndr->cb.listitem)+		rndr->cb.listitem(ob, inter, *flags, rndr->opaque);++	rndr_popbuf(rndr, BUFFER_SPAN);+	rndr_popbuf(rndr, BUFFER_SPAN);+	return beg;+}+++/* parse_list • parsing ordered or unordered list block */+static size_t+parse_list(struct buf *ob, struct render *rndr, char *data, size_t size, int flags)+{+	struct buf *work = 0;+	size_t i = 0, j;++	work = rndr_newbuf(rndr, BUFFER_BLOCK);++	while (i < size) {+		j = parse_listitem(work, rndr, data + i, size - i, &flags);+		i += j;++		if (!j || (flags & MKD_LI_END))+			break;+	}++	if (rndr->cb.list)+		rndr->cb.list(ob, work, flags, rndr->opaque);+	rndr_popbuf(rndr, BUFFER_BLOCK);+	return i;+}++/* parse_atxheader • parsing of atx-style headers */+static size_t+parse_atxheader(struct buf *ob, struct render *rndr, char *data, size_t size)+{+	size_t level = 0;+	size_t i, end, skip;++	while (level < size && level < 6 && data[level] == '#')+		level++;++	for (i = level; i < size && (data[i] == ' ' || data[i] == '\t'); i++);++	for (end = i; end < size && data[end] != '\n'; end++);+	skip = end;++	while (end && data[end - 1] == '#')+		end--;++	while (end && (data[end - 1] == ' ' || data[end - 1] == '\t'))+		end--;++	if (end > i) {+		struct buf *work = rndr_newbuf(rndr, BUFFER_SPAN);++		parse_inline(work, rndr, data + i, end - i);++		if (rndr->cb.header)+			rndr->cb.header(ob, work, (int)level, rndr->opaque);++		rndr_popbuf(rndr, BUFFER_SPAN);+	}++	return skip;+}+++/* htmlblock_end • checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */+/*	returns the length on match, 0 otherwise */+static size_t+htmlblock_end(struct html_tag *tag, struct render *rndr, char *data, size_t size)+{+	size_t i, w;++	/* assuming data[0] == '<' && data[1] == '/' already tested */++	/* checking if tag is a match */+	if (tag->size + 3 >= size+	|| strncasecmp(data + 2, tag->text, tag->size)+	|| data[tag->size + 2] != '>')+		return 0;++	/* checking white lines */+	i = tag->size + 3;+	w = 0;+	if (i < size && (w = is_empty(data + i, size - i)) == 0)+		return 0; /* non-blank after tag */+	i += w;+	w = 0;++	if (rndr->ext_flags & MKDEXT_LAX_HTML_BLOCKS) {+		if (i < size)+			w = is_empty(data + i, size - i);+	} else  {+		if (i < size && (w = is_empty(data + i, size - i)) == 0)+			return 0; /* non-blank line after tag line */+	}++	return i + w;+}+++/* parse_htmlblock • parsing of inline HTML block */+static size_t+parse_htmlblock(struct buf *ob, struct render *rndr, char *data, size_t size, int do_render)+{+	size_t i, j = 0;+	struct html_tag *curtag;+	int found;+	struct buf work = { data, 0, 0, 0, 0 };++	/* identification of the opening tag */+	if (size < 2 || data[0] != '<') return 0;+	curtag = find_block_tag(data + 1, size - 1);++	/* handling of special cases */+	if (!curtag) {++		/* HTML comment, laxist form */+		if (size > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') {+			i = 5;++			while (i < size && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>'))+				i++;++			i++;++			if (i < size)+				j = is_empty(data + i, size - i);++			if (j) {+				work.size = i + j;+				if (do_render && rndr->cb.blockhtml)+					rndr->cb.blockhtml(ob, &work, rndr->opaque);+				return work.size;+			} +		}++		/* HR, which is the only self-closing block tag considered */+		if (size > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) {+			i = 3;+			while (i < size && data[i] != '>')+				i++;++			if (i + 1 < size) {+				i++;+				j = is_empty(data + i, size - i);+				if (j) {+					work.size = i + j;+					if (do_render && rndr->cb.blockhtml)+						rndr->cb.blockhtml(ob, &work, rndr->opaque);+					return work.size;+				}+			} +		}++		/* no special case recognised */+		return 0;+	}++	/* looking for an unindented matching closing tag */+	/*	followed by a blank line */+	i = 1;+	found = 0;++	/* if not found, trying a second pass looking for indented match */+	/* but not if tag is "ins" or "del" (following original Markdown.pl) */+	if (curtag != INS_TAG && curtag != DEL_TAG) {+		i = 1;+		while (i < size) {+			i++;+			while (i < size && !(data[i - 1] == '<' && data[i] == '/'))+				i++;++			if (i + 2 + curtag->size >= size)+				break;++			j = htmlblock_end(curtag, rndr, data + i - 1, size - i + 1);++			if (j) {+				i += j - 1;+				found = 1;+				break;+			}+		} +	}++	if (!found) return 0;++	/* the end of the block has been found */+	work.size = i;+	if (do_render && rndr->cb.blockhtml)+		rndr->cb.blockhtml(ob, &work, rndr->opaque);++	return i;+}++static void+parse_table_row(struct buf *ob, struct render *rndr, char *data, size_t size, size_t columns, int *col_data)+{+	size_t i = 0, col;+	struct buf *row_work = 0;++	if (!rndr->cb.table_cell || !rndr->cb.table_row)+		return;++	row_work = rndr_newbuf(rndr, BUFFER_SPAN);++	if (i < size && data[i] == '|')+		i++;++	for (col = 0; col < columns && i < size; ++col) {+		size_t cell_start, cell_end;+		struct buf *cell_work;++		cell_work = rndr_newbuf(rndr, BUFFER_SPAN);++		while (i < size && isspace(data[i]))+			i++;++		cell_start = i;++		while (i < size && data[i] != '|')+			i++;++		cell_end = i - 1;++		while (cell_end > cell_start && isspace(data[cell_end]))+			cell_end--;++		parse_inline(cell_work, rndr, data + cell_start, 1 + cell_end - cell_start);+		rndr->cb.table_cell(row_work, cell_work, col_data[col], rndr->opaque);++		rndr_popbuf(rndr, BUFFER_SPAN);+		i++;+	}++	for (; col < columns; ++col) {+		struct buf empty_cell = {0, 0, 0, 0, 0};+		rndr->cb.table_cell(row_work, &empty_cell, col_data[col], rndr->opaque);+	}++	rndr->cb.table_row(ob, row_work, rndr->opaque);++	rndr_popbuf(rndr, BUFFER_SPAN);+}++static size_t+parse_table_header(struct buf *ob, struct render *rndr, char *data, size_t size, size_t *columns, int **column_data)+{+	int pipes;+	size_t i = 0, col, header_end, under_end;++	pipes = 0;+	while (i < size && data[i] != '\n')+		if (data[i++] == '|')+			pipes++;++	if (i == size || pipes == 0)+		return 0;++	header_end = i;++	if (data[0] == '|')+		pipes--;++	if (i > 2 && data[i - 1] == '|')+		pipes--;++	*columns = pipes + 1;+	*column_data = calloc(*columns, sizeof(int));++	/* Parse the header underline */+	i++;+	if (i < size && data[i] == '|')+		i++;++	under_end = i;+	while (under_end < size && data[under_end] != '\n')+		under_end++;++	for (col = 0; col < *columns && i < under_end; ++col) {+		size_t dashes = 0;++		(*column_data)[col] |= MKD_TABLE_HEADER;++		while (i < under_end && (data[i] == ' ' || data[i] == '\t'))+			i++;++		if (data[i] == ':') {+			i++; (*column_data)[col] |= MKD_TABLE_ALIGN_L;+			dashes++;+		}++		while (i < under_end && data[i] == '-') {+			i++; dashes++;+		}++		if (i < under_end && data[i] == ':') {+			i++; (*column_data)[col] |= MKD_TABLE_ALIGN_R;+			dashes++;+		}++		while (i < under_end && (data[i] == ' ' || data[i] == '\t'))+			i++;++		if (i < under_end && data[i] != '|')+			break;++		if (dashes < 3)+			break;++		i++;+	}++	if (col < *columns)+		return 0;++	parse_table_row(ob, rndr, data, header_end, *columns, *column_data);+	return under_end + 1;+}++static size_t+parse_table(struct buf *ob, struct render *rndr, char *data, size_t size)+{+	size_t i;++	struct buf *header_work = 0;+	struct buf *body_work = 0;++	size_t columns;+	int *col_data = NULL;++	header_work = rndr_newbuf(rndr, BUFFER_SPAN);+	body_work = rndr_newbuf(rndr, BUFFER_BLOCK);++	i = parse_table_header(header_work, rndr, data, size, &columns, &col_data);+	if (i > 0) {++		while (i < size) {+			size_t row_start;+			int pipes = 0;++			row_start = i;++			while (i < size && data[i] != '\n')+				if (data[i++] == '|')+					pipes++;++			if (pipes == 0 || i == size) {+				i = row_start;+				break;+			}++			parse_table_row(body_work, rndr, data + row_start, i - row_start, columns, col_data);+			i++;+		}++		if (rndr->cb.table)+			rndr->cb.table(ob, header_work, body_work, rndr->opaque);+	}++	free(col_data);+	rndr_popbuf(rndr, BUFFER_SPAN);+	rndr_popbuf(rndr, BUFFER_BLOCK);+	return i;+}++/* parse_block • parsing of one block, returning next char to parse */+static void+parse_block(struct buf *ob, struct render *rndr, char *data, size_t size)+{+	size_t beg, end, i;+	char *txt_data;+	beg = 0;++	if (rndr->work_bufs[BUFFER_SPAN].size ++		rndr->work_bufs[BUFFER_BLOCK].size > (int)rndr->max_nesting)+		return;++	while (beg < size) {+		txt_data = data + beg;+		end = size - beg;++		if (is_atxheader(rndr, txt_data, end))+			beg += parse_atxheader(ob, rndr, txt_data, end);++		else if (data[beg] == '<' && rndr->cb.blockhtml &&+				(i = parse_htmlblock(ob, rndr, txt_data, end, 1)) != 0)+			beg += i;++		else if ((i = is_empty(txt_data, end)) != 0)+			beg += i;++		else if (is_hrule(txt_data, end)) {+			if (rndr->cb.hrule)+				rndr->cb.hrule(ob, rndr->opaque);++			while (beg < size && data[beg] != '\n')+				beg++;++			beg++;+		}++		else if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 &&+			(i = parse_fencedcode(ob, rndr, txt_data, end)) != 0)+			beg += i;++		else if ((rndr->ext_flags & MKDEXT_TABLES) != 0 &&+			(i = parse_table(ob, rndr, txt_data, end)) != 0)+			beg += i;++		else if (prefix_quote(txt_data, end))+			beg += parse_blockquote(ob, rndr, txt_data, end);++		else if (prefix_code(txt_data, end))+			beg += parse_blockcode(ob, rndr, txt_data, end);++		else if (prefix_uli(txt_data, end))+			beg += parse_list(ob, rndr, txt_data, end, 0);++		else if (prefix_oli(txt_data, end))+			beg += parse_list(ob, rndr, txt_data, end, MKD_LIST_ORDERED);++		else+			beg += parse_paragraph(ob, rndr, txt_data, end);+	}+}++++/*********************+ * REFERENCE PARSING *+ *********************/++/* is_ref • returns whether a line is a reference or not */+static int+is_ref(char *data, size_t beg, size_t end, size_t *last, struct array *refs)+{+/*	int n; */+	size_t i = 0;+	size_t id_offset, id_end;+	size_t link_offset, link_end;+	size_t title_offset, title_end;+	size_t line_end;+	struct link_ref *lr;+/*	struct buf id = { 0, 0, 0, 0, 0 }; / * volatile buf for id search */++	/* up to 3 optional leading spaces */+	if (beg + 3 >= end) return 0;+	if (data[beg] == ' ') { i = 1;+	if (data[beg + 1] == ' ') { i = 2;+	if (data[beg + 2] == ' ') { i = 3;+	if (data[beg + 3] == ' ') return 0; } } }+	i += beg;++	/* id part: anything but a newline between brackets */+	if (data[i] != '[') return 0;+	i++;+	id_offset = i;+	while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']')+		i++;+	if (i >= end || data[i] != ']') return 0;+	id_end = i;++	/* spacer: colon (space | tab)* newline? (space | tab)* */+	i++;+	if (i >= end || data[i] != ':') return 0;+	i++;+	while (i < end && (data[i] == ' ' || data[i] == '\t')) i++;+	if (i < end && (data[i] == '\n' || data[i] == '\r')) {+		i++;+		if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; }+	while (i < end && (data[i] == ' ' || data[i] == '\t')) i++;+	if (i >= end) return 0;++	/* link: whitespace-free sequence, optionally between angle brackets */+	if (data[i] == '<') i++;+	link_offset = i;+	while (i < end && data[i] != ' ' && data[i] != '\t'+			&& data[i] != '\n' && data[i] != '\r') i++;+	if (data[i - 1] == '>') link_end = i - 1;+	else link_end = i;++	/* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */+	while (i < end && (data[i] == ' ' || data[i] == '\t')) i++;+	if (i < end && data[i] != '\n' && data[i] != '\r'+			&& data[i] != '\'' && data[i] != '"' && data[i] != '(')+		return 0;+	line_end = 0;+	/* computing end-of-line */+	if (i >= end || data[i] == '\r' || data[i] == '\n') line_end = i;+	if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')+		line_end = i + 1;++	/* optional (space|tab)* spacer after a newline */+	if (line_end) {+		i = line_end + 1;+		while (i < end && (data[i] == ' ' || data[i] == '\t')) i++; }++	/* optional title: any non-newline sequence enclosed in '"()+					alone on its line */+	title_offset = title_end = 0;+	if (i + 1 < end+	&& (data[i] == '\'' || data[i] == '"' || data[i] == '(')) {+		i++;+		title_offset = i;+		/* looking for EOL */+		while (i < end && data[i] != '\n' && data[i] != '\r') i++;+		if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')+			title_end = i + 1;+		else	title_end = i;+		/* stepping back */+		i -= 1;+		while (i > title_offset && (data[i] == ' ' || data[i] == '\t'))+			i -= 1;+		if (i > title_offset+		&& (data[i] == '\'' || data[i] == '"' || data[i] == ')')) {+			line_end = title_end;+			title_end = i; } }+	if (!line_end) return 0; /* garbage after the link */++	/* a valid ref has been found, filling-in return structures */+	if (last) *last = line_end;+	if (!refs) return 1;+	lr = arr_item(refs, arr_newitem(refs));+	lr->id = bufnew(id_end - id_offset);+	bufput(lr->id, data + id_offset, id_end - id_offset);+	lr->link = bufnew(link_end - link_offset);+	bufput(lr->link, data + link_offset, link_end - link_offset);+	if (title_end > title_offset) {+		lr->title = bufnew(title_end - title_offset);+		bufput(lr->title, data + title_offset,+					title_end - title_offset); }+	else lr->title = 0;+	return 1; +}++static void expand_tabs(struct buf *ob, const char *line, size_t size)+{+	size_t  i = 0, tab = 0;++	while (i < size) {+		size_t org = i;++		while (i < size && line[i] != '\t') {+			i++; tab++;+		}++		if (i > org)+			bufput(ob, line + org, i - org);++		if (i >= size)+			break;++		do {+			bufputc(ob, ' '); tab++;+		} while (tab % 4);++		i++;+	}+}++/**********************+ * EXPORTED FUNCTIONS *+ **********************/++/* markdown • parses the input buffer and renders it into the output buffer */+void+sd_markdown(struct buf *ob,+	const struct buf *ib,+	unsigned int extensions,+	const struct sd_callbacks *callbacks,+	void *opaque) {++	static const float MARKDOWN_GROW_FACTOR = 1.4f;++	struct link_ref *lr;+	struct buf *text;+	size_t i, beg, end;+	struct render rndr;++	/* filling the render structure */+	if (!callbacks)+		return;++	text = bufnew(64);+	if (!text)+		return;++	/* Preallocate enough space for our buffer to avoid expanding while copying */+	bufgrow(text, ib->size);++	memcpy(&rndr.cb, callbacks, sizeof(struct sd_callbacks));+	arr_init(&rndr.refs, sizeof (struct link_ref));+	parr_init(&rndr.work_bufs[BUFFER_BLOCK]);+	parr_init(&rndr.work_bufs[BUFFER_SPAN]);++/*	for (i = 0; i < 256; i++)+		rndr.active_char[i] = 0; */++	memset(rndr.active_char, 0x0, 256);++	if (rndr.cb.emphasis || rndr.cb.double_emphasis || rndr.cb.triple_emphasis) {+		rndr.active_char['*'] = MD_CHAR_EMPHASIS;+		rndr.active_char['_'] = MD_CHAR_EMPHASIS;+		if (extensions & MKDEXT_STRIKETHROUGH)+			rndr.active_char['~'] = MD_CHAR_EMPHASIS;+	}++	if (rndr.cb.codespan)+		rndr.active_char['`'] = MD_CHAR_CODESPAN;++	if (rndr.cb.linebreak)+		rndr.active_char['\n'] = MD_CHAR_LINEBREAK;++	if (rndr.cb.image || rndr.cb.link)+		rndr.active_char['['] = MD_CHAR_LINK;++	rndr.active_char['<'] = MD_CHAR_LANGLE;+	rndr.active_char['\\'] = MD_CHAR_ESCAPE;+	rndr.active_char['&'] = MD_CHAR_ENTITITY;++	if (extensions & MKDEXT_AUTOLINK) {+		rndr.active_char[':'] = MD_CHAR_AUTOLINK_URL;+		rndr.active_char['@'] = MD_CHAR_AUTOLINK_EMAIL;+		rndr.active_char['w'] = MD_CHAR_AUTOLINK_WWW;+	}++	if (extensions & MKDEXT_SUPERSCRIPT)+		rndr.active_char['^'] = MD_CHAR_SUPERSCRIPT;++	/* Extension data */+	rndr.ext_flags = extensions;+	rndr.opaque = opaque;+	rndr.max_nesting = 16;++	/* first pass: looking for references, copying everything else */+	beg = 0;+	while (beg < ib->size) /* iterating over lines */+		if (is_ref(ib->data, beg, ib->size, &end, &rndr.refs))+			beg = end;+		else { /* skipping to the next line */+			end = beg;+			while (end < ib->size && ib->data[end] != '\n' && ib->data[end] != '\r')+				end++;++			/* adding the line body if present */+			if (end > beg)+				expand_tabs(text, ib->data + beg, end - beg);++			while (end < ib->size && (ib->data[end] == '\n' || ib->data[end] == '\r')) {+				/* add one \n per newline */+				if (ib->data[end] == '\n' || (end + 1 < ib->size && ib->data[end + 1] != '\n'))+					bufputc(text, '\n');+				end++;+			}++			beg = end;+		}++	/* sorting the reference array */+	if (rndr.refs.size)+		qsort(rndr.refs.base, rndr.refs.size, rndr.refs.unit, cmp_link_ref_sort);++	/* pre-grow the output buffer to minimize allocations */+	bufgrow(ob, text->size * MARKDOWN_GROW_FACTOR);++	/* second pass: actual rendering */+	if (rndr.cb.doc_header)+		rndr.cb.doc_header(ob, rndr.opaque);++	if (text->size) {+		/* adding a final newline if not already present */+		if (text->data[text->size - 1] != '\n' &&  text->data[text->size - 1] != '\r')+			bufputc(text, '\n');++		parse_block(ob, &rndr, text->data, text->size);+	}++	if (rndr.cb.doc_footer)+		rndr.cb.doc_footer(ob, rndr.opaque);++	/* clean-up */+	bufrelease(text);+	lr = rndr.refs.base;+	for (i = 0; i < (size_t)rndr.refs.size; i++) {+		bufrelease(lr[i].id);+		bufrelease(lr[i].link);+		bufrelease(lr[i].title);+	}++	arr_free(&rndr.refs);++	assert(rndr.work_bufs[BUFFER_SPAN].size == 0);+	assert(rndr.work_bufs[BUFFER_BLOCK].size == 0);++	for (i = 0; i < (size_t)rndr.work_bufs[BUFFER_SPAN].asize; ++i)+		bufrelease(rndr.work_bufs[BUFFER_SPAN].item[i]);++	for (i = 0; i < (size_t)rndr.work_bufs[BUFFER_BLOCK].asize; ++i)+		bufrelease(rndr.work_bufs[BUFFER_BLOCK].item[i]);++	parr_free(&rndr.work_bufs[BUFFER_SPAN]);+	parr_free(&rndr.work_bufs[BUFFER_BLOCK]);+}++void+sd_version(int *ver_major, int *ver_minor, int *ver_revision)+{+	*ver_major = UPSKIRT_VER_MAJOR;+	*ver_minor = UPSKIRT_VER_MINOR;+	*ver_revision = UPSKIRT_VER_REVISION;+}++/* vim: set filetype=c: */
+ cbits/markdown.h view
@@ -0,0 +1,120 @@+/* markdown.h - generic markdown parser */++/*+ * Copyright (c) 2009, Natacha Porté+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ */++#ifndef UPSKIRT_MARKDOWN_H+#define UPSKIRT_MARKDOWN_H++#include "buffer.h"+#include "autolink.h"++#define UPSKIRT_VERSION "1.15.2"+#define UPSKIRT_VER_MAJOR 1+#define UPSKIRT_VER_MINOR 15+#define UPSKIRT_VER_REVISION 2++/********************+ * TYPE DEFINITIONS *+ ********************/++/* mkd_autolink - type of autolink */+enum mkd_autolink {+	MKDA_NOT_AUTOLINK,	/* used internally when it is not an autolink*/+	MKDA_NORMAL,		/* normal http/http/ftp/mailto/etc link */+	MKDA_EMAIL,			/* e-mail link without explit mailto: */+};++enum mkd_tableflags {+	MKD_TABLE_ALIGN_L = 1,+	MKD_TABLE_ALIGN_R = 2,+	MKD_TABLE_ALIGN_CENTER = 3,+	MKD_TABLE_ALIGNMASK = 3,+	MKD_TABLE_HEADER = 4+};++enum mkd_extensions {+	MKDEXT_NO_INTRA_EMPHASIS = (1 << 0),+	MKDEXT_TABLES = (1 << 1),+	MKDEXT_FENCED_CODE = (1 << 2),+	MKDEXT_AUTOLINK = (1 << 3),+	MKDEXT_STRIKETHROUGH = (1 << 4),+	MKDEXT_LAX_HTML_BLOCKS = (1 << 5),+	MKDEXT_SPACE_HEADERS = (1 << 6),+	MKDEXT_SUPERSCRIPT = (1 << 7),+};++/* sd_callbacks - functions for rendering parsed data */+struct sd_callbacks {+	/* block level callbacks - NULL skips the block */+	void (*blockcode)(struct buf *ob, struct buf *text, struct buf *lang, void *opaque);+	void (*blockquote)(struct buf *ob, struct buf *text, void *opaque);+	void (*blockhtml)(struct buf *ob, struct buf *text, void *opaque);+	void (*header)(struct buf *ob, struct buf *text, int level, void *opaque);+	void (*hrule)(struct buf *ob, void *opaque);+	void (*list)(struct buf *ob, struct buf *text, int flags, void *opaque);+	void (*listitem)(struct buf *ob, struct buf *text, int flags, void *opaque);+	void (*paragraph)(struct buf *ob, struct buf *text, void *opaque);+	void (*table)(struct buf *ob, struct buf *header, struct buf *body, void *opaque);+	void (*table_row)(struct buf *ob, struct buf *text, void *opaque);+	void (*table_cell)(struct buf *ob, struct buf *text, int flags, void *opaque);+++	/* span level callbacks - NULL or return 0 prints the span verbatim */+	int (*autolink)(struct buf *ob, struct buf *link, enum mkd_autolink type, void *opaque);+	int (*codespan)(struct buf *ob, struct buf *text, void *opaque);+	int (*double_emphasis)(struct buf *ob, struct buf *text, void *opaque);+	int (*emphasis)(struct buf *ob, struct buf *text, void *opaque);+	int (*image)(struct buf *ob, struct buf *link, struct buf *title, struct buf *alt, void *opaque);+	int (*linebreak)(struct buf *ob, void *opaque);+	int (*link)(struct buf *ob, struct buf *link, struct buf *title, struct buf *content, void *opaque);+	int (*raw_html_tag)(struct buf *ob, struct buf *tag, void *opaque);+	int (*triple_emphasis)(struct buf *ob, struct buf *text, void *opaque);+	int (*strikethrough)(struct buf *ob, struct buf *text, void *opaque);+	int (*superscript)(struct buf *ob, struct buf *text, void *opaque);++	/* low level callbacks - NULL copies input directly into the output */+	void (*entity)(struct buf *ob, struct buf *entity, void *opaque);+	void (*normal_text)(struct buf *ob, struct buf *text, void *opaque);++	/* header and footer */+	void (*doc_header)(struct buf *ob, void *opaque);+	void (*doc_footer)(struct buf *ob, void *opaque);+};++/*********+ * FLAGS *+ *********/++/* list/listitem flags */+#define MKD_LIST_ORDERED	1+#define MKD_LI_BLOCK		2  /* <li> containing block data */++/**********************+ * EXPORTED FUNCTIONS *+ **********************/++/* sd_markdown * parses the input buffer and renders it into the output buffer */+extern void+sd_markdown(struct buf *ob, const struct buf *ib, unsigned int extensions, const struct sd_callbacks *rndr, void *opaque);++/* sd_version * returns the library version as major.minor.rev */+extern void+sd_version(int *major, int *minor, int *revision);++#endif++/* vim: set filetype=c: */
+ src/Text/Sundown.hs view
@@ -0,0 +1,29 @@+{-|++Bindings to the github fork of the sundown library - previously known as upskirt:+<https://github.com/tanoku/sundown>++Example usage:++> import Text.Sundown+> import Text.Sundown.Renderers.Html+> import qualified Data.ByteString as BS+> import qualified Data.ByteString.UTF8 as UTF8+> import System (getArgs)+> import Control.Monad (liftM)+>+> main :: IO ()+> main = do+>   input <- liftM (!! 0) getArgs >>= BS.readFile+>   putStrLn $ UTF8.toString $ renderHtml input allExtensions noHtmlModes++-}++module Text.Sundown+       ( module Text.Sundown.Markdown+       ) where++import Text.Sundown.Markdown+++
+ src/Text/Sundown/Buffer/Foreign.hsc view
@@ -0,0 +1,53 @@+{-# Language ForeignFunctionInterface #-}++module Text.Sundown.Buffer.Foreign+       ( Buffer (..)+       , c_bufnew+       , c_bufputs+       , c_bufgrow+       , c_bufrelease+       ) where++import Foreign+import Foreign.C.Types+import Foreign.C.String+import Data.ByteString         (ByteString)+import qualified Data.ByteString as BS++#include "buffer.h"++data Buffer = Buffer { bufData  :: ByteString+                     , bufSize  :: Int+                     , bufASize :: Int+                     , bufUnit  :: Int+                     , bufRef   :: Int+                     }++instance Storable Buffer where+  sizeOf _ = (#size struct buf)+  alignment _ = alignment (undefined :: CInt)+  peek ptr = do+    d  <- (#peek struct buf, data) ptr+    dbs <- if d == nullPtr+           then return $ BS.pack [0]+           else BS.packCString d+    s  <- (#peek struct buf, size) ptr+    as <- (#peek struct buf, asize) ptr+    u  <- (#peek struct buf, unit) ptr+    r  <- (#peek struct buf, ref) ptr+    return $ Buffer dbs s as u r+  poke _ _ = error "Buffer.poke not implemented."++foreign import ccall "buffer.h bufnew"+  c_bufnew :: CSize -> IO (Ptr Buffer)++c_bufputs :: Ptr Buffer -> ByteString -> IO ()+c_bufputs buf bs = BS.useAsCString bs $ c_bufputs' buf+foreign import ccall "buffer.h bufputs"+  c_bufputs' :: Ptr Buffer -> CString -> IO ()++foreign import ccall "buffer.h bufgrow"+  c_bufgrow :: Ptr Buffer -> CSize -> IO CInt++foreign import ccall "buffer.h bufrelease"+  c_bufrelease :: Ptr Buffer -> IO ()
+ src/Text/Sundown/Flag.hs view
@@ -0,0 +1,18 @@+module Text.Sundown.Flag+       ( Flag (..)+       , toCUInt+       ) where+++import Foreign.C.Types+import Data.Bits++class Flag a where+  flagIndexes :: a -> [(Int, Bool)]++toCUInt :: Flag a => a -> CUInt+toCUInt flag = foldl (\uint (ix, b) -> shiftL (fromIntegral . fromEnum $ b) ix .|. uint)+                     0 (flagIndexes flag)+++
+ src/Text/Sundown/Markdown.hs view
@@ -0,0 +1,15 @@+module Text.Sundown.Markdown+       ( Extensions (..)+       , allExtensions+       , noExtensions+       ) where++import Text.Sundown.Markdown.Foreign++-- | All 'Extensions' disabled+noExtensions :: Extensions+noExtensions = Extensions False False False False False False ++-- | All 'Extensions' enabled+allExtensions :: Extensions+allExtensions = Extensions True True True True True True
+ src/Text/Sundown/Markdown/Foreign.hsc view
@@ -0,0 +1,48 @@+{-# Language ForeignFunctionInterface #-}++module Text.Sundown.Markdown.Foreign+       ( Callbacks+       , Extensions (..)+       , c_sd_markdown+       ) where++import Foreign+import Foreign.C.Types++import Text.Sundown.Buffer.Foreign+import Text.Sundown.Flag++#include "markdown.h"++data Callbacks++-- | A set of switches to enable or disable markdown features.+data Extensions = Extensions { extNoIntraEmphasis :: Bool+                             , extTables          :: Bool+                             , extFencedCode      :: Bool+                             , extAutolink        :: Bool+                             , extStrikethrough   :: Bool+                             , extLaxHtmlBlocks   :: Bool+                             }++instance Flag Extensions where+  flagIndexes exts = [ (0, extNoIntraEmphasis exts)+                     , (1, extTables exts)+                     , (2, extFencedCode exts)+                     , (3, extAutolink exts)+                     , (4, extStrikethrough exts)+                     , (5, extLaxHtmlBlocks exts)+                     ]++++instance Storable Callbacks where+    sizeOf _ = (#size struct sd_callbacks)+    alignment _ = alignment (undefined :: Ptr ())+    peek _ = error "Callbacks.peek is not implemented"+    poke _ _ = error "Callbacks.poke is not implemented"++c_sd_markdown :: Ptr Buffer -> Ptr Buffer -> Extensions -> Ptr Callbacks -> Ptr () -> IO ()+c_sd_markdown ob ib exts rndr opaque = c_sd_markdown' ob ib (toCUInt exts) rndr opaque+foreign import ccall "markdown.h sd_markdown"+  c_sd_markdown' :: Ptr Buffer -> Ptr Buffer -> CUInt -> Ptr Callbacks -> Ptr () -> IO ()
+ src/Text/Sundown/Renderers/Html.hs view
@@ -0,0 +1,72 @@+{-# Language ForeignFunctionInterface #-}++module Text.Sundown.Renderers.Html+       ( renderHtml+       , noHtmlModes+       , allHtmlModes+       , smartypants+       , HtmlRenderMode (..)+       ) where+++import Foreign++import Data.ByteString (ByteString)+import qualified Data.ByteString as BS++import Text.Sundown.Markdown.Foreign+import Text.Sundown.Buffer.Foreign+import Text.Sundown.Renderers.Html.Foreign++-- | Parses a 'ByteString' containing the markdown, returns the Html+-- code.+renderHtml :: ByteString -> Extensions -> HtmlRenderMode -> ByteString+renderHtml input exts mode =+  unsafePerformIO $+  alloca $ \renderer ->+  alloca $ \options -> do+    -- Allocate buffers+    ob <- c_bufnew 64+    ib <- c_bufnew . fromIntegral . BS.length $ input+    +    -- Put the input content into the buffer+    c_bufputs ib input+    +    -- Do the markdown+    c_sdhtml_renderer renderer options mode+    c_sd_markdown ob ib exts renderer (castPtr options)+    +    -- Get the result+    Buffer {bufData = output} <- peek ob+    +    c_bufrelease ib+    c_bufrelease ob    +    +    return output++-- | All the 'HtmlRenderMode' disabled+noHtmlModes :: HtmlRenderMode+noHtmlModes = HtmlRenderMode False False False False False False False False False False++-- | All the 'HtmlRenderMode' enabled+allHtmlModes :: HtmlRenderMode+allHtmlModes = HtmlRenderMode True True True True True True True True True True++-- | Converts punctuation in Html entities,+-- <http://daringfireball.net/projects/smartypants/>+smartypants :: ByteString -> ByteString+smartypants input =+  unsafePerformIO $ do+    ob <- c_bufnew 64+    ib <- c_bufnew $ fromInteger . toInteger $ BS.length input+    +    c_bufputs ib input+    +    c_sdhtml_smartypants ob ib+    +    Buffer {bufData = output} <- peek ob+    +    c_bufrelease ib+    c_bufrelease ob+    +    return output
+ src/Text/Sundown/Renderers/Html/Foreign.hsc view
@@ -0,0 +1,63 @@+{-# Language ForeignFunctionInterface #-}++module Text.Sundown.Renderers.Html.Foreign+       ( HtmlRenderMode (..)+       , c_sdhtml_renderer+       , c_sdhtml_toc_renderer+       , c_sdhtml_smartypants+       ) where+++import Foreign+import Foreign.C.Types++import Text.Sundown.Buffer.Foreign+import Text.Sundown.Markdown.Foreign+import Text.Sundown.Flag++#include "html.h"++data HtmlRenderMode = HtmlRenderMode { htmlSkipHtml :: Bool+                                     , htmlSkipStyle :: Bool+                                     , htmlSkipImages :: Bool+                                     , htmlSkipLinks :: Bool+                                     , htmlExpandTabs :: Bool+                                     , htmlSafelink :: Bool+                                     , htmlToc :: Bool+                                     , htmlHardWrap :: Bool+                                     , htmlGithubBlockcode :: Bool+                                     , htmlUseXhtml :: Bool+                                     }+++instance Flag HtmlRenderMode where+  flagIndexes mode = [ (0,  htmlSkipHtml mode)+                     , (1,  htmlSkipStyle mode)+                     , (2,  htmlSkipImages mode)+                     , (3,  htmlSkipLinks mode)+                     , (5,  htmlExpandTabs mode)+                     , (7,  htmlSafelink mode)+                     , (8,  htmlToc mode)+                     , (9,  htmlHardWrap mode)+                     , (10, htmlGithubBlockcode mode)+                     , (11, htmlUseXhtml mode)+                     ]++data HtmlRenderOptions++instance Storable HtmlRenderOptions where+  sizeOf _ = (#size struct html_renderopt)+  alignment _ = alignment (undefined :: Ptr ())+  peek _ = error "HtmlRenderopt.peek is not implemented"+  poke _ = error "HtmlRenderopt.poke is not implemented"++c_sdhtml_renderer :: Ptr Callbacks -> Ptr HtmlRenderOptions -> HtmlRenderMode -> IO ()+c_sdhtml_renderer rndr options mode = c_sdhtml_renderer' rndr options (toCUInt mode)+foreign import ccall "html.h sdhtml_renderer"+  c_sdhtml_renderer' :: Ptr Callbacks -> Ptr HtmlRenderOptions -> CUInt -> IO ()++foreign import ccall "html.h sdhtml_toc_renderer"+  c_sdhtml_toc_renderer :: Ptr Callbacks -> Ptr HtmlRenderOptions -> IO ()++foreign import ccall "html.h sdhtml_smartypants"+  c_sdhtml_smartypants :: Ptr Buffer -> Ptr Buffer -> IO ()
+ sundown.cabal view
@@ -0,0 +1,47 @@+Cabal-version:          >= 1.6+Name:                   sundown+Version:                0.1+Author:                 Francesco Mazzoli (f@mazzo.li)+Maintainer:             Francesco Mazzoli (f@mazzo.li)+Build-Type:             Simple+License:                PublicDomain+Build-Type:             Simple+Category:               Foreign binding, Text+Synopsis:               Binding to upskirt+Tested-With:            GHC==7.0.2+Description:+  Bindings to github's sundown, a nice C markdown library:+  <https://github.com/tanoku/sundown>.++source-repository head+  type:     git+  location: git://github.com/rostayob/sundown.git++Library+  Hs-Source-Dirs:       src++  Build-Depends:        base >= 3 && < 5+                      , bytestring++  GHC-Options:          -Wall -O2+  +  Extensions:           ForeignFunctionInterface, EmptyDataDecls+  +  Exposed-Modules:      Text.Sundown+                      , Text.Sundown.Markdown+                      , Text.Sundown.Renderers.Html++  Other-Modules:        Text.Sundown.Buffer.Foreign+                      , Text.Sundown.Renderers.Html.Foreign+                      , Text.Sundown.Flag+                      , Text.Sundown.Markdown.Foreign+  +  C-Sources:            cbits/array.c, cbits/buffer.c, cbits/markdown.c,+                        cbits/html.c, cbits/html_smartypants.c,+                        cbits/autolink.c+  +  Include-Dirs:         cbits+  +  Includes:             array.h, autolink.h, buffer.h, markdown.h, html.h+  +  Install-includes:     array.h, autolink.h, buffer.h, markdown.h, html.h