Document Authoring¶
A document definition is a part def specializing
DocumentQueries::Document. Its nested parts are its content, rendered in
declaration order. This chapter covers each block; the snippets are excerpts
of models that render with the current binary (most are drawn from the
worked example).
Document and sections¶
part def MassReport :> Document {
attribute redefines title = "Telescope Mass Report";
part breakdown : Section {
attribute redefines title = "Subsystem Masses";
part heavy : Section {
attribute redefines title = "Heavy Subsystems";
// ...
}
}
}
- The document's
titleis required and renders as the level-1 heading. - A
Sectionrequires atitletoo and renders as a heading one level deeper than its parent, saturating at Markdown's level 6. - Sections nest arbitrarily; anything a document can contain, a section can.
- A document nested inside a document is a planning error.
Paragraphs¶
A Paragraph carries exactly one of three things:
Static text — a literal text attribute:
A query — each projected value becomes one plain text run, joined by spaces (nested column runs restyle this — see Styled query text):
part summary : Paragraph {
calc names : HeavySubsystemNames {
in root = telescope;
in threshold = "10";
}
}
renders as mount segmentControl.
Inline runs — nested Span, Link and Ref parts, composed in
declaration order and joined by single spaces (next section).
Giving a paragraph both text and a query, or runs alongside either, is a typed planning error rather than a guess about your intent.
Inline runs, links and cross-references¶
part guide : Paragraph {
part lead : Span {
attribute redefines text = "This report is";
}
part generated : Span {
attribute redefines text = "generated";
attribute redefines style = "emphasis";
}
part tool : Span {
attribute redefines text = "sysml -render-document";
attribute redefines style = "code";
}
part docsLink : Link {
attribute redefines text = "(OpenSysML)";
attribute redefines target = "https://opensysml.org/";
}
part massesRef : Ref {
ref redefines target = breakdown;
}
}
renders as:
This report is *generated* `sysml -render-document` [(OpenSysML)](<https://opensysml.org/>) [Subsystem Masses](#breakdown)
Span carries required text and an optional style: "plain" (the
default), "emphasis" (*text*), "strong" (**text**), "code"
(`text`) or "math" ($text$, the text being LaTeX — see
Mathematics). Content is still escaped inside the styling — a
* in an emphasis span cannot break out of it, and a code span grows its
backtick fence past any backticks in the text.
Link carries required text and a required target URL, rendering as
an inline Markdown link with the destination in pointy brackets (so
parentheses and spaces in URLs survive).
Ref cross-references a named content block of this document — or of
another document — by name: ref redefines target = <block>. The renderer
gives the referenced block a stable HTML anchor derived from its named path —
<a id="breakdown"></a> before the section above — and the Ref renders as
a link to it. text is optional; it defaults to the target's title (for a
section), caption (for a table or diagram) or name. A target that is neither
a content block nor a document, or one without a stable name, is a typed
planning error.
Cross-document references¶
To reference another document, declare a usage typed by the target document definition, then name it (for the document's root) or reach into it with dot notation (for one of its content blocks):
ref appendix : 'Mass Appendix';
part def SystemReport :> Document {
attribute redefines title = "System Report";
part intro : Paragraph {
part see : Ref {
ref redefines target = appendix.tables.masses;
}
part whole : Ref {
ref redefines target = appendix;
}
}
}
Targets resolve at planning time against the loaded workspace. A content
target renders as a relative link into the target document's generated file
with the block's stable anchor — a destination like
Observatory-Mass.20Appendix.md#tables-masses behind the link text;
a root target links to the file alone. The file name is deterministic: the
target document's fully qualified name with :: replaced by - and any
byte outside ASCII letters, digits and _ escaped as .XX (uppercase hex),
plus .md; where two names would meet in one file (see
Multi-document sets) the link carries the
tagged name the set writes. Render the whole set with -render-documents
<dir> so the links resolve on disk. Rendering a single document that
references another still succeeds — the link points at the file name the set
gives the unrendered target, and it dangles until that document is rendered
into the same directory. An unknown target is a typed planning error, and a target usage
typed by more than one document definition is an ambiguous-target error;
both carry the reference's source location.
Styled query text (column runs)¶
A query-backed paragraph or list may nest column runs — SpanColumn and
LinkColumn parts — that map projected columns to styled runs. Each result
row renders one run per column run, in declaration order:
part styledSummary : Paragraph {
calc names : StyledHeavyNames {
in root = telescope;
in threshold = "10";
}
part styledName : SpanColumn {
attribute redefines column = "name";
attribute redefines styleColumn = "style";
}
part linkedName : LinkColumn {
attribute redefines column = "name";
attribute redefines targetColumn = "url";
}
}
**mount** [mount](<https://example.com/parts#mount>) **segmentControl** [segmentControl](<https://example.com/parts#segmentControl>)
SpanColumn carries a required column naming the projected column its
text comes from, plus at most one of:
style— a fixed"plain"(the default),"emphasis","strong","code"or"math"applied to every row;styleColumn— a projected column supplying each row's style. Each row must supply exactly one string value among the five styles; anything else is a typed evaluation error naming the query, column and row.
A "math" column run reads each row's value as LaTeX, so a query can typeset
formulas stored on the model — a latex attribute of each relation, say. A
row whose math value is blank or absent is a typed evaluation error naming the
query, column and row, since an empty formula has nothing to typeset.
LinkColumn carries a required column for the link text and a required
targetColumn naming a projected column that supplies each row's one
non-empty link destination.
Computed columns (Column with expression, cell, or path) feed column runs like any
projected property, so a query can compute both the text and the style or
target it renders with — the styleColumn/targetColumn example above uses
computed style and url columns. A RelatedColumn(...) cell is
multi-valued — a table renders its elements comma-separated, each an element
value with its data-element link in HTML, as any multi-valued projection is
rendered — and its count or any form is a scalar a table can group by
(Traceability matrix).
Column names are checked against the query's statically-known projection at
planning time; a projection only known at evaluation (e.g. a parameter-driven
properties) is checked when the query runs. Column runs alongside static
text or inline runs, on a paragraph without a query, or anywhere other than a
query-backed paragraph or list, are typed planning errors.
Tables¶
A Table requires a query and may carry a caption (rendered in emphasis
above the table):
part masses : Table {
attribute redefines caption = "All subsystems by mass";
calc rows : SubsystemTable {
in root = telescope;
}
}
*All subsystems by mass*
| name | mass |
| --- | --- |
| mount | 15 |
| optics | 8.5 |
| segmentControl | 20 |
The header row is the query's projected column names; a query that projects
no columns gets a single element column holding each element's qualified
name. An empty result still renders the header and delimiter rows, so the
document shows that the table is empty rather than omitting it. Cell values
render faithfully: strings unquoted, integers in base 10, reals in shortest
notation, booleans as true/false, unbounded multiplicity as *, elements
— a general, an enumeration literal — by effective name, the one the name
property reads (HTML links each and carries its qualified name in
data-element), and quantities as the magnitude followed by the unit the
model spelt in brackets — 2290000 [kg], escaped in Markdown as
2290000 \[kg\] so the brackets read as text. An attribute whose value is
not a constant (mass = dryMass + propellantMass) is a typed error naming
the query, the property and the row, not an empty cell; see the query
cookbook.
Grouped tables¶
A groupBy attribute names one projected column; rows partition into one
subtable per distinct value of that column, in order of first appearance,
with the group key above each in strong emphasis:
part zones : Table {
attribute redefines caption = "Subsystems grouped by zone";
attribute redefines groupBy = "zone";
calc rows : ZonedSubsystems {
in root = telescope;
}
}
*Subsystems grouped by zone*
**zone: support**
| zone | name | mass |
| --- | --- | --- |
| support | mount | 15 |
**zone: payload**
| zone | name | mass |
| --- | --- | --- |
| payload | optics | 8.5 |
| payload | segmentControl | 20 |
The group column must be one the query statically projects — an unknown name is a typed error at planning time, not an empty rendering. Row order within each group is the query's order.
Column widths¶
A columnWidths attribute states the relative widths of the projected columns,
in order, in whatever units the source used — a migrated Cameo table keeps its
pixel widths — with 0 for a column sized automatically; an entry for every
column is not required, the rest are automatic. Renderers honour them
proportionally: HTML writes one <col> per column with its share of the table
width, PDF sizes the columns by them, and Markdown, which has no column widths,
ignores them (Wide tables).
part masses : Table {
attribute redefines caption = "Masses";
attribute redefines columnWidths = (200, 0, 80);
calc rows : MassTable {
in root = telescope;
}
}
Column labels¶
A columnLabels attribute states the headings of the projected columns, in
order, where they differ from the column names: a column's name is the unique
key the query, groupBy and the HTML data-column attribute know it by, while
its label is what the reader sees over it, so two columns may be headed alike
(Key and Key) though named apart (Key and Key 2). "" heads a column
by its name, and an entry for every column is not required. A migrated Cameo
table is headed as the tool headed it — Id, Text, classifier, the tag's
name over a stereotype-tag column — over the query properties it reads
(shortName, documentation, general). Every backend prints the label;
HTML keeps the name on the cell and heading as data-column.
part key : Table {
attribute redefines caption = "Key Requirements";
attribute redefines columnLabels = ("Id", "", "Text");
calc rows : KeyRequirements;
}
Lists¶
A List requires a query and renders each result value as one item. style
is "bullet" (the default) or "number"; anything else is a typed planning
error. Nested column runs restyle each item's runs — see
Styled query text.
part heavyItems : List {
attribute redefines style = "number";
calc items : HeavySubsystemNames {
in root = telescope;
in threshold = "10";
}
}
An empty result renders as nothing — unlike a table, an empty list leaves no trace.
Definitions — query rows as prose¶
A Definitions block renders each query row as one prose entry: a term
followed by its description, both taken from projected columns the block
names. It is how model-authored text — a requirement's short name and its
doc body, a part's identifier and its description — reads as sentences
rather than as cells. term and description are required and name
projected columns of the bound query; either may be a property (shortName,
name, documentation, …) or a computed Column:
calc def Reqs :> Query {
in root : Element;
Project(
source = RelatedElements(
source = WhereType(source = Descendants(source = root, maxDepth = 1), type = "RequirementUsage"),
relationshipKind = "typing", direction = "outgoing", maxDepth = 1
),
properties = ("shortName", "name", "documentation")
)
}
part requirementText : Definitions {
attribute redefines term = "shortName";
attribute redefines description = "documentation";
calc rows : Reqs {
in root = spec;
}
}
**HLR-R001** — The mission shall safely return all three crew members to Earth.
**HLR-R002** — The mission shall achieve a soft landing on the lunar surface.
Markdown writes one paragraph per row: the term in strong emphasis, an em
dash, then the description. HTML writes a description list — <dl> with one
<dt>/<dd> group per row, each group carrying the row's element — so a
theme can style terms and descriptions apart; PDF follows the Markdown form.
A cell with several values (an element with two doc comments) joins them
with spaces, as a list item does. A row whose term is absent renders its
description alone and vice versa; a row with neither writes nothing in
Markdown and an empty group in HTML. An empty result follows a list's: it
leaves no trace in Markdown, while HTML keeps the empty <dl> — as it keeps
an empty <ul> — so the block stays addressable by name and query.
Both column names are checked against the query's statically-known
projection at planning time; a name the query does not project is a typed
error there, or at evaluation when the projection is only known then. A
Definitions block without a query, or one nesting other content, is a typed
planning error.
The complete model is examples/requirements.sysml
and its rendered output examples/requirements.md:
$ sysml docs/manual/examples/requirements.sysml -run-query "Requirements::Reqs root=Requirements::spec"
$ sysml docs/manual/examples/requirements.sysml -render-document Requirements::RequirementsReport
$ sysml docs/manual/examples/requirements.sysml -render-document Requirements::RequirementsReport -doc-form html
Mathematics¶
Formulas are written in LaTeX, inline or displayed. An inline formula is a
Span (or SpanColumn) with style = "math"; a displayed one is a
Formula block with the LaTeX in source and an optional caption:
part intro : Paragraph {
part lead : Span {
attribute redefines text = "The mirror's mass scales as";
}
part scaling : Span {
attribute redefines text = "m \\propto D^{2.5}_{\\text{eff}}";
attribute redefines style = "math";
}
part cost : Span {
attribute redefines text = "and each $ of budget buys about 1 cm^2 of aperture.";
}
}
part mirrorArea : Formula {
attribute redefines caption = "Collecting area of a circular mirror";
attribute redefines source = "A = \\pi \\left(\\frac{D}{2}\\right)^2 = \\frac{\\pi D^2}{4}";
}
renders as:
The mirror's mass scales as $m \propto D^{2.5}_{\text{eff}}$ and each \$ of budget buys about 1 cm^2 of aperture.
*Collecting area of a circular mirror*
$$
A = \pi \left(\frac{D}{2}\right)^2 = \frac{\pi D^2}{4}
$$
The LaTeX passes through untouched: math is the one run whose text is not
escaped as prose, so \frac, _{eff} and ^2 reach the typesetter as
written (SysML string literals still need their own backslashes doubled). In
return, ordinary prose is kept out of the math: a $ in a plain span renders
as \$, so "each $ of budget" can never open a formula, and a $ inside a
formula — \text{cost} = 10^6\,\$ — is escaped so it cannot close one.
Markdown carries the formulas as $…$ and $$…$$ blocks, which GitHub,
pandoc and most Markdown viewers typeset; HTML and PDF have their own
typesetters — see Outputs.
Formula.source is required and cannot be blank, and so is the text of a math
span: an empty formula is a typed planning error rather than an empty box. A
Formula is a content block like a Table or Diagram, not a paragraph: it
takes no query and nests no runs, its caption renders above it, and a named
one is a Ref target — ref redefines target = mirrorArea links to it, and
the link text defaults to its caption. Line breaks in source survive into
the display block (alignment environments keep their rows), while an inline
formula's are folded to spaces.
Diagrams¶
A Diagram embeds a rendering of a model element, drawn by the same view
engine that serves the editor's diagram panel. Its source names either a
declared view or a plain element:
view interconnectView {
expose imagingChain;
render asInterconnectionDiagram;
}
part imaging : Diagram {
attribute redefines caption = "Imaging chain interconnection";
ref redefines source = interconnectView;
}
part structure : Diagram {
attribute redefines caption = "Telescope part tree, left to right";
attribute redefines kind = "tree";
attribute redefines direction = "LR";
attribute redefines palette = "okabe-ito";
ref redefines source = telescope;
}
- A view source carries its own rendering kind from its
renderclause; stating akindon the diagram too is a conflict error. - A plain element source requires a
kind:"tree","interconnection","state","action","table"or"sequence". captionis optional and renders in emphasis above the diagram.direction—"TB","LR","RL"or"BT"— is accepted only by kinds drawn as directed graphs; it becomes the Mermaid flowchart direction or astateDiagram-v2directionstatement, the Graphvizrankdirwhen the document is rendered with DOT diagrams, or PlantUML'stop to bottom direction/left to right directionwith PlantUML ones. Stating one on a sequence diagram is a typed error.palette—"okabe-ito","tol-bright","tol-muted","tol-light","brewer-set2","brewer-dark2","viridis"or"cividis"— is accepted only by kinds that have a DOT or PlantUML form (tree, interconnection, state, action, sequence). When the document is rendered with DOT or PlantUML diagrams, the diagram's nodes are filled by keyword family from that colourblind-safe palette, apart defand itspartusages sharing a hue, with black text kept legible on every fill (the palettes); with Mermaid diagrams the palette is noted as not represented, and the HTML figure carries it asdata-paletteeither way. Any other name, or a palette on a table diagram, is a typed error.
A diagram block states what is drawn, not the notation it is written in:
that is a choice made when the document is rendered. By default most kinds
render as a fenced ```mermaid block:
*Imaging chain interconnection*
```mermaid
---
config:
flowchart:
subGraphTitleMargin:
bottom: 24
---
%% Observatory::interconnectView — interconnection rendering (render asInterconnectionDiagram)
flowchart LR
subgraph n0 ["imagingChain<br>«part»"]
direction LR
n1["camera : Camera<br>«part»"]
n2["recorder : Recorder<br>«part»"]
end
n1 ---|"link"| n2
Rendered with `-diagram-form dot` (`%render-document <name> dot` in the REPL,
`diagramForm: "dot"` over the LSP), every graph-shaped diagram of the
document — a `tree`, `interconnection`, `state` or `action` rendering — is a
fenced ` ```dot ` block of Graphviz DOT instead, for a toolchain that lays
diagrams out with Graphviz. No Graphviz installation is needed to write it:
```markdown
*Imaging chain interconnection*
```dot
// view: Observatory::interconnectView
// kind: interconnection
// stated: render asInterconnectionDiagram
// layout: dot
digraph "Observatory::interconnectView" {
graph [rankdir=LR];
node [shape=box];
subgraph "cluster_n0" {
label=<<b>imagingChain</b><br/><font point-size="10">«part»</font>>;
"n0" [shape=point, style=invis, width=0, height=0, label=""];
"n1" [label=<<b>camera : Camera</b><br/><font point-size="10">«part»</font>>];
"n2" [label=<<b>recorder : Recorder</b><br/><font point-size="10">«part»</font>>];
}
"n1" -> "n2" [label="link", arrowhead=none];
}
A view that states where its elements go (`DiagramLayout` annotations, see the
[CLI reference](../reference/cli.md#rendering-a-view)) is written with those
positions pinned and its header naming `neato`, so Graphviz draws it as laid
out. The HTML backend embeds the source in `<pre class="dot">`, and the PDF
backend keeps it as source under a notice rather than drawing it. A `sequence` kind
has no DOT form, so a document holding one cannot be rendered with DOT
diagrams; the failure is a typed error naming the block.
Rendered with `-diagram-form plantuml` (`%render-document <name> plantuml`,
`diagramForm: "plantuml"`), every diagram — the `sequence` kind included, which
PlantUML has a grammar for — is a fenced ` ```plantuml ` block in the OMG Pilot
visualizer's B&W style, its style inline so the file stands alone. No Java or
PlantUML jar is needed to write it:
```markdown
*Imaging chain interconnection*
```plantuml
@startuml
' Observatory::interconnectView — interconnection rendering (render asInterconnectionDiagram)
<style>
…
</style>
skinparam wrapWidth 300
hide stereotype
rectangle "**imagingChain**\n<size:10>//«part»//</size>" as n0 <<part>> <<usage>> {
rectangle "**camera : Camera**\n<size:10>//«part»//</size>" as n1 <<part>> <<usage>>
rectangle "**recorder : Recorder**\n<size:10>//«part»//</size>" as n2 <<part>> <<usage>>
}
n1 -[thickness=3]- n2 : link
@enduml
The HTML backend embeds it in `<pre class="plantuml">` and the PDF backend keeps
it as source under a notice. PlantUML pins no positions, so a view's
`DiagramLayout` geometry rides along as `'` comments; DOT is the form that
honours it ([the PlantUML form](../project/view-rendering-forms.md#plantuml)).
The `table` kind is the exception — it renders as a pipe table of the
element's structure (Element / Kind / Type / Declared in) rather than a
Mermaid, DOT or PlantUML block, whichever diagram form the document is rendered
with.
## Images
An `Image` shows an image file under an optional caption:
```sysml
part plate : Image {
attribute redefines location = "images/mark.png";
attribute redefines caption = "Plate 1: the survey mark";
attribute redefines alt = "a brass survey mark";
}
location is required and cannot be blank: it is a path relative to the
document's source file — resolved the same way a relative link or stylesheet
is, against the output file's directory — or an http(s) or file URL. The
block above renders as:
Markdown writes the CommonMark image of the location under its caption; HTML a
<figure class="sysml-image"> whose <img> carries the location verbatim,
with alt the declared text alternative (the caption when none is declared)
and the caption its <figcaption>; a PDF draws the file — a missing local
location is a typed missing-image error naming the block, while an
http(s) location is left for the engine to fetch. An Image is a content
block like a Table or Formula: it takes no query, nests nothing, and a
named one is a Ref target.
Binding queries to blocks¶
Every query-carrying block uses the same form:
Bindings are validated against the query's compiled signature at planning
time: an unknown parameter, a duplicate, a missing one without a usable
default, or a type or multiplicity mismatch is a typed error before anything
runs. A binding's value is an element name (telescope, or
telescope.optics.mirror for a nested usage, in dot notation) or a literal;
the engine does not evaluate arbitrary default expressions.
Escaping — write content freely¶
Model text cannot corrupt document structure. The renderer backslash-escapes
Markdown metacharacters (|, *, _, #, backticks, backslashes,
brackets, HTML-sensitive characters), folds newlines to spaces in prose and
<br> in table cells, and escapes a leading quote, bullet or list marker.
A part named baffle|shroud *tricky* renders as exactly that text in a
table cell, not as a broken row.