Commit 301ff84642
Verified · cmc
README.md added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | # org-conformance | |
| 2 | ||
| 3 | A language-neutral conformance corpus for org-mode → HTML renderers. | |
| 4 | ||
| 5 | The problem it solves: the same `.org` file is rendered by several independent | |
| 6 | codebases — a Go forge (gitbay via go-org), a Rust static-site generator (orgo), a Swift | |
| 7 | package (OrgSwift, shared by the iOS clients), a browser previewer (org-live). They drift. | |
| 8 | A footnote renders in one and vanishes in another; a `:tag:` styles here and prints raw | |
| 9 | there. Nobody notices until they look at the same document on two surfaces. | |
| 10 | ||
| 11 | This corpus is the shared answer to "what is the *right* output". Each implementation runs | |
| 12 | it in its own test suite and asserts against the same goldens. | |
| 13 | ||
| 14 | ## What's here | |
| 15 | ||
| 16 | ``` | |
| 17 | cases/ | |
| 18 | <name>.org the input | |
| 19 | <name>.html orgo's rendered HTML — the reference rendering, for humans | |
| 20 | <name>.skeleton the golden semantic skeleton — the assertion target | |
| 21 | manifest.json every case, the constructs it exercises, and its scope (in/out) | |
| 22 | SKELETON.md the exact reduction each implementation must port | |
| 23 | tools/generate.sh regenerate .html and .skeleton from orgo | |
| 24 | ``` | |
| 25 | ||
| 26 | ## The reference | |
| 27 | ||
| 28 | The goldens are produced by **orgo**, and orgo alone. orgo is not self-appointed: it | |
| 29 | carries an oracle test that exports every fixture with **Emacs's own `ox-html`** under | |
| 30 | `emacs --batch` and diffs the two semantic skeletons, so its output is anchored to org's | |
| 31 | reference exporter rather than to itself. When orgo and Emacs deliberately differ (orgo | |
| 32 | emits `<time datetime>` for a timestamp where Emacs writes plain text, `<em>` where Emacs | |
| 33 | writes `<i>`, and so on), those divergences are enumerated and reviewed in orgo's oracle. | |
| 34 | Everything downstream conforms to orgo. | |
| 35 | ||
| 36 | ## How an implementation conforms | |
| 37 | ||
| 38 | Comparing raw HTML across languages is pointless — every renderer wraps and classes things | |
| 39 | differently. Instead, both sides are reduced to a **skeleton**: the ordered sequence of | |
| 40 | element opens, closes, and text runs, with `div`/`span` and all attributes except | |
| 41 | `href`/`src` dropped, whitespace collapsed, and entities decoded. See | |
| 42 | [SKELETON.md](SKELETON.md) for the exact algorithm. | |
| 43 | ||
| 44 | Each implementation: | |
| 45 | ||
| 46 | 1. Ports the ~150-line skeleton reduction. Verify the port first by feeding each | |
| 47 | `cases/<name>.html` through it and checking it reproduces `cases/<name>.skeleton` | |
| 48 | exactly — this separates a skeleton-port bug from a renderer bug. | |
| 49 | 2. Renders each `cases/<name>.org` with its own renderer, reduces the output with its | |
| 50 | port, and asserts equality against `cases/<name>.skeleton`. | |
| 51 | 3. For constructs it does not yet support, keeps an explicit allowlist of expected | |
| 52 | divergences (mirroring orgo's own "deliberate divergence" list against Emacs). A | |
| 53 | green suite with a documented allowlist beats a red suite nobody acts on — the point | |
| 54 | is that *new, unintended* drift shows up as a diff. | |
| 55 | ||
| 56 | Cases with `"scope": "out"` in the manifest are deliberately-unsupported constructs | |
| 57 | (babel, LaTeX, macros, arbitrary drawers). The contract there is only that they degrade | |
| 58 | predictably and never crash; an implementation may legitimately differ from orgo, so treat | |
| 59 | those as documentation rather than hard assertions. | |
| 60 | ||
| 61 | ## Consumers | |
| 62 | ||
| 63 | | Implementation | Language | Engine | Status | | |
| 64 | |---|---|---|---| | |
| 65 | | orgo | Rust | own element-tree renderer | reference (generates the goldens) | | |
| 66 | | OrgSwift | Swift | extracted from hutch | conformance test in `swift test` | | |
| 67 | | gitbay | Go | go-org | planned | | |
| 68 | | org-live | JS | (to migrate off vendored org-js) | planned | | |
| 69 | ||
| 70 | ## Regenerating | |
| 71 | ||
| 72 | The goldens are derived, not hand-written. After an intended change to orgo's renderer: | |
| 73 | ||
| 74 | ``` | |
| 75 | ORGO_DIR=../orgo tools/generate.sh | |
| 76 | ``` | |
| 77 | ||
| 78 | Review the diff, and only commit it once orgo's own oracle agrees the change is right. | |
SKELETON.md added +76
| @@ -0,0 +1,76 @@ | ||
| 1 | # The skeleton reduction | |
| 2 | ||
| 3 | Two correct org→HTML renderers disagree on almost everything at the byte level. org | |
| 4 | wraps every section in `outline-container` divs keyed by generated ids; syntect emits one | |
| 5 | `<span>` per code token; every backend picks its own class names and id scheme. Comparing | |
| 6 | raw HTML across implementations measures none of the things that matter. | |
| 7 | ||
| 8 | The **skeleton** throws that away and keeps what two renderers can meaningfully agree or | |
| 9 | disagree about: the ordered sequence of element opens, element closes, and text runs. | |
| 10 | ||
| 11 | A conformant implementation ports this reduction and asserts that | |
| 12 | `skeleton(its_html) == the checked-in .skeleton file`. | |
| 13 | ||
| 14 | ## The reduction, exactly | |
| 15 | ||
| 16 | Given an HTML fragment, walk it left to right and emit one line per event: | |
| 17 | ||
| 18 | 1. **Element open** → `<name>` where `name` is lowercased. Keep only the attributes | |
| 19 | `href` and `src`, in that fixed order, each rendered as ` href="value"` / | |
| 20 | ` src="value"` with entities in the value decoded. Drop every other attribute (`id`, | |
| 21 | `class`, `style`, `datetime`, …). A self-closing `/` is stripped from the name. | |
| 22 | 2. **Element close** → `</name>`, lowercased. | |
| 23 | 3. **Text run** → the text, with entities decoded and whitespace collapsed (any run of | |
| 24 | whitespace becomes a single space, leading/trailing trimmed), then emitted as a | |
| 25 | **Rust debug-quoted string** — i.e. wrapped in double quotes with `"` and `\` | |
| 26 | backslash-escaped. Empty runs (nothing but whitespace) emit nothing. | |
| 27 | ||
| 28 | Special rules: | |
| 29 | ||
| 30 | **Ignored elements: `div` and `span`.** Neither emits an open or a close line. `div` | |
| 31 | is pure layout. `span` matters more than it looks: a syntax highlighter emits one | |
| 32 | span per token, and keeping them would turn one source block into ~60 lines of noise. | |
| 33 | **Text merges across ignored tags.** Text is flushed to the output *only when a | |
| 34 | non-ignored tag is emitted*. So the text on either side of a `<span>` joins into one | |
| 35 | run — which is exactly why a highlighted source block compares as the single string of | |
| 36 | code it is, not as a token-by-token sequence that has to line up. | |
| 37 | **Void elements never emit a close:** `br hr img input meta link col area base source | |
| 38 | wbr`. A `</img>` in the input is ignored. | |
| 39 | **Comments and doctypes** (`<!-- … -->`, `<!DOCTYPE …>`) carry nothing and are skipped. | |
| 40 | ||
| 41 | ## Entity decoding | |
| 42 | ||
| 43 | Decode the named entities `& < > " ' ` and numeric entities | |
| 44 | (` `, ` `). A non-breaking space (`\u{a0}`) is treated as a plain space for | |
| 45 | comparison. Anything that is not a recognizable entity — a bare `&` — is left as-is. Only | |
| 46 | consider a `&…;` an entity if the `;` is within 12 characters of the `&`. | |
| 47 | ||
| 48 | ## Worked example | |
| 49 | ||
| 50 | Input HTML: | |
| 51 | ||
| 52 | ```html | |
| 53 | <h2 id="write-parser"><span class="todo TODO">TODO</span> Write the <b>parser</b></h2> | |
| 54 | ``` | |
| 55 | ||
| 56 | Skeleton: | |
| 57 | ||
| 58 | ``` | |
| 59 | <h2> | |
| 60 | "TODO Write the" | |
| 61 | <b> | |
| 62 | "parser" | |
| 63 | </b> | |
| 64 | </h2> | |
| 65 | ``` | |
| 66 | ||
| 67 | The `id` is dropped, both `span`s vanish but their text `TODO` survives and merges with | |
| 68 | ` Write the ` into one collapsed run, and `<b>` (not ignored) splits the run and emits its | |
| 69 | own open/close. | |
| 70 | ||
| 71 | ## Reference implementation | |
| 72 | ||
| 73 | The canonical implementation is `skeleton()` in orgo's `src/skeleton.rs` (Rust). A port is | |
| 74 | correct when, for every case in `cases/`, feeding orgo's own `<name>.html` back through | |
| 75 | the port reproduces `<name>.skeleton` byte for byte. That is the first test any port | |
| 76 | should run — it isolates a skeleton bug from a renderer bug. | |
cases/blocks.html added +32
| @@ -0,0 +1,32 @@ | ||
| 1 | <h2 id="quote">Quote</h2> | |
| 2 | <blockquote> | |
| 3 | <p>A quoted paragraph with <em>markup</em>.</p> | |
| 4 | <p>And a second paragraph.</p> | |
| 5 | </blockquote> | |
| 6 | <h2 id="center">Center</h2> | |
| 7 | <div class="center"> | |
| 8 | <p>Centred text.</p> | |
| 9 | </div> | |
| 10 | <h2 id="example">Example</h2> | |
| 11 | <pre>Verbatim *not bold* text. | |
| 12 | Indentation preserved.</pre> | |
| 13 | <h2 id="export">Export</h2> | |
| 14 | <aside class="raw">Raw HTML passes through.</aside> | |
| 15 | <h2 id="source">Source</h2> | |
| 16 | <pre><code class="language-python highlight"><span class="source python"><span class="meta function python"><span class="storage type function python">def</span> <span class="entity name function python"><span class="meta generic-name python">greet</span></span></span><span class="meta function parameters python"><span class="punctuation section parameters begin python">(</span></span><span class="meta function parameters python"><span class="variable parameter python">name</span><span class="punctuation section parameters end python">)</span></span><span class="meta function python"><span class="punctuation section function begin python">:</span></span> | |
| 17 | <span class="keyword control flow return python">return</span> <span class="storage type string python">f</span><span class="meta string interpolated python"><span class="string quoted double python"><span class="punctuation definition string begin python">"</span></span></span><span class="meta string interpolated python"><span class="string quoted double python">hello </span><span class="meta interpolation python"><span class="punctuation section interpolation begin python">{</span><span class="source python embedded"><span class="meta qualified-name python"><span class="meta generic-name python">name</span></span></span></span><span class="meta interpolation python"><span class="punctuation section interpolation end python">}</span></span><span class="string quoted double python"><span class="punctuation definition string end python">"</span></span></span></span></code></pre> | |
| 18 | <pre><code class="language-none">plain block, no language</code></pre> | |
| 19 | <h2 id="nested">Nested</h2> | |
| 20 | <blockquote> | |
| 21 | <p>A quote containing a source block:</p> | |
| 22 | <pre><code class="language-sh highlight"><span class="source shell bash"><span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> hi</span></span></code></pre> | |
| 23 | </blockquote> | |
| 24 | <h2 id="verse">Verse</h2> | |
| 25 | <p class="verse"> | |
| 26 | Line breaks are the point<br> | |
| 27 | and indentation survives. | |
| 28 | </p> | |
| 29 | <h2 id="a-named-special-block">A named special block</h2> | |
| 30 | <div class="note"> | |
| 31 | <p>Contents are <strong>org</strong>, not literal text.</p> | |
| 32 | </div> | |
cases/blocks.org added +66
| @@ -0,0 +1,66 @@ | ||
| 1 | #+TITLE: Blocks | |
| 2 | ||
| 3 | * Quote | |
| 4 | ||
| 5 | #+BEGIN_QUOTE | |
| 6 | A quoted paragraph with /markup/. | |
| 7 | ||
| 8 | And a second paragraph. | |
| 9 | #+END_QUOTE | |
| 10 | ||
| 11 | * Center | |
| 12 | ||
| 13 | #+BEGIN_CENTER | |
| 14 | Centred text. | |
| 15 | #+END_CENTER | |
| 16 | ||
| 17 | * Example | |
| 18 | ||
| 19 | #+BEGIN_EXAMPLE | |
| 20 | Verbatim *not bold* text. | |
| 21 | Indentation preserved. | |
| 22 | #+END_EXAMPLE | |
| 23 | ||
| 24 | * Export | |
| 25 | ||
| 26 | #+BEGIN_EXPORT html | |
| 27 | <aside class="raw">Raw HTML passes through.</aside> | |
| 28 | #+END_EXPORT | |
| 29 | ||
| 30 | #+BEGIN_EXPORT latex | |
| 31 | \emph{A non-HTML backend is dropped.} | |
| 32 | #+END_EXPORT | |
| 33 | ||
| 34 | * Source | |
| 35 | ||
| 36 | #+BEGIN_SRC python | |
| 37 | def greet(name): | |
| 38 | return f"hello {name}" | |
| 39 | #+END_SRC | |
| 40 | ||
| 41 | #+BEGIN_SRC | |
| 42 | plain block, no language | |
| 43 | #+END_SRC | |
| 44 | ||
| 45 | * Nested | |
| 46 | ||
| 47 | #+BEGIN_QUOTE | |
| 48 | A quote containing a source block: | |
| 49 | ||
| 50 | #+BEGIN_SRC sh | |
| 51 | echo hi | |
| 52 | #+END_SRC | |
| 53 | #+END_QUOTE | |
| 54 | ||
| 55 | * Verse | |
| 56 | ||
| 57 | #+BEGIN_VERSE | |
| 58 | Line breaks are the point | |
| 59 | and indentation survives. | |
| 60 | #+END_VERSE | |
| 61 | ||
| 62 | * A named special block | |
| 63 | ||
| 64 | #+BEGIN_NOTE | |
| 65 | Contents are *org*, not literal text. | |
| 66 | #+END_NOTE | |
cases/blocks.skeleton added +77
| @@ -0,0 +1,77 @@ | ||
| 1 | <h2> | |
| 2 | "Quote" | |
| 3 | </h2> | |
| 4 | <blockquote> | |
| 5 | <p> | |
| 6 | "A quoted paragraph with" | |
| 7 | <em> | |
| 8 | "markup" | |
| 9 | </em> | |
| 10 | "." | |
| 11 | </p> | |
| 12 | <p> | |
| 13 | "And a second paragraph." | |
| 14 | </p> | |
| 15 | </blockquote> | |
| 16 | <h2> | |
| 17 | "Center" | |
| 18 | </h2> | |
| 19 | <p> | |
| 20 | "Centred text." | |
| 21 | </p> | |
| 22 | <h2> | |
| 23 | "Example" | |
| 24 | </h2> | |
| 25 | <pre> | |
| 26 | "Verbatim *not bold* text. Indentation preserved." | |
| 27 | </pre> | |
| 28 | <h2> | |
| 29 | "Export" | |
| 30 | </h2> | |
| 31 | <aside> | |
| 32 | "Raw HTML passes through." | |
| 33 | </aside> | |
| 34 | <h2> | |
| 35 | "Source" | |
| 36 | </h2> | |
| 37 | <pre> | |
| 38 | <code> | |
| 39 | "def greet(name): return f\"hello {name}\"" | |
| 40 | </code> | |
| 41 | </pre> | |
| 42 | <pre> | |
| 43 | <code> | |
| 44 | "plain block, no language" | |
| 45 | </code> | |
| 46 | </pre> | |
| 47 | <h2> | |
| 48 | "Nested" | |
| 49 | </h2> | |
| 50 | <blockquote> | |
| 51 | <p> | |
| 52 | "A quote containing a source block:" | |
| 53 | </p> | |
| 54 | <pre> | |
| 55 | <code> | |
| 56 | "echo hi" | |
| 57 | </code> | |
| 58 | </pre> | |
| 59 | </blockquote> | |
| 60 | <h2> | |
| 61 | "Verse" | |
| 62 | </h2> | |
| 63 | <p> | |
| 64 | "Line breaks are the point" | |
| 65 | <br> | |
| 66 | "and indentation survives." | |
| 67 | </p> | |
| 68 | <h2> | |
| 69 | "A named special block" | |
| 70 | </h2> | |
| 71 | <p> | |
| 72 | "Contents are" | |
| 73 | <strong> | |
| 74 | "org" | |
| 75 | </strong> | |
| 76 | ", not literal text." | |
| 77 | </p> | |
cases/core.html added +15
| @@ -0,0 +1,15 @@ | ||
| 1 | <p>Intro paragraph with a bare URL <a href="https://example.com">https://example.com</a> and some <code>inline code</code>.</p> | |
| 2 | <h2 id="ordered-and-checked">Ordered and checked</h2> | |
| 3 | <ol> | |
| 4 | <li>first item</li> | |
| 5 | <li>second item with <em>emphasis</em></li> | |
| 6 | </ol> | |
| 7 | <ul> | |
| 8 | <li class="off"><code>[ ]</code> todo item</li> | |
| 9 | <li class="on"><code>[X]</code> done item</li> | |
| 10 | </ul> | |
| 11 | <h2 id="links-and-code">Links and code</h2> | |
| 12 | <p>An external <a href="https://example.org">site</a> and a bare <a href="https://bare.example">https://bare.example</a>.</p> | |
| 13 | <pre><code class="language-rust highlight"><span class="source rust"><span class="meta function rust"><span class="meta function rust"><span class="storage type function rust">fn</span> </span><span class="entity name function rust">main</span></span><span class="meta function rust"><span class="meta function parameters rust"><span class="punctuation section parameters begin rust">(</span></span><span class="meta function rust"><span class="meta function parameters rust"><span class="punctuation section parameters end rust">)</span></span></span></span><span class="meta function rust"> </span><span class="meta function rust"><span class="meta block rust"><span class="punctuation section block begin rust">{</span> | |
| 14 | <span class="support macro rust">println!</span><span class="meta group rust"><span class="punctuation section group begin rust">(</span></span><span class="meta group rust"><span class="string quoted double rust"><span class="punctuation definition string begin rust">"</span>hello<span class="punctuation definition string end rust">"</span></span></span><span class="meta group rust"><span class="punctuation section group end rust">)</span></span><span class="punctuation terminator rust">;</span> | |
| 15 | </span><span class="meta block rust"><span class="punctuation section block end rust">}</span></span></span></span></code></pre> | |
cases/core.org added +21
| @@ -0,0 +1,21 @@ | ||
| 1 | #+TITLE: Core Constructs | |
| 2 | ||
| 3 | Intro paragraph with a bare URL https://example.com and some ~inline code~. | |
| 4 | ||
| 5 | * Ordered and checked | |
| 6 | ||
| 7 | 1. first item | |
| 8 | 2. second item with /emphasis/ | |
| 9 | ||
| 10 | [ ] todo item | |
| 11 | [X] done item | |
| 12 | ||
| 13 | * Links and code | |
| 14 | ||
| 15 | An external [[https://example.org][site]] and a bare [[https://bare.example]]. | |
| 16 | ||
| 17 | #+BEGIN_SRC rust | |
| 18 | fn main() { | |
| 19 | println!("hello"); | |
| 20 | } | |
| 21 | #+END_SRC | |
cases/core.skeleton added +58
| @@ -0,0 +1,58 @@ | ||
| 1 | <p> | |
| 2 | "Intro paragraph with a bare URL" | |
| 3 | <a href="https://example.com"> | |
| 4 | "https://example.com" | |
| 5 | </a> | |
| 6 | "and some" | |
| 7 | <code> | |
| 8 | "inline code" | |
| 9 | </code> | |
| 10 | "." | |
| 11 | </p> | |
| 12 | <h2> | |
| 13 | "Ordered and checked" | |
| 14 | </h2> | |
| 15 | <ol> | |
| 16 | <li> | |
| 17 | "first item" | |
| 18 | </li> | |
| 19 | <li> | |
| 20 | "second item with" | |
| 21 | <em> | |
| 22 | "emphasis" | |
| 23 | </em> | |
| 24 | </li> | |
| 25 | </ol> | |
| 26 | <ul> | |
| 27 | <li> | |
| 28 | <code> | |
| 29 | "[ ]" | |
| 30 | </code> | |
| 31 | "todo item" | |
| 32 | </li> | |
| 33 | <li> | |
| 34 | <code> | |
| 35 | "[X]" | |
| 36 | </code> | |
| 37 | "done item" | |
| 38 | </li> | |
| 39 | </ul> | |
| 40 | <h2> | |
| 41 | "Links and code" | |
| 42 | </h2> | |
| 43 | <p> | |
| 44 | "An external" | |
| 45 | <a href="https://example.org"> | |
| 46 | "site" | |
| 47 | </a> | |
| 48 | "and a bare" | |
| 49 | <a href="https://bare.example"> | |
| 50 | "https://bare.example" | |
| 51 | </a> | |
| 52 | "." | |
| 53 | </p> | |
| 54 | <pre> | |
| 55 | <code> | |
| 56 | "fn main() { println!(\"hello\"); }" | |
| 57 | </code> | |
| 58 | </pre> | |
cases/elements.html added +28
| @@ -0,0 +1,28 @@ | ||
| 1 | <h2 id="code-and-tables">Code and tables</h2> | |
| 2 | <pre><code class="language-rust highlight"><span class="source rust"><span class="meta function rust"><span class="meta function rust"><span class="storage type function rust">fn</span> </span><span class="entity name function rust">main</span></span><span class="meta function rust"><span class="meta function parameters rust"><span class="punctuation section parameters begin rust">(</span></span><span class="meta function rust"><span class="meta function parameters rust"><span class="punctuation section parameters end rust">)</span></span></span></span><span class="meta function rust"> </span><span class="meta function rust"><span class="meta block rust"><span class="punctuation section block begin rust">{</span> | |
| 3 | <span class="support macro rust">println!</span><span class="meta group rust"><span class="punctuation section group begin rust">(</span></span><span class="meta group rust"><span class="string quoted double rust"><span class="punctuation definition string begin rust">"</span>hello<span class="punctuation definition string end rust">"</span></span></span><span class="meta group rust"><span class="punctuation section group end rust">)</span></span><span class="punctuation terminator rust">;</span> | |
| 4 | </span><span class="meta block rust"><span class="punctuation section block end rust">}</span></span></span></span></code></pre> | |
| 5 | <table> | |
| 6 | <thead> | |
| 7 | <tr><th>Name</th><th>Score</th></tr> | |
| 8 | </thead> | |
| 9 | <tbody> | |
| 10 | <tr><td>alpha</td><td>10</td></tr> | |
| 11 | <tr><td>beta</td><td>20</td></tr> | |
| 12 | </tbody> | |
| 13 | </table> | |
| 14 | <h2 id="links-and-footnotes">Links and footnotes</h2> | |
| 15 | <p>An external link: <a href="https://example.com">Example</a> and an id link <a href="#abc-123">abc-123</a>.</p> | |
| 16 | <p>Text with a footnote reference.<sup class="footnote-ref"><a id="fnr-1" href="#fn-1">1</a></sup></p> | |
| 17 | <h2 id="blocks">Blocks</h2> | |
| 18 | <blockquote> | |
| 19 | <p>A quoted paragraph.</p> | |
| 20 | </blockquote> | |
| 21 | <hr> | |
| 22 | <section class="footnotes" aria-label="Footnotes"> | |
| 23 | <hr> | |
| 24 | <ol> | |
| 25 | <li id="fn-1"><p>The footnote definition.</p> | |
| 26 | <a class="footnote-back" href="#fnr-1" aria-label="Back to reference 1">↩</a></li> | |
| 27 | </ol> | |
| 28 | </section> | |
cases/elements.org added +31
| @@ -0,0 +1,31 @@ | ||
| 1 | #+TITLE: Element Sampler | |
| 2 | #+FILETAGS: :sample: | |
| 3 | ||
| 4 | * Code and tables | |
| 5 | ||
| 6 | #+BEGIN_SRC rust | |
| 7 | fn main() { | |
| 8 | println!("hello"); | |
| 9 | } | |
| 10 | #+END_SRC | |
| 11 | ||
| 12 | | Name | Score | | |
| 13 | |-------+-------| | |
| 14 | | alpha | 10 | | |
| 15 | | beta | 20 | | |
| 16 | ||
| 17 | * Links and footnotes | |
| 18 | ||
| 19 | An external link: [[https://example.com][Example]] and an id link [[id:abc-123]]. | |
| 20 | ||
| 21 | Text with a footnote reference.[fn:1] | |
| 22 | ||
| 23 | [fn:1] The footnote definition. | |
| 24 | ||
| 25 | * Blocks | |
| 26 | ||
| 27 | #+BEGIN_QUOTE | |
| 28 | A quoted paragraph. | |
| 29 | #+END_QUOTE | |
| 30 | ||
| 31 | ---- | |
cases/elements.skeleton added +82
| @@ -0,0 +1,82 @@ | ||
| 1 | <h2> | |
| 2 | "Code and tables" | |
| 3 | </h2> | |
| 4 | <pre> | |
| 5 | <code> | |
| 6 | "fn main() { println!(\"hello\"); }" | |
| 7 | </code> | |
| 8 | </pre> | |
| 9 | <table> | |
| 10 | <thead> | |
| 11 | <tr> | |
| 12 | <th> | |
| 13 | "Name" | |
| 14 | </th> | |
| 15 | <th> | |
| 16 | "Score" | |
| 17 | </th> | |
| 18 | </tr> | |
| 19 | </thead> | |
| 20 | <tbody> | |
| 21 | <tr> | |
| 22 | <td> | |
| 23 | "alpha" | |
| 24 | </td> | |
| 25 | <td> | |
| 26 | "10" | |
| 27 | </td> | |
| 28 | </tr> | |
| 29 | <tr> | |
| 30 | <td> | |
| 31 | "beta" | |
| 32 | </td> | |
| 33 | <td> | |
| 34 | "20" | |
| 35 | </td> | |
| 36 | </tr> | |
| 37 | </tbody> | |
| 38 | </table> | |
| 39 | <h2> | |
| 40 | "Links and footnotes" | |
| 41 | </h2> | |
| 42 | <p> | |
| 43 | "An external link:" | |
| 44 | <a href="https://example.com"> | |
| 45 | "Example" | |
| 46 | </a> | |
| 47 | "and an id link" | |
| 48 | <a href="#abc-123"> | |
| 49 | "abc-123" | |
| 50 | </a> | |
| 51 | "." | |
| 52 | </p> | |
| 53 | <p> | |
| 54 | "Text with a footnote reference." | |
| 55 | <sup> | |
| 56 | <a href="#fn-1"> | |
| 57 | "1" | |
| 58 | </a> | |
| 59 | </sup> | |
| 60 | </p> | |
| 61 | <h2> | |
| 62 | "Blocks" | |
| 63 | </h2> | |
| 64 | <blockquote> | |
| 65 | <p> | |
| 66 | "A quoted paragraph." | |
| 67 | </p> | |
| 68 | </blockquote> | |
| 69 | <hr> | |
| 70 | <section> | |
| 71 | <hr> | |
| 72 | <ol> | |
| 73 | <li> | |
| 74 | <p> | |
| 75 | "The footnote definition." | |
| 76 | </p> | |
| 77 | <a href="#fnr-1"> | |
| 78 | "↩" | |
| 79 | </a> | |
| 80 | </li> | |
| 81 | </ol> | |
| 82 | </section> | |
cases/footnote.html added +12
| @@ -0,0 +1,12 @@ | ||
| 1 | <p>Text with a reference.<sup class="footnote-ref"><a id="fnr-1" href="#fn-1">1</a></sup> And a second one.<sup class="footnote-ref"><a id="fnr-2" href="#fn-2">2</a></sup></p> | |
| 2 | <p>An inline footnote.<sup class="footnote-ref"><a id="fnr-3" href="#fn-3">3</a></sup></p> | |
| 3 | <section class="footnotes" aria-label="Footnotes"> | |
| 4 | <hr> | |
| 5 | <ol> | |
| 6 | <li id="fn-1"><p>The first definition.</p> | |
| 7 | <a class="footnote-back" href="#fnr-1" aria-label="Back to reference 1">↩</a></li> | |
| 8 | <li id="fn-2"><p>The second definition, with <em>emphasis</em>.</p> | |
| 9 | <a class="footnote-back" href="#fnr-2" aria-label="Back to reference 2">↩</a></li> | |
| 10 | <li id="fn-3">defined right here <a class="footnote-back" href="#fnr-3" aria-label="Back to reference 3">↩</a></li> | |
| 11 | </ol> | |
| 12 | </section> | |
cases/footnote.org added +8
| @@ -0,0 +1,8 @@ | ||
| 1 | #+TITLE: Footnote Sample | |
| 2 | ||
| 3 | Text with a reference.[fn:1] And a second one.[fn:2] | |
| 4 | ||
| 5 | An inline footnote.[fn:3:defined right here] | |
| 6 | ||
| 7 | [fn:1] The first definition. | |
| 8 | [fn:2] The second definition, with /emphasis/. | |
cases/footnote.skeleton added +53
| @@ -0,0 +1,53 @@ | ||
| 1 | <p> | |
| 2 | "Text with a reference." | |
| 3 | <sup> | |
| 4 | <a href="#fn-1"> | |
| 5 | "1" | |
| 6 | </a> | |
| 7 | </sup> | |
| 8 | "And a second one." | |
| 9 | <sup> | |
| 10 | <a href="#fn-2"> | |
| 11 | "2" | |
| 12 | </a> | |
| 13 | </sup> | |
| 14 | </p> | |
| 15 | <p> | |
| 16 | "An inline footnote." | |
| 17 | <sup> | |
| 18 | <a href="#fn-3"> | |
| 19 | "3" | |
| 20 | </a> | |
| 21 | </sup> | |
| 22 | </p> | |
| 23 | <section> | |
| 24 | <hr> | |
| 25 | <ol> | |
| 26 | <li> | |
| 27 | <p> | |
| 28 | "The first definition." | |
| 29 | </p> | |
| 30 | <a href="#fnr-1"> | |
| 31 | "↩" | |
| 32 | </a> | |
| 33 | </li> | |
| 34 | <li> | |
| 35 | <p> | |
| 36 | "The second definition, with" | |
| 37 | <em> | |
| 38 | "emphasis" | |
| 39 | </em> | |
| 40 | "." | |
| 41 | </p> | |
| 42 | <a href="#fnr-2"> | |
| 43 | "↩" | |
| 44 | </a> | |
| 45 | </li> | |
| 46 | <li> | |
| 47 | "defined right here" | |
| 48 | <a href="#fnr-3"> | |
| 49 | "↩" | |
| 50 | </a> | |
| 51 | </li> | |
| 52 | </ol> | |
| 53 | </section> | |
cases/headings.html added +10
| @@ -0,0 +1,10 @@ | ||
| 1 | <h2 id="write-parser"><span class="todo TODO">TODO</span> <span class="priority">[#A]</span> Write the parser <span class="tag">work</span> <span class="tag">rust</span></h2> | |
| 2 | <p>A heading carrying a keyword, a priority, tags and a property drawer.</p> | |
| 3 | <h3 id="nested-and-finished"><span class="done DONE">DONE</span> Nested and finished</h3> | |
| 4 | <p>Sub-headings nest by star count.</p> | |
| 5 | <h3 id="priority-without-a-keyword"><span class="priority">[#C]</span> Priority without a keyword</h3> | |
| 6 | <p>A priority cookie can stand alone.</p> | |
| 7 | <h2 id="todos-are-not-a-keyword">TODOs are not a keyword</h2> | |
| 8 | <p>The word boundary matters: this heading has no TODO keyword.</p> | |
| 9 | <h2><span class="done DONE">DONE</span> </h2> | |
| 10 | <p>A keyword with no title at all.</p> | |
cases/headings.org added +20
| @@ -0,0 +1,20 @@ | ||
| 1 | #+TITLE: Heading Metadata | |
| 2 | ||
| 3 | * TODO [#A] Write the parser :work:rust: | |
| 4 | :PROPERTIES: | |
| 5 | :CUSTOM_ID: write-parser | |
| 6 | :OWNER: nobody | |
| 7 | :END: | |
| 8 | A heading carrying a keyword, a priority, tags and a property drawer. | |
| 9 | ||
| 10 | ** DONE Nested and finished | |
| 11 | Sub-headings nest by star count. | |
| 12 | ||
| 13 | ** [#C] Priority without a keyword | |
| 14 | A priority cookie can stand alone. | |
| 15 | ||
| 16 | * TODOs are not a keyword | |
| 17 | The word boundary matters: this heading has no TODO keyword. | |
| 18 | ||
| 19 | * DONE | |
| 20 | A keyword with no title at all. | |
cases/headings.skeleton added +30
| @@ -0,0 +1,30 @@ | ||
| 1 | <h2> | |
| 2 | "TODO [#A] Write the parser work rust" | |
| 3 | </h2> | |
| 4 | <p> | |
| 5 | "A heading carrying a keyword, a priority, tags and a property drawer." | |
| 6 | </p> | |
| 7 | <h3> | |
| 8 | "DONE Nested and finished" | |
| 9 | </h3> | |
| 10 | <p> | |
| 11 | "Sub-headings nest by star count." | |
| 12 | </p> | |
| 13 | <h3> | |
| 14 | "[#C] Priority without a keyword" | |
| 15 | </h3> | |
| 16 | <p> | |
| 17 | "A priority cookie can stand alone." | |
| 18 | </p> | |
| 19 | <h2> | |
| 20 | "TODOs are not a keyword" | |
| 21 | </h2> | |
| 22 | <p> | |
| 23 | "The word boundary matters: this heading has no TODO keyword." | |
| 24 | </p> | |
| 25 | <h2> | |
| 26 | "DONE" | |
| 27 | </h2> | |
| 28 | <p> | |
| 29 | "A keyword with no title at all." | |
| 30 | </p> | |
cases/images.html added +10
| @@ -0,0 +1,10 @@ | ||
| 1 | <h2 id="bare-image">Bare image</h2> | |
| 2 | <p><img src="diagram.png" alt=""></p> | |
| 3 | <h2 id="captioned-figure">Captioned figure</h2> | |
| 4 | <figure><img src="pipeline.svg" alt="The pipeline, end to end" width="640" class="diagram"><figcaption><span class="figure-number">Figure 1: </span>The pipeline, end to end</figcaption></figure> | |
| 5 | <h2 id="caption-with-markup">Caption with markup</h2> | |
| 6 | <figure><img src="chart.png" alt="A stylised chart"><figcaption><span class="figure-number">Figure 2: </span>A <em>stylised</em> chart</figcaption></figure> | |
| 7 | <h2 id="quoted-attribute-values">Quoted attribute values</h2> | |
| 8 | <figure><img src="cat.jpg" alt="a cat, sitting" loading="lazy"></figure> | |
| 9 | <h2 id="image-with-a-description-is-a-link">Image with a description is a link</h2> | |
| 10 | <p><a href="diagram.png">the diagram</a></p> | |
cases/images.org added +25
| @@ -0,0 +1,25 @@ | ||
| 1 | #+TITLE: Images | |
| 2 | ||
| 3 | * Bare image | |
| 4 | ||
| 5 | [[file:diagram.png]] | |
| 6 | ||
| 7 | * Captioned figure | |
| 8 | ||
| 9 | #+CAPTION: The pipeline, end to end | |
| 10 | #+ATTR_HTML: :width 640 :class diagram | |
| 11 | [[file:pipeline.svg]] | |
| 12 | ||
| 13 | * Caption with markup | |
| 14 | ||
| 15 | #+CAPTION: A /stylised/ chart | |
| 16 | [[file:chart.png]] | |
| 17 | ||
| 18 | * Quoted attribute values | |
| 19 | ||
| 20 | #+ATTR_HTML: :alt "a cat, sitting" :loading lazy | |
| 21 | [[file:cat.jpg]] | |
| 22 | ||
| 23 | * Image with a description is a link | |
| 24 | ||
| 25 | [[file:diagram.png][the diagram]] | |
cases/images.skeleton added +42
| @@ -0,0 +1,42 @@ | ||
| 1 | <h2> | |
| 2 | "Bare image" | |
| 3 | </h2> | |
| 4 | <p> | |
| 5 | <img src="diagram.png"> | |
| 6 | </p> | |
| 7 | <h2> | |
| 8 | "Captioned figure" | |
| 9 | </h2> | |
| 10 | <figure> | |
| 11 | <img src="pipeline.svg"> | |
| 12 | <figcaption> | |
| 13 | "Figure 1: The pipeline, end to end" | |
| 14 | </figcaption> | |
| 15 | </figure> | |
| 16 | <h2> | |
| 17 | "Caption with markup" | |
| 18 | </h2> | |
| 19 | <figure> | |
| 20 | <img src="chart.png"> | |
| 21 | <figcaption> | |
| 22 | "Figure 2: A" | |
| 23 | <em> | |
| 24 | "stylised" | |
| 25 | </em> | |
| 26 | "chart" | |
| 27 | </figcaption> | |
| 28 | </figure> | |
| 29 | <h2> | |
| 30 | "Quoted attribute values" | |
| 31 | </h2> | |
| 32 | <figure> | |
| 33 | <img src="cat.jpg"> | |
| 34 | </figure> | |
| 35 | <h2> | |
| 36 | "Image with a description is a link" | |
| 37 | </h2> | |
| 38 | <p> | |
| 39 | <a href="diagram.png"> | |
| 40 | "the diagram" | |
| 41 | </a> | |
| 42 | </p> | |
cases/lists.html added +44
| @@ -0,0 +1,44 @@ | ||
| 1 | <h2 id="nesting">Nesting</h2> | |
| 2 | <ul> | |
| 3 | <li>outer item<ul> | |
| 4 | <li>inner item<ul> | |
| 5 | <li>deepest item</li> | |
| 6 | </ul> | |
| 7 | </li> | |
| 8 | <li>second inner</li> | |
| 9 | </ul> | |
| 10 | </li> | |
| 11 | <li>second outer</li> | |
| 12 | </ul> | |
| 13 | <h2 id="ordered">Ordered</h2> | |
| 14 | <ol> | |
| 15 | <li>first</li> | |
| 16 | <li>second<ol> | |
| 17 | <li>second point one</li> | |
| 18 | <li>second point two</li> | |
| 19 | </ol> | |
| 20 | </li> | |
| 21 | <li>third</li> | |
| 22 | </ol> | |
| 23 | <h2 id="checkboxes">Checkboxes</h2> | |
| 24 | <ul> | |
| 25 | <li class="off"><code>[ ]</code> not done</li> | |
| 26 | <li class="on"><code>[X]</code> done</li> | |
| 27 | <li class="trans"><code>[-]</code> partially done</li> | |
| 28 | </ul> | |
| 29 | <h2 id="description">Description</h2> | |
| 30 | <dl> | |
| 31 | <dt>term one</dt> | |
| 32 | <dd>the first definition</dd> | |
| 33 | <dt>term two</dt> | |
| 34 | <dd>the second definition, which is soft-wrapped across two lines</dd> | |
| 35 | <dt><em>marked up</em> term</dt> | |
| 36 | <dd>definitions hold inline markup</dd> | |
| 37 | </dl> | |
| 38 | <h2 id="multi-paragraph-items">Multi-paragraph items</h2> | |
| 39 | <ul> | |
| 40 | <li><p>an item whose body has two paragraphs</p> | |
| 41 | <p>the second paragraph, indented under the bullet</p> | |
| 42 | </li> | |
| 43 | <li>a plain sibling</li> | |
| 44 | </ul> | |
cases/lists.org added +38
| @@ -0,0 +1,38 @@ | ||
| 1 | #+TITLE: Lists | |
| 2 | ||
| 3 | * Nesting | |
| 4 | ||
| 5 | outer item | |
| 6 | - inner item | |
| 7 | - deepest item | |
| 8 | - second inner | |
| 9 | second outer | |
| 10 | ||
| 11 | * Ordered | |
| 12 | ||
| 13 | 1. first | |
| 14 | 2. second | |
| 15 | 1. second point one | |
| 16 | 2. second point two | |
| 17 | 3. third | |
| 18 | ||
| 19 | * Checkboxes | |
| 20 | ||
| 21 | [ ] not done | |
| 22 | [X] done | |
| 23 | [-] partially done | |
| 24 | ||
| 25 | * Description | |
| 26 | ||
| 27 | term one :: the first definition | |
| 28 | term two :: the second definition, which is | |
| 29 | soft-wrapped across two lines | |
| 30 | /marked up/ term :: definitions hold inline markup | |
| 31 | ||
| 32 | * Multi-paragraph items | |
| 33 | ||
| 34 | an item whose body has two paragraphs | |
| 35 | ||
| 36 | the second paragraph, indented under the bullet | |
| 37 | ||
| 38 | a plain sibling | |
cases/lists.skeleton added +111
| @@ -0,0 +1,111 @@ | ||
| 1 | <h2> | |
| 2 | "Nesting" | |
| 3 | </h2> | |
| 4 | <ul> | |
| 5 | <li> | |
| 6 | "outer item" | |
| 7 | <ul> | |
| 8 | <li> | |
| 9 | "inner item" | |
| 10 | <ul> | |
| 11 | <li> | |
| 12 | "deepest item" | |
| 13 | </li> | |
| 14 | </ul> | |
| 15 | </li> | |
| 16 | <li> | |
| 17 | "second inner" | |
| 18 | </li> | |
| 19 | </ul> | |
| 20 | </li> | |
| 21 | <li> | |
| 22 | "second outer" | |
| 23 | </li> | |
| 24 | </ul> | |
| 25 | <h2> | |
| 26 | "Ordered" | |
| 27 | </h2> | |
| 28 | <ol> | |
| 29 | <li> | |
| 30 | "first" | |
| 31 | </li> | |
| 32 | <li> | |
| 33 | "second" | |
| 34 | <ol> | |
| 35 | <li> | |
| 36 | "second point one" | |
| 37 | </li> | |
| 38 | <li> | |
| 39 | "second point two" | |
| 40 | </li> | |
| 41 | </ol> | |
| 42 | </li> | |
| 43 | <li> | |
| 44 | "third" | |
| 45 | </li> | |
| 46 | </ol> | |
| 47 | <h2> | |
| 48 | "Checkboxes" | |
| 49 | </h2> | |
| 50 | <ul> | |
| 51 | <li> | |
| 52 | <code> | |
| 53 | "[ ]" | |
| 54 | </code> | |
| 55 | "not done" | |
| 56 | </li> | |
| 57 | <li> | |
| 58 | <code> | |
| 59 | "[X]" | |
| 60 | </code> | |
| 61 | "done" | |
| 62 | </li> | |
| 63 | <li> | |
| 64 | <code> | |
| 65 | "[-]" | |
| 66 | </code> | |
| 67 | "partially done" | |
| 68 | </li> | |
| 69 | </ul> | |
| 70 | <h2> | |
| 71 | "Description" | |
| 72 | </h2> | |
| 73 | <dl> | |
| 74 | <dt> | |
| 75 | "term one" | |
| 76 | </dt> | |
| 77 | <dd> | |
| 78 | "the first definition" | |
| 79 | </dd> | |
| 80 | <dt> | |
| 81 | "term two" | |
| 82 | </dt> | |
| 83 | <dd> | |
| 84 | "the second definition, which is soft-wrapped across two lines" | |
| 85 | </dd> | |
| 86 | <dt> | |
| 87 | <em> | |
| 88 | "marked up" | |
| 89 | </em> | |
| 90 | "term" | |
| 91 | </dt> | |
| 92 | <dd> | |
| 93 | "definitions hold inline markup" | |
| 94 | </dd> | |
| 95 | </dl> | |
| 96 | <h2> | |
| 97 | "Multi-paragraph items" | |
| 98 | </h2> | |
| 99 | <ul> | |
| 100 | <li> | |
| 101 | <p> | |
| 102 | "an item whose body has two paragraphs" | |
| 103 | </p> | |
| 104 | <p> | |
| 105 | "the second paragraph, indented under the bullet" | |
| 106 | </p> | |
| 107 | </li> | |
| 108 | <li> | |
| 109 | "a plain sibling" | |
| 110 | </li> | |
| 111 | </ul> | |
cases/minimal.html added +10
| @@ -0,0 +1,10 @@ | ||
| 1 | <p>A single paragraph of preamble text before any heading.</p> | |
| 2 | <h2 id="first">First Heading</h2> | |
| 3 | <p>Some body text with <strong>bold</strong>, <em>italic</em>, and <code class="verbatim">verbatim</code>.</p> | |
| 4 | <h3 id="a-subheading">A Subheading <span class="tag">tag1</span> <span class="tag">tag2</span></h3> | |
| 5 | <ul> | |
| 6 | <li>an unordered item</li> | |
| 7 | <li>another with a checkbox [ ]</li> | |
| 8 | </ul> | |
| 9 | <h2 id="second-heading">Second Heading</h2> | |
| 10 | <p>See <a href="#first">the first heading</a>.</p> | |
cases/minimal.org added +20
| @@ -0,0 +1,20 @@ | ||
| 1 | #+TITLE: Minimal Fixture | |
| 2 | #+DATE: 2026-08-08 | |
| 3 | #+AUTHOR: Owner | |
| 4 | ||
| 5 | A single paragraph of preamble text before any heading. | |
| 6 | ||
| 7 | * First Heading | |
| 8 | :PROPERTIES: | |
| 9 | :CUSTOM_ID: first | |
| 10 | :END: | |
| 11 | ||
| 12 | Some body text with *bold*, /italic/, and =verbatim=. | |
| 13 | ||
| 14 | ** A Subheading :tag1:tag2: | |
| 15 | ||
| 16 | an unordered item | |
| 17 | another with a checkbox [ ] | |
| 18 | ||
| 19 | * Second Heading | |
| 20 | See [[#first][the first heading]]. | |
cases/minimal.skeleton added +42
| @@ -0,0 +1,42 @@ | ||
| 1 | <p> | |
| 2 | "A single paragraph of preamble text before any heading." | |
| 3 | </p> | |
| 4 | <h2> | |
| 5 | "First Heading" | |
| 6 | </h2> | |
| 7 | <p> | |
| 8 | "Some body text with" | |
| 9 | <strong> | |
| 10 | "bold" | |
| 11 | </strong> | |
| 12 | "," | |
| 13 | <em> | |
| 14 | "italic" | |
| 15 | </em> | |
| 16 | ", and" | |
| 17 | <code> | |
| 18 | "verbatim" | |
| 19 | </code> | |
| 20 | "." | |
| 21 | </p> | |
| 22 | <h3> | |
| 23 | "A Subheading tag1 tag2" | |
| 24 | </h3> | |
| 25 | <ul> | |
| 26 | <li> | |
| 27 | "an unordered item" | |
| 28 | </li> | |
| 29 | <li> | |
| 30 | "another with a checkbox [ ]" | |
| 31 | </li> | |
| 32 | </ul> | |
| 33 | <h2> | |
| 34 | "Second Heading" | |
| 35 | </h2> | |
| 36 | <p> | |
| 37 | "See" | |
| 38 | <a href="#first"> | |
| 39 | "the first heading" | |
| 40 | </a> | |
| 41 | "." | |
| 42 | </p> | |
cases/outofscope.html added +21
| @@ -0,0 +1,21 @@ | ||
| 1 | <p>Every construct here is on the explicit "Not supported" list in the org-support guide. The contract is not that we handle them — it is that they degrade predictably and never crash the build.</p> | |
| 2 | <h2 id="babel">Babel</h2> | |
| 3 | <pre><code class="language-sh highlight"><span class="source shell bash"><span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">"</span>the block renders; :results is never executed<span class="punctuation definition string end shell">"</span></span></span></span></code></pre> | |
| 4 | <h2 id="table-formulas">Table formulas</h2> | |
| 5 | <table> | |
| 6 | <thead> | |
| 7 | <tr><th>item</th><th>cost</th></tr> | |
| 8 | </thead> | |
| 9 | <tbody> | |
| 10 | <tr><td>a</td><td>1</td></tr> | |
| 11 | <tr><td>b</td><td>2</td></tr> | |
| 12 | </tbody> | |
| 13 | </table> | |
| 14 | <h2 id="latex">LaTeX</h2> | |
| 15 | <p>Inline math $x^2 + y^2$ and a display block:</p> | |
| 16 | <p>\begin{equation} E = mc^2 \end{equation}</p> | |
| 17 | <h2 id="macros-and-radio-targets">Macros and radio targets</h2> | |
| 18 | <p>A macro call {{{author}}} and a <<<radio target>>> stay literal.</p> | |
| 19 | <h2 id="drawers">Drawers</h2> | |
| 20 | <h2 id="unknown-entities">Unknown entities</h2> | |
| 21 | <p>An entity org does not know, \notanentity, stays literal.</p> | |
cases/outofscope.org added +49
| @@ -0,0 +1,49 @@ | ||
| 1 | #+TITLE: Out of Scope | |
| 2 | #+INCLUDE: "other.org" | |
| 3 | ||
| 4 | Every construct here is on the explicit "Not supported" list in the org-support guide. | |
| 5 | The contract is not that we handle them — it is that they degrade predictably and never | |
| 6 | crash the build. | |
| 7 | ||
| 8 | * Babel | |
| 9 | ||
| 10 | #+BEGIN_SRC sh :results output :exports both | |
| 11 | echo "the block renders; :results is never executed" | |
| 12 | #+END_SRC | |
| 13 | ||
| 14 | #+RESULTS: | |
| 15 | : stale output from a previous evaluation | |
| 16 | ||
| 17 | * Table formulas | |
| 18 | ||
| 19 | | item | cost | | |
| 20 | |------+------| | |
| 21 | | a | 1 | | |
| 22 | | b | 2 | | |
| 23 | #+TBLFM: $2=vsum(@2..@3) | |
| 24 | ||
| 25 | * LaTeX | |
| 26 | ||
| 27 | Inline math $x^2 + y^2$ and a display block: | |
| 28 | ||
| 29 | \begin{equation} | |
| 30 | E = mc^2 | |
| 31 | \end{equation} | |
| 32 | ||
| 33 | * Macros and radio targets | |
| 34 | ||
| 35 | A macro call {{{author}}} and a <<<radio target>>> stay literal. | |
| 36 | ||
| 37 | * Drawers | |
| 38 | ||
| 39 | :LOGBOOK: | |
| 40 | CLOCK: [2024-01-15 Mon 09:00]--[2024-01-15 Mon 10:00] => 1:00 | |
| 41 | :END: | |
| 42 | ||
| 43 | :CUSTOM_DRAWER: | |
| 44 | Drawer contents are captured and dropped. | |
| 45 | :END: | |
| 46 | ||
| 47 | * Unknown entities | |
| 48 | ||
| 49 | An entity org does not know, \notanentity, stays literal. | |
cases/outofscope.skeleton added +68
| @@ -0,0 +1,68 @@ | ||
| 1 | <p> | |
| 2 | "Every construct here is on the explicit \"Not supported\" list in the org-support guide. The contract is not that we handle them — it is that they degrade predictably and never crash the build." | |
| 3 | </p> | |
| 4 | <h2> | |
| 5 | "Babel" | |
| 6 | </h2> | |
| 7 | <pre> | |
| 8 | <code> | |
| 9 | "echo \"the block renders; :results is never executed\"" | |
| 10 | </code> | |
| 11 | </pre> | |
| 12 | <h2> | |
| 13 | "Table formulas" | |
| 14 | </h2> | |
| 15 | <table> | |
| 16 | <thead> | |
| 17 | <tr> | |
| 18 | <th> | |
| 19 | "item" | |
| 20 | </th> | |
| 21 | <th> | |
| 22 | "cost" | |
| 23 | </th> | |
| 24 | </tr> | |
| 25 | </thead> | |
| 26 | <tbody> | |
| 27 | <tr> | |
| 28 | <td> | |
| 29 | "a" | |
| 30 | </td> | |
| 31 | <td> | |
| 32 | "1" | |
| 33 | </td> | |
| 34 | </tr> | |
| 35 | <tr> | |
| 36 | <td> | |
| 37 | "b" | |
| 38 | </td> | |
| 39 | <td> | |
| 40 | "2" | |
| 41 | </td> | |
| 42 | </tr> | |
| 43 | </tbody> | |
| 44 | </table> | |
| 45 | <h2> | |
| 46 | "LaTeX" | |
| 47 | </h2> | |
| 48 | <p> | |
| 49 | "Inline math $x^2 + y^2$ and a display block:" | |
| 50 | </p> | |
| 51 | <p> | |
| 52 | "\\begin{equation} E = mc^2 \\end{equation}" | |
| 53 | </p> | |
| 54 | <h2> | |
| 55 | "Macros and radio targets" | |
| 56 | </h2> | |
| 57 | <p> | |
| 58 | "A macro call {{{author}}} and a <<<radio target>>> stay literal." | |
| 59 | </p> | |
| 60 | <h2> | |
| 61 | "Drawers" | |
| 62 | </h2> | |
| 63 | <h2> | |
| 64 | "Unknown entities" | |
| 65 | </h2> | |
| 66 | <p> | |
| 67 | "An entity org does not know, \\notanentity, stays literal." | |
| 68 | </p> | |
cases/table.html added +9
| @@ -0,0 +1,9 @@ | ||
| 1 | <table> | |
| 2 | <thead> | |
| 3 | <tr><th>Name</th><th>Score</th></tr> | |
| 4 | </thead> | |
| 5 | <tbody> | |
| 6 | <tr><td>alpha</td><td>10</td></tr> | |
| 7 | <tr><td>beta</td><td>20</td></tr> | |
| 8 | </tbody> | |
| 9 | </table> | |
cases/table.org added +6
| @@ -0,0 +1,6 @@ | ||
| 1 | #+TITLE: Table Sample | |
| 2 | ||
| 3 | | Name | Score | | |
| 4 | |-------+-------| | |
| 5 | | alpha | 10 | | |
| 6 | | beta | 20 | | |
cases/table.skeleton added +30
| @@ -0,0 +1,30 @@ | ||
| 1 | <table> | |
| 2 | <thead> | |
| 3 | <tr> | |
| 4 | <th> | |
| 5 | "Name" | |
| 6 | </th> | |
| 7 | <th> | |
| 8 | "Score" | |
| 9 | </th> | |
| 10 | </tr> | |
| 11 | </thead> | |
| 12 | <tbody> | |
| 13 | <tr> | |
| 14 | <td> | |
| 15 | "alpha" | |
| 16 | </td> | |
| 17 | <td> | |
| 18 | "10" | |
| 19 | </td> | |
| 20 | </tr> | |
| 21 | <tr> | |
| 22 | <td> | |
| 23 | "beta" | |
| 24 | </td> | |
| 25 | <td> | |
| 26 | "20" | |
| 27 | </td> | |
| 28 | </tr> | |
| 29 | </tbody> | |
| 30 | </table> | |
cases/tblfm.html added +10
| @@ -0,0 +1,10 @@ | ||
| 1 | <p>The cells deliberately contradict the formula: org's HTML exporter does not recalculate on export, so both exporters must emit the values as written.</p> | |
| 2 | <table> | |
| 3 | <thead> | |
| 4 | <tr><th>N</th><th>N<sup>2</sup></th></tr> | |
| 5 | </thead> | |
| 6 | <tbody> | |
| 7 | <tr><td>2</td><td>999</td></tr> | |
| 8 | <tr><td>3</td><td>888</td></tr> | |
| 9 | </tbody> | |
| 10 | </table> | |
cases/tblfm.org added +10
| @@ -0,0 +1,10 @@ | ||
| 1 | #+TITLE: Table Formula | |
| 2 | ||
| 3 | The cells deliberately contradict the formula: org's HTML exporter does not | |
| 4 | recalculate on export, so both exporters must emit the values as written. | |
| 5 | ||
| 6 | | N | N^2 | | |
| 7 | |---+-----| | |
| 8 | | 2 | 999 | | |
| 9 | | 3 | 888 | | |
| 10 | #+TBLFM: $2=$1^2 | |
cases/tblfm.skeleton added +36
| @@ -0,0 +1,36 @@ | ||
| 1 | <p> | |
| 2 | "The cells deliberately contradict the formula: org's HTML exporter does not recalculate on export, so both exporters must emit the values as written." | |
| 3 | </p> | |
| 4 | <table> | |
| 5 | <thead> | |
| 6 | <tr> | |
| 7 | <th> | |
| 8 | "N" | |
| 9 | </th> | |
| 10 | <th> | |
| 11 | "N" | |
| 12 | <sup> | |
| 13 | "2" | |
| 14 | </sup> | |
| 15 | </th> | |
| 16 | </tr> | |
| 17 | </thead> | |
| 18 | <tbody> | |
| 19 | <tr> | |
| 20 | <td> | |
| 21 | "2" | |
| 22 | </td> | |
| 23 | <td> | |
| 24 | "999" | |
| 25 | </td> | |
| 26 | </tr> | |
| 27 | <tr> | |
| 28 | <td> | |
| 29 | "3" | |
| 30 | </td> | |
| 31 | <td> | |
| 32 | "888" | |
| 33 | </td> | |
| 34 | </tr> | |
| 35 | </tbody> | |
| 36 | </table> | |
cases/timestamps.html added +10
| @@ -0,0 +1,10 @@ | ||
| 1 | <h2 id="single">Single</h2> | |
| 2 | <p>An active date <time class="timestamp" datetime="2024-01-15">2024-01-15</time> and an inactive one <time class="timestamp inactive" datetime="2024-01-15">2024-01-15</time>.</p> | |
| 3 | <p>With a time: <time class="timestamp" datetime="2024-01-15T10:30">2024-01-15 10:30</time>.</p> | |
| 4 | <h2 id="ranges">Ranges</h2> | |
| 5 | <p>A same-day time range <time class="timestamp" datetime="2024-01-15T10:00">2024-01-15 10:00</time>–<time class="timestamp" datetime="2024-01-15T11:45">11:45</time>.</p> | |
| 6 | <p>A multi-day range <time class="timestamp" datetime="2024-01-15">2024-01-15</time>–<time class="timestamp" datetime="2024-01-20">2024-01-20</time>.</p> | |
| 7 | <h2 id="ignored-decorations">Ignored decorations</h2> | |
| 8 | <p>A repeater is dropped: <time class="timestamp" datetime="2024-01-15">2024-01-15</time>.</p> | |
| 9 | <h2 id="not-timestamps">Not timestamps</h2> | |
| 10 | <p>Comparisons like 3 < 4 and [not a stamp] stay literal text.</p> | |
cases/timestamps.org added +21
| @@ -0,0 +1,21 @@ | ||
| 1 | #+TITLE: Timestamps | |
| 2 | ||
| 3 | * Single | |
| 4 | ||
| 5 | An active date <2024-01-15 Mon> and an inactive one [2024-01-15 Mon]. | |
| 6 | ||
| 7 | With a time: <2024-01-15 Mon 10:30>. | |
| 8 | ||
| 9 | * Ranges | |
| 10 | ||
| 11 | A same-day time range <2024-01-15 Mon 10:00-11:45>. | |
| 12 | ||
| 13 | A multi-day range <2024-01-15 Mon>--<2024-01-20 Sat>. | |
| 14 | ||
| 15 | * Ignored decorations | |
| 16 | ||
| 17 | A repeater is dropped: <2024-01-15 Mon +1w>. | |
| 18 | ||
| 19 | * Not timestamps | |
| 20 | ||
| 21 | Comparisons like 3 < 4 and [not a stamp] stay literal text. | |
cases/timestamps.skeleton added +62
| @@ -0,0 +1,62 @@ | ||
| 1 | <h2> | |
| 2 | "Single" | |
| 3 | </h2> | |
| 4 | <p> | |
| 5 | "An active date" | |
| 6 | <time> | |
| 7 | "2024-01-15" | |
| 8 | </time> | |
| 9 | "and an inactive one" | |
| 10 | <time> | |
| 11 | "2024-01-15" | |
| 12 | </time> | |
| 13 | "." | |
| 14 | </p> | |
| 15 | <p> | |
| 16 | "With a time:" | |
| 17 | <time> | |
| 18 | "2024-01-15 10:30" | |
| 19 | </time> | |
| 20 | "." | |
| 21 | </p> | |
| 22 | <h2> | |
| 23 | "Ranges" | |
| 24 | </h2> | |
| 25 | <p> | |
| 26 | "A same-day time range" | |
| 27 | <time> | |
| 28 | "2024-01-15 10:00" | |
| 29 | </time> | |
| 30 | "–" | |
| 31 | <time> | |
| 32 | "11:45" | |
| 33 | </time> | |
| 34 | "." | |
| 35 | </p> | |
| 36 | <p> | |
| 37 | "A multi-day range" | |
| 38 | <time> | |
| 39 | "2024-01-15" | |
| 40 | </time> | |
| 41 | "–" | |
| 42 | <time> | |
| 43 | "2024-01-20" | |
| 44 | </time> | |
| 45 | "." | |
| 46 | </p> | |
| 47 | <h2> | |
| 48 | "Ignored decorations" | |
| 49 | </h2> | |
| 50 | <p> | |
| 51 | "A repeater is dropped:" | |
| 52 | <time> | |
| 53 | "2024-01-15" | |
| 54 | </time> | |
| 55 | "." | |
| 56 | </p> | |
| 57 | <h2> | |
| 58 | "Not timestamps" | |
| 59 | </h2> | |
| 60 | <p> | |
| 61 | "Comparisons like 3 < 4 and [not a stamp] stay literal text." | |
| 62 | </p> | |
manifest.json added +83
| @@ -0,0 +1,83 @@ | ||
| 1 | { | |
| 2 | "version": 1, | |
| 3 | "reference": { | |
| 4 | "implementation": "orgo", | |
| 5 | "repo": "krz/orgo", | |
| 6 | "note": "Golden .html and .skeleton files are produced by orgo, which is validated against Emacs ox-html via its own oracle test. orgo is the reference; other implementations conform to it." | |
| 7 | }, | |
| 8 | "comparison": "skeleton", | |
| 9 | "cases": [ | |
| 10 | { | |
| 11 | "name": "minimal", | |
| 12 | "constructs": ["document-keywords", "preamble-paragraph", "heading", "property-drawer", "custom-id", "tags", "bold", "italic", "verbatim", "unordered-list", "checkbox", "custom-id-link"], | |
| 13 | "scope": "in", | |
| 14 | "note": "Smallest end-to-end document: metadata header, one nesting of headings, inline emphasis, a checkbox, an internal link." | |
| 15 | }, | |
| 16 | { | |
| 17 | "name": "core", | |
| 18 | "constructs": ["heading", "bare-url", "inline-code", "ordered-list", "checkbox", "external-link", "src-block"], | |
| 19 | "scope": "in", | |
| 20 | "note": "The common README surface: links, code, lists, a source block." | |
| 21 | }, | |
| 22 | { | |
| 23 | "name": "elements", | |
| 24 | "constructs": ["filetags", "src-block", "table", "external-link", "id-link", "footnote-ref", "footnote-def", "quote-block", "horizontal-rule"], | |
| 25 | "scope": "in", | |
| 26 | "note": "A sampler spanning most block-level elements." | |
| 27 | }, | |
| 28 | { | |
| 29 | "name": "headings", | |
| 30 | "constructs": ["todo-keyword", "done-keyword", "priority", "tags", "property-drawer", "custom-id", "todo-word-boundary", "empty-title"], | |
| 31 | "scope": "in", | |
| 32 | "note": "Heading anatomy, including the negatives: 'TODOs' is not a keyword, a bare 'DONE' has no title." | |
| 33 | }, | |
| 34 | { | |
| 35 | "name": "lists", | |
| 36 | "constructs": ["nested-unordered", "ordered-list", "nested-ordered", "checkbox", "checkbox-partial", "description-list", "inline-markup-in-term", "multi-paragraph-item"], | |
| 37 | "scope": "in", | |
| 38 | "note": "Every list shape, including three-deep nesting and multi-paragraph items." | |
| 39 | }, | |
| 40 | { | |
| 41 | "name": "table", | |
| 42 | "constructs": ["table", "table-header-rule"], | |
| 43 | "scope": "in", | |
| 44 | "note": "A header band separated by a rule row." | |
| 45 | }, | |
| 46 | { | |
| 47 | "name": "blocks", | |
| 48 | "constructs": ["quote-block", "center-block", "example-block", "export-html", "export-latex-dropped", "src-block", "src-block-no-lang", "nested-src-in-quote", "verse-block", "special-block"], | |
| 49 | "scope": "in", | |
| 50 | "note": "All standard blocks. Export blocks: html passes through raw, non-html backends are dropped. Named special blocks render as a container carrying the name." | |
| 51 | }, | |
| 52 | { | |
| 53 | "name": "timestamps", | |
| 54 | "constructs": ["active-timestamp", "inactive-timestamp", "timestamp-with-time", "same-day-range", "multi-day-range", "repeater-dropped", "non-timestamp-text"], | |
| 55 | "scope": "in", | |
| 56 | "note": "orgo renders timestamps as <time datetime>; the last section checks that '3 < 4' and '[not a stamp]' stay literal." | |
| 57 | }, | |
| 58 | { | |
| 59 | "name": "footnote", | |
| 60 | "constructs": ["footnote-ref", "footnote-def", "inline-footnote", "inline-markup-in-note"], | |
| 61 | "scope": "in", | |
| 62 | "note": "Numbered references, definitions, and an anonymous inline footnote." | |
| 63 | }, | |
| 64 | { | |
| 65 | "name": "images", | |
| 66 | "constructs": ["bare-image", "captioned-figure", "attr-html", "caption-with-markup", "quoted-attr-values", "image-link-with-description"], | |
| 67 | "scope": "in", | |
| 68 | "note": "A bare image link becomes <img>; a #+CAPTION: promotes it to <figure>; a description turns it back into an ordinary link." | |
| 69 | }, | |
| 70 | { | |
| 71 | "name": "tblfm", | |
| 72 | "constructs": ["table", "tblfm-inert"], | |
| 73 | "scope": "in", | |
| 74 | "note": "#+TBLFM: is kept inert: cells are emitted as written, never recalculated, matching org's HTML exporter." | |
| 75 | }, | |
| 76 | { | |
| 77 | "name": "outofscope", | |
| 78 | "constructs": ["include-reported", "babel-results-dropped", "table-formula", "latex-literal", "macro-literal", "radio-target-literal", "logbook-drawer-dropped", "custom-drawer-dropped", "unknown-entity-literal"], | |
| 79 | "scope": "out", | |
| 80 | "note": "Deliberately unsupported constructs. The contract is not that they render, but that they degrade predictably and never crash. An implementation may legitimately differ from orgo here; treat this case as documentation, not a hard assertion." | |
| 81 | } | |
| 82 | ] | |
| 83 | } | |
tools/generate.sh added +30
| @@ -0,0 +1,30 @@ | ||
| 1 | #!/usr/bin/env bash | |
| 2 | # Regenerate cases/<name>.html and cases/<name>.skeleton from orgo. | |
| 3 | # | |
| 4 | # The golden outputs are derived from orgo, the reference implementation — never edited by | |
| 5 | # hand. Point ORGO_DIR at an orgo checkout (default: ../orgo). Each case's .org input is | |
| 6 | # the source of truth for the input; this script only refreshes the two derived files. | |
| 7 | # | |
| 8 | # ORGO_DIR=../orgo tools/generate.sh | |
| 9 | set -euo pipefail | |
| 10 | ||
| 11 | here="$(cd "$(dirname "$0")/.." && pwd)" | |
| 12 | orgo="${ORGO_DIR:-$here/../orgo}" | |
| 13 | ||
| 14 | if [ ! -f "$orgo/Cargo.toml" ]; then | |
| 15 | echo "orgo checkout not found at $orgo (set ORGO_DIR)" >&2 | |
| 16 | exit 1 | |
| 17 | fi | |
| 18 | ||
| 19 | # One release-less build up front so the per-case runs are fast. | |
| 20 | ( cd "$orgo" && cargo build -q --example skeleton ) | |
| 21 | ||
| 22 | count=0 | |
| 23 | for org in "$here"/cases/*.org; do | |
| 24 | name="$(basename "$org" .org)" | |
| 25 | ( cd "$orgo" && cargo run -q --example skeleton -- --html "$org" ) > "$here/cases/$name.html" | |
| 26 | ( cd "$orgo" && cargo run -q --example skeleton -- "$org" ) > "$here/cases/$name.skeleton" | |
| 27 | count=$((count + 1)) | |
| 28 | echo " regenerated $name" | |
| 29 | done | |
| 30 | echo "done: $count cases" | |