mirror of
https://github.com/danbulant/markdown-wasm
synced 2026-06-06 08:10:28 +00:00
adds onCodeBlock callback re #11
This commit is contained in:
parent
da6fc41638
commit
2938af6f0b
11 changed files with 262 additions and 93 deletions
|
|
@ -13,16 +13,16 @@
|
||||||
<p>line 1
|
<p>line 1
|
||||||
line 2</p>
|
line 2</p>
|
||||||
<h2><a id="code-poetry" class="anchor" aria-hidden="true" href="#code-poetry"></a>Code & Poetry</h2>
|
<h2><a id="code-poetry" class="anchor" aria-hidden="true" href="#code-poetry"></a>Code & Poetry</h2>
|
||||||
<pre><code>You can also indent
|
<pre><code>YOU CAN ALSO INDENT
|
||||||
blocks to display
|
BLOCKS TO DISPLAY
|
||||||
code or poetry.
|
CODE OR POETRY.
|
||||||
|
|
||||||
Indented code/poetry blocks
|
INDENTED CODE/POETRY BLOCKS
|
||||||
can be hard-wrapped.
|
CAN BE HARD-WRAPPED.
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p><b>Or, wrap your code in three backticks:</b></p>
|
<p><b>Or, wrap your code in three backticks:</b></p>
|
||||||
<pre><code class="language-js">function codeBlocks() {
|
<pre><code class="language-js">FUNCTION CODEBLOCKS() {
|
||||||
return "Can be inserted"
|
RETURN "CAN BE INSERTED"
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3><a id="block-quotes" class="anchor" aria-hidden="true" href="#block-quotes"></a>Block Quotes</h3>
|
<h3><a id="block-quotes" class="anchor" aria-hidden="true" href="#block-quotes"></a>Block Quotes</h3>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
const md = require("../dist/markdown.node.js")
|
const md = require("../dist/markdown.node.js")
|
||||||
|
// const md = require("../build/debug/markdown.node.js")
|
||||||
|
|
||||||
const source = fs.readFileSync(__dirname + "/example.md")
|
const source = fs.readFileSync(__dirname + "/example.md")
|
||||||
const outbuf = md.parse(source, { bytes: true })
|
const outbuf = md.parse(source, {
|
||||||
|
bytes: true,
|
||||||
|
onCodeBlock(lang, body) {
|
||||||
|
console.log(`onCodeBlock (${lang})`)
|
||||||
|
return html_escape(body.toString().toUpperCase())
|
||||||
|
},
|
||||||
|
})
|
||||||
const outfile = __dirname + "/example.html"
|
const outfile = __dirname + "/example.html"
|
||||||
console.log("write", outfile)
|
console.log("write", outfile)
|
||||||
fs.writeFileSync(outfile, outbuf)
|
fs.writeFileSync(outfile, outbuf)
|
||||||
|
|
@ -24,3 +31,9 @@ if (process.argv.includes("-bench")) {
|
||||||
const timeSpent = Date.now() - timeStart
|
const timeSpent = Date.now() - timeStart
|
||||||
console.log(`benchmark end -- avg parse time: ${((timeSpent / ntotal) * 1000).toFixed(1)}us`)
|
console.log(`benchmark end -- avg parse time: ${((timeSpent / ntotal) * 1000).toFixed(1)}us`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function html_escape(str) {
|
||||||
|
return str.replace(/[&<>'"]/g, tag => ({
|
||||||
|
'&': '&','<': '<','>': '>',"'": ''','"': '"'
|
||||||
|
}[tag]))
|
||||||
|
}
|
||||||
|
|
|
||||||
21
markdown.d.ts
vendored
21
markdown.d.ts
vendored
|
|
@ -28,10 +28,31 @@ export interface ParseOptions {
|
||||||
*/
|
*/
|
||||||
bytes? :boolean
|
bytes? :boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onCodeBlock is an optional callback which if provided is called for each code block.
|
||||||
|
* langname holds the "language tag", if any, of the block.
|
||||||
|
*
|
||||||
|
* The returned value is inserted into the resulting HTML verbatim, without HTML escaping.
|
||||||
|
* Thus, you should take care of properly escaping any special HTML characters.
|
||||||
|
*
|
||||||
|
* If the function returns null or undefined, or an exception occurs, the body will be
|
||||||
|
* included as-is after going through HTML escaping.
|
||||||
|
*
|
||||||
|
* Note that use of this callback has an adverse impact on performance as it casues
|
||||||
|
* calls and data to be bridged between WASM and JS on every invocation.
|
||||||
|
*/
|
||||||
|
onCodeBlock? :(langname :string, body :UTF8Bytes) => Uint8Array|string|null|undefined
|
||||||
|
|
||||||
/** @depreceated use "bytes" instead (v1.1.1) */
|
/** @depreceated use "bytes" instead (v1.1.1) */
|
||||||
asMemoryView? :boolean
|
asMemoryView? :boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** UTF8Bytes is a Uint8Array representing UTF8 text */
|
||||||
|
export interface UTF8Bytes extends Uint8Array {
|
||||||
|
/** toString returns a UTF8 decoded string (lazily decoded and cached) */
|
||||||
|
toString() :string
|
||||||
|
}
|
||||||
|
|
||||||
/** Flags that customize Markdown parsing */
|
/** Flags that customize Markdown parsing */
|
||||||
export enum ParseFlags {
|
export enum ParseFlags {
|
||||||
/** In TEXT, collapse non-trivial whitespace into single ' ' */ COLLAPSE_WHITESPACE,
|
/** In TEXT, collapse non-trivial whitespace into single ' ' */ COLLAPSE_WHITESPACE,
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,14 @@ typedef int32_t i32;
|
||||||
#endif
|
#endif
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#define dlog(...) printf(__VA_ARGS__)
|
#define dlog(fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
#define dlog(...)
|
#define dlog(...)
|
||||||
#endif /* DEBUG > 0 */
|
#endif /* DEBUG > 0 */
|
||||||
|
|
||||||
|
#include "wbuf.h"
|
||||||
|
|
||||||
|
typedef int(*JSTextFilterFun)(
|
||||||
|
const char* metaptr, u32 metalen,
|
||||||
|
const char* inptr, u32 inlen,
|
||||||
|
const char** outptrp); // return outlen
|
||||||
|
|
|
||||||
172
src/fmt_html.c
172
src/fmt_html.c
|
|
@ -30,12 +30,12 @@
|
||||||
#include "fmt_html.h"
|
#include "fmt_html.h"
|
||||||
#include "md4c.h"
|
#include "md4c.h"
|
||||||
|
|
||||||
typedef struct HtmlRenderer_st {
|
// typedef struct FmtHTML_st {
|
||||||
WBuf* outbuf;
|
// WBuf* outbuf;
|
||||||
int imgnest;
|
// int imgnest;
|
||||||
int addanchor;
|
// int addanchor;
|
||||||
u32 flags;
|
// u32 flags;
|
||||||
} HtmlRenderer;
|
// } FmtHTML;
|
||||||
|
|
||||||
|
|
||||||
static char htmlEscapeMap[256] = {
|
static char htmlEscapeMap[256] = {
|
||||||
|
|
@ -61,20 +61,20 @@ static char htmlEscapeMap[256] = {
|
||||||
static const char ucReplacementUTF8[] = { 0xef, 0xbf, 0xbd };
|
static const char ucReplacementUTF8[] = { 0xef, 0xbf, 0xbd };
|
||||||
|
|
||||||
|
|
||||||
static inline void render_text(HtmlRenderer* r, const char* pch, size_t len) {
|
static inline void render_text(FmtHTML* r, const char* pch, size_t len) {
|
||||||
WBufAppendBytes(r->outbuf, pch, len);
|
WBufAppendBytes(r->outbuf, pch, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void render_literal(HtmlRenderer* r, const char* cs) {
|
static inline void render_literal(FmtHTML* r, const char* cs) {
|
||||||
WBufAppendBytes(r->outbuf, cs, strlen(cs));
|
WBufAppendBytes(r->outbuf, cs, strlen(cs));
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void render_char(HtmlRenderer* r, char c) {
|
static inline void render_char(FmtHTML* r, char c) {
|
||||||
WBufAppendc(r->outbuf, c);
|
WBufAppendc(r->outbuf, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void render_html_escaped(HtmlRenderer* r, const char* data, size_t size) {
|
static void render_html_escaped(FmtHTML* r, const char* data, size_t size) {
|
||||||
MD_OFFSET beg = 0;
|
MD_OFFSET beg = 0;
|
||||||
MD_OFFSET off = 0;
|
MD_OFFSET off = 0;
|
||||||
|
|
||||||
|
|
@ -167,7 +167,7 @@ static size_t WBufAppendSlug(WBuf* b, const char* pch, size_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void render_attribute(HtmlRenderer* r, const MD_ATTRIBUTE* attr) {
|
static void render_attribute(FmtHTML* r, const MD_ATTRIBUTE* attr) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; attr->substr_offsets[i] < attr->size; i++) {
|
for (i = 0; attr->substr_offsets[i] < attr->size; i++) {
|
||||||
MD_TEXTTYPE type = attr->substr_types[i];
|
MD_TEXTTYPE type = attr->substr_types[i];
|
||||||
|
|
@ -183,7 +183,7 @@ static void render_attribute(HtmlRenderer* r, const MD_ATTRIBUTE* attr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void render_open_ol_block(HtmlRenderer* r, const MD_BLOCK_OL_DETAIL* det) {
|
static void render_open_ol_block(FmtHTML* r, const MD_BLOCK_OL_DETAIL* det) {
|
||||||
if (det->start == 1) {
|
if (det->start == 1) {
|
||||||
render_literal(r, "<ol>\n");
|
render_literal(r, "<ol>\n");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -193,7 +193,7 @@ static void render_open_ol_block(HtmlRenderer* r, const MD_BLOCK_OL_DETAIL* det)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_open_li_block(HtmlRenderer* r, const MD_BLOCK_LI_DETAIL* det) {
|
static void render_open_li_block(FmtHTML* r, const MD_BLOCK_LI_DETAIL* det) {
|
||||||
if (det->is_task) {
|
if (det->is_task) {
|
||||||
render_literal(r, "<li class=\"task-list-item\"><input type=\"checkbox\" disabled");
|
render_literal(r, "<li class=\"task-list-item\"><input type=\"checkbox\" disabled");
|
||||||
if (det->task_mark == 'x' || det->task_mark == 'X') {
|
if (det->task_mark == 'x' || det->task_mark == 'X') {
|
||||||
|
|
@ -205,7 +205,7 @@ static void render_open_li_block(HtmlRenderer* r, const MD_BLOCK_LI_DETAIL* det)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_open_code_block(HtmlRenderer* r, const MD_BLOCK_CODE_DETAIL* det) {
|
static void render_open_code_block(FmtHTML* r, const MD_BLOCK_CODE_DETAIL* det) {
|
||||||
render_literal(r, "<pre><code");
|
render_literal(r, "<pre><code");
|
||||||
if (det->lang.text != NULL) {
|
if (det->lang.text != NULL) {
|
||||||
render_literal(r, " class=\"language-");
|
render_literal(r, " class=\"language-");
|
||||||
|
|
@ -213,9 +213,41 @@ static void render_open_code_block(HtmlRenderer* r, const MD_BLOCK_CODE_DETAIL*
|
||||||
render_char(r, '"');
|
render_char(r, '"');
|
||||||
}
|
}
|
||||||
render_char(r, '>');
|
render_char(r, '>');
|
||||||
|
r->codeBlockNest++;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_open_td_block(HtmlRenderer* r, bool isTH, const MD_BLOCK_TD_DETAIL* det) {
|
static void render_close_code_block(FmtHTML* r, const MD_BLOCK_CODE_DETAIL* det) {
|
||||||
|
dlog("end code block (lang \"%.*s\")", (int)det->lang.size, det->lang.text);
|
||||||
|
|
||||||
|
r->codeBlockNest--;
|
||||||
|
|
||||||
|
if (r->onCodeBlock) {
|
||||||
|
const char* text = r->tmpbuf.start;
|
||||||
|
size_t len = WBufLen(&r->tmpbuf);
|
||||||
|
|
||||||
|
int outlen = -1;
|
||||||
|
|
||||||
|
if (len <= 0x7FFFFFFF) {
|
||||||
|
const char* outptr = NULL;
|
||||||
|
outlen = r->onCodeBlock(det->lang.text, (u32)det->lang.size, text, (u32)len, &outptr);
|
||||||
|
if (outlen > 0 && outptr != NULL)
|
||||||
|
WBufAppendBytes(r->outbuf, outptr, (size_t)outlen);
|
||||||
|
if (outptr != NULL)
|
||||||
|
free((void*)outptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outlen < 0) {
|
||||||
|
// The function failed or opted out of taking care of formatting
|
||||||
|
render_html_escaped(r, text, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
WBufReset(&r->tmpbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
render_literal(r, "</code></pre>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void render_open_td_block(FmtHTML* r, bool isTH, const MD_BLOCK_TD_DETAIL* det) {
|
||||||
render_text(r, isTH ? "<th" : "<td", 3);
|
render_text(r, isTH ? "<th" : "<td", 3);
|
||||||
switch (det->align) {
|
switch (det->align) {
|
||||||
case MD_ALIGN_LEFT: render_literal(r, " align=\"left\">"); break;
|
case MD_ALIGN_LEFT: render_literal(r, " align=\"left\">"); break;
|
||||||
|
|
@ -225,7 +257,7 @@ static void render_open_td_block(HtmlRenderer* r, bool isTH, const MD_BLOCK_TD_D
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_open_a_span(HtmlRenderer* r, const MD_SPAN_A_DETAIL* det) {
|
static void render_open_a_span(FmtHTML* r, const MD_SPAN_A_DETAIL* det) {
|
||||||
render_literal(r, "<a href=\"");
|
render_literal(r, "<a href=\"");
|
||||||
render_attribute(r, &det->href);
|
render_attribute(r, &det->href);
|
||||||
if (det->title.text != NULL) {
|
if (det->title.text != NULL) {
|
||||||
|
|
@ -235,14 +267,14 @@ static void render_open_a_span(HtmlRenderer* r, const MD_SPAN_A_DETAIL* det) {
|
||||||
render_literal(r, "\">");
|
render_literal(r, "\">");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_open_img_span(HtmlRenderer* r, const MD_SPAN_IMG_DETAIL* det) {
|
static void render_open_img_span(FmtHTML* r, const MD_SPAN_IMG_DETAIL* det) {
|
||||||
render_literal(r, "<img src=\"");
|
render_literal(r, "<img src=\"");
|
||||||
render_attribute(r, &det->src);
|
render_attribute(r, &det->src);
|
||||||
render_literal(r, "\" alt=\"");
|
render_literal(r, "\" alt=\"");
|
||||||
r->imgnest++;
|
r->imgnest++;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_close_img_span(HtmlRenderer* r, const MD_SPAN_IMG_DETAIL* det) {
|
static void render_close_img_span(FmtHTML* r, const MD_SPAN_IMG_DETAIL* det) {
|
||||||
if(det->title.text != NULL) {
|
if(det->title.text != NULL) {
|
||||||
render_literal(r, "\" title=\"");
|
render_literal(r, "\" title=\"");
|
||||||
render_attribute(r, &det->title);
|
render_attribute(r, &det->title);
|
||||||
|
|
@ -251,7 +283,7 @@ static void render_close_img_span(HtmlRenderer* r, const MD_SPAN_IMG_DETAIL* det
|
||||||
r->imgnest--;
|
r->imgnest--;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_open_wikilink_span(HtmlRenderer* r, const MD_SPAN_WIKILINK_DETAIL* det) {
|
static void render_open_wikilink_span(FmtHTML* r, const MD_SPAN_WIKILINK_DETAIL* det) {
|
||||||
render_literal(r, "<x-wikilink data-target=\"");
|
render_literal(r, "<x-wikilink data-target=\"");
|
||||||
render_attribute(r, &det->target);
|
render_attribute(r, &det->target);
|
||||||
render_literal(r, "\">");
|
render_literal(r, "\">");
|
||||||
|
|
@ -266,30 +298,30 @@ static void render_open_wikilink_span(HtmlRenderer* r, const MD_SPAN_WIKILINK_DE
|
||||||
|
|
||||||
static int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) {
|
static int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) {
|
||||||
static const MD_CHAR* head[6] = { "<h1>", "<h2>", "<h3>", "<h4>", "<h5>", "<h6>" };
|
static const MD_CHAR* head[6] = { "<h1>", "<h2>", "<h3>", "<h4>", "<h5>", "<h6>" };
|
||||||
HtmlRenderer* r = (HtmlRenderer*) userdata;
|
FmtHTML* r = (FmtHTML*) userdata;
|
||||||
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case MD_BLOCK_DOC: /* noop */ break;
|
case MD_BLOCK_DOC: /* noop */ break;
|
||||||
case MD_BLOCK_QUOTE: render_literal(r, "<blockquote>\n"); break;
|
case MD_BLOCK_QUOTE: render_literal(r, "<blockquote>\n"); break;
|
||||||
case MD_BLOCK_UL: render_literal(r, "<ul>\n"); break;
|
case MD_BLOCK_UL: render_literal(r, "<ul>\n"); break;
|
||||||
case MD_BLOCK_OL: render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
|
case MD_BLOCK_OL: render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
|
||||||
case MD_BLOCK_LI: render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
|
case MD_BLOCK_LI: render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
|
||||||
case MD_BLOCK_HR: render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr/>\n" : "<hr>\n"); break;
|
case MD_BLOCK_HR: render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr/>\n" : "<hr>\n"); break;
|
||||||
case MD_BLOCK_H:
|
case MD_BLOCK_H:
|
||||||
{
|
{
|
||||||
render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]);
|
render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]);
|
||||||
r->addanchor = 1;
|
r->addanchor = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MD_BLOCK_CODE: render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail); break;
|
case MD_BLOCK_CODE: render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail); break;
|
||||||
case MD_BLOCK_HTML: /* noop */ break;
|
case MD_BLOCK_HTML: /* noop */ break;
|
||||||
case MD_BLOCK_P: render_literal(r, "<p>"); break;
|
case MD_BLOCK_P: render_literal(r, "<p>"); break;
|
||||||
case MD_BLOCK_TABLE: render_literal(r, "<table>\n"); break;
|
case MD_BLOCK_TABLE: render_literal(r, "<table>\n"); break;
|
||||||
case MD_BLOCK_THEAD: render_literal(r, "<thead>\n"); break;
|
case MD_BLOCK_THEAD: render_literal(r, "<thead>\n"); break;
|
||||||
case MD_BLOCK_TBODY: render_literal(r, "<tbody>\n"); break;
|
case MD_BLOCK_TBODY: render_literal(r, "<tbody>\n"); break;
|
||||||
case MD_BLOCK_TR: render_literal(r, "<tr>\n"); break;
|
case MD_BLOCK_TR: render_literal(r, "<tr>\n"); break;
|
||||||
case MD_BLOCK_TH: render_open_td_block(r, true, (MD_BLOCK_TD_DETAIL*)detail); break;
|
case MD_BLOCK_TH: render_open_td_block(r, true, (MD_BLOCK_TD_DETAIL*)detail); break;
|
||||||
case MD_BLOCK_TD: render_open_td_block(r, false, (MD_BLOCK_TD_DETAIL*)detail); break;
|
case MD_BLOCK_TD: render_open_td_block(r, false, (MD_BLOCK_TD_DETAIL*)detail); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -297,32 +329,32 @@ static int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
|
||||||
|
|
||||||
static int leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) {
|
static int leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) {
|
||||||
static const MD_CHAR* head[6] = { "</h1>\n", "</h2>\n", "</h3>\n", "</h4>\n", "</h5>\n", "</h6>\n" };
|
static const MD_CHAR* head[6] = { "</h1>\n", "</h2>\n", "</h3>\n", "</h4>\n", "</h5>\n", "</h6>\n" };
|
||||||
HtmlRenderer* r = (HtmlRenderer*) userdata;
|
FmtHTML* r = (FmtHTML*) userdata;
|
||||||
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case MD_BLOCK_DOC: /*noop*/ break;
|
case MD_BLOCK_DOC: /*noop*/ break;
|
||||||
case MD_BLOCK_QUOTE: render_literal(r, "</blockquote>\n"); break;
|
case MD_BLOCK_QUOTE: render_literal(r, "</blockquote>\n"); break;
|
||||||
case MD_BLOCK_UL: render_literal(r, "</ul>\n"); break;
|
case MD_BLOCK_UL: render_literal(r, "</ul>\n"); break;
|
||||||
case MD_BLOCK_OL: render_literal(r, "</ol>\n"); break;
|
case MD_BLOCK_OL: render_literal(r, "</ol>\n"); break;
|
||||||
case MD_BLOCK_LI: render_literal(r, "</li>\n"); break;
|
case MD_BLOCK_LI: render_literal(r, "</li>\n"); break;
|
||||||
case MD_BLOCK_HR: /*noop*/ break;
|
case MD_BLOCK_HR: /*noop*/ break;
|
||||||
case MD_BLOCK_H: render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
|
case MD_BLOCK_H: render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
|
||||||
case MD_BLOCK_CODE: render_literal(r, "</code></pre>\n"); break;
|
case MD_BLOCK_CODE: render_close_code_block(r, (const MD_BLOCK_CODE_DETAIL*)detail); break;
|
||||||
case MD_BLOCK_HTML: /* noop */ break;
|
case MD_BLOCK_HTML: /* noop */ break;
|
||||||
case MD_BLOCK_P: render_literal(r, "</p>\n"); break;
|
case MD_BLOCK_P: render_literal(r, "</p>\n"); break;
|
||||||
case MD_BLOCK_TABLE: render_literal(r, "</table>\n"); break;
|
case MD_BLOCK_TABLE: render_literal(r, "</table>\n"); break;
|
||||||
case MD_BLOCK_THEAD: render_literal(r, "</thead>\n"); break;
|
case MD_BLOCK_THEAD: render_literal(r, "</thead>\n"); break;
|
||||||
case MD_BLOCK_TBODY: render_literal(r, "</tbody>\n"); break;
|
case MD_BLOCK_TBODY: render_literal(r, "</tbody>\n"); break;
|
||||||
case MD_BLOCK_TR: render_literal(r, "</tr>\n"); break;
|
case MD_BLOCK_TR: render_literal(r, "</tr>\n"); break;
|
||||||
case MD_BLOCK_TH: render_literal(r, "</th>\n"); break;
|
case MD_BLOCK_TH: render_literal(r, "</th>\n"); break;
|
||||||
case MD_BLOCK_TD: render_literal(r, "</td>\n"); break;
|
case MD_BLOCK_TD: render_literal(r, "</td>\n"); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
static int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
||||||
HtmlRenderer* r = (HtmlRenderer*) userdata;
|
FmtHTML* r = (FmtHTML*) userdata;
|
||||||
|
|
||||||
if(r->imgnest > 0) {
|
if(r->imgnest > 0) {
|
||||||
/* We are inside a Markdown image label. Markdown allows to use any
|
/* We are inside a Markdown image label. Markdown allows to use any
|
||||||
|
|
@ -360,7 +392,7 @@ static int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
static int leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
||||||
HtmlRenderer* r = (HtmlRenderer*) userdata;
|
FmtHTML* r = (FmtHTML*) userdata;
|
||||||
|
|
||||||
if(r->imgnest > 0) {
|
if(r->imgnest > 0) {
|
||||||
/* Ditto as in enter_span_callback(), except we have to allow the
|
/* Ditto as in enter_span_callback(), except we have to allow the
|
||||||
|
|
@ -387,7 +419,12 @@ static int leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) {
|
static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) {
|
||||||
HtmlRenderer* r = (HtmlRenderer*) userdata;
|
FmtHTML* r = (FmtHTML*) userdata;
|
||||||
|
|
||||||
|
if (r->codeBlockNest && r->onCodeBlock) {
|
||||||
|
WBufAppendBytes(&r->tmpbuf, text, size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (r->addanchor) {
|
if (r->addanchor) {
|
||||||
r->addanchor = 0;
|
r->addanchor = 0;
|
||||||
|
|
@ -409,7 +446,7 @@ static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, vo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(type) {
|
switch (type) {
|
||||||
case MD_TEXT_NULLCHAR: render_text(r, ucReplacementUTF8, sizeof(ucReplacementUTF8)); break;
|
case MD_TEXT_NULLCHAR: render_text(r, ucReplacementUTF8, sizeof(ucReplacementUTF8)); break;
|
||||||
case MD_TEXT_BR:
|
case MD_TEXT_BR:
|
||||||
render_literal(
|
render_literal(
|
||||||
|
|
@ -435,18 +472,15 @@ static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, vo
|
||||||
// dlog("MD4C: %s\n", msg);
|
// dlog("MD4C: %s\n", msg);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
int fmt_html(
|
int fmt_html(const MD_CHAR* input, MD_SIZE input_size, FmtHTML* fmt) {
|
||||||
const MD_CHAR* input,
|
fmt->imgnest = 0;
|
||||||
MD_SIZE input_size,
|
fmt->addanchor = 0;
|
||||||
WBuf* outbuf,
|
fmt->codeBlockNest = 0;
|
||||||
u32 parser_flags,
|
fmt->tmpbuf = (WBuf){0};
|
||||||
u32 render_flags
|
|
||||||
) {
|
|
||||||
HtmlRenderer render = { outbuf, 0, 0, render_flags };
|
|
||||||
|
|
||||||
MD_PARSER parser = {
|
MD_PARSER parser = {
|
||||||
0,
|
0,
|
||||||
parser_flags,
|
fmt->parserFlags,
|
||||||
enter_block_callback,
|
enter_block_callback,
|
||||||
leave_block_callback,
|
leave_block_callback,
|
||||||
enter_span_callback,
|
enter_span_callback,
|
||||||
|
|
@ -456,5 +490,11 @@ int fmt_html(
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
return md_parse(input, input_size, &parser, (void*) &render);
|
WBufInit(&fmt->tmpbuf);
|
||||||
|
|
||||||
|
int res = md_parse(input, input_size, &parser, (void*)fmt);
|
||||||
|
|
||||||
|
WBufFree(&fmt->tmpbuf);
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,20 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "wbuf.h"
|
|
||||||
|
|
||||||
#define MD_HTML_FLAG_XHTML 0x0008 // instead of e.g. <br>, generate <br/>
|
#define MD_HTML_FLAG_XHTML 0x0008 // instead of e.g. <br>, generate <br/>
|
||||||
|
|
||||||
int fmt_html(const char* input, u32 inputlen, WBuf* outbuf, u32 parserFlags, u32 renderFlags);
|
typedef struct FmtHTML {
|
||||||
|
u32 flags; // MD_HTML_FLAG_*
|
||||||
|
u32 parserFlags; // passed along to md_parse
|
||||||
|
WBuf* outbuf;
|
||||||
|
|
||||||
|
// optional callbacks
|
||||||
|
JSTextFilterFun onCodeBlock;
|
||||||
|
|
||||||
|
// internal state
|
||||||
|
int imgnest;
|
||||||
|
int addanchor;
|
||||||
|
int codeBlockNest;
|
||||||
|
WBuf tmpbuf;
|
||||||
|
} FmtHTML;
|
||||||
|
|
||||||
|
int fmt_html(const char* input, u32 inputlen, FmtHTML* fmt);
|
||||||
|
|
|
||||||
23
src/md.c
23
src/md.c
|
|
@ -1,7 +1,6 @@
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "wlib.h"
|
#include "wlib.h"
|
||||||
#include "wbuf.h"
|
|
||||||
#include "fmt_html.h"
|
#include "fmt_html.h"
|
||||||
// #include "fmt_json.h"
|
// #include "fmt_json.h"
|
||||||
|
|
||||||
|
|
@ -20,7 +19,7 @@ typedef enum ErrorCode {
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
void __attribute__((constructor)) init() {
|
void __attribute__((constructor)) init() {
|
||||||
dlog("WASM INIT\n");
|
dlog("WASM INIT");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -35,21 +34,27 @@ export size_t parseUTF8(
|
||||||
u32 inbuflen,
|
u32 inbuflen,
|
||||||
u32 parser_flags,
|
u32 parser_flags,
|
||||||
OutputFlags outflags,
|
OutputFlags outflags,
|
||||||
const char** outptr
|
const char** outptr,
|
||||||
|
JSTextFilterFun onCodeBlock
|
||||||
) {
|
) {
|
||||||
dlog("parseUTF8 called with inbufptr=%p inbuflen=%u\n", inbufptr, inbuflen);
|
dlog("parseUTF8 called with inbufptr=%p inbuflen=%u", inbufptr, inbuflen);
|
||||||
|
|
||||||
WBufReset(&outbuf);
|
WBufReset(&outbuf);
|
||||||
|
|
||||||
if (outflags & OutputFlagHTML) {
|
if (outflags & OutputFlagHTML) {
|
||||||
WBufReserve(&outbuf, inbuflen * 2); // approximate output size to minimize reallocations
|
WBufReserve(&outbuf, inbuflen * 2); // approximate output size to minimize reallocations
|
||||||
|
|
||||||
u32 render_flags = 0;
|
FmtHTML fmt = {
|
||||||
if (outflags & OutputFlagXHTML) {
|
.flags = 0,
|
||||||
render_flags |= MD_HTML_FLAG_XHTML;
|
.parserFlags = parser_flags,
|
||||||
}
|
.outbuf = &outbuf,
|
||||||
|
.onCodeBlock = onCodeBlock,
|
||||||
|
};
|
||||||
|
|
||||||
if (fmt_html(inbufptr, inbuflen, &outbuf, parser_flags, render_flags) != 0) {
|
if (outflags & OutputFlagXHTML)
|
||||||
|
fmt.flags |= MD_HTML_FLAG_XHTML;
|
||||||
|
|
||||||
|
if (fmt_html(inbufptr, inbuflen, &fmt) != 0) {
|
||||||
// fmt_html returns status of md_parse which only fails in extreme cases
|
// fmt_html returns status of md_parse which only fails in extreme cases
|
||||||
// like when out of memory. md4c does not provide error codes or error messages.
|
// like when out of memory. md4c does not provide error codes or error messages.
|
||||||
WErrSet(ERR_MD_PARSE, "md parser error");
|
WErrSet(ERR_MD_PARSE, "md parser error");
|
||||||
|
|
|
||||||
76
src/md.js
76
src/md.js
|
|
@ -1,4 +1,10 @@
|
||||||
import { utf8, withTmpBytePtr, withOutPtr, werrCheck } from "./wlib"
|
import {
|
||||||
|
utf8,
|
||||||
|
withTmpBytePtr,
|
||||||
|
withOutPtr,
|
||||||
|
werrCheck,
|
||||||
|
mallocbuf,
|
||||||
|
} from "./wlib"
|
||||||
|
|
||||||
export const ready = Module.ready
|
export const ready = Module.ready
|
||||||
|
|
||||||
|
|
@ -41,6 +47,7 @@ const OutputFlags = {
|
||||||
XHTML: 1 << 1, // Output XHTML (only has effect with HTML flag set)
|
XHTML: 1 << 1, // Output XHTML (only has effect with HTML flag set)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function parse(source, options) {
|
export function parse(source, options) {
|
||||||
options = options || {}
|
options = options || {}
|
||||||
|
|
||||||
|
|
@ -66,11 +73,16 @@ export function parse(source, options) {
|
||||||
throw new Error(`invalid format "${options.format}"`)
|
throw new Error(`invalid format "${options.format}"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
let buf = typeof source == "string" ? utf8.encode(source) : source
|
let onCodeBlockPtr = options.onCodeBlock ? create_onCodeBlock_fn(options.onCodeBlock) : 0
|
||||||
|
|
||||||
|
let buf = as_byte_array(source)
|
||||||
let outbuf = withOutPtr(outptr => withTmpBytePtr(buf, (inptr, inlen) =>
|
let outbuf = withOutPtr(outptr => withTmpBytePtr(buf, (inptr, inlen) =>
|
||||||
_parseUTF8(inptr, inlen, parseFlags, outputFlags, outptr)
|
_parseUTF8(inptr, inlen, parseFlags, outputFlags, outptr, onCodeBlockPtr)
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if (options.onCodeBlock)
|
||||||
|
removeFunction(onCodeBlockPtr)
|
||||||
|
|
||||||
// check for error and throw if needed
|
// check for error and throw if needed
|
||||||
werrCheck()
|
werrCheck()
|
||||||
|
|
||||||
|
|
@ -79,8 +91,62 @@ export function parse(source, options) {
|
||||||
// console.log(utf8.decode(outbuf))
|
// console.log(utf8.decode(outbuf))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (options.bytes || options.asMemoryView) {
|
if (options.bytes || options.asMemoryView)
|
||||||
return outbuf
|
return outbuf
|
||||||
}
|
|
||||||
return utf8.decode(outbuf)
|
return utf8.decode(outbuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function create_onCodeBlock_fn(onCodeBlock) {
|
||||||
|
// See https://emscripten.org/docs/porting/connecting_cpp_and_javascript/
|
||||||
|
// Interacting-with-code.html#calling-javascript-functions-as-function-pointers-from-c
|
||||||
|
//
|
||||||
|
// Function's C type: JSTextFilterFun
|
||||||
|
// (metaptr ptr, metalen ptr, inptr ptr, inlen ptr, outptr ptr) -> outlen int
|
||||||
|
const fnptr = addFunction(function(metaptr, metalen, inptr, inlen, outptr) {
|
||||||
|
try {
|
||||||
|
// lang is the "language" tag, if any, provided with the code block
|
||||||
|
const lang = metalen > 0 ? utf8.decode(HEAPU8.subarray(metaptr, metaptr + metalen)) : ""
|
||||||
|
|
||||||
|
// body is a view into heap memory of the segment of source (UTF8 bytes)
|
||||||
|
const body = HEAPU8.subarray(inptr, inptr + inlen)
|
||||||
|
let bodystr = undefined
|
||||||
|
body.toString = () => (bodystr || (bodystr = utf8.decode(body)))
|
||||||
|
|
||||||
|
// result is the result from the onCodeBlock function
|
||||||
|
let result = null
|
||||||
|
result = onCodeBlock(lang, body)
|
||||||
|
|
||||||
|
if (result === null || result === undefined) {
|
||||||
|
// Callback indicates that it does not wish to filter.
|
||||||
|
// The md.c implementation will html-encode the body.
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
let resbuf = as_byte_array(result)
|
||||||
|
if (resbuf.length > 0) {
|
||||||
|
// copy resbuf to WASM heap memory
|
||||||
|
const resptr = mallocbuf(resbuf, resbuf.length)
|
||||||
|
// write pointer value
|
||||||
|
HEAPU32[outptr >> 2 /* == outptr / 4 */] = resptr
|
||||||
|
// Note: fmt_html.c calls free(resptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resbuf.length
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`error in markdown onCodeBlock callback: ${err.stack||err}`)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}, "iiiiii")
|
||||||
|
return fnptr
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function as_byte_array(something) {
|
||||||
|
if (typeof something == "string")
|
||||||
|
return utf8.encode(something)
|
||||||
|
if (something instanceof Uint8Array)
|
||||||
|
return something
|
||||||
|
return new Uint8Array(something)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "wbuf.h"
|
#include "common.h"
|
||||||
|
|
||||||
void WBufInit(WBuf* b) {
|
void WBufInit(WBuf* b) {
|
||||||
b->start = 0;
|
b->start = 0;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
typedef struct WBuf_s {
|
typedef struct WBuf_s {
|
||||||
char* start; // pointer to start of data
|
char* start; // pointer to start of data
|
||||||
|
|
|
||||||
4
wasmc.js
4
wasmc.js
|
|
@ -23,6 +23,10 @@ const m = {
|
||||||
] : [
|
] : [
|
||||||
// release flags
|
// release flags
|
||||||
]),
|
]),
|
||||||
|
lflags: [
|
||||||
|
// force inclusion of addFunction & removeFunction in release builds (emcc bug?)
|
||||||
|
"-s","EXPORTED_RUNTIME_METHODS=addFunction,removeFunction",
|
||||||
|
],
|
||||||
constants: {
|
constants: {
|
||||||
VERSION: package.version,
|
VERSION: package.version,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue