Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check that schematron files can be executed #57

Merged
merged 2 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<version.logback>1.4.14</version.logback>
<version.maven-model>3.9.1</version.maven-model>
<version.picocli>4.6.3</version.picocli>
<version.ph-schematron>7.1.3</version.ph-schematron>
<version.ph-schematron>8.0.0</version.ph-schematron>
<version.semver4j>3.1.0</version.semver4j>
<version.slf4j>2.0.3</version.slf4j>
<version.xmlschema>2.3.0</version.xmlschema>
Expand Down Expand Up @@ -183,6 +183,11 @@
<artifactId>ph-schematron-api</artifactId>
<version>${version.ph-schematron}</version>
</dependency>
<dependency>
<groupId>com.helger.schematron</groupId>
<artifactId>ph-schematron-pure</artifactId>
<version>${version.ph-schematron}</version>
</dependency>
<dependency>
<groupId>com.helger.schematron</groupId>
<artifactId>ph-schematron-validator</artifactId>
Expand Down Expand Up @@ -335,6 +340,10 @@
<groupId>com.helger.schematron</groupId>
<artifactId>ph-schematron-api</artifactId>
</dependency>
<dependency>
<groupId>com.helger.schematron</groupId>
<artifactId>ph-schematron-pure</artifactId>
</dependency>
<dependency>
<groupId>com.helger.schematron</groupId>
<artifactId>ph-schematron-validator</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import java.util.Locale;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
Expand All @@ -18,7 +20,9 @@
import com.helger.commons.error.list.IErrorList;
import com.helger.commons.io.resource.FileSystemResource;
import com.helger.commons.io.resource.IReadableResource;
import com.helger.schematron.ISchematronResource;
import com.helger.schematron.SchematronHelper;
import com.helger.schematron.pure.SchematronResourcePure;
import com.helger.xml.microdom.IMicroDocument;
import com.helger.xml.microdom.serialize.MicroWriter;
import com.helger.xml.transform.TransformSourceFactory;
Expand Down Expand Up @@ -59,48 +63,53 @@ public Validator validate() throws Exception {
return;
}

// Create a fact for teh file being validated, to use in the validation result
// Create a fact for the file being validated, to use in the validation result
SchematronFileFact schematronFileFact = new SchematronFileFact(new SchematronFile(file));

IReadableResource schematron = new FileSystemResource(file);
// Resolve all included files, so that they also get validated.
final IMicroDocument doc = SchematronHelper.getWithResolvedSchematronIncludes(schematron,
e -> handleError(e, schematronFileFact));

if (doc == null) {
ValidationResult result = new ValidationResult(schematronFileFact,
"File is not well-formed XML", ValidationStatusEnum.ERROR);

results.add(result);
return;
}

String resolved = MicroWriter.getNodeAsString(doc);
if (resolved == null) {
ValidationResult result = new ValidationResult(schematronFileFact,
"Resolved schematron could not be processed", ValidationStatusEnum.ERROR);

results.add(result);
return;
}
Source source = TransformSourceFactory.create(resolved);
// This will return an empty list if the schematron is valid.
IErrorList errors = com.helger.schematron.validator.SchematronValidator.validateSchematron(source);

if (errors != null) {
errors.forEach(e -> handleError(e, schematronFileFact));
} else {
ValidationResult result = new ValidationResult(schematronFileFact,
"Error while validating schematron", ValidationStatusEnum.ERROR);

results.add(result);
return;
}
checkAgainstSchema(schematronFileFact, schematron);
checkCanExecute(schematronFileFact, schematron);
});

return this;
}

private void checkAgainstSchema(SchematronFileFact schematronFileFact, IReadableResource schematron) {
// Resolve all included files, so that they also get validated.
final IMicroDocument doc = SchematronHelper.getWithResolvedSchematronIncludes(schematron,
e -> handleError(e, schematronFileFact));

if (doc == null) {
ValidationResult result = new ValidationResult(schematronFileFact,
"File is not well-formed XML", ValidationStatusEnum.ERROR);

results.add(result);
return;
}

String resolved = MicroWriter.getNodeAsString(doc);
if (resolved == null) {
ValidationResult result = new ValidationResult(schematronFileFact,
"Resolved schematron could not be processed", ValidationStatusEnum.ERROR);

results.add(result);
return;
}
Source source = TransformSourceFactory.create(resolved);
// This will return an empty list if the schematron is valid.
IErrorList errors = com.helger.schematron.validator.SchematronValidator.validateSchematron(source);

if (errors != null) {
errors.forEach(e -> handleError(e, schematronFileFact));
} else {
ValidationResult result = new ValidationResult(schematronFileFact,
"Error while validating schematron", ValidationStatusEnum.ERROR);

results.add(result);
}
}

private void handleError(IError error, SchematronFileFact schematronFileFact) {
if (error.getErrorLevel().isError()) {
Locale locale = Locale.getDefault();
Expand All @@ -114,6 +123,22 @@ private void handleError(IError error, SchematronFileFact schematronFileFact) {
}
}


private void checkCanExecute(SchematronFileFact schematronFileFact, IReadableResource schematron) {
ISchematronResource phSchematron = new SchematronResourcePure(schematron);
try {
// Execute the schematron on a dummy XML.
// Having an XML that causes all rules to be executed is not possible anyway.
Source source = new StreamSource(new StringReader("<a></a>"));
phSchematron.applySchematronValidation(source);
} catch (Exception e) {
ValidationResult result = new ValidationResult(schematronFileFact,
"Error during execution: " + e.getMessage(), ValidationStatusEnum.ERROR);

results.add(result);
}
}

@Override
public Set<ValidationResult> getResults() {
return results;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# eForms Schematron rules

The Schematron rules used to validate eForms notices.

## Structure

The validation of eForms notices is conceptually structured in several stages:
- Stage 1: Check the presence and absence of container elements;
- Stage 2: Check the presence and absence of the leaf elements (a.k.a. Business Terms) together with the notice type and legal basis as per the eForms regulation. It also includes checks of the repeatable elements and the multilingual ones.
- Stage 3: Check values of leaf elements (Business Terms). This includes:
- checking that a value matches a specific pattern, for example for a URL or an email address;
- checking that a value is part of a given codelist.
- Stage 4: Check the presence and absence of leaf elements or their related values depending on specific conditions which apply to the notice being checked. This includes, among others, the "conditional mandatory" rules from the eForms regulation.
- Stage 5 : Check the values of leaf elements in the notice are consistent with each other.

Those stages are implemented in one or more schematron files. Each file contains a single `pattern` root element. Some stages require rules with distinct contexts to be applied to the same node, so they are implemented in more than one pattern.

Those Schematron files are all referenced in a single file `complete-validation.sch`, using the Schematron `include` element.

Schematron rules are organised in 2 folders:
* `static`: all rules that only use information in the notice being validated, as described above.
* `dynamic`: all rules in `static`, plus rules that use information in other notices.

In the `dynamic` folder, additional schematron files named `validation-stage-6*.sch` checks that a change notice is consistent with the original notice. This requires fetching the original notice based on its identifier, by making an HTTP request to an external service.
The URL of this service is configured by a variable defined in the file `config.sch`, which is included in `complete-validation.sch`.

## Usage

The schematron rules can be executed via any standard Schematron implementation, using the file `complete-validation.sch`.

If you use the XSLT implementation at https://github.com/Schematron/schematron, you must implement the following fix so that rules on attributes are fired: https://github.com/Schematron/schematron/issues/29.
Loading