model: The Typed Document Model (nodes, token bridge, and traversal)#

The absolute middle of the myform pipeline: nodes defines the myst-spec-aligned pydantic node vocabulary plus the Doc wrapper; bridge maintains the bijective mapping between markdown-it token streams and Doc trees (to_tokens(from_tokens(T)) == T for every stream the markdown readers produce); walk provides visitors, transforms, and node addressing. Nodes are pure data – every operation on them lives here or in the readers/writers around them.

The Typed Document Model (nodes, token bridge, and traversal).

The absolute middle of the myform pipeline: nodes defines the myst-spec-aligned pydantic node vocabulary plus the Doc wrapper; bridge maintains the bijective mapping between markdown-it token streams and Doc trees (to_tokens(from_tokens(T)) == T for every stream the markdown readers produce); walk provides visitors, transforms, and node addressing. Nodes are pure data – every operation on them lives here or in the readers/writers around them.

class myform.model.BlockBreak(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'blockBreak' = 'blockBreak', value: str = '', children: list[PhrasingNode] = [])#

A MyST block break (+++), optionally carrying metadata text.

class myform.model.Blockquote(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'blockquote' = 'blockquote', children: list[FlowNode] = [])#

A >-quoted block of flow content.

class myform.model.Break(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'break' = 'break', hard: bool = False, children: list[PhrasingNode] = [])#

A line break – hard (explicit) or soft (newline in source).

class myform.model.Code(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'code' = 'code', lang: str = '', meta: str = '', value: str = '', children: list[PhrasingNode] = [])#

A literal code block: fenced (with lang and trailing info meta) or indented.

class myform.model.Comment(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'mystComment' = 'mystComment', value: str = '', children: list[PhrasingNode] = [])#

A MyST line comment (% text).

class myform.model.DefinitionDescription(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'definitionDescription' = 'definitionDescription', children: list[FlowNode] = [])#

A definition-list description of flow content.

class myform.model.DefinitionList(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'definitionList' = 'definitionList', children: list[FlowNode] = [])#

A definition list; children alternate DefinitionTerm / DefinitionDescription.

class myform.model.DefinitionTerm(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'definitionTerm' = 'definitionTerm', children: list[PhrasingNode] = [])#

A definition-list term of phrasing content.

class myform.model.Delete(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'delete' = 'delete', children: list[PhrasingNode] = [])#

Struck-through phrasing content (GFM ~~...~~).

class myform.model.Directive(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'mystDirective' = 'mystDirective', name: str = '', args: str = '', options: dict[str, Any] = {}, value: str | None = None, children: list[FlowNode] = [])#

A MyST directive.

Carries the name, positional args, and options, plus either parsed flow children (admonition-like directives) or a verbatim value (code/raw directives).

class myform.model.Doc(*, root: Root = <factory>, env: dict[str, ~typing.Any]={}, source: str = '')#

A parsed document: the typed Root tree plus the parse-environment snapshot.

env is the markdown-it parse environment as the reader left it – link-reference definitions and similar document-level state live there rather than in tokens, and mdformat’s renderer consults it, so writers deep-copy it back into each render.

class myform.model.Emphasis(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'emphasis' = 'emphasis', children: list[PhrasingNode] = [])#

Emphasized (italic) phrasing content.

class myform.model.FootnoteDefinition(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'footnoteDefinition' = 'footnoteDefinition', label: str = '', children: list[FlowNode] = [])#

A footnote definition ([^label]: ...) of flow content.

class myform.model.FootnoteReference(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'footnoteReference' = 'footnoteReference', label: str = '', children: list[PhrasingNode] = [])#

A footnote reference ([^label]).

class myform.model.FrontMatter(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'frontMatter' = 'frontMatter', value: str = '', children: list[PhrasingNode] = [])#

YAML front matter delimited by --- fences.

class myform.model.Heading(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'heading' = 'heading', depth: int = 1, children: list[PhrasingNode] = [])#

A section heading at depth (1-6).

class myform.model.Ignored(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'ignored' = 'ignored', value: str = '', children: list[PhrasingNode] = [])#

A verbatim source span preserved by a skip-formatting comment (see Ignore).

class myform.model.Embed(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'embed' = 'embed', target: str = '', alias: str = '', children: list[PhrasingNode] = [])#

A transclusion: render another document here, rather than link to it.

