From 56ad4b0c67c38592a677c024d6ca90b523eafc82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Urs=20St=C3=B6ckli?= Date: Tue, 14 Apr 2026 17:33:35 +0200 Subject: [PATCH 1/5] Generate table files as html. --- generated/xcore/contab/test.html | 115 ++--- .../complexTypes/AlternativeNameType.html | 191 ++++---- .../complexTypes/AlternativeTextType.html | 113 +++-- .../test/complexTypes/CentroidType.html | 87 ++-- .../test/complexTypes/ExtensionsType.html | 117 +++-- .../contab/test/complexTypes/KeyListType.html | 87 ++-- .../test/complexTypes/KeyValueType.html | 109 +++-- .../complexTypes/LocalisedStringType.html | 93 ++-- .../contab/test/complexTypes/QuayType.html | 165 ++++--- .../test/complexTypes/StopPlaceType.html | 457 +++++++++--------- .../complexTypes/TopographicPlaceRefType.html | 109 +++-- .../test/complexTypes/ValidBetweenType.html | 109 +++-- .../xcore/contab/test/elements/StopPlace.html | 57 ++- tools/pycore/pycore.py | 411 ++++++++++++++++ tools/xcore/xquery/xco-html.xqm | 46 +- 15 files changed, 1419 insertions(+), 847 deletions(-) create mode 100644 tools/pycore/pycore.py diff --git a/generated/xcore/contab/test.html b/generated/xcore/contab/test.html index 45b20ed..ccc221e 100644 --- a/generated/xcore/contab/test.html +++ b/generated/xcore/contab/test.html @@ -1,70 +1,57 @@ - - + - NeTEx + NeTEx + + + + + - +
- +
+

1.18. The complex type complexType[test:StopPlaceType]/quays#complexType (typedef-1.6)

+
+ + + + + + + + + + + + + + + + + + + + + + + +
+

complexType[test:StopPlaceType]
  /quays #complexType
  (typedef-1.6)

+
+

+
+

-

+
+

test:Quay

+
+

0:*

+
+

+test:QuayType

+
+

+
+
+
\ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/AlternativeNameType.html b/generated/xcore/contab/test/complexTypes/AlternativeNameType.html index 65f593e..ff6e453 100644 --- a/generated/xcore/contab/test/complexTypes/AlternativeNameType.html +++ b/generated/xcore/contab/test/complexTypes/AlternativeNameType.html @@ -1,9 +1,20 @@ - -
-

1.2. The complex type test:AlternativeNameType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.2. The complex type test:AlternativeNameType

+
+
+ @@ -11,86 +22,88 @@

1.2. The complex type test:AlternativeNameType

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

test:AlternativeNameType

-
-

-
-

-

-
-

@id

-
-

1:1

-
-

-
-

@version

-
-

1:1

-
-

-
-

test:NameType

-
-

1:1

-
-

-
-

test:TypeOfName

-
-

1:1

-
-

-
-

test:Name

-
-

1:1

-
-

>test:LocalisedStringType

-
-

-
-
-
\ No newline at end of file + + + +

test:AlternativeNameType

+ + +

+ + +

-

+ + + + + +

@id

+ + +

1:1

+ + + +

+ + + + + +

@version

+ + +

1:1

+ + + +

+ + + + + +

test:NameType

+ + +

1:1

+ + + +

+ + + + + +

test:TypeOfName

+ + +

1:1

+ + + +

+ + + + + +

test:Name

+ + +

1:1

+ + +

>test:LocalisedStringType

+ + +

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/AlternativeTextType.html b/generated/xcore/contab/test/complexTypes/AlternativeTextType.html index 81c2320..176d579 100644 --- a/generated/xcore/contab/test/complexTypes/AlternativeTextType.html +++ b/generated/xcore/contab/test/complexTypes/AlternativeTextType.html @@ -1,9 +1,20 @@ - -
-

1.3. The complex type test:AlternativeTextType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.3. The complex type test:AlternativeTextType

+
+
+ @@ -11,47 +22,49 @@

1.3. The complex type test:AlternativeTextType

- - - - - - - - - - - - - - - - - - - - - -
-

test:AlternativeTextType

-
-

-
-

-

-
-

@attributeName

-
-

1:1

-
-

-
-

test:Text

-
-

1:1

-
-

>test:LocalisedStringType

-
-

-
-
-
\ No newline at end of file + + + +

test:AlternativeTextType

+ + +

+ + +

-

+ + + + + +

@attributeName

+ + +

1:1

+ + + +

+ + + + + +

test:Text

+ + +

1:1

+ + +

>test:LocalisedStringType

+ + +

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/CentroidType.html b/generated/xcore/contab/test/complexTypes/CentroidType.html index b257998..fbeb55f 100644 --- a/generated/xcore/contab/test/complexTypes/CentroidType.html +++ b/generated/xcore/contab/test/complexTypes/CentroidType.html @@ -1,9 +1,20 @@ - -
-

1.4. The complex type test:CentroidType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.4. The complex type test:CentroidType

+
+
+ @@ -11,34 +22,36 @@

1.4. The complex type test:CentroidType

- - - - - - - - - - - - - - -
-

test:CentroidType

-
-

-
-

-

-
-

test:Location

-
-

1:1

-
-

+local-type: typedef-1.3

-
-

-
-
-
\ No newline at end of file + + + +

test:CentroidType

+ + +

+ + +

-

+ + + + + +

test:Location

+ + +

1:1

+ + +

+local-type: typedef-1.3

+ + +

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/ExtensionsType.html b/generated/xcore/contab/test/complexTypes/ExtensionsType.html index 56b6c57..df08c6a 100644 --- a/generated/xcore/contab/test/complexTypes/ExtensionsType.html +++ b/generated/xcore/contab/test/complexTypes/ExtensionsType.html @@ -1,9 +1,20 @@ - -
-

1.5. The complex type test:ExtensionsType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.5. The complex type test:ExtensionsType

+
+
+ @@ -11,49 +22,51 @@

1.5. The complex type test:ExtensionsType

- - - - - - - - - - - - - - - - - - - - - -
-

test:ExtensionsType

-
-

-
-

-

-
-

test:HafasPriority

-
-

0:1

-
-

+local-type: typedef-1.1

-
-

-
-

test:HafasKMInfo

-
-

0:1

-
-

+local-type: typedef-1.2

-
-

-
-
-
\ No newline at end of file + + + +

test:ExtensionsType

+ + +

+ + +

-

+ + + + + +

test:HafasPriority

+ + +

0:1

+ + +

+local-type: typedef-1.1

+ + +

+ + + + + +

test:HafasKMInfo

+ + +

0:1

+ + +

+local-type: typedef-1.2

+ + +

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/KeyListType.html b/generated/xcore/contab/test/complexTypes/KeyListType.html index dcd6446..76a62f6 100644 --- a/generated/xcore/contab/test/complexTypes/KeyListType.html +++ b/generated/xcore/contab/test/complexTypes/KeyListType.html @@ -1,9 +1,20 @@ - -
-

1.6. The complex type test:KeyListType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.6. The complex type test:KeyListType

+
+
+ @@ -11,34 +22,36 @@

1.6. The complex type test:KeyListType

- - - - - - - - - - - - - - -
-

test:KeyListType

-
-

-
-

-

-
-

test:KeyValue

-
-

1:*

-
-

+test:KeyValueType

-
-

-
-
-
\ No newline at end of file + + + +

test:KeyListType

+ + +

+ + +

-

+ + + + + +

test:KeyValue

+ + +

1:*

+ + +

+test:KeyValueType

+ + +

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/KeyValueType.html b/generated/xcore/contab/test/complexTypes/KeyValueType.html index 6d16961..07962df 100644 --- a/generated/xcore/contab/test/complexTypes/KeyValueType.html +++ b/generated/xcore/contab/test/complexTypes/KeyValueType.html @@ -1,9 +1,20 @@ - -
-

1.7. The complex type test:KeyValueType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.7. The complex type test:KeyValueType

+
+
+ @@ -11,45 +22,47 @@

1.7. The complex type test:KeyValueType

- - - - - - - - - - - - - - - - - - - - - -
-

test:KeyValueType

-
-

-
-

-

-
-

test:Key

-
-

1:1

-
-

-
-

test:Value

-
-

1:1

-
-

-
-
-
\ No newline at end of file + + + +

test:KeyValueType

+ + +

+ + +

-

+ + + + + +

test:Key

+ + +

1:1

+ + + +

+ + + + + +

test:Value

+ + +

1:1

+ + + +

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/LocalisedStringType.html b/generated/xcore/contab/test/complexTypes/LocalisedStringType.html index b381f1e..2ea697d 100644 --- a/generated/xcore/contab/test/complexTypes/LocalisedStringType.html +++ b/generated/xcore/contab/test/complexTypes/LocalisedStringType.html @@ -1,9 +1,20 @@ - -
-

1.8. The complex type test:LocalisedStringType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.8. The complex type test:LocalisedStringType

+
+
+ @@ -11,37 +22,39 @@

1.8. The complex type test:LocalisedStringType

- - - - - - - - - - - - - - - - - -
-

test:LocalisedStringType

-
-

xs:string

-
-

-

-
-

Inherited content is followed by own content:

-
-

@lang

-
-

1:1

-
-

-
-
-
\ No newline at end of file + + + +

test:LocalisedStringType

+ + +

xs:string

+ + +

-

+ + + + +

Inherited content is followed by own content:

+ + + + + +

@lang

+ + +

1:1

+ + + +

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/QuayType.html b/generated/xcore/contab/test/complexTypes/QuayType.html index 755f185..ee15c12 100644 --- a/generated/xcore/contab/test/complexTypes/QuayType.html +++ b/generated/xcore/contab/test/complexTypes/QuayType.html @@ -1,9 +1,20 @@ - -
-

1.9. The complex type test:QuayType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.9. The complex type test:QuayType

+
+
+ @@ -11,73 +22,75 @@

1.9. The complex type test:QuayType

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

test:QuayType

-
-

-
-

The QUAYs contained in the STOP PLACE, that is platforms, jetties, bays, taxi ranks, and other points of physical access to VEHICLEs.

-
-

@id

-
-

1:1

-
-

-
-

@version

-
-

1:1

-
-

-
-

test:PublicCode

-
-

1:1

-
-

-
-

test:keyList

-
-

0:1

-
-

+test:KeyListType

-
-

-
-
-
\ No newline at end of file + + + +

test:QuayType

+ + +

+ + +

The QUAYs contained in the STOP PLACE, that is platforms, jetties, bays, taxi ranks, and other points of physical access to VEHICLEs.

+ + + + + +

@id

+ + +

1:1

+ + + +

+ + + + + +

@version

+ + +

1:1

+ + + +

+ + + + + +

test:PublicCode

+ + +

1:1

+ + + +

+ + + + + +

test:keyList

+ + +

0:1

+ + +

+test:KeyListType

+ + +

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/StopPlaceType.html b/generated/xcore/contab/test/complexTypes/StopPlaceType.html index 3456c30..84dc90d 100644 --- a/generated/xcore/contab/test/complexTypes/StopPlaceType.html +++ b/generated/xcore/contab/test/complexTypes/StopPlaceType.html @@ -1,9 +1,20 @@ - -
-

1.10. The complex type test:StopPlaceType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.10. The complex type test:StopPlaceType

+
+
+ @@ -11,219 +22,221 @@

1.10. The complex type test:StopPlaceType

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

test:StopPlaceType

-
-

-
-

-

-
-

@id

-
-

1:1

-
-

-
-

@version

-
-

1:1

-
-

-
-

test:ValidBetween

-
-

1:1

-
-

+test:ValidBetweenType

-
-

Validity of the StopPlace

-
-

test:alternativeTexts

-
-

0:1

-
-

+local-type: typedef-1.4

-
-

Alternative texts for the StopPlace

-
-

test:keyList

-
-

0:1

-
-

+test:KeyListType

-
-

KEY LIST with the KEY VALUEs related to the STOP PLACE. SKI use KeyValues: one for the Didok number one for the SLOID For delivery to SKI only one Value is necessary.

-
-

test:Extensions

-
-

0:1

-
-

+test:ExtensionsType

-
-

See description of extensions

-
-

test:Name

-
-

1:1

-
-

The name of the StopPlace

-
-

test:ShortName

-
-

0:1

-
-

>test:LocalisedStringType

-
-

Description of TYPE OF VALUE. Is used to transmit the abbreviation of the StopPlace. There is not one abbreviation for all StopPlaces

-
-

test:PrivateCode

-
-

0:1

-
-

Private Code of STOP PLACE. Field must be filled. In Switzerland it is the DiDok number.

-
-

test:Centroid

-
-

0:1

-
-

+test:CentroidType

-
-

Global or national location of STOP PLACE.

-
-

test:alternativeNames

-
-

0:1

-
-

+local-type: typedef-1.5

-
-

Alternative names for SITE ELEMENT. We will also use these for synonyms. From INFO+ the synonyms are used on the StopPlace.

-
-

test:TopographicPlaceRef

-
-

0:1

-
-

test:TopographicPlaceRefType

-
-

Reference to TopographicPlace. Link to TopographicPlace of type county or country

-
-

test:Weighting

-
-

0:1

-
-

Default relative weighting to be used for stop place. The STOP PLACE element WEIGHTING basically accomplishes this feature but only allows the following values: noInterchange interchangeAllowed recommendedInterchange preferredInterchange. To incorporate the desired value range, we will add an EXTENSION element “HafasPriority” that contains the full information.

-
-

test:quays

