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