Skip to content

Commit 0cbc6e9

Browse files
committed
Allow user to customize generated HTML
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.
1 parent da77b92 commit 0cbc6e9

File tree

4 files changed

+266
-21
lines changed

4 files changed

+266
-21
lines changed

src/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ set_target_properties(md4c-html PROPERTIES
2828
)
2929
target_link_libraries(md4c-html md4c)
3030

31+
# Build rules for example programs
32+
33+
add_executable(md4c-html-example md4c-html-example.c)
34+
target_link_libraries(md4c-html-example md4c-html)
35+
3136

3237
# Install rules
3338

src/md4c-html-example.c

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* MD4C: Markdown parser for C
3+
* (http://github.com/mity/md4c)
4+
*
5+
* Copyright (c) 2016-2021 Martin Mitas
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a
8+
* copy of this software and associated documentation files (the "Software"),
9+
* to deal in the Software without restriction, including without limitation
10+
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
11+
* and/or sell copies of the Software, and to permit persons to whom the
12+
* Software is furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18+
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23+
* IN THE SOFTWARE.
24+
*/
25+
26+
#include <string.h>
27+
#include <stdio.h>
28+
#include <stdlib.h>
29+
#include "md4c-html.h"
30+
31+
static const char example_markdown[] =
32+
"# HTML example\n"
33+
"\n"
34+
"This example program shows how to use the `md_html_create` API.\n"
35+
"\n"
36+
"This program uses `<tt>` instead of `<code>` for inline code blocks.\n";
37+
38+
typedef struct {
39+
MD_PARSER parser;
40+
MD_PARSER* html_renderer;
41+
MD_SIZE html_renderer_size;
42+
FILE* output_file;
43+
} EXAMPLE_PARSER;
44+
45+
static int example_enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
46+
{
47+
EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata;
48+
p->html_renderer->enter_block(type, detail, p->html_renderer);
49+
return 0;
50+
}
51+
52+
static int example_leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
53+
{
54+
EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata;
55+
p->html_renderer->leave_block(type, detail, p->html_renderer);
56+
return 0;
57+
}
58+
59+
static int example_enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
60+
{
61+
EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata;
62+
if (type == MD_SPAN_CODE) {
63+
fprintf(p->output_file, "<tt>");
64+
} else {
65+
p->html_renderer->enter_span(type, detail, p->html_renderer);
66+
}
67+
return 0;
68+
}
69+
70+
static int example_leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
71+
{
72+
EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata;
73+
if (type == MD_SPAN_CODE) {
74+
fprintf(p->output_file, "</tt>");
75+
} else {
76+
p->html_renderer->leave_span(type, detail, p->html_renderer);
77+
}
78+
return 0;
79+
}
80+
81+
static int example_text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata)
82+
{
83+
EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata;
84+
p->html_renderer->text(type, text, size, p->html_renderer);
85+
return 0;
86+
}
87+
88+
static void example_process_output(const MD_CHAR* buffer, MD_SIZE buffer_size, void* userdata)
89+
{
90+
EXAMPLE_PARSER* p = (EXAMPLE_PARSER*) userdata;
91+
fwrite(buffer, sizeof(MD_CHAR), buffer_size, p->output_file);
92+
}
93+
94+
int main()
95+
{
96+
int rc;
97+
98+
unsigned parser_flags = 0;
99+
unsigned renderer_flags = 0;
100+
EXAMPLE_PARSER p = {
101+
{
102+
0,
103+
parser_flags,
104+
example_enter_block_callback,
105+
example_leave_block_callback,
106+
example_enter_span_callback,
107+
example_leave_span_callback,
108+
example_text_callback,
109+
NULL,
110+
NULL,
111+
},
112+
NULL,
113+
0,
114+
stdout,
115+
};
116+
117+
md_html_create(NULL, &p.html_renderer_size,
118+
example_process_output, &p,
119+
parser_flags, renderer_flags);
120+
p.html_renderer = malloc(p.html_renderer_size);
121+
rc = md_html_create(p.html_renderer, &p.html_renderer_size,
122+
example_process_output, &p,
123+
parser_flags, renderer_flags);
124+
if (rc != 0) {
125+
fprintf(stderr, "error: failed to create HTML renderer\n");
126+
return 1;
127+
}
128+
129+
rc = md_parse(example_markdown, strlen(example_markdown), &p.parser, &p);
130+
if (rc != 0) {
131+
fprintf(stderr, "error: failed to parse Markdown\n");
132+
return 1;
133+
}
134+
135+
rc = md_html_destroy(p.html_renderer, p.html_renderer_size);
136+
if (rc != 0) {
137+
fprintf(stderr, "error: failed to destroy HTML renderer\n");
138+
return 1;
139+
}
140+
free(p.html_renderer);
141+
142+
return 0;
143+
}

