diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4895c91..c48a2f2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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.
diff --git a/Schematron_DataQuality_Checks/Basic_Checks/invocation/README.md b/Schematron_DataQuality_Checks/Basic_Checks/invocation/README.md
index 0c7e92c..8298b7a 100644
--- a/Schematron_DataQuality_Checks/Basic_Checks/invocation/README.md
+++ b/Schematron_DataQuality_Checks/Basic_Checks/invocation/README.md
@@ -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
diff --git a/Schematron_DataQuality_Checks/Basic_Checks/invocation/SchematronValidate.cs b/Schematron_DataQuality_Checks/Basic_Checks/invocation/SchematronValidate.cs
index a330a3f..259dd26 100644
--- a/Schematron_DataQuality_Checks/Basic_Checks/invocation/SchematronValidate.cs
+++ b/Schematron_DataQuality_Checks/Basic_Checks/invocation/SchematronValidate.cs
@@ -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;
@@ -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;
}
diff --git a/Schematron_DataQuality_Checks/Basic_Checks/invocation/SchematronValidate.csproj b/Schematron_DataQuality_Checks/Basic_Checks/invocation/SchematronValidate.csproj
index 9ecac82..026a1d6 100644
--- a/Schematron_DataQuality_Checks/Basic_Checks/invocation/SchematronValidate.csproj
+++ b/Schematron_DataQuality_Checks/Basic_Checks/invocation/SchematronValidate.csproj
@@ -1,7 +1,8 @@
-
+
Exe
net8.0
@@ -10,6 +11,14 @@
FundsXml.Schematron
-
+
+
diff --git a/XML_Signature/dotnet/SignVerify.csproj b/XML_Signature/dotnet/SignVerify.csproj
index f23f5af..8eba658 100644
--- a/XML_Signature/dotnet/SignVerify.csproj
+++ b/XML_Signature/dotnet/SignVerify.csproj
@@ -1,7 +1,7 @@
-
+
Exe
net8.0
diff --git a/XSD_Validation/README.md b/XSD_Validation/README.md
index 66d49a2..c7a519e 100644
--- a/XSD_Validation/README.md
+++ b/XSD_Validation/README.md
@@ -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
@@ -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 ` `, exits `0` on valid, `1` on
@@ -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 "`
-(`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 `
+(exit 0 valid / 1 invalid).
+
+The CLI/`xmllint` stack still uses `tools/fetch-schema.sh 4.2.9` until its
+phase lands.
diff --git a/XSD_Validation/dotnet/SchemaResolver.cs b/XSD_Validation/dotnet/SchemaResolver.cs
new file mode 100644
index 0000000..2274055
--- /dev/null
+++ b/XSD_Validation/dotnet/SchemaResolver.cs
@@ -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. /.schema-cache//FundsXML.xsd — reused if present.
+// 3. download from the official GitHub release (HttpClient follows the
+// 302 by default), caching into .schema-cache//; 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/";
+
+ /// Resolve FundsXML.xsd for .
+ 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);
+ }
+}
diff --git a/XSD_Validation/dotnet/XsdValidate.cs b/XSD_Validation/dotnet/XsdValidate.cs
index 5f15d86..7261df0 100644
--- a/XSD_Validation/dotnet/XsdValidate.cs
+++ b/XSD_Validation/dotnet/XsdValidate.cs
@@ -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 --
-// 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
@@ -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;
}
diff --git a/XSD_Validation/dotnet/XsdValidate.csproj b/XSD_Validation/dotnet/XsdValidate.csproj
index 84d7946..c55f9a1 100644
--- a/XSD_Validation/dotnet/XsdValidate.csproj
+++ b/XSD_Validation/dotnet/XsdValidate.csproj
@@ -1,6 +1,7 @@
-
+
Exe
net8.0