Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to customize generated HTML #158

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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