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
langand trailing infometa) 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, positionalargs, andoptions, plus either parsed flow children (admonition-like directives) or a verbatimvalue(code/raw directives).
- class myform.model.Doc(*, root: Root = <factory>, env: dict[str, ~typing.Any]={}, source: str = '')#
A parsed document: the typed
Roottree plus the parse-environment snapshot.envis 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 amyform.collect.Collectionto 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.
- class myform.model.Link(*, span: Span | None = None, raw: TokenRaw | None = None, source_raw: SyntaxRaw | None = None, type: 'link' = 'link', url: str = '', title: str | None = None, wiki: bool = False, children: list[PhrasingNode] = [])#
A hyperlink around phrasing content.
wikirecords 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
ListItemnodes.
- 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.
checkedis the mdast/GFM task-item primitive:Nonefor an ordinary item,True/Falsefor a checked/unchecked task item (a pure checkbox list, per GFM’s own schema –Listitself carries no task-list flag, exactly as mdast does not). Populated on read by readers that encounter a native checkbox construct (GFM task lists, TiptaptaskItemnodes); writers without a native task-list construct degrade it through the lowering registry rather than silently dropping it (seedegrade/lowerings.py’s*-tasklist-checked-*rules forrst/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
typediscriminator and its own typedchildrenlist (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 positionalmap/levelkeys 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
unitto'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;
headermarks 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
TableCellnodes.
- 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).
openerholds the node’s own token;closeris present for paired (*_open/*_close) tokens;inlineis the wrappinginlinetoken payload for leaf blocks whose children are phrasing content.tailcounts 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
Tokenfield exceptchildren: achildrenkey equal toNonerecords 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) whosetypeis one oftypes.
- myform.model.from_tokens(tokens: Sequence[Token], env: Mapping[str, Any] | None = None, source: str = '') Doc#
Build a typed
Docfrom 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_tokenscan rebuild an equal stream.
- myform.model.get(node: Node, path: Path) Node#
Resolve a
walk_with_pathaddress back to its node.- Parameters:
node – The root the path is relative to.
path – The child-index trail to follow.
- Returns:
The addressed descendant (
nodeitself for the empty path).
- myform.model.to_tokens(doc: Doc) list[Token]#
Rebuild a markdown-it token stream from a typed
Doctree.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 noraw.
- 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.
fnthen maps each (possibly rebuilt) node to its replacement – returningNonekeeps 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;
Nonemeans “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
fnonnodeand every descendant, depth-first (return values ignored).
- myform.model.walk(node: Node) Iterator[Node]#
Yield
nodeand 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;pathis 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.