sundown (empty) → 0.1
raw patch · 20 files changed
+5144/−0 lines, 20 filesdep +basedep +bytestringsetup-changed
Dependencies added: base, bytestring
Files
- Setup.hs +2/−0
- cbits/array.c +300/−0
- cbits/array.h +147/−0
- cbits/autolink.c +261/−0
- cbits/autolink.h +36/−0
- cbits/buffer.c +323/−0
- cbits/buffer.h +154/−0
- cbits/html.c +674/−0
- cbits/html.h +71/−0
- cbits/html_smartypants.c +383/−0
- cbits/markdown.c +2328/−0
- cbits/markdown.h +120/−0
- src/Text/Sundown.hs +29/−0
- src/Text/Sundown/Buffer/Foreign.hsc +53/−0
- src/Text/Sundown/Flag.hs +18/−0
- src/Text/Sundown/Markdown.hs +15/−0
- src/Text/Sundown/Markdown/Foreign.hsc +48/−0
- src/Text/Sundown/Renderers/Html.hs +72/−0
- src/Text/Sundown/Renderers/Html/Foreign.hsc +63/−0
- sundown.cabal +47/−0
+ 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, "<"); break;+ case '>': BUFPUTSL(ob, ">"); break;+ case '&': BUFPUTSL(ob, "&"); break;+ case '"': BUFPUTSL(ob, """); 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, "’");+ 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, "’");+ 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, "©");+ return 2;+ }++ if (t1 == 'r' && t2 == ')') {+ BUFPUTSL(ob, "®");+ return 2;+ }++ if (size >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')') {+ BUFPUTSL(ob, "™");+ 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, "—");+ return 2;+ }++ if (size >= 2 && text[1] == '-') {+ BUFPUTSL(ob, "–");+ 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, """, 6) == 0) {+ if (smartypants_quotes(ob, previous_char, size >= 7 ? text[6] : 0, 'd', &smrt->in_dquote))+ return 5;+ }++ if (size >= 4 && memcmp(text, "�", 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, "…");+ return 2;+ }++ if (size >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.') {+ BUFPUTSL(ob, "…");+ 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, "½");+ 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, "¼");+ 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, "¾");+ 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, """);++ 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>", "’", 0 },+ { '\'', "'t>", "’", 0 },+ { '\'', "'re>", "’", 0 },+ { '\'', "'ll>", "’", 0 },+ { '\'', "'ve>", "’", 0 },+ { '\'', "'m>", "’", 0 },+ { '\'', "'d>", "’", 0 },+ { '-', "--", "—", 1 },+ { '-', "<->", "–", 0 },+ { '.', "...", "…", 2 },+ { '.', ". . .", "…", 4 },+ { '(', "(c)", "©", 2 },+ { '(', "(r)", "®", 2 },+ { '(', "(tm)", "™", 3 },+ { '3', "<3/4>", "¾", 2 },+ { '3', "<3/4ths>", "¾", 2 },+ { '1', "<1/2>", "½", 2 },+ { '1', "<1/4>", "¼", 2 },+ { '1', "<1/4th>", "¼", 2 },+ { '&', "�", 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