9 n__markdown_8h
gullradriel edited this page 2026-11-01 17:45:54 +01:00

include/nilorea/n_markdown.h

Parse a SAFE, strict subset of Markdown into a block / styled-span model. More...

Structures

Name
struct n_markdown_span
One inline run: visible text, a style bitmask, and (for a link) an already-http(s)-validated url.
struct n_markdown_inline
One flow of inline runs (a paragraph, a quote, or one list item).
struct n_markdown_doc
A parsed document: an owned array of blocks.
struct n_markdown_block
One block.

Types

Name
enum n_markdown_block_kind { N_MARKDOWN_UL, N_MARKDOWN_TABLE, N_MARKDOWN_QUOTE, N_MARKDOWN_PARA = 0, N_MARKDOWN_OL, N_MARKDOWN_HEADING, N_MARKDOWN_CODE_BLOCK}
Block kinds.

Functions

Name
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
void n_markdown_parse(const char * text, size_t len, n_markdown_doc * out)
parse text (len bytes, plain UTF-8) into out
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
int n_markdown_has_markup(const char * text, size_t len)
1 if text contains any renderable markup, else 0
void n_markdown_free(n_markdown_doc * doc)
free everything a n_markdown_doc owns and zero it

Defines

Name
N_MARKDOWN_TABLES
n_markdown_parse_ex flag: recognise GitHub-style pipe tables.
N_MARKDOWN_STRIKETHROUGH
n_markdown_parse_ex flag: "text" carries N_MARKDOWN_STRIKE.
N_MARKDOWN_STRIKE
strike inline style flag (only with N_MARKDOWN_STRIKETHROUGH)
N_MARKDOWN_NESTED
n_markdown_parse_ex flag: indentation nests.
N_MARKDOWN_LINK
[[text]()](http(s)://url) style flag; url is set only when this is present
N_MARKDOWN_ITALIC
italic inline style flag
N_MARKDOWN_INLINE_ALL
every inline option: autolinks, strikethrough and escapes.
N_MARKDOWN_HEADINGS
n_markdown_parse_ex flag: recognise "# " to "###### " headings.
N_MARKDOWN_ESCAPES
n_markdown_parse_ex flag: a backslash before ASCII punctuation makes that character literal ("\not italic\"); before anything else it stays.
N_MARKDOWN_CODE
inline code style flag (carries no emphasis)
N_MARKDOWN_BOLD
bold inline style flag
N_MARKDOWN_AUTOLINKS
n_markdown_parse_ex flag: a bare http(s) URL, or one in , is a link whose text is the URL.
N_MARKDOWN_ALL
every block kind this parser knows.
N_MARKDOWN_ALIGN_RIGHT
what "
N_MARKDOWN_ALIGN_LEFT
table column alignment: the default, and what "
N_MARKDOWN_ALIGN_CENTER
what "

Detailed Description

Parse a SAFE, strict subset of Markdown into a block / styled-span model.

Author: Castagnier Mickael

Version: 1.0

A renderer-agnostic parser for a small, safe subset of Markdown, suitable for chat / message text. It emits a flat, renderer-friendly model rather than a DOM: each inline run carries a style bitmask (bold / italic / code / link) so a caller can turn it into styled glyph runs / DOM nodes / ANSI, etc. The recursive nesting of the source (bold containing italic) is flattened into per-span flags.

It faces hostile input, so it is bounded and safe: a pathological input truncates (caps on blocks / spans / list-items and on bold/italic recursion depth) rather than exploding, and an allocation failure keeps what it has. Unknown markup is left as literal text.

Subset: inline bold / bold, italic / italic, code, text (a non-http(s) scheme renders literally) block paragraphs, ``` fenced code blocks, > blockquotes, "- " / "* " unordered lists, "1. " ordered lists

Three inline options are available the same way: bare and http(s) autolinks, strikethrough and backslash escapes (N_MARKDOWN_INLINE_ALL).

Two more block kinds are available on request, through n_markdown_parse_ex() and its flags: "# " to "###### " headings, and GitHub-style pipe tables. They are opt-in because the default subset is what a chat message is parsed with, where a line beginning with a # is a hash tag far more often than a heading, and where a line of pipes is a line of pipes. A document parsed without a flag sees those lines as ordinary text, exactly as it did before the flags existed.

Types Documentation

enum n_markdown_block_kind

Enumerator Value Description
N_MARKDOWN_UL unordered list (items)
N_MARKDOWN_TABLE pipe table (items, n_cols)
N_MARKDOWN_QUOTE

blockquote | | N_MARKDOWN_PARA | 0| paragraph | | N_MARKDOWN_OL | | ordered list (items) | | N_MARKDOWN_HEADING | | "## " heading (inl, level) | | N_MARKDOWN_CODE_BLOCK | | ``` fenced code (raw code) |

Block kinds.

The last two appear only when n_markdown_parse_ex() was asked for them, so a caller that has not been updated cannot meet one.

Functions Documentation

function n_markdown_parse_ex

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

Parameters:

  • text the UTF-8 source (may be NULL)
  • len number of bytes of text
  • flags any of N_MARKDOWN_HEADINGS, N_MARKDOWN_TABLES, N_MARKDOWN_AUTOLINKS, N_MARKDOWN_STRIKETHROUGH, N_MARKDOWN_ESCAPES, N_MARKDOWN_ALL, N_MARKDOWN_INLINE_ALL; unknown bits are ignored
  • out the document to fill (zeroed first)

n_markdown_parse() is this with flags of zero, and that is the whole difference: the default subset is unchanged, byte for byte, so a caller that never passes a flag can never be handed a block kind it does not know.

Same guarantees as n_markdown_parse(): out is zeroed first, an allocation failure or a bounds cap truncates safely rather than failing, and the result is always released with n_markdown_free().

function n_markdown_parse

void n_markdown_parse(
    const char * text,
    size_t len,
    n_markdown_doc * out
)

parse text (len bytes, plain UTF-8) into out

Parameters:

  • text the UTF-8 source (may be NULL)
  • len number of bytes of text
  • out the document to fill (zeroed first)

Never fails destructively: on allocation failure or a bounds cap it keeps what it has (safe truncation). out is zeroed first. Always pair with n_markdown_free.

function n_markdown_has_markup_ex

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

Parameters:

Return: 1 if there is renderable markup, 0 otherwise

n_markdown_has_markup() is this with flags of zero. A document that is nothing but a heading has no markup in the default subset and has markup with N_MARKDOWN_HEADINGS, which is the answer each caller wants.

function n_markdown_has_markup

int n_markdown_has_markup(
    const char * text,
    size_t len
)

1 if text contains any renderable markup, else 0

Parameters:

  • text the UTF-8 source
  • len number of bytes of text

Return: 1 if there is renderable markup, 0 otherwise

Lets a caller skip the styled path for a plain message.

function n_markdown_free

void n_markdown_free(
    n_markdown_doc * doc
)

free everything a n_markdown_doc owns and zero it

Parameters:

  • doc the document (safe on a zeroed doc and on NULL)

Macros Documentation

define N_MARKDOWN_TABLES

#define N_MARKDOWN_TABLES (1u << 1)

n_markdown_parse_ex flag: recognise GitHub-style pipe tables.

define N_MARKDOWN_STRIKETHROUGH

#define N_MARKDOWN_STRIKETHROUGH (1u << 3)

n_markdown_parse_ex flag: "text" carries N_MARKDOWN_STRIKE.

define N_MARKDOWN_STRIKE

#define N_MARKDOWN_STRIKE (1u << 4)

strike inline style flag (only with N_MARKDOWN_STRIKETHROUGH)

define N_MARKDOWN_NESTED

#define N_MARKDOWN_NESTED (1u << 5)

n_markdown_parse_ex flag: indentation nests.

A list item's marker, a fence, a quote or a paragraph indented under a list item starts a block one level deeper (n_markdown_block.depth) instead of being folded into the item's text; a fence may be indented, and its code keeps only the indentation beyond the fence's; an ordered list keeps the number its first item has (n_markdown_block.start), so a list cut by nested blocks goes on counting. What a model writes in a chat answer: steps with sub-points and code under them.

#define N_MARKDOWN_LINK (1u << 3)

[[text]()](http(s)://url) style flag; url is set only when this is present

define N_MARKDOWN_ITALIC

#define N_MARKDOWN_ITALIC (1u << 1)

italic inline style flag

define N_MARKDOWN_INLINE_ALL

#define N_MARKDOWN_INLINE_ALL (N_MARKDOWN_AUTOLINKS | N_MARKDOWN_STRIKETHROUGH | N_MARKDOWN_ESCAPES)

every inline option: autolinks, strikethrough and escapes.

define N_MARKDOWN_HEADINGS

#define N_MARKDOWN_HEADINGS (1u << 0)

n_markdown_parse_ex flag: recognise "# " to "###### " headings.

define N_MARKDOWN_ESCAPES

#define N_MARKDOWN_ESCAPES (1u << 4)

n_markdown_parse_ex flag: a backslash before ASCII punctuation makes that character literal ("\not italic\"); before anything else it stays.

define N_MARKDOWN_CODE

#define N_MARKDOWN_CODE (1u << 2)

inline code style flag (carries no emphasis)

define N_MARKDOWN_BOLD

#define N_MARKDOWN_BOLD (1u << 0)

bold inline style flag

#define N_MARKDOWN_AUTOLINKS (1u << 2)

n_markdown_parse_ex flag: a bare http(s) URL, or one in , is a link whose text is the URL.

Trailing sentence punctuation and an unbalanced ")" are left out of it, and a bare one starts only at a word boundary.

define N_MARKDOWN_ALL

#define N_MARKDOWN_ALL (N_MARKDOWN_HEADINGS | N_MARKDOWN_TABLES | N_MARKDOWN_NESTED)

every block kind this parser knows.

Deliberately NOT the inline options below: a renderer asking for every block kind (n_gui_md) would otherwise be handed a N_MARKDOWN_STRIKE span it does not draw.

define N_MARKDOWN_ALIGN_RIGHT

#define N_MARKDOWN_ALIGN_RIGHT 2

what "|---:|" asks for.

define N_MARKDOWN_ALIGN_LEFT

#define N_MARKDOWN_ALIGN_LEFT 0

table column alignment: the default, and what "|:---|" asks for.

define N_MARKDOWN_ALIGN_CENTER

#define N_MARKDOWN_ALIGN_CENTER 1

what "|:---:|" asks for.

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#ifndef N_MARKDOWN_H
#define N_MARKDOWN_H

#include <stddef.h>

#define N_MARKDOWN_BOLD (1u << 0)
#define N_MARKDOWN_ITALIC (1u << 1)
#define N_MARKDOWN_CODE (1u << 2)
#define N_MARKDOWN_LINK (1u << 3)
#define N_MARKDOWN_STRIKE (1u << 4)

#define N_MARKDOWN_HEADINGS (1u << 0)
#define N_MARKDOWN_TABLES (1u << 1)
#define N_MARKDOWN_AUTOLINKS (1u << 2)
#define N_MARKDOWN_STRIKETHROUGH (1u << 3)
#define N_MARKDOWN_ESCAPES (1u << 4)
#define N_MARKDOWN_NESTED (1u << 5)
#define N_MARKDOWN_ALL (N_MARKDOWN_HEADINGS | N_MARKDOWN_TABLES | N_MARKDOWN_NESTED)
#define N_MARKDOWN_INLINE_ALL \
    (N_MARKDOWN_AUTOLINKS | N_MARKDOWN_STRIKETHROUGH | N_MARKDOWN_ESCAPES)

#define N_MARKDOWN_ALIGN_LEFT 0
#define N_MARKDOWN_ALIGN_CENTER 1
#define N_MARKDOWN_ALIGN_RIGHT 2

enum n_markdown_block_kind {
    N_MARKDOWN_PARA = 0,   
    N_MARKDOWN_QUOTE,      
    N_MARKDOWN_CODE_BLOCK, 
    N_MARKDOWN_UL,         
    N_MARKDOWN_OL,         
    N_MARKDOWN_HEADING,    
    N_MARKDOWN_TABLE       
};

typedef struct n_markdown_span {
    char* text;
    char* url;
    unsigned style;
} n_markdown_span;

typedef struct n_markdown_inline {
    n_markdown_span* spans;
    size_t n_spans;
} n_markdown_inline;

typedef struct n_markdown_block {
    int kind; 
    n_markdown_inline inl;
    char* code;
    n_markdown_inline* items;
    size_t n_items;
    int level;
    size_t n_cols;
    unsigned char* align;
    int depth;
    long start;
} n_markdown_block;

typedef struct n_markdown_doc {
    n_markdown_block* blocks;
    size_t n_blocks;
} n_markdown_doc;

void n_markdown_parse(const char* text, size_t len, n_markdown_doc* out);

void n_markdown_parse_ex(const char* text, size_t len, unsigned flags, n_markdown_doc* out);

void n_markdown_free(n_markdown_doc* doc);

int n_markdown_has_markup(const char* text, size_t len);

int n_markdown_has_markup_ex(const char* text, size_t len, unsigned flags);

#endif /* N_MARKDOWN_H */