-
-

0:1

-
-

+local-type: typedef-1.6

-
-

The QUAYs contained in the STOP PLACE, that is platforms, jetties, bays, taxi ranks, and other points of physical access to VEHICLEs.

-
-
-
\ No newline at end of file + + + +

test:StopPlaceType

+ + +

+ + +

-

+ + + + + +

@id

+ + +

1:1

+ + + +

+ + + + + +

@version

+ + +

1:1

+ + + +

+ + + + + +

test:ValidBetween

+ + +

1:1

+ + +

+test:ValidBetweenType

+ + +

Validity of the StopPlace

+ + + + + +

test:alternativeTexts

+ + +

0:1

+ + +

+local-type: typedef-1.4

+ + +

Alternative texts for the StopPlace

+ + + + + +

test:keyList

+ + +

0:1

+ + +

+test:KeyListType

+ + +

KEY LIST with the KEY VALUEs related to the STOP PLACE. SKI use KeyValues: one for the Didok number one for the SLOID For delivery to SKI only one Value is necessary.

+ + + + + +

test:Extensions

+ + +

0:1

+ + +

+test:ExtensionsType

+ + +

See description of extensions

+ + + + + +

test:Name

+ + +

1:1

+ + + +

The name of the StopPlace

+ + + + + +

test:ShortName

+ + +

0:1

+ + +

>test:LocalisedStringType

+ + +

Description of TYPE OF VALUE. Is used to transmit the abbreviation of the StopPlace. There is not one abbreviation for all StopPlaces

+ + + + + +

test:PrivateCode

+ + +

0:1

+ + + +

Private Code of STOP PLACE. Field must be filled. In Switzerland it is the DiDok number.

+ + + + + +

test:Centroid

+ + +

0:1

+ + +

+test:CentroidType

+ + +

Global or national location of STOP PLACE.

+ + + + + +

test:alternativeNames

+ + +

0:1

+ + +

+local-type: typedef-1.5

+ + +

Alternative names for SITE ELEMENT. We will also use these for synonyms. From INFO+ the synonyms are used on the StopPlace.

+ + + + + +

test:TopographicPlaceRef

+ + +

0:1

+ + +

test:TopographicPlaceRefType

+ + +

Reference to TopographicPlace. Link to TopographicPlace of type county or country

+ + + + + +

test:Weighting

+ + +

0:1

+ + + +

Default relative weighting to be used for stop place. The STOP PLACE element WEIGHTING basically accomplishes this feature but only allows the following values: noInterchange interchangeAllowed recommendedInterchange preferredInterchange. To incorporate the desired value range, we will add an EXTENSION element “HafasPriority” that contains the full information.

+ + + + + +

test:quays

+ + +

0:1

+ + +

+local-type: typedef-1.6

+ + +

The QUAYs contained in the STOP PLACE, that is platforms, jetties, bays, taxi ranks, and other points of physical access to VEHICLEs.

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/TopographicPlaceRefType.html b/generated/xcore/contab/test/complexTypes/TopographicPlaceRefType.html index e3dbf24..0683d6b 100644 --- a/generated/xcore/contab/test/complexTypes/TopographicPlaceRefType.html +++ b/generated/xcore/contab/test/complexTypes/TopographicPlaceRefType.html @@ -1,9 +1,20 @@ - -
-

1.11. The complex type test:TopographicPlaceRefType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.11. The complex type test:TopographicPlaceRefType

+
+
+ @@ -11,45 +22,47 @@

1.11. The complex type test:TopographicPlaceRefType

- - - - - - - - - - - - - - - - - - - - - -
-

test:TopographicPlaceRefType

-
-

-
-

-

-
-

@ref

-
-

1:1

-
-

-
-

@version

-
-

1:1

-
-

-
-
-
\ No newline at end of file + + + +

test:TopographicPlaceRefType

+ + +

+ + +

-

+ + + + + +

@ref

+ + +

1:1

+ + + +

+ + + + + +

@version

+ + +

1:1

+ + + +

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/complexTypes/ValidBetweenType.html b/generated/xcore/contab/test/complexTypes/ValidBetweenType.html index 82d37da..2b84f90 100644 --- a/generated/xcore/contab/test/complexTypes/ValidBetweenType.html +++ b/generated/xcore/contab/test/complexTypes/ValidBetweenType.html @@ -1,9 +1,20 @@ - -
-

1.12. The complex type test:ValidBetweenType

-
- - + + + + NeTEx + + + + + + + +
+
+

1.12. The complex type test:ValidBetweenType

+
+
+ @@ -11,45 +22,47 @@

1.12. The complex type test:ValidBetweenType

- - - - - - - - - - - - - - - - - - - - - -
-

test:ValidBetweenType

-
-

-
-

-

-
-

test:FromDate

-
-

1:1

-
-

-
-

test:ToDate

-
-

1:1

-
-

-
-
-
\ No newline at end of file + + + +

test:ValidBetweenType

+ + +

+ + +

-

+ + + + + +

test:FromDate

+ + +

1:1

+ + + +

+ + + + + +

test:ToDate

+ + +

1:1

+ + + +

+ + + + + + + + \ No newline at end of file diff --git a/generated/xcore/contab/test/elements/StopPlace.html b/generated/xcore/contab/test/elements/StopPlace.html index 21f61da..ff1cf7d 100644 --- a/generated/xcore/contab/test/elements/StopPlace.html +++ b/generated/xcore/contab/test/elements/StopPlace.html @@ -1,9 +1,20 @@ - -
-

1.1. The toplevel element test:StopPlace

-
- - + + + + NeTEx + + + + + + + +
+
+

1.1. The toplevel element test:StopPlace

+
+
+ @@ -11,19 +22,21 @@

1.1. The toplevel element test:StopPlace

- - - - - - - -
-

test:StopPlace

-
-

+test:StopPlaceType

-
-

-
-
-
\ No newline at end of file + + + +

test:StopPlace

+ + +

+test:StopPlaceType

+ + +

