Escape the quoted attribute value, and pin the canonical form and the pipe cell's edges #23

Merged
lilleman merged 4 commits from attribute-escape into main 2026-08-27 00:09:23 +02:00
2 changed files with 13 additions and 17 deletions
Showing only changes of commit 81ba247ad8 - Show all commits
+8 -11
View File
@@ -94,14 +94,12 @@ container is open, a named error otherwise.
valid — no attributes. A bare value matches `[A-Za-z0-9_-]+`; any other value is double-quoted valid — no attributes. A bare value matches `[A-Za-z0-9_-]+`; any other value is double-quoted
with JSON string escaping (`\"` `\\` `\n` `\t` `\uXXXX`, …) — total over with JSON string escaping (`\"` `\\` `\n` `\t` `\uXXXX`, …) — total over
Unicode, and raw newlines never appear inside quotes. A quoted value also escapes `` ` ``, `&`, Unicode, and raw newlines never appear inside quotes. A quoted value also escapes `` ` ``, `&`,
`<` and `|` as `\u0060`, `\u0026`, `\u003c` and `\u007c` — the code span, entity reference, `<` and `|` as `\u0060`, `\u0026`, `\u003c` and `\u007c`; `*`, `_`, `~`, `[` and `(` resolve after a
autolink, raw HTML and pipe cell's split all bind at or before a directive does, so a raw one directive binds and stay raw. The closing `}` is the first one outside quotes, since a quoted value
would reach back out of the value; `*`, `_`, `~`, `[` and `(` resolve after and stay raw. The holds `}` unescaped. All values are strings at the grammar level; each node's section assigns types.
closing `}` is the first one outside quotes, since a quoted value holds `}` unescaped. All values Canonical form orders keys alphabetically, spells values bare wherever allowed, escapes inside
are strings at the grammar level; each node's section assigns types. Canonical form orders keys quotes in the shortest form each escape has, and omits empty `{attrs}` except where the `{` itself
alphabetically, spells values bare wherever allowed, escapes inside quotes in the shortest form claims the directive (`:hardBreak{}`).
each escape has, and omits empty `{attrs}` except where the `{` itself claims the directive
(`:hardBreak{}`).
**Escaping**: the emitter backslash-escapes whatever literal text would otherwise parse as **Escaping**: the emitter backslash-escapes whatever literal text would otherwise parse as
directive syntax — the leading `:` of a would-be directive, `]` inside content, a `{` right directive syntax — the leading `:` of a would-be directive, `]` inside content, a `{` right
@@ -222,9 +220,8 @@ One header row plus plain inline cells is a pipe table; anything richer is the d
and every cell carry no attrs and no marks, the first row is all `tableHeader` and the rest all and every cell carry no attrs and no marks, the first row is all `tableHeader` and the rest all
`tableCell`, every row has the header's cell count, and every cell holds exactly one attr-less, `tableCell`, every row has the header's cell count, and every cell holds exactly one attr-less,
mark-less paragraph — an empty cell holds one empty paragraph — with no `|` anywhere the mark-less paragraph — an empty cell holds one empty paragraph — with no `|` anywhere the
inline layer spells as syntax — a code span, a link destination or title: backslash escapes are inline layer spells as syntax — a code span, a link destination or title: pipe form cannot spell
inert there, so pipe form cannot spell that pipe and the table takes the directive form. A pipe that pipe and the table takes the directive form. A pipe table parses back to exactly that shape.
table parses back to exactly that shape.
``` ```
| Part | Qty | | Part | Qty |
+5 -6
View File
@@ -72,12 +72,11 @@ detail is settled at its own milestone.
the emitter's refusals a corpus home while the directories grow: `corpus/unspellable/`, the emitter's refusals a corpus home while the directories grow: `corpus/unspellable/`,
a `.json` beside the `ConvertErrorCode` it must return, the emitter half of `corpus/errors/`. a `.json` beside the `ConvertErrorCode` it must return, the emitter half of `corpus/errors/`.
- [x] **2c — Inline nodes and marks.** `inline-nodes/` green. `InlineSegment` splits into its - [x] **2c — Inline nodes and marks.** `inline-nodes/` green. `InlineSegment` splits into its
two axes — escapability (`attribute` for `:text{text="…"}`, `backslash`, `bracketed`, two axes — escapability (`backslash`, `bracketed`, `none`) and the emphasis role. A lone
`none`) and the emphasis role. A lone surrogate in a text node emits verbatim and becomes surrogate in a text node emits verbatim and becomes U+FFFD on any UTF-8 encode, a §2 break
U+FFFD on any UTF-8 encode, a §2 break plain text still holds open — attribute values plain text still holds open — attribute values already escape it. The pipe form's fallback
already escape it. The pipe form's fallback reads the emitted segments rather than naming reads the emitted segments rather than naming the nodes whose attribute values spell a pipe
the nodes whose attribute values spell a pipe as syntax, so 2e's `\u007c` narrows it in as syntax, so 2e's `\u007c` narrows it in one place.
one place.
- [x] **2d — The opaque carry** (§3). Fixtures and emitter together, into - [x] **2d — The opaque carry** (§3). Fixtures and emitter together, into
`corpus/round-trip/opaque-carry/`: an unknown node in both positions, the reserved `adf` `corpus/round-trip/opaque-carry/`: an unknown node in both positions, the reserved `adf`
info string, and the `codeBlock` whose language is `adf` — carried whole ahead of the info string, and the `codeBlock` whose language is `adf` — carried whole ahead of the