Processing Instructions
Processing instructions provide document-level configuration, overriding application configuration for a specific document.
The instruction is named paperwork. The engine also accepts the same instruction named scryber (<?scryber ... ?>), with the same attributes, so existing templates continue to work.
Syntax
<?paperwork parser-mode='Strict' parser-log='false' append-log='false'
log-level='Warnings' controller='MyNamespace.MyController, MyAssembly'
parser-culture='en-GB' ?>
Supported Attributes
| Attribute | Type | Values | Description |
|---|---|---|---|
parser-mode |
enum | Strict, Lax |
Strict throws on invalid elements; Lax ignores them. Default is Lax. See Binding, Expression and Template Errors for how expression errors are handled |
parser-log |
bool | true, false |
Enable parser trace output |
append-log |
bool | true, false |
Append trace log to PDF document |
log-level |
enum | Off, Errors, Warnings, Messages, Verbose, Diagnostic |
Minimum log level |
parser-culture |
string | Culture code | e.g., en-GB, fr-FR - affects number/date parsing |
controller |
string | Assembly-qualified type name | Document controller class |
Examples
Basic Parser Configuration
<?paperwork parser-mode='Lax' log-level='Warnings' ?>
With Controller
<?paperwork parser-mode='Strict'
controller='MyCompany.Reports.SalesController, MyCompany.Reports' ?>
Development Mode
<?paperwork parser-mode='Strict'
parser-log='true'
append-log='true'
log-level='Verbose' ?>
Localized Document
<?paperwork parser-culture='fr-FR' ?>
Implementation Details
Processing Instruction Parsing
Parser implementation in XMLParser.ParseProcessingInstructions():
// Scryber.Generation/XMLParser.cs
protected void ParseProcessingInstructions(XmlReader reader, string name)
{
string value = reader.Value;
this.Settings.ReadProcessingInstructions(value);
LogAdd(reader, TraceLevel.Message,
"Parsed processing instructions. Controller = {5}, parser mode = {1}, logging = {2}, append-log = {3}, log-level = {4}",
value, this.Mode, this.Settings.LogParserOutput,
this.Settings.AppendLog, this.Settings.TraceLog.RecordLevel,
this.Settings.ControllerType == null ? "[NONE]" : this.Settings.ControllerType.ToString());
}
Use Cases
1. Production Document (Lax Mode)
<?paperwork parser-mode='Lax' log-level='Errors' ?>
<html xmlns='http://www.w3.org/1999/xhtml'>
<!-- Skips invalid elements, but logs everything. -->
</html>
2. Development Document (Strict Mode with Logging)
<?paperwork parser-mode='Strict'
parser-log='true'
append-log='true'
log-level='Messages' ?>
<html xmlns='http://www.w3.org/1999/xhtml'>
<!-- Throws exception on any invalid element, and logs document execution.-->
</html>
3. Controller-Based Document
<?paperwork controller='MyCompany.Reports.InvoiceController, MyCompany.Reports' ?>
<html xmlns='http://www.w3.org/1999/xhtml'>
<body on-init='InitInvoice' on-loaded='LoadData'>
<main>
<span id='CustomerLabel' />
</main>
</body>
</html>
4. Localized Document
<?paperwork parser-culture='de-DE' ?>
<html xmlns='http://www.w3.org/1999/xhtml'>
<!-- Numbers formatted as 1.234,56 in template content -->
<!-- Dates formatted as 31.12.2025 in template content -->
</html>
**NOTE:** Content bound to values within the document will be parsed under the current ThreadCulture, the parser culture is purely for fixed values within the template.
Parser Modes
Using Lax in production, and strict in development means that end users will not face errors that have been missed. And will at least receive the document they requested.
Strict Mode
- Throws exceptions on unrecognized elements or attributes
- Enforces valid structure according to component definitions
- Recommended for development and testing - catches errors early, before they reach a user
- Use when: Developing templates, running automated tests, or checking a template before release
<?paperwork parser-mode='Strict' ?>
Lax Mode
- Ignores unrecognized elements and attributes
- Logs warnings instead of throwing exceptions
- Continues parsing despite errors
- Recommended for production - the end user still receives the document they requested
- Use when: Running in production, gradual migration, forward compatibility
- Invalid syntax is still an error - a template with an invalid expression throws in either mode. Lax only relaxes unknown content and missing data. See Binding, Expression and Template Errors.
<?paperwork parser-mode='Lax' ?>
Binding, Expression and Template Errors
The parser mode also decides what happens when an expression, binding or template is wrong. Most outcomes are the same in both modes - the mode only matters where the table says so.
Since 9.7.5 - the outcomes below were made consistent. Rows marked 9.7.5 changed in that release. In addition, the engineβs
Document.CreateParserSettings()andCreateParserSettingsAsync()now default toLax(previouslyStrict), matchingDocument.ParseDocument()and the document render options. Aparser-modeprocessing instruction still overrides the default.
Parse is when the template is read (by the document builder, or Document.ParseDocument). Bind is when the data is bound and the document is generated (SaveAsync, BuildBytesAsync or BuildAsync). Throw means an exception is raised and no document is produced. Log error means one entry is written to the trace log at Error level, the value is left unset, and generation continues.
The guiding rule is that invalid syntax is invalid completely - it always throws, in strict or lax - while missing data is not a syntax error.
Invalid syntax
| Scenario | Example | Strict | Lax | Since |
|βββ-|βββ|βββ|ββ|ββ-|
| Expression cannot be compiled, in the main template (text or attribute) | | Throw on parse | Throw on parse | |
| Expression compiles, but is incomplete when evaluated |, | Throw on bind | Throw on bind | **9.7.5** (lax previously logged a warning) |
| Wrong number of function parameters | | Throw on bind | Throw on bind | 9.7.5 (lax previously logged a warning) |
| Invalid expression inside a repeating or conditional template | `` | Throw on bind | Throw on bind | 9.7.5 (lax previously logged an error and skipped the template) |
| Invalid XML in the main template | <p>Unclosed <b>bold</p> | Throw on parse | Throw on parse | |
| Invalid XML inside a repeating template | <p>Unclosed <b>bold</p> | Throw on parse | Throw on parse | |
Invalid XML inside a repeating template is raised on parse, not bind, because the content of the loop is read as part of the document.
Missing data in an expression
Using model.missing.value as the example.
| Scenario | Strict | Lax | Since |
|---|---|---|---|
There is no model (not set, or null) |
Log error | Log error | 9.7.5 (strict previously threw, lax logged a warning twice) |
model exists, but missing does not exist or is null |
Silent, value is null | Silent, value is null | 9.7.5 (strict previously threw, lax logged a warning) |
missing exists, but value does not exist or is null |
Silent, value is null | Silent, value is null | Β |
| Any other failure while evaluating (e.g. an invalid conversion) | Throw on bind | Log warning | Β |
An expression supplied as data to eval() is invalid |
Throw on bind | Log warning | Β |
Only the root of the path (model) not being set is reported. A null part way along a path, or at the end of it, is normal data and just results in null - so `` with no object outputs 1. .
An expression that is supplied as data to eval() is not part of the template, so it is treated as any other evaluation failure, even if it is not valid syntax.
CSS expressions
var() and calc() in style attributes and stylesheets follow the same rules.
| Scenario | Example | Strict | Lax | Since |
|---|---|---|---|---|
Invalid syntax in an inline style attribute |
style="color: var(model.value, red;" |
Throw on parse | Throw on parse | Β |
| Invalid syntax in a stylesheet | :root { --x: var(model.value, red; } |
Throw when the styles are parsed (as the document is initialized) | Throw when the styles are parsed | 9.7.5 (previously only found later, when bound or output) |
| Incomplete expression | var(model.missing.[, red), calc(1 + ) |
Throw on bind | Throw on bind | Β |
var() with a default, and there is no model, no missing or no value |
var(model.missing.value, red) |
Default is used, silent | Default is used, silent | 9.7.5 (a missing model or missing previously threw) |
var() without a default, and there is no model |
var(model.missing.value) |
Log error, property unset | Log error, property unset | 9.7.5 (previously threw) |
var() without a default, and there is no missing or no value |
var(model.missing.value) |
Silent, property unset | Silent, property unset | 9.7.5 (a missing missing previously threw) |
Log Levels
| Level | Description | Use Case |
|---|---|---|
Off |
No logging | Production, performance-critical |
Errors |
Errors only | Production with error tracking |
Warnings |
Errors and warnings | Default production level |
Messages |
Info messages | Development, debugging |
Verbose |
Detailed execution | Detailed debugging |
Diagnostic |
Everything including performance, positioning and structure | Performance analysis |
See Logging and Tracing for full details on log levels, appending the log to a PDF, and routing output to custom sinks.
Best Practices
Production Documents
<?paperwork parser-mode='Lax' log-level='Errors' ?>
Development Documents
<?paperwork parser-mode='Strict' parser-log='true' log-level='Messages' ?>
Debugging Documents
<?paperwork parser-mode='Lax'
parser-log='true'
append-log='true'
log-level='Verbose' ?>
Verbose provides a high level of detail to identify what issues have occured without the significant performance drain or Diagnostic, which should only be used in extraneous circumstances.
Localized Documents
<?paperwork parser-culture='en-GB' log-level='Warnings' ?>
Programmatic Configuration
The same settings can be made in code, by parsing the template into an engine Document with your own parser settings, and passing it to the document builder. See The Engine Document.
using System.Globalization;
using Scryber;
using Scryber.Components;
var settings = Document.CreateParserSettings("template.html", ParserConformanceMode.Strict);
settings.LogParserOutput = false;
settings.TraceLog.SetRecordLevel(TraceRecordLevel.Warnings);
settings.SpecificCulture = new CultureInfo("en-GB");
settings.ControllerType = typeof(MyController);
using var reader = new StreamReader("template.html");
using var doc = (Document)Document.Parse("template.html", reader, ParseSourceType.LocalFile, settings.Resolver, settings);
await factory.NewDocument()
.WithLayout(doc)
.SaveAsync("output.pdf");
Related Documentation
- Document Controllers - Using the
controllerattribute - Configuration Files - Application-level configuration
- Logging and Tracing - Log levels, appending to PDF, and custom log sinks
- Best Practices - Configuration guidelines