Composition
Including another document
@include(src="introduction.xtxt")
Splices the target's blocks in place, unchanged.
Embedding under a section
## Reference
@embed(src="api-reference.xtxt")
The same, but the target's headings are demoted so it nests beneath the including section instead of competing with it. A # Title inside an embedded file becomes ## when embedded under a # heading.
Use @include when the pieces are peers, @embed when one belongs inside the other.
Resolving
Neither is expanded unless you ask:
xtxt export book.xtxt html --resolve
xtxt validate book.xtxt --resolve
Without --resolve the directive is preserved as-is. That is deliberate: a tool that only wants to read the document's own structure should not have to touch the filesystem.
What a resolver refuses
Rendering a document must never become a way to read arbitrary files, so a resolver is required to refuse:
- absolute paths
- any path that escapes the including document's directory
- remote sources, unless the host application opts in explicitly
It must also detect cycles, bound nesting depth, and bound the total number of nodes produced.
note
- Title
- Why the total matters separately Cycle detection tracks the path currently being expanded, so including the same file twice from different places is legal — that is a diamond, not a cycle, and it should stay legal. But it means depth alone does not bound the work: a file that includes four others, fourteen levels deep, is 268 million nodes from under 1.5 KB on disk. Bounding depth without bounding total expansion looks like a defence without being one.
A book layout
book.xtxt
chapters/01-introduction.xtxt
chapters/02-method.xtxt
@metadata
title = The Book
@endmetadata
# The Book
@embed(src="chapters/01-introduction.xtxt")
@embed(src="chapters/02-method.xtxt")
xtxt export book.xtxt html --resolve -o book.html