diff options
| author | Alex Crichton <alex@alexcrichton.com> | 2013-09-23 15:35:09 -0700 |
|---|---|---|
| committer | Alex Crichton <alex@alexcrichton.com> | 2013-09-25 14:27:41 -0700 |
| commit | 6aba140fa738acea9d06e29e95abbfcc9d1cba5d (patch) | |
| tree | d5b6dd07428938dd8851002e9e374bae537895be /src/rt/sundown/html | |
| parent | af25f58ac3da45899ed65b3af965150c8a90dcda (diff) | |
rustdoc: Add sundown to src/rt/
This also starts compiling it in the same manner as linenoise, it's just bundled with librustrt directly, and we export just a few symbols out of it.
Diffstat (limited to 'src/rt/sundown/html')
| -rw-r--r-- | src/rt/sundown/html/houdini.h | 37 | ||||
| -rw-r--r-- | src/rt/sundown/html/houdini_href_e.c | 108 | ||||
| -rw-r--r-- | src/rt/sundown/html/houdini_html_e.c | 84 | ||||
| -rwxr-xr-x | src/rt/sundown/html/html.c | 635 | ||||
| -rw-r--r-- | src/rt/sundown/html/html.h | 77 | ||||
| -rw-r--r-- | src/rt/sundown/html/html_smartypants.c | 389 |
6 files changed, 1330 insertions, 0 deletions
diff --git a/src/rt/sundown/html/houdini.h b/src/rt/sundown/html/houdini.h new file mode 100644 index 00000000000..b4954c02f67 --- /dev/null +++ b/src/rt/sundown/html/houdini.h @@ -0,0 +1,37 @@ +#ifndef HOUDINI_H__ +#define HOUDINI_H__ + +#include "buffer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef HOUDINI_USE_LOCALE +# define _isxdigit(c) isxdigit(c) +# define _isdigit(c) isdigit(c) +#else +/* + * Helper _isdigit methods -- do not trust the current locale + * */ +# define _isxdigit(c) (strchr("0123456789ABCDEFabcdef", (c)) != NULL) +# define _isdigit(c) ((c) >= '0' && (c) <= '9') +#endif + +extern void houdini_escape_html(struct buf *ob, const uint8_t *src, size_t size); +extern void houdini_escape_html0(struct buf *ob, const uint8_t *src, size_t size, int secure); +extern void houdini_unescape_html(struct buf *ob, const uint8_t *src, size_t size); +extern void houdini_escape_xml(struct buf *ob, const uint8_t *src, size_t size); +extern void houdini_escape_uri(struct buf *ob, const uint8_t *src, size_t size); +extern void houdini_escape_url(struct buf *ob, const uint8_t *src, size_t size); +extern void houdini_escape_href(struct buf *ob, const uint8_t *src, size_t size); +extern void houdini_unescape_uri(struct buf *ob, const uint8_t *src, size_t size); +extern void houdini_unescape_url(struct buf *ob, const uint8_t *src, size_t size); +extern void houdini_escape_js(struct buf *ob, const uint8_t *src, size_t size); +extern void houdini_unescape_js(struct buf *ob, const uint8_t *src, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/rt/sundown/html/houdini_href_e.c b/src/rt/sundown/html/houdini_href_e.c new file mode 100644 index 00000000000..981b3b17e41 --- /dev/null +++ b/src/rt/sundown/html/houdini_href_e.c @@ -0,0 +1,108 @@ +#include <assert.h> +#include <stdio.h> +#include <string.h> + +#include "houdini.h" + +#define ESCAPE_GROW_FACTOR(x) (((x) * 12) / 10) + +/* + * The following characters will not be escaped: + * + * -_.+!*'(),%#@?=;:/,+&$ alphanum + * + * Note that this character set is the addition of: + * + * - The characters which are safe to be in an URL + * - The characters which are *not* safe to be in + * an URL because they are RESERVED characters. + * + * We asume (lazily) that any RESERVED char that + * appears inside an URL is actually meant to + * have its native function (i.e. as an URL + * component/separator) and hence needs no escaping. + * + * There are two exceptions: the chacters & (amp) + * and ' (single quote) do not appear in the table. + * They are meant to appear in the URL as components, + * yet they require special HTML-entity escaping + * to generate valid HTML markup. + * + * All other characters will be escaped to %XX. + * + */ +static const char HREF_SAFE[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +void +houdini_escape_href(struct buf *ob, const uint8_t *src, size_t size) +{ + static const char hex_chars[] = "0123456789ABCDEF"; + size_t i = 0, org; + char hex_str[3]; + + bufgrow(ob, ESCAPE_GROW_FACTOR(size)); + hex_str[0] = '%'; + + while (i < size) { + org = i; + while (i < size && HREF_SAFE[src[i]] != 0) + i++; + + if (i > org) + bufput(ob, src + org, i - org); + + /* escaping */ + if (i >= size) + break; + + switch (src[i]) { + /* amp appears all the time in URLs, but needs + * HTML-entity escaping to be inside an href */ + case '&': + BUFPUTSL(ob, "&"); + break; + + /* the single quote is a valid URL character + * according to the standard; it needs HTML + * entity escaping too */ + case '\'': + BUFPUTSL(ob, "'"); + break; + + /* the space can be escaped to %20 or a plus + * sign. we're going with the generic escape + * for now. the plus thing is more commonly seen + * when building GET strings */ +#if 0 + case ' ': + bufputc(ob, '+'); + break; +#endif + + /* every other character goes with a %XX escaping */ + default: + hex_str[1] = hex_chars[(src[i] >> 4) & 0xF]; + hex_str[2] = hex_chars[src[i] & 0xF]; + bufput(ob, hex_str, 3); + } + + i++; + } +} diff --git a/src/rt/sundown/html/houdini_html_e.c b/src/rt/sundown/html/houdini_html_e.c new file mode 100644 index 00000000000..d9bbf187d82 --- /dev/null +++ b/src/rt/sundown/html/houdini_html_e.c @@ -0,0 +1,84 @@ +#include <assert.h> +#include <stdio.h> +#include <string.h> + +#include "houdini.h" + +#define ESCAPE_GROW_FACTOR(x) (((x) * 12) / 10) /* this is very scientific, yes */ + +/** + * According to the OWASP rules: + * + * & --> & + * < --> < + * > --> > + * " --> " + * ' --> ' ' is not recommended + * / --> / forward slash is included as it helps end an HTML entity + * + */ +static const char HTML_ESCAPE_TABLE[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 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 const char *HTML_ESCAPES[] = { + "", + """, + "&", + "'", + "/", + "<", + ">" +}; + +void +houdini_escape_html0(struct buf *ob, const uint8_t *src, size_t size, int secure) +{ + size_t i = 0, org, esc = 0; + + bufgrow(ob, ESCAPE_GROW_FACTOR(size)); + + while (i < size) { + org = i; + while (i < size && (esc = HTML_ESCAPE_TABLE[src[i]]) == 0) + i++; + + if (i > org) + bufput(ob, src + org, i - org); + + /* escaping */ + if (i >= size) + break; + + /* The forward slash is only escaped in secure mode */ + if (src[i] == '/' && !secure) { + bufputc(ob, '/'); + } else { + bufputs(ob, HTML_ESCAPES[esc]); + } + + i++; + } +} + +void +houdini_escape_html(struct buf *ob, const uint8_t *src, size_t size) +{ + houdini_escape_html0(ob, src, size, 1); +} + diff --git a/src/rt/sundown/html/html.c b/src/rt/sundown/html/html.c new file mode 100755 index 00000000000..7f08ee8ef1b --- /dev/null +++ b/src/rt/sundown/html/html.c @@ -0,0 +1,635 @@ +/* + * 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> + +#include "houdini.h" + +#define USE_XHTML(opt) (opt->flags & HTML_USE_XHTML) + +int +sdhtml_is_tag(const uint8_t *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; +} + +static inline void escape_html(struct buf *ob, const uint8_t *source, size_t length) +{ + houdini_escape_html0(ob, source, length, 0); +} + +static inline void escape_href(struct buf *ob, const uint8_t *source, size_t length) +{ + houdini_escape_href(ob, source, length); +} + +/******************** + * GENERIC RENDERER * + ********************/ +static int +rndr_autolink(struct buf *ob, const 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:"); + escape_href(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) { + escape_html(ob, link->data + 7, link->size - 7); + } else { + escape_html(ob, link->data, link->size); + } + + BUFPUTSL(ob, "</a>"); + + return 1; +} + +static void +rndr_blockcode(struct buf *ob, const struct buf *text, const 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, ' '); + escape_html(ob, lang->data + org, i - org); + } + } + + BUFPUTSL(ob, "\">"); + } else + BUFPUTSL(ob, "<pre><code>"); + + if (text) + escape_html(ob, text->data, text->size); + + BUFPUTSL(ob, "</code></pre>\n"); +} + +static void +rndr_blockquote(struct buf *ob, const 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, const struct buf *text, void *opaque) +{ + BUFPUTSL(ob, "<code>"); + if (text) escape_html(ob, text->data, text->size); + BUFPUTSL(ob, "</code>"); + return 1; +} + +static int +rndr_strikethrough(struct buf *ob, const 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, const 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, const 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, const 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, const struct buf *link, const struct buf *title, const 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) + escape_href(ob, link->data, link->size); + + if (title && title->size) { + BUFPUTSL(ob, "\" title=\""); + escape_html(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, const 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, const struct buf *text, int flags, void *opaque) +{ + BUFPUTSL(ob, "<li>"); + if (text) { + size_t size = text->size; + while (size && text->data[size - 1] == '\n') + size--; + + bufput(ob, text->data, size); + } + BUFPUTSL(ob, "</li>\n"); +} + +static void +rndr_paragraph(struct buf *ob, const 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, const struct buf *text, void *opaque) +{ + size_t org, sz; + if (!text) return; + sz = text->size; + while (sz > 0 && text->data[sz - 1] == '\n') sz--; + org = 0; + while (org < sz && text->data[org] == '\n') org++; + 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, const 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, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque) +{ + struct html_renderopt *options = opaque; + if (!link || !link->size) return 0; + + BUFPUTSL(ob, "<img src=\""); + escape_href(ob, link->data, link->size); + BUFPUTSL(ob, "\" alt=\""); + + if (alt && alt->size) + escape_html(ob, alt->data, alt->size); + + if (title && title->size) { + BUFPUTSL(ob, "\" title=\""); + escape_html(ob, title->data, title->size); } + + bufputs(ob, USE_XHTML(options) ? "\"/>" : "\">"); + return 1; +} + +static int +rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque) +{ + struct html_renderopt *options = opaque; + + /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES + * It doens't see if there are any valid tags, just escape all of them. */ + if((options->flags & HTML_ESCAPE) != 0) { + escape_html(ob, text->data, text->size); + return 1; + } + + if ((options->flags & HTML_SKIP_HTML) != 0) + return 1; + + if ((options->flags & HTML_SKIP_STYLE) != 0 && + sdhtml_is_tag(text->data, text->size, "style")) + return 1; + + if ((options->flags & HTML_SKIP_LINKS) != 0 && + sdhtml_is_tag(text->data, text->size, "a")) + return 1; + + if ((options->flags & HTML_SKIP_IMAGES) != 0 && + sdhtml_is_tag(text->data, text->size, "img")) + return 1; + + bufput(ob, text->data, text->size); + return 1; +} + +static void +rndr_table(struct buf *ob, const struct buf *header, const 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, const 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, const 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, const 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, const struct buf *text, void *opaque) +{ + if (text) + escape_html(ob, text->data, text->size); +} + +static void +toc_header(struct buf *ob, const struct buf *text, int level, void *opaque) +{ + struct html_renderopt *options = opaque; + + /* set the level offset if this is the first header + * we're parsing for the document */ + if (options->toc_data.current_level == 0) { + options->toc_data.level_offset = level - 1; + } + level -= options->toc_data.level_offset; + + if (level > options->toc_data.current_level) { + while (level > options->toc_data.current_level) { + BUFPUTSL(ob, "<ul>\n<li>\n"); + options->toc_data.current_level++; + } + } else if (level < options->toc_data.current_level) { + BUFPUTSL(ob, "</li>\n"); + while (level < options->toc_data.current_level) { + BUFPUTSL(ob, "</ul>\n</li>\n"); + options->toc_data.current_level--; + } + BUFPUTSL(ob,"<li>\n"); + } else { + BUFPUTSL(ob,"</li>\n<li>\n"); + } + + bufprintf(ob, "<a href=\"#toc_%d\">", options->toc_data.header_count++); + if (text) + escape_html(ob, text->data, text->size); + BUFPUTSL(ob, "</a>\n"); +} + +static int +toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque) +{ + if (content && content->size) + bufput(ob, content->data, content->size); + return 1; +} + +static void +toc_finalize(struct buf *ob, void *opaque) +{ + struct html_renderopt *options = opaque; + + while (options->toc_data.current_level > 0) { + BUFPUTSL(ob, "</li>\n</ul>\n"); + options->toc_data.current_level--; + } +} + +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, + toc_link, + 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 || render_flags & HTML_ESCAPE) + callbacks->blockhtml = NULL; +} diff --git a/src/rt/sundown/html/html.h b/src/rt/sundown/html/html.h new file mode 100644 index 00000000000..4c8810d471c --- /dev/null +++ b/src/rt/sundown/html/html.h @@ -0,0 +1,77 @@ +/* + * 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> + +#ifdef __cplusplus +extern "C" { +#endif + +struct html_renderopt { + struct { + int header_count; + int current_level; + int level_offset; + } toc_data; + + unsigned int flags; + + /* extra callbacks */ + void (*link_attributes)(struct buf *ob, const 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 << 4), + HTML_SAFELINK = (1 << 5), + HTML_TOC = (1 << 6), + HTML_HARD_WRAP = (1 << 7), + HTML_USE_XHTML = (1 << 8), + HTML_ESCAPE = (1 << 9), +} html_render_mode; + +typedef enum { + HTML_TAG_NONE = 0, + HTML_TAG_OPEN, + HTML_TAG_CLOSE, +} html_tag; + +int +sdhtml_is_tag(const uint8_t *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, const uint8_t *text, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/rt/sundown/html/html_smartypants.c b/src/rt/sundown/html/html_smartypants.c new file mode 100644 index 00000000000..367c26aeb9c --- /dev/null +++ b/src/rt/sundown/html/html_smartypants.c @@ -0,0 +1,389 @@ +/* + * 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> + +#if defined(_WIN32) +#define snprintf _snprintf +#endif + +struct smartypants_data { + int in_squote; + int in_dquote; +}; + +static size_t smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); +static size_t smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); +static size_t smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); +static size_t smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); +static size_t smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); +static size_t smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); +static size_t smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); +static size_t smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); +static size_t smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); +static size_t smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); + +static size_t (*smartypants_cb_ptrs[]) + (struct buf *, struct smartypants_data *, uint8_t, const uint8_t *, 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 uint8_t 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(uint8_t c) +{ + return c == 0 || isspace(c) || ispunct(c); +} + +static int +smartypants_quotes(struct buf *ob, uint8_t previous_char, uint8_t next_char, uint8_t 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, uint8_t previous_char, const uint8_t *text, size_t size) +{ + if (size >= 2) { + uint8_t 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) { + uint8_t 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, uint8_t previous_char, const uint8_t *text, size_t size) +{ + if (size >= 3) { + uint8_t t1 = tolower(text[1]); + uint8_t 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, uint8_t previous_char, const uint8_t *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, uint8_t previous_char, const uint8_t *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, uint8_t previous_char, const uint8_t *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, uint8_t previous_char, const uint8_t *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, uint8_t previous_char, const uint8_t *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, uint8_t previous_char, const uint8_t *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, uint8_t previous_char, const uint8_t *text, size_t size) +{ + static const char *skip_tags[] = { + "pre", "code", "var", "samp", "kbd", "math", "script", "style" + }; + static const size_t skip_tags_count = 8; + + size_t tag, i = 0; + + while (i < size && text[i] != '>') + i++; + + for (tag = 0; tag < skip_tags_count; ++tag) { + if (sdhtml_is_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_is_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, uint8_t previous_char, const uint8_t *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 { + uint8_t c0; + const uint8_t *pattern; + const uint8_t *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, const uint8_t *text, size_t size) +{ + size_t i; + struct smartypants_data smrt = {0, 0}; + + if (!text) + return; + + bufgrow(ob, size); + + for (i = 0; i < size; ++i) { + size_t org; + uint8_t action = 0; + + org = i; + while (i < size && (action = smartypants_cb_chars[text[i]]) == 0) + i++; + + if (i > org) + bufput(ob, text + org, i - org); + + if (i < size) { + i += smartypants_cb_ptrs[(int)action] + (ob, &smrt, i ? text[i - 1] : 0, text + i, size - i); + } + } +} + + |
