Metadata-Version: 2.4
Name: linkml-xsd
Version: 0.2.0
Summary: Convert LinkML schemas to XSD 1.0/1.1 and Schematron.
Author-email: Christian Gutknecht <christian.gutknecht@glue.ch>
License: MIT
Project-URL: Homepage, https://gitlab.com/glueswe/public/linkml
Project-URL: Repository, https://gitlab.com/glueswe/public/linkml
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Code Generators
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: click>=8.0
Requires-Dist: lxml>=4.9
Requires-Dist: pyyaml>=6.0
Requires-Dist: linkml-runtime>=1.8
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: hypothesis>=6.0; extra == "dev"
Requires-Dist: xmlschema>=2.0; extra == "dev"
Requires-Dist: ruff>=0.4.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Requires-Dist: types-pyyaml; extra == "dev"
Requires-Dist: lxml-stubs; extra == "dev"
Requires-Dist: pre-commit>=3.0; extra == "dev"
Dynamic: license-file

# linkml-to-xsd

[![pipeline](https://gitlab.com/glueswe/public/linkml/badges/main/pipeline.svg)](https://gitlab.com/glueswe/public/linkml/-/pipelines)
[![coverage](https://gitlab.com/glueswe/public/linkml/badges/main/coverage.svg)](https://gitlab.com/glueswe/public/linkml/-/pipelines)
[![release](https://gitlab.com/glueswe/public/linkml/-/badges/release.svg)](https://gitlab.com/glueswe/public/linkml/-/releases)
[![python](https://img.shields.io/badge/python-3.10%E2%80%933.14-blue)](pyproject.toml)
[![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)

> **Status 0.x** — the mapping is exercised by 200+ tests and at parity on a
> real production schema, but the Python API may still change before 1.0.

A Python library and CLI that converts [LinkML](https://linkml.io) schemas into
[XML Schema (XSD)](https://www.w3.org/XML/Schema) — and, for constraints XSD
cannot express, [Schematron](https://schematron.com). Built on
[`linkml-runtime`](https://github.com/linkml/linkml-runtime)'s `SchemaView`, so
`imports`, `mixins`, `slot_usage` and inlining are resolved by the reference
implementation rather than re-guessed.

## Features

- **XSD 1.0** output: classes → `xs:complexType` + global element; `is_a` →
  `xs:extension` (UPA-safe); mixins flattened; enums, user types with facets
  (`pattern`, `minimum_value`, `maximum_value`); cardinality.
- **References vs. inlining** follow LinkML semantics: a non-inlined class-range
  slot with an identifier becomes a *reference*; `inlined`/`inlined_as_list`
  produce nested elements.
- **Identity constraints**: `identifier` → `xs:key`, `unique_keys` → `xs:unique`,
  references → `xs:keyref` (real referential integrity).
- **Value constraints**: `@default` from literal `ifabsent`, `@fixed` from
  `equals_string`/`equals_number`.
- **Documentation & metadata**: `description` → `xs:documentation`,
  `class_uri`/`slot_uri`/`enum_uri`/`meaning` → `xs:appinfo`.
- **XSD 1.1** (opt-in): class `rules` also emitted as `xs:assert`.
- **Schematron**: class `rules` (precondition ⇒ postcondition) and boolean
  combinators (`any_of`/`all_of`/`exactly_one_of`/`none_of`) as XPath assertions.
- **Profiles**: the eCH-0296 / Akoma Ntoso annotation vocabulary
  (`xml_element`, `xml_attribute`, `xml_namespace`, `xsd_mixed`,
  `xsd_content_model`) via `--profile akn` — proven at parity on the real Fedlex
  SR-101.
- Example-XML generation and XSD validation helpers.

## Installation

From a checkout:

```bash
pip install -e ".[dev]"
```

Released versions are published to PyPI and this project's GitLab package
registry on every `vX.Y.Z` tag:

```bash
# PyPI (recommended)
pip install linkml-xsd

# GitLab package registry
pip install linkml-xsd \
  --index-url https://gitlab.com/api/v4/projects/83894258/packages/pypi/simple
```

Requires Python ≥ 3.10 (CI tests 3.10 and 3.14). Versions are derived
from git tags (`setuptools_scm`) — no manual version bumps.

**No Python at hand** (Java/XSLT toolchains)? Use the Docker image published
on every release:

```bash
docker run --rm -v "$PWD":/work \
  registry.gitlab.com/glueswe/public/linkml:latest /work/schema.yaml -o /work/schema.xsd
```

## Usage

### CLI

Two entry points: **`gen-xsd`** (the [linkml generator
convention](https://linkml.io/linkml/generators/), like `gen-json-schema`; see
[`docs/generators/xsd.md`](docs/generators/xsd.md)) and the richer `linkml2xsd`
group (`convert` / `example` / `validate`).

```bash
# linkml-style generator CLI
gen-xsd schema.yaml -o schema.xsd
gen-xsd schema.yaml --xsd-version 1.1 --schematron schema.sch

# Standard LinkML → XSD (targetNamespace derived from the schema id/default_prefix)
linkml2xsd convert schema.yaml -o schema.xsd

# eCH-0296 / Akoma Ntoso profile + a companion Schematron for rules
linkml2xsd convert schema.yaml -o schema.xsd --profile akn --schematron schema.sch

# Example XML instance / validate an instance against an XSD
linkml2xsd example schema.yaml -o example.xml
linkml2xsd validate example.xml --xsd schema.xsd
```

### Python API

```python
from linkml_to_xsd import XSDGenerator, SchematronGenerator, ConversionOptions

options = ConversionOptions(profile="akn", xsd_version="1.1", emit_appinfo=True)
xsd = XSDGenerator("schema.yaml", options=options).generate()

sch_gen = SchematronGenerator("schema.yaml", options=options)
sch = sch_gen.generate() if sch_gen.has_rules() else None
```

`XSDGenerator` accepts a file path, inline YAML, a dict, a `SchemaDefinition` or a
`SchemaFrontend`.

## Options (`ConversionOptions`)

Every genuine mapping choice is a validated field with a default:

| Option | Default | Effect |
|--------|---------|--------|
| `xsd_version` | `"1.0"` | `"1.1"` also emits `xs:assert` for rules |
| `emit_schematron` | `False` | (API flag; the CLI uses `--schematron`) |
| `inlining` | `"linkml"` | or `always_inline` / `always_reference` |
| `reference_style` | `"identifier"` | or `uri` (`key`/`idref` planned) |
| `emit_value_constraints` | `True` | facets / `@fixed` |
| `emit_identity_constraints` | `True` | `xs:key`/`unique`/`keyref` |
| `emit_documentation` / `emit_appinfo` | `True` / `False` | annotations |
| `multilingual_documentation` | `False` | split `[en] … [de] …`-tagged descriptions into per-language `xs:documentation` |
| `target_namespace` | `None` | override the derived namespace |
| `profile` | `None` | e.g. `"akn"` |

## Architecture

```
src/linkml_to_xsd/
  config.py        # ConversionOptions — one toggle per mapping decision
  frontend/        # SchemaFrontend over linkml-runtime SchemaView
  emit/            # XSDGenerator (+ naming, typemap)
  schematron/      # SchematronGenerator
  rules.py         # shared rule → XPath compiler (Schematron + xs:assert)
  profile/         # base (no-op) + akn (eCH-0296 / Akoma Ntoso)
  cli.py  xml_generator.py
```

See [`docs/adr/`](docs/adr/) for architectural decisions (ADR 003 records the
move to `SchemaView`) and [`docs/LINKML_XSD_MAPPING.md`](docs/LINKML_XSD_MAPPING.md)
for the full LinkML ↔ XSD element mapping and current status.

## Testing

Multi-tier and documented in [`tests/README.md`](tests/README.md);
[`docs/TEST_COVERAGE.md`](docs/TEST_COVERAGE.md) is the enforced feature × engine
matrix.

| Tier | Checks |
|------|--------|
| **Compliance** (`tests/compliance/*`) | one feature per case → XSD compiles, matches its golden, accepts/rejects the right instances |
| **Coverage** | every case is feature-tagged; baseline features stay covered |
| **Corpus** | real `kitchen_sink.yaml` + `meta.yaml` convert and compile |
| **Property** (Hypothesis) | random supported schemas always compile |
| **XSTS reference** | inventories XSD constructs LinkML can never emit |

```bash
pytest
ruff check src tests && ruff format --check src tests
mypy src/linkml_to_xsd/

python tools/fetch_corpora.py [--xsts]   # opt-in corpora (git-ignored)
python tools/gen_golden.py               # refresh golden XSDs after a change
python tools/coverage_matrix.py          # regenerate docs/TEST_COVERAGE.md
```

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) — most improvements are data, not code:
a new feature mapping is one compliance case + one option + one emitter change.

## License

MIT
