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
58 changes: 42 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,32 @@ jobs:
fi
echo "xsd-invalid correctly rejected"

# ---------------------------------------------------------------------
# Python examples — one idiomatic, OS-agnostic install (venv + editable
# pyproject) replaces the old ad-hoc `pip install lxml`. This also
# exposes the `fundsxml_schema` resolver module to every Python script.
# ---------------------------------------------------------------------
- name: Python - venv & install (pyproject)
run: |
python -m venv .venv
.venv/bin/pip install --quiet --upgrade pip
.venv/bin/pip install --quiet -e .
.venv/bin/python -c "import lxml, saxonche, fundsxml_schema; print('python deps OK')"

- name: Python - XSD validation (in-language schema resolve)
run: |
set -e
V=".venv/bin/python XSD_Validation/python/validate.py"
# Cache is warm from the xmllint step: exercises the resolver's
# cache-hit path and the $FUNDSXML_SCHEMA_DIR env override.
$V 4.2.9 FundsXML_Files/4.2.9/positions/Mixed-Fund_Positions.xml
FUNDSXML_SCHEMA_DIR="$PWD/.schema-cache/4.1.0" \
$V 4.1.0 FundsXML_Files/4.1.0/positions/Equity-Fund_Positions.xml
if $V 4.2.9 tests/fixtures/invalid/xsd-invalid_Positions.xml; then
echo "::error::xsd-invalid unexpectedly validated (Python)"; exit 1
fi
echo "Python validate.py: 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 Expand Up @@ -155,11 +181,11 @@ jobs:
- name: DB integration - multi-fund import+export in 4 languages
run: |
set -e
python3 -m pip install --quiet lxml
PY=.venv/bin/python
FX=FundsXML_Files/4.2.9/positions/Multi-Fund_Positions.xml
MX=FundsXML_Files/4.2.9/positions/Mixed-Fund_Positions.xml
DOC=FUNDSXML_MULTI_1
EQ="python3 Database_Integration/tools/xml_equiv.py"
EQ="$PY Database_Integration/tools/xml_equiv.py"
XSD=".schema-cache/4.2.9/FundsXML.xsd"

# The multi-fund fixture must itself be schema-valid.
Expand All @@ -169,8 +195,8 @@ jobs:
# exported file == input file (and is schema-valid).

# Python
python3 Database_Integration/python/import_fundsxml.py py.db "$FX"
python3 Database_Integration/python/export_fundsxml.py py.db "$DOC" py.xml
$PY Database_Integration/python/import_fundsxml.py py.db "$FX"
$PY Database_Integration/python/export_fundsxml.py py.db "$DOC" py.xml
xmllint --noout --nonet --schema "$XSD" py.xml
$EQ "$FX" py.xml

Expand Down Expand Up @@ -198,44 +224,44 @@ jobs:
$EQ py.xml java.xml && $EQ py.xml js.xml && $EQ py.xml cs.xml

# Single-fund sample still imports+exports (lossy -> XSD-valid only).
python3 Database_Integration/python/import_fundsxml.py mx.db "$MX"
python3 Database_Integration/python/export_fundsxml.py mx.db FUNDSXML_FILE_1 mx.xml
$PY Database_Integration/python/import_fundsxml.py mx.db "$MX"
$PY Database_Integration/python/export_fundsxml.py mx.db FUNDSXML_FILE_1 mx.xml
xmllint --noout --nonet --schema "$XSD" mx.xml
echo "multi-fund import+export equivalent in python/java/javascript/csharp"

- name: Large-file - streaming aggregate / split / delta (constant memory)
run: |
set -e
python3 -m pip install --quiet lxml
PY=.venv/bin/python
P=Large_File_Processing/python
python3 $P/make_large_sample.py big.xml 30000
$PY $P/make_large_sample.py big.xml 30000
xmllint --noout --nonet --schema .schema-cache/4.2.9/FundsXML.xsd big.xml
python3 $P/stream_aggregate.py big.xml | tee agg.txt
$PY $P/stream_aggregate.py big.xml | tee agg.txt
grep -q '^positions : 30000$' agg.txt
grep -q '^sum value (EUR): 30000000.00$' agg.txt
# Java StreamAggregate (pure JDK) under a small heap proves the
# constant-memory claim; MAVEN_OPTS bounds the in-process JVM.
MAVEN_OPTS=-Xmx64m ./mvnw -q -B -pl Large_File_Processing/java exec:java \
-Dexec.args="big.xml" | tee aggj.txt
grep -q '^positions : 30000$' aggj.txt
python3 $P/split.py big.xml chunks/ 10000
$PY $P/split.py big.xml chunks/ 10000
test "$(ls chunks/chunk-*.xml | wc -l)" = "3"
xmllint --noout --nonet --schema .schema-cache/4.2.9/FundsXML.xsd chunks/chunk-0001.xml
python3 $P/delta_diff.py big.xml big.xml # identical -> exit 0
$PY $P/delta_diff.py big.xml big.xml # identical -> exit 0

