A language-neutral conformance corpus for org-mode to HTML renderers.

conformance html org-mode testing

SKELETON.md

76 lines · 3422 bytes

 1# The skeleton reduction
 2
 3Two correct org→HTML renderers disagree on almost everything at the byte level. org
 4wraps 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
 6raw HTML across implementations measures none of the things that matter.
 7
 8The **skeleton** throws that away and keeps what two renderers can meaningfully agree or
 9disagree about: the ordered sequence of element opens, element closes, and text runs.
10
11A conformant implementation ports this reduction and asserts that
12`skeleton(its_html) == the checked-in .skeleton file`.
13
14## The reduction, exactly
15
16Given an HTML fragment, walk it left to right and emit one line per event:
17
181. **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.
222. **Element close**`</name>`, lowercased.
233. **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
28Special 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
43Decode the named entities `&amp; &lt; &gt; &quot; &apos; &nbsp;` and numeric entities
44(`&#160;`, `&#xA0;`). A non-breaking space (`\u{a0}`) is treated as a plain space for
45comparison. Anything that is not a recognizable entity — a bare `&` — is left as-is. Only
46consider a `&…;` an entity if the `;` is within 12 characters of the `&`.
47
48## Worked example
49
50Input HTML:
51
52```html
53<h2 id="write-parser"><span class="todo TODO">TODO</span> Write the <b>parser</b></h2>
54```
55
56Skeleton:
57
58```
59<h2>
60"TODO Write the"
61<b>
62"parser"
63</b>
64</h2>
65```
66
67The `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
69own open/close.
70
71## Reference implementation
72
73The canonical implementation is `skeleton()` in orgo's `src/skeleton.rs` (Rust). A port is
74correct when, for every case in `cases/`, feeding orgo's own `<name>.html` back through
75the port reproduces `<name>.skeleton` byte for byte. That is the first test any port
76should run — it isolates a skeleton bug from a renderer bug.