Documentclass#
The editable document tree — the working format you read, edit, and output.
A Document wraps the internal arena tree behind a fallible, DOM-style
surface, so no internal panic ever reaches the caller. Reads go through
read-only Node handles; edits are validated eagerly and raise on misuse.
The tree is always structurally valid, even when it contains Error nodes.
A tree that has_errors() is read-only, fixed that way at construction:
every editing method then raises EditError. The only use for an error
tree is inspection.
Construct one by parsing (Parser.parse), from scratch (Document()),
or from a snapshot (Document.from_syntax). For the conceptual model, see
the Parsing guide.
import texform
doc = texform.Parser().parse(r"\frac{x}{y}")["document"]
doc.to_latex() # '\frac { x } { y }'Construction and copying#
Document#
Document( knowledge_base: KnowledgeBase | None = None, mode: Literal['math', 'text'] = 'math',) -> NoneConstruct an empty document holding a single empty root.
This is a complete tree: has_errors() is False and it is fully
editable. It serializes to the empty string.
| Parameter | Type | Default |
|---|---|---|
knowledge_base | Knowledge | None |
mode | Literal[ | 'math' |
doc = texform.Document()
doc.to_latex() # ''from_syntaxstatic method#
from_syntax( node: SyntaxNode, knowledge_base: KnowledgeBase | None = None,) -> DocumentBuild a document from a SyntaxNode dict, the lossless parse snapshot.
from_syntax and to_syntax are symmetric over every node kind,
including Error and Prime. A bare Prime is a math symbol;
use Scripted with an empty group base for a leading quote. Invalid external syntax is rejected
rather than corrupting the tree. The document can be transformed by any
engine sharing its knowledge-base instance.
| Parameter | Type | Default | Description |
|---|---|---|---|
node | Syntax | — | A |
knowledge_base | Knowledge | None | — |
Document — A new Document wrapping the snapshot.
ConformanceError—If the snapshot does not conform to the knowledge base.
result = texform.Parser().parse(r"\frac{x}{y}")
document = result["document"]
assert document is not None
syntax = document.to_syntax()
doc = texform.Document.from_syntax(syntax)copy#
copy() -> DocumentDeep-copy the tree into a new document with new node identities.
The copy shares this document's knowledge base, so a transform engine built on it accepts both. Also available as copy.copy and copy.deepcopy.
Queries#
root#
root() -> NodeReturn the root Node of the tree.
Node — The Root node handle.
doc.root().kind() # 'Root'has_errors#
has_errors() -> boolReport whether the tree contains any Error placeholder node.
This is a cheap O(1) signal, separate from structural validity and
independent of the abort_on_error parse setting. When True, the
document is read-only.
bool — True if the tree holds at least one Error node.
result = texform.Parser().parse(r"\sqrt[")
document = result["document"]
assert document is not None
document.has_errors() # Trueis_read_only#
is_read_only() -> boolReport whether the document is read-only.
A tree that has_errors() is read-only and fixed that way at
construction; its error count cannot change, so its read-only-ness cannot
either.
bool — True if no editing method will succeed on this document.
errors#
errors() -> list[Node]Return the Error placeholder nodes in the tree.
list[Node] — The list of Error node handles, empty when has_errors() is False.
find_commands#
find_commands(name: str) -> list[Node]Return every command node whose name matches.
| Parameter | Type | Description |
|---|---|---|
name | str | The command name without the leading backslash. |
list[Node] — The matching Command node handles, in document order.
doc = texform.Parser().parse(r"\frac{x}{y} + \frac{a}{b}")["document"]
len(doc.find_commands("frac")) # 2find_environments#
find_environments(name: str) -> list[Node]Return every environment node whose name matches.
| Parameter | Type | Description |
|---|---|---|
name | str | The environment name. |
list[Node] — The matching Environment node handles, in document order.
node_spans#
node_spans() -> list[NodeSpanEntry]Return source byte spans for the nodes that carry them.
list[NodeSpanEntry] — A list of {"id": str, "span": {...}} entries mapping node identifiers to source byte ranges.
node_at#
node_at(path: str) -> NodeResolve a tree path to a node handle.
Paths are rooted at root and use the scheme of Node.path(); use paths read from the current tree, since edits shift them.
| Parameter | Type | Description |
|---|---|---|
path | str | A path such as one returned by |
Node — The node at that path.
EditError—If the path does not exist.
knowledge_base#
knowledge_base() -> KnowledgeBaseReturn the immutable knowledge base bound to this document.
Construction and edits are checked against it; to_latex is not.
Output#
to_latex#
to_latex(**options) -> strSerialize the tree back to LaTeX text.
Error nodes round-trip their captured source snippet verbatim, so a
partial tree round-trips losslessly. The serializer guarantees text
idempotency: re-parsing and re-serializing the output yields the
same string. There is intentionally no method named serialize on
Document; this is the text channel and to_syntax() is the data
channel.
| Parameter | Type | Description |
|---|---|---|
**options | Unpack[ |
|
str — The serialized LaTeX string.
ConfigError—If an option key is unknown or a value has the wrong type.
TypeError—If options are passed as a positional dict instead of keyword arguments. Use
doc.to_latex(**options)for a dict.
doc = texform.Parser().parse(r"x^2")["document"]
doc.to_latex() # 'x ^ { 2 }'
doc.to_latex(style="compact") # 'x^2'to_tokenized_latex#
to_tokenized_latex(**options) -> TokenizedLatexSerialize the tree to LaTeX together with typed output tokens.
start_byte and end_byte are UTF-8 byte offsets, not Python
string indices. Empty error snippets produce no zero-width token; use
has_errors() to detect whether the document contains error nodes.
| Parameter | Type | Description |
|---|---|---|
**options | Unpack[ |
|
TokenizedLatex — A TokenizedLatex dict with latex and tokens.
ConfigError—If an option key is unknown or a value has the wrong type.
tok = texform.Parser().parse(r"x^2")["document"].to_tokenized_latex()
tok["latex"] # 'x ^ { 2 }'
tok["tokens"][0]["kind"] # 'character'to_syntax#
to_syntax() -> SyntaxNodeConvert the tree to a SyntaxNode dict for serde and transport.
This is the structured-data channel, distinct from the text channel
to_latex(). The conversion is symmetric with from_syntax.
SyntaxNode — A SyntaxNode dict, the single serde DTO for the tree.
doc.to_syntax() # {'Root': {...}}to_columnar#
to_columnar() -> ColumnarTreeFlatten the tree into columns for bulk structural analysis with Arrow or DataFrame tools.
Each table can be passed directly to pyarrow.table. Parent, slot
index, and content columns use -1 when no row or index applies. Other
missing values are None. Modes describe each node's context. Detached
subtrees are excluded; incomplete documents remain exportable.
Staging constructors#
The create_* methods, parse_fragment, clone_node, and import_node build detached subtrees owned by this document. A staged node is not in the tree until you attach it with an edit method.
create_char#
create_char(value: str, mode: RuntimeContentMode | None = None) -> NodeCreate a detached Char node owned by this document.
Staged nodes are not in the tree until attached with an edit method. The context mode defaults to the document root's mode; every create_* method accepts it as a keyword to construct in another context.
| Parameter | Type | Default | Description |
|---|---|---|---|
value | str | — | The single character. |
mode | Runtime | None | The context mode, |
Node — The detached Char node handle.
ConformanceError—If the character cannot be written in that mode, such as
\,^,~, or a math-mode space.EditError—If the document is read-only.
create_text#
create_text(value: str, mode: RuntimeContentMode | None = None) -> NodeCreate a detached Text node owned by this document.
| Parameter | Type | Default | Description |
|---|---|---|---|
value | str | — | The text value. |
mode | Runtime | None | The context mode. Defaults to the root mode. |
Node — The detached Text node handle.
ConformanceError—If the text cannot be written in that mode.
EditError—If the document is read-only.
create_active_space#
create_active_space(mode: RuntimeContentMode | None = None) -> NodeCreate a detached ActiveSpace node owned by this document.
| Parameter | Type | Default |
|---|---|---|
mode | Runtime | None |
Node — The detached ActiveSpace node handle.
create_group#
create_group( mode: RuntimeContentMode | None = None, children: list[Node | str] | None = None,) -> NodeCreate a detached brace Group, built in its own mode.
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | Runtime | None | The group's content mode and context. Defaults to the root mode. |
children | list[ | None | Detached nodes or source strings. A string is parsed in |
Node — The detached Group node handle.
ParseError—If a source string does not parse cleanly.
ConformanceError—If a child was built in another mode or an infix has siblings.
EditError—If a child is attached, foreign, or repeated, or the document is read-only.
doc = texform.Document()
group = doc.create_group("math", ["x^2", doc.create_char("y")])create_command#
create_command( name: str, args: list[Arg] | None = None, mode: RuntimeContentMode | None = None,) -> NodeCreate a detached prefix Command owned by this document.
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | — | The command name without the leading backslash. A name missing from the knowledge base creates an unknown command without arguments. |
args | list[ | None | Values for every signature slot, or only the required slots. A |
mode | Runtime | None | The context mode. Defaults to the root mode. |
Node — The detached Command node handle.
ConformanceError—If the name is not a prefix command (the message names the right constructor, such as
create_declarativeforbf), the argument count is wrong, or an argument does not fit its slot.ParseError—If a source argument does not parse cleanly.
EditError—For invalid nodes or a read-only document.
doc = texform.Document()
cmd = doc.create_command("sqrt", ["x"])
doc.append_child(doc.root(), cmd)
doc.to_latex() # '\sqrt { x }'create_declarative#
create_declarative( name: str, args: list[Arg] | None = None, mode: RuntimeContentMode | None = None,) -> NodeCreate a detached Declarative command such as \bf.
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | — | The declarative command name without the leading backslash. |
args | list[ | None | Slot values, as for |
mode | Runtime | None | The context mode. Defaults to the root mode. |
Node — The detached Declarative node handle.
ConformanceError—Under the conditions of
create_command, except the record must be declarative.ParseError—If a source argument does not parse cleanly.
create_environment#
create_environment( name: str, args: list[Arg] | None = None, body: Node | list[Node | str] | str | None = None, mode: RuntimeContentMode | None = None,) -> NodeCreate a detached Environment owned by this document.
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | — | The environment name. An unknown environment takes no arguments and uses the surrounding mode. |
args | list[ | None | Slot values, as for |
body | Node | | None | A detached group in the body mode, a source string parsed in the recorded body mode, a list whose source strings are spliced as in |
mode | Runtime | None | The context mode. Defaults to the root mode. |
Node — The detached Environment node handle.
ConformanceError—If an argument does not fit the signature or the body is not a group in the body mode.
ParseError—If source does not parse cleanly.
EditError—For invalid nodes or a read-only document.
doc = texform.Document()
env = doc.create_environment("matrix", body="a & b")create_alignment_tab#
create_alignment_tab(mode: RuntimeContentMode | None = None) -> NodeCreate a detached AlignmentTab node owned by this document.
This is the unescaped & that separates cells in alignment environments. Use create_char("&") for a literal ampersand, which serializes as \&.
| Parameter | Type | Default |
|---|---|---|
mode | Runtime | None |
Node — The detached AlignmentTab node handle.
create_delimited_group#
create_delimited_group( left: str, right: str, children: list[Node | str] | None = None, mode: RuntimeContentMode | None = None,) -> NodeCreate a detached \left...\right group, built in math mode.
| Parameter | Type | Default | Description |
|---|---|---|---|
left | str | — | The left delimiter source, such as |
right | str | — | The right delimiter source. |
children | list[ | None | Detached nodes or source strings, spliced as in |
mode | Runtime | None | The context mode. Defaults to |
Node — The detached delimited Group node handle.
ConformanceError—If a delimiter is neither
.nor registered in the knowledge base, or a child does not fit.ParseError—If a source string does not parse cleanly.
EditError—For invalid children or a read-only document.
create_inline_math#
create_inline_math( children: list[Node | str] | None = None, mode: RuntimeContentMode | None = None,) -> NodeCreate a detached $...$ group whose children are math.
| Parameter | Type | Default | Description |
|---|---|---|---|
children | list[ | None | Detached nodes or source strings, spliced as in |
mode | Runtime | None | The context mode. Defaults to |
Node — The detached inline-math Group node handle.
ParseError—If a source string does not parse cleanly.
ConformanceError—If a child does not fit or the context is math.
EditError—For invalid children or a read-only document.
create_scripted#
create_scripted( base: Node | str, sub: Node | str | None = None, sup: Node | str | None = None, mode: RuntimeContentMode | None = None,) -> NodeCreate a detached Scripted node with a base and subscript and/or superscript.
| Parameter | Type | Default | Description |
|---|---|---|---|
base | Node | | — | The base node or source string. |
sub | Node | | None | The subscript node or source, or |
sup | Node | | None | The superscript node or source, or |
mode | Runtime | None | The context mode. Defaults to |
Node — The detached Scripted node handle.
ConformanceError—If both scripts are absent, or the base is a scripted node, an infix, or an alignment tab.
ParseError—If a source string does not parse cleanly.
EditError—For invalid nodes or a read-only document.
doc = texform.Document()
doc.append_child(doc.root(), doc.create_scripted("x", sup="2"))create_prime#
create_prime(count: int, mode: RuntimeContentMode | None = None) -> NodeCreate a detached run of count primes.
| Parameter | Type | Default | Description |
|---|---|---|---|
count | int | — | The number of primes; must be positive. |
mode | Runtime | None | The context mode. Defaults to |
Node — The detached Prime node handle.
ConformanceError—If
countis zero.
create_infix#
create_infix( name: str, left: Node | str, right: Node | str, args: list[Arg] | None = None, mode: RuntimeContentMode | None = None,) -> NodeCreate a detached Infix command such as \over.
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | — | The infix command name without the leading backslash. |
left | Node | | — | The left operand node or source string. |
right | Node | | — | The right operand node or source string. |
args | list[ | None | Values for extra slots, such as the dimension of |
mode | Runtime | None | The context mode. Defaults to |
Node — The detached Infix node handle.
ConformanceError—Under the conditions of
create_command, except the record must be infix.ParseError—If a source operand or argument does not parse cleanly.
parse_fragment#
parse_fragment( source: str, mode: RuntimeContentMode | None = None,) -> NodeParse source into a detached implicit Group.
| Parameter | Type | Default | Description |
|---|---|---|---|
source | str | — | The LaTeX source. |
mode | Runtime | None | The parse mode. Defaults to the root mode. |
Node — The detached Group node handle.
ParseError—If the source does not parse cleanly.
diagnosticscarries the parser diagnostics.
clone_node#
clone_node(node: Node) -> NodeDeep-copy a subtree of this document as a detached node.
| Parameter | Type | Description |
|---|---|---|
node | Node | The subtree root to copy. |
Node — The detached copy, ready to attach.
EditError—If
nodeis foreign or missing, or the document is read-only.
import_node#
import_node(node: Node) -> NodeCopy a subtree from another document into this one as a detached node.
The subtree is validated against this document's knowledge base, so importing across knowledge bases can fail where clone_node cannot.
| Parameter | Type | Description |
|---|---|---|
node | Node | A node of another document. |
Node — The detached copy, owned by this document.
ConformanceError—If the subtree does not conform to this knowledge base.
EditError—If the edit is invalid.
Edit methods#
All edits are validated eagerly and raise EditError on misuse. Nodes must belong to this document, the root cannot be detached, and a tree that has_errors() is read-only.
append_child#
append_child(parent: Node, child: Node) -> NoneAppend child as the last child of parent.
| Parameter | Type | Description |
|---|---|---|
parent | Node | The container node to append into. |
child | Node | The detached or detached node to attach. |
EditError—If the edit is invalid (read-only tree, root protection, wrong container shape, or a foreign node).
insert_before#
insert_before(anchor: Node, new: Node) -> NoneInsert new immediately before anchor among its siblings.
| Parameter | Type | Description |
|---|---|---|
anchor | Node | The node to insert before. |
new | Node | The detached or detached node to attach. |
EditError—If the edit is invalid.
insert_after#
insert_after(anchor: Node, new: Node) -> NoneInsert new immediately after anchor among its siblings.
| Parameter | Type | Description |
|---|---|---|
anchor | Node | The node to insert after. |
new | Node | The detached or detached node to attach. |
EditError—If the edit is invalid.
insert_child#
insert_child(parent: Node, index: int, child: Node) -> NoneInsert child at index among parent's children.
| Parameter | Type | Description |
|---|---|---|
parent | Node | The container node to insert into. |
index | int | The zero-based position to insert at. |
child | Node | The detached or detached node to attach. |
EditError—If the edit is invalid, including an out-of-bounds index.
replace_with#
replace_with(target: Node, replacement: Node) -> NoneReplace target in the tree with replacement.
| Parameter | Type | Description |
|---|---|---|
target | Node | The node to remove from its position. |
replacement | Node | The detached or detached node to put in its place. |
EditError—If the edit is invalid.
wrap#
wrap(target: Node, wrapper: Node) -> NodeWrap target inside wrapper, putting wrapper in its place.
| Parameter | Type | Description |
|---|---|---|
target | Node | The node to be wrapped. |
wrapper | Node | The detached container node to wrap it in. |
Node — The wrapper node handle, now in the tree.
EditError—If the edit is invalid.
unwrap#
unwrap(group: Node) -> list[Node]Remove a group, splicing its children into the parent in its place.
| Parameter | Type | Description |
|---|---|---|
group | Node | The group node to dissolve. |
list[Node] — The freed child node handles, now attached to the parent.
EditError—If the edit is invalid.
doc = texform.Parser().parse(r"{x y}")["document"]
group = next(n for n in doc.root().descendants() if n.kind() == "Group")
doc.unwrap(group)
doc.to_latex() # 'x y'extract#
extract(node: Node) -> NodeDetach node from the tree and return it as a detached node.
| Parameter | Type | Description |
|---|---|---|
node | Node | The node to remove from its position and keep. |
Node — The detached node handle, reusable in a later edit.
EditError—If the edit is invalid.
remove#
remove(node: Node) -> NoneRemove node from the tree and discard it.
| Parameter | Type | Description |
|---|---|---|
node | Node | The node to remove. |
EditError—If the edit is invalid, such as removing the root.
clear#
clear(container: Node) -> NoneRemove every child of container.
| Parameter | Type | Description |
|---|---|---|
container | Node | The node whose children are removed. |
EditError—If the edit is invalid.
set_command_name#
set_command_name(node: Node, name: str) -> NoneRename a command node in place.
| Parameter | Type | Description |
|---|---|---|
node | Node | The |
name | str | The new command name without the leading backslash. |
EditError—If the edit is invalid.
set_env_name#
set_env_name(node: Node, name: str) -> NoneRename an environment, keeping its arguments and body.
| Parameter | Type | Description |
|---|---|---|
node | Node | The |
name | str | The new environment name. |
ConformanceError—If the new name's signature or body mode does not fit the existing arguments and body.
EditError—If the edit is invalid.
set_text#
set_text(node: Node, value: str) -> NoneSet a text node's value in place.
| Parameter | Type | Description |
|---|---|---|
node | Node | The |
value | str | The new text value. |
EditError—If the edit is invalid.
set_char#
set_char(node: Node, value: str) -> NoneSet a char node's value in place.
| Parameter | Type | Description |
|---|---|---|
node | Node | The |
value | str | The new single character. |
EditError—If the edit is invalid.
set_arg#
set_arg(node: Node, index: int, value: Arg) -> NoneSet the argument at index of a command-like node or environment.
| Parameter | Type | Description |
|---|---|---|
node | Node | The command-like node or environment whose argument is set. |
index | int | The zero-based slot index. |
value | Arg | The slot source (a |
ConformanceError—If the value does not fit the slot.
ParseError—If a source string does not parse cleanly.
EditError—If the edit is invalid, including an out-of-bounds index.
set_arg_delimiters#
set_arg_delimiters( node: Node, index: int, open: str, close: str,) -> NoneChange only the boundaries of a filled paired argument.
| Parameter | Type | Description |
|---|---|---|
node | Node | The command-like node. |
index | int | The zero-based slot index. |
open | str | The new opening delimiter source. |
close | str | The new closing delimiter source. |
ConformanceError—If the slot is not a filled paired slot or the delimiters are not allowed.
EditError—If the edit is invalid.
set_delimiters#
set_delimiters(node: Node, left: str, right: str) -> NoneChange the delimiters of a \left...\right group.
| Parameter | Type | Description |
|---|---|---|
node | Node | The delimited |
left | str | The new left delimiter source. |
right | str | The new right delimiter source. |
ConformanceError—If a delimiter is neither
.nor registered in the knowledge base.EditError—If the edit is invalid.
set_subscript#
set_subscript(target: Node, sub: Arg) -> NodeSet or clear the subscript of target.
If target is not scripted, it becomes the base of a new Scripted node; an existing scripted base is resolved automatically.
| Parameter | Type | Description |
|---|---|---|
target | Node | The base node or an existing |
sub | Arg | The new subscript node or source, or |
Node — The resulting Scripted node, or the base when clearing the last script collapses the wrapper.
ConformanceError—If the base cannot carry scripts.
ParseError—If a source string does not parse cleanly.
EditError—If the edit is invalid.
set_superscript#
set_superscript(target: Node, sup: Arg) -> NodeSet or clear the superscript of target.
Behaves like set_subscript for the superscript slot.
| Parameter | Type | Description |
|---|---|---|
target | Node | The base node or an existing |
sup | Arg | The new superscript node or source, or |
Node — The resulting Scripted node, or the base after a collapse.
ConformanceError—If the base cannot carry scripts.
ParseError—If a source string does not parse cleanly.
EditError—If the edit is invalid.
set_prime_count#
set_prime_count(node: Node, count: int) -> NoneSet the number of primes in a Prime node.
| Parameter | Type | Description |
|---|---|---|
node | Node | The |
count | int | The new count; must be positive. |
ConformanceError—If
countis not positive.EditError—If the edit is invalid.
NodeSpanEntrydict type#
One entry mapping a node identifier to its source byte span.
| Field | Type | Description |
|---|---|---|
id | str | The node identifier within the document. |
span | Span | A |
TokenizedLatexdict type#
Serialized LaTeX and tokens produced by the same serializer traversal.
Text and tokenized output share one traversal and spacing policy; this is not a second lexer pass.
| Field | Type | Description |
|---|---|---|
latex | str | The serialized LaTeX string, identical to |
tokens | list[ | Ordered, non-overlapping, non-empty fragments. Gaps between spans contain serializer-inserted spacing. |
SerializationTokendict type#
One serialization fragment with UTF-8 byte offsets.
start_byte / end_byte index the accompanying TokenizedLatex.latex
string and are UTF-8 byte offsets, not Python code-point indices. Spans do
not overlap. Empty error snippets emit no token.
| Field | Type | Description |
|---|---|---|
text | str | The UTF-8 decoding of the byte slice |
start_byte | int | Inclusive UTF-8 byte offset into the serialized LaTeX string. |
end_byte | int | Exclusive UTF-8 byte offset into the serialized LaTeX string. |
kind | Serialization | Semantic category of the fragment (control sequence, character, ...). |
mode | Runtime | Semantic math/text mode of the fragment, independent of spacing policy. |
SerializationTokenKindtype#
Literal['control_sequence', 'character', 'delimiter', 'text', 'raw', 'error']Semantic category of one serialization fragment.
NodeTabledict type#
Preorder node columns. Row indices are local to this snapshot.
| Field | Type |
|---|---|
kind | list[ |
parent | list[ |
slot | list[ |
slot_index | list[ |
depth | list[ |
mode | list[ |
name | list[ |
value | list[ |
known | list[ |
group_kind | list[ |
left | list[ |
right | list[ |
ArgumentTabledict type#
All argument slots, ordered by owner row and slot index.
| Field | Type |
|---|---|
owner | list[ |
index | list[ |
form | list[ |
present | list[ |
value_kind | list[ |
value | list[ |
content | list[ |
open | list[ |
close | list[ |
ColumnarTreedict type#
Tree columns for bulk structural analysis with Arrow or DataFrame tools.
| Field | Type |
|---|---|
nodes | Node |
args | Argument |