src/md4c-html.c

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
* IN THE SOFTWARE.
2424
*/
2525

26+
#include <assert.h>
27+
#include <stdalign.h>
2628
#include <stdio.h>
2729
#include <string.h>
2830

@@ -49,6 +51,7 @@
4951

5052
typedef struct MD_HTML_tag MD_HTML;
5153
struct MD_HTML_tag {
54+
MD_PARSER parser;
5255
void (*process_output)(const MD_CHAR*, MD_SIZE, void*);
5356
void* userdata;
5457
unsigned flags;
@@ -533,41 +536,85 @@ md_html(const MD_CHAR* input, MD_SIZE input_size,
533536
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
534537
void* userdata, unsigned parser_flags, unsigned renderer_flags)
535538
{
536-
MD_HTML render = { process_output, userdata, renderer_flags, 0, { 0 } };
539+
int rc;
540+
MD_HTML renderer;
541+
MD_SIZE renderer_size = sizeof(renderer);
542+
rc = md_html_create(&renderer.parser, &renderer_size,
543+
process_output, userdata,
544+
parser_flags, renderer_flags);
545+
assert(renderer_size == sizeof(renderer));
546+
if (rc != 0) {
547+
return rc;
548+
}
549+
550+
/* Consider skipping UTF-8 byte order mark (BOM). */
551+
if(renderer_flags & MD_HTML_FLAG_SKIP_UTF8_BOM && sizeof(MD_CHAR) == 1) {
552+
static const MD_CHAR bom[3] = { 0xef, 0xbb, 0xbf };
553+
if(input_size >= sizeof(bom) && memcmp(input, bom, sizeof(bom)) == 0) {
554+
input += sizeof(bom);
555+
input_size -= sizeof(bom);
556+
}
557+
}
558+
559+
rc = md_parse(input, input_size, &renderer.parser, &renderer);
560+
561+
md_html_destroy(&renderer.parser, renderer_size);
562+
return rc;
563+
}
564+
565+
int md_html_create(MD_PARSER* out_renderer, MD_SIZE* out_renderer_size,
566+
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
567+
void* userdata,
568+
unsigned parser_flags, unsigned renderer_flags)
569+
{
570+
static_assert(alignof(MD_HTML) <= alignof(*out_renderer),
571+
"Alignment of MD_HTML should be no more strict than alignment of md_parser");
572+
MD_HTML* renderer;
537573
int i;
538574

539-
MD_PARSER parser = {
575+
if (out_renderer == NULL || *out_renderer_size < sizeof(MD_HTML)) {
576+
*out_renderer_size = sizeof(MD_HTML);
577+
return -1;
578+
}
579+
580+
renderer = (MD_HTML*)out_renderer;
581+
*renderer = (MD_HTML){
582+
{
583+
0,
584+
parser_flags,
585+
enter_block_callback,
586+
leave_block_callback,
587+
enter_span_callback,
588+
leave_span_callback,
589+
text_callback,
590+
debug_log_callback,
591+
NULL
592+
},
593+
process_output,
594+
userdata,
595+
renderer_flags,
540596
0,
541-
parser_flags,
542-
enter_block_callback,
543-
leave_block_callback,
544-
enter_span_callback,
545-
leave_span_callback,
546-
text_callback,
547-
debug_log_callback,
548-
NULL
597+
{ 0 }
549598
};
550599

551600
/* Build map of characters which need escaping. */
552601
for(i = 0; i < 256; i++) {
553602
unsigned char ch = (unsigned char) i;
554603

555604
if(strchr("\"&<>", ch) != NULL)
556-
render.escape_map[i] |= NEED_HTML_ESC_FLAG;
605+
renderer->escape_map[i] |= NEED_HTML_ESC_FLAG;
557606

558607
if(!ISALNUM(ch) && strchr("-_.+!*(),%#@?=;:/,+$", ch) == NULL)
559-
render.escape_map[i] |= NEED_URL_ESC_FLAG;
608+
renderer->escape_map[i] |= NEED_URL_ESC_FLAG;
560609
}
561610

562-
/* Consider skipping UTF-8 byte order mark (BOM). */
563-
if(renderer_flags & MD_HTML_FLAG_SKIP_UTF8_BOM && sizeof(MD_CHAR) == 1) {
564-
static const MD_CHAR bom[3] = { 0xef, 0xbb, 0xbf };
565-
if(input_size >= sizeof(bom) && memcmp(input, bom, sizeof(bom)) == 0) {
566-
input += sizeof(bom);
567-
input_size -= sizeof(bom);
568-
}
569-
}
611+
*out_renderer_size = sizeof(MD_HTML);
612+
return 0;
613+
}
570614

571-
return md_parse(input, input_size, &parser, (void*) &render);
615+
int md_html_destroy(MD_PARSER* renderer, MD_SIZE renderer_size)
616+
{
617+
assert(renderer_size == sizeof(MD_HTML));
618+
return 0;
572619
}
573620

src/md4c-html.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,56 @@ int md_html(const MD_CHAR* input, MD_SIZE input_size,
6060
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
6161
void* userdata, unsigned parser_flags, unsigned renderer_flags);
6262

63+
/* Create an MD_PARSER which renders Markdown into HTML.
64+
*
65+
* The caller is responsible for allocating enough memory to store the
66+
* returned MD_PARSER. This size might be greater than sizeof(md_parser), so
67+
* callers must ensure enough memory is allocated. To determine the size,
68+
* first call md_html_create with *out_renderer_size equal to 0.
69+
*
70+
* Before deallcoating memory used for the returned MD_PARSER, call
71+
* md_html_destroy.
72+
*
73+
* See md4c-html-example.c for example usage.
74+
*
75+
* Note only contents of <body> tag is generated. Caller must generate
76+
* HTML header/footer manually before/after using the parser
77+
* returned by md_html_create.
78+
*
79+
* Param out_renderer is initialized if *out_renderer_size is large enough.
80+
* Param out_renderer_size points to the size of the allocation pointed to
81+
* by out_renderer (in chars). If *out_renderer_size is too small,
82+
* md_html_create modifies *out_renderer_size with the expected size then
83+
* returns -1.
84+
* Callback process_output() gets called with chunks of HTML output.
85+
* (Typical implementation may just output the bytes to a file or append to
86+
* some buffer) as parsing occurs.
87+
* Param userdata is just propgated back to process_output() callback.
88+
* Param parser_flags are flags from md4c.h propagated to md_parse().
89+
* Param render_flags is bitmask of MD_HTML_FLAG_xxxx.
90+
*
91+
* Do not specify MD_HTML_FLAG_SKIP_UTF8_BOM in render_flag. Currently,
92+
* md_html_create ignores this flag, but this behavior might change in the
93+
* future.
94+
*
95+
* Returns -1 and modifies *out_renderer_size if *out_renderer_size is too
96+
* small.
97+
* Returns -1 and does not modify *out_renderer_size if another error
98+
* occurs.
99+
* Returns 0 and modifies *out_renderer_size on success.
100+
*/
101+
int md_html_create(MD_PARSER* out_renderer, MD_SIZE* out_renderer_size,
102+
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
103+
void* userdata,
104+
unsigned parser_flags, unsigned renderer_flags);
105+
106+
/* Clean up resources allocated by md_html_create.
107+
*
108+
* Returns -1 on error.
109+
* Returns 0 on success.
110+
*/
111+
int md_html_destroy(MD_PARSER* renderer, MD_SIZE renderer_size);
112+
63113

64114
#ifdef __cplusplus
65115
} /* extern "C" { */

0 commit comments

Comments
 (0)