+ + + + + + + + \ No newline at end of file diff --git a/tools/pycore/pycore.py b/tools/pycore/pycore.py new file mode 100644 index 0000000..d72eeb4 --- /dev/null +++ b/tools/pycore/pycore.py @@ -0,0 +1,411 @@ +#!/usr/bin/env python3 +import argparse +import os +import re +import sys +from typing import List, Optional, Tuple + +try: + import xmlschema + from xmlschema.validators import XsdElement, XsdGroup, XsdComplexType, XsdType +except ImportError: + print("Missing dependency: xmlschema. Install with: pip install xmlschema", file=sys.stderr) + sys.exit(1) + +XS_NS = "http://www.w3.org/2001/XMLSchema" + + + + + +def ensure_dir(path: str): + os.makedirs(path, exist_ok=True) + return path + + +def sanitize_namespace(ns: Optional[str]) -> str: + if not ns: + return "no_namespace" + # Remove scheme to keep names shorter but predictable + ns_no_scheme = re.sub(r"^[a-z]+://", "", ns) + # Replace path separators and unsafe chars with underscores + safe = re.sub(r"[^A-Za-z0-9._-]+", "_", ns_no_scheme.strip("/")) + # Collapse multiple underscores + safe = re.sub(r"_+", "_", safe) + return safe or "namespace" + + +def local_name_from_qname(qname: Optional[str]) -> str: + if not qname: + return "" + # Clark notation: {ns}local + if qname.startswith("{"): + return qname.split("}", 1)[1] + # Already a local or prefixed name; best effort + return qname.split(":")[-1] + + +def get_component_namespace(component) -> Optional[str]: + # xmlschema components usually have .target_namespace + return getattr(component, "target_namespace", None) + + +def get_component_local_name(component) -> str: + # Try local_name if present, else from qname/name + ln = getattr(component, "local_name", None) + if ln: + return ln + # name is usually local name for named comps + name = getattr(component, "name", None) or getattr(component, "qname", None) + return local_name_from_qname(name) + + +def read_documentation_from_elem(elem) -> str: + """ + Given the underlying XML element (lxml/Element or ElementTree), extract xs:annotation/xs:documentation text. + """ + if elem is None: + return "" + # elem can be Element or may lack namespace prefixes; rely on Clark-notation + docs = [] + # Walk children; avoid deep nested traversal beyond annotation + for child in list(elem): + # annotation may be in XS namespace + if child.tag.endswith("annotation") or child.tag == f"{{{XS_NS}}}annotation": + for doc in list(child): + if doc.tag.endswith("documentation") or doc.tag == f"{{{XS_NS}}}documentation": + text = (doc.text or "").strip() + if text: + docs.append(text) + return " ".join(docs).strip() + + +def get_best_documentation_for_element(el: XsdElement) -> str: + """ + Try documentation in order: + - The occurrence (local declaration with its own annotation) + - The referenced global element (if ref=) + - The element's type + """ + # 1) From this element node + doc = read_documentation_from_elem(getattr(el, "elem", None)) + if doc: + return doc + + # 2) From referenced global + ref = getattr(el, "ref", None) + if ref is not None: + doc = read_documentation_from_elem(getattr(ref, "elem", None)) + if doc: + return doc + + # 3) From type + el_type = getattr(el, "type", None) + if el_type is not None: + doc = read_documentation_from_elem(getattr(el_type, "elem", None)) + if doc: + return doc + + return "" + + +def get_best_documentation_for_component(component) -> str: + """ + For element/complexType/group: get top-level documentation. + """ + # Primary: this component's elem + doc = read_documentation_from_elem(getattr(component, "elem", None)) + if doc: + return doc + + # For elements, try ref/type like above + if isinstance(component, XsdElement): + return get_best_documentation_for_element(component) + + return "" + + +def is_builtin_type(xsd_type: XsdType) -> bool: + ns = getattr(xsd_type, "target_namespace", None) + return ns == XS_NS + + +def display_type_name(xsd_type: Optional[XsdType]) -> str: + if xsd_type is None: + return "anyType" + # xmlschema XsdType usually has .name (local) and .qname + name = getattr(xsd_type, "name", None) # local name, if named + if is_builtin_type(xsd_type) and name: + return name # e.g., string, int, date + if name: + return name # named complex/simple type + # Anonymous type: try base type or fallback + base_type = getattr(xsd_type, "base_type", None) + if base_type is not None and getattr(base_type, "name", None): + return getattr(base_type, "name") + # Last resort + qname = getattr(xsd_type, "qname", None) + if qname: + return local_name_from_qname(qname) + return "anonymousType" + + +def format_usage(min_occurs: Optional[int], max_occurs) -> str: + def as_int(value, default): + if value is None: + return default + if isinstance(value, str): + if value.lower() == "unbounded": + return "n" + try: + return int(value) + except Exception: + return default + + mino = as_int(min_occurs, 1) + maxo = as_int(max_occurs, 1) + if maxo == "n": + return f"{mino}..n" + return f"{mino}..{maxo}" + + +def type_with_array_suffix(xsd_type: Optional[XsdType], max_occurs) -> str: + tname = display_type_name(xsd_type) + if isinstance(max_occurs, str) and max_occurs.lower() == "unbounded": + return f"{tname}[]" + try: + if int(max_occurs) > 1: + return f"{tname}[]" + except Exception: + pass + return tname + + +def collect_child_elements_from_complex_type(xsd_complex_type: XsdComplexType) -> List[Tuple[str, str, str, str]]: + """ + Returns rows for table: + - Element (local name) + - Usage (min..max) + - Type (with [] suffix if max > 1) + - Description (documentation) + We use deep iteration over content model to include nested groups/choices. + """ + rows = [] + content = getattr(xsd_complex_type, "content_type", None) + if content is None: + return rows + + # xmlschema's content_type for complex types often supports iter_elements() + iter_elems = getattr(content, "iter_elements", None) + if iter_elems is None: + return rows + + # We keep a sequence; duplicates may appear if the model repeats; it's usually fine + for el in iter_elems(): + if not isinstance(el, XsdElement): + continue + name = get_component_local_name(el) + usage = format_usage(getattr(el, "min_occurs", 1), getattr(el, "max_occurs", 1)) + typ = type_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) + desc = get_best_documentation_for_element(el) + rows.append((name, usage, typ, desc)) + return rows + + +def collect_child_elements_from_group(xsd_group: XsdGroup) -> List[Tuple[str, str, str, str]]: + rows = [] + iter_elems = getattr(xsd_group, "iter_elements", None) + if iter_elems is None: + return rows + for el in iter_elems(): + if not isinstance(el, XsdElement): + continue + name = get_component_local_name(el) + usage = format_usage(getattr(el, "min_occurs", 1), getattr(el, "max_occurs", 1)) + typ = type_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) + desc = get_best_documentation_for_element(el) + rows.append((name, usage, typ, desc)) + return rows + + +def render_md_table(rows: List[Tuple[str, str, str, str]]) -> str: + header = "| Element | Usage | Type | Description |\n|---|---|---|---|" + if not rows: + return header + "\n" + lines = [header] + for name, usage, typ, desc in rows: + # Escape pipes in description + safe_desc = desc.replace("|", "\\|") + lines.append(f"| {name} | {usage} | {typ} | {safe_desc} |") + return "\n".join(lines) + "\n" + + +def generate_markdown_for_element(el: XsdElement) -> str: + name = get_component_local_name(el) + top_desc = get_best_documentation_for_component(el) + # Determine element's type and collect its child elements if complex + el_type = getattr(el, "type", None) + rows = [] + if isinstance(el_type, XsdComplexType): + rows = collect_child_elements_from_complex_type(el_type) + # Heading + parts = [f"### Element {name}"] + if top_desc: + parts.append(top_desc.strip()) + parts.append(render_md_table(rows)) + # Optionally: include attributes table (commented out) + # attrs_rows = [] + # if isinstance(el_type, XsdComplexType): + # for attr in getattr(el_type, "attributes", []): + # # Implement attribute documentation if desired + # pass + # if attrs_rows: + # parts.append("#### Attributes\n") + # parts.append(render_attrs_table(attrs_rows)) + return "\n\n".join(parts).strip() + "\n" + + +def generate_markdown_for_complex_type(ct: XsdComplexType) -> str: + name = get_component_local_name(ct) + top_desc = get_best_documentation_for_component(ct) + rows = collect_child_elements_from_complex_type(ct) + parts = [f"### ComplexType {name}"] + if top_desc: + parts.append(top_desc.strip()) + parts.append(render_md_table(rows)) + return "\n\n".join(parts).strip() + "\n" + + +def generate_markdown_for_group(gr: XsdGroup) -> str: + name = get_component_local_name(gr) + top_desc = get_best_documentation_for_component(gr) + rows = collect_child_elements_from_group(gr) + parts = [f"### Group {name}"] + if top_desc: + parts.append(top_desc.strip()) + parts.append(render_md_table(rows)) + return "\n\n".join(parts).strip() + "\n" + + +def write_file(path: str, content: str): + with open(path, "w", encoding="utf-8") as f: + f.write(content) + +def parse_args(): + parser = argparse.ArgumentParser( + description="Generate Markdown tables from an XSD schema (elements, complexTypes, groups)" + ) + parser.add_argument("xsd", help="Path to the main XSD file") + parser.add_argument( + "--output", + "-o", + default=".", + help="Path to the output directory.", + ) + return parser.parse_args() + +def main(): + args = parse_args() + xsd_path = args.xsd + out_root = ensure_dir(args.output) + + # Load schema (resolves includes/imports) + try: + schema = xmlschema.XMLSchema(xsd_path) + except Exception as e: + print(f"Failed to load XSD schema: {e}", file=sys.stderr) + sys.exit(2) + + # Prepare namespace mapping file + ns_dirs = {} # ns -> dir name + ns_map_path = os.path.join(out_root, "namespaces.txt") + + def get_ns_dir(ns: Optional[str]) -> str: + if ns not in ns_dirs: + safe = sanitize_namespace(ns) + ns_dirs[ns] = safe + ensure_dir(os.path.join(out_root, safe)) + return os.path.join(out_root, ns_dirs[ns]) + + # Collect and write Elements + # xmlschema 2.x: schema.maps.elements is a dict of QName -> XsdElement + elements_map = getattr(schema, "elements", None) + if elements_map is None: + elements_map = getattr(schema, "maps", None) + if elements_map is not None: + elements_map = getattr(elements_map, "elements", {}) + # Normalize to dict-like + if hasattr(elements_map, "items"): + elements_items = elements_map.items() + else: + elements_items = [] + + for _qname, el in elements_items: + if not isinstance(el, XsdElement): + continue + ns = get_component_namespace(el) + ns_dir = get_ns_dir(ns) + elements_dir = ensure_dir(os.path.join(ns_dir, "elements")) + name = get_component_local_name(el) + md = generate_markdown_for_element(el) + write_file(os.path.join(elements_dir, f"{name}.md"), md) + + # Collect and write ComplexTypes (named only) + types_map = getattr(schema, "types", None) + if types_map is None: + types_map = getattr(getattr(schema, "maps", None), "types", {}) + if hasattr(types_map, "items"): + types_items = types_map.items() + else: + types_items = [] + + for _qname, t in types_items: + if not isinstance(t, XsdComplexType): + continue + # Only named complex types + if not getattr(t, "name", None): + continue + ns = get_component_namespace(t) + ns_dir = get_ns_dir(ns) + cts_dir = ensure_dir(os.path.join(ns_dir, "complexTypes")) + name = get_component_local_name(t) + md = generate_markdown_for_complex_type(t) + write_file(os.path.join(cts_dir, f"{name}.md"), md) + + # Collect and write Groups (named only) + groups_map = getattr(schema, "groups", None) + if groups_map is None: + groups_map = getattr(getattr(schema, "maps", None), "groups", {}) + if hasattr(groups_map, "items"): + groups_items = groups_map.items() + else: + groups_items = [] + + for _qname, gr in groups_items: + if not isinstance(gr, XsdGroup): + continue + if not getattr(gr, "name", None): + continue + ns = get_component_namespace(gr) + ns_dir = get_ns_dir(ns) + groups_dir = ensure_dir(os.path.join(ns_dir, "groups")) + name = get_component_local_name(gr) + md = generate_markdown_for_group(gr) + write_file(os.path.join(groups_dir, f"{name}.md"), md) + + # Write namespace mapping file + if ns_dirs: + lines = [] + for ns, safe in ns_dirs.items(): + lines.append(f"{safe}\t{ns or '(no namespace)'}") + write_file(ns_map_path, "\n".join(lines) + "\n") + + print(f"Documentation generated under: {out_root}") + if ns_dirs: + print("Namespaces:") + for ns, safe in ns_dirs.items(): + print(f" {safe} -> {ns or '(no namespace)'}") + + +if __name__ == "__main__": + main() diff --git a/tools/xcore/xquery/xco-html.xqm b/tools/xcore/xquery/xco-html.xqm index 9128eee..f3546e5 100644 --- a/tools/xcore/xquery/xco-html.xqm +++ b/tools/xcore/xquery/xco-html.xqm @@ -136,7 +136,8 @@ declare function hl:contabReport_domain( let $compKind := $comp/local-name(.) let $compPathPart := "/" || $compKind || "s/" let $complexTypePath := dm:getReportPartPath('contab', $compPathPart, $compName, $domain , $options) - let $written := u:writeXhtmlDoc($complexTypePath, $table) + let $tableHtml := hl:create_html($head, $table) + let $written := u:writeXhtmlDoc($complexTypePath, $tableHtml) let $domainName := $domain/processing/reportFileBaseName (: TODO refactor to function compHref :) let $compHref := $domainName || $compPathPart || $compName || '.html' @@ -157,27 +158,44 @@ declare function hl:contabReport_domain( } let $title := ($domain/processing/title/

