35#define CHECK(cond, msg) \
38 n_log(LOG_ERR, "FAIL: %s", (msg)); \
61 for (i = 0; i < inl->
n_spans; i++) {
63 size_t n = t ? strlen(t) : 0;
66 memcpy(out + w, t, n);
95 "bold+italic+code seen");
103 int link = 0, js_literal = 1;
110 CHECK(s->
url && strcmp(s->
url,
"https://e.org") == 0,
111 "http url captured");
113 if (s->
url && strstr(s->
url,
"javascript"))
116 CHECK(link,
"http link recognised");
117 CHECK(js_literal,
"javascript: scheme not turned into a link");
124 int quote = 0, code = 0, ul = 0;
134 CHECK(quote && code && ul,
"quote + code block + 2-item list");
143 "ordered list of 3");
151 "has_markup false for plain");
153 "unclosed marker is not markup");
171 "heading is a paragraph in the default subset");
180 CHECK(ok && strcmp(buf,
"Title") == 0,
"flagged heading, level 1, hashes stripped");
187 "six hashes is a level 6 heading");
205 "table is a paragraph in the default subset");
215 CHECK(ok,
"flagged table: 2 columns, a header and two body rows");
219 "alignment read from the rule row");
221 CHECK(strcmp(buf,
"Effect") == 0,
"header cell (0,1)");
223 CHECK(strcmp(buf,
"H") == 0,
"body cell (2,0)");
233 "table without edge pipes, centred second column");
242 "pipes with no rule row stay a paragraph");
251 "short and long rows are squared off to the header's width");
258 "a row of bare pipes parses and does not crash");
271 CHECK(ok,
"a wrapped list item does not end its list");
274 CHECK(strcmp(buf,
"two carries on onto a second line") == 0,
275 "the continuation joins with a space, indent stripped");
284 "a block start ends the item even when indented text preceded it");
290#define IA N_MARKDOWN_INLINE_ALL
297 {
"see https://x.org/a_b_c today",
"https://x.org/a_b_c"},
298 {
"see https://x.org.",
"https://x.org"},
299 {
"(https://x.org/w)",
"https://x.org/w"},
300 {
"https://en.wikipedia.org/wiki/C_(lang)",
"https://en.wikipedia.org/wiki/C_(lang)"},
301 {
"wow https://x.org!?",
"https://x.org"},
302 {
"<http://a>",
"http://a"},
303 {
"<https://x.org/q?a=1&b=2>",
"https://x.org/q?a=1&b=2"},
304 {
"HTTPS://X.ORG/P",
"HTTPS://X.ORG/P"},
305 {
"xhttps://x.org", NULL},
307 {
"<https://>", NULL},
308 {
"ftp://x.org", NULL},
309 {
"`https://x.org`", NULL},
310 {
"<javascript:alert(1)>", NULL},
313 for (k = 0; k <
sizeof(al) /
sizeof(al[0]); k++) {
315 const char* got = NULL;
322 "an autolink's text is its url");
325 CHECK(got && strcmp(got, al[k].url) == 0, al[k].in);
327 CHECK(got == NULL, al[k].in);
334 CHECK(strcmp(buf,
"see https://x.org.") == 0,
335 "no character is lost around an autolink");
343 "an explicit link is not re-linked");
352 "~~x~~ is a strike span");
358 "strike nests with bold");
362 CHECK(strcmp(buf,
"~~~~ and ~one~") == 0 &&
364 "an empty or single-tilde strike is text");
366 d =
parse(
"a ~~gone~~ b");
376 strcmp(buf,
"*not italic* and # not \\a") == 0,
377 "a backslash makes punctuation literal, and only punctuation");
380 "an escape is markup: the plain path would show it");
382 "but not unless asked for");
389 "the default parse links nothing bare");
399 const char* lit[] = {
"an **unclosed run",
"a ** b",
"a **** b",
401 for (k = 0; k <
sizeof(lit) /
sizeof(lit[0]); k++) {
415 "non-empty emphasis still works");
426 "a heading, a list item or a quote after a table is not a row");
430 "an ordinary line with a pipe is still a row");
439 const char* lit[] = {
":throwing_kiss: :wrapped_present: :two_hearts:",
440 "snake_case_name and other_name",
441 "café_au_lait_x",
"a__b__c",
"x_y _z"};
442 for (k = 0; k <
sizeof(lit) /
sizeof(lit[0]); k++) {
455 CHECK(strcmp(buf,
"a_b c and x_y and abc") == 0 &&
458 "word-boundary underscores still emphasise; '*' is unchanged");
466 "punctuation is a boundary");
475 "a heading is markup when asked for");
477 "the default subset still counts under the flags");
static const char * row(N_GUI_TREE *tree, int index)
the text of a visible row, "" past the end
static n_http_req_status parse(const char *s, n_http_request *out)
static n_markdown_doc parse_ex(const char *s, unsigned flags)
static void flow_text(const n_markdown_inline *inl, char *out, size_t cap)
static void cell_text(const n_markdown_block *b, size_t row, size_t col, char *out, size_t cap)
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
#define LOG_NOTICE
normal but significant condition
void n_markdown_free(n_markdown_doc *doc)
free everything a n_markdown_doc owns and zero it
int n_markdown_has_markup_ex(const char *text, size_t len, unsigned flags)
1 if text contains any markup the given flags would render, else 0
void n_markdown_parse(const char *text, size_t len, n_markdown_doc *out)
parse text (len bytes, plain UTF-8) into out
void n_markdown_parse_ex(const char *text, size_t len, unsigned flags, n_markdown_doc *out)
parse text into out, choosing which optional block kinds to recognise
int n_markdown_has_markup(const char *text, size_t len)
1 if text contains any renderable markup, else 0
Parse a SAFE, strict subset of Markdown into a block / styled-span model.
#define N_MARKDOWN_CODE
inline code style flag (carries no emphasis)
#define N_MARKDOWN_ALIGN_CENTER
what "|:---:|" asks for.
#define N_MARKDOWN_ALIGN_RIGHT
what "|---:|" asks for.
#define N_MARKDOWN_ITALIC
italic inline style flag
#define N_MARKDOWN_ALL
every block kind this parser knows.
#define N_MARKDOWN_LINK
[text](http(s)://url) style flag; url is set only when this is present
#define N_MARKDOWN_BOLD
bold inline style flag
n_markdown_inline * items
#define N_MARKDOWN_TABLES
n_markdown_parse_ex flag: recognise GitHub-style pipe tables.
@ N_MARKDOWN_HEADING
"## " heading (inl, level)
@ N_MARKDOWN_OL
ordered list (items)
@ N_MARKDOWN_TABLE
pipe table (items, n_cols)
@ N_MARKDOWN_UL
unordered list (items)
@ N_MARKDOWN_CODE_BLOCK
``` fenced code (raw code)
@ N_MARKDOWN_PARA
paragraph
#define N_MARKDOWN_STRIKE
strike inline style flag (only with N_MARKDOWN_STRIKETHROUGH)
#define N_MARKDOWN_HEADINGS
n_markdown_parse_ex flag: recognise "# " to "###### " headings.
int kind
enum n_markdown_block_kind
unsigned char * align
TABLE: N_MARKDOWN_ALIGN_* per column, n_cols entries, owned.
#define N_MARKDOWN_ALIGN_LEFT
table column alignment: the default, and what "|:---|" asks for.
n_markdown_block * blocks
size_t n_cols
TABLE: cells per row, always at least 1.
int level
HEADING: 1 to 6.
A parsed document: an owned array of blocks.
One flow of inline runs (a paragraph, a quote, or one list item).
One inline run: visible text, a style bitmask, and (for a link) an already-http(s)-validated url.