`. |
## Markdown support
| Markdown | WML |
| --- | --- |
| Headings | `` for the first on a card, `
` after |
| Paragraphs, lists | `
` — WML has no list element, so bullets and numbers become literal text |
| `---` / `***` / `___` | a card divider; never drawn, as WML has no rule element |
| Setext headings (`===` / `---` underlines) | same as `#` / `##` |
| `**bold**`, `*italic*` | ``, `` |
| `~~strike~~`, `` `code` `` | markers stripped; WML has no equivalent |
| Links | `` |
| Images | `
`, subject to `--images` |
| Fenced/indented code | `` with `
` between lines |
| Blockquotes | `
` |
| Pipe tables | `
` with a bold header row |
| `{.card Title}` | a card divider carrying a title |
| `{.include file.md}` | inlined before conversion |
Two consequences of WML's own grammar are worth knowing:
- **A literal `$` is written `$$`.** WML uses `$` to introduce a variable substitution, so wapdown doubles it for you. `Permit fee: $5` comes out as `Permit fee: $$5`, which the device renders as `$5`.
- **Link labels cannot carry emphasis.** WML declares `` as `(#PCDATA | br | img)*`, so there is nowhere valid to put a `` inside one. `[**go** now](url)` becomes `go now`, with the markers stripped rather than translated.
WML 1.3 is the only supported DTD, by design rather than by flag.
## Serving a deck
Serve `.wml` as `text/vnd.wap.wml` — a browser that receives `text/html` will usually refuse to render it. `--cache-control 0` is worth setting for content that changes, since WAP gateways cache aggressively.
## Development
```bash
uv sync
uv run pytest # unit, golden, and well-formedness tests
uv run --extra dtd pytest # ...plus validation against the real WML 1.3 DTD
```
The DTD is vendored at `tests/dtd/wml13.dtd` so the suite never depends on fetching a twenty-year-old URL. DTD validation needs `lxml` and is skipped when it is absent.
Golden files under `tests/golden/` pin whole-document output. After an intentional renderer change:
```bash
uv run python tests/regenerate_golden.py
jj diff tests/golden # the diff is the change, stated in output
```
## Architecture
Markdown is parsed into a stream of block events, which a renderer turns into output. The pieces are registered as plugins (`src/wapdown/plugins/`), so another WAP-era target — HDML, i-mode cHTML — can be added alongside `wml` without restructuring anything.
- `parsers/markdown.py` — Markdown to `BlockEvent`s
- `renderers/wml/inline.py` — inline markup and WML's escaping rules
- `renderers/wml/deck.py` — sectioning, byte packing, navigation topology
- `renderers/wml/renderer.py` — element emission
- `config.py` — defaults, frontmatter, and CLI layering
Unlike a streaming text renderer, the WML renderer buffers the whole event stream: card boundaries, menu choices, and nav targets all depend on knowing the document's full structure up front.
## Credit
The WML renderer began life inside [md2txt](https://codeberg.org/randogoth/md2txt), a suite of plain-text Markdown exporters, and was extracted here because a deck compiler is a different kind of thing from a text exporter.
## License
MIT