- Python 76.1%
- HTML 10.2%
- CSS 6.9%
- JavaScript 6.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| scripts | ||
| src/prettyxsd | ||
| tests | ||
| .gitignore | ||
| .python-version | ||
| PLAN.md | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
prettyxsd
Generate a browsable, static HTML reference from XML Schema Definition (XSD) files. The output includes an element index, a searchable global-element sidebar, pages for global elements and named types, links between declarations, and local CSS/JavaScript assets. The generated site can be opened locally or hosted as a static site; it does not require a web server or network connection.
Requirements
- Python 3.14 or newer
uv
Install and run
From this directory, sync the project and run the CLI with uv:
uv sync
uv run prettyxsd --input path/to/schema.xsd --output path/to/site
Use --input more than once to provide multiple schema entry points:
uv run prettyxsd \
--input schemas/main.xsd \
--input schemas/another-entry-point.xsd \
--output docs/
Use --title to set the documentation title shown in the sidebar and browser tab (default prettyxsd), and --logo to replace the default mark at the top left with an image file, which is copied into the site's assets/ directory:
uv run prettyxsd --input schemas/main.xsd --output docs/ --title "NeTEx" --logo netex-logo.png
The command recursively resolves relative local xsd:include and xsd:import schema locations. Remote schema locations are intentionally not fetched. Missing local dependencies and remote locations are reported as notices in the terminal and generated site; generation continues with the schemas it could load.
Every schema file gets a highlighted source page under src/, at its path relative to the deepest directory holding all the loaded schemas. To link those source pages to their files on GitHub, pass the GitHub URL for the directory containing the first input XSD. For example, NeTEx NX.xsd is in xsd/, so use:
uv run prettyxsd \
--input ../NeTEx/xsd/NX.xsd \
--output docs/netex \
--github-base-url https://github.com/TransmodelEcosystem/NeTEx/tree/v2.0/xsd
Each source page under that directory then has a "View on GitHub" link to the file's GitHub blob URL, which follows the line the page is showing. Everything else (element, type and index pages, and the trees on them) links to the site's own source pages, never to GitHub. When the schemas come from several repositories, give --github-base-url DIR=URL once per repository's local directory; a schema takes the URL of the most specific directory that holds it:
uv run prettyxsd \
--variant siri=SIRI/xsd/siri.xsd --variant opra=OpRa/xsd/opra.xsd \
--github-base-url SIRI=https://github.com/TransmodelEcosystem/SIRI/tree/v2.3 \
--github-base-url OpRa=https://github.com/TransmodelEcosystem/OpRa/tree/1.0rc \
--output docs
To link NeTEx declarations to the Transmodel 6.2 concepts they implement, pass --transmodel:
uv run prettyxsd --input ../NeTEx/xsd/NX.xsd --output docs/netex --transmodel
NeTEx names its elements after Transmodel concepts in CamelCase, so StopPlace is the STOP PLACE concept and TypeOfLine is TYPE OF LINE. With --transmodel, each element and type in the NeTEx namespace whose name matches a Transmodel class gets a "Transmodel concept" link under its heading to that class's page. Types match after their structure suffix is removed (StopPlace_VersionStructure, TypeOfLine_ValueStructure), view elements match the concept they view (StopPlaceView), and a few names that differ from Transmodel's are mapped by hand (Timeband is TIME BAND). Concept names written in capitals in their documentation are linked too, as in "a STOP PLACE" or "SITE PATH LINKs": the longest matching concept name wins, lower-case plural endings are read as the singular (ENTITies is ENTITY, PLACEs is PLACE), and concepts Transmodel has renamed are found by their other name. Text in code and existing links is left alone. Use --transmodel-namespace (repeatable) to link declarations in other namespaces instead. The concept catalog is bundled with prettyxsd, so generation needs no network access; rebuild it from the published model with uv run python scripts/update_transmodel_catalog.py.
To publish the Transmodel model itself alongside the schemas, pass --transmodel-pages (it implies --transmodel). The site gets a transmodel/ section with an index of the model's packages, a page for each of its 1,633 classes and a page for each of its diagrams, and the Transmodel links on element and type pages, and in their documentation, go to these pages instead of transmodel-cen.eu. A class page shows the class's definition, attributes with their types, multiplicities and definitions, supertypes and subtypes, associations with their roles and multiplicities, the diagrams it appears on, the NeTEx elements and types that implement it, and a drawing of its neighbourhood in which every class, attribute and relationship is a link. Diagrams with up to 20 classes and 30 relationships are laid out by Graphviz; larger ones show EA's own image from transmodel-cen.eu, with its clickable areas pointed at these pages. Everything is taken from the Enterprise Architect XMI export that transmodel-cen.eu publishes, and the drawings are made in advance, so generation needs neither Graphviz nor network access. Rebuild the model data and drawings with uv run python scripts/update_transmodel_model.py, which needs Graphviz's dot (or $DOT) and network access.
To document several entry schemas that share most of their files, give each one with --variant NAME=XSD instead of --input. They are loaded together, so element, type, source and example pages are written once and shared, and each variant gets a start page with its own element tree at NAME/index.html (index.html lists the variants). --variant-title NAME=TITLE sets a variant's title (default --title). The sidebar, which has a variant switcher, lists only the declarations the reader's current variant loads, and links to pages outside it are muted. When an entry schema declares its own version of a name another variant also declares (NeTEx's timetable subset has its own PublicationDelivery), that variant gets its own pages for those names under NAME/pages/:
uv run prettyxsd \
--variant nx=../NeTEx/xsd/NX.xsd --variant-title "nx=NeTEx 2.0" \
--variant ifopt=../NeTEx/xsd/ifopt.xsd --variant-title "ifopt=IFOPT" \
--output docs --redirects-caddyfile docs/redirects.caddy
A site built before pages were shared had each variant's pages under NAME/elements/.... --redirects-caddyfile FILE writes a Caddyfile snippet that permanently redirects those URLs to the shared pages; import it inside the site's block in the main Caddyfile. Use --site-path when the site isn't served from /.
Every declaration (elements, types, attributes, child elements/tree nodes, and enumeration values) shows its defining schema file and line number (e.g. netex_transportOrganisation_version.xsd:287), linked to that line on the file's source page.
To publish example instance documents alongside the reference, pass a directory of XML files with --examples. Every .xml file under it (recursively) gets a page under examples/ with its element names linked to their declarations, and each global element's page lists the examples that use it, each linked to the line of its first occurrence:
uv run prettyxsd --input ../NeTEx/xsd/NX.xsd --output docs/netex --examples ../NeTEx/examples
Elements are matched against the schema's content model, following the parent element's type, base types, groups and substitution groups, so a local element such as a Name inside a Line links to the row declaring it on the Line page. Elements the content model doesn't explain fall back to a global element of the same name. An example page stores the document as plain text plus a compact list of link targets per start tag; site.js highlights it and adds the links in the browser, which keeps the generated pages close to the size of the examples themselves.
Open docs/index.html in a browser to browse the generated documentation. The index tree is rooted in global elements declared by the supplied entry-point schema files; local structure is expanded and named model groups (xsd:group) are kept as labelled branches, while referenced global elements link to their detail pages. Declarations brought in only by includes/imports remain available in the reference list and sidebar without overwhelming the top-level tree. This supports organization schemas such as NeTEx NX.xsd, whose Nx element provides a useful browsing hierarchy.
What is documented
- Global elements, with one page per element
- Element documentation rendered as Markdown, including supported inline XSD markup
- Child elements, occurrence ranges, referenced global elements, and types
- Content inherited from base types, collapsed under one "inherited from" block per base type (oldest first) in element and type trees, with an "Expand inherited" toggle; NeTEx-style restrictions that restate their base types' groups are split back up by the type that declares each group, and attributes from base types are listed in a collapsed table naming the type each comes from
- Named model groups, shown as
groupbranches in the content model with the group's own documentation and occurrence range - Anonymous
xsd:choicecompositors with more than one alternative, shown as achoicebranch so alternatives aren't misread as a required sequence - Anonymous
xsd:sequence/xsd:allcompositors that repeat, are optional, or carry their own documentation, shown as a labelled branch instead of being silently flattened away - Attributes, required/optional use, referenced types, documentation, and any
default/fixedvalue - Elements with a
default/fixedvalue, shown inline in the content-model tree - Named simple and complex types, with links from elements and child declarations
- Simple type restrictions (
minInclusive,maxInclusive,minExclusive,maxExclusive,minLength,maxLength,length,totalDigits,fractionDigits,whiteSpace,pattern), listed alongside the base type they narrow - Abstract elements and types, flagged with an
abstractbadge wherever they appear (their own page, substitution-group listings, content-model trees, and the global element list) since they cannot be used directly in an instance document - Known subtypes of a complex or simple type ("Derived types"), linked from the base type's page
xsd:listsimple types, showing the linked item type ("Item type")xsd:unionsimple types, showing each linked member type ("Member types"); inline anonymous member types are described in place (e.g. their restriction base and enumeration values) since they have no page of their own- The full substitution-group ancestry chain for an element ("Substitution group chain"), not just its immediate parent, so a deeply nested element like NeTEx's
Operatorshows its whole path back to the ultimate abstract root (Operator → TransportOrganisation_Dummy → Organisation_Dummy → DataManagedObject → EntityInVersion) - The full set of concrete elements reachable transitively through an element's substitution-group descendants ("Concrete substitution alternatives"), so an abstract root element's page lists every element that can actually be used in its place in an instance document, not just its immediate substitution members
- Identity constraints (
xsd:key,xsd:keyref,xsd:unique) declared directly on an element, listed in an "Identity constraints" table with their selector/field XPath expressions and documentation; akeyref'sreferis resolved and linked to thekey/uniqueit targets (which may be on the same or a different element's page), falling back to plain text when the target can't be resolved
Generated detail pages are grouped under elements/<namespace>/<name>.html and types/<namespace>/<name>.html. The path segments are readable slugs; a short hash is added only when distinct names would otherwise collide. Shared assets remain under assets/, and nested pages use relative links so the site works locally and when hosted under a subdirectory.
XSD documentation is rendered as Markdown, with supported inline XSD markup preserved. The generated HTML is then passed through a conservative allowlist sanitizer: unsafe elements are not emitted as HTML, and unsafe link schemes are removed. Legacy inline option markers such as for:- …, or- … are formatted as Markdown lists.
Development
Dependencies and lockfile are managed with uv (do not use pip):
uv sync
uv run python -m unittest discover -s tests -v
See PLAN.md for the implementation scope and design decisions.