Zig 0.17.0

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

zig
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 (zig for a block opened with three backticks and zig). As typed: escape it.
  • href: ?[]const u8 = null: The address of a link or image. As typed: check it with Formatters.safeAddress and 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

zig
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:

zig
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.meta is its language, if any.
  • link: Handler = Default.link: [text](address): node.title is the text, node.href the address.
  • image: Handler = Default.image: ![title](address): node.title and node.href.
  • h1: Handler = Default.h1
  • h2: Handler = Default.h2
  • h3: Handler = Default.h3
  • h4: Handler = Default.h4
  • h5: Handler = Default.h5
  • h6: Handler = Default.h6
  • blockquote: Handler = Default.blockquote: A line starting with >.
  • bold: Handler = Default.bold: **text**.
  • italic: Handler = Default.italic: _text_ or *text*.
  • unordered_list: Handler = Default.unordered_list
  • ordered_list: Handler = Default.ordered_list
  • list_item: Handler = Default.list_item
  • code: 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.content is 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

zig
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

zig
pub const Default = struct

The default formatters, one per field of Formatters.

zmd.Formatters.Default.root

zig
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

zig
pub fn root_partial(allocator: Allocator, node: Node) ![]const u8

The content alone; zmd.parse uses it.

zmd.Formatters.Default.block

zig
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.

zig
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

zig
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

zig
pub fn h1(allocator: Allocator, node: Node) ![]const u8

<h1>.

zmd.Formatters.Default.h2

zig
pub fn h2(allocator: Allocator, node: Node) ![]const u8

<h2>.

zmd.Formatters.Default.h3

zig
pub fn h3(allocator: Allocator, node: Node) ![]const u8

<h3>.

zmd.Formatters.Default.h4

zig
pub fn h4(allocator: Allocator, node: Node) ![]const u8

<h4>.

zmd.Formatters.Default.h5

zig
pub fn h5(allocator: Allocator, node: Node) ![]const u8

<h5>.

zmd.Formatters.Default.h6

zig
pub fn h6(allocator: Allocator, node: Node) ![]const u8

<h6>.

zmd.Formatters.Default.blockquote

zig
pub fn blockquote(allocator: Allocator, node: Node) ![]const u8

<blockquote>.

zmd.Formatters.Default.bold

zig
pub fn bold(allocator: Allocator, node: Node) ![]const u8

<b>.

zmd.Formatters.Default.italic

zig
pub fn italic(allocator: Allocator, node: Node) ![]const u8

<i>.

zmd.Formatters.Default.unordered_list

zig
pub fn unordered_list(allocator: Allocator, node: Node) ![]const u8

<ul>.

zmd.Formatters.Default.ordered_list

zig
pub fn ordered_list(allocator: Allocator, node: Node) ![]const u8

<ol>.

zmd.Formatters.Default.list_item

zig
pub fn list_item(allocator: Allocator, node: Node) ![]const u8

<li>.

zmd.Formatters.Default.code

zig
pub fn code(allocator: Allocator, node: Node) ![]const u8

<code>.

zmd.Formatters.Default.paragraph

zig
pub fn paragraph(allocator: Allocator, node: Node) ![]const u8

<p>.

zmd.Formatters.Default.raw_block

zig
pub fn raw_block(allocator: Allocator, node: Node) ![]const u8

The content as it is.

zmd.Formatters.Default.default

zig
pub fn default(allocator: Allocator, node: Node) ![]const u8

The content as it is.

zmd.Formatters.escape

zig
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

zig
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

zig
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.

zig
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

zig
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.