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
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ jobs:
fi
echo "Python validate.py: positive ok, negative correctly rejected"

- name: .NET - XSD validation (in-language schema resolve)
run: |
set -e
D="dotnet run --project XSD_Validation/dotnet --"
# Cache is warm from the xmllint step: exercises the resolver's
# cache-hit path and the $FUNDSXML_SCHEMA_DIR env override.
$D 4.2.9 FundsXML_Files/4.2.9/positions/Mixed-Fund_Positions.xml
FUNDSXML_SCHEMA_DIR="$PWD/.schema-cache/4.1.0" \
$D 4.1.0 FundsXML_Files/4.1.0/positions/Equity-Fund_Positions.xml
if $D 4.2.9 tests/fixtures/invalid/xsd-invalid_Positions.xml; then
echo "::error::xsd-invalid unexpectedly validated (.NET)"; exit 1
fi
echo ".NET XsdValidate: positive ok, negative correctly rejected"

# ---------------------------------------------------------------------
# Java examples — built & run via the committed Maven Wrapper. The first
# ./mvnw bootstraps Maven itself, then resolves all deps from Central.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ exits 1 on any failed-assert (including warnings).
|-------|------|----------------------|
| Java (native) | [`SchematronValidate.java`](SchematronValidate.java) | ✅ verified (SchXslt Java API, via Maven Wrapper) |
| Python | [`validate_schematron.py`](validate_schematron.py) | saxonche via repo venv (`pip install -e .`); SchXslt jar via `$FUNDSXML_SCHXSLT_JAR` or Maven local repo — reference variant |
| .NET/C# | [`SchematronValidate.cs`](SchematronValidate.cs) | needs .NET SDK + `SaxonHE` package |
| .NET/C# | [`SchematronValidate.cs`](SchematronValidate.cs) | SaxonHE via NuGet (`dotnet build`); SchXslt jar via `$FUNDSXML_SCHXSLT_JAR` or Maven local repo — reference variant |
| shared | [`svrl-summary.py`](svrl-summary.py) | ✅ classifier used by all + CI |

The Java example runs standalone and cross-platform via the committed Maven
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
// Schematron validation in .NET / C# via Saxon for .NET (Saxonica SaxonHE).
//
// dotnet add package SaxonHE # Saxon 12.x for .NET
// # add the Saxon-HE-for-.NET package matching your TFM (see .csproj note)
// dotnet run --project Schematron_DataQuality_Checks/Basic_Checks/invocation \
// -- ../basic_checks.sch document.xml
// -- Schematron_DataQuality_Checks/Basic_Checks/basic_checks.sch document.xml
// Exit: 0 = no error-role failed-assert, 1 = at least one, 2 = setup error.
//
// basic_checks.sch uses queryBinding="xslt2"; Saxon supplies the XSLT 3.0
// engine. The SchXslt pipeline stylesheets are reused from the SchXslt CLI jar
// (this .NET stack resolves it via NuGet once migrated; the Java example
// already runs standalone via the Maven Wrapper): the whole xslt/ tree is extracted so
// the pipeline's relative imports resolve, then compile .sch -> SVRL stylesheet
// basic_checks.sch uses queryBinding="xslt2"; the SaxonHE NuGet package
// supplies the XSLT 3.0 engine (resolved by `dotnet build`, standalone). The
// SchXslt pipeline stylesheets have no NuGet/.NET distribution, so they are
// reused from the SchXslt CLI jar, located via $FUNDSXML_SCHXSLT_JAR or the
// Maven local repo (see below); the whole xslt/ tree is extracted so the
// pipeline's relative imports resolve, then compile .sch -> SVRL stylesheet
// -> apply to instance -> SVRL, then classify (same logic as svrl-summary.py).
//
// NOTE: reference implementation — not executed in this environment (no .NET
// SDK). The flow mirrors the verified Python/CLI paths exactly.
// NOTE: reference variant. The Java Schematron example is the verified, fully
// standalone path (Maven Wrapper). The flow here mirrors it exactly.

using System;
using System.IO;
Expand All @@ -38,15 +39,29 @@ private static int Main(string[] args)
string failOn = args.SkipWhile(a => a != "--fail-on")
.Skip(1).FirstOrDefault() ?? "error";