- name: Data binding / JSON - round-trip + native Java binding
run: |
set -e
python3 -m pip install --quiet lxml
PY=.venv/bin/python
SRC=FundsXML_Files/4.2.9/positions/Mixed-Fund_Positions.xml
python3 Data_Binding_JSON/python/fundsxml_json.py roundtrip "$SRC" rj.xml
$PY Data_Binding_JSON/python/fundsxml_json.py roundtrip "$SRC" rj.xml
xmllint --noout --nonet --schema .schema-cache/4.2.9/FundsXML.xsd rj.xml
# Multi-fund JSON round-trip is lossless -> must be xml_equiv-equal.
MF=FundsXML_Files/4.2.9/positions/Multi-Fund_Positions.xml
python3 Data_Binding_JSON/python/fundsxml_json.py roundtrip "$MF" rjm.xml
$PY Data_Binding_JSON/python/fundsxml_json.py roundtrip "$MF" rjm.xml
xmllint --noout --nonet --schema .schema-cache/4.2.9/FundsXML.xsd rjm.xml
python3 Database_Integration/tools/xml_equiv.py "$MF" rjm.xml
python3 - "$SRC" rj.xml <<'PY'
$PY Database_Integration/tools/xml_equiv.py "$MF" rjm.xml
$PY - "$SRC" rj.xml <<'PY'
import re, sys
o, r = (open(p).read() for p in sys.argv[1:3])
nav = lambda x: re.search(r'<TotalNetAssetValue>\s*<Amount ccy="EUR">([0-9.]+)', x).group(1)
Expand Down
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ fund.json
*.db
*.svrl

# Python bytecode cache
# Python virtualenv (created by `python -m venv .venv`) + bytecode cache +
# editable-install metadata. Deps come from pyproject.toml via pip.
.venv/
__pycache__/
*.pyc
*.egg-info/

# Node example deps (Database_Integration/javascript) — restored via npm install
node_modules/
Expand All @@ -52,3 +55,6 @@ package-lock.json
# .NET build output (Database_Integration/csharp) — restored via dotnet build
bin/
obj/

# Claude Code local session state
.claude/
23 changes: 13 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,29 @@ Please read this before opening a pull request.

Run what you changed and confirm it actually works — no "should pass" claims.

The Java examples build standalone via the committed Maven Wrapper (`./mvnw`,
or `mvnw.cmd` on Windows) — dependencies come from Maven Central, there is no
`fetch-tools.sh` and no `.lib/`.
Java examples build standalone via the committed Maven Wrapper (`./mvnw`, or
`mvnw.cmd` on Windows). Python examples install once into a venv from
`pyproject.toml` and resolve the XSD themselves. Neither needs `fetch-tools.sh`
(gone) and Python no longer needs `fetch-schema.sh`.

```bash
tools/fetch-schema.sh 4.2.9 # XSD cache for the xmllint/Python steps
# (the Java examples also resolve the
# XSD themselves; see XsdValidate)
# Python stack (cross-platform; Windows: .venv\Scripts\activate)
python -m venv .venv && . .venv/bin/activate && pip install -e .
python XSD_Validation/python/validate.py 4.2.9 <your-sample>.xml # self-resolves the XSD

# xmllint still uses the cached schema (CLI stack, removed in a later phase)
tools/fetch-schema.sh 4.2.9
xmllint --noout --schema .schema-cache/4.2.9/FundsXML.xsd <your-sample>.xml

# Schematron via the Maven Wrapper (positive sample -> exit 0)
./mvnw -q -pl Schematron_DataQuality_Checks/Basic_Checks/invocation \
compile exec:java \
-Dexec.args="Schematron_DataQuality_Checks/Basic_Checks/basic_checks.sch <sample>.xml"

# DB round-trip example:
python3 Database_Integration/python/import_fundsxml.py fx.db <sample>.xml
python3 Database_Integration/python/export_fundsxml.py fx.db <docId> out.xml
python3 Database_Integration/tools/xml_equiv.py <sample>.xml out.xml
# DB round-trip example (venv python):
python Database_Integration/python/import_fundsxml.py fx.db <sample>.xml
python Database_Integration/python/export_fundsxml.py fx.db <docId> out.xml
python Database_Integration/tools/xml_equiv.py <sample>.xml out.xml
```

