Syntaxes & file types
A ScriptPub script can be stored in any supported syntax. An adapter is the
bridge for one syntax — { parse, serialize } against the
canonical model: parse turns a document in that syntax into the
model, serialize writes it back. Adapters are additive — registering a new
syntax id needs no format-version bump. The adapters are the text tier of the
format registry, which also describes the binary formats
(EPUB, DOCX, PDF) the tooling lists and reads without a text adapter.
Adapter = {
id: string,
parse(text: string, config?: FormatConfig): ParsedDocument, // structural errors throw
serialize(doc: ParsedDocument): string,
}
The six adapters
| Syntax | File | Config | Notes |
|---|---|---|---|
html |
.spub.html |
Embedded <script> |
The common self-contained default. |
md |
.md |
Frontmatter or companion | CommonMark subset. |
fountain |
.fountain |
Title-page keys or companion | Screenwriting plain text. |
fdx |
.fdx |
XML container or companion | Final Draft interchange. |
txt |
.txt |
Companion (default) or frontmatter | Pure recognition — makes a Gutenberg book editable. |
json / jsonl / json5 |
.spub.json |
is the config | The canonical model, serialized. |
File names. SPub files use the
*.spub.*double extension —Title-by-Author.spub.html,notes.spub.json. Content-derived syntaxes keep their own plain extensions; the point of those adapters is not renaming anyone’s files.
Two families:
- Embedded-config (
json,html) carry their config in the file. An embedded config always wins over a companion; anhtmlfile without a config script parses as a config-less fragment under the companion — a multi-file work’s part. - Content-derived (
md,txt,fountain,fdx) infer kind from the text and take their config from a companion (Markdown also reads JSON frontmatter).
html — the common default
One self-contained HTML file, readable and styleable by a plain browser with no special editor.
- Config embedded as pretty JSON in
<script type="application/json" id="scriptpub">in the<head>. - The stylesheet is the
<style id="scriptpub-style">element — the document’s visual truth as real CSS (spec §The stylesheet): the machine@layerstatement +sp-baseprelude are regenerated on save and skipped on parse; everything else round-trips as the document’s own sheet.
The poetry preset is a complete, minimal example of a
.spub.html file.
Head-spelled metadata
Every meta key with a standard HTML head expression is spelled as that
HTML and omitted from the embedded config JSON; on parse both are read and
the head wins. The canonical model is untouched — config.meta remains the
one metadata home; this is purely the html syntax’s spelling of it. Keys with
no standard expression (year, credit, custom keys, …) stay in the JSON.
meta key |
Head spelling |
|---|---|
title |
<title> |
language |
<html lang> |
author |
<meta name="author"> (one per author) |
description / keywords / copyright / cover |
<meta name="…"> |
coverAlt |
<meta name="cover-alt"> (kebab in HTML, camel in the model) |
license |
<link rel="license"> when a URL, else <meta name="license"> |
uri |
<link rel="canonical"> |
Legacy files carrying these keys only in the JSON parse identically forever;
they migrate to the head spelling on next save. parseHtmlHead reads a
document’s head alone — config + merged meta, no body work — the cheap
per-file scan a library Reindex runs.
A file named index.html whose config says kind: "library" is a
library’s index (Libraries) — the plain
index.html name (not the double extension) is what static hosting serves by
default, and the embedded config is what marks it as ScriptPub.
Supported HTML tags
A curated subset — not the whole HTML spec — chosen so a document still degrades
meaningfully with its stylesheet stripped. A tag may be legal for more than one use
(blockquote as a text block or a grid; div anywhere).
| Use | Tags |
|---|---|
| Text blocks | p h1 h2 h3 h4 h5 h6 blockquote pre address figcaption summary li dt dd div — plus no tag (bare text: the default Text block) |
| Void blocks | hr img audio video (media blocks — src and the media attrs; audio / video close-tagged with empty content) |
| Grid blocks | table (a semantic table) · div figure details (first child = its summary) blockquote aside dl (cells: dt / dd) hgroup (wrapper grids) |
| Section wrappers | header main section footer div |
| Marks | a em strong b i u s mark small sub sup cite q abbr dfn code kbd samp var time bdi bdo del ins ruby wbr span |
| Inline objects | img in inline position — an inline image object run, amid a block’s run markup, wrappable by mark tags |
| Adapter-managed (never a kind’s tag) | ul ol (synthesized around list blocks) · caption thead tbody tr th td + div class="cell" (inside a grid) · rt rp (inside ruby) · br (newline encoding) · source track picture (resolved on parse — see the media bullet above) · main (the content root) |
md
- Config in frontmatter, or a companion.
- Constructs: headings, paragraphs, blockquotes, fenced code,
---rules, GFM pipe tables (↔ agridblock:display: table,headerRows: 1, a:---:column → cellalign), images, links, ordered / unordered lists (→ list metadata), footnotes. Kind inference may combine a Markdown construct with text predicates (a scene heading = paragraph and anINT.prefix). - Images:
amid text ↔ an inline image object run; a paragraph that is nothing but one image ↔ a voidimageblock (the"image"construct). Reference-style links and images (![alt][ref]+[ref]: url "title") parse — definitions are resolved and consumed — but serialization is always inline-style. The other media attributes have no Markdown spelling and drop (tiered). - Media-element passthrough: a block-level line opening with
<img,<audio,<video, or<figureparses that HTML block through the html adapter’s element machinery into the corresponding block(s), and such blocks serialize back as the same one-element HTML — CommonMark-legal, GFM-renderable. This is the only raw-HTML form the adapter reads; everything else stays unsupported. - Fenced code: ``` fences ↔ a
pre-tagged code kind; the info string rides asdata.language(the escape hatch) and re-emits on the fence. - Autolinks (
<https://…>) parse to links; GFM task-list markers (- [ ]) parse with the checkbox state carried asdata.task("todo"/"done") and re-emit. - Block refs use the Obsidian-style trailing
^id.
fountain
- Config in frontmatter / title-page keys, or a companion.
- Constructs: scene heading, action, character, parenthetical, dialogue,
transition, centered, lyrics, page break, section / synopsis, dual dialogue
(
^), notes[[…]]and boneyard/* … */(→printing: falsekinds). - The context predicates
prev/nextare essential here — a dialogue line is defined by what precedes it.
fdx
- Config in an XML container element or comments, or a companion.
- Constructs: FDX paragraph
Typenames, dual dialogue, the FDX title page. - Imposed preset: exactly two sections — a title page and a body — non-editable.
- Imposed requirement: exactly one block kind mapped to the FDX
Characterconstruct, and that kind must carry an index — the character list and cue mapping derive from it. The requirement is id-agnostic (the kind may be named anything); the format itself stays free of speaker semantics.
txt
- Config in a companion (the default posture — don’t touch the file) or, if the user opts in, frontmatter.
- Pure recognition: kinds inferred entirely from text predicates — regex, caps,
prefixes, indentation, blank lines,
prev/next. The hairiest adapter, and the one that makes a Project Gutenberg book editable in place.
json / jsonl / json5
- The canonical model, serialized. Kind is explicit per block — always lossless, no inference.
jsonlis a line-delimited variant (an interchange / streaming form more than an authoring one);json5is a relaxed authoring variant of the same shape.
Config placement
Per script, the config lives in one or both of:
1. Embedded — via the syntax’s native metadata mechanism:
| Syntax | Mechanism |
|---|---|
html |
<script type="application/json" id="scriptpub"> in <head>. |
md |
Frontmatter. |
txt |
Frontmatter — only if the user opts to touch the file. |
fountain |
Frontmatter / title-page keys. |
fdx |
XML container element or comments. |
json |
It is the config (plus content). |
2. Companion file — <file>.spub.json beside the content file
(screenplay.fdx → screenplay.fdx.spub.json), holding the config alone. The
natural choice when the content file must stay untouched — someone else’s FDX, a
Gutenberg .txt.
If both exist, the companion extends the embedded config: deep merge, arrays
merged by id, companion wins on conflict. For the order-significant blocks
array, merge-by-id preserves the original order; overlay-only blocks append at the
end (lowest inference precedence).
Full-script JSON vs companion config.
<name>.spub.json(a full script, has acontentkey) is distinguished from<name>.<ext>.spub.json(a config-only companion) by both naming convention and content — the parser checks for thecontentkey regardless of the name.