string repoRoot = Path.GetFullPath(Path.Combine(
AppContext.BaseDirectory, "..", "..", "..", "..", "..", ".."));
string cliJar = Path.Combine(repoRoot, ".lib", "schxslt-cli-1.10.1.jar");
// SchXslt has no NuGet/.NET distribution, so this (reference) .NET
// stack locates the SchXslt CLI jar standalone, in order:
// 1. $FUNDSXML_SCHXSLT_JAR (explicit path)
// 2. the Maven local repo — the Java Schematron module declares
// name.dmaus.schxslt:cli:1.10.1, so `./mvnw -pl Schematron_
// DataQuality_Checks/Basic_Checks/invocation compile` populates it.
const string schxsltVersion = "1.10.1";
string m2 = Environment.GetEnvironmentVariable("MAVEN_REPO_LOCAL")
?? Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
".m2", "repository");
string cliJar = Environment.GetEnvironmentVariable("FUNDSXML_SCHXSLT_JAR")
?? Path.Combine(m2, "name", "dmaus", "schxslt", "cli",
schxsltVersion, $"cli-{schxsltVersion}.jar");
if (!File.Exists(cliJar))
{
Console.Error.WriteLine(
"SchXslt jar missing (this .NET stack is migrated to NuGet in "
+ "a later phase; the Java example already runs standalone via "
+ "the Maven Wrapper)");
$"SchXslt jar not found at {cliJar}.\n"
+ "Set $FUNDSXML_SCHXSLT_JAR, or populate the Maven local repo "
+ "once with:\n ./mvnw -q -pl Schematron_DataQuality_Checks/"
+ "Basic_Checks/invocation compile\n"
+ "(the Java Schematron example runs fully standalone via the "
+ "Maven Wrapper and is the verified path.)");
return 2;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Reference project. Requires the Saxon-HE for .NET package:
dotnet add package SaxonHE
Run: dotnet run -- ../basic_checks.sch <xml-file> [--fail-on error|any] -->
<!-- Reference variant. SaxonHE comes from NuGet (restored by dotnet build);
the SchXslt jar is located via $FUNDSXML_SCHXSLT_JAR or the Maven local
repo (no NuGet SchXslt). Run from the repo root:
dotnet run project <thisdir> ../basic_checks.sch <xml-file> [fail-on error|any] -->
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
Expand All @@ -10,6 +11,14 @@
<RootNamespace>FundsXml.Schematron</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SaxonHE" Version="12.5.0" />
<!-- REFERENCE VARIANT — not verified, not in CI. Saxonica's Saxon-HE 12
for .NET (the `Saxon.Api` s9api this file uses) is published as a set
of `SaxonHE12Net*` packages whose exact id/version/target-framework
pairing is environment-specific (and unrelated to FundsXML). Pick the
one matching your TFM, e.g.:
dotnet add package SaxonHE12NetXslt
The Java Schematron example is the verified, fully standalone path
(Maven Wrapper); this .NET file mirrors its logic for reference. -->
<PackageReference Include="SaxonHE12NetXslt" Version="12.9.10" />
</ItemGroup>
</Project>
6 changes: 3 additions & 3 deletions XML_Signature/dotnet/SignVerify.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Reference project. System.Security.Cryptography.Xml is a NuGet package
on .NET (Core): dotnet add package System.Security.Cryptography.Xml
Run: dotnet run -- sign|verify ... -->
<!-- Reference project. System.Security.Cryptography.Xml comes from NuGet
(restored by dotnet build). Run from the repo root:
dotnet run project XML_Signature/dotnet sign|verify ... -->
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
Expand Down
21 changes: 13 additions & 8 deletions XSD_Validation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ GitHub release (302-aware; also pulls the imported `xmldsig-core-schema.xsd`),
caching into `.schema-cache/`. The official release stays the source of truth
— no committed catalog.

The **Java** (`XsdValidate`) and **Python** (`validate.py`) examples do this
themselves — standalone, cross-platform, no prior step. `tools/fetch-schema.sh`
still seeds the cache for the CLI/xmllint and (until their phases land)
.NET/PowerShell stacks:
The **Java** (`XsdValidate`), **Python** (`validate.py`) and **.NET**
(`XsdValidate.cs` + `SchemaResolver.cs`) examples do this themselves —
standalone, cross-platform, no prior step. `tools/fetch-schema.sh` still seeds
the cache for the CLI/xmllint and (until its phase lands) PowerShell stacks:

