diff --git a/html/html.c b/html/html.c index 51dbae1..e94e8b2 100755 --- a/html/html.c +++ b/html/html.c @@ -222,10 +222,15 @@ rndr_header(struct buf *ob, const struct buf *text, int level, void *opaque) if (ob->size) bufputc(ob, '\n'); - if (options->flags & HTML_TOC) - bufprintf(ob, "", level, options->toc_data.header_count++); - else + if (options->flags & HTML_TOC) { + bufprintf(ob, "toc_id_prefix) { + bufprintf(ob, options->toc_id_prefix); + } + bufprintf(ob, "toc_%d\">", options->toc_data.header_count++); + } else { bufprintf(ob, "", level); + } if (text) bufput(ob, text->data, text->size); bufprintf(ob, "\n", level); @@ -382,10 +387,121 @@ rndr_image(struct buf *ob, const struct buf *link, const struct buf *title, cons return 1; } - int +static void +rndr_html_tag(struct buf *ob, const struct buf *text, void *opaque, + char* tagname, char** whitelist, int tagtype) +{ + size_t i, x, z, in_str = 0, seen_equals = 0, done, reset; + struct buf *attr = bufnew(16); + char c; + + bufputc(ob, '<'); + + i = 1 + strlen(tagname); + + if(tagtype == HTML_TAG_CLOSE) { + bufputc(ob, '/'); + i += 1; + } + + bufputs(ob, tagname); + + if(tagtype != HTML_TAG_CLOSE) { + for(;i < text->size;i++) { + c = text->data[i]; + done = 0; + reset = 0; + + switch(c) { + case '>': + if(seen_equals && !in_str) { + done = 1; + reset = 1; + } else { + reset = 1; + } + break; + case '\'': + case '"': + if(!in_str) + in_str = c; + else if(in_str == c) + in_str = !in_str; + break; + default: + if(!in_str) { + switch(c) { + case ' ': + if(seen_equals) { + done = 1; + reset = 1; + } else + reset = 1; + break; + case '=': + if(seen_equals) { + reset = 1; + } else { + for(z=0; whitelist[z]; z++) { + if(strlen(whitelist[z]) != attr->size) + continue; + for(x=0;x < attr->size; x++) { + if(tolower(whitelist[z][x]) != tolower(attr->data[x])) + break; + } + if(x == attr->size) + seen_equals = 1; + } + if(!seen_equals) + reset = 1; + } + break; + } + } + } + + if(done) { + bufputc(ob, ' '); + bufput(ob, attr->data, attr->size); + } + + if(reset) { + seen_equals = 0; + in_str = 0; + bufreset(attr); + } else { + bufputc(attr, c); + } + } + } + + bufrelease(attr); + + bufputc(ob, '>'); + +} + +static int rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque) { struct html_renderopt *options = opaque; + char** whitelist = options->html_element_whitelist; + int i, tagtype; + + + /* Items on the whitelist ignore all other flags and just output */ + if (((options->flags & HTML_ALLOW_ELEMENT_WHITELIST) != 0) && whitelist) { + for(i = 0; whitelist[i]; i++) { + tagtype = sdhtml_is_tag(text->data, text->size, whitelist[i]); + if(tagtype != HTML_TAG_NONE) { + rndr_html_tag(ob, text, opaque, + whitelist[i], + options->html_attr_whitelist, + tagtype); + return 1; + } + } + } /* 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. */ @@ -516,7 +632,13 @@ toc_header(struct buf *ob, const struct buf *text, int level, void *opaque) BUFPUTSL(ob,"\n
  • \n"); } - bufprintf(ob, "", options->toc_data.header_count++); + bufprintf(ob, "toc_id_prefix) { + bufprintf(ob, options->toc_id_prefix); + } + + bufprintf(ob, "toc_%d\">", options->toc_data.header_count++); if (text) escape_html(ob, text->data, text->size); BUFPUTSL(ob, "\n"); @@ -530,6 +652,14 @@ toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const return 1; } +static void +reset_toc(struct buf *ob, void *opaque) +{ + struct html_renderopt *options = opaque; + + memset(&(options->toc_data), 0, sizeof(options->toc_data)); +} + static void toc_finalize(struct buf *ob, void *opaque) { @@ -539,6 +669,8 @@ toc_finalize(struct buf *ob, void *opaque) BUFPUTSL(ob, "
  • \n\n"); options->toc_data.current_level--; } + + reset_toc(ob, opaque); } void @@ -577,7 +709,7 @@ sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *optio }; memset(options, 0x0, sizeof(struct html_renderopt)); - options->flags = HTML_TOC; + options->flags = HTML_TOC | HTML_SKIP_HTML; memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks)); } @@ -614,7 +746,7 @@ sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options, rndr_normal_text, NULL, - NULL, + reset_toc, }; /* Prepare the options pointer */ diff --git a/html/html.h b/html/html.h index 4c8810d..105a8d5 100644 --- a/html/html.h +++ b/html/html.h @@ -32,7 +32,12 @@ struct html_renderopt { int level_offset; } toc_data; + char* toc_id_prefix; + unsigned int flags; + + char** html_element_whitelist; + char** html_attr_whitelist; /* extra callbacks */ void (*link_attributes)(struct buf *ob, const struct buf *url, void *self); @@ -49,6 +54,7 @@ typedef enum { HTML_HARD_WRAP = (1 << 7), HTML_USE_XHTML = (1 << 8), HTML_ESCAPE = (1 << 9), + HTML_ALLOW_ELEMENT_WHITELIST = (1 << 10), } html_render_mode; typedef enum { diff --git a/snudown.c b/snudown.c index b19cf76..38e249e 100644 --- a/snudown.c +++ b/snudown.c @@ -7,36 +7,64 @@ #define SNUDOWN_VERSION "1.0.6" +enum snudown_renderer_mode { + RENDERER_USERTEXT = 0, + RENDERER_WIKI, + RENDERER_COUNT +}; + struct snudown_renderopt { struct html_renderopt html; int nofollow; const char *target; }; -static struct module_state { +struct snudown_renderer { + struct sd_markdown* main_renderer; + struct sd_markdown* toc_renderer; + struct module_state* state; + struct module_state* toc_state; +}; + +struct module_state { struct sd_callbacks callbacks; struct snudown_renderopt options; -} _state; +}; + +static struct snudown_renderer sundown[RENDERER_COUNT]; -static struct sd_markdown* sundown = NULL; +static char* html_element_whitelist[] = { "tr", "th", "td", "table", "tbody", "thead", "tfoot", "caption", NULL }; +static char* html_attr_whitelist[] = {"colspan", "rowspan", "cellspacing", "cellpadding", "scope", NULL}; + +static struct module_state usertext_toc_state; +static struct module_state wiki_toc_state; +static struct module_state usertext_state; +static struct module_state wiki_state; /* The module doc strings */ PyDoc_STRVAR(snudown_module__doc__, "When does the narwhal bacon? At Sundown."); PyDoc_STRVAR(snudown_md__doc__, "Render a Markdown document"); -static const int snudown_md_flags = +static const unsigned int snudown_default_md_flags = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_SUPERSCRIPT | MKDEXT_AUTOLINK | MKDEXT_STRIKETHROUGH | MKDEXT_TABLES; -static const int snudown_render_flags = +static const unsigned int snudown_default_render_flags = HTML_SKIP_HTML | HTML_SKIP_IMAGES | HTML_SAFELINK | - HTML_ESCAPE | - HTML_USE_XHTML; + HTML_ESCAPE | + HTML_USE_XHTML; + +static const unsigned int snudown_wiki_render_flags = + HTML_SKIP_HTML | + HTML_SAFELINK | + HTML_ALLOW_ELEMENT_WHITELIST | + HTML_ESCAPE | + HTML_USE_XHTML; static void snudown_link_attr(struct buf *ob, const struct buf *link, void *opaque) @@ -53,29 +81,104 @@ snudown_link_attr(struct buf *ob, const struct buf *link, void *opaque) } } +static struct sd_markdown* custom_render(struct module_state* state, + const unsigned int renderflags, + const unsigned int markdownflags, + int toc_renderer) { + if(toc_renderer) { + sdhtml_toc_renderer(&state->callbacks, + (struct html_renderopt *)&state->options); + } else { + sdhtml_renderer(&state->callbacks, + (struct html_renderopt *)&state->options, + renderflags); + } + + state->options.html.link_attributes = &snudown_link_attr; + state->options.html.html_element_whitelist = html_element_whitelist; + state->options.html.html_attr_whitelist = html_attr_whitelist; + + return sd_markdown_new( + markdownflags, + 16, + &state->callbacks, + &state->options + ); +} + +void init_default_render(PyObject *module) { + PyModule_AddIntConstant(module, "RENDERER_USERTEXT", RENDERER_USERTEXT); + sundown[RENDERER_USERTEXT].main_renderer = custom_render(&usertext_state, snudown_default_render_flags, snudown_default_md_flags, 0); + sundown[RENDERER_USERTEXT].toc_renderer = custom_render(&usertext_toc_state, snudown_default_render_flags, snudown_default_md_flags, 1); + sundown[RENDERER_USERTEXT].state = &usertext_state; + sundown[RENDERER_USERTEXT].toc_state = &usertext_toc_state; +} + +void init_wiki_render(PyObject *module) { + PyModule_AddIntConstant(module, "RENDERER_WIKI", RENDERER_WIKI); + sundown[RENDERER_WIKI].main_renderer = custom_render(&wiki_state, snudown_wiki_render_flags, snudown_default_md_flags, 0); + sundown[RENDERER_WIKI].toc_renderer = custom_render(&wiki_toc_state, snudown_wiki_render_flags, snudown_default_md_flags, 1); + sundown[RENDERER_WIKI].state = &wiki_state; + sundown[RENDERER_WIKI].toc_state = &wiki_toc_state; +} + static PyObject * snudown_md(PyObject *self, PyObject *args, PyObject *kwargs) { - static char *kwlist[] = {"text", "nofollow", "target", NULL}; + static char *kwlist[] = {"text", "nofollow", "target", "toc_id_prefix", "renderer", "enable_toc", NULL}; struct buf ib, *ob; PyObject *py_result; const char* result_text; + int renderer = RENDERER_USERTEXT; + int enable_toc = 0; + struct snudown_renderer _snudown; + int nofollow = 0; + char* target = NULL; + char* toc_id_prefix = NULL; + unsigned int flags; memset(&ib, 0x0, sizeof(struct buf)); /* Parse arguments */ - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|iz", kwlist, - &ib.data, &ib.size, &_state.options.nofollow, &_state.options.target)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|izzii", kwlist, + &ib.data, &ib.size, &nofollow, + &target, &toc_id_prefix, &renderer, &enable_toc)) { return NULL; } - + + if(renderer < 0 || renderer >= RENDERER_COUNT) { + PyErr_SetString(PyExc_ValueError, "Invalid renderer"); + return NULL; + } + + _snudown = sundown[renderer]; + + struct snudown_renderopt *options = &(_snudown.state->options); + options->nofollow = nofollow; + options->target = target; + /* Output buffer */ ob = bufnew(128); - + + flags = options->html.flags; + + if (enable_toc) { + _snudown.toc_state->options.html.toc_id_prefix = toc_id_prefix; + sd_markdown_render(ob, ib.data, ib.size, _snudown.toc_renderer); + _snudown.toc_state->options.html.toc_id_prefix = NULL; + + options->html.flags |= HTML_TOC; + } + + options->html.toc_id_prefix = toc_id_prefix; + /* do the magic */ - sd_markdown_render(ob, ib.data, ib.size, sundown); - + sd_markdown_render(ob, ib.data, ib.size, _snudown.main_renderer); + + options->html.toc_id_prefix = NULL; + options->html.flags = flags; + /* make a Python string */ result_text = ""; if (ob->data) @@ -99,22 +202,10 @@ PyMODINIT_FUNC initsnudown(void) module = Py_InitModule3("snudown", snudown_methods, snudown_module__doc__); if (module == NULL) return; - - /* initialize the html renderer */ - sdhtml_renderer(&_state.callbacks, - (struct html_renderopt *)&_state.options, - snudown_render_flags); - - _state.options.html.link_attributes = &snudown_link_attr; - - /* initialize the markdown parser */ - sundown = sd_markdown_new( - snudown_md_flags, - 16, - &_state.callbacks, - &_state.options - ); - + + init_default_render(module); + init_wiki_render(module); + /* Version */ PyModule_AddStringConstant(module, "__version__", SNUDOWN_VERSION); }