A dependency-free Swift library that renders org-mode to sanitized HTML.

html library org-mode swift

Clone: git clone https://gitbay.org/krz/org-swift.git

12 commits 1 branch 0 tags

Swift 90.3%Markdown 9.7%

2 contributorscmcChristian Cleberg

OrgSwift

A dependency-free Swift library that renders a practical subset of org-mode to sanitized HTML. Pure Foundation — no SwiftUI, WebKit, UIKit, or third-party packages.

Originally extracted from the hand-rolled renderer in the Hutch iOS client so it could be shared across apps, and since rebuilt around an element tree: org is parsed once into a document model, and each output format walks it. See ARCHITECTURE.md.

Supported syntax

Headings (with trailing :tags:), paragraphs, ordered/unordered lists (with arbitrary-depth nesting, wrapped lines, and [ ]/[x]/[-] task checkboxes), description lists (term :: definition<dl>), multi-paragraph list items, tables (with :---: alignment), #+begin_src / example / quote / center / verse / export blocks and #+begin_<name> special blocks, #+TITLE/#+AUTHOR/#+DATE metadata, #+CAPTION/#+ATTR_HTML/#+NAME figures, org links and images ([[file:x.png]]<img>, [[dest][label]] → link, [[id:target]]#target), horizontal rules, comments, timestamps (<2024-01-15 Mon>, inactive, times, and ranges → <time>), footnotes (references, inline, and definitions → a <section class="footnotes">), and inline markup (*bold*, /italic/, ~code~, =verbatim=, +strike+, _underline_, x^2 superscript, bare-URL and email autolinks). Other #+KEYWORD: lines are consumed as document metadata.

Output is sanitized: only http/https/mailto link schemes and http/https image schemes are allowed; everything else is dropped.

Usage

import OrgSwift

let html = OrgRenderer.renderToHTML(orgSource)

Relative links

Pass an OrgRenderOptions to rewrite repository-relative links and images to absolute blob URLs. When owner/repositoryName are nil (the default), relative links are left as-is, which the scheme allowlist then drops.

let html = OrgRenderer.renderToHTML(
    orgSource,
    options: OrgRenderOptions(
        host: "git.sr.ht",       // default
        owner: "~ccleberg",
        repositoryName: "Hutch",
        ref: "HEAD",             // default
        readmePath: "README.org" // resolves paths relative to this file
    )
)

./images/badge.svg then resolves to https://git.sr.ht/~ccleberg/Hutch/blob/HEAD/images/badge.svg.

Title and heading level

Two presentation knobs, both defaulting to how Hutch rendered:

  • metadataHeader (default true) — emit a leading <div class="org-metadata"> with the #+TITLE/#+AUTHOR/#+DATE. Set false to drop it (e.g. when the surrounding UI shows the title itself).
  • headingLevelOffset (default 0) — added to each heading's star count, clamped to 1...6. Default renders * as <h1>; use 1 to render * as <h2>, leaving <h1> for a document title.

Syntax highlighting

Code blocks are highlighted through a protocol so the library carries no highlighter dependency:

public protocol CodeHighlighter {
    func highlightedHTML(code: String, language: String?) -> String?
}

The default PlainCodeHighlighter returns nil, so blocks fall back to escaped <pre><code>. Provide your own conformer to plug in a real highlighter:

let html = OrgRenderer.renderToHTML(orgSource, highlighter: MyHighlighter())

A conformer returning nil for a given block gets the same escaped fallback.

Conformance

OrgSwift is tested against the shared org-conformance corpus, whose golden outputs come from orgo (itself validated against Emacs ox-html). swift test locates a sibling ../org-conformance checkout, or set ORG_CONFORMANCE_DIR. The suite reduces both this renderer's HTML and orgo's to a semantic skeleton and compares them; the current pass/divergence state is recorded per case, so a closed gap or a regression both surface as a failing test. See GAPS.md for the outstanding backlog.