Templates & Referencing
Relevant source files
The following files were used as context for generating this wiki page:
The chemd system implements a lightweight referencing and templating engine designed to reduce redundancy in electronic lab notebooks (ELN). It allows authors to declare chemical data once in structured blocks and reference that data throughout the document or via reusable templates. The system is intentionally non-programmable, focusing on value lookup and AST expansion rather than arbitrary scripting.
Sources: chemd-v0.1-spec.md:1-26, chemd-v0.1-architecture.md:415-418
Referencing System
References in chemd use the @ prefix to perform lightweight lookups against the document metadata or structured object index. These references are tokenized during the parsing stage and resolved during the resolution stage.
Reference Syntax Types
| Syntax | Kind | Description |
|---|---|---|
@id | Object Reference | References a block by its unique ID (e.g., @rxn-main). |
@id.field | Object Field | Reads a specific field from an object (e.g., @res-main.yield). |
@meta.key | Meta Reference | Reads a value from the YAML frontmatter (e.g., @meta.project). |
@alias.field | Alias Reference | Uses a template-bound or implicit alias to find a field. |
@param.key | Param Reference | Accesses a free parameter passed into a use block. |
Sources: chemd-v0.1-spec.md:383-406, packages/parser/tests/parser.test.ts:74-83
Resolution Logic
The resolver package is responsible for turning these tokens into concrete values. It builds an object index of all blocks with IDs and a template index for name-based lookups.
The diagram above illustrates the priority-based resolution order used by the resolver.
Sources: chemd-v0.1-spec.md:507-526, chemd-v0.1-architecture.md:270-280, packages/resolver/tests/resolver.test.ts:161-180
Template System
The template system allows users to define reusable Markdown fragments that can be injected with parameters and bound to specific data objects.
The template Block
A template block defines the structure and the expected data bindings. It includes an optional bind field for mapping aliases to sources and a params field for declaring free variables.
:::template quick-summary
bind: reaction=primary_reaction | result=primary_result
params: note
## Summary
Temp: @reaction.temperature
Yield: @result.yield
Note: @param.note
:::
Sources: chemd-v0.1-spec.md:434-457, packages/parser/tests/parser.test.ts:187-205
The use Block
The use block triggers the expansion of a template. It can provide overrides for aliases (e.g., specifying a specific reaction ID) or values for free parameters.
:::use quick-summary
note: Successful run
reaction: #rxn-02
:::
Sources: chemd-v0.1-spec.md:472-487, packages/resolver/tests/resolver.test.ts:79-111
Template Expansion Architecture
Expansion occurs in the resolver before rendering. The process transforms use nodes into standard AST fragments (Markdown or semantic blocks).
The sequence diagram shows how the use block is consumed and replaced by the resolver.
Sources: chemd-v0.1-architecture.md:295-310, packages/resolver/tests/resolver.test.ts:121-155
Technical Implementation
Data Structures
The internal representation of references and templates is managed through specific node types in the AST.
// Reference Token (from packages/parser/src/index.ts)
{
"type": "reference",
"kind": "object_field",
"source": "res-main",
"field": "yield",
"raw": "@res-main.yield"
}
// Use Node (from packages/core/src/index.ts)
{
"type": "use",
"template": "quick-summary",
"values": { "note": "stable run" }
}
Sources: chemd-v0.1-spec.md:594-601, packages/parser/tests/parser.test.ts:220-225
Rendering Resolved Values
The HTML renderer replaces resolved references with their values or formatted spans. If a reference is part of a Markdown node, the renderer iterates through the references array and performs substitution before generating the final HTML.
const applyInlineTokens = (escapedValue: string, node: MarkdownNode): string => {
let rendered = escapedValue;
for (const reference of node.references) {
if (reference.resolution?.status !== "resolved") continue;
rendered = rendered.split(escapeHtml(reference.raw)).join(
escapeHtml(stringifyValue(reference.resolution.value))
);
}
return rendered;
};
Sources: packages/renderer-html/src/index.ts:70-84, packages/renderer-html/tests/renderer-html.test.ts:80-105
Validation and Constraints
To maintain stability, the system enforces several constraints:
- No Circularity: Circular template chains (A uses B, B uses A) result in an
E_TEMPLATE_CYCLEerror. - Expansion Limits: A hard depth limit (e.g., 40 levels) is enforced to prevent Denial of Service via recursive expansion.
- No Scripting: Template bodies are restricted to literal text, standard Markdown, and recognized
@references.
Sources: chemd-v0.1-spec.md:550-552, packages/resolver/tests/resolver.test.ts:241-260, packages/resolver/tests/resolver.test.ts:310-335
Summary
Templates and referencing provide the "glue" that connects structured chemical data with narrative laboratory reports. By separating the declaration of data (blocks) from its display (references and templates), chemd ensures that experimental parameters remain consistent and easily updated across the entire document. The resolution process transforms these high-level abstractions into a normalized AST ready for multi-format rendering into HTML, JSON, or DOCX.