diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 66f2f501..69f2147f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,11 @@ set_target_properties(md4c-html PROPERTIES ) target_link_libraries(md4c-html md4c) +# Build rules for example programs + +add_executable(md4c-html-example md4c-html-example.c) +target_link_libraries(md4c-html-example md4c-html) + # Install rules diff --git a/src/md4c-html-example.c b/src/md4c-html-example.c new file mode 100644 index 00000000..a3f85368 --- /dev/null +++ b/src/md4c-html-example.c @@ -0,0 +1,143 @@ +/* + * MD4C: Markdown parser for C + * (http://github.com/mity/md4c) + * + * Copyright (c) 2016-2021 Martin Mitas + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include "md4c-html.h" + +static const char example_markdown[] = + "# HTML example\n" + "\n" + "This example program shows how to use the `md_html_create` API.\n" + "\n" + "This program uses `` instead of `` for inline code blocks.\n"; + +typedef struct { + MD_PARSER parser; + MD_PARSER* html_renderer; + MD_SIZE html_renderer_size; + FILE* output_file; +} EXAMPLE_PARSER; + +static int example_enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) +{ + EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata; + p->html_renderer->enter_block(type, detail, p->html_renderer); + return 0; +} + +static int example_leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) +{ + EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata; + p->html_renderer->leave_block(type, detail, p->html_renderer); + return 0; +} + +static int example_enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) +{ + EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata; + if (type == MD_SPAN_CODE) { + fprintf(p->output_file, ""); + } else { + p->html_renderer->enter_span(type, detail, p->html_renderer); + } + return 0; +} + +static int example_leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata) +{ + EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata; + if (type == MD_SPAN_CODE) { + fprintf(p->output_file, ""); + } else { + p->html_renderer->leave_span(type, detail, p->html_renderer); + } + return 0; +} + +static int example_text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) +{ + EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata; + p->html_renderer->text(type, text, size, p->html_renderer); + return 0; +} + +static void example_process_output(const MD_CHAR* buffer, MD_SIZE buffer_size, void* userdata) +{ + EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata; + fwrite(buffer, sizeof(MD_CHAR), buffer_size, p->output_file); +} + +int main() +{ + int rc; + + unsigned parser_flags = 0; + unsigned renderer_flags = 0; + EXAMPLE_PARSER p = { + { + 0, + parser_flags, + example_enter_block_callback, + example_leave_block_callback, + example_enter_span_callback, + example_leave_span_callback, + example_text_callback, + NULL, + NULL, + }, + NULL, + 0, + stdout, + }; + + md_html_create(NULL, &p.html_renderer_size, + example_process_output, &p, + parser_flags, renderer_flags); + p.html_renderer = malloc(p.html_renderer_size); + rc = md_html_create(p.html_renderer, &p.html_renderer_size, + example_process_output, &p, + parser_flags, renderer_flags); + if (rc != 0) { + fprintf(stderr, "error: failed to create HTML renderer\n"); + return 1; + } + + rc = md_parse(example_markdown, strlen(example_markdown), &p.parser, &p); + if (rc != 0) { + fprintf(stderr, "error: failed to parse Markdown\n"); + return 1; + } + + rc = md_html_destroy(p.html_renderer, p.html_renderer_size); + if (rc != 0) { + fprintf(stderr, "error: failed to destroy HTML renderer\n"); + return 1; + } + free(p.html_renderer); + + return 0; +} diff --git a/src/md4c-html.c b/src/md4c-html.c index eb60eaab..412a3005 100644 --- a/src/md4c-html.c +++ b/src/md4c-html.c @@ -23,6 +23,8 @@ * IN THE SOFTWARE. */ +#include +#include #include #include @@ -49,6 +51,7 @@ typedef struct MD_HTML_tag MD_HTML; struct MD_HTML_tag { + MD_PARSER parser; void (*process_output)(const MD_CHAR*, MD_SIZE, void*); void* userdata; unsigned flags; @@ -533,19 +536,65 @@ md_html(const MD_CHAR* input, MD_SIZE input_size, void (*process_output)(const MD_CHAR*, MD_SIZE, void*), void* userdata, unsigned parser_flags, unsigned renderer_flags) { - MD_HTML render = { process_output, userdata, renderer_flags, 0, { 0 } }; + int rc; + MD_HTML renderer; + MD_SIZE renderer_size = sizeof(renderer); + rc = md_html_create(&renderer.parser, &renderer_size, + process_output, userdata, + parser_flags, renderer_flags); + assert(renderer_size == sizeof(renderer)); + if (rc != 0) { + return rc; + } + + /* Consider skipping UTF-8 byte order mark (BOM). */ + if(renderer_flags & MD_HTML_FLAG_SKIP_UTF8_BOM && sizeof(MD_CHAR) == 1) { + static const MD_CHAR bom[3] = { 0xef, 0xbb, 0xbf }; + if(input_size >= sizeof(bom) && memcmp(input, bom, sizeof(bom)) == 0) { + input += sizeof(bom); + input_size -= sizeof(bom); + } + } + + rc = md_parse(input, input_size, &renderer.parser, &renderer); + + md_html_destroy(&renderer.parser, renderer_size); + return rc; +} + +int md_html_create(MD_PARSER* out_renderer, MD_SIZE* out_renderer_size, + void (*process_output)(const MD_CHAR*, MD_SIZE, void*), + void* userdata, + unsigned parser_flags, unsigned renderer_flags) +{ + static_assert(alignof(MD_HTML) <= alignof(*out_renderer), + "Alignment of MD_HTML should be no more strict than alignment of md_parser"); + MD_HTML* renderer; int i; - MD_PARSER parser = { + if (out_renderer == NULL || *out_renderer_size < sizeof(MD_HTML)) { + *out_renderer_size = sizeof(MD_HTML); + return -1; + } + + renderer = (MD_HTML*)out_renderer; + *renderer = (MD_HTML){ + { + 0, + parser_flags, + enter_block_callback, + leave_block_callback, + enter_span_callback, + leave_span_callback, + text_callback, + debug_log_callback, + NULL + }, + process_output, + userdata, + renderer_flags, 0, - parser_flags, - enter_block_callback, - leave_block_callback, - enter_span_callback, - leave_span_callback, - text_callback, - debug_log_callback, - NULL + { 0 } }; /* Build map of characters which need escaping. */ @@ -553,21 +602,19 @@ md_html(const MD_CHAR* input, MD_SIZE input_size, unsigned char ch = (unsigned char) i; if(strchr("\"&<>", ch) != NULL) - render.escape_map[i] |= NEED_HTML_ESC_FLAG; + renderer->escape_map[i] |= NEED_HTML_ESC_FLAG; if(!ISALNUM(ch) && strchr("-_.+!*(),%#@?=;:/,+$", ch) == NULL) - render.escape_map[i] |= NEED_URL_ESC_FLAG; + renderer->escape_map[i] |= NEED_URL_ESC_FLAG; } - /* Consider skipping UTF-8 byte order mark (BOM). */ - if(renderer_flags & MD_HTML_FLAG_SKIP_UTF8_BOM && sizeof(MD_CHAR) == 1) { - static const MD_CHAR bom[3] = { 0xef, 0xbb, 0xbf }; - if(input_size >= sizeof(bom) && memcmp(input, bom, sizeof(bom)) == 0) { - input += sizeof(bom); - input_size -= sizeof(bom); - } - } + *out_renderer_size = sizeof(MD_HTML); + return 0; +} - return md_parse(input, input_size, &parser, (void*) &render); +int md_html_destroy(MD_PARSER* renderer, MD_SIZE renderer_size) +{ + assert(renderer_size == sizeof(MD_HTML)); + return 0; } diff --git a/src/md4c-html.h b/src/md4c-html.h index 1042d29c..47a62029 100644 --- a/src/md4c-html.h +++ b/src/md4c-html.h @@ -60,6 +60,56 @@ int md_html(const MD_CHAR* input, MD_SIZE input_size, void (*process_output)(const MD_CHAR*, MD_SIZE, void*), void* userdata, unsigned parser_flags, unsigned renderer_flags); +/* Create an MD_PARSER which renders Markdown into HTML. + * + * The caller is responsible for allocating enough memory to store the + * returned MD_PARSER. This size might be greater than sizeof(md_parser), so + * callers must ensure enough memory is allocated. To determine the size, + * first call md_html_create with *out_renderer_size equal to 0. + * + * Before deallcoating memory used for the returned MD_PARSER, call + * md_html_destroy. + * + * See md4c-html-example.c for example usage. + * + * Note only contents of tag is generated. Caller must generate + * HTML header/footer manually before/after using the parser + * returned by md_html_create. + * + * Param out_renderer is initialized if *out_renderer_size is large enough. + * Param out_renderer_size points to the size of the allocation pointed to + * by out_renderer (in chars). If *out_renderer_size is too small, + * md_html_create modifies *out_renderer_size with the expected size then + * returns -1. + * Callback process_output() gets called with chunks of HTML output. + * (Typical implementation may just output the bytes to a file or append to + * some buffer) as parsing occurs. + * Param userdata is just propgated back to process_output() callback. + * Param parser_flags are flags from md4c.h propagated to md_parse(). + * Param render_flags is bitmask of MD_HTML_FLAG_xxxx. + * + * Do not specify MD_HTML_FLAG_SKIP_UTF8_BOM in render_flag. Currently, + * md_html_create ignores this flag, but this behavior might change in the + * future. + * + * Returns -1 and modifies *out_renderer_size if *out_renderer_size is too + * small. + * Returns -1 and does not modify *out_renderer_size if another error + * occurs. + * Returns 0 and modifies *out_renderer_size on success. + */ +int md_html_create(MD_PARSER* out_renderer, MD_SIZE* out_renderer_size, + void (*process_output)(const MD_CHAR*, MD_SIZE, void*), + void* userdata, + unsigned parser_flags, unsigned renderer_flags); + +/* Clean up resources allocated by md_html_create. + * + * Returns -1 on error. + * Returns 0 on success. + */ +int md_html_destroy(MD_PARSER* renderer, MD_SIZE renderer_size); + #ifdef __cplusplus } /* extern "C" { */