Skip to content

Commit

Permalink
Allow user to customize generated HTML
Browse files Browse the repository at this point in the history
md_html allows very little customization. If users want to use <tt>
instead of <code>, for example, they need to reimplement md_html
entirely or post-process md_html's output.

Expose some of md_html's internals to make it easier for users to
customize md_html's output.
  • Loading branch information
strager committed May 6, 2021
1 parent da77b92 commit 5d4c578
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 21 deletions.
5 changes: 5 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
143 changes: 143 additions & 0 deletions src/md4c-html-example.c
Original file line number Diff line number Diff line change
@@ -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 <string.h>
#include <stdio.h>
#include <stdlib.h>
#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 `<tt>` instead of `<code>` 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, "<tt>");
} 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, "</tt>");
} 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;
}
90 changes: 69 additions & 21 deletions src/md4c-html.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* IN THE SOFTWARE.
*/

#include <assert.h>
#include <stdio.h>
#include <string.h>

Expand All @@ -49,6 +50,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;
Expand Down Expand Up @@ -533,41 +535,87 @@ 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)
{
#if __STDC_VERSION__ >= 201112L
static_assert(_Alignof(MD_HTML) <= _Alignof(MD_PARSER),
"Alignment of MD_HTML should be no more strict than alignment of md_parser");
#endif
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. */
for(i = 0; i < 256; i++) {
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;
}

50 changes: 50 additions & 0 deletions src/md4c-html.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <body> 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" { */
Expand Down

0 comments on commit 5d4c578

Please sign in to comment.