Toolchain notes: the SchXslt CLI jar bundles its own Saxon — do **not** add the
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,10 @@ xsltproc XSLT_DataQuality_Checks/Enhanced_Check/FundsXML_CompleteDQReport_HTML.x
### Option 3: Python

```bash
# Install dependencies
pip install lxml # XSLT 1.0
pip install saxonche # XSLT 2.0/3.0
# Install all Python deps once (lxml + saxonche), cross-platform:
python -m venv .venv
. .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e . # see pyproject.toml

# Run transformation
python -c "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ exits 1 on any failed-assert (including warnings).
| Stack | File | Runnable on this box |
|-------|------|----------------------|
| Java (native) | [`SchematronValidate.java`](SchematronValidate.java) | ✅ verified (SchXslt Java API, via Maven Wrapper) |
| Python | [`validate_schematron.py`](validate_schematron.py) | needs `pip install saxonche` |
| 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 |
| shared | [`svrl-summary.py`](svrl-summary.py) | ✅ classifier used by all + CI |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@

basic_checks.sch uses queryBinding="xslt2", so an XSLT 2.0 processor is
required. `lxml` only does XSLT 1.0 and CANNOT run this ruleset — saxonche
(`pip install saxonche`) embeds Saxon's XSLT 3.0 engine.
(installed via the repo's pyproject.toml: `pip install -e .`) embeds Saxon's
XSLT 3.0 engine.

The SchXslt pipeline stylesheets are reused straight out of the SchXslt CLI jar
(this Python stack will resolve it via pyproject.toml once migrated; the Java
Schematron example already runs standalone via the Maven Wrapper). The whole
`xslt/` tree is extracted
to a temp dir so the pipeline's relative xsl:import/include resolve, then:
Reference variant: SchXslt has no PyPI package, so the SchXslt pipeline
stylesheets are reused straight out of the SchXslt CLI jar, located via
$FUNDSXML_SCHXSLT_JAR or the Maven local repo (see _find_schxslt_jar). The
Java Schematron example is the verified, fully standalone path (Maven
Wrapper). The whole `xslt/` tree is extracted to a temp dir so the
pipeline's relative xsl:import/include resolve, then:
1) compile the .sch into an SVRL stylesheet,
2) apply it to the instance to get SVRL,
3) classify with the shared svrl-summary.py.
"""
import os
import subprocess
import sys
import tempfile
Expand All @@ -25,7 +28,28 @@

HERE = Path(__file__).resolve().parent
REPO_ROOT = HERE.parents[2]
CLI_JAR = REPO_ROOT / ".lib" / "schxslt-cli-1.10.1.jar"

# The SchXslt pipeline stylesheets are reused straight out of the SchXslt CLI
# jar. There is no PyPI distribution of SchXslt, so this (reference) Python
# stack locates the jar standalone, in order:
# 1. $FUNDSXML_SCHXSLT_JAR (explicit path)
# 2. the Maven local repo — the Java Schematron module already declares
# name.dmaus.schxslt:cli:1.10.1, so `./mvnw -pl Schematron_DataQuality_
# Checks/Basic_Checks/invocation compile` populates ~/.m2 with it.
_SCHXSLT_VERSION = "1.10.1"
_M2 = Path(os.environ.get("MAVEN_REPO_LOCAL",
Path.home() / ".m2" / "repository"))


def _find_schxslt_jar() -> Path:
env = os.environ.get("FUNDSXML_SCHXSLT_JAR")
if env:
return Path(env)
return (_M2 / "name" / "dmaus" / "schxslt" / "cli" / _SCHXSLT_VERSION
/ f"cli-{_SCHXSLT_VERSION}.jar")


CLI_JAR = _find_schxslt_jar()


def main() -> int:
Expand All @@ -44,10 +68,13 @@ def main() -> int:
return 2

if not CLI_JAR.is_file():
print("SchXslt jar missing (this Python stack is migrated to a build "
"system in a later phase; the Java example already runs "
"standalone: ./mvnw -pl Schematron_DataQuality_Checks/"
"Basic_Checks/invocation compile exec:java)", file=sys.stderr)
print(f"SchXslt jar not found at {CLI_JAR}.\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.)", file=sys.stderr)
return 2

