Worum es geht
Der Quelltext einer Vorlage ist FreeMarker. FreeMarker ist eine Vorlagensprache für Java: normaler Text mit Platzhaltern wie ${title} und Anweisungen wie <#if> und <#list>. CRMS führt sie in einer Sandbox aus. Das ist eine abgeschottete Umgebung, in der eine Vorlage nur die Daten sieht, die du ihr gibst, und sonst nichts erreicht.
Was die Vorlage schreibt
Was eine Vorlage ausgibt, hängt von ihrer Ausgabeart ab. Danach richtet sich auch das automatische Escaping.
| Ausgabeart | Die Vorlage schreibt | Escaping | Formate |
|---|---|---|---|
DOCUMENT | XHTML, also HTML als sauberes XML | XHTML | HTML, PDF |
WORD | das ganze Word-Paket als ein XML-Dokument (Flat OPC) | XML | DOCX |
TABLE | das CRMS-Tabellendokument (<workbook>, <sheet>, <row>, <cell>) | XML | XLSX, CSV, ODS |
Escaping heißt: Zeichen wie < und & in einem Wert werden so umgeschrieben, dass sie als Text erscheinen und kein Markup werden. Steht in der Payload <script>alert(1)</script>, schreibt <p>${title}</p> daraus <p><script>alert(1)</script></p>. Die Einzelheiten zu jedem Format stehen unter Ausgabearten und Formate.
Auf die Payload zugreifen
Die Payload ist die oberste Ebene der Vorlage. Ihre Felder sind direkt da.
<#-- Payload: { "title": "Audit Halle 3", "customer": { "name": "ACME" },
"items": [ { "label": "HACCP", "amount": 480 } ] } -->
<h1>${title}</h1>
<p>Kunde: ${customer.name}</p>
<table>
<#list items as item>
<tr><td>${item.label}</td><td>${item.amount}</td></tr>
</#list>
</table>
<p>Prüfer: ${(inspector)!"–"}</p>
<#if remarks?has_content><p>${remarks}</p></#if>
${…}gibt einen Wert aus, escaped.<#list>läuft über eine Liste,<#if>prüft eine Bedingung.!setzt einen Ersatzwert, wenn ein Feld fehlt.??fragt, ob es da ist.<#assign>,<#macro>und<#function>bauen Hilfen innerhalb desselben Quelltexts. Die Hub-Vorlagen machen davon reichlich Gebrauch, etwa für Überschriften und das Inhaltsverzeichnis.
Die Payload hat höchstens zwei Ebenen: title, customer.name, items[].amount. Tiefer geht es nicht. Was CRMS an der Payload prüft, steht unter Die Payload-Prüfung.
Zahlen, Daten, Wahrheitswerte
CRMS setzt für jede Ausgabe die Sprache und die Zeitzone des Dokuments. Danach formatiert FreeMarker:
| Ausdruck | Wert | Ausgabe bei de |
|---|---|---|
${amount} | 1234.5 | 1.234,5 |
${amount?string("0.00")} | 1234.5 | 1234,50 |
${due?string("dd.MM.yyyy")} | "2026-10-17", als DATE deklariert | 17.10.2026 |
${done} | true | true |
Ein Feld, das die Revision als DATE oder DATETIME deklariert, wird zu einem echten Datum, auch wenn es als ISO-Text kommt. So kommt es aus JSON. Ein DATE ist Mitternacht in der Zeitzone des Dokuments, nie in der des Servers. Ohne Deklaration bleibt ein ISO-Text ein Text. Woher Sprache und Zeitzone kommen, steht unter Sprache und Zeitzone.
Assets
Ein Asset der Revision nennst du mit asset:<name>. Was CRMS daraus macht, hängt vom Format ab:
| Format | Wo asset: steht | Was passiert |
|---|---|---|
| HTML | src, href, CSS url() | wird zu einer data:-Adresse, die Seite ist danach in sich vollständig |
Bilder, Schriften (@font-face { src: url('asset:own.ttf') }), Stylesheets | wird aus dem Speicher geladen. Jede andere Adresse wird abgewiesen | |
| DOCX | <pkg:binaryData>asset:logo.png</pkg:binaryData> | wird mit dem Inhalt des Assets gefüllt |
<img src="asset:logo.png" alt="Logo"/>
Nennt die Vorlage ein Asset, das die Revision nicht hat, schlägt das Rendern fehl, bei HTML mit render-asset-unknown.
Bilder, die zur Anfrage gehören, etwa eine Unterschrift oder Fotos einer Begehung, sind keine Assets. Die Vorlage nennt sie attachment:<name>. Mehr unter Bilder und SVG.
SVG aus der Payload: crms.svg
Eine Grafik, die der Aufrufer selbst zeichnet, zum Beispiel ein Diagramm, kommt als SVG-Text in der Payload. Ausgeben würde sie das Escaping nur als Text. Dafür gibt es die Funktion crms.svg:
<div class="figure">${crms.svg(diagramSvg)}</div>
CRMS prüft das SVG gegen eine Liste erlaubter Elemente und Attribute und setzt es dann ein. crms gehört CRMS: Ein Payload-Feld namens crms ersetzt die Funktion nicht.
| Ausgabeart | crms.svg schreibt |
|---|---|
DOCUMENT | das SVG direkt ins XHTML. HTML zeigt es, PDF zeichnet es als Vektorgrafik |
WORD | einen Platzhalter innerhalb von <w:p>. Daraus wird ein eingebettetes Bild |
TABLE | nichts: 422 template-svg-not-for-output-type|table |
Was erlaubt ist und was abgewiesen wird, steht unter Bilder und SVG.
Was gesperrt ist
${…},<#if>,<#list>,<#assign><#macro>,<#function>im selben Quelltext- Built-ins wie
?string,?has_content,?size,?filter - Ersatzwerte mit
!, Abfragen mit?? crms.svg(…)
?new: keine Klasse lässt sich erzeugen?api: keine Methode hinter einem Wert<#include>,<#import>: es gibt keine andere Datei und keine andere Vorlage- Methodenaufrufe an Objekten: die Payload enthält nur Text, Zahlen, Wahrheitswerte, Daten, Listen und Maps
Dazu kommen Grenzen beim Rendern: Die Ausgabe hat eine Höchstgröße, und eine Vorlage, die nicht endet, wird nach der Zeitgrenze abgebrochen. Siehe Ausführung und Grenzen.
Fehler in der Vorlage
Fehler kommen als Schlüssel mit Zeile und Spalte im Quelltext. Den Text von FreeMarker bekommt der Client nicht.
| Schlüssel | Wann |
|---|---|
template-syntax-error|<zeile>|<spalte> | der Quelltext lässt sich nicht parsen |
template-render-failed|<zeile>|<spalte> | beim Rendern ging etwas schief, etwa ein fehlender Wert oder ein gesperrtes ?new |
render-output-too-large | das Dokument wurde größer als erlaubt |
Einen Syntaxfehler findet CRMS schon beim Einreichen. Der Renderer parst den Entwurf, und eine Revision, die sich nicht parsen lässt, entsteht gar nicht: 422 template-source-rejected mit violations: [{"field": "source", "rule": "template-syntax-error|1|7"}].