Outputs¶
Markdown¶
Markdown is the primary output form and the default:
$ sysml report.sysml -render-document Observatory::MassReport # stdout
$ sysml report.sysml -render-document Observatory::MassReport -o report.md
What the renderer emits:
- The document title as a level-1 ATX heading; each section one level deeper, saturating at level 6.
- Paragraphs as prose, with inline runs styled (
*emphasis*,**strong**,`code`, and links as[text]followed by the angle-bracketed URL in parentheses). - A stable
<a id="..."></a>anchor before every block that aReftargets; the reference renders as a link to it. - Tables as GitHub-flavored pipe tables; grouped tables as one subtable per
group key; a nested row (one a
Treequery placed under another) opens its first cell with↳after a non-breaking indent per level above the first, the cell's own text unchanged (see hierarchical rows). - Lists as
-bullets or1.numbered items. - Diagrams as fenced
```mermaidblocks (```dotblocks of Graphviz DOT when rendered with-diagram-form dot,```plantumlblocks with-diagram-form plantuml, or a pipe table for thetablekind whichever form), with captions in emphasis. - Images as a CommonMark image of the location under the caption, the location verbatim.
- An object the session holds as its path from the object the query was bound
through (
car.wheels[2]), and a verdict as<assertion> on <path>: <verdict>(assert constraint powerLow on car.engine: violated). - All model-derived text escaped so it cannot break document structure.
- A single trailing newline, no trailing whitespace.
The output is deterministic: the same model — and, for a document that reads the objects a session holds, the same objects in the same state — produces byte-identical Markdown on every run, which is why the repository can keep rendered documents as golden files (this manual does exactly that — see the worked example).
Multi-document sets¶
-render-documents <dir> renders every document definition the model
declares into the directory, one Markdown file per document:
File names are deterministic: the document's fully qualified name with ::
replaced by -, any byte outside ASCII letters, digits and _ escaped as
.XX (uppercase hex), plus .md, so documents sharing a short name in
different packages get distinct files. Two names whose files would meet on a
case-insensitive file system, a stem that would name a Windows device, and a
name too long for a file system are written under a tagged name carrying a
~ and a hash of the whole name, as -render-all writes its views.
Cross-document references (see the authoring chapter)
therefore resolve as relative links between the written files, tagged names
included, and repeated runs write identical bytes. Rendering a
cross-referencing document on its own still succeeds; its external links
point at the file names the set gives the targets and dangle until those
documents are rendered into the same directory.
Each document in the set is compiled and evaluated on its own. One that
cannot be rendered — a query column with no value for a row, a diagram past
its form's limit — does not stop the others: they are written, a page carrying
the document's title, This document could not be rendered. and the error
is written in its place so links to it resolve — a link into one of its blocks
lands on a line naming that block, under the anchor the link expects — each
failure is reported on stderr as document <qualified name> could not be
rendered: <reason>, and the run exits with status 3 rather than 0.
HTML¶
-doc-form html renders the same document tree as HTML. Nothing is
converted from the Markdown: the renderer reads the compiled document tree,
so the model facts Markdown cannot carry reach the markup.
The structure is ordinary semantic HTML — <article>, nested <section>
whose heading levels follow the nesting, <p>, <table> with <caption>,
<thead> and <th scope="col">, <ul>/<ol>, <figure> with
<figcaption>, <dl> for definitions, <nav> for the contents, and <em>, <strong>, <code>,
<a> inline — so a reader, a screen reader and a static-site generator all
see a document rather than a grid of <div>s.
The model rides alongside the structure. A small sysml- class vocabulary
names each part of the document (sysml-document, sysml-section,
sysml-table, sysml-row, sysml-cell, sysml-value, sysml-list,
sysml-item, sysml-definitions, sysml-entry, sysml-term,
sysml-description, sysml-diagram, sysml-image, sysml-caption, sysml-link,
sysml-ref and their kin), and data- attributes carry the facts behind
it: the content kind and name, the query behind a table, list or
definitions block, the group-by column, each row's, item's or entry's
selected element with its element kind
(partUsage, requirementDef, …), each cell's projected column and value
kind (a quantity value also carries its data-magnitude and data-unit
apart), and a diagram's view, kind and flow direction.
<tr class="sysml-row" data-element="Observatory::telescope::mount"
data-element-kind="partUsage">
<td class="sysml-cell" data-column="mass" data-value-kind="real">
<span class="sysml-value" data-value-kind="real">15</span></td>
</tr>
A column heading prints the column's label (Column labels)
and carries the column's name, the key its cells are marked with, as
data-column, so a theme or script keys on the name whatever the heading
reads. A table whose columnWidths the model states carries the class
sysml-table-sized and a <colgroup> of one <col> per column, the stated
width as data-width and its share of the table as style="width: 52.7%" (an
automatic column takes the mean stated width), so the default stylesheet lays
the table out fixed in that proportion and a theme can read the source widths;
the PDF backend gives an unsized wide table the same group, its columns
sharing the width evenly and none narrower than its heading needs.
A nested row states its depth as data-depth and as the --sysml-depth
property its first cell's span.sysml-indent is widened by
(hierarchical rows).
A row over an object the session holds (Objects the session holds)
adds data-object="#<id>", the id the instantiation report printed, beside
the usage the object stands for, and an object-valued cell is a
span.sysml-object whose text is the object's path. A row a Verdicts query
answered (Which constraints and requirements hold)
is the assertion checked, and its row or list item carries data-verdict
(holds, violated or undecided), data-path (the object checked) and,
over a held object, its data-object; a verdict-valued cell is a
span.sysml-verdict with the same three attributes, whose text is the line
Markdown prints, <assertion> on <path>: <verdict>. So a stylesheet colours
what is violated with
[data-verdict="violated"] { … } and a script reads which objects it is
about without parsing the text.
<tr class="sysml-row" data-object="#2" data-verdict="violated" data-path="car.engine"
data-element="Garage::Engine::powerLow" data-element-kind="constraintUsage">
<td class="sysml-cell" data-column="path" data-value-kind="string">
<span class="sysml-value" data-value-kind="string">car.engine</span></td>
<td class="sysml-cell" data-column="verdict" data-value-kind="string">
<span class="sysml-value" data-value-kind="string">violated</span></td>
</tr>
Identifiers are the same anchors the Markdown writes, so a Ref resolves
within the page; in a -render-documents set it resolves across pages, whose
file names are the Markdown names with .html instead of .md. Diagram
blocks embed their Mermaid source in <pre class="mermaid">, which a page
that loads Mermaid renders as a diagram and any other page shows as source.
By default the output loads nothing over the network, runs no JavaScript of
its own, and is byte-identical between runs. To have a browser draw the
diagrams, -html-mermaid cdn adds a <script> loading a pinned Mermaid
release from jsDelivr, and -html-mermaid <url> loads it from a URL of your
own; a second <script> configures it to draw the page's largest chart, which
Mermaid's default size limits (50 000 characters, 500 edges) would refuse. The
configuration never exceeds twenty times those defaults, so no page asks a
browser for unbounded work: a chart past 1 000 000 characters or 10 000 edges
is refused before it is written, naming the chart and its size, and is drawn
with -diagram-form dot or plantuml instead. The page keeps the source, so it
still reads where the script cannot load. A fragment has no page shell for the
script, so a page embedding one loads Mermaid itself. Rendered with
-diagram-form dot or -diagram-form plantuml, every graph-shaped diagram
embeds its Graphviz DOT source in
<pre class="dot"> or its PlantUML source in <pre class="plantuml"> instead;
the page never draws it, and -html-mermaid leaves it alone.
An Image block is a <figure class="sysml-image"> whose <img> carries its
location verbatim — a relative path stays relative, so a page reads an
image beside it, as a relative link does — and its alt the text
alternative, the caption its <figcaption>.
Formulas work the same way. A math span becomes <span class="sysml-math">
and a Formula block a <figure class="sysml-formula"> with its caption,
each holding the LaTeX between MathJax's \(…\) or \[…\] delimiters.
By default the page loads nothing and shows the source; -html-math cdn
adds a <script> loading a pinned MathJax release from jsDelivr, and
-html-math <url> loads it from a URL of your own. The script is configured
to typeset .sysml-math elements alone, so a $ or \( in ordinary prose
is never mistaken for a formula. -html-math and -html-mermaid combine, and
neither combines with -html-fragment — the embedding page loads the
typesetter itself. See Mathematics for the PDF side.
Styling it¶
The default stylesheet is inlined in a standalone page and declared in a cascade layer:
Your own CSS is unlayered, so it wins on cascade origin rather than
specificity — overriding a default needs neither !important nor a matching
selector. Every default value comes from a --sysml-* custom property on
.sysml-document, so retheming can be a handful of properties, and the
renderer emits no style attributes to compete with.
| Flag | Effect |
|---|---|
-html-theme <name> |
Layer a bundled theme over the default sheet: default, acm, ieee, modern, nasa, print or report |
-html-default-css |
Write the default sheet and exit, to copy from; with -html-theme, the theme's whole sheet |
-html-css <file\|url> |
Add a sheet after the default one: a file is inlined, a URL is linked (repeatable, applied in order) |
-html-no-default-css |
Leave the default sheet out |
-html-fragment |
Write the <article> alone, with no page shell and no stylesheet |
The bundled themes are written against the same tokens, inside the same layer, right after the default sheet — so a theme changes the look while your unlayered CSS still wins over both:
| Theme | Look |
|---|---|
default |
The default sheet alone: system sans-serif, one navy accent, boxed tables |
modern |
Clean corporate sans-serif: filled table headers, zebra rows, rounded surfaces for code and contents |
report |
Formal technical report: serif body, wider measure, open tables ruled top and bottom, captions above |
print |
Monochrome and compact for paper: black rules, no fills, tables and figures kept whole across page breaks, external links spelled out |
nasa |
NASA STI report series: Times 12pt body, Arial headings, tables and captions, letter page with 1in margins, page numbers centred below, black on white |
ieee |
IEEE Transactions manuscript: Times 10pt body, 8pt captions and tables, centred small-caps section heads, italic subheads, justified with a 1pc indent, letter page with 0.67in margins, single column |
acm |
ACM article (acmart): Libertine 10pt body falling back to Times, bold sans numbered heads, 9pt captions, letter page with 1in margins, single column |
The three convention themes set their faces and point sizes on screen as on
paper, so a page and its PDF agree. The Libertine fonts acm names are
rarely installed, so Times metrics are what most machines print; and none of
the three lays out two columns or writes a cover beyond the title, since the
document model carries no report number, authors or affiliations. The
sources and the choices made where a convention is silent are recorded in
the backend's design notes.
A theme needs the default sheet under it, so it is refused with
-html-no-default-css, and a fragment has no page to style, so it is refused
with -html-fragment.
A -render-documents -doc-form html set writes its stylesheets as files
beside the pages — sysml-document.css (default sheet, with the theme when
one is named) and each -html-css file, under its
own base name, escaped and shortened where a name is not a portable file name
and distinguished where two sheets share one — and every page links them in
order, so the styling of a whole set is edited in one place. A -html-css URL
stays a link.
A title page, a table of contents and section numbering are deliverable options shared with PDF.
PDF¶
-doc-form pdf renders the same document tree to PDF. It requires -o
(PDFs are not written to stdout):
Internally the PDF backend reads the compiled document tree, renders any
Mermaid diagrams to SVG with Mermaid CLI (mmdc) — each under a
configuration sized to the chart, within the same ceiling the
HTML backend draws under, and the same five-minute limit every
converter runs under — typesets any formulas
with KaTeX (katex), and hands an external converter the document in the
form it reads: an HTML-to-PDF engine gets the HTML backend's page
with the drawn diagrams and typeset formulas in place of their source and a
print stylesheet over the default sheet; pandoc gets the
Markdown with a filter swapping the artwork in on its own
syntax tree. Rendered with -diagram-form dot, the diagrams are drawn by
Graphviz — the dot named by OPENSYSML_DOT, else the one on PATH, writing
SVG under the layout engine the block's // layout: header names, so a view
the model positions is drawn where its Layout annotations put it — and with
-diagram-form plantuml by the PlantUML jar named by OPENSYSML_PLANTUML_JAR,
run by the java named by OPENSYSML_JAVA or found on PATH. Both are
optional where Mermaid CLI is required: without the tool, the PDF keeps the
block's DOT or PlantUML source under a notice naming the variable to set, and
the render still succeeds. A tool that is present and fails stops the render
with a typed tool-failed error carrying its output, as a failing mmdc
does.
Styling a PDF¶
An engine reading HTML lays out the same markup the HTML form writes, so a PDF is styled the way an HTML page is. The print stylesheet — page size and margins, the page-number footer, print faces and sizes, page breaks kept out of tables and figures, the title page and contents on pages of their own — is declared in a second cascade layer after the default sheet:
@layer opensysml; /* the default sheet, or the theme over it */
@layer opensysml-print; /* the PDF backend's print sheet */
@layer opensysml-print-theme; /* the theme's print companion, when it has one */
A theme that means to govern paper carries a print companion — print,
report, nasa, ieee and acm do — that the PDF backend lays over the
print sheet in the third layer, so the theme's page size and margins, faces,
body size, heading scale and page-number footer reach the PDF rather than
being overwritten by the print sheet's defaults. All three layers draw their
values from the same --sysml-* tokens and write no style attributes, so
-html-theme rethemes a PDF as it does a page, -html-css sheets apply
unlayered after every layer and win on cascade origin, and
-html-no-default-css leaves every bundled layer out so that only your
sheets — @page rules included — style the PDF. Without a theme, the PDF is
set in Times, Arial and Courier where they are installed and in their
metric-compatible free equivalents (Liberation, Nimbus) where they are not. A sheet's relative url()
and @import references resolve against the PDF's directory, as a page's
resolve against the page's. The pandoc engine reads Markdown and writes its
own HTML, so -html-theme and -html-no-default-css are refused for it,
while -html-css sheets are attached in pandoc's page after its own.
-html-fragment, -html-mermaid and -html-math shape a browser page and
are refused for PDF.
Engines¶
-pdf-engine selects the converter (default weasyprint):
| Engine | Tool invoked | Notes |
|---|---|---|
weasyprint |
weasyprint, an HTML-to-PDF paged-media engine |
Default; open source |
pandoc |
pandoc reading the Markdown itself, with WeasyPrint as its PDF engine |
|
prince |
prince, a commercial HTML-to-PDF engine |
License required |
The converters are external tools, not bundled with the binary. If the
selected tool is not on PATH, the render fails with a typed tool-missing
error naming it. Environment variables override discovery:
OPENSYSML_WEASYPRINT, OPENSYSML_PANDOC, OPENSYSML_PRINCE,
OPENSYSML_MMDC, OPENSYSML_MMDC_PUPPETEER (extra Puppeteer
configuration for Mermaid CLI), OPENSYSML_KATEX and OPENSYSML_KATEX_CSS
(the KaTeX stylesheet, when it is not installed beside the katex command),
OPENSYSML_DOT (Graphviz), OPENSYSML_PLANTUML_JAR and OPENSYSML_JAVA
(PlantUML). The repository's scripts/download-doc-pdf-toolchain.sh fetches
a pinned WeasyPrint, pandoc, Mermaid CLI, KaTeX, Graphviz and PlantUML jar and
prints the exports to use them; the jar still needs a Java runtime of your
own.
Deliverable options¶
| Flag | Effect |
|---|---|
-doc-title-page |
A separate title page before the content |
-doc-toc |
A table of contents built from the section headings |
-doc-number-sections |
Hierarchical section numbers (1, 1.1, ...) |
All three are off by default and shape HTML and PDF alike; -pdf-title-page,
-pdf-toc and -pdf-number-sections are aliases of them.
Wide tables¶
The PDF stylesheet keeps every table within the text width. A cell wraps at
word boundaries and breaks a token only when it fits no line on its own, so a
long qualified name breaks rather than pushing the rightmost columns off the
page; the header row repeats at the top of every page a table continues onto;
a row never splits across pages, though a long table does. A table of seven or
more columns — a traceability matrix, an instance table of many value
properties — is placed on landscape pages in smaller type (9pt; 8pt from
eleven columns), together with the heading and caption that introduce it,
while the surrounding pages stay portrait, and its first column, the one
naming each row, keeps a readable minimum width however many columns follow.
A table whose columnWidths the model states
(Column widths) is sized in that proportion
instead, though no column narrower than its heading's longest word needs, the
others giving up the difference in proportion. Wider still, the column set is
split rather than squeezed. The backend measures each table's headings against
the line its column count has — 140 characters of the dense type across a
landscape page, four fifths of that in the wide type, half of it in the
ordinary type on a portrait page — the first column counting its least share
of 18 % and every other column the longest word of its heading plus its cell
padding, so no heading breaks inside a word. Headings that overrun their own
line but fit the dense landscape line set the table on that line (HTML class
sysml-table-wide: landscape, in the dense type, whatever a theme sets
ordinary cells at) however few columns it holds; headings that overrun even
that line, or more than twelve columns, are written as continuation tables on
it, each repeating the first column ahead of as many of the following columns
as head unbroken beside it (at most eleven), under the table's caption with
"(continued)" appended and no number of its own; a heading longer than the
line itself still sets beside the repeated column, the two sharing the line in
proportion to their needs and the heading breaking. A table of few
wide-headed columns therefore goes landscape or splits sooner than one of many
short-headed ones, and a table that fits its own line is never moved or split. The weasyprint and prince
engines split the HTML table; the pandoc engine gets the same split in the
Markdown it converts, so every engine sets the same column sets, though a
Markdown table carries no class, so pandoc sets a part of fewer than seven
columns on a portrait page in the ordinary type. The landscape and first-column rules use the CSS :has() selector,
which WeasyPrint — and so weasyprint and pandoc — supports; an engine
without it keeps the whole document portrait.
Hierarchical rows¶
A query that arranges its rows as a tree (Nesting rows)
gives each row a depth, and every backend shows the nesting structurally,
leaving the cell's own text as the model states it: Markdown opens a nested
row's first cell with ↳, indented one step per level beyond the first with
non-breaking spaces; HTML states the depth on the row as data-depth and
widens an empty span.sysml-indent ahead of the first cell's values by
--sysml-depth steps of --sysml-row-indent; PDF inherits the HTML rule. A
flat query has every row at depth 0 and renders as before.
PDF rendering of inline runs and anchors¶
Inline runs and cross-reference anchors keep their meaning in PDF. A
paragraph built from Span/Link/Ref runs renders with emphasis, strong
and code styling and working links; a Ref anchor becomes an invisible
PDF-native anchor, so an in-document Ref is a clickable internal link; and
a grouped table's group key heads each group. All three engines support
internal links: weasyprint and prince from the HTML's element ids and
fragment hrefs, and pandoc from the Markdown itself, whose CommonMark
reader keeps the anchor's raw HTML. Captions are <caption> and
<figcaption> elements in the HTML the first two read; the pandoc engine
tells a caption from an emphasized paragraph by matching the paragraph ahead
of each table, diagram and formula block against the document's captions in
order, and styles it small.
Mathematics¶
Formulas are typeset in the PDF, not printed as LaTeX. The backend lists the
document's distinct formulas — math spans in paragraphs, list items and
definitions, and Formula blocks — and runs each once through the KaTeX
command line (katex, found on PATH or named by OPENSYSML_KATEX), which
typesets it as HTML that needs no JavaScript. KaTeX's stylesheet and fonts
are copied beside the page, so the finished PDF embeds the KaTeX faces and
shows the formula as a formula:
weasyprintandprincelay out the typeset HTML in the HTML backend's own places, an inline formula in its<span class="sysml-math">and a displayed one in the<div class="sysml-math">of its<figure class="sysml-formula">, centered under its caption;pandocreads the Markdown, and its filter replaces each formula pandoc parses with the typeset HTML as a raw block or span, linking the same stylesheet.
A document without formulas needs no KaTeX, as one without diagrams needs no
Mermaid. An escaped \$ in prose stays a dollar sign, and a $ inside a
fenced code or diagram block is never read as math. A missing katex stops
the run with the usual tool-missing message naming OPENSYSML_KATEX; a
missing stylesheet names OPENSYSML_KATEX_CSS; and LaTeX KaTeX cannot parse
fails the run with KaTeX's own message quoting the formula, rather than a PDF
with a hole in it.
Determinism¶
Markdown is fully deterministic: byte-identical output for the same model, the same held objects and binary. Query results preserve declaration order unless ordered explicitly, ordering policies are explicit parameters, and rendering introduces no timestamps, random identifiers or map-order dependence.
PDF is deterministic for a pinned toolchain. The engine does its
part (it invokes converters with SOURCE_DATE_EPOCH=0 so they embed a fixed
creation date), but the bytes also depend on the converter version and the
fonts installed on the machine. Two runs on the same machine with the same
toolchain produce identical PDFs; two machines with different WeasyPrint
versions or fonts generally do not. For reproducible PDFs, pin the toolchain
(the download script above is how CI does it).