tree

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package tree builds the version-1 native render tree: a layout-free, fully RESOLVED semantic tree that native hosts (Flutter, SwiftUI, Compose, …) render as platform widgets, with everything policy-heavy — URL resolution and filtering, raw-HTML sanitizing, admonition titles, footnote pairing, math/mermaid fallbacks — already applied library-side by Build, through the same shared derivations the HTML renderer uses (render/internal/derive and the resolve package), so the two renderers cannot drift.

Evolution within version 1

The version stays 1 across additive growth: new OPTIONAL fields may appear on existing nodes, and new block/inline KINDS may appear as the document model grows. Consumers must therefore default-case on unknown kinds (render nothing, or a host-chosen placeholder) rather than treat the kind set as closed — the Flutter model's Unknown nodes and any TypeScript switch's default arm are the intended paths. A version bump is reserved for changes that break existing fields.

Wire schema (version 1, strict)

Tree.MarshalJSON produces the envelope

{"version":1, "blocks":[…], "footnotes":[…]}

where "blocks" and "footnotes" are always arrays (empty, never null). Every block object carries, in order: "kind", "span" (omitted when the node has no source position — same rule and shape as the document codec's spans: {startLine,endLine,startOffset,endOffset}), "id" (see "Block identity" below), its kind-specific fields, and its children — named "children" when they are inlines, "blocks" when they are nested blocks. Inline objects carry "kind", "span" (same omission rule), and kind-specific fields.

Kind names are exactly the document codec's wire names (document.Kind String values): heading, paragraph, blockQuote, admonition, list, codeBlock, mathBlock, diagram, table, thematicBreak, htmlBlock, definitionList, definitionTerm, definitionDesc, footnoteDef, text, softBreak, hardBreak, emphasis, strong, strikethrough, codeSpan, link, image, mathInline, htmlInline, footnoteRef. One naming universe across the library: a kind in the doc JSON and the same kind in the render tree spell identically.

Field presence is strict and documented per type below: fields that distinguish states are always emitted (codeBlock "runs" is null vs an array; list item "task" is null vs a boolean; link/image "url" is present even when empty), while purely-optional decorations use omit-when-empty ("anchorId", "title", "blocked", "display", wiki-link "source").

Block identity

A block's "id" is hex(sha256(source bytes of the block's span))[:16] — the first 8 hash bytes, 16 hex characters — computed over Options.Source[span.StartOffset:span.EndOffset]. It is a CONTENT hash: stable across edits elsewhere in the document (the basis for host-side diffing / itemized rebuilds), which also means two blocks with byte-identical source share an id — hosts needing unique keys disambiguate by position among equal ids. When no source bytes are available for a block (Options.Source is empty or nil — e.g. building from decoded doc JSON — or the node carries the zero span), the id falls back to hex(sha256("\x00mdv-fallback\x00" + kind + ":" + ordinal))[:16], where ordinal is the block's 0-based position in a document-order count of every tree-emitted block (list items and footnotes included). The NUL-delimited prefix domain-separates the fallback preimage from the content-hash form — source bytes can never spell it, since decoded markdown contains no NUL — so a block whose source literally reads e.g. "list:2" cannot collide with a fallback id. Fallback ids are deterministic for a given document but positional, not content-stable.

Index

Constants

View Source
const Version = 1

Version is the render-tree wire-schema version emitted in the envelope's "version" field.

Variables

This section is empty.

Functions

This section is empty.

Types

type Admonition

type Admonition struct {
	Span    document.Span
	ID      string
	Variant string // note|tip|important|warning|caution (normalized)
	Title   string // derived display title ("Note", …)
	Blocks  []Block
}

Admonition is a callout box. Variant and Title come from the shared derivation (derive.AdmonitionTitle): Variant is normalized (empty → "note") and Title is its title-cased display form — the SAME title the HTML renderer shows. Wire: {"kind":"admonition",span,id,"variant","title","blocks"}.

type Block

type Block interface {
	// contains filtered or unexported methods
}

Block is a block-level node of the render tree. Concrete types: Heading, Paragraph, BlockQuote, Admonition, List, CodeBlock, MathBlock, Diagram, Table, ThematicBreak, HTMLBlock, DefinitionList, DefinitionTerm, DefinitionDesc.

type BlockQuote

type BlockQuote struct {
	Span   document.Span
	ID     string
	Blocks []Block
}

BlockQuote is quoted block content. Wire: {"kind":"blockQuote",span,id,"blocks"}.

type Cell

type Cell []Inline

Cell is one table cell: its inline content.

