@dialecte/create 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +88 -0
  2. package/dist/cli/index.js +281 -0
  3. package/package.json +43 -0
  4. package/python/generate/__init__.py +1 -0
  5. package/python/generate/__main__.py +103 -0
  6. package/python/generate/collector.py +128 -0
  7. package/python/generate/deriver.py +117 -0
  8. package/python/generate/emitters/__init__.py +1 -0
  9. package/python/generate/emitters/constants.py +69 -0
  10. package/python/generate/emitters/definition.py +49 -0
  11. package/python/generate/emitters/ts_helpers.py +283 -0
  12. package/python/generate/emitters/types.py +67 -0
  13. package/python/generate/extractors/__init__.py +1 -0
  14. package/python/generate/extractors/attributes.py +83 -0
  15. package/python/generate/extractors/children.py +175 -0
  16. package/python/generate/extractors/constraints.py +154 -0
  17. package/python/generate/extractors/docs.py +32 -0
  18. package/python/generate/extractors/facets.py +168 -0
  19. package/python/generate/extractors/namespace.py +59 -0
  20. package/python/generate/globals.py +99 -0
  21. package/python/generate/helpers.py +143 -0
  22. package/python/generate/ir.py +81 -0
  23. package/python/generate/orphans.py +69 -0
  24. package/python/generate/xpath_parser.py +167 -0
  25. package/python/generate/xsi_type.py +150 -0
  26. package/python/pyproject.toml +15 -0
  27. package/templates/dialecte/README.md +39 -0
  28. package/templates/dialecte/_gitignore +4 -0
  29. package/templates/dialecte/docs/.vitepress/config.ts +15 -0
  30. package/templates/dialecte/docs/index.md +18 -0
  31. package/templates/dialecte/env.d.ts +1 -0
  32. package/templates/dialecte/package.json +45 -0
  33. package/templates/dialecte/src/__version__/config/dialecte.config.ts +48 -0
  34. package/templates/dialecte/src/__version__/config/hydrated.types.ts +78 -0
  35. package/templates/dialecte/src/__version__/config/index.ts +2 -0
  36. package/templates/dialecte/src/__version__/config/namespaces.ts +6 -0
  37. package/templates/dialecte/src/__version__/definition/.gitkeep +2 -0
  38. package/templates/dialecte/src/__version__/definition/index.ts +4 -0
  39. package/templates/dialecte/src/__version__/dialecte.ts +30 -0
  40. package/templates/dialecte/src/__version__/extensions/index.ts +3 -0
  41. package/templates/dialecte/src/__version__/index.ts +2 -0
  42. package/templates/dialecte/src/__version__/test/hydrated-test.ts +53 -0
  43. package/templates/dialecte/src/__version__/test/index.ts +1 -0
  44. package/templates/dialecte/src/index.ts +1 -0
  45. package/templates/dialecte/tsconfig.build.json +24 -0
  46. package/templates/dialecte/tsconfig.json +8 -0
  47. package/templates/dialecte/tsconfig.node.json +13 -0
  48. package/templates/dialecte/tsconfig.vitest.json +10 -0
  49. package/templates/dialecte/vite.config.ts +36 -0
  50. package/templates/dialecte/vitest.config.ts +35 -0
  51. package/vendor/elementpath-5.1.1-py3-none-any.whl +0 -0
  52. package/vendor/xmlschema-4.3.1-py3-none-any.whl +0 -0
