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

html library org-mode swift

README.md

main
org-swift/README.md history · blame · raw

100 lines · 3974 bytes

  1# OrgSwift
  2
  3A dependency-free Swift library that renders a practical subset of
  4[org-mode](https://orgmode.org) to sanitized HTML. Pure Foundation — no
  5SwiftUI, WebKit, UIKit, or third-party packages.
  6
  7Originally extracted from the hand-rolled renderer in the Hutch iOS client so it
  8could be shared across apps, and since rebuilt around an element tree: org is
  9parsed once into a document model, and each output format walks it. See
 10[ARCHITECTURE.md](ARCHITECTURE.md).
 11
 12## Supported syntax
 13
 14Headings (with trailing `:tags:`), paragraphs, ordered/unordered lists (with
 15arbitrary-depth nesting, wrapped lines, and `[ ]`/`[x]`/`[-]` task checkboxes),
 16description lists (`term :: definition``<dl>`), multi-paragraph list items,
 17tables (with `:---:` alignment), `#+begin_src` / `example` / `quote` / `center` /
 18`verse` / `export` blocks and `#+begin_<name>` special blocks,
 19`#+TITLE`/`#+AUTHOR`/`#+DATE` metadata, `#+CAPTION`/`#+ATTR_HTML`/`#+NAME`
 20figures, org links and images (`[[file:x.png]]``<img>`, `[[dest][label]]` 21link, `[[id:target]]``#target`), horizontal rules, comments, timestamps
 22(`<2024-01-15 Mon>`, inactive, times, and ranges → `<time>`), footnotes
 23(references, inline, and definitions → a `<section class="footnotes">`), and
 24inline markup (`*bold*`, `/italic/`, `~code~`, `=verbatim=`, `+strike+`,
 25`_underline_`, `x^2` superscript, bare-URL and email autolinks). Other
 26`#+KEYWORD:` lines are consumed as document metadata.
 27
 28Output is sanitized: only `http`/`https`/`mailto` link schemes and
 29`http`/`https` image schemes are allowed; everything else is dropped.
 30
 31## Usage
 32
 33```swift
 34import OrgSwift
 35
 36let html = OrgRenderer.renderToHTML(orgSource)
 37```
 38
 39### Relative links
 40
 41Pass an `OrgRenderOptions` to rewrite repository-relative links and images to
 42absolute `blob` URLs. When `owner`/`repositoryName` are nil (the default),
 43relative links are left as-is, which the scheme allowlist then drops.
 44
 45```swift
 46let html = OrgRenderer.renderToHTML(
 47    orgSource,
 48    options: OrgRenderOptions(
 49        host: "git.sr.ht",       // default
 50        owner: "~ccleberg",
 51        repositoryName: "Hutch",
 52        ref: "HEAD",             // default
 53        readmePath: "README.org" // resolves paths relative to this file
 54    )
 55)
 56```
 57
 58`./images/badge.svg` then resolves to
 59`https://git.sr.ht/~ccleberg/Hutch/blob/HEAD/images/badge.svg`.
 60
 61### Title and heading level
 62
 63Two presentation knobs, both defaulting to how Hutch rendered:
 64
 65- `metadataHeader` (default `true`) — emit a leading `<div class="org-metadata">`
 66  with the `#+TITLE`/`#+AUTHOR`/`#+DATE`. Set `false` to drop it (e.g. when the
 67  surrounding UI shows the title itself).
 68- `headingLevelOffset` (default `0`) — added to each heading's star count, clamped
 69  to `1...6`. Default renders `*` as `<h1>`; use `1` to render `*` as `<h2>`,
 70  leaving `<h1>` for a document title.
 71
 72### Syntax highlighting
 73
 74Code blocks are highlighted through a protocol so the library carries no
 75highlighter dependency:
 76
 77```swift
 78public protocol CodeHighlighter {
 79    func highlightedHTML(code: String, language: String?) -> String?
 80}
 81```
 82
 83The default `PlainCodeHighlighter` returns `nil`, so blocks fall back to escaped
 84`<pre><code>`. Provide your own conformer to plug in a real highlighter:
 85
 86```swift
 87let html = OrgRenderer.renderToHTML(orgSource, highlighter: MyHighlighter())
 88```
 89
 90A conformer returning `nil` for a given block gets the same escaped fallback.
 91
 92## Conformance
 93
 94OrgSwift is tested against the shared [org-conformance](../org-conformance)
 95corpus, whose golden outputs come from `orgo` (itself validated against Emacs
 96`ox-html`). `swift test` locates a sibling `../org-conformance` checkout, or set
 97`ORG_CONFORMANCE_DIR`. The suite reduces both this renderer's HTML and orgo's to
 98a semantic skeleton and compares them; the current pass/divergence state is
 99recorded per case, so a closed gap or a regression both surface as a failing
100test. See [GAPS.md](GAPS.md) for the outstanding backlog.