Skip to content
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,14 @@ tasks.register('queryOne', name.jurgenei.gradle.xml.XQueryTask) {
```groovy
tasks.register('validateSchematron', name.jurgenei.gradle.xml.SchematronTask) {
schema 'src/main/schematron/rules.sch'
// Optional persistent compiled stylesheet cache.
style 'build/generated/schematron/rules.compiled.xsl'
source(fileTree('src/main/xml') { include '**/*.xml' })
outputDir.set(layout.buildDirectory.dir('reports/schematron'))
reportFormat.set(name.jurgenei.gradle.xml.validation.ReportFormat.SVRL_AND_JUNIT)
// Optional SchXslt transpiler parameters.
phase.set('#ALL')
severityThreshold.set('warning')
workers.set(4)
failOnError.set(false)
}
Expand All @@ -223,6 +228,18 @@ tasks.register('validateXsd', name.jurgenei.gradle.xml.XsdTask) {
}
```

Schematron-specific options:

- `style(...)`/`style.set(...)` (optional): persistent location for compiled Schematron XSLT.
- When unset, a temp compiled stylesheet is used per validation run.
- When set, recompilation is skipped if the compiled stylesheet is newer than inputs and transpiler parameters are unchanged.
- `transpilerStylesheet(...)` (optional): override bundled SchXslt transpiler.
- Optional SchXslt transpiler parameter properties (only passed when explicitly set):
- `debug`, `phase`, `expandText`, `streamable`, `locationFunction`, `failEarly`
- `terminateValidationOnError`, `reportActivePattern`, `reportFiredRule`, `reportSuppressedRule`
- `reportSkippedAssertion`, `compactReport`, `severityThreshold`, `defaultSeverity`, `defaultFrom`
- `checkAssembledSchema`, `handleDynamicErrors`

## Run tests

```bash
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
}

group = "name.jurgenei.gradle"
version = "0.1.3"
version = "0.1.6"

repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pluginManagement {

- `xslt-basic` - transform one XML with XSLT
- `xquery-basic` - transform one XML with XQuery
- `validation-basic` - validate XML with XSD and Schematron (SVRL and JUnit reports)
- `validation-basic` - validate XML with XSD and Schematron (SVRL/JUnit), including a persisted compiled Schematron stylesheet (`style`) and transpiler params

## Run samples

Expand Down
4 changes: 4 additions & 0 deletions samples/validation-basic/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ tasks.register('runXsd', name.jurgenei.gradle.xml.XsdTask) {

tasks.register('runSchematron', name.jurgenei.gradle.xml.SchematronTask) {
schema 'src/main/schematron/rules.sch'
style 'build/generated/schematron/rules.compiled.xsl'
source(fileTree('src/main/xml') { include '*.xml' })
outputDir.set(layout.buildDirectory.dir('reports/schematron'))
reportFormat.set(name.jurgenei.gradle.xml.validation.ReportFormat.SVRL_AND_JUNIT)
phase.set('#ALL')
severityThreshold.set('warning')
failOnError.set(false)
}

Expand All @@ -28,6 +31,7 @@ tasks.register('verifySample') {
file('build/reports/xsd/invalid.svrl.xml'),
file('build/reports/schematron/valid.svrl.xml'),
file('build/reports/schematron/invalid.svrl.xml'),
file('build/generated/schematron/rules.compiled.xsl'),
file('build/reports/xml-validation/junit/valid.junit.xml'),
file('build/reports/xml-validation/junit/invalid.junit.xml')
]
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
rootProject.name = "gradle-xml-transform"
rootProject.name = "gradle-xml-plugin"

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand Down Expand Up @@ -42,6 +44,8 @@
@DisableCachingByDefault(because = "Uses external transform engines and file trees")
public abstract class AbstractXmlTransformTask extends SourceTask {

private static final Set<String> SUPPORTED_OUTPUT_METHODS = Set.of("xml", "json", "text");

/**
* Destination root directory for transformed files.
*
Expand Down Expand Up @@ -81,6 +85,18 @@ public abstract class AbstractXmlTransformTask extends SourceTask {
@Input
public abstract Property<String> getOutputExtension();

/**
* Optional explicit serializer output method used by Saxon.
*
* <p>Supported values are {@code xml}, {@code json}, and {@code text}. If not set,
* the method is inferred from the destination file extension.</p>
*
* @return output method property
*/
@Input
@Optional
public abstract Property<String> getOutputMethod();

/**
* Transform parameters exposed to the execution engine.
*
Expand Down Expand Up @@ -160,6 +176,15 @@ public void output(Object path) {
getOutputFile().set(getProject().file(path));
}

/**
* Sets an explicit serializer output method (Gradle DSL friendly).
*
* @param method one of {@code xml}, {@code json}, {@code text}
*/
public void outputMethod(String method) {
getOutputMethod().set(method);
}

@Override
@PathSensitive(PathSensitivity.RELATIVE)
public org.gradle.api.file.FileTree getSource() {
Expand Down Expand Up @@ -325,6 +350,42 @@ protected long latestDependencyTimestamp(File inputFile) {
return inputFile.lastModified();
}

/**
* Resolves the serializer output method from explicit task setting or output extension.
*
* @param outputFile destination file
* @return serializer method to pass to Saxon
*/
protected String resolveSerializerMethod(File outputFile) {
if (getOutputMethod().isPresent()) {
return normalizeAndValidateMethod(getOutputMethod().get());
}
return inferSerializerMethod(outputFile);
}

private String inferSerializerMethod(File outputFile) {
String fileName = outputFile.getName().toLowerCase(Locale.ROOT);
if (fileName.endsWith(".json")) {
return "json";
}
if (fileName.endsWith(".txt") || fileName.endsWith(".text")) {
return "text";
}
return "xml";
}

private String normalizeAndValidateMethod(String configuredMethod) {
String normalized = configuredMethod == null ? "" : configuredMethod.trim().toLowerCase(Locale.ROOT);
if ("txt".equals(normalized)) {
normalized = "text";
}
if (!SUPPORTED_OUTPUT_METHODS.contains(normalized)) {
throw new GradleException("Unsupported outputMethod '" + configuredMethod
+ "'. Supported values: xml, json, text");
}
return normalized;
}

private File fingerprintMarkerFor(File outputFile) {
return new File(outputFile.getParentFile(), outputFile.getName() + ".inputs.sha256");
}
Expand All @@ -349,6 +410,7 @@ private String computeNonFileInputFingerprint(Map<String, String> params) {
StringBuilder builder = new StringBuilder();
builder.append("task=").append(getClass().getName()).append('\n');
builder.append("outputExtension=").append(getOutputExtension().get()).append('\n');
builder.append("outputMethod=").append(getOutputMethod().getOrElse("<auto>")).append('\n');

List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
Expand Down Expand Up @@ -63,6 +64,14 @@ public abstract class AbstractXmlValidationTask extends SourceTask implements Va
@Input
public abstract MapProperty<String, String> getParams();

/**
* Project directory used during execution-time path resolution.
*
* @return project directory property
*/
@Internal
public abstract DirectoryProperty getProjectDir();

/**
* Creates a validation task with default conventions.
*/
Expand All @@ -73,6 +82,7 @@ public AbstractXmlValidationTask() {
getMaxFailures().convention(1);
getReportFormat().convention(ReportFormat.SVRL);
getJunitSuiteName().convention(getName());
getProjectDir().convention(getProject().getLayout().getProjectDirectory());
getJunitOutputDir().convention(getProject().getLayout().getBuildDirectory().dir("reports/xml-validation/junit"));
}

Expand Down Expand Up @@ -198,7 +208,7 @@ private void validateOne(File inputFile, File outputRoot, Map<Path, String> rela

private File svrlFileFor(File inputFile, File outputRoot, Map<Path, String> relativePaths) {
Path inputPath = inputFile.toPath().toAbsolutePath().normalize();
Path projectPath = getProject().getProjectDir().toPath().toAbsolutePath().normalize();
Path projectPath = getProjectDir().get().getAsFile().toPath().toAbsolutePath().normalize();

String relative = relativePaths.get(inputPath);
if (relative == null) {
Expand All @@ -216,7 +226,7 @@ private File svrlFileFor(File inputFile, File outputRoot, Map<Path, String> rela

private File junitFileFor(File inputFile, Map<Path, String> relativePaths) {
Path inputPath = inputFile.toPath().toAbsolutePath().normalize();
Path projectPath = getProject().getProjectDir().toPath().toAbsolutePath().normalize();
Path projectPath = getProjectDir().get().getAsFile().toPath().toAbsolutePath().normalize();

String relative = relativePaths.get(inputPath);
if (relative == null) {
Expand Down
Loading
Loading