Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Phalcon 2.0.0 patch: Phalcon\Mvc\View\Engine\Markdown
diff --git a/config.json b/config.json
index 8183eec..b5cf118 100644
--- a/config.json
+++ b/config.json
@@ -28,6 +28,16 @@
"phalcon/mvc/model/query/parser.c",
"phalcon/mvc/view/engine/volt/parser.c",
"phalcon/mvc/view/engine/volt/scanner.c",
+ "phalcon/mvc/view/engine/markdown/parser.c",
+ "phalcon/mvc/view/engine/markdown/autolink.c",
+ "phalcon/mvc/view/engine/markdown/escape.c",
+ "phalcon/mvc/view/engine/markdown/html_smartypants.c",
+ "phalcon/mvc/view/engine/markdown/buffer.c",
+ "phalcon/mvc/view/engine/markdown/html.c",
+ "phalcon/mvc/view/engine/markdown/stack.c",
+ "phalcon/mvc/view/engine/markdown/document.c",
+ "phalcon/mvc/view/engine/markdown/html_blocks.c",
+ "phalcon/mvc/view/engine/markdown/hash.c",
"phalcon/assets/filters/jsminifier.c",
"phalcon/assets/filters/cssminifier.c",
"phalcon/mvc/url/utils.c"
diff --git a/ext/phalcon/mvc/view/engine/markdown/autolink.c b/ext/phalcon/mvc/view/engine/markdown/autolink.c
new file mode 100644
index 0000000..7142094
--- /dev/null
+++ b/ext/phalcon/mvc/view/engine/markdown/autolink.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2008, Natacha Porté
+ * Copyright (c) 2011, Vicent Martí
+ * Copyright (c) 2013, Devin Torres and the Hoedown authors
+ *
+ * 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 "autolink.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#ifndef _MSC_VER
+#include <strings.h>
+#else
+#define strncasecmp _strnicmp
+#endif
+
+int
+hoedown_autolink_is_safe(const uint8_t *data, size_t size)
+{
+ static const size_t valid_uris_count = 6;
+ static const char *valid_uris[] = {
+ "http://", "https://", "/", "#", "ftp://", "mailto:"
+ };
+ static const size_t valid_uris_size[] = { 7, 8, 1, 1, 6, 7 };
+ size_t i;
+
+ for (i = 0; i < valid_uris_count; ++i) {
+ size_t len = valid_uris_size[i];
+
+ if (size > len &&
+ strncasecmp((char *)data, valid_uris[i], len) == 0 &&
+ isalnum(data[len]))
+ return 1;
+ }
+
+ return 0;
+}
+
+static size_t
+autolink_delim(uint8_t *data, size_t link_end, size_t max_rewind, size_t size)
+{
+ uint8_t 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(uint8_t *data, size_t size, int allow_short)
+{
+ size_t i, np = 0;
+
+ if (!isalnum(data[0]))
+ return 0;
+
+ for (i = 1; i < size - 1; ++i) {
+ if (strchr(".:", data[i]) != NULL) np++;
+ else if (!isalnum(data[i]) && data[i] != '-') break;
+ }
+
+ if (allow_short) {
+ /* We don't need a valid domain in the strict sense (with
+ * least one dot; so just make sure it's composed of valid
+ * domain characters and return the length of the the valid
+ * sequence. */
+ return i;
+ } else {
+ /* a valid domain needs to have at least a dot.
+ * that's as far as we get */
+ return np ? i : 0;
+ }
+}
+
+size_t
+hoedown_autolink__www(
+ size_t *rewind_p,
+ hoedown_buffer *link,
+ uint8_t *data,
+ size_t max_rewind,
+ size_t size,
+ unsigned int flags)
+{
+ size_t link_end;
+
+ if (max_rewind > 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, 0);
+
+ if (link_end == 0)
+ return 0;
+
+ while (link_end < size && !isspace(data[link_end]))
+ link_end++;
+
+ link_end = autolink_delim(data, link_end, max_rewind, size);
+
+ if (link_end == 0)
+ return 0;
+
+ hoedown_buffer_put(link, data, link_end);
+ *rewind_p = 0;
+
+ return (int)link_end;
+}
+
+size_t
+hoedown_autolink__email(
+ size_t *rewind_p,
+ hoedown_buffer *link,
+ uint8_t *data,
+ size_t max_rewind,
+ size_t size,
+ unsigned int flags)
+{
+ size_t link_end, rewind;
+ int nb = 0, np = 0;
+
+ for (rewind = 0; rewind < max_rewind; ++rewind) {
+ uint8_t c = data[-1 - rewind];
+
+ if (isalnum(c))
+ continue;
+
+ if (strchr(".+-_", c) != NULL)
+ continue;
+
+ break;
+ }
+
+ if (rewind == 0)
+ return 0;
+
+ for (link_end = 0; link_end < size; ++link_end) {
+ uint8_t 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 ||
+ !isalpha(data[link_end - 1]))
+ return 0;
+
+ link_end = autolink_delim(data, link_end, max_rewind, size);
+
+ if (link_end == 0)
+ return 0;
+
+ hoedown_buffer_put(link, data - rewind, link_end + rewind);
+ *rewind_p = rewind;
+
+ return link_end;
+}
+
+size_t
+hoedown_autolink__url(
+ size_t *rewind_p,
+ hoedown_buffer *link,
+ uint8_t *data,
+ size_t max_rewind,
+ size_t size,
+ unsigned int flags)
+{
+ size_t link_end, rewind = 0, domain_len;
+
+ if (size < 4 || data[1] != '/' || data[2] != '/')
+ return 0;
+
+ while (rewind < max_rewind && isalpha(data[-1 - rewind]))
+ rewind++;
+
+ if (!hoedown_autolink_is_safe(data - rewind, size + rewind))
+ return 0;
+
+ link_end = strlen("://");
+
+ domain_len = check_domain(
+ data + link_end,
+ size - link_end,
+ flags & HOEDOWN_AUTOLINK_SHORT_DOMAINS);
+
+ 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, max_rewind, size);
+
+ if (link_end == 0)
+ return 0;
+
+ hoedown_buffer_put(link, data - rewind, link_end + rewind);
+ *rewind_p = rewind;
+
+ return link_end;
+}
diff --git a/ext/phalcon/mvc/view/engine/markdown/autolink.h b/ext/phalcon/mvc/view/engine/markdown/autolink.h
new file mode 100644
index 0000000..55cb49b
--- /dev/null
+++ b/ext/phalcon/mvc/view/engine/markdown/autolink.h
@@ -0,0 +1,64 @@
+/* autolink.h - versatile autolinker */
+
+/*
+ * Copyright (c) 2008, Natacha Porté
+ * Copyright (c) 2011, Vicent Martí
+ * Copyright (c) 2013, Devin Torres and the Hoedown authors
+ *
+ * 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 HOEDOWN_AUTOLINK_H
+#define HOEDOWN_AUTOLINK_H
+
+#include "buffer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/*************
+ * CONSTANTS *
+ *************/
+
+typedef enum hoedown_autolink_flags {
+ HOEDOWN_AUTOLINK_SHORT_DOMAINS = (1 << 0)
+} hoedown_autolink_flags;
+
+
+/*************
+ * FUNCTIONS *
+ *************/
+
+/* hoedown_autolink_is_safe: verify that a URL has a safe protocol */
+int hoedown_autolink_is_safe(const uint8_t *data, size_t size);
+
+/* hoedown_autolink__www: search for the next www link in data */
+size_t hoedown_autolink__www(size_t *rewind_p, hoedown_buffer *link,
+ uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
+
+/* hoedown_autolink__email: search for the next email in data */
+size_t hoedown_autolink__email(size_t *rewind_p, hoedown_buffer *link,
+ uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
+
+/* hoedown_autolink__url: search for the next URL in data */
+size_t hoedown_autolink__url(size_t *rewind_p, hoedown_buffer *link,
+ uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /** HOEDOWN_AUTOLINK_H **/
diff --git a/ext/phalcon/mvc/view/engine/markdown/buffer.c b/ext/phalcon/mvc/view/engine/markdown/buffer.c
new file mode 100644
index 0000000..ca1e516
--- /dev/null
+++ b/ext/phalcon/mvc/view/engine/markdown/buffer.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2008, Natacha Porté
+ * Copyright (c) 2011, Vicent Martí
+ * Copyright (c) 2013, Devin Torres and the Hoedown authors
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+void *
+hoedown_malloc(size_t size)
+{
+ void *ret = malloc(size);
+
+ if (!ret) {
+ fprintf(stderr, "Allocation failed.\n");
+ abort();
+ }
+
+ return ret;
+}
+
+void *
+hoedown_calloc(size_t nmemb, size_t size)
+{
+ void *ret = calloc(nmemb, size);
+
+ if (!ret) {
+ fprintf(stderr, "Allocation failed.\n");
+ abort();
+ }
+
+ return ret;
+}
+
+void *
+hoedown_realloc(void *ptr, size_t size)
+{
+ void *ret = realloc(ptr, size);
+
+ if (!ret) {
+ fprintf(stderr, "Allocation failed.\n");
+ abort();
+ }
+
+ return ret;
+}
+
+void
+hoedown_buffer_init(
+ hoedown_buffer *buf,
+ size_t unit,
+ hoedown_realloc_callback data_realloc,
+ hoedown_free_callback data_free,
+ hoedown_free_callback buffer_free)
+{
+ assert(buf);
+
+ buf->data = NULL;
+ buf->size = buf->asize = 0;
+ buf->unit = unit;
+ buf->data_realloc = data_realloc;
+ buf->data_free = data_free;
+ buf->buffer_free = buffer_free;
+}
+
+hoedown_buffer *
+hoedown_buffer_new(size_t unit)
+{
+ hoedown_buffer *ret = hoedown_malloc(sizeof (hoedown_buffer));
+ hoedown_buffer_init(ret, unit, hoedown_realloc, free, free);
+ return ret;
+}
+
+void
+hoedown_buffer_free(hoedown_buffer *buf)
+{
+ if (!buf) return;
+
+ buf->data_free(buf->data);
+
+ if (buf->buffer_free)
+ buf->buffer_free(buf);
+}
+
+void
+hoedown_buffer_reset(hoedown_buffer *buf)
+{
+ assert(buf && buf->unit);
+
+ buf->data_free(buf->data);
+ buf->data = NULL;
+ buf->size = buf->asize = 0;
+}
+
+void
+hoedown_buffer_grow(hoedown_buffer *buf, size_t neosz)
+{
+ size_t neoasz;
+ assert(buf && buf->unit);
+
+ if (buf->asize >= neosz)
+ return;
+
+ neoasz = buf->asize + buf->unit;
+ while (neoasz < neosz)
+ neoasz += buf->unit;
+
+ buf->data = buf->data_realloc(buf->data, neoasz);
+ buf->asize = neoasz;
+}
+
+void
+hoedown_buffer_put(hoedown_buffer *buf, const uint8_t *data, size_t size)
+{
+ assert(buf && buf->unit);
+
+ if (buf->size + size > buf->asize)
+ hoedown_buffer_grow(buf, buf->size + size);
+
+ memcpy(buf->data + buf->size, data, size);
+ buf->size += size;
+}
+
+void
+hoedown_buffer_puts(hoedown_buffer *buf, const char *str)
+{
+ hoedown_buffer_put(buf, (const uint8_t *)str, strlen(str));
+}
+
+void
+hoedown_buffer_putc(hoedown_buffer *buf, uint8_t c)
+{
+ assert(buf && buf->unit);
+
+ if (buf->size >= buf->asize)
+ hoedown_buffer_grow(buf, buf->size + 1);
+
+ buf->data[buf->size] = c;
+ buf->size += 1;
+}
+
+void
+hoedown_buffer_set(hoedown_buffer *buf, const uint8_t *data, size_t size)
+{
+ assert(buf && buf->unit);
+
+ if (size > buf->asize)
+ hoedown_buffer_grow(buf, size);
+
+ memcpy(buf->data, data, size);
+ buf->size = size;
+}
+
+void
+hoedown_buffer_sets(hoedown_buffer *buf, const char *str)
+{
+ hoedown_buffer_set(buf, (const uint8_t *)str, strlen(str));
+}
+
+int
+hoedown_buffer_eq(const hoedown_buffer *buf, const uint8_t *data, size_t size)
+{
+ if (buf->size != size) return 0;
+ return memcmp(buf->data, data, size) == 0;
+}
+
+int
+hoedown_buffer_eqs(const hoedown_buffer *buf, const char *str)
+{
+ return hoedown_buffer_eq(buf, (const uint8_t *)str, strlen(str));
+}
+
+int
+hoedown_buffer_prefix(const hoedown_buffer *buf, const char *prefix)
+{
+ size_t i;
+
+ assert(buf && buf->unit);
+
+ 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;
+}
+
+void
+hoedown_buffer_slurp(hoedown_buffer *buf, size_t size)
+{
+ assert(buf && buf->unit);
+
+ if (size >= buf->size) {
+ buf->size = 0;
+ return;
+ }
+
+ buf->size -= size;
+ memmove(buf->data, buf->data + size, buf->size);
+}
+
+const char *
+hoedown_buffer_cstr(hoedown_buffer *buf)
+{
+ assert(buf && buf->unit);
+
+ if (buf->size < buf->asize && buf->data[buf->size] == 0)
+ return (char *)buf->data;
+
+ hoedown_buffer_grow(buf, buf->size + 1);
+ buf->data[buf->size] = 0;
+
+ return (char *)buf->data;
+}
+
+void
+hoedown_buffer_printf(hoedown_buffer *buf, const char *fmt, ...)
+{
+ va_list ap;
+ int n;
+
+ assert(buf && buf->unit);
+
+ if (buf->size >= buf->asize)
+ hoedown_buffer_grow(buf, buf->size + 1);
+
+ va_start(ap, fmt);
+ n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
+ va_end(ap);
+
+ if (n < 0) {
+#ifndef _MSC_VER
+ return;
+#else
+ va_start(ap, fmt);
+ n = _vscprintf(fmt, ap);
+ va_end(ap);
+#endif
+ }
+
+ if ((size_t)n >= buf->asize - buf->size) {
+ hoedown_buffer_grow(buf, buf->size + n + 1);
+
+ va_start(ap, fmt);
+ n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
+ va_end(ap);
+ }
+
+ if (n < 0)
+ return;
+
+ buf->size += n;
+}
diff --git a/ext/phalcon/mvc/view/engine/markdown/buffer.h b/ext/phalcon/mvc/view/engine/markdown/buffer.h
new file mode 100644
index 0000000..162ac28
--- /dev/null
+++ b/ext/phalcon/mvc/view/engine/markdown/buffer.h
@@ -0,0 +1,143 @@
+/* buffer.h - simple, fast buffers */
+
+/*
+ * Copyright (c) 2008, Natacha Porté
+ * Copyright (c) 2011, Vicent Martí
+ * Copyright (c) 2013, Devin Torres and the Hoedown authors
+ *
+ * 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 HOEDOWN_BUFFER_H
+#define HOEDOWN_BUFFER_H
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(_MSC_VER)
+#define __attribute__(x)
+#define inline __inline
+#define __builtin_expect(x,n) x
+#endif
+
+
+/*********
+ * TYPES *
+ *********/
+
+typedef void *(*hoedown_realloc_callback)(void *, size_t);
+typedef void (*hoedown_free_callback)(void *);
+
+struct hoedown_buffer {
+ uint8_t *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) */
+
+ hoedown_realloc_callback data_realloc;
+ hoedown_free_callback data_free;
+ hoedown_free_callback buffer_free;
+};
+
+typedef struct hoedown_buffer hoedown_buffer;
+
+
+/*************
+ * FUNCTIONS *
+ *************/
+
+/* allocation wrappers */
+void *hoedown_malloc(size_t size) __attribute__ ((malloc));
+void *hoedown_calloc(size_t nmemb, size_t size) __attribute__ ((malloc));
+void *hoedown_realloc(void *ptr, size_t size) __attribute__ ((malloc));
+
+/* hoedown_buffer_init: initialize a buffer with custom allocators */
+void hoedown_buffer_init(
+ hoedown_buffer *buffer,
+ size_t unit,
+ hoedown_realloc_callback data_realloc,
+ hoedown_free_callback data_free,
+ hoedown_free_callback buffer_free
+);
+
+/* hoedown_buffer_new: allocate a new buffer */
+hoedown_buffer *hoedown_buffer_new(size_t unit) __attribute__ ((malloc));
+
+/* hoedown_buffer_reset: free internal data of the buffer */
+void hoedown_buffer_reset(hoedown_buffer *buf);
+
+/* hoedown_buffer_grow: increase the allocated size to the given value */
+void hoedown_buffer_grow(hoedown_buffer *buf, size_t neosz);
+
+/* hoedown_buffer_put: append raw data to a buffer */
+void hoedown_buffer_put(hoedown_buffer *buf, const uint8_t *data, size_t size);
+
+/* hoedown_buffer_puts: append a NUL-terminated string to a buffer */
+void hoedown_buffer_puts(hoedown_buffer *buf, const char *str);
+
+/* hoedown_buffer_putc: append a single char to a buffer */
+void hoedown_buffer_putc(hoedown_buffer *buf, uint8_t c);
+
+/* hoedown_buffer_set: replace the buffer's contents with raw data */
+void hoedown_buffer_set(hoedown_buffer *buf, const uint8_t *data, size_t size);
+
+/* hoedown_buffer_sets: replace the buffer's contents with a NUL-terminated string */
+void hoedown_buffer_sets(hoedown_buffer *buf, const char *str);
+
+/* hoedown_buffer_eq: compare a buffer's data with other data for equality */
+int hoedown_buffer_eq(const hoedown_buffer *buf, const uint8_t *data, size_t size);
+
+/* hoedown_buffer_eq: compare a buffer's data with NUL-terminated string for equality */
+int hoedown_buffer_eqs(const hoedown_buffer *buf, const char *str);
+
+/* hoedown_buffer_prefix: compare the beginning of a buffer with a string */
+int hoedown_buffer_prefix(const hoedown_buffer *buf, const char *prefix);
+
+/* hoedown_buffer_slurp: remove a given number of bytes from the head of the buffer */
+void hoedown_buffer_slurp(hoedown_buffer *buf, size_t size);
+
+/* hoedown_buffer_cstr: NUL-termination of the string array (making a C-string) */
+const char *hoedown_buffer_cstr(hoedown_buffer *buf);
+
+/* hoedown_buffer_printf: formatted printing to a buffer */
+void hoedown_buffer_printf(hoedown_buffer *buf, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
+
+/* hoedown_buffer_free: free the buffer */
+void hoedown_buffer_free(hoedown_buffer *buf);
+
+
+/* HOEDOWN_BUFPUTSL: optimized hoedown_buffer_puts of a string literal */
+#define HOEDOWN_BUFPUTSL(output, literal) \
+ hoedown_buffer_put(output, (const uint8_t *)literal, sizeof(literal) - 1)
+
+/* HOEDOWN_BUFSETSL: optimized hoedown_buffer_sets of a string literal */
+#define HOEDOWN_BUFSETSL(output, literal) \
+ hoedown_buffer_set(output, (const uint8_t *)literal, sizeof(literal) - 1)
+
+/* HOEDOWN_BUFEQSL: optimized hoedown_buffer_eqs of a string literal */
+#define HOEDOWN_BUFEQSL(output, literal) \
+ hoedown_buffer_eq(output, (const uint8_t *)literal, sizeof(literal) - 1)
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /** HOEDOWN_BUFFER_H **/
diff --git a/ext/phalcon/mvc/view/engine/markdown/document.c b/ext/phalcon/mvc/view/engine/markdown/document.c
new file mode 100644
index 0000000..f4cad0b
--- /dev/null
+++ b/ext/phalcon/mvc/view/engine/markdown/document.c
@@ -0,0 +1,3384 @@
+/*
+ * Copyright (c) 2008, Natacha Porté
+ * Copyright (c) 2011, Vicent Martí
+ * Copyright (c) 2013, Devin Torres and the Hoedown authors
+ *
+ * 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 "document.h"
+
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#include "stack.h"
+
+#ifndef _MSC_VER
+#include <strings.h>
+#else
+#define strncasecmp _strnicmp
+#endif
+
+#define REF_TABLE_SIZE 8
+
+#define BUFFER_BLOCK 0
+#define BUFFER_SPAN 1
+#define BUFFER_ATTRIBUTE 2
+
+const char *hoedown_find_block_tag(const char *str, unsigned int len);
+
+/***************
+ * LOCAL TYPES *
+ ***************/
+
+/* link_ref: reference to a link */
+struct link_ref {
+ unsigned int id;
+
+ hoedown_buffer *link;
+ hoedown_buffer *title;
+ hoedown_buffer *attr;
+
+ struct link_ref *next;
+};
+
+/* footnote_ref: reference to a footnote */
+struct footnote_ref {
+ unsigned int id;
+
+ int is_used;
+ unsigned int num;
+
+ hoedown_buffer *contents;
+};
+
+/* footnote_item: an item in a footnote_list */
+struct footnote_item {
+ struct footnote_ref *ref;
+ struct footnote_item *next;
+};
+
+/* footnote_list: linked list of footnote_item */
+struct footnote_list {
+ unsigned int count;
+ struct footnote_item *head;
+ struct footnote_item *tail;
+};
+
+/* 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 */
+typedef size_t
+(*char_trigger)(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+
+static size_t char_emphasis(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_quote(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_linebreak(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_codespan(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_escape(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_entity(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_langle_tag(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_autolink_url(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_autolink_email(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_autolink_www(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_link(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_superscript(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size);
+static size_t char_math(hoedown_buffer *ob, hoedown_document *doc, uint8_t *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_ENTITY,
+ MD_CHAR_AUTOLINK_URL,
+ MD_CHAR_AUTOLINK_EMAIL,
+ MD_CHAR_AUTOLINK_WWW,
+ MD_CHAR_SUPERSCRIPT,
+ MD_CHAR_QUOTE,
+ MD_CHAR_MATH
+};
+
+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,
+ &char_quote,
+ &char_math
+};
+
+struct hoedown_document {
+ hoedown_renderer md;
+ hoedown_renderer_data data;
+
+ struct link_ref *refs[REF_TABLE_SIZE];
+ struct footnote_list footnotes_found;
+ struct footnote_list footnotes_used;
+ uint8_t active_char[256];
+ hoedown_stack work_bufs[3];
+ hoedown_extensions ext_flags;
+ size_t max_nesting;
+ int in_link_body;
+
+ hoedown_user_block user_block;
+ hoedown_buffer *meta;
+};
+
+/***************************
+ * HELPER FUNCTIONS *
+ ***************************/
+
+static hoedown_buffer *
+newbuf(hoedown_document *doc, int type)
+{
+ static const size_t buf_size[3] = {256, 64, 64};
+ hoedown_buffer *work = NULL;
+ hoedown_stack *pool = &doc->work_bufs[type];
+
+ if (pool->size < pool->asize &&
+ pool->item[pool->size] != NULL) {
+ work = pool->item[pool->size++];
+ work->size = 0;
+ } else {
+ work = hoedown_buffer_new(buf_size[type]);
+ hoedown_stack_push(pool, work);
+ }
+
+ return work;
+}
+
+static void
+popbuf(hoedown_document *doc, int type)
+{
+ doc->work_bufs[type].size--;
+}
+
+static void
+unscape_text(hoedown_buffer *ob, hoedown_buffer *src)
+{
+ size_t i = 0, org;
+ while (i < src->size) {
+ org = i;
+ while (i < src->size && src->data[i] != '\\')
+ i++;
+
+ if (i > org)
+ hoedown_buffer_put(ob, src->data + org, i - org);
+
+ if (i + 1 >= src->size)
+ break;
+
+ hoedown_buffer_putc(ob, src->data[i + 1]);
+ i += 2;
+ }
+}
+
+static unsigned int
+hash_link_ref(const uint8_t *link_ref, size_t length)
+{
+ size_t i;
+ unsigned int hash = 0;
+
+ for (i = 0; i < length; ++i)
+ hash = tolower(link_ref[i]) + (hash << 6) + (hash << 16) - hash;
+
+ return hash;
+}
+
+static struct link_ref *
+add_link_ref(
+ struct link_ref **references,
+ const uint8_t *name, size_t name_size)
+{
+ struct link_ref *ref = hoedown_calloc(1, sizeof(struct link_ref));
+
+ ref->id = hash_link_ref(name, name_size);
+ ref->next = references[ref->id % REF_TABLE_SIZE];
+
+ references[ref->id % REF_TABLE_SIZE] = ref;
+ return ref;
+}
+
+static struct link_ref *
+find_link_ref(struct link_ref **references, uint8_t *name, size_t length)
+{
+ unsigned int hash = hash_link_ref(name, length);
+ struct link_ref *ref = NULL;
+
+ ref = references[hash % REF_TABLE_SIZE];
+
+ while (ref != NULL) {
+ if (ref->id == hash)
+ return ref;
+
+ ref = ref->next;
+ }
+
+ return NULL;
+}
+
+static void
+free_link_refs(struct link_ref **references)
+{
+ size_t i;
+
+ for (i = 0; i < REF_TABLE_SIZE; ++i) {
+ struct link_ref *r = references[i];
+ struct link_ref *next;
+
+ while (r) {
+ next = r->next;
+ hoedown_buffer_free(r->link);
+ hoedown_buffer_free(r->title);
+ hoedown_buffer_free(r->attr);
+ free(r);
+ r = next;
+ }
+ }
+}
+
+static struct footnote_ref *
+create_footnote_ref(struct footnote_list *list, const uint8_t *name, size_t name_size)
+{
+ struct footnote_ref *ref = hoedown_calloc(1, sizeof(struct footnote_ref));
+
+ ref->id = hash_link_ref(name, name_size);
+
+ return ref;
+}
+
+static int
+add_footnote_ref(struct footnote_list *list, struct footnote_ref *ref)
+{
+ struct footnote_item *item = hoedown_calloc(1, sizeof(struct footnote_item));
+ if (!item)
+ return 0;
+ item->ref = ref;
+
+ if (list->head == NULL) {
+ list->head = list->tail = item;
+ } else {
+ list->tail->next = item;
+ list->tail = item;
+ }
+ list->count++;
+
+ return 1;
+}
+
+static struct footnote_ref *
+find_footnote_ref(struct footnote_list *list, uint8_t *name, size_t length)
+{
+ unsigned int hash = hash_link_ref(name, length);
+ struct footnote_item *item = NULL;
+
+ item = list->head;
+
+ while (item != NULL) {
+ if (item->ref->id == hash)
+ return item->ref;
+ item = item->next;
+ }
+
+ return NULL;
+}
+
+static void
+free_footnote_ref(struct footnote_ref *ref)
+{
+ hoedown_buffer_free(ref->contents);
+ free(ref);
+}
+
+static void
+free_footnote_list(struct footnote_list *list, int free_refs)
+{
+ struct footnote_item *item = list->head;
+ struct footnote_item *next;
+
+ while (item) {
+ next = item->next;
+ if (free_refs)
+ free_footnote_ref(item->ref);
+ free(item);
+ item = next;
+ }
+}
+
+
+/*
+ * Check whether a char is a Markdown spacing char.
+
+ * Right now we only consider spaces the actual
+ * space and a newline: tabs and carriage returns
+ * are filtered out during the preprocessing phase.
+ *
+ * If we wanted to actually be UTF-8 compliant, we
+ * should instead extract an Unicode codepoint from
+ * this character and check for space properties.
+ */
+static int
+_isspace(int c)
+{
+ return c == ' ' || c == '\n';
+}
+
+/* is_empty_all: verify that all the data is spacing */
+static int
+is_empty_all(const uint8_t *data, size_t size)
+{
+ size_t i = 0;
+ while (i < size && _isspace(data[i])) i++;
+ return i == size;
+}
+
+/*
+ * Replace all spacing characters in data with spaces. As a special
+ * case, this collapses a newline with the previous space, if possible.
+ */
+static void
+replace_spacing(hoedown_buffer *ob, const uint8_t *data, size_t size)
+{
+ size_t i = 0, mark;
+ hoedown_buffer_grow(ob, size);
+ while (1) {
+ mark = i;
+ while (i < size && data[i] != '\n') i++;
+ hoedown_buffer_put(ob, data + mark, i - mark);
+
+ if (i >= size) break;
+
+ if (!(i > 0 && data[i-1] == ' '))
+ hoedown_buffer_putc(ob, ' ');
+ i++;
+ }
+}
+
+/****************************
+ * 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(uint8_t *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;
+}
+
+static size_t
+script_tag_length(uint8_t *data, size_t size)
+{
+ size_t i = 2;
+ char comment = 0;
+
+ if (size < 3 || data[0] != '<' || data[1] != '?') {
+ return 0;
+ }
+
+ i = 2;
+
+ while (i < size) {
+ if (data[i - 1] == '?' && data[i] == '>' && comment == 0) {
+ break;
+ }
+
+ if (data[i] == '\'' || data[i] == '"') {
+ if (comment != 0) {
+ if (data[i] == comment && data[i - 1] != '\\') {
+ comment = 0;
+ }
+ } else {
+ comment = data[i];
+ }
+ }
+
+ ++i;
+ }
+
+ return i + 1;
+}
+
+/* tag_length 窶「 returns the length of the given tag, or 0 is it's not valid */
+static size_t
+tag_length(uint8_t *data, size_t size, hoedown_autolink_type *autolink, int script_tag)
+{
+ 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])) {
+ if (script_tag) {
+ return script_tag_length(data, size);
+ }
+ return 0;
+ }
+
+ /* scheme test */
+ *autolink = HOEDOWN_AUTOLINK_NONE;
+
+ /* 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 = HOEDOWN_AUTOLINK_EMAIL;
+ return i + j;
+ }
+ }
+
+ if (i > 2 && data[i] == ':') {
+ *autolink = HOEDOWN_AUTOLINK_NORMAL;
+ i++;
+ }
+
+ /* completing autolink test: no spacing or ' or " */
+ if (i >= size)
+ *autolink = HOEDOWN_AUTOLINK_NONE;
+
+ else if (*autolink) {
+ j = i;
+
+ while (i < size) {
+ if (data[i] == '\\') i += 2;
+ else if (data[i] == '>' || data[i] == '\'' ||
+ data[i] == '"' || data[i] == ' ' || data[i] == '\n')
+ 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 = HOEDOWN_AUTOLINK_NONE;
+ }
+
+ /* looking for something 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(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
+{
+ size_t i = 0, end = 0;
+ hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL };
+ uint8_t *active_char = doc->active_char;
+
+ if (doc->work_bufs[BUFFER_SPAN].size +
+ doc->work_bufs[BUFFER_BLOCK].size > doc->max_nesting)
+ return;
+
+ while (i < size) {
+ size_t user_block = 0;
+ while (end < size) {
+ if (doc->user_block) {
+ user_block = doc->user_block(data+end, size - end, &doc->data);
+ if (user_block) {
+ break;
+ }
+ }
+ /* copying inactive chars into the output */
+ if (active_char[data[end]] != 0) {
+ break;
+ }
+ end++;
+ }
+
+ if (doc->md.normal_text) {
+ work.data = data + i;
+ work.size = end - i;
+ doc->md.normal_text(ob, &work, &doc->data);
+ }
+ else
+ hoedown_buffer_put(ob, data + i, end - i);
+
+ if (end >= size) {
+ break;
+ }
+ i = end;
+
+ if (user_block) {
+ work.data = data + i;
+ work.size = user_block;
+ end = user_block;
+ if (doc->md.user_block) {
+ doc->md.user_block(ob, &work, &doc->data);
+ } else {
+ hoedown_buffer_put(ob, data + i, size - i);
+ }
+ if (!end) {
+ end = i + 1;
+ } else {
+ i += end;
+ end = i;
+ }
+ } else {
+ end = markdown_char_ptrs[ (int)active_char[data[end]] ](ob, doc, data + i, i, size - i);
+ if (!end) /* no action from the callback */
+ end = i + 1;
+ else {
+ i += end;
+ end = i;
+ }
+ }
+ }
+}
+
+static size_t parse_attributes(uint8_t *data, size_t size, struct hoedown_buffer *attr, struct hoedown_buffer *block_attr, int is_header)
+{
+ size_t i, len, begin = 0, end = 0;
+
+ i = size;
+ while (data[i-1] == '\n') {
+ i--;
+ }
+ len = i;
+
+ if (i && data[i-1] == '}') {
+ do {
+ i--;
+ } while (i && data[i] != '{');
+
+ begin = i + 1;
+ end = len - 1;
+ while (i && data[i-1] == ' ') {
+ i--;
+ }
+ }
+
+ if (is_header && i && data[i-1] == '#') {
+ while (i && data[i-1] == '#') {
+ i--;
+ }
+ while (i && data[i-1] == ' ') {
+ i--;
+ }
+ }
+
+ if (begin && end) {
+ if (block_attr && data[begin] == '@') {
+ while (data[begin] != ' ') {
+ begin++;
+ }
+ block_attr->data = data + begin;
+ block_attr->size = end - begin;
+ len = i;
+ if (attr) {
+ len = parse_attributes(data, len, attr, NULL, is_header);
+ }
+ } else if (attr) {
+ if (attr->size) {
+ hoedown_buffer_reset(attr);
+ }
+ hoedown_buffer_put(attr, data + begin, end - begin);
+ len = i;
+ }
+ }
+
+ return len;
+}
+
+/* is_escaped 窶「 returns whether special char at data[loc] is escaped by '\\' */
+static int
+is_escaped(uint8_t *data, size_t loc)
+{
+ size_t i = loc;
+ while (i >= 1 && data[i - 1] == '\\')
+ i--;
+
+ /* odd numbers of backslashes escapes data[loc] */
+ return (loc - i) % 2;
+}
+
+/* find_emph_char 窶「 looks for the next emph uint8_t, skipping other constructs */
+static size_t
+find_emph_char(uint8_t *data, size_t size, uint8_t c)
+{
+ size_t i = 0;
+
+ while (i < size) {
+ while (i < size && data[i] != c && data[i] != '[' && data[i] != '`')
+ i++;
+
+ if (i == size)
+ return 0;
+
+ /* not counting escaped chars */
+ if (is_escaped(data, i)) {
+ i++; continue;
+ }
+
+ if (data[i] == c)
+ return i;
+
+ /* skipping a codespan */
+ 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++;
+ }
+
+ /* not a well-formed codespan; use found matching emph char */
+ if (i >= size) return tmp_i;
+ }
+ /* skipping a link */
+ else if (data[i] == '[') {
+ size_t tmp_i = 0;
+ uint8_t cc;
+
+ i++;
+ while (i < size && data[i] != ']') {
+ if (!tmp_i && data[i] == c) tmp_i = i;
+ i++;
+ }
+
+ i++;
+ while (i < size && _isspace(data[i]))
+ i++;
+
+ if (i >= size)
+ return tmp_i;
+
+ switch (data[i]) {
+ case '[':
+ cc = ']'; break;
+
+ case '(':
+ cc = ')'; break;
+
+ default:
+ if (tmp_i)
+ return tmp_i;
+ else
+ continue;
+ }
+
+ 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 spacing and not followed by symbol */
+static size_t
+parse_emph1(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, uint8_t c)
+{
+ size_t i = 0, len;
+ hoedown_buffer *work = 0;
+ int r;
+
+ /* 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 (data[i] == c && !_isspace(data[i - 1])) {
+
+ if (doc->ext_flags & HOEDOWN_EXT_NO_INTRA_EMPHASIS) {
+ if (i + 1 < size && isalnum(data[i + 1]))
+ continue;
+ }
+
+ work = newbuf(doc, BUFFER_SPAN);
+ parse_inline(work, doc, data, i);
+
+ if (doc->ext_flags & HOEDOWN_EXT_UNDERLINE && c == '_')
+ r = doc->md.underline(ob, work, &doc->data);
+ else
+ r = doc->md.emphasis(ob, work, &doc->data);
+
+ popbuf(doc, BUFFER_SPAN);
+ return r ? i + 1 : 0;
+ }
+ }
+
+ return 0;
+}
+
+/* parse_emph2 窶「 parsing single emphase */
+static size_t
+parse_emph2(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, uint8_t c)
+{
+ size_t i = 0, len;
+ hoedown_buffer *work = 0;
+ int r;
+
+ 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 = newbuf(doc, BUFFER_SPAN);
+ parse_inline(work, doc, data, i);
+
+ if (c == '~')
+ r = doc->md.strikethrough(ob, work, &doc->data);
+ else if (c == '=')
+ r = doc->md.highlight(ob, work, &doc->data);
+ else
+ r = doc->md.double_emphasis(ob, work, &doc->data);
+
+ popbuf(doc, 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(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, uint8_t 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 spacing preceded symbols */
+ if (data[i] != c || _isspace(data[i - 1]))
+ continue;
+
+ if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && doc->md.triple_emphasis) {
+ /* triple symbol found */
+ hoedown_buffer *work = newbuf(doc, BUFFER_SPAN);
+
+ parse_inline(work, doc, data, i);
+ r = doc->md.triple_emphasis(ob, work, &doc->data);
+ popbuf(doc, 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, doc, 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, doc, data - 1, size + 1, c);
+ if (!len) return 0;
+ else return len - 1;
+ }
+ }
+ return 0;
+}
+
+/* parse_math 窶「 parses a math span until the given ending delimiter */
+static size_t
+parse_math(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size, const char *end, size_t delimsz, int displaymode)
+{
+ hoedown_buffer text = { NULL, 0, 0, 0, NULL, NULL, NULL };
+ size_t i = delimsz;
+
+ if (!doc->md.math)
+ return 0;
+
+ /* find ending delimiter */
+ while (1) {
+ while (i < size && data[i] != (uint8_t)end[0])
+ i++;
+
+ if (i >= size)
+ return 0;
+
+ if (!is_escaped(data, i) && !(i + delimsz > size)
+ && memcmp(data + i, end, delimsz) == 0)
+ break;
+
+ i++;
+ }
+
+ /* prepare buffers */
+ text.data = data + delimsz;
+ text.size = i - delimsz;
+
+ /* if this is a $$ and MATH_EXPLICIT is not active,
+ * guess whether displaymode should be enabled from the context */
+ i += delimsz;
+ if (delimsz == 2 && !(doc->ext_flags & HOEDOWN_EXT_MATH_EXPLICIT))
+ displaymode = is_empty_all(data - offset, offset) && is_empty_all(data + i, size - i);
+
+ /* call callback */
+ if (doc->md.math(ob, &text, displaymode, &doc->data))
+ return i;
+
+ return 0;
+}
+
+/* char_emphasis 窶「 single and double emphasis parsing */
+static size_t
+char_emphasis(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ uint8_t c = data[0];
+ size_t ret;
+
+ if (doc->ext_flags & HOEDOWN_EXT_NO_INTRA_EMPHASIS) {
+ if (offset > 0 && !_isspace(data[-1]) && data[-1] != '>' && data[-1] != '(')
+ return 0;
+ }
+
+ if (size > 2 && data[1] != c) {
+ /* spacing cannot follow an opening emphasis;
+ * strikethrough and highlight only takes two characters '~~' */
+ if (c == '~' || c == '=' || _isspace(data[1]) || (ret = parse_emph1(ob, doc, 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, doc, 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 == '~' || c == '=' || _isspace(data[3]) || (ret = parse_emph3(ob, doc, 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(hoedown_buffer *ob, hoedown_document *doc, uint8_t *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 doc->md.linebreak(ob, &doc->data) ? 1 : 0;
+}
+
+
+/* char_codespan 窶「 '`' parsing a code span (assuming codespan != 0) */
+static size_t
+char_codespan(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL };
+ 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 spaces */
+ f_begin = nb;
+ while (f_begin < end && data[f_begin] == ' ')
+ f_begin++;
+
+ f_end = end - nb;
+ while (f_end > nb && data[f_end-1] == ' ')
+ f_end--;
+
+ /* real code span */
+ if (f_begin < f_end) {
+ hoedown_buffer attr = { 0, 0, 0, 0, NULL, NULL, NULL };
+
+ work.data = data + f_begin;
+ work.size = f_end - f_begin;
+
+ if ((doc->ext_flags & HOEDOWN_EXT_SPECIAL_ATTRIBUTE) &&
+ data[end] == '{') {
+ size_t a_begin = end+1, a_end = a_begin;
+ while (a_end < size && data[a_end] != '}') {
+ ++a_end;
+ }
+ if (a_end <= size) {
+ attr.data = data + a_begin;
+ attr.size = a_end - a_begin;
+ end += attr.size + 2;
+ }
+ }
+
+ if (!doc->md.codespan(ob, &work, &attr, &doc->data))
+ end = 0;
+ } else {
+ if (!doc->md.codespan(ob, 0, 0, &doc->data))
+ end = 0;
+ }
+
+ return end;
+}
+
+/* char_quote 窶「 '"' parsing a quote */
+static size_t
+char_quote(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ size_t end, nq = 0, i, f_begin, f_end;
+
+ /* counting the number of quotes in the delimiter */
+ while (nq < size && data[nq] == '"')
+ nq++;
+
+ /* finding the next delimiter */
+ end = nq;
+ while (1) {
+ i = end;
+ end += find_emph_char(data + end, size - end, '"');
+ if (end == i) return 0; /* no matching delimiter */
+ i = end;
+ while (end < size && data[end] == '"' && end - i < nq) end++;
+ if (end - i >= nq) break;
+ }
+
+ /* trimming outside spaces */
+ f_begin = nq;
+ while (f_begin < end && data[f_begin] == ' ')
+ f_begin++;
+
+ f_end = end - nq;
+ while (f_end > nq && data[f_end-1] == ' ')
+ f_end--;
+
+ /* real quote */
+ if (f_begin < f_end) {
+ hoedown_buffer *work = newbuf(doc, BUFFER_SPAN);
+ parse_inline(work, doc, data + f_begin, f_end - f_begin);
+
+ if (!doc->md.quote(ob, work, &doc->data))
+ end = 0;
+ popbuf(doc, BUFFER_SPAN);
+ } else {
+ if (!doc->md.quote(ob, 0, &doc->data))
+ end = 0;
+ }
+
+ return end;
+}
+
+
+/* char_escape 窶「 '\\' backslash escape */
+static size_t
+char_escape(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ static const char *escape_chars = "\\`*_{}[]()#+-.!:|&<>^~=\"$";
+ hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL };
+ size_t w;
+
+ if (size > 1) {
+ if (data[1] == '\\' && (doc->ext_flags & HOEDOWN_EXT_MATH) &&
+ size > 2 && (data[2] == '(' || data[2] == '[')) {
+ const char *end = (data[2] == '[') ? "\\\\]" : "\\\\)";
+ w = parse_math(ob, doc, data, offset, size, end, 3, data[2] == '[');
+ if (w) return w;
+ }
+
+ if (strchr(escape_chars, data[1]) == NULL)
+ return 0;
+
+ if (doc->md.normal_text) {
+ work.data = data + 1;
+ work.size = 1;
+ doc->md.normal_text(ob, &work, &doc->data);
+ }
+ else hoedown_buffer_putc(ob, data[1]);
+ } else if (size == 1) {
+ hoedown_buffer_putc(ob, data[0]);
+ }
+
+ 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(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ size_t end = 1;
+ hoedown_buffer work = { 0, 0, 0, 0, NULL, NULL, NULL };
+
+ 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 (doc->md.entity) {
+ work.data = data;
+ work.size = end;
+ doc->md.entity(ob, &work, &doc->data);
+ }
+ else hoedown_buffer_put(ob, data, end);
+
+ return end;
+}
+
+/* char_langle_tag 窶「 '<' when tags or autolinks are allowed */
+static size_t
+char_langle_tag(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL };
+ hoedown_autolink_type altype = HOEDOWN_AUTOLINK_NONE;
+ size_t end = tag_length(data, size, &altype, doc->ext_flags & HOEDOWN_EXT_SCRIPT_TAGS);
+ int ret = 0;
+
+ work.data = data;
+ work.size = end;
+
+ if (end > 2) {
+ if (doc->md.autolink && altype != HOEDOWN_AUTOLINK_NONE) {
+ hoedown_buffer *u_link = newbuf(doc, BUFFER_SPAN);
+ work.data = data + 1;
+ work.size = end - 2;
+ unscape_text(u_link, &work);
+ ret = doc->md.autolink(ob, u_link, altype, &doc->data);
+ popbuf(doc, BUFFER_SPAN);
+ }
+ else if (doc->md.raw_html)
+ ret = doc->md.raw_html(ob, &work, &doc->data);
+ }
+
+ if (!ret) return 0;
+ else return end;
+}
+
+static size_t
+char_autolink_www(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ hoedown_buffer *link, *link_url, *link_text;
+ size_t link_len, rewind;
+
+ if (!doc->md.link || doc->in_link_body)
+ return 0;
+
+ link = newbuf(doc, BUFFER_SPAN);
+
+ if ((link_len = hoedown_autolink__www(&rewind, link, data, offset, size, HOEDOWN_AUTOLINK_SHORT_DOMAINS)) > 0) {
+ link_url = newbuf(doc, BUFFER_SPAN);
+ HOEDOWN_BUFPUTSL(link_url, "http://");
+ hoedown_buffer_put(link_url, link->data, link->size);
+
+ ob->size -= rewind;
+ if (doc->md.normal_text) {
+ link_text = newbuf(doc, BUFFER_SPAN);
+ doc->md.normal_text(link_text, link, &doc->data);
+ doc->md.link(ob, link_text, link_url, NULL, NULL, &doc->data);
+ popbuf(doc, BUFFER_SPAN);
+ } else {
+ doc->md.link(ob, link, link_url, NULL, NULL, &doc->data);
+ }
+ popbuf(doc, BUFFER_SPAN);
+ }
+
+ popbuf(doc, BUFFER_SPAN);
+ return link_len;
+}
+
+static size_t
+char_autolink_email(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ hoedown_buffer *link;
+ size_t link_len, rewind;
+
+ if (!doc->md.autolink || doc->in_link_body)
+ return 0;
+
+ link = newbuf(doc, BUFFER_SPAN);
+
+ if ((link_len = hoedown_autolink__email(&rewind, link, data, offset, size, 0)) > 0) {
+ ob->size -= rewind;
+ doc->md.autolink(ob, link, HOEDOWN_AUTOLINK_EMAIL, &doc->data);
+ }
+
+ popbuf(doc, BUFFER_SPAN);
+ return link_len;
+}
+
+static size_t
+char_autolink_url(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ hoedown_buffer *link;
+ size_t link_len, rewind;
+
+ if (!doc->md.autolink || doc->in_link_body)
+ return 0;
+
+ link = newbuf(doc, BUFFER_SPAN);
+
+ if ((link_len = hoedown_autolink__url(&rewind, link, data, offset, size, 0)) > 0) {
+ ob->size -= rewind;
+ doc->md.autolink(ob, link, HOEDOWN_AUTOLINK_NORMAL, &doc->data);
+ }
+
+ popbuf(doc, BUFFER_SPAN);
+ return link_len;
+}
+
+/* char_link 窶「 '[': parsing a link, a footnote or an image */
+static size_t
+char_link(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ int is_img = (offset && data[-1] == '!' && !is_escaped(data - offset, offset - 1));
+ int is_footnote = (doc->ext_flags & HOEDOWN_EXT_FOOTNOTES && data[1] == '^');
+ size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0;
+ size_t attr_b = 0, attr_e = 0;
+ hoedown_buffer *content = NULL;
+ hoedown_buffer *link = NULL;
+ hoedown_buffer *title = NULL;
+ hoedown_buffer *u_link = NULL;
+ hoedown_buffer *attr = NULL;
+ size_t org_work_size = doc->work_bufs[BUFFER_SPAN].size;
+ int ret = 0, in_title = 0, qtype = 0;
+
+ /* checking whether the correct renderer exists */
+ if ((is_footnote && !doc->md.footnote_ref) || (is_img && !doc->md.image)
+ || (!is_img && !is_footnote && !doc->md.link))
+ goto cleanup;
+
+ /* looking for the matching closing bracket */
+ i += find_emph_char(data + i, size - i, ']');
+ txt_e = i;
+
+ if (i < size && data[i] == ']') i++;
+ else goto cleanup;
+
+ /* footnote link */
+ if (is_footnote) {
+ hoedown_buffer id = { NULL, 0, 0, 0, NULL, NULL, NULL };
+ struct footnote_ref *fr;
+
+ if (txt_e < 3)
+ goto cleanup;
+
+ id.data = data + 2;
+ id.size = txt_e - 2;
+
+ fr = find_footnote_ref(&doc->footnotes_found, id.data, id.size);
+
+ /* mark footnote used */
+ if (fr && !fr->is_used) {
+ if(!add_footnote_ref(&doc->footnotes_used, fr))
+ goto cleanup;
+ fr->is_used = 1;
+ fr->num = doc->footnotes_used.count;
+
+ /* render */
+ if (doc->md.footnote_ref)
+ ret = doc->md.footnote_ref(ob, fr->num, &doc->data);
+ }
+
+ goto cleanup;
+ }
+
+ /* skip any amount of spacing */
+ /* (this is much more laxist than original markdown syntax) */
+ while (i < size && _isspace(data[i]))
+ i++;
+
+ /* inline style link */
+ if (i < size && data[i] == '(') {
+ size_t nb_p;
+
+ /* skipping initial spacing */
+ i++;
+
+ while (i < size && _isspace(data[i]))
+ i++;
+
+ link_b = i;
+
+ /* looking for link end: ' " ) */
+ /* Count the number of open parenthesis */
+ nb_p = 0;
+
+ while (i < size) {
+ if (data[i] == '\\') i += 2;
+ else if (data[i] == '(' && i != 0) {
+ nb_p++; i++;
+ }
+ else if (data[i] == ')') {
+ if (nb_p == 0) break;
+ else nb_p--; i++;
+ } else if (i >= 1 && _isspace(data[i-1]) && (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] == '"') {
+ qtype = data[i];
+ in_title = 1;
+ i++;
+ title_b = i;
+
+ while (i < size) {
+ if (data[i] == '\\') i += 2;
+ else if (data[i] == qtype) {in_title = 0; i++;}
+ else if ((data[i] == ')') && !in_title) break;
+ else i++;
+ }
+
+ if (i >= size) goto cleanup;
+
+ /* skipping spacing 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 spacing 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 = newbuf(doc, BUFFER_SPAN);
+ hoedown_buffer_put(link, data + link_b, link_e - link_b);
+ }
+
+ if (title_e > title_b) {
+ title = newbuf(doc, BUFFER_SPAN);
+ hoedown_buffer_put(title, data + title_b, title_e - title_b);
+ }
+
+ i++;
+ }
+
+ /* reference style link */
+ else if (i < size && data[i] == '[') {
+ hoedown_buffer *id = newbuf(doc, BUFFER_SPAN);
+ 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)
+ replace_spacing(id, data + 1, txt_e - 1);
+ else
+ hoedown_buffer_put(id, data + link_b, link_e - link_b);
+
+ lr = find_link_ref(doc->refs, id->data, id->size);
+ if (!lr)
+ goto cleanup;
+
+ /* keeping link and title from link_ref */
+ link = lr->link;
+ title = lr->title;
+ attr = lr->attr;
+ i++;
+ }
+
+ /* shortcut reference style link */
+ else {
+ hoedown_buffer *id = newbuf(doc, BUFFER_SPAN);
+ struct link_ref *lr;
+
+ /* crafting the id */
+ replace_spacing(id, data + 1, txt_e - 1);
+
+ /* finding the link_ref */
+ lr = find_link_ref(doc->refs, id->data, id->size);
+ if (!lr)
+ goto cleanup;
+
+ /* keeping link and title from link_ref */
+ link = lr->link;
+ title = lr->title;
+ attr = lr->attr;
+
+ /* rewinding the spacing */
+ i = txt_e + 1;
+ }
+
+ /* building content: img alt is kept, only link content is parsed */
+ if (txt_e > 1) {
+ content = newbuf(doc, BUFFER_SPAN);
+ if (is_img) {
+ hoedown_buffer_put(content, data + 1, txt_e - 1);
+ } else {
+ /* disable autolinking when parsing inline the
+ * content of a link */
+ doc->in_link_body = 1;
+ parse_inline(content, doc, data + 1, txt_e - 1);
+ doc->in_link_body = 0;
+ }
+ }
+
+ if (link) {
+ u_link = newbuf(doc, BUFFER_SPAN);
+ unscape_text(u_link, link);
+ }
+
+ if (doc->ext_flags & HOEDOWN_EXT_SPECIAL_ATTRIBUTE) {
+ if (i < size && data[i] == '{') {
+ /* skipping initial whitespace */
+ i++;
+
+ while (i < size && _isspace(data[i]))
+ i++;
+
+ attr_b = i;
+
+ while (i < size) {
+ if (data[i] == '\\') i += 2;
+ else if (data[i] == '}') break;
+ else if (i >= 1 && _isspace(data[i-1]) && (data[i] == '\'' || data[i] == '"')) break;
+ else i++;
+ }
+
+ if (i >= size) goto cleanup;
+ attr_e = i;
+
+ /* remove whitespace at the end of the attributes */
+ while (attr_e > attr_b && _isspace(data[attr_e - 1]))
+ attr_e--;
+
+ /* remove optional angle brackets around the attributes */
+ if (data[attr_b] == '<') attr_b++;
+ if (data[attr_e - 1] == '>') attr_e--;
+
+ /* building escaped attributes */
+ if (attr_e > attr_b) {
+ if (attr) {
+ hoedown_buffer_putc(attr, ' ');
+ } else {
+ attr = newbuf(doc, BUFFER_SPAN);
+ }
+ hoedown_buffer_put(attr, data + attr_b, attr_e - attr_b);
+ }
+
+ i++;
+ }
+ }
+
+ /* calling the relevant rendering function */
+ if (is_img) {
+ if (ob->size && ob->data[ob->size - 1] == '!')
+ ob->size -= 1;
+
+ ret = doc->md.image(ob, u_link, title, content, attr, &doc->data);
+ } else {
+ ret = doc->md.link(ob, content, u_link, title, attr, &doc->data);
+ }
+
+ /* cleanup */
+cleanup:
+ doc->work_bufs[BUFFER_SPAN].size = (int)org_work_size;
+ return ret ? i : 0;
+}
+
+static size_t
+char_superscript(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ size_t sup_start, sup_len;
+ hoedown_buffer *sup;
+
+ if (!doc->md.superscript)
+ return 0;
+
+ if (size < 2)
+ return 0;
+
+ if (data[1] == '(') {
+ sup_start = 2;
+ sup_len = find_emph_char(data + 2, size - 2, ')') + 2;
+
+ 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 = newbuf(doc, BUFFER_SPAN);
+ parse_inline(sup, doc, data + sup_start, sup_len - sup_start);
+ doc->md.superscript(ob, sup, &doc->data);
+ popbuf(doc, BUFFER_SPAN);
+
+ return (sup_start == 2) ? sup_len + 1 : sup_len;
+}
+
+static size_t
+char_math(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t offset, size_t size)
+{
+ /* double dollar */
+ if (size > 1 && data[1] == '$')
+ return parse_math(ob, doc, data, offset, size, "$$", 2, 1);
+
+ /* single dollar allowed only with MATH_EXPLICIT flag */
+ if (doc->ext_flags & HOEDOWN_EXT_MATH_EXPLICIT)
+ return parse_math(ob, doc, data, offset, size, "$", 1, 0);
+
+ return 0;
+}
+
+/*********************************
+ * BLOCK-LEVEL PARSING FUNCTIONS *
+ *********************************/
+
+/* is_empty 窶「 returns the line length when it is empty, 0 otherwise */
+static size_t
+is_empty(const uint8_t *data, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size && data[i] != '\n'; i++)
+ if (data[i] != ' ')
+ return 0;
+
+ return i + 1;
+}
+
+/* is_hrule 窶「 returns whether a line is a horizontal rule */
+static int
+is_hrule(uint8_t *data, size_t size)
+{
+ size_t i = 0, n = 0;
+ uint8_t 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 uint8_t */
+ if (i + 2 >= size
+ || (data[i] != '*' && data[i] != '-' && data[i] != '_'))
+ return 0;
+ c = data[i];
+
+ /* the whole line must be the char or space */
+ while (i < size && data[i] != '\n') {
+ if (data[i] == c) n++;
+ else if (data[i] != ' ')
+ return 0;
+
+ i++;
+ }
+
+ return n >= 3;
+}
+
+/* check if a line is a code fence; return the
+ * end of the code fence. if passed, width of
+ * the fence rule and character will be returned */
+static size_t
+is_codefence(uint8_t *data, size_t size, size_t *width, uint8_t *chr)
+{
+ size_t i = 0, n = 1;
+ uint8_t 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 uint8_t */
+ c = data[i];
+ if (i + 2 >= size || !(c=='~' || c=='`'))
+ return 0;
+
+ /* the fence must be that same character */
+ while (++i < size && data[i] == c)
+ ++n;
+
+ if (n < 3)
+ return 0;
+
+ if (width) *width = n;
+ if (chr) *chr = c;
+ return i;
+}
+
+/* expects single line, checks if it's a codefence and extracts language */
+static int
+parse_codefence(uint8_t *data, size_t size, hoedown_buffer *lang, size_t *width, uint8_t *chr, unsigned int flags, hoedown_buffer *attr)
+{
+ size_t i, w, lang_start, attr_start = 0;
+
+ i = w = is_codefence(data, size, width, chr);
+ if (i == 0)
+ return 0;
+
+ while (i < size && _isspace(data[i]))
+ i++;
+
+ lang_start = i;
+
+ if (flags & HOEDOWN_EXT_SPECIAL_ATTRIBUTE) {
+ int e = 0;
+ while (i < size) {
+ if (!e) {
+ if (data[i] == '{') {
+ attr_start = i + 1;
+ e = '}';
+ } else if (_isspace(data[i])) {
+ if ((i+1 < size) && data[i+1] != '{') {
+ break;
+ }
+ }
+ } else if (data[i] == '}') {
+ attr->data = data + attr_start;
+ attr->size = i - attr_start;
+ break;
+ }
+ ++i;
+ }
+ } else {
+ while (i < size && !_isspace(data[i]))
+ i++;
+ }
+
+ lang->data = data + lang_start;
+ lang->size = i - lang_start;
+
+ /* Avoid parsing a codespan as a fence */
+ i = lang_start + 2;
+ while (i < size && !(data[i] == *chr && data[i-1] == *chr && data[i-2] == *chr)) i++;
+ if (i < size) return 0;
+
+ return w;
+}
+
+/* is_atxheader 窶「 returns whether the line is a hash-prefixed header */
+static int
+is_atxheader(hoedown_document *doc, uint8_t *data, size_t size)
+{
+ if (data[0] != '#')
+ return 0;
+
+ if (doc->ext_flags & HOEDOWN_EXT_SPACE_HEADERS) {
+ size_t level = 0;
+
+ while (level < size && level < 6 && data[level] == '#')
+ level++;
+
+ if (level < size && data[level] != ' ')
+ return 0;
+ }
+
+ return 1;
+}
+
+/* is_headerline 窶「 returns whether the line is a setext-style hdr underline */
+static int
+is_headerline(uint8_t *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] == ' ') 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] == ' ') i++;
+ return (i >= size || data[i] == '\n') ? 2 : 0; }
+
+ return 0;
+}
+
+static int
+is_next_headerline(uint8_t *data, size_t size)
+{
+ size_t i = 0;
+
+ while (i < size && data[i] != '\n')
+ i++;
+
+ if (++i >= size)
+ return 0;
+
+ return is_headerline(data + i, size - i);
+}
+
+/* prefix_quote 窶「 returns blockquote prefix length */
+static size_t
+prefix_quote(uint8_t *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] == ' ')
+ return i + 2;
+
+ return i + 1;
+ }
+
+ return 0;
+}
+
+/* prefix_code 窶「 returns prefix length for block code*/
+static size_t
+prefix_code(uint8_t *data, size_t size)
+{
+ 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(uint8_t *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] != ' ')
+ return 0;
+
+ if (is_next_headerline(data + i, size - i))
+ return 0;
+
+ return i + 2;
+}
+
+/* prefix_uli 窶「 returns ordered list item prefix */
+static size_t
+prefix_uli(uint8_t *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] != ' ')
+ return 0;
+
+ if (is_next_headerline(data + i, size - i))
+ return 0;
+
+ return i + 2;
+}
+
+
+/* parse_block 窶「 parsing of one block, returning next uint8_t to parse */
+static void parse_block(hoedown_buffer *ob, hoedown_document *doc,
+ uint8_t *data, size_t size);
+
+
+/* parse_blockquote 窶「 handles parsing of a blockquote fragment */
+static size_t
+parse_blockquote(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
+{
+ size_t beg, end = 0, pre, work_size = 0;
+ uint8_t *work_data = 0;
+ hoedown_buffer *out = 0;
+
+ out = newbuf(doc, 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 */
+ /* hoedown_buffer_put(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, doc, work_data, work_size);
+ if (doc->md.blockquote)
+ doc->md.blockquote(ob, out, &doc->data);
+ popbuf(doc, BUFFER_BLOCK);
+ return end;
+}
+
+static size_t
+parse_htmlblock(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, int do_render);
+
+/* parse_blockquote 窶「 handles parsing of a regular paragraph */
+static size_t
+parse_paragraph(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
+{
+ hoedown_buffer work = { NULL, 0, 0, 0, NULL, NULL, NULL };
+ size_t i = 0, end = 0;
+ int level = 0;
+
+ work.data = data;
+
+ while (i < size) {
+ for (end = i + 1; end < size && data[end - 1] != '\n'; end++) /* empty */;
+
+ if (is_empty(data + i, size - i))
+ break;
+
+ if ((level = is_headerline(data + i, size - i)) != 0)
+ break;
+
+ if (is_atxheader(doc, data + i, size - i) ||
+ is_hrule(data + i, size - i) ||
+ prefix_quote(data + i, size - i)) {
+ end = i;
+ break;
+ }
+
+ i = end;
+ }
+
+ work.size = i;
+ while (work.size && data[work.size - 1] == '\n')
+ work.size--;
+
+ if (!level) {
+ hoedown_buffer *tmp = newbuf(doc, BUFFER_BLOCK);
+ parse_inline(tmp, doc, work.data, work.size);
+ if (doc->md.paragraph)
+ doc->md.paragraph(ob, tmp, &doc->data);
+ popbuf(doc, BUFFER_BLOCK);
+ } else {
+ hoedown_buffer *header_work;
+ hoedown_buffer *attr_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) {
+ hoedown_buffer *tmp = newbuf(doc, BUFFER_BLOCK);
+ parse_inline(tmp, doc, work.data, work.size);
+
+ if (doc->md.paragraph)
+ doc->md.paragraph(ob, tmp, &doc->data);
+
+ popbuf(doc, BUFFER_BLOCK);
+ work.data += beg;
+ work.size = i - beg;
+ }
+ else work.size = i;
+ }
+
+ header_work = newbuf(doc, BUFFER_SPAN);
+ attr_work = newbuf(doc, BUFFER_ATTRIBUTE);
+ parse_inline(header_work, doc, work.data, work.size);
+
+ if (doc->md.header) {
+ if (doc->ext_flags & HOEDOWN_EXT_SPECIAL_ATTRIBUTE) {
+ header_work->size = parse_attributes(header_work->data, header_work->size, attr_work, NULL, 1);
+ }
+ doc->md.header(ob, header_work, attr_work, (int)level, &doc->data);
+ }
+
+ popbuf(doc, BUFFER_SPAN);
+ popbuf(doc, BUFFER_ATTRIBUTE);
+ }
+
+ return end;
+}
+
+/* parse_fencedcode 窶「 handles parsing of a block-level code fragment */
+static size_t
+parse_fencedcode(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size, unsigned int flags)
+{
+ hoedown_buffer text = { 0, 0, 0, 0, NULL, NULL, NULL };
+ hoedown_buffer lang = { 0, 0, 0, 0, NULL, NULL, NULL };
+ size_t i = 0, text_start, line_start;
+ size_t w, w2;
+ size_t width, width2;
+ uint8_t chr, chr2;
+ hoedown_buffer attr = { 0, 0, 0, 0, NULL, NULL, NULL };
+
+
+ /* parse codefence line */
+ while (i < size && data[i] != '\n')
+ i++;
+
+ w = parse_codefence(data, i, &lang, &width, &chr, flags, &attr);
+ if (!w)
+ return 0;
+
+ /* search for end */
+ i++;
+ text_start = i;
+ while ((line_start = i) < size) {
+ while (i < size && data[i] != '\n')
+ i++;
+
+ w2 = is_codefence(data + line_start, i - line_start, &width2, &chr2);
+ if (w == w2 && width == width2 && chr == chr2 &&
+ is_empty(data + (line_start+w), i - (line_start+w)))
+ break;
+
+ i++;
+ }
+
+ text.data = data + text_start;
+ text.size = line_start - text_start;
+
+ if (doc->md.blockcode)
+ doc->md.blockcode(ob, text.size ? &text : NULL, lang.size ? &lang : NULL, attr.size ? &attr : NULL, &doc->data);
+
+ return i;
+}
+
+static size_t
+parse_blockcode(hoedown_buffer *ob, hoedown_document *doc, uint8_t *data, size_t size)
+{
+ size_t beg, end, pre;
+ hoedown_buffer *work = 0;
+ hoedown_buffer attr = { 0, 0, 0, 0, NULL, NULL, NULL };
+
+ work = newbuf(doc, 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))
+ hoedown_buffer_putc(work, '\n');
+ else hoedown_buffer_put(work, data + beg, end - beg);
+ }
+ beg = end;
+ }
+
+ while (work->size && work->data[work->size - 1] == '\n')
+ work->size -= 1;
+