type CodeBlock

type CodeBlock struct {
	Span     document.Span
	ID       string
	Language string     // fence info-string language tag, "" if none
	Label    string     // display label (derive.CodeLabel: tag or "code")
	Runs     []TokenRun // nil = no runs (highlighting off / unavailable)
	Text     string     // literal source text, always present
}

CodeBlock is a fenced or indented code block. Text always carries the full literal source. Runs is nil (wire: null) when highlighting is off or no token runs are available (unknown/missing language, chroma failure), an array otherwise — whose Text fields concatenate to exactly Text, so a host styling runs and a host printing Text show the same characters.

Wire: {"kind":"codeBlock",span,id,"language","label","runs","text"} — "runs" and "text" always present.

type CodeSpan

type CodeSpan struct {
	Span  document.Span
	Value string
}

CodeSpan is inline code. Wire: {"kind":"codeSpan",span,"value"}.

type DefinitionDesc

type DefinitionDesc struct {
	Span   document.Span
	ID     string
	Blocks []Block
}

DefinitionDesc is the description half of a definition-list entry. Wire: {"kind":"definitionDesc",span,id,"blocks"}.

type DefinitionList

type DefinitionList struct {
	Span   document.Span
	ID     string
	Blocks []Block // DefinitionTerm / DefinitionDesc, in order
}

DefinitionList is a list of DefinitionTerm/DefinitionDesc blocks, in source order (a term may be followed by several descriptions and vice versa, so the pairing stays positional, mirroring the document model). Wire: {"kind":"definitionList",span,id,"blocks"}.

type DefinitionTerm

type DefinitionTerm struct {
	Span     document.Span
	ID       string
	Children []Inline
}

DefinitionTerm is the term half of a definition-list entry. Wire: {"kind":"definitionTerm",span,id,"children"}.

type Diagram

type Diagram struct {
	Span   document.Span
	ID     string
	Engine string // "mermaid"
	Source string
}

Diagram is a diagram definition for the host's diagram engine. Wire: {"kind":"diagram",span,id,"source","engine":"mermaid"}.

type Emphasis

type Emphasis struct {
	Span     document.Span
	Children []Inline
}

Emphasis is emphasized (italic) content. Wire: {"kind":"emphasis",span,"children"}.

type Footnote

type Footnote struct {
	Span     document.Span
	ID       string
	Index    int // 1-based, matches FootnoteRef.Index
	RefCount int
	Blocks   []Block
}

Footnote is one resolved footnote definition in the envelope's "footnotes" array, paired with its reference sites: Index matches the FootnoteRef inlines that cite it, and RefCount is the number of such sites (for hosts rendering per-reference backlinks). Pairing and order come from the shared derivation (derive.Footnotes). Wire: {"kind":"footnoteDef",span,id,"index","refCount","blocks"}.

type FootnoteRef

type FootnoteRef struct {
	Index int
	DefID string // "" (omitted) only when no definition has a matching Index
}

FootnoteRef is a footnote reference site; Index pairs it with the envelope Footnote carrying the same index. It never carries a span (the parser records none).

DefID is the definition's block id (the same value as the matching Footnote.ID, whether that id is a content hash or a positional fallback) — an explicit ref→definition linkage so a host can jump straight to the definition it should key/scroll to, without scanning Tree.Footnotes by Index or reconstructing the link from source line numbers. Populated for every ref whose Index matches a definition in the same Tree, regardless of Options.Source — this is consistent whether the tree came from markdown directly or from previously parsed document JSON (a document.Document has no source bytes on that path, so ordinary block ids there are the positional fallback form, and DefID matches that same fallback value). It is "" (omitted on the wire) only for a ref with no matching definition at all (a hand-built tree with a stray/mismatched Index). Tree.FootnoteByIndex performs the equivalent lookup directly against Tree.Footnotes. Wire: {"kind":"footnoteRef","index",("defId")}.

type HTMLBlock

type HTMLBlock struct {
	Span   document.Span
	ID     string
	HTML   string
	Unsafe bool
}

HTMLBlock is a raw block of HTML markup. Unless Options.AllowRawHTML is set, HTML is the bluemonday-sanitized form (the shared derive.SanitizeHTML policy) and Unsafe is false; with AllowRawHTML, HTML is the raw source markup and Unsafe is true, telling the host the content was NOT sanitized. Wire: {"kind":"htmlBlock",span,id,"html","unsafe"} — both always present.

type HTMLInline

type HTMLInline struct {
	Span   document.Span
	HTML   string
	Unsafe bool
}