with tempfile.TemporaryDirectory() as tmp:
Expand Down
2 changes: 1 addition & 1 deletion XQuery_Examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ no XML namespace — queries use bare element names, so they work unchanged on t
| Stack | Entry point | Status |
|-------|-------------|--------|
| Java (s9api, no JAXB) | [`invocation/RunXQuery.java`](invocation/RunXQuery.java) | ✅ verified (via Maven Wrapper) |
| Python | [`invocation/run_xquery.py`](invocation/run_xquery.py) | needs `pip install saxonche` |
| Python | [`invocation/run_xquery.py`](invocation/run_xquery.py) | standalone via repo venv (`pip install -e .`) |
| BaseX | see below | needs BaseX install |

The Java runner is standalone & cross-platform via the committed Maven Wrapper
Expand Down
33 changes: 24 additions & 9 deletions XSD_Validation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,20 @@ Two realities every example must deal with:
2. **Relative import.** From release 4.2.9 on, `FundsXML.xsd` imports
`xmldsig-core-schema.xsd` via a *relative* path; both files must sit together.

`tools/fetch-schema.sh <version>` resolves both (proxy-aware via curl) and
materializes the released schema into `.schema-cache/<version>/`. The examples
validate against that materialized release. Run it once up front:
**Schema resolution (same convention in every stack):**
`$FUNDSXML_SCHEMA_DIR` (a hand-placed copy — offline / corporate-network
escape hatch) → `.schema-cache/<version>/` → download from the official
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:

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

## Security
Expand All @@ -40,8 +48,8 @@ Every example disables external entity resolution / DTD loading
| Stack | Script | API | Runnable on this box |
|-------|--------|-----|----------------------|
| CLI | [`cli/validate.sh`](cli/validate.sh) | `xmllint` (+ Saxon note) | ✅ |
| Python | [`python/validate.py`](python/validate.py) | `lxml.etree.XMLSchema` | ✅ |
| Java | [`java/XsdValidate.java`](java/XsdValidate.java) | `javax.xml.validation` | ✅ (single-file, JDK 11+) |
| 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 |
| PowerShell | [`powershell/Validate-FundsXml.ps1`](powershell/Validate-FundsXml.ps1) | `System.Xml.Schema` | needs PowerShell |

Expand All @@ -50,8 +58,15 @@ invalid, prints errors to stderr.

## Quick check (positive + negative)

Python (standalone — resolves the schema itself; `pip install -e .` once, see
the repo `pyproject.toml`):

```bash
tools/fetch-schema.sh 4.2.9
XSD_Validation/cli/validate.sh 4.2.9 FundsXML_Files/4.2.9/positions/Mixed-Fund_Positions.xml # exit 0
XSD_Validation/cli/validate.sh 4.2.9 tests/fixtures/invalid/xsd-invalid_Positions.xml # exit 1
python -m venv .venv && . .venv/bin/activate && pip install -e . # Windows: .venv\Scripts\activate
python XSD_Validation/python/validate.py 4.2.9 FundsXML_Files/4.2.9/positions/Mixed-Fund_Positions.xml # exit 0
python XSD_Validation/python/validate.py 4.2.9 tests/fixtures/invalid/xsd-invalid_Positions.xml # exit 1
```

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.
29 changes: 11 additions & 18 deletions XSD_Validation/python/validate.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
#!/usr/bin/env python3
"""XSD validation in Python via lxml.

Usage: python XSD_Validation/python/validate.py <version> <xml-file>
Standalone & cross-platform — no bash, no prior tool step (works on Windows).
After `pip install -e .` (see pyproject.toml):

python XSD_Validation/python/validate.py <version> <xml-file>
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 program itself via the shared
`fundsxml_schema` resolver: $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 that
FundsXML 4.2.9+ imports). The official release stays the source of truth.

Security: the XML parser is hardened against XXE / entity-expansion
(no_network=True, resolve_entities=False, no DTD load). FundsXML needs none
of those features.
"""
import subprocess
import sys
from pathlib import Path

from lxml import etree

REPO_ROOT = Path(__file__).resolve().parents[2]


def ensure_schema(version: str) -> Path:
schema = REPO_ROOT / ".schema-cache" / version / "FundsXML.xsd"
if not schema.is_file():
print(f"schema not cached; fetching official release {version}...",
file=sys.stderr)
subprocess.run([str(REPO_ROOT / "tools" / "fetch-schema.sh"), version],
check=True, stdout=subprocess.DEVNULL)
return schema
from fundsxml_schema import resolve_schema


def main() -> int:
Expand All @@ -37,7 +30,7 @@ def main() -> int:
return 2
version, xml_path = sys.argv[1], sys.argv[2]

schema_path = ensure_schema(version)
schema_path = resolve_schema(version)

# Hardened parser: no network, no entity resolution, no huge-tree blowups.
safe = etree.XMLParser(no_network=True, resolve_entities=False,
Expand Down
Loading