Für ein eigenes Projekt habe ich das Repository mmnewmedia/schematron-parser entwickelt. Hierbei handelt es sich um einen Parser, der Schematron-Regeln in PHP-Klassen übersetzt, die ohne weitere Library, wie z.B. Saxon, zum Validieren von komplexen XML Strukturen genutzt werden können.
Wie Schematron-Regeln in native PHP-Klassen übersetzt werden
Schematron ist ein mächtiges Regelwerkzeug für die Validierung von XML-Strukturen – aber für PHP-Entwickler oft ein rotes Tuch. Der Grund ist simpel: Schematron basiert auf XPath 2.0, und PHP unterstützt nur XPath 1.0. Wer ernsthaft Schematron validieren will, landet schnell bei Java-Abhängigkeiten wie Saxon, XSLT-Transformationen oder externen Validatoren.
Genau hier setzt das Open-Source-Projekt mmnewmedia/schematron-parser an: Ein Parser, der Schematron-Dateien automatisch in ausführbare PHP-Klassen übersetzt – ganz ohne Saxon, ohne Java, ohne XSLT 2.0.
Das Ergebnis: Eine vollständig native PHP-Validierungsengine, die Schematron-Regeln direkt ausführt.
Das Problem: XPath 2.0 in PHP existiert nicht
Schematron nutzt XPath 2.0 intensiv:
- distinct-values()
- matches()
- tokenize()
- avg()
- sum()
- for-each-group()
- komplexe logische Ausdrücke
- Sequenzen, Typkonvertierungen, String-Funktionen
PHPs DOMXPath kann davon … nichts.
Nur XPath 1.0.
Das führt zu einem Dilemma: Schematron ist Standard. PHP kann den Standard nicht ausführen.
Bisherige Lösungen:
- Java-Validator über CLI aufrufen
- Saxon HE/PE/EE einbinden
- XSLT 2.0 Transformationen nutzen
- externe Webservices
- oder: Schematron komplett ignorieren
Für produktive Systeme ist das alles unbefriedigend.
Die Idee: Schematron → PHP Compiler
Der Schematron-Parser löst das Problem nicht durch Emulation von XPath 2.0, sondern durch Übersetzung.
Der Ansatz
- Schematron-Datei einlesen
- Patterns, Rules und Assertions extrahieren
- XPath-Tests analysieren
- XPath 2.0 Funktionen erkennen
- Equivalenten PHP-Code generieren
- Jede Regel wird zu einer eigenen PHP-Klasse
Damit entsteht ein regelbasierter Validator, der exakt das tut, was Schematron verlangt – aber vollständig in PHP.
Beispiel: Eine Schematron Regel
Schematron:
<rule context="ram:TaxSubtotal">
<assert test="ram:CategoryCode = 'S'">Category must be S</assert>
</rule>
Generierter PHP-Code:
foreach ($xpath->query('//ram:TaxSubtotal') as $ctx) {
$code = $xpath->evaluate('string(ram:CategoryCode)', $ctx);
if ($code !== 'S') {
$errors[] = "Category must be S";
}
}
Die Logik bleibt identisch – nur die Ausführung erfolgt nativ in PHP.
Beispiel: XPath 2.0 → PHP
Schematron:
<assert test="count(distinct-values(ram:ApplicableTaxRate)) = count(ram:TaxCategory)">
BR-S-08 violated
</assert>
Generierter PHP-Code:
$rates = [];
foreach ($xpath->query('ram:ApplicableTaxRate', $ctx) as $node) {
$rates[] = floatval($node->textContent);
}
$categories = iterator_to_array($xpath->query('ram:TaxCategory', $ctx));
if (count(array_unique($rates)) !== count($categories)) {
$errors[] = "BR-S-08 violated";
}
Der Parser erkennt:
distinct-values()→array_unique()count()→count()- Node-Listen → Arrays
- numerische Typen →
floatval()
Damit wird XPath 2.0 elegant in PHP übersetzt.
Unterstützte XPath 2.0 Funktionen
Der Parser deckt bereits einen großen Teil der realen Schematron-Praxis ab:
| XPath 2.0 Funktion | PHP-Äquivalent |
|---|---|
abs() | abs() |
| avg() | Anonyme PHP Funktion, die alle vorhandenen Node Values summiert und dann durch die Anzahl der vorhandenen Einträge der Node Liste teilt. |
| boolean() | PHP Bedingung, die den übergebenen Wert wandelt und auf true oder false prüft |
ceiling() | ceil() |
concat() | PHP Konkatenierung mittels . |
contains() | str_contains() |
count() | count() |
distinct-values() | array_unique() |
empty() | DOMNodeList->length, count(), instanceof() und Prüfung auf Leere Strings |
ends-with() | str_ends_with() |
exists() | DOMNodeList->length, count(), instanceof() und Prüfung auf Leere Strings |
false() | false |
floor() | floor() |
last() | count() |
local-name() | Anonyme PHP Funktion, die den lokalen Namen eines Elements DOM ELements liefert. |
lower-case() | mb_strtolower() |
matches() | preg_match() |
max() | max() |
min() | min() |
name() | Anonyme PHP Funktion, die den Namen einer DOMNode Instanz oder einen leeren String liefert. |
normalize-space() | preg_replace() als Funktion zum Ersetzen mehrfach aufeinander folgender Whitespaces mit einem einzelnen Whitespace. |
not() | ! (Not-Operator) |
number() | floatval() |
position() | X-Path 1.0 Funktion ( $xpath->evaluate('position', $ctx)) |
replace() | preg_replace() |
round() | round() |
starts-with() | strncmp() |
string-length() | mb_strlen() |
substring-after() | Anonyme PHP Funktion, die mittels substr() den Teilstring ab dem ersten Auftreten des übergebenen Vergleichsstrings bis zum Ende des zu durchsuchenden Strings zurück gibt. |
substring-before() | Anonyme PHP Funktion, die mittels substr() den Teilstring ab Beginn des zu durchsuchenden Strings bis zum Auftreten des übergebenen Vergleichsstrings. |
substring() | substr() |
sum() | Summierungslogik in PHP |
tokenize() | preg_split() |
true() | true |
upper-case() | mb_strtoupper() |
Damit lassen sich z.B. die EN16931-Regeln problemlos abbilden – inklusive X-Rechnung.
Architektur: Wie der Parser funktioniert
Der Schematron-Parser besteht aus drei Kernkomponenten:
1. Schematron Parser
Extrahiert:
- Patterns
- Rules
- Contexts
- Assertions
- Messages
2. XPath-Compiler
Erkennt:
- Funktionen
- Operatoren
- Node-Selektoren
- Sequenzen
- numerische und string-basierte Ausdrücke
Und erzeugt daraus ausführbaren PHP-Code.
3. Rule Engine
Jede Regel wird zu einer Klasse:
<?php
declare(strict_types=1);
namespace MMNewmedia\Schematron\Rules\Cii;
use DOMNode;
use DOMXPath;
final class BR01Rule
{
public static function validate(DOMXPath $xpath, DOMNode $context): bool
{
return (
(
trim(
preg_replace('/\s+/', ' ', $xpath->evaluate("string(rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID)", $context))
)
) != ('')
) !== false;
}
public static function message(): string
{
return "[BR-01]-An Invoice shall have a Specification identifier (BT-24).";
}
public static function context(DOMXPath $xpath, DOMNode $context): iterable
{
return ($xpath->evaluate("/rsm:CrossIndustryInvoice", $context));
}
}
Ein Validator orchestriert alle Regeln:
$validator = new SchematronValidator();
/** var bool $result */
$result = $validator->validate($xml);
Warum das wichtig ist
- Keine Java-Abhängigkeiten
- Keine Saxon-Lizenz
- Keine XSLT 2.0 Transformation
- Vollständig native PHP-Validierung
- Ideal für EN16931 / XRechnung / UBL / Custom Schematron
- Deployment-freundlich (Docker, Shared Hosting, CI / CD)
- Performant und erweiterbar
Für PHP-Teams, die XML-Validierung ernst nehmen müssen, ist das ein echter Gamechanger.
Repository und Einstieg
Das Projekt ist Open Source und auf GitHub verfügbar: https://github.com/ezkimo/schematron-parser
Der Einstieg ist einfach:
composer require mmnewmedia/schematron-parser
Erstellen von Rule-Klassen über CLI:
php ./vendor/bin/compile-schematron.php <source.sch> <outputDir> <namespace>
Erstellen von Rule-Klassen mit PHP:
use MMNewmedia\Schematron\Ast\XPathTokenizer;
use MMNewmedia\Schematron\Compiler\ExpressionCompiler;
use MMNewmedia\Schematron\Compiler\ExpressionNormalizer;
use MMNewmedia\Schematron\Compiler\FunctionLoader;
use MMNewmedia\Schematron\Compiler\XPathAstCompiler;
use MMNewmedia\Schematron\Parser\XPathAstParser;
use MMNewmedia\Schematron\SchematronCompilerService;
$functionLoader = new FunctionLoader(
dirname(__DIR__) . '/vendor/mmnewmedia/schematron-parser/src/Schematron/Compiler/Functions',
'MMNewmedia\Schematron\Compiler\Functions',
);
$compiler = new ExpressionCompiler(
new XPathAstParser(new XPathTokenizer()),
new XPathAstCompiler($functionLoader->load()),
new ExpressionNormalizer(),
);
// XPath -> PHP expression evaluating against a context node ($context by default)
$php = $compiler->compile('substring-before(ram:ID, "-")', '$node');
// XPath -> PHP expression evaluating to a node-set (DOMNodeList)
$nodeSet = $compiler->compileNodeSet('.//ram:SpecifiedLineTradeTransaction', '$node');
$service = new SchematronCompilerService($compiler, '/path/to/output/Rules/Cii');
$service->compile(
'/path/to/EN16931-CII-validation-preprocessed.sch',
'MyProject\Schematron\Rules\Cii'
);
Fazit
Der Schematron-Parser zeigt, dass Schematron-Validierung in PHP nicht kompliziert sein muss. Durch die automatische Übersetzung von Schematron-Regeln in native PHP-Klassen entsteht eine Lösung, die:
- leichtgewichtig
- performant
- standardkonform
- und komplett ohne externe Abhängigkeiten
auskommt.
Ein Werkzeug, dass die Lücke zwischen Schematron-Standard und PHP-Ökosystem endlich schließt.