{node()}

,

API Content

)[1] - let $htmlReport := - { + let $htmlReport := hl:create_html_with_toc($head, $xsdDivs, $title, $toc) + | hu:finalizeHtmlReport(., $options) + | u:prettyNode(.) + let $_WRITE := + let $reportPath := dm:getReportPath('contab', $domain, $options) + where ($reportPath) + return u:writeXhtmlDoc($reportPath, $htmlReport) + return $htmlReport +}; + +declare function hl:create_html($head as element(), $content as element()) as element() { + { + $head, + { + , +
{ + $content + }
, + + } + } +}; + +declare function hl:create_html_with_toc($head as element(), $content as element(), $title as element(), $toc as element()) as element() { + { $head, { ,
{ - $xsdDivs + $content }
, } - } - ! hu:finalizeHtmlReport(., $options) - ! u:prettyNode(.) - let $_WRITE := - let $reportPath := dm:getReportPath('contab', $domain, $options) - where ($reportPath) - return u:writeXhtmlDoc($reportPath, $htmlReport) - return $htmlReport + } }; (: NeTEx: Removes the namespace prefix:) @@ -277,7 +295,7 @@ declare function hl:contabReport_head( - + }; From b5bb762b027147d7d3dfd3fb71b9617cfb5fee6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Urs=20St=C3=B6ckli?= Date: Wed, 15 Apr 2026 08:03:28 +0200 Subject: [PATCH 2/5] Add first draft of pycore.py. --- tools/pycore/pycore.py | 229 ++++++++++++++++------------------------- 1 file changed, 87 insertions(+), 142 deletions(-) diff --git a/tools/pycore/pycore.py b/tools/pycore/pycore.py index d72eeb4..0c26a75 100644 --- a/tools/pycore/pycore.py +++ b/tools/pycore/pycore.py @@ -3,38 +3,15 @@ import os import re import sys +from argparse import Namespace from typing import List, Optional, Tuple -try: - import xmlschema - from xmlschema.validators import XsdElement, XsdGroup, XsdComplexType, XsdType -except ImportError: - print("Missing dependency: xmlschema. Install with: pip install xmlschema", file=sys.stderr) - sys.exit(1) +import xmlschema +from xmlschema import XsdElement, XsdType, XMLSchema10, XsdComponent +from xmlschema.validators import XsdGroup, XsdComplexType XS_NS = "http://www.w3.org/2001/XMLSchema" - - - - -def ensure_dir(path: str): - os.makedirs(path, exist_ok=True) - return path - - -def sanitize_namespace(ns: Optional[str]) -> str: - if not ns: - return "no_namespace" - # Remove scheme to keep names shorter but predictable - ns_no_scheme = re.sub(r"^[a-z]+://", "", ns) - # Replace path separators and unsafe chars with underscores - safe = re.sub(r"[^A-Za-z0-9._-]+", "_", ns_no_scheme.strip("/")) - # Collapse multiple underscores - safe = re.sub(r"_+", "_", safe) - return safe or "namespace" - - def local_name_from_qname(qname: Optional[str]) -> str: if not qname: return "" @@ -59,6 +36,8 @@ def get_component_local_name(component) -> str: name = getattr(component, "name", None) or getattr(component, "qname", None) return local_name_from_qname(name) +def get_namespace_prefix(name: str) -> str: + return name.split(":",1)[0] def read_documentation_from_elem(elem) -> str: """ @@ -180,8 +159,7 @@ def type_with_array_suffix(xsd_type: Optional[XsdType], max_occurs) -> str: pass return tname - -def collect_child_elements_from_complex_type(xsd_complex_type: XsdComplexType) -> List[Tuple[str, str, str, str]]: +def collect_child_elements_from_element(node: XsdElement) -> List[Tuple[str, str, str, str]]: """ Returns rows for table: - Element (local name) @@ -191,7 +169,7 @@ def collect_child_elements_from_complex_type(xsd_complex_type: XsdComplexType) - We use deep iteration over content model to include nested groups/choices. """ rows = [] - content = getattr(xsd_complex_type, "content_type", None) + content = getattr(node, "content_type", None) if content is None: return rows @@ -211,10 +189,40 @@ def collect_child_elements_from_complex_type(xsd_complex_type: XsdComplexType) - rows.append((name, usage, typ, desc)) return rows +def collect_child_elements_from_complex_type(node: XsdComplexType) -> List[Tuple[str, str, str, str]]: + """ + Returns rows for table: + - Element (local name) + - Usage (min..max) + - Type (with [] suffix if max > 1) + - Description (documentation) + We use deep iteration over content model to include nested groups/choices. + """ + rows = [] + content = getattr(node, "content", None) + if content is None: + return rows + + # xmlschema's content_type for complex types often supports iter_elements() + elements = getattr(content, "elements", None) + if elements is None: + return rows + + # We keep a sequence; duplicates may appear if the model repeats; it's usually fine + for el in elements: + if not isinstance(el, XsdElement): + continue + name = get_component_local_name(el) + usage = format_usage(getattr(el, "min_occurs", 1), getattr(el, "max_occurs", 1)) + typ = type_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) + desc = get_best_documentation_for_element(el) + rows.append((name, usage, typ, desc)) + return rows + -def collect_child_elements_from_group(xsd_group: XsdGroup) -> List[Tuple[str, str, str, str]]: +def collect_child_elements_from_group(node: XsdGroup) -> List[Tuple[str, str, str, str]]: rows = [] - iter_elems = getattr(xsd_group, "iter_elements", None) + iter_elems = getattr(node, "iter_elements", None) if iter_elems is None: return rows for el in iter_elems(): @@ -240,18 +248,24 @@ def render_md_table(rows: List[Tuple[str, str, str, str]]) -> str: return "\n".join(lines) + "\n" -def generate_markdown_for_element(el: XsdElement) -> str: - name = get_component_local_name(el) - top_desc = get_best_documentation_for_component(el) +def generate_markdown_for_node(node: XsdComponent) -> str: + name = get_component_local_name(node) + top_desc = get_best_documentation_for_component(node) # Determine element's type and collect its child elements if complex - el_type = getattr(el, "type", None) + el_type = getattr(node, "type", None) rows = [] - if isinstance(el_type, XsdComplexType): + if isinstance(el_type, XsdElement): + rows = collect_child_elements_from_element(el_type) + elif isinstance(el_type, XsdComplexType): rows = collect_child_elements_from_complex_type(el_type) + elif isinstance(el_type, XsdGroup): + rows = collect_child_elements_from_group(el_type) + # Heading - parts = [f"### Element {name}"] + parts = [f"### {name}"] if top_desc: parts.append(top_desc.strip()) + parts.append(render_md_table(rows)) # Optionally: include attributes table (commented out) # attrs_rows = [] @@ -264,29 +278,6 @@ def generate_markdown_for_element(el: XsdElement) -> str: # parts.append(render_attrs_table(attrs_rows)) return "\n\n".join(parts).strip() + "\n" - -def generate_markdown_for_complex_type(ct: XsdComplexType) -> str: - name = get_component_local_name(ct) - top_desc = get_best_documentation_for_component(ct) - rows = collect_child_elements_from_complex_type(ct) - parts = [f"### ComplexType {name}"] - if top_desc: - parts.append(top_desc.strip()) - parts.append(render_md_table(rows)) - return "\n\n".join(parts).strip() + "\n" - - -def generate_markdown_for_group(gr: XsdGroup) -> str: - name = get_component_local_name(gr) - top_desc = get_best_documentation_for_component(gr) - rows = collect_child_elements_from_group(gr) - parts = [f"### Group {name}"] - if top_desc: - parts.append(top_desc.strip()) - parts.append(render_md_table(rows)) - return "\n\n".join(parts).strip() + "\n" - - def write_file(path: str, content: str): with open(path, "w", encoding="utf-8") as f: f.write(content) @@ -304,10 +295,17 @@ def parse_args(): ) return parser.parse_args() + # def get_ns_dir(ns: Optional[str]) -> str: + # if ns not in ns_dirs: + # safe = sanitize_namespace(ns) + # ns_dirs[ns] = safe + # ensure_dir(os.path.join(out_root, safe)) + # return os.path.join(out_root, ns_dirs[ns]) + def main(): args = parse_args() xsd_path = args.xsd - out_root = ensure_dir(args.output) + # Load schema (resolves includes/imports) try: @@ -316,95 +314,42 @@ def main(): print(f"Failed to load XSD schema: {e}", file=sys.stderr) sys.exit(2) - # Prepare namespace mapping file - ns_dirs = {} # ns -> dir name - ns_map_path = os.path.join(out_root, "namespaces.txt") + out_dir = args.output + write_md_of_elements(out_dir, schema) + write_md_of_complexTypes(out_dir, schema) + write_md_of_groups(out_dir, schema) - def get_ns_dir(ns: Optional[str]) -> str: - if ns not in ns_dirs: - safe = sanitize_namespace(ns) - ns_dirs[ns] = safe - ensure_dir(os.path.join(out_root, safe)) - return os.path.join(out_root, ns_dirs[ns]) + print(f"Documentation generated under: {out_dir}") +def write_md_of_nodes(out_dir: str, nodes_name: str, node_type: type, schema: XMLSchema10): # Collect and write Elements - # xmlschema 2.x: schema.maps.elements is a dict of QName -> XsdElement - elements_map = getattr(schema, "elements", None) - if elements_map is None: - elements_map = getattr(schema, "maps", None) - if elements_map is not None: - elements_map = getattr(elements_map, "elements", {}) + nodes_map = getattr(schema, nodes_name, None) # Normalize to dict-like - if hasattr(elements_map, "items"): - elements_items = elements_map.items() + if hasattr(nodes_map, "items"): + items = nodes_map.items() else: - elements_items = [] + items = [] - for _qname, el in elements_items: - if not isinstance(el, XsdElement): + for _qname, node in items: + if not isinstance(node, node_type): continue - ns = get_component_namespace(el) - ns_dir = get_ns_dir(ns) - elements_dir = ensure_dir(os.path.join(ns_dir, "elements")) - name = get_component_local_name(el) - md = generate_markdown_for_element(el) - write_file(os.path.join(elements_dir, f"{name}.md"), md) - - # Collect and write ComplexTypes (named only) - types_map = getattr(schema, "types", None) - if types_map is None: - types_map = getattr(getattr(schema, "maps", None), "types", {}) - if hasattr(types_map, "items"): - types_items = types_map.items() - else: - types_items = [] - - for _qname, t in types_items: - if not isinstance(t, XsdComplexType): - continue - # Only named complex types - if not getattr(t, "name", None): + if not getattr(node, "name", None): continue - ns = get_component_namespace(t) - ns_dir = get_ns_dir(ns) - cts_dir = ensure_dir(os.path.join(ns_dir, "complexTypes")) - name = get_component_local_name(t) - md = generate_markdown_for_complex_type(t) - write_file(os.path.join(cts_dir, f"{name}.md"), md) - - # Collect and write Groups (named only) - groups_map = getattr(schema, "groups", None) - if groups_map is None: - groups_map = getattr(getattr(schema, "maps", None), "groups", {}) - if hasattr(groups_map, "items"): - groups_items = groups_map.items() - else: - groups_items = [] + md = generate_markdown_for_node(node) + ns_prefix = get_namespace_prefix(node.prefixed_name) + nodes_path = os.path.join(out_dir, ns_prefix, nodes_name) + os.makedirs(nodes_path, exist_ok=True) + name = get_component_local_name(node) + write_file(os.path.join(nodes_path, f"{name}.md"), md) - for _qname, gr in groups_items: - if not isinstance(gr, XsdGroup): - continue - if not getattr(gr, "name", None): - continue - ns = get_component_namespace(gr) - ns_dir = get_ns_dir(ns) - groups_dir = ensure_dir(os.path.join(ns_dir, "groups")) - name = get_component_local_name(gr) - md = generate_markdown_for_group(gr) - write_file(os.path.join(groups_dir, f"{name}.md"), md) - - # Write namespace mapping file - if ns_dirs: - lines = [] - for ns, safe in ns_dirs.items(): - lines.append(f"{safe}\t{ns or '(no namespace)'}") - write_file(ns_map_path, "\n".join(lines) + "\n") - - print(f"Documentation generated under: {out_root}") - if ns_dirs: - print("Namespaces:") - for ns, safe in ns_dirs.items(): - print(f" {safe} -> {ns or '(no namespace)'}") +def write_md_of_elements(out_dir: str, schema: XMLSchema10): + write_md_of_nodes(out_dir, "elements", XsdElement, schema) + +def write_md_of_complexTypes(out_dir, schema: XMLSchema10): + write_md_of_nodes(out_dir, "types", XsdComplexType, schema) + +def write_md_of_groups(out_dir, schema: XMLSchema10): + write_md_of_nodes(out_dir, "groups", XsdGroup, schema) if __name__ == "__main__": From 581d3f587e21a094dabebfb8af7ae78ba23f6e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Urs=20St=C3=B6ckli?= Date: Wed, 15 Apr 2026 08:05:05 +0200 Subject: [PATCH 3/5] Optimize imports. --- tools/pycore/pycore.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/pycore/pycore.py b/tools/pycore/pycore.py index 0c26a75..14b6248 100644 --- a/tools/pycore/pycore.py +++ b/tools/pycore/pycore.py @@ -1,9 +1,7 @@ #!/usr/bin/env python3 import argparse import os -import re import sys -from argparse import Namespace from typing import List, Optional, Tuple import xmlschema From 53b612f891a702767d06594285df19c8a1dd2cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Urs=20St=C3=B6ckli?= Date: Wed, 15 Apr 2026 08:56:58 +0200 Subject: [PATCH 4/5] Use local name for type. --- tools/pycore/pycore.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tools/pycore/pycore.py b/tools/pycore/pycore.py index 14b6248..ec40bf3 100644 --- a/tools/pycore/pycore.py +++ b/tools/pycore/pycore.py @@ -101,7 +101,6 @@ def get_best_documentation_for_component(component) -> str: return "" - def is_builtin_type(xsd_type: XsdType) -> bool: ns = getattr(xsd_type, "target_namespace", None) return ns == XS_NS @@ -111,7 +110,7 @@ def display_type_name(xsd_type: Optional[XsdType]) -> str: if xsd_type is None: return "anyType" # xmlschema XsdType usually has .name (local) and .qname - name = getattr(xsd_type, "name", None) # local name, if named + name = getattr(xsd_type, "local_name", None) # local name, if named if is_builtin_type(xsd_type) and name: return name # e.g., string, int, date if name: @@ -146,7 +145,7 @@ def as_int(value, default): return f"{mino}..{maxo}" -def type_with_array_suffix(xsd_type: Optional[XsdType], max_occurs) -> str: +def type_name_with_array_suffix(xsd_type: Optional[XsdType], max_occurs) -> str: tname = display_type_name(xsd_type) if isinstance(max_occurs, str) and max_occurs.lower() == "unbounded": return f"{tname}[]" @@ -182,9 +181,9 @@ def collect_child_elements_from_element(node: XsdElement) -> List[Tuple[str, str continue name = get_component_local_name(el) usage = format_usage(getattr(el, "min_occurs", 1), getattr(el, "max_occurs", 1)) - typ = type_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) + type_name = type_name_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) desc = get_best_documentation_for_element(el) - rows.append((name, usage, typ, desc)) + rows.append((name, usage, type_name, desc)) return rows def collect_child_elements_from_complex_type(node: XsdComplexType) -> List[Tuple[str, str, str, str]]: @@ -212,9 +211,9 @@ def collect_child_elements_from_complex_type(node: XsdComplexType) -> List[Tuple continue name = get_component_local_name(el) usage = format_usage(getattr(el, "min_occurs", 1), getattr(el, "max_occurs", 1)) - typ = type_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) + type_name = type_name_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) desc = get_best_documentation_for_element(el) - rows.append((name, usage, typ, desc)) + rows.append((name, usage, type_name, desc)) return rows @@ -228,9 +227,9 @@ def collect_child_elements_from_group(node: XsdGroup) -> List[Tuple[str, str, st continue name = get_component_local_name(el) usage = format_usage(getattr(el, "min_occurs", 1), getattr(el, "max_occurs", 1)) - typ = type_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) + type_name = type_name_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) desc = get_best_documentation_for_element(el) - rows.append((name, usage, typ, desc)) + rows.append((name, usage, type_name, desc)) return rows @@ -239,10 +238,10 @@ def render_md_table(rows: List[Tuple[str, str, str, str]]) -> str: if not rows: return header + "\n" lines = [header] - for name, usage, typ, desc in rows: + for name, usage, type_name, desc in rows: # Escape pipes in description safe_desc = desc.replace("|", "\\|") - lines.append(f"| {name} | {usage} | {typ} | {safe_desc} |") + lines.append(f"| {name} | {usage} | {type_name} | {safe_desc} |") return "\n".join(lines) + "\n" From 4ef20a0003d99be8d087a202a84cfda13e2590e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Urs=20St=C3=B6ckli?= Date: Wed, 15 Apr 2026 11:47:59 +0200 Subject: [PATCH 5/5] Fixed type links. --- tools/pycore/pycore.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/tools/pycore/pycore.py b/tools/pycore/pycore.py index ec40bf3..1cb3c1c 100644 --- a/tools/pycore/pycore.py +++ b/tools/pycore/pycore.py @@ -2,7 +2,7 @@ import argparse import os import sys -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Any import xmlschema from xmlschema import XsdElement, XsdType, XMLSchema10, XsdComponent @@ -106,7 +106,7 @@ def is_builtin_type(xsd_type: XsdType) -> bool: return ns == XS_NS -def display_type_name(xsd_type: Optional[XsdType]) -> str: +def retrieve_type_name(xsd_type: Optional[XsdType]) -> str: if xsd_type is None: return "anyType" # xmlschema XsdType usually has .name (local) and .qname @@ -125,6 +125,15 @@ def display_type_name(xsd_type: Optional[XsdType]) -> str: return local_name_from_qname(qname) return "anonymousType" +def build_type_path(xsd_type: Optional[XsdType], name: str) -> str | None: + if xsd_type is None: + return None + # xmlschema XsdType usually has .name (local) and .qname + if is_builtin_type(xsd_type): + return None # e.g., string, int, date + if name: + return f"../types/{name}.md" # named complex/simple type + return None def format_usage(min_occurs: Optional[int], max_occurs) -> str: def as_int(value, default): @@ -145,15 +154,15 @@ def as_int(value, default): return f"{mino}..{maxo}" -def type_name_with_array_suffix(xsd_type: Optional[XsdType], max_occurs) -> str: - tname = display_type_name(xsd_type) +def build_linked_type_name(xsd_type: Optional[XsdType], max_occurs) -> str: + tname = retrieve_type_name(xsd_type) + tpath = build_type_path(xsd_type, tname) if isinstance(max_occurs, str) and max_occurs.lower() == "unbounded": - return f"{tname}[]" - try: - if int(max_occurs) > 1: - return f"{tname}[]" - except Exception: - pass + tname = f"{tname}[]" + if int(max_occurs) > 1: + tname = f"{tname}[]" + if tpath: + return f"[{tname}]({tpath})" return tname def collect_child_elements_from_element(node: XsdElement) -> List[Tuple[str, str, str, str]]: @@ -181,7 +190,7 @@ def collect_child_elements_from_element(node: XsdElement) -> List[Tuple[str, str continue name = get_component_local_name(el) usage = format_usage(getattr(el, "min_occurs", 1), getattr(el, "max_occurs", 1)) - type_name = type_name_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) + type_name = build_linked_type_name(getattr(el, "type", None), getattr(el, "max_occurs", 1)) desc = get_best_documentation_for_element(el) rows.append((name, usage, type_name, desc)) return rows @@ -211,7 +220,7 @@ def collect_child_elements_from_complex_type(node: XsdComplexType) -> List[Tuple continue name = get_component_local_name(el) usage = format_usage(getattr(el, "min_occurs", 1), getattr(el, "max_occurs", 1)) - type_name = type_name_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) + type_name = build_linked_type_name(getattr(el, "type", None), getattr(el, "max_occurs", 1)) desc = get_best_documentation_for_element(el) rows.append((name, usage, type_name, desc)) return rows @@ -227,7 +236,7 @@ def collect_child_elements_from_group(node: XsdGroup) -> List[Tuple[str, str, st continue name = get_component_local_name(el) usage = format_usage(getattr(el, "min_occurs", 1), getattr(el, "max_occurs", 1)) - type_name = type_name_with_array_suffix(getattr(el, "type", None), getattr(el, "max_occurs", 1)) + type_name = build_linked_type_name(getattr(el, "type", None), getattr(el, "max_occurs", 1)) desc = get_best_documentation_for_element(el) rows.append((name, usage, type_name, desc)) return rows