Slice 2 of #92. Add PureXML.read, a deterministic recovering reader: it never
throws and never traps, returning the maximal best-effort tree plus one located
Diagnostic per problem.
- ReadResult { node, diagnostics } is the Equatable result type, so a
deterministic read can be compared whole.
- Diagnostic carries a message and a structured source Mark; a thrown ParseError
maps to one directly (ParseError.mark exposes the position each case carries).
- The reader resynchronizes after a failure (EventReader.recover(from:)):
progress-aware, so a failure that already consumed the bad construct retries
from there, while a stuck failure skips to the next markup start; it always
advances when input remains, guaranteeing termination.
- The tree builder assembles leniently: elements left open by truncation are
closed, an end tag with no open element is dropped. Well-formed input yields
exactly parse(_:)'s tree with no diagnostics.
Tests pin exact trees and diagnostics for truncation, mismatched tags, and stray
junk, prove determinism (same bytes give an identical ReadResult), and fuzz 400
seeded inputs (arbitrary plus truncated valid docs) asserting no crash.
Recovery here is resync-based; finer in-reader recovery (keep the element on a
duplicate attribute, pop to a matching open tag) is the next slice.