```bash
tools/fetch-schema.sh 4.2.9 # only needed for the CLI/.NET/PS stacks
tools/fetch-schema.sh 4.2.9 # only needed for the CLI/PowerShell stacks
```

## Security
Expand All @@ -50,7 +50,7 @@ Every example disables external entity resolution / DTD loading
| CLI | [`cli/validate.sh`](cli/validate.sh) | `xmllint` (+ Saxon note) | ✅ |
| Python | [`python/validate.py`](python/validate.py) | `lxml.etree.XMLSchema` | ✅ standalone (`pip install -e .`) |
| Java | [`java/XsdValidate.java`](java/XsdValidate.java) | `javax.xml.validation` | ✅ standalone (`./mvnw`) |
| .NET/C# | [`dotnet/XsdValidate.cs`](dotnet/XsdValidate.cs) | `XmlSchemaSet` | needs .NET SDK |
| .NET/C# | [`dotnet/XsdValidate.cs`](dotnet/XsdValidate.cs) | `XmlSchemaSet` | ✅ standalone (`dotnet run`) |
| PowerShell | [`powershell/Validate-FundsXml.ps1`](powershell/Validate-FundsXml.ps1) | `System.Xml.Schema` | needs PowerShell |

Convention: each takes `<version> <xml-file>`, exits `0` on valid, `1` on
Expand All @@ -68,5 +68,10 @@ python XSD_Validation/python/validate.py 4.2.9 tests/fixtures/invalid/xsd-invali
```

Java (standalone): `./mvnw -q -pl XSD_Validation/java compile exec:java -Dexec.args="4.2.9 <file>"`
(`mvnw.cmd` on Windows). The CLI/`xmllint` stack still uses
`tools/fetch-schema.sh 4.2.9` until its phase lands.
(`mvnw.cmd` on Windows).

.NET (standalone): `dotnet run --project XSD_Validation/dotnet -- 4.2.9 <file>`
(exit 0 valid / 1 invalid).

The CLI/`xmllint` stack still uses `tools/fetch-schema.sh 4.2.9` until its
phase lands.
85 changes: 85 additions & 0 deletions XSD_Validation/dotnet/SchemaResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SchemaResolver — obtain the official FundsXML XSD for a version, standalone
// & cross-platform (no bash, no prior tool step; works on Windows).
//
// Same 3-step convention as the Java (XsdValidate.java) and Python
// (tools/fundsxml_schema.py) resolvers:
//
// 1. $FUNDSXML_SCHEMA_DIR — a directory holding FundsXML.xsd (+ the
// xmldsig-core-schema.xsd sibling for 4.2.9+). Used as-is, NO network.
// The escape hatch for locked-down corporate networks / offline use.
// 2. <cwd>/.schema-cache/<version>/FundsXML.xsd — reused if present.
// 3. download from the official GitHub release (HttpClient follows the
// 302 by default), caching into .schema-cache/<version>/; the relative
// xmldsig-core-schema.xsd sibling is fetched only when imported (4.2.9+).
//
// The source of truth stays the official release URL — no committed catalog.
// Kept as a second file in this same project (one self-contained example),
// mirroring how the Java example inlines its resolver.

using System;
using System.IO;
using System.Net.Http;

internal static class SchemaResolver
{
private const string ReleaseBase =
"https://github.com/fundsxml/schema/releases/download/";

/// <summary>Resolve FundsXML.xsd for <paramref name="version"/>.</summary>
internal static string Resolve(string version)
{
// 1. Offline / corporate-network escape hatch: a hand-placed copy.
string envDir = Environment.GetEnvironmentVariable("FUNDSXML_SCHEMA_DIR");
if (!string.IsNullOrWhiteSpace(envDir))
{
string envXsd = Path.Combine(envDir, "FundsXML.xsd");
if (!File.Exists(envXsd))
{
throw new FileNotFoundException(
$"FUNDSXML_SCHEMA_DIR set but {envXsd} not found");
}
Console.Error.WriteLine(
$"schema: using $FUNDSXML_SCHEMA_DIR -> {envXsd}");
return envXsd;
}

// 2. Local cache (shared by every stack, gitignored). The examples are
// documented to run from the repo root.
string cacheDir = Path.Combine(
Directory.GetCurrentDirectory(), ".schema-cache", version);
string xsd = Path.Combine(cacheDir, "FundsXML.xsd");
if (File.Exists(xsd))
{
Console.Error.WriteLine($"schema: cached -> {xsd}");
return xsd;
}

// 3. Download from the official release (source of truth).
Directory.CreateDirectory(cacheDir);
Download($"{ReleaseBase}{version}/FundsXML.xsd", xsd);
// From 4.2.9 on, FundsXML.xsd imports xmldsig-core-schema.xsd via a
// relative path — it must sit next to FundsXML.xsd or the schema does
// not compile. Fetch the sibling only when it is actually referenced.
if (File.ReadAllText(xsd).Contains("xmldsig-core-schema.xsd"))
{
Download($"{ReleaseBase}{version}/xmldsig-core-schema.xsd",
Path.Combine(cacheDir, "xmldsig-core-schema.xsd"));
}
return xsd;
}

// HttpClient follows the GitHub 302 (AllowAutoRedirect is on by default).
private static void Download(string url, string outPath)
{
Console.Error.WriteLine($"schema: fetch {url}");
using var http = new HttpClient();
byte[] body = http.GetByteArrayAsync(url).GetAwaiter().GetResult();
string tmp = outPath + ".part";
File.WriteAllBytes(tmp, body);
if (File.Exists(outPath))
{
File.Delete(outPath);
}
File.Move(tmp, outPath);
}
}
30 changes: 16 additions & 14 deletions XSD_Validation/dotnet/XsdValidate.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// XSD validation in .NET / C# via System.Xml.Schema.
//
// Run as a single-file program (no project needed) with the .NET SDK:
// Standalone & cross-platform (no bash, no prior tool step) with the .NET SDK,
// run from the repo root:
// dotnet run --project XSD_Validation/dotnet -- <version> <xml-file>
// or compile XsdValidate.cs into any console app. Exit: 0 valid, 1 invalid,
// 2 usage/setup error.
// Exit: 0 valid, 1 invalid, 2 usage/setup error.
//
// Validates against the official released schema, materialized locally by
// tools/fetch-schema.sh (handles the GitHub 302 redirect and the relative
// xmldsig-core-schema.xsd import that FundsXML 4.2.9+ requires).
// The official released schema is obtained by this example itself via the
// sibling SchemaResolver (see SchemaResolver.cs): $FUNDSXML_SCHEMA_DIR
// (offline/corporate escape hatch) -> .schema-cache/ -> download from the
// official GitHub release (following the 302; fetching the relative
// xmldsig-core-schema.xsd sibling FundsXML 4.2.9+ imports). The official
// release stays the source of truth — no committed catalog.
//
// Security: XmlResolver = null on the reader closes XXE / external-entity
// vectors. An XmlUrlResolver is used ONLY to resolve the schema set's local
Expand All @@ -31,15 +34,14 @@ private static int Main(string[] args)
string version = args[0];
string xmlFile = args[1];

string repoRoot = Path.GetFullPath(
Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
string schemaPath = Path.Combine(
repoRoot, ".schema-cache", version, "FundsXML.xsd");

if (!File.Exists(schemaPath))
string schemaPath;
try
{
Console.Error.WriteLine(
$"schema not cached; run: tools/fetch-schema.sh {version}");
schemaPath = SchemaResolver.Resolve(version);
}
catch (Exception ex)
{
Console.Error.WriteLine($"schema resolution failed: {ex.Message}");
return 2;
}

Expand Down
5 changes: 3 additions & 2 deletions XSD_Validation/dotnet/XsdValidate.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Minimal single-file console project.
Run: dotnet run --project XSD_Validation/dotnet -- 4.2.9 <xml-file> -->
<!-- Minimal console project (XsdValidate.cs + SchemaResolver.cs).
Run from the repo root:
dotnet run project XSD_Validation/dotnet 4.2.9 <xml-file> -->
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
Expand Down