XSchematron.Cli
0.2.2
dotnet tool install --global XSchematron.Cli --version 0.2.2
dotnet new tool-manifest
dotnet tool install --local XSchematron.Cli --version 0.2.2
#tool dotnet:?package=XSchematron.Cli&version=0.2.2
nuke :add-package XSchematron.Cli --version 0.2.2
XSchematron.Cli
A cross-platform .NET tool — xschematron — that validates an XML document against one or
more ISO Schematron (.sch) rule files and exits non-zero when it finds problems. Built for
build scripts and CI pipelines: one command, machine-readable output, a controllable exit code.
Schematron is the business-rule layer XSD can't express ("if the VAT category is X, the rate must be Y"), which is why it carries the substantive rules in e-invoicing standards such as CEN EN 16931 and Peppol BIS Billing 3.0.
Need to validate from inside your own code instead? Use the XSchematron library this tool is built on.
Install
# as a global tool
dotnet tool install -g XSchematron.Cli
# or pinned per repository, which is the CI-friendly form
dotnet new tool-manifest # once per repo, if you have no .config/dotnet-tools.json
dotnet tool install XSchematron.Cli
dotnet tool restore # on a fresh clone or CI runner
The command is xschematron (or dotnet xschematron when installed as a local tool). It runs on
the .NET 8 or .NET 10 runtime.
You supply the rule files. This package ships code only — no
.schfiles are included. Point the tool at the rule sets you need (CEN EN 16931, Peppol BIS Billing 3.0, or your own).
Usage
# one or more rule files
xschematron validate invoice.xml -s CEN-EN16931-UBL.sch -s PEPPOL-EN16931-UBL.sch
# or a whole directory of them
xschematron validate invoice.xml --schema-dir ./schematron --recursive
# machine-readable output, written to a file
xschematron validate invoice.xml --schema-dir ./schematron --format json -o report.json
Usage:
xschematron validate <xml-file> [options]
Options:
-s, --schema <schema> A Schematron (.sch) file to validate against. Repeat to use several.
--schema-dir <schema-dir> Directory of .sch files to validate against.
--recursive Search --schema-dir recursively.
--phase <phase> Run only the patterns activated by this phase id. Defaults to all patterns.
--format <json|text> Output format: text or json. [default: text]
-o, --output <output> Write the report to this file instead of stdout.
--fail-on <error|info|warning> Lowest severity that causes a non-zero exit code. [default: error]
--quiet Print only the summary line, not individual messages.
--schema and --schema-dir combine; the resulting set is de-duplicated and validated in a
stable order.
Exit codes
| Code | Meaning |
|---|---|
0 |
Valid — no message reached the --fail-on threshold |
1 |
One or more messages at or above --fail-on |
2 |
Usage error, missing document, or a rule file that failed to load |
The split matters in CI: 1 means your document is wrong, 2 means the check itself is
misconfigured. Don't treat them as the same failure.
Output
text (default)
Messages grouped by the rule file that produced them, then a summary line. Paths are printed absolute, as resolved:
/work/schematron/CEN-EN16931-UBL.sch:
error: [BR-CO-17] VAT category tax amount (BT-117) = VAT category taxable amount (BT-116) x (VAT category rate (BT-119) / 100), rounded to two decimals.
at /Invoice[1]/cac:TaxTotal[1]/cac:TaxSubtotal[1]
error: [BR-S-09] The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where VAT category code (BT-118) is "Standard rated" shall equal the VAT category taxable amount (BT-116) multiplied by the VAT category rate (BT-119).
at /Invoice[1]/cac:TaxTotal[1]/cac:TaxSubtotal[1]/cac:TaxCategory[1]
/work/invoice.xml: 2 error(s), 0 warning(s), 0 info.
Rule sets conventionally repeat the rule id at the head of the message text ("[BR-CO-17]-VAT category…"); the text format shows the id once and drops the duplicate. --quiet prints the
summary line only.
json
A stable contract — an object with document, isValid, and a messages array. Null-valued
properties are omitted, and message carries the rule text verbatim, id prefix included.
{
"document": "/work/invoice.xml",
"isValid": false,
"messages": [
{
"schema": "/work/schematron/CEN-EN16931-UBL.sch",
"ruleId": "BR-CO-17",
"patternId": "UBL-model",
"kind": "Assert",
"severity": "Error",
"flag": "fatal",
"message": "[BR-CO-17]-VAT category tax amount (BT-117) = VAT category taxable amount (BT-116) x (VAT category rate (BT-119) / 100), rounded to two decimals.",
"xPathContext": "/Invoice[1]/cac:TaxTotal[1]/cac:TaxSubtotal[1]",
"schemaLineNumber": 224,
"documentLineNumber": 66,
"documentLinePosition": 6
}
]
}
schemaLineNumber locates the assert in the .sch file; documentLineNumber /
documentLinePosition locate the offending node in the validated document.
Severity mapping
| Source | Severity |
|---|---|
<assert> fails |
Error |
<assert flag="fatal"> fails |
Error |
<assert flag="warning"> fails |
Warning |
<report> fires |
Info (does not make a document invalid) |
Use --fail-on warning to make warnings break the build, or --fail-on info to fail on anything
the rule sets report at all.
In a GitHub Actions workflow
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"
- name: Install the validator
run: dotnet tool install -g XSchematron.Cli
- name: Validate invoices
run: |
for invoice in samples/*.xml; do
xschematron validate "$invoice" \
--schema-dir ./schematron --recursive \
--fail-on warning --format json -o "${invoice%.xml}.report.json"
done
Notes and limits
- Rule sets using
queryBinding="xslt2"work without an XSLT 2.0 processor — the underlying engine walks the rules and evaluates XPath 2.0 directly, with no XSLT compilation step. - Custom
<function>blocks are supported two ways: 19 national-identifier checksum and format functions are natively implemented, and any declaration whose body is pure XPath is evaluated as written. A.schdeclaring a function that is neither fails to load with exit code2rather than silently skipping the rule. document('…')resolves against the schema's own directory only — a rule file cannot read arbitrary paths or fetch over the network, which matters when the.schcame from elsewhere.<sch:include>and abstract patterns (abstract="true"/is-a) are not supported, and a.schusing them fails to load with exit code2naming the construct. Use the flattenedpreprocessed/variant upstream publishes alongside the modular source.<extends>rule inheritance, SVRL output, and SARIF output are not supported. See the library README for the full feature table.
Licensing
MIT — see LICENSE. Third-party attribution
(XPath2.Net is MS-PL, System.CommandLine is MIT) is in NOTICE.md, included in this package and
in the repository.
Links
- Source, issues, and contributing: https://github.com/WimSuenens/XML
- Library package: XSchematron
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.