Obsidian spells this ![[Target]], and uses one syntax for two things – embedding an asset (![[diagram.png]]) and transcluding a note (![[Note#section]]). The distinction needs a myform.collect.Collection to make, so this node deliberately records the reference verbatim and leaves interpretation to whoever has one.

class myform.model.Highlight(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'highlight' = 'highlight', children: list[PhrasingNode] = [])#

Marked or highlighted phrasing (Obsidian ==text==, HTML <mark>).

class myform.model.Image(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'image' = 'image', url: str = '', title: str | None = None, children: list[PhrasingNode] = [])#

An image; children carry the alt-text phrasing.

class myform.model.InlineTypstRaw(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'inlineTypstRaw' = 'inlineTypstRaw', syntax_kind: str = '', value: str = '', grade: MappingGrade = MappingGrade.ABSENT, children: list[PhrasingNode] = [])#

An unsupported Typst inline slice retained until reported degradation lowers it.

class myform.model.InlineCode(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'inlineCode' = 'inlineCode', value: str = '', children: list[PhrasingNode] = [])#

An inline code span.

class myform.model.InlineMath(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'inlineMath' = 'inlineMath', value: str = '', children: list[PhrasingNode] = [])#

An inline math span ($...$).

class myform.model.InlineUnknown(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'inlineUnknown' = 'inlineUnknown', children: list[AnyNode] = [])#

The phrasing catch-all: any inline token outside the typed vocabulary.

A hyperlink around phrasing content.

wiki records that the source spelled this link as a wikilink ([[Target|alias]]) rather than as a CommonMark link. It is a spelling rather than a distinct semantic: a wikilink and a markdown link denote the same thing, and every writer may render either spelling, so the flag exists only so a dialect that has the wikilink form can round-trip it exactly. Writers with no wikilink syntax ignore it and emit an ordinary link, which is why this is additive rather than a new node kind.

class myform.model.List(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'list' = 'list', ordered: bool = False, start: int | None = None, children: list[FlowNode] = [])#

An ordered or bullet list; children are its ListItem nodes.

class myform.model.ListItem(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'listItem' = 'listItem', checked: bool | None = None, children: list[FlowNode] = [])#

One list item of flow content.

checked is the mdast/GFM task-item primitive: None for an ordinary item, True/ False for a checked/unchecked task item (a pure checkbox list, per GFM’s own schema – List itself carries no task-list flag, exactly as mdast does not). Populated on read by readers that encounter a native checkbox construct (GFM task lists, Tiptap taskItem nodes); writers without a native task-list construct degrade it through the lowering registry rather than silently dropping it (see degrade/lowerings.py’s *-tasklist-checked-* rules for rst/typst).

class myform.model.Math(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'math' = 'math', value: str = '', label: str | None = None, children: list[PhrasingNode] = [])#

A display math block, optionally labeled ($$...$$ (label)).

class myform.model.MappingGrade(*values)#

Confidence grade for a reader’s source construct to absolute-tree mapping.

class myform.model.Node(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None)#

The base document node: pure data with a stable content hash.

Every concrete subclass declares its literal type discriminator and its own typed children list (flow or phrasing, enforced by validation); the base deliberately declares neither, so the discriminated unions (FlowNode, PhrasingNode, AnyNode) are the typed face consumers match on. Logic beyond derived accessors is deliberately absent – rendering and lowering live in the bridge, writers, and the degradation engine.

property content_hash: str#

A stable SHA-256 over this node’s content, excluding source positions.

The dump is scrubbed of span, syntax-slice positions, and the positional map/level keys inside raw token payloads (the one documented normalization point), so two nodes with identical content at different source locations hash identically – the property the assist cache and the conversion report key on.

class myform.model.Paragraph(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'paragraph' = 'paragraph', children: list[PhrasingNode] = [])#

A paragraph of phrasing content.

class myform.model.Role(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'mystRole' = 'mystRole', name: str = '', value: str = '', children: list[PhrasingNode] = [])#

A MyST role ({name}`content`).

class myform.model.Root(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'root' = 'root', children: list[FlowNode] = [])#

The document root; its children are the top-level flow blocks.

class myform.model.Span(*, start: int, stop: int, unit: 'line' | 'byte' = 'line')#

A half-open source range [start, stop) as reported by the reader.

Markdown readers retain their historical line coordinates. Parse-only readers whose native parser reports UTF-8 byte offsets set unit to 'byte' instead.

class myform.model.Strong(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'strong' = 'strong', children: list[PhrasingNode] = [])#

Strong (bold) phrasing content.

class myform.model.Table(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'table' = 'table', children: list[AnyNode] = [])#

A pipe table; children are its section wrappers and rows as read.

class myform.model.TableCell(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'tableCell' = 'tableCell', header: bool = False, children: list[PhrasingNode] = [])#

One table cell of phrasing content; header marks a <th> cell.

class myform.model.TableRow(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'tableRow' = 'tableRow', children: list[AnyNode] = [])#

One table row; children are its TableCell nodes.

class myform.model.Target(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'mystTarget' = 'mystTarget', label: str = '', children: list[PhrasingNode] = [])#

A MyST target anchor ((label)=).

class myform.model.Text(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'text' = 'text', value: str = '', children: list[PhrasingNode] = [])#

A plain text run.

class myform.model.ThematicBreak(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'thematicBreak' = 'thematicBreak', children: list[PhrasingNode] = [])#

A horizontal rule.

class myform.model.TokenRaw(*, opener: TokenPayload | None = None, closer: TokenPayload | None = None, inline: TokenPayload | None = None, tail: int = 0)#

The verbatim token payloads backing a node (the absoluteness escape hatch).

opener holds the node’s own token; closer is present for paired (*_open/*_close) tokens; inline is the wrapping inline token payload for leaf blocks whose children are phrasing content. tail counts the trailing children that were block-level siblings of the inline token rather than its children (markdown-it’s footnote anchor is the one known producer); the bridge re-emits them after the inline token.

A payload stores every Token field except children: a children key equal to None records a childless token, while an absent key means the token’s children were captured as node children and are rebuilt by the bridge.

class myform.model.SyntaxRaw(*, format: 'typst' = 'typst', kind: str, source: str, start: int, end: int, grade: MappingGrade)#

A lossless parse-only syntax slice attached to a mapped Typst node.

Unlike TokenRaw, this payload is not a markdown-it token and cannot be replayed by the markdown bridge. Writers synthesize their format from the typed node while this payload keeps the source span, original bytes-as-text, CST kind, and mapping confidence inspectable.

class myform.model.TypstRaw(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'typstRaw' = 'typstRaw', syntax_kind: str = '', value: str = '', grade: MappingGrade = MappingGrade.ABSENT, children: list[PhrasingNode] = [])#

An unsupported or malformed Typst block retained as one source-spanned opaque slice.

class myform.model.Unknown(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'unknown' = 'unknown', children: list[AnyNode] = [])#

The flow catch-all: any block token outside the typed vocabulary, carried by raw.

myform.model.children_of(node: Node) → list[Node]#

Return a node’s children list (empty for value-only leaves).

myform.model.find_all(node: Node, *types: str) → list[Node]#

Collect every descendant (including node) whose type is one of types.

myform.model.from_tokens(tokens: Sequence[Token], env: Mapping[str, Any] | None = None, source: str = '') → Doc#

Build a typed Doc from a markdown-it token stream and its parse environment.

Parameters:
  • tokens – The block-level token stream a markdown reader produced.

  • env – The parse environment the same parse populated (link-reference definitions and similar document state live here); deep-copied into the returned document.

  • source – The producing reader’s name, recorded informationally.

Returns:

The typed document tree, from which to_tokens can rebuild an equal stream.

myform.model.get(node: Node, path: Path) → Node#

Resolve a walk_with_path address back to its node.

Parameters:
  • node – The root the path is relative to.

  • path – The child-index trail to follow.

Returns:

The addressed descendant (node itself for the empty path).

myform.model.to_tokens(doc: Doc) → list[Token]#

Rebuild a markdown-it token stream from a typed Doc tree.

The stream structure is reconstructed from the tree (pairing, inline wrapping, children); per-token payloads come from each node’s raw, overridden by the typed fields whenever a field was edited after reading, and synthesized outright for nodes with no raw.

myform.model.transform(node: N, fn: Callable[[Node], Node | None]) → N#

Rewrite a tree bottom-up without mutating the original.

Children are transformed first; if any changed, the parent is shallow-copied with the new children. fn then maps each (possibly rebuilt) node to its replacement – returning None keeps the node as-is. The root’s replacement type is trusted to match the input’s.

Parameters:
  • node – The subtree root to transform.

  • fn – The node mapper; None means “keep unchanged”.

Returns:

The transformed tree (the original object when nothing changed at all).

myform.model.visit(node: Node, fn: Callable[[Node], Any]) → None#

Call fn on node and every descendant, depth-first (return values ignored).

myform.model.walk(node: Node) → Iterator[Node]#

Yield node and every descendant, depth-first and pre-order.

myform.model.walk_with_path(node: Node, _path: Path = ()) → Iterator[tuple[Path, Node]]#

Yield (path, node) pairs depth-first; path is the child-index trail from the root.

Parameters:
  • node – The subtree root (yielded first, with path ()).

  • _path – The path prefix accumulated by recursion; callers leave it defaulted.