HTMLInline is a raw inline HTML span, sanitized/flagged exactly like HTMLBlock. Wire: {"kind":"htmlInline",span,"html","unsafe"}.

type HardBreak

type HardBreak struct{}

HardBreak is an explicit line break. It never carries a span. Wire: {"kind":"hardBreak"}.

type Heading

type Heading struct {
	Span     document.Span
	ID       string
	Level    int      // 1-6
	AnchorID string   // slugified per-document-unique anchor, "" (omitted on the wire) when Options.HeadingAnchors is off or the document carries none
	Children []Inline // heading content
}

Heading is a section heading. Wire: {"kind":"heading",span,id,"level",("anchorId"),"children"}.

type Image

type Image struct {
	Span    document.Span
	URL     string
	Blocked bool
	Alt     string
	Title   string
}

Image is an image with its destination resolved by the same pipeline as Link (a blocked destination yields URL "" + Blocked true, and a declined/absent resolver takes the default resolution path). Wire: {"kind":"image",span,"url",("blocked"),"alt",("title")} — "url" and "alt" always present.

type Inline

type Inline interface {
	// contains filtered or unexported methods
}

Inline is an inline node of the render tree. Concrete types: Text, SoftBreak, HardBreak, Emphasis, Strong, Strikethrough, CodeSpan, Link, Image, MathInline, HTMLInline, FootnoteRef.

type Link struct {
	Span     document.Span
	URL      string
	Blocked  bool   // destination was blocked by URL policy
	Title    string // "" omitted on the wire
	Source   string // "wikiLink" when resolved from [[…]], else ""
	Children []Inline
}

Link is a hyperlink with its destination fully resolved: the shared pipeline (derive.Href — Resolver trust, resolve.DefaultResolution, resolve.SafeURL filtering, percent-encoding) has already run. A policy-blocked destination yields URL "" with Blocked true — distinct from an empty-but-allowed destination (URL "" and Blocked false). Resolver-returned URLs are host-trusted and carried verbatim.

A wiki link resolves INTO a Link node (via the same pipeline, with the resolve.ResolveWikiLink kind and the default ".md" fallback); Source is then "wikiLink" so hosts can style it differently, and "" (omitted on the wire) for an ordinary link. Wire: {"kind":"link",span,"url",("blocked"),("title"),("source"), "children"} — "url" always present.

type List

type List struct {
	Span    document.Span
	ID      string
	Ordered bool
	Start   int // first item's number; meaningful when Ordered
	Tight   bool
	Items   []ListItem
}

List is an ordered or unordered list. Wire: {"kind":"list",span,id,"ordered","start","tight","items"}.

type ListItem

type ListItem struct {
	Span   document.Span
	ID     string
	Task   *bool // nil: not a task item; else the checked state
	Blocks []Block
}

ListItem is one list entry. Task is nil for an ordinary item; for a task-list item it points at the checked state, so the wire "task" field is null | false | true. Wire: {span,id,"task","blocks"} — "task" always present.

type MathBlock

type MathBlock struct {
	Span   document.Span
	ID     string
	Source string // TeX source, without delimiters
}

MathBlock is display math for the host's math engine. Wire: {"kind":"mathBlock",span,id,"source","engine":"katex"}.

type MathInline

type MathInline struct {
	Span    document.Span
	Source  string
	Display bool
}

MathInline is inline math for the host's math engine. Display marks a $$…$$ span that renders as display math despite sitting inline. Wire: {"kind":"mathInline",span,"source",("display")}.

type Options

type Options struct {
	Resolver       resolve.Resolver // optional hook to rewrite link/image/wiki-link targets; nil uses default resolution
	HeadingAnchors bool             // anchor ids on headings
	Highlighting   bool             // code token runs (see note above)
	Math           bool             // math nodes; off falls back to code shapes
	Mermaid        bool             // mermaid diagram nodes; off falls back to code blocks
	AllowRawHTML   bool             // carry raw HTML unsanitized (unsafe:true)

	// Source is the markdown source doc was parsed from, used to derive
	// content-hash block ids from node spans (see "Block identity" in
	// the package documentation). Empty or nil is valid — e.g. when
	// building from decoded doc JSON with no source at hand — and
	// switches every block to the deterministic kind+ordinal fallback
	// id.
	Source []byte
}

Options configures Build. The toggles mirror the HTML renderer's semantics exactly:

  • Math/Mermaid off: math and mermaid nodes fall back to the same shapes the HTML path falls back to — a MathBlock becomes a CodeBlock with language "math", a Diagram becomes a CodeBlock with the engine as its language, and a MathInline becomes a CodeSpan of its source.
  • HeadingAnchors off: headings carry no anchor id.
  • AllowRawHTML: raw HTML nodes carry the raw markup with Unsafe=true; otherwise the bluemonday-sanitized form with Unsafe=false. Unlike the HTML renderer's single Unsafe flag, AllowRawHTML governs ONLY raw HTML: URL policy (resolve.SafeURL) always applies — a blocked destination is reported as url:"" + blocked:true and the host decides what to do with it.
  • Highlighting: code blocks carry token runs (CodeBlock.Runs) from the same chroma tokenise seam the HTML renderer highlights through (htmlrender.TokenRuns, cached there). Off — or on with an unknown/missing language, or when chroma cannot reproduce the code text exactly — leaves Runs nil (wire: null) and the host renders Text plain, the tree analogue of the HTML renderer's fallback-to-plain path.

func DefaultOptions

func DefaultOptions() Options

DefaultOptions returns the recommended Options, matching the HTML renderer's defaults: highlighting, mermaid, math, and heading anchors enabled; raw HTML sanitized.

type Paragraph

type Paragraph struct {
	Span     document.Span
	ID       string
	Children []Inline
}

Paragraph is a run of inline content. Wire: {"kind":"paragraph",span,id,"children"}.

type SoftBreak

type SoftBreak struct{}

SoftBreak is an intra-paragraph line break that renders as whitespace. It never carries a span (the parser records none). Wire: {"kind":"softBreak"}.

type Strikethrough

type Strikethrough struct {
	Span     document.Span
	Children []Inline
}

Strikethrough is struck-through content. Wire: {"kind":"strikethrough",span,"children"}.

type Strong

type Strong struct {
	Span     document.Span
	Children []Inline
}

Strong is strongly-emphasized (bold) content. Wire: {"kind":"strong",span,"children"}.

type Table

type Table struct {
	Span       document.Span
	ID         string
	Alignments []document.Alignment
	Header     []Cell
	Rows       [][]Cell
}

Table is a pipe table. Alignments has one entry per column; Header is the header row's cells (nil only for hand-built documents without a header row); Rows are the body rows. Wire: {"kind":"table",span,id,"alignments" (array of "none"|"left"|"center"|"right"),"header","rows"} — header and rows always arrays.

type Text

type Text struct {
	Span  document.Span
	Value string
}

Text is a literal text run (emoji shortcodes already substituted by the parser). Wire: {"kind":"text",span,"value"}.

type ThematicBreak

type ThematicBreak struct {
	Span document.Span
	ID   string
}

ThematicBreak is a horizontal rule. Wire: {"kind":"thematicBreak",span,id}.

type TokenRun

type TokenRun struct {
	Text      string
	TokenType string // chroma TokenType.String() name, e.g. "Keyword"
}

TokenRun is one syntax-highlight token of a code block: a slice of the code text tagged with chroma's canonical token-type name.

type Tree

type Tree struct {
	Blocks    []Block    // top-level blocks, in document order
	Footnotes []Footnote // footnote definitions, in first-reference order
}

Tree is the built render tree. Its wire form is produced by Tree.MarshalJSON; see the package documentation for the schema.

func Build

func Build(doc *document.Document, opts Options) (*Tree, error)

Build produces the resolved render tree for doc. It never mutates doc. All policy is applied here, through the same shared code paths the HTML renderer uses: URL resolution/filtering via derive.Href (resolve.Resolver trust contract included), raw-HTML sanitizing via derive.SanitizeHTML, admonition titles via derive.AdmonitionTitle, and footnote pairing via derive.Footnotes.

func (*Tree) FootnoteByIndex added in v0.11.0

func (t *Tree) FootnoteByIndex(index int) (Footnote, bool)

FootnoteByIndex returns the footnote definition in t.Footnotes whose Index matches index (typically a FootnoteRef.Index), so a host can resolve a reference to its definition's ID/Span/Blocks — e.g. for scroll-to-definition — without walking source line numbers. Unlike FootnoteRef.DefID, this always works regardless of Options.Source. Reports ok=false when no definition with that index exists (e.g. a hand-built tree with mismatched refs).

func (*Tree) MarshalJSON

func (t *Tree) MarshalJSON() ([]byte, error)

MarshalJSON encodes the tree in the strict version-1 wire format documented on the package: {"version":1,"blocks":[…],"footnotes":[…]} with per-kind block/inline objects. Field order within each object is fixed (kind, span, id, kind fields, children/blocks) and presence rules are strict — see each node type's Wire comment.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL