Artifact 2b03aed79336d7becba88fe3f7ac0efa1b32fe42d0be79d564fe16b4297bacee:
- File
internal/markup/resolver.go
— part of check-in
[5ba3499b8a]
at
2026-08-15 11:20:48
on branch trunk
— markup: goldmark + cleanroom sanitizer + resolver seam (0002 §2)
Two-lineage conformance corpus in third_party (ledger rows, inputs only); parse-oracle fuzz target; [[wiki]]/hash resolution post-sanitize. (user: dmestas, size: 2836) [annotate] [blame] [check-ins using]
// resolver.go — zit reference resolution (0002 §2 rework: git-ref hooks // replaced with zit resolution). Runs AFTER sanitization, over text // nodes only — the links it inserts are quarry-built, and code spans, // pre blocks, and existing links are left alone. package markup import ( "bytes" "regexp" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) var ( wikiRef = regexp.MustCompile(`\[\[([^\[\]|]{1,100})\]\]`) hashRef = regexp.MustCompile(`\b[0-9a-f]{10,64}\b`) ) func resolveRefs(in []byte, r Resolver) []byte { body := &html.Node{Type: html.ElementNode, Data: "body", DataAtom: atom.Body} nodes, err := html.ParseFragment(bytes.NewReader(in), body) if err != nil { return in } var out bytes.Buffer for _, n := range nodes { walkResolve(n, r) if err := html.Render(&out, n); err != nil { return in } } return out.Bytes() } var skipTags = map[string]bool{"a": true, "code": true, "pre": true} func walkResolve(n *html.Node, r Resolver) { if n.Type == html.ElementNode && skipTags[n.Data] { return } for c := n.FirstChild; c != nil; c = c.NextSibling { walkResolve(c, r) } // Collect text children first: splicing replaces nodes mid-walk. var texts []*html.Node for c := n.FirstChild; c != nil; c = c.NextSibling { if c.Type == html.TextNode { texts = append(texts, c) } } for _, t := range texts { splice(n, t, r) } } // splice replaces one text node with text/link/text/... nodes for each // resolvable reference found in it. func splice(parent, text *html.Node, r Resolver) { s := text.Data type ref struct { start, end int label, url string } var refs []ref for _, m := range wikiRef.FindAllStringSubmatchIndex(s, -1) { name := s[m[2]:m[3]] if url := r.ResolveWiki(name); url != "" { refs = append(refs, ref{m[0], m[1], name, url}) } } for _, m := range hashRef.FindAllStringIndex(s, -1) { tok := s[m[0]:m[1]] if url := r.ResolveHash(tok); url != "" { refs = append(refs, ref{m[0], m[1], tok, url}) } } if len(refs) == 0 { return } // Wiki and hash matches cannot overlap ([[..]] never lexes as a hex // word), so a simple position sort orders the splice. for i := 1; i < len(refs); i++ { for j := i; j > 0 && refs[j-1].start > refs[j].start; j-- { refs[j-1], refs[j] = refs[j], refs[j-1] } } pos := 0 insert := func(nn *html.Node) { parent.InsertBefore(nn, text) } for _, rf := range refs { if rf.start > pos { insert(&html.Node{Type: html.TextNode, Data: s[pos:rf.start]}) } a := &html.Node{Type: html.ElementNode, Data: "a", DataAtom: atom.A, Attr: []html.Attribute{{Key: "href", Val: rf.url}}} a.AppendChild(&html.Node{Type: html.TextNode, Data: rf.label}) insert(a) pos = rf.end } if pos < len(s) { insert(&html.Node{Type: html.TextNode, Data: s[pos:]}) } parent.RemoveChild(text) }