@@ -0,0 +1,175 @@
1
+ """Extract child elements, choices, and text content from an XSD element."""
2
+ from typing import Any
3
+
4
+ from generate.extractors.constraints import extract_constraints
5
+ from generate.extractors.facets import extract_facets
6
+ from generate.ir import ChildDef, ChoiceGroup, TextContent
7
+ def extract_children(xsd_elem: Any) -> tuple[list[str], bool, dict[str, ChildDef]]:
8
+ """Extract child element definitions from an XSD element's content model.
9
+
10
+ Returns:
11
+ (child_sequence, has_any_element, child_details)
12
+
13
+ xmlschema API:
14
+ XsdElement.type: XsdComplexType
15
+ XsdComplexType.content: XsdGroup (model group)
16
+ XsdGroup.iter_elements() → Iterator[XsdElement | XsdAnyElement]
17
+ Each yielded element has:
18
+ .local_name: str
19
+ .min_occurs: int
20
+ .max_occurs: int | None
21
+ Content wildcards:
22
+ XsdGroup — may contain XsdAnyElement children
23
+ """
24
+ return _extract_children_from_content(_get_content_model(xsd_elem))
25
+ def extract_children_from_type(xsd_type: Any) -> tuple[list[str], bool, dict[str, ChildDef]]:
26
+ """Like ``extract_children`` but reads a complex type's content model directly.
27
+
28
+ Used by xsi:type expansion where children come from a substitutable type
29
+ (selected via ``xsi:type``) rather than the element's own declared type.
30
+ """
31
+ return _extract_children_from_content(getattr(xsd_type, 'content', None))
32
+ def _extract_children_from_content(content: Any) -> tuple[list[str], bool, dict[str, ChildDef]]:
33
+ """Shared core: extract child definitions from a content model (XsdGroup)."""
34
+ sequence: list[str] = []
35
+ details: dict[str, ChildDef] = {}
36
+ any_child = False
37
+
38
+ if content is None:
39
+ return sequence, any_child, details
40
+
41
+ seen_names: set[str] = set()
42
+
43
+ for child in _iter_child_elements(content):
44
+ # Check if it's a wildcard (xs:any)
45
+ cls_name = type(child).__name__
46
+ if 'Any' in cls_name and 'Element' in cls_name:
47
+ any_child = True
48
+ continue
49
+
50
+ name = getattr(child, 'local_name', None)
51
+ if not name:
52
+ continue
53
+
54
+ if name in seen_names:
55
+ continue
56
+ seen_names.add(name)
57
+
58
+ min_occ = getattr(child, 'min_occurs', 0)
59
+ max_occ = getattr(child, 'max_occurs', None) # None = unbounded
60
+
61
+ sequence.append(name)
62
+ details[name] = ChildDef(
63
+ required=min_occ > 0,
64
+ min_occurs=min_occ,
65
+ max_occurs=max_occ,
66
+ constraints=extract_constraints(child) or None,
67
+ facets=None, # Rare: child text content facets
68
+ )
69
+
70
+ return sequence, any_child, details
71
+ def extract_choices(xsd_elem: Any) -> list[ChoiceGroup]:
72
+ """Extract xs:choice groups from the content model.
73
+
74
+ xmlschema API:
75
+ XsdGroup.model: str ('sequence' | 'choice' | 'all')
76
+ XsdGroup is iterable — yields XsdElement | XsdGroup | XsdAnyElement
77
+ """
78
+ content = _get_content_model(xsd_elem)
79
+ if content is None:
80
+ return []
81
+
82
+ choices: list[ChoiceGroup] = []
83
+ _walk_groups_for_choices(content, choices)
84
+ return choices
85
+ def extract_text_content(xsd_elem: Any) -> TextContent | None:
86
+ """Extract text content definition for elements with simple or mixed content.
87
+
88
+ xmlschema API:
89
+ XsdComplexType.has_simple_content() → bool
90
+ XsdComplexType.mixed: bool
91
+ XsdComplexType.content_type_label: str ('simple' | 'mixed' | 'element-only' | 'empty')
92
+ """
93
+ xsd_type = getattr(xsd_elem, 'type', None)
94
+ if xsd_type is None:
95
+ return None
96
+
97
+ has_simple = False
98
+ if callable(getattr(xsd_type, 'has_simple_content', None)):
99
+ has_simple = xsd_type.has_simple_content()
100
+ mixed = getattr(xsd_type, 'mixed', False)
101
+
102
+ if not has_simple and not mixed:
103
+ return None
104
+
105
+ # Find the simple type to extract facets from
106
+ facets_source = (
107
+ getattr(xsd_type, 'content', None)
108
+ or getattr(xsd_type, 'simple_type', None)
109
+ or getattr(xsd_type, 'base_type', None)
110
+ )
111
+
112
+ facets = extract_facets(facets_source)
113
+ return TextContent(facets=facets) if facets else TextContent()
114
+ # --- Internal helpers ---
115
+ def _get_content_model(xsd_elem: Any) -> Any:
116
+ """Get the content model (XsdGroup) from an element's type."""
117
+ xsd_type = getattr(xsd_elem, 'type', None)
118
+ if xsd_type is None:
119
+ return None
120
+ return getattr(xsd_type, 'content', None)
121
+ def _iter_child_elements(content: Any):
122
+ """Iterate child elements from a content model.
123
+
124
+ xmlschema API:
125
+ XsdGroup.iter_elements() → yields XsdElement | XsdAnyElement
126
+ """
127
+ iter_fn = getattr(content, 'iter_elements', None)
128
+ if iter_fn and callable(iter_fn):
129
+ yield from iter_fn()
130
+ def iter_child_elements(xsd_elem: Any):
131
+ """Public helper: iterate XsdElement children of an element for recursive walking."""
132
+ yield from _iter_named_child_elements(_get_content_model(xsd_elem))
133
+ def iter_type_child_elements(xsd_type: Any):
134
+ """Public helper: iterate XsdElement children declared in a complex type's content."""
135
+ yield from _iter_named_child_elements(getattr(xsd_type, 'content', None))
136
+ def _iter_named_child_elements(content: Any):
137
+ """Iterate non-wildcard XsdElement children of a content model."""
138
+ if content is None:
139
+ return
140
+ for child in _iter_child_elements(content):
141
+ cls_name = type(child).__name__
142
+ if 'Any' in cls_name and 'Element' in cls_name:
143
+ continue
144
+ yield child
145
+ def _walk_groups_for_choices(group: Any, out: list[ChoiceGroup]) -> None:
146
+ """Recursively walk model groups to find xs:choice groups.
147
+
148
+ xmlschema API:
149
+ XsdGroup.model: str
150
+ XsdGroup is iterable (yields children)
151
+ """
152
+ model = getattr(group, 'model', None)
153
+ if model is None:
154
+ return
155
+
156
+ if model == 'choice':
157
+ options: list[str] = []
158
+ for item in group:
159
+ name = getattr(item, 'local_name', None)
160
+ if name:
161
+ options.append(name)
162
+ # Recurse into nested groups
163
+ if getattr(item, 'model', None) is not None:
164
+ _walk_groups_for_choices(item, out)
165
+ if options:
166
+ out.append(ChoiceGroup(
167
+ options=sorted(options),
168
+ min_occurs=getattr(group, 'min_occurs', 0),
169
+ max_occurs=getattr(group, 'max_occurs', None),
170
+ ))
171
+ else:
172
+ # sequence or all — recurse into nested groups
173
+ for item in group:
174
+ if getattr(item, 'model', None) is not None:
175
+ _walk_groups_for_choices(item, out)
@@ -0,0 +1,154 @@
1
+ """Extract identity constraints (xs:unique, xs:key, xs:keyref) from XSD elements."""
2
+ from typing import Any
3
+
4
+ from generate.helpers import local_name
5
+ from generate.ir import IdentityConstraint
6
+ from generate.xpath_parser import parse_field, parse_selector
7
+ def extract_constraints(xsd_elem: Any) -> list[IdentityConstraint]:
8
+ """Extract identity constraints from an XSD element.
9
+
10
+ Tries compiled constraint objects first, then raw XML fallback.
11
+
12
+ xmlschema API (compiled path):
13
+ XsdElement.identities: dict[str, XsdIdentity] — keys/uniques/keyrefs
14
+ XsdIdentity.selector: XsdSelector
15
+ XsdSelector.path: str
16
+ XsdIdentity.fields: list[XsdFieldSelector]
17
+ XsdFieldSelector.path: str
18
+ XsdUnique, XsdKey, XsdKeyref — subclasses
19
+ XsdKeyref.refer: XsdKey | XsdUnique
20
+ .refer.local_name: str
21
+
22
+ xmlschema API (raw XML fallback):
23
+ XsdElement.elem: Element — raw lxml/ET element
24
+ Scan for xs:key, xs:unique, xs:keyref tags
25
+ """
26
+ constraints: list[IdentityConstraint] = []
27
+ seen: set[tuple[str, str]] = set()
28
+
29
+ # Try compiled identities
30
+ for ic in _iter_identity_constraints(xsd_elem):
31
+ kind = _classify_constraint(ic)
32
+ if kind is None:
33
+ continue
34
+
35
+ name = getattr(ic, 'name', '') or ''
36
+ # Use local_name if it's a Clark QName
37
+ if name.startswith('{'):
38
+ name = local_name(name)
39
+
40
+ sig = (kind, name)
41
+ if sig in seen:
42
+ continue
43
+ seen.add(sig)
44
+
45
+ selector_xpath = _get_selector_path(ic)
46
+ field_xpaths = _get_field_paths(ic)
47
+
48
+ selector_paths = parse_selector(selector_xpath)
49
+ field_paths = [parse_field(fp) for fp in field_xpaths]
50
+
51
+ refer = None
52
+ if kind == 'keyref':
53
+ refer = _get_refer_name(ic)
54
+
55
+ constraints.append(IdentityConstraint(
56
+ kind=kind,
57
+ name=name,
58
+ selector=selector_paths,
59
+ fields=field_paths,
60
+ deep='//' in (selector_xpath or ''),
61
+ refer=refer,
62
+ ))
63
+
64
+ return constraints
65
+ # --- Internal helpers ---
66
+ def _iter_identity_constraints(xsd_elem: Any):
67
+ """Yield identity constraint objects from an element.
68
+
69
+ Checks multiple container shapes that xmlschema uses:
70
+ - .identities dict
71
+ - .identity_constraints dict
72
+ - .keys, .uniques, .keyrefs dicts/lists
73
+ """
74
+ # Preferred: .identities (xmlschema v2+)
75
+ identities = getattr(xsd_elem, 'identities', None)
76
+ if identities:
77
+ if hasattr(identities, 'values'):
78
+ yield from identities.values()
79
+ else:
80
+ yield from identities
81
+ return
82
+
83
+ # Alternative: .identity_constraints
84
+ ic_map = getattr(xsd_elem, 'identity_constraints', None)
85
+ if ic_map:
86
+ if hasattr(ic_map, 'values'):
87
+ yield from ic_map.values()
88
+ else:
89
+ yield from ic_map
90
+ return
91
+
92
+ # Separate containers
93
+ for attr_name in ('keys', 'uniques', 'keyrefs'):
94
+ container = getattr(xsd_elem, attr_name, None)
95
+ if container:
96
+ if hasattr(container, 'values'):
97
+ yield from container.values()
98
+ else:
99
+ yield from container
100
+ def _classify_constraint(ic: Any) -> str | None:
101
+ """Determine if an identity constraint is 'unique', 'key', or 'keyref'."""
102
+ # Category attribute
103
+ category = getattr(ic, 'category', None)
104
+ if category:
105
+ cat = str(category).lower()
106
+ if 'key' in cat and 'ref' in cat:
107
+ return 'keyref'
108
+ if 'key' in cat:
109
+ return 'key'
110
+ if 'unique' in cat:
111
+ return 'unique'
112
+
113
+ # Class name fallback
114
+ cls_name = type(ic).__name__.lower()
115
+ if 'keyref' in cls_name:
116
+ return 'keyref'
117
+ if 'key' in cls_name:
118
+ return 'key'
119
+ if 'unique' in cls_name:
120
+ return 'unique'
121
+
122
+ return None
123
+ def _get_selector_path(ic: Any) -> str:
124
+ """Get XPath selector path from a constraint."""
125
+ selector = getattr(ic, 'selector', None)
126
+ if selector is None:
127
+ return ''
128
+ path = getattr(selector, 'path', None)
129
+ if path:
130
+ return str(path)
131
+ # Fallback: selector might be string itself
132
+ return str(selector) if selector else ''
133
+ def _get_field_paths(ic: Any) -> list[str]:
134
+ """Get XPath field paths from a constraint."""
135
+ fields = getattr(ic, 'fields', None)
136
+ if not fields:
137
+ return []
138
+ result = []
139
+ for f in fields:
140
+ path = getattr(f, 'path', None)
141
+ if path:
142
+ result.append(str(path))
143
+ elif isinstance(f, str):
144
+ result.append(f)
145
+ return result
146
+ def _get_refer_name(ic: Any) -> str | None:
147
+ """Get the name of the referred key/unique for a keyref constraint."""
148
+ refer = getattr(ic, 'refer', None)
149
+ if refer is None:
150
+ return None
151
+ name = getattr(refer, 'local_name', None) or getattr(refer, 'name', None)
152
+ if name:
153
+ return local_name(str(name))
154
+ return str(refer)
@@ -0,0 +1,32 @@
1
+ """Extract documentation string from an XSD element."""
2
+ from typing import Any
3
+ def extract_docs(xsd_elem: Any) -> str | None:
4
+ """Extract the first xs:documentation text from an XSD element's annotations.
5
+
6
+ xmlschema API:
7
+ XsdElement.annotation → XsdAnnotation | None
8
+ XsdAnnotation.documentation → list[XsdDocumentation]
9
+ XsdDocumentation.text → str
10
+ """
11
+ # Try .annotation (singular) first — xmlschema v2+
12
+ annotation = getattr(xsd_elem, 'annotation', None)
13
+ if annotation is not None:
14
+ docs = getattr(annotation, 'documentation', None)
15
+ if docs:
16
+ for doc in docs:
17
+ text = getattr(doc, 'text', None)
18
+ if text and text.strip():
19
+ return text.strip()
20
+
21
+ # Fallback: .annotations (plural) — older xmlschema
22
+ annotations = getattr(xsd_elem, 'annotations', None)
23
+ if annotations:
24
+ for ann in annotations:
25
+ docs = getattr(ann, 'documentation', None)
26
+ if docs:
27
+ for doc in docs:
28
+ text = getattr(doc, 'text', None)
29
+ if text and text.strip():
30
+ return text.strip()
31
+
32
+ return None
@@ -0,0 +1,168 @@
1
+ """Extract facets (validation constraints) from an XSD type, walking the base type chain."""
2
+ from typing import Any
3
+
4
+ from generate.helpers import get_facet_value, local_name, xsd_pattern_to_js
5
+ from generate.ir import Facets
6
+ def extract_facets(xsd_type: Any) -> Facets | None:
7
+ """Walk base type chain, collect all facets. Return None if empty.
8
+
9
+ xmlschema API used:
10
+ XsdSimpleType.facets: dict[str, XsdFacet] — keyed by Clark-notation QName
11
+ XsdSimpleType.enumeration: iterable of values
12
+ XsdSimpleType.patterns: iterable of XsdPatternFacets
13
+ XsdSimpleType.base_type: XsdSimpleType | XsdComplexType | None
14
+ XsdFacet.value / .v / .min_value / .max_value — scalar facet value
15
+ XsdPatternFacets — iterable, each has .regexps or str()
16
+ XsdEnumerationFacets — iterable, each has .value or is str
17
+ """
18
+ if xsd_type is None:
19
+ return None
20
+
21
+ facets = Facets()
22
+ visited: set[int] = set()
23
+ queue: list[Any] = [xsd_type]
24
+
25
+ while queue:
26
+ current = queue.pop(0)
27
+ if current is None or id(current) in visited:
28
+ continue
29
+ visited.add(id(current))
30
+
31
+ _collect_facets_from_type(current, facets)
32
+
33
+ # Walk base type chain
34
+ base = getattr(current, 'base_type', None)
35
+ if base is not None:
36
+ queue.append(base)
37
+
38
+ # Walk union member types
39
+ member_types = getattr(current, 'member_types', None)
40
+ if member_types:
41
+ for mt in member_types:
42
+ if mt is not None and id(mt) not in visited:
43
+ queue.append(mt)
44
+
45
+ # Walk simple_type (for complexType with simpleContent)
46
+ simple = getattr(current, 'simple_type', None)
47
+ if simple is not None and id(simple) not in visited:
48
+ queue.append(simple)
49
+
50
+ return None if facets.is_empty() else facets
51
+ def _collect_facets_from_type(current: Any, facets: Facets) -> None:
52
+ """Read facets dict from a single type level and populate the Facets dataclass."""
53
+ raw_facets = getattr(current, 'facets', None)
54
+ if not raw_facets:
55
+ return
56
+
57
+ # Ensure it's dict-like
58
+ items = raw_facets.items() if hasattr(raw_facets, 'items') else []
59
+
60
+ for name, facet in items:
61
+ local = local_name(str(name))
62
+ match local:
63
+ case 'enumeration':
64
+ new_enums = _extract_enumeration(current, facet)
65
+ if new_enums:
66
+ if facets.enumeration is None:
67
+ facets.enumeration = new_enums
68
+ else:
69
+ existing = set(facets.enumeration)
70
+ facets.enumeration.extend(v for v in new_enums if v not in existing)
71
+ case 'pattern':
72
+ new_patterns = _extract_patterns(current, facet)
73
+ if new_patterns:
74
+ if facets.pattern is None:
75
+ facets.pattern = new_patterns
76
+ else:
77
+ existing = set(facets.pattern)
78
+ facets.pattern.extend(p for p in new_patterns if p not in existing)
79
+ case 'minLength':
80
+ if facets.min_length is None:
81
+ facets.min_length = get_facet_value(facet)
82
+ case 'maxLength':
83
+ if facets.max_length is None:
84
+ facets.max_length = get_facet_value(facet)
85
+ case 'length':
86
+ if facets.length is None:
87
+ facets.length = get_facet_value(facet)
88
+ case 'minInclusive':
89
+ if facets.min_inclusive is None:
90
+ facets.min_inclusive = get_facet_value(facet)
91
+ case 'maxInclusive':
92
+ if facets.max_inclusive is None:
93
+ facets.max_inclusive = get_facet_value(facet)
94
+ case 'minExclusive':
95
+ if facets.min_exclusive is None:
96
+ facets.min_exclusive = get_facet_value(facet)
97
+ case 'maxExclusive':
98
+ if facets.max_exclusive is None:
99
+ facets.max_exclusive = get_facet_value(facet)
100
+ case 'totalDigits':
101
+ if facets.total_digits is None:
102
+ facets.total_digits = get_facet_value(facet)
103
+ case 'fractionDigits':
104
+ if facets.fraction_digits is None:
105
+ facets.fraction_digits = get_facet_value(facet)
106
+ case 'whiteSpace':
107
+ if facets.white_space is None:
108
+ facets.white_space = get_facet_value(facet)
109
+ def _extract_enumeration(xsd_type: Any, facet: Any) -> list[str]:
110
+ """Extract enumeration values from a type or its facet object.
111
+
112
+ xmlschema stores enumerations in multiple ways:
113
+ - XsdSimpleType.enumeration → list of values directly
114
+ - XsdEnumerationFacets → iterable, .values or .enumeration
115
+ - Fallback: scan raw elem for xs:enumeration value=...
116
+ """
117
+ # Direct enumeration property on the type
118
+ enum = getattr(xsd_type, 'enumeration', None)
119
+ if enum:
120
+ return [str(v) for v in enum]
121
+
122
+ # Facet object approaches
123
+ values = getattr(facet, 'values', None)
124
+ if values:
125
+ return [str(v) for v in values]
126
+
127
+ enum_list = getattr(facet, 'enumeration', None)
128
+ if enum_list:
129
+ result = []
130
+ for item in enum_list:
131
+ v = getattr(item, 'value', None)
132
+ result.append(str(v) if v is not None else str(item))
133
+ return result
134
+
135
+ # Raw XML fallback
136
+ elem = getattr(xsd_type, 'elem', None)
137
+ if elem is not None:
138
+ result = []
139
+ for child in elem.iter():
140
+ tag = getattr(child, 'tag', '')
141
+ if 'enumeration' in str(tag):
142
+ val = child.get('value')
143
+ if val is not None:
144
+ result.append(val)
145
+ if result:
146
+ return result
147
+
148
+ return []
149
+ def _extract_patterns(xsd_type: Any, facet: Any) -> list[str]:
150
+ """Extract regex pattern strings from a type or facet.
151
+
152
+ xmlschema API:
153
+ XsdPatternFacets.regexps: list[str] — the actual regex strings
154
+ XsdSimpleType.patterns: XsdPatternFacets — same object as facet dict entry
155
+ """
156
+ # XsdPatternFacets (the facet dict entry) has .regexps with actual regex strings
157
+ regexps = getattr(facet, 'regexps', None)
158
+ if regexps:
159
+ return [xsd_pattern_to_js(str(r)) for r in regexps]
160
+
161
+ # Fallback: via type.patterns property (same object, different access path)
162
+ patterns = getattr(xsd_type, 'patterns', None)
163
+ if patterns:
164
+ regexps = getattr(patterns, 'regexps', None)
165
+ if regexps:
166
+ return [xsd_pattern_to_js(str(r)) for r in regexps]
167
+
168
+ return []
@@ -0,0 +1,59 @@
1
+ """Extract Namespace from an XSD element."""
2
+ from typing import Any
3
+
4
+ from generate.helpers import local_name, namespace_uri
5
+ from generate.ir import Namespace
6
+ def extract_namespace(xsd_elem: Any) -> Namespace:
7
+ """Build a Namespace from an XsdElement.
8
+
9
+ Uses:
10
+ - xsd_elem.target_namespace → URI
11
+ - xsd_elem.schema.namespaces → prefix lookup
12
+ - xsd_elem.prefixed_name fallback
13
+
14
+ xmlschema API:
15
+ XsdComponent.target_namespace: str
16
+ XMLSchemaBase.namespaces: dict[str, str] (prefix → uri)
17
+ """
18
+ uri = getattr(xsd_elem, 'target_namespace', '') or ''
19
+ prefix = _resolve_prefix(xsd_elem, uri)
20
+ return Namespace(prefix=prefix, uri=uri)
21
+ def extract_attr_namespace(xsd_attr: Any) -> Namespace | None:
22
+ """Build a Namespace for an XsdAttribute, or None if it's in the element's own namespace.
23
+
24
+ A namespace-qualified attribute has a non-empty target_namespace that differs
25
+ from its parent element's target_namespace.
26
+
27
+ xmlschema API:
28
+ XsdAttribute.target_namespace: str
29
+ XsdAttribute.qualified: bool
30
+ XsdAttribute.name: str (Clark notation '{uri}local')
31
+ """
32
+ name = getattr(xsd_attr, 'name', '') or ''
33
+ attr_ns = namespace_uri(name)
34
+
35
+ if not attr_ns:
36
+ return None
37
+
38
+ uri = attr_ns
39
+ prefix = _resolve_prefix(xsd_attr, uri)
40
+ if not prefix:
41
+ return None
42
+ return Namespace(prefix=prefix, uri=uri)
43
+ def _resolve_prefix(component: Any, uri: str) -> str:
44
+ """Find the prefix for a namespace URI by walking up to the schema's namespace map."""
45
+ if not uri:
46
+ return ''
47
+
48
+ schema = getattr(component, 'schema', None)
49
+ if schema is None:
50
+ return ''
51
+
52
+ namespaces = getattr(schema, 'namespaces', {}) or {}
53
+ for pfx, ns_uri in namespaces.items():
54
+ if ns_uri == uri and not pfx:
55
+ return ''
56
+ for pfx, ns_uri in namespaces.items():
57
+ if ns_uri == uri and pfx:
58
+ return pfx
59
+ return ''
@@ -0,0 +1,99 @@
1
+ """Inject explicitly mapped global attributes from the schema into target elements.
2
+
3
+ Reads ``attribute-mapping.json`` from the same directory as the entry XSD.
4
+ Format::
5
+
6
+ {
7
+ "ElementName": {
8
+ "prefix:localName": "namespace-uri",
9
+ ...
10
+ }
11
+ }
12
+
13
+ The key ``prefix:localName`` becomes the attribute key in ``attr_sequence`` and
14
+ ``attributes``. The namespace URI is used to look up the global ``xs:attribute``
15
+ declaration in ``schema.maps.attributes`` via Clark notation ``{uri}localName``.
16
+
17
+ No heuristics — only explicitly declared mappings are injected.
18
+ Pattern mirrors ``orphans.py``.
19
+ """
20
+ import json
21
+ from pathlib import Path
22
+ from typing import Any
23
+
24
+ from generate.extractors.facets import extract_facets
25
+ from generate.extractors.namespace import extract_attr_namespace
26
+ from generate.ir import AttributeDef, ElementDef
27
+
28
+
29
+ def load_attr_mapping(entry_path: Path) -> dict[str, dict[str, str]]:
30
+ """Read attribute-mapping.json from the same directory as the entry XSD.
31
+
32
+ Returns empty dict if the file does not exist.
33
+ """
34
+ mapping_file = entry_path.parent / 'attribute-mapping.json'
35
+ if not mapping_file.exists():
36
+ return {}
37
+ with open(mapping_file) as f:
38
+ return json.load(f)
39
+
40
+
41
+ def inject_mapped_attributes(
42
+ schema: Any,
43
+ elements: dict[str, ElementDef],
44
+ mapping: dict[str, dict[str, str]],
45
+ ) -> int:
46
+ """Inject global attributes into elements as declared in the mapping.
47
+
48
+ For each ``(element_name, attr_key, ns_uri)`` in the mapping:
49
+ - Looks up the global attr in ``schema.maps.attributes`` by Clark name ``{ns_uri}local``.
50
+ - Builds an ``AttributeDef`` from the global attr declaration.
51
+ - Injects into ``element.attributes[attr_key]`` and ``element.attr_sequence``.
52
+ - Idempotent: skips if key already present.
53
+
54
+ Returns total number of attributes injected.
55
+ """
56
+ maps = getattr(schema, 'maps', None)
57
+ global_attrs = getattr(maps, 'attributes', None) if maps else {}
58
+
59
+ injected = 0
60
+
61
+ for element_name, attr_entries in mapping.items():
62
+ elem = elements.get(element_name)
63
+ if elem is None:
64
+ continue
65
+
66
+ for key, ns_uri in attr_entries.items():
67
+ if key in elem.attributes:
68
+ continue # already present
69
+
70
+ # Derive local name from key (strip prefix if present)
71
+ local = key.split(':', 1)[-1]
72
+ clark = f'{{{ns_uri}}}{local}'
73
+ xsd_attr = global_attrs.get(clark)
74
+ if xsd_attr is None:
75
+ continue # not found in schema — skip silently
76
+
77
+ attr_ns = extract_attr_namespace(xsd_attr)
78
+ fixed = getattr(xsd_attr, 'fixed', None)
79
+ default = getattr(xsd_attr, 'default', None) if fixed is None else None
80
+ use = getattr(xsd_attr, 'use', 'optional')
81
+ attr_type = getattr(xsd_attr, 'type', None)
82
+
83
+ elem.attributes[key] = AttributeDef(
84
+ required=use == 'required',
85
+ default=default,
86
+ fixed=fixed,
87
+ namespace=attr_ns,
88
+ facets=extract_facets(attr_type),
89
+ )
90
+ elem.attr_sequence.append(key)
91
+ injected += 1
92
+
93
+ if any(k in elem.attr_sequence for k in attr_entries):
94
+ elem.attr_sequence.sort()
95
+
96
+ return injected
97
+
98
+
99
+ return injected