zmd (Markdown)
On this page:
- spider.zmd: Markdown to HTML:
spider.zmd.parse(arena, text, .{}). What the author typed is escaped.
spider.zmd
Markdown to HTML (spider.zmd). parse gives a fragment to put inside a page, parseFull a whole HTML document. Everything the author typed is escaped. The HTML of each element comes from a Formatters value, which an app can replace field by field.
spider.zmd.Node
pub const Node = struct
One element of the parsed document: what a formatter function receives.
Fields
token: tokens.Token: The syntax element this node came from and its place in the input.content: []const u8 = "": The inner HTML of the element when its formatter runs: the children already rendered, text and code already escaped.meta: ?[]const u8 = null: The language named after the opening fence of a code block (zigfor a block opened with three backticks andzig). As typed: escape it.href: ?[]const u8 = null: The address of a link or image. As typed: check it withFormatters.safeAddressand escape it.title: ?[]const u8 = null: The text of a link or image. As typed: escape it.children: ArrayList(*Node): The elements inside this one, in order.index: usize = 0
spider.zmd.Formatters
pub const Formatters = struct
The function that writes the HTML of each kind of element. .{} is the default set; name a field to replace one:
const Allocator = std.mem.Allocator;
fn heading(allocator: Allocator, node: spider.zmd.Node) Allocator.Error![]const u8 {
return std.fmt.allocPrint(allocator, "<h1 class=\"title\">{s}</h1>\n", .{node.content});
}
const html = try spider.zmd.parse(c.arena, text, .{ .h1 = heading });
Fields
root: Handler = Default.root_partial: The whole document. The default returns the content as a fragment.block: Handler = Default.block: A fenced code block;node.metais its language, if any.link: Handler = Default.link:[text](address):node.titleis the text,node.hrefthe address.image: Handler = Default.image::node.titleandnode.href.h1: Handler = Default.h1h2: Handler = Default.h2h3: Handler = Default.h3h4: Handler = Default.h4h5: Handler = Default.h5h6: Handler = Default.h6blockquote: Handler = Default.blockquote: A line starting with>.bold: Handler = Default.bold:**text**.italic: Handler = Default.italic:_text_or*text*.unordered_list: Handler = Default.unordered_listordered_list: Handler = Default.ordered_listlist_item: Handler = Default.list_itemcode: Handler = Default.code: Inline code between backticks.paragraph: Handler = Default.paragraph: A top-level paragraph.raw_block: Handler = Default.raw_block: A{% raw %}...{% endraw %}block:node.contentis the block as typed, the two markers included, escaped.default_handler: Handler = Default.default: Every element without a field of its own (plain text, nested paragraphs).
zmd.Formatters.Handler
pub const Handler = fn (Allocator, Node) Allocator.Error![]const u8
A formatter: gets one element and returns its HTML. node.content is the inner HTML, already rendered and escaped; node.href, node.title and node.meta are as the author typed them and must be escaped with escape (and an address checked with safeAddress). The result must be allocated with the given allocator: the caller frees it.
zmd.Formatters.Default
pub const Default = struct
The default formatters, one per field of Formatters.
zmd.Formatters.Default.root
pub fn root(allocator: Allocator, node: Node) ![]const u8
A whole HTML document around the content; zmd.parseFull uses it.
zmd.Formatters.Default.root_partial
pub fn root_partial(allocator: Allocator, node: Node) ![]const u8
The content alone; zmd.parse uses it.
zmd.Formatters.Default.block
pub fn block(allocator: Allocator, node: Node) ![]const u8
<div class="code-block"> with <pre><code>, and a bar naming the language when the block has one.
zmd.Formatters.Default.link
pub fn link(allocator: Allocator, node: Node) ![]const u8
The address and the text are what the author typed: both are escaped, and an address that would run code (javascript:, data:) leaves only the text.
zmd.Formatters.Default.image
pub fn image(allocator: Allocator, node: Node) ![]const u8
<img src title>, both escaped; an address that is not safe leaves only the title.
zmd.Formatters.Default.h1
pub fn h1(allocator: Allocator, node: Node) ![]const u8
<h1>.
zmd.Formatters.Default.h2
pub fn h2(allocator: Allocator, node: Node) ![]const u8
<h2>.
zmd.Formatters.Default.h3
pub fn h3(allocator: Allocator, node: Node) ![]const u8
<h3>.
zmd.Formatters.Default.h4
pub fn h4(allocator: Allocator, node: Node) ![]const u8
<h4>.
zmd.Formatters.Default.h5
pub fn h5(allocator: Allocator, node: Node) ![]const u8
<h5>.
zmd.Formatters.Default.h6
pub fn h6(allocator: Allocator, node: Node) ![]const u8
<h6>.
zmd.Formatters.Default.blockquote
pub fn blockquote(allocator: Allocator, node: Node) ![]const u8
<blockquote>.
zmd.Formatters.Default.bold
pub fn bold(allocator: Allocator, node: Node) ![]const u8
<b>.
zmd.Formatters.Default.italic
pub fn italic(allocator: Allocator, node: Node) ![]const u8
<i>.
zmd.Formatters.Default.unordered_list
pub fn unordered_list(allocator: Allocator, node: Node) ![]const u8
<ul>.
zmd.Formatters.Default.ordered_list
pub fn ordered_list(allocator: Allocator, node: Node) ![]const u8
<ol>.
zmd.Formatters.Default.list_item
pub fn list_item(allocator: Allocator, node: Node) ![]const u8
<li>.
zmd.Formatters.Default.code
pub fn code(allocator: Allocator, node: Node) ![]const u8
<code>.
zmd.Formatters.Default.paragraph
pub fn paragraph(allocator: Allocator, node: Node) ![]const u8
<p>.
zmd.Formatters.Default.raw_block
pub fn raw_block(allocator: Allocator, node: Node) ![]const u8
The content as it is.
zmd.Formatters.Default.default
pub fn default(allocator: Allocator, node: Node) ![]const u8
The content as it is.
zmd.Formatters.escape
pub fn escape(allocator: Allocator, input: []const u8) Allocator.Error![]const u8
Escapes text for HTML, inside an element or a quoted attribute.
zmd.Formatters.safeAddress
pub fn safeAddress(address: []const u8) bool
An address a reader may follow: http, https, mailto, or one with no scheme at all (a path, a fragment). javascript: and data: are not.
spider.zmd.parse
pub fn parse(
allocator: Allocator,
input: []const u8,
formatters: Formatters,
) ![]const u8
Converts Markdown to an HTML fragment (no <html> or <body> around it). formatters is .{} for the default HTML.
const html = try spider.zmd.parse(c.arena, post.body, .{});
return c.view("posts/show", .{ .body = spider.RawHtml{ .html = html } }, .{});
The result is allocated with allocator and owned by the caller; input is not kept. Text, link addresses and code are escaped, so the result is safe to wrap in spider.RawHtml. \r\n line ends are read as \n. The only failure is running out of memory (error.OutOfMemory, or error.WriteFailed from the buffer the HTML is written to).
formatters must be known at compile time. The result is always a fragment: Formatters.Default.root (the whole document) given as formatters.root is replaced by the fragment root, and the other fields are used as given. Use parseFull for a document.
spider.zmd.parseFull
pub fn parseFull(
allocator: Allocator,
input: []const u8,
formatters: Formatters,
) ![]const u8
Like parse, but the result is a whole HTML document: the default root formatter wraps the content in <!DOCTYPE html>, <head> (charset only) and <body><main>.
The other fields of formatters are used as given (.{ .h1 = heading } changes the headings of the document); a root of your own replaces the document wrapper.