ipxact-compiler 0.1.0__py3-none-any.whl

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.
ipxact/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ __version__ = "0.1.0"
2
+
3
+ from .parser import parse_element, parse_file
4
+ from .schema import * # noqa: F401,F403
5
+ from .schema import __all__ as _schema_all
6
+
7
+ __all__ = [*_schema_all, "parse_element", "parse_file", "__version__"]
@@ -0,0 +1,3 @@
1
+ from .main_parser import parse_element, parse_file
2
+
3
+ __all__ = ["parse_element", "parse_file"]
@@ -0,0 +1,159 @@
1
+ from __future__ import annotations
2
+
3
+ from lxml import etree
4
+
5
+ from ..schema.abstractiondefinition import (
6
+ AbstractionDefinition,
7
+ AbstractionPort,
8
+ PortConstraints,
9
+ Presence,
10
+ RequiresDriver,
11
+ TransactionalAbstractionPort,
12
+ TransactionalModeConstraints,
13
+ TransactionalSystemConstraints,
14
+ WireAbstractionPort,
15
+ WireModeConstraints,
16
+ WireSystemConstraints,
17
+ )
18
+ from .common_parser import (
19
+ as_bool,
20
+ attr_bool,
21
+ bool_text,
22
+ child,
23
+ children,
24
+ elem_text,
25
+ parse_assertions,
26
+ parse_children,
27
+ parse_choices,
28
+ parse_drive_constraint,
29
+ parse_load_constraint,
30
+ parse_parameters,
31
+ parse_protocol,
32
+ parse_qualifier,
33
+ parse_timing_constraints,
34
+ parse_vendor_extensions,
35
+ parse_vlnv,
36
+ parse_vlnv_ref,
37
+ text,
38
+ )
39
+
40
+
41
+ def parse_abstractiondefinition(root: etree._Element) -> AbstractionDefinition:
42
+ """Parse an ipxact:abstractionDefinition root element into an AbstractionDefinition object."""
43
+ extends_elem = child(root, "extends")
44
+ return AbstractionDefinition(
45
+ vlnv=parse_vlnv(root),
46
+ bus_type=parse_vlnv_ref(child(root, "busType")),
47
+ extends=parse_vlnv_ref(extends_elem) if extends_elem is not None else None,
48
+ ports=parse_children(root, "ports", "port", _parse_abstraction_port),
49
+ choices=parse_choices(root),
50
+ parameters=parse_parameters(root),
51
+ assertions=parse_assertions(root),
52
+ display_name=text(root, "displayName"),
53
+ short_description=text(root, "shortDescription"),
54
+ description=text(root, "description"),
55
+ vendor_extensions=parse_vendor_extensions(root),
56
+ )
57
+
58
+
59
+ def _parse_port_constraints(elem: etree._Element) -> PortConstraints:
60
+ return PortConstraints(
61
+ timing_constraints=parse_timing_constraints(elem),
62
+ drive_constraint=parse_drive_constraint(elem),
63
+ load_constraint=parse_load_constraint(elem),
64
+ )
65
+
66
+
67
+ def _parse_requires_driver(elem: etree._Element) -> RequiresDriver:
68
+ return RequiresDriver(value=as_bool(elem.text, False), driver_type=elem.get("driverType", "any"))
69
+
70
+
71
+ def _parse_wire_mode_constraints(elem: etree._Element) -> WireModeConstraints:
72
+ presence = text(elem, "presence")
73
+ width_elem = child(elem, "width")
74
+ direction_elem = child(elem, "direction")
75
+ mode_constraints_elem = child(elem, "modeConstraints")
76
+ mirrored_mode_constraints_elem = child(elem, "mirroredModeConstraints")
77
+ return WireModeConstraints(
78
+ presence=Presence(presence) if presence else Presence.OPTIONAL,
79
+ width=elem_text(width_elem),
80
+ width_all_bits_required=attr_bool(width_elem, "allBits", False) if width_elem is not None else False,
81
+ # direction defaults to "out" when absent
82
+ direction=elem_text(direction_elem) or "out",
83
+ mode_constraints=(
84
+ _parse_port_constraints(mode_constraints_elem) if mode_constraints_elem is not None else None
85
+ ),
86
+ mirrored_mode_constraints=(
87
+ _parse_port_constraints(mirrored_mode_constraints_elem)
88
+ if mirrored_mode_constraints_elem is not None
89
+ else None
90
+ ),
91
+ )
92
+
93
+
94
+ def _parse_wire_system_constraints(elem: etree._Element) -> WireSystemConstraints:
95
+ return WireSystemConstraints(group=text(elem, "group") or "", constraints=_parse_wire_mode_constraints(elem))
96
+
97
+
98
+ def _parse_wire_abstraction_port(elem: etree._Element) -> WireAbstractionPort:
99
+ on_initiator_elem = child(elem, "onInitiator")
100
+ on_target_elem = child(elem, "onTarget")
101
+ requires_driver_elem = child(elem, "requiresDriver")
102
+ return WireAbstractionPort(
103
+ qualifier=parse_qualifier(elem),
104
+ on_system=[_parse_wire_system_constraints(e) for e in children(elem, "onSystem")],
105
+ on_initiator=_parse_wire_mode_constraints(on_initiator_elem) if on_initiator_elem is not None else None,
106
+ on_target=_parse_wire_mode_constraints(on_target_elem) if on_target_elem is not None else None,
107
+ default_value=text(elem, "defaultValue"),
108
+ requires_driver=_parse_requires_driver(requires_driver_elem) if requires_driver_elem is not None else None,
109
+ )
110
+
111
+
112
+ def _parse_transactional_mode_constraints(elem: etree._Element) -> TransactionalModeConstraints:
113
+ presence = text(elem, "presence")
114
+ initiative_elem = child(elem, "initiative")
115
+ kind_elem = child(elem, "kind")
116
+ return TransactionalModeConstraints(
117
+ presence=Presence(presence) if presence else Presence.OPTIONAL,
118
+ # initiative defaults to "requires" when absent
119
+ initiative=elem_text(initiative_elem) or "requires",
120
+ kind=elem_text(kind_elem),
121
+ bus_width=text(elem, "busWidth"),
122
+ protocol=parse_protocol(elem),
123
+ )
124
+
125
+
126
+ def _parse_transactional_system_constraints(elem: etree._Element) -> TransactionalSystemConstraints:
127
+ return TransactionalSystemConstraints(
128
+ group=text(elem, "group") or "", constraints=_parse_transactional_mode_constraints(elem)
129
+ )
130
+
131
+
132
+ def _parse_transactional_abstraction_port(elem: etree._Element) -> TransactionalAbstractionPort:
133
+ on_initiator_elem = child(elem, "onInitiator")
134
+ on_target_elem = child(elem, "onTarget")
135
+ return TransactionalAbstractionPort(
136
+ qualifier=parse_qualifier(elem),
137
+ on_system=[_parse_transactional_system_constraints(e) for e in children(elem, "onSystem")],
138
+ on_initiator=(
139
+ _parse_transactional_mode_constraints(on_initiator_elem) if on_initiator_elem is not None else None
140
+ ),
141
+ on_target=_parse_transactional_mode_constraints(on_target_elem) if on_target_elem is not None else None,
142
+ )
143
+
144
+
145
+ def _parse_abstraction_port(elem: etree._Element) -> AbstractionPort:
146
+ wire_elem = child(elem, "wire")
147
+ transactional_elem = child(elem, "transactional")
148
+ return AbstractionPort(
149
+ logical_name=text(elem, "logicalName") or "",
150
+ wire=_parse_wire_abstraction_port(wire_elem) if wire_elem is not None else None,
151
+ transactional=(
152
+ _parse_transactional_abstraction_port(transactional_elem) if transactional_elem is not None else None
153
+ ),
154
+ match=bool_text(elem, "match", False),
155
+ display_name=text(elem, "displayName"),
156
+ short_description=text(elem, "shortDescription"),
157
+ description=text(elem, "description"),
158
+ vendor_extensions=parse_vendor_extensions(elem),
159
+ )
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from lxml import etree
4
+
5
+ from ..schema.busdefinition import BusDefinition
6
+ from .common_parser import (
7
+ bool_text,
8
+ child,
9
+ parse_assertions,
10
+ parse_choices,
11
+ parse_parameters,
12
+ parse_texts,
13
+ parse_vendor_extensions,
14
+ parse_vlnv,
15
+ parse_vlnv_ref,
16
+ text,
17
+ )
18
+
19
+
20
+ def parse_busdefinition(root: etree._Element) -> BusDefinition:
21
+ """Parse an ipxact:busDefinition root element into a BusDefinition object."""
22
+ extends_elem = child(root, "extends")
23
+ return BusDefinition(
24
+ vlnv=parse_vlnv(root),
25
+ direct_connection=bool_text(root, "directConnection", False),
26
+ is_addressable=bool_text(root, "isAddressable", False),
27
+ broadcast=bool_text(root, "broadcast"),
28
+ extends=parse_vlnv_ref(extends_elem) if extends_elem is not None else None,
29
+ max_initiators=text(root, "maxInitiators"),
30
+ max_targets=text(root, "maxTargets"),
31
+ system_group_names=parse_texts(root, "systemGroupNames", "systemGroupName"),
32
+ choices=parse_choices(root),
33
+ parameters=parse_parameters(root),
34
+ assertions=parse_assertions(root),
35
+ display_name=text(root, "displayName"),
36
+ short_description=text(root, "shortDescription"),
37
+ description=text(root, "description"),
38
+ vendor_extensions=parse_vendor_extensions(root),
39
+ )
@@ -0,0 +1,443 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Callable, Optional, TypeVar
4
+
5
+ from lxml import etree
6
+
7
+ from ..schema.common import (
8
+ ArrayBound,
9
+ Assertion,
10
+ Choice,
11
+ ChoiceEnumeration,
12
+ File,
13
+ FileBuilder,
14
+ FileSet,
15
+ ModeRef,
16
+ Parameter,
17
+ PartSelect,
18
+ SubPortReference,
19
+ Vector,
20
+ )
21
+ from ..schema.ports import (
22
+ CellSpecification,
23
+ DriveConstraint,
24
+ FlowControlFlag,
25
+ LevelFlag,
26
+ LoadConstraint,
27
+ Payload,
28
+ Protocol,
29
+ Qualifier,
30
+ TimingConstraint,
31
+ UserFlag,
32
+ )
33
+ from ..schema.vlnv import VLNV, VLNVRef
34
+
35
+ NAMESPACE = "http://www.accellera.org/XMLSchema/IPXACT/1685-2022"
36
+
37
+
38
+ def qn(tag: str) -> str:
39
+ """Build a namespace-qualified IP-XACT tag, e.g. qn("component") -> "{ns}component"."""
40
+ return f"{{{NAMESPACE}}}{tag}"
41
+
42
+
43
+ def child(elem: etree._Element, tag: str) -> Optional[etree._Element]:
44
+ return elem.find(qn(tag))
45
+
46
+
47
+ def children(elem: etree._Element, tag: str) -> list[etree._Element]:
48
+ return elem.findall(qn(tag))
49
+
50
+
51
+ T = TypeVar("T")
52
+
53
+
54
+ def parse_children(
55
+ elem: etree._Element, container_tag: str, item_tag: str, parse_fn: Callable[[etree._Element], T]
56
+ ) -> list[T]:
57
+ """Parse item_tag children of elem's optional container_tag container, or [] if absent.
58
+
59
+ Covers the common IP-XACT shape of an optional wrapper element (e.g. busInterfaces)
60
+ holding one or more repeated items (e.g. busInterface).
61
+ """
62
+ container = child(elem, container_tag)
63
+ if container is None:
64
+ return []
65
+ return [parse_fn(e) for e in children(container, item_tag)]
66
+
67
+
68
+ def parse_texts(elem: etree._Element, container_tag: str, item_tag: str) -> list[str]:
69
+ """Text-list analogue of parse_children: item_tag text values inside elem's
70
+ optional container_tag container, or [] if absent.
71
+ """
72
+ container = child(elem, container_tag)
73
+ if container is None:
74
+ return []
75
+ return texts(container, item_tag)
76
+
77
+
78
+ def elem_text(elem: Optional[etree._Element]) -> Optional[str]:
79
+ """Strip an already-located element's text content, or None if absent/empty.
80
+
81
+ lxml returns element text verbatim, including any indentation whitespace a
82
+ pretty-printed or hand-edited file wraps around it (e.g. "\\n required\\n").
83
+ Left unstripped, that whitespace flows into enum construction (Direction(...),
84
+ Presence(...), ...) and raises ValueError on otherwise-valid input.
85
+ """
86
+ if elem is None or elem.text is None:
87
+ return None
88
+ return elem.text.strip()
89
+
90
+
91
+ def text(elem: Optional[etree._Element], tag: str) -> Optional[str]:
92
+ if elem is None:
93
+ return None
94
+ return elem_text(child(elem, tag))
95
+
96
+
97
+ def texts(elem: etree._Element, tag: str) -> list[str]:
98
+ return [t for t in (elem_text(e) for e in children(elem, tag)) if t is not None]
99
+
100
+
101
+ def as_bool(value: Optional[str], default: Optional[bool] = None) -> Optional[bool]:
102
+ if value is None:
103
+ return default
104
+ return value.strip().lower() in ("true", "1")
105
+
106
+
107
+ def bool_text(elem: etree._Element, tag: str, default: Optional[bool] = None) -> Optional[bool]:
108
+ return as_bool(text(elem, tag), default)
109
+
110
+
111
+ def attr_bool(elem: etree._Element, name: str, default: bool = False) -> bool:
112
+ return as_bool(elem.get(name), default)
113
+
114
+
115
+ def parse_vlnv(elem: etree._Element) -> VLNV:
116
+ """documentNameGroup - vendor/library/name/version as child elements."""
117
+ return VLNV(
118
+ vendor=text(elem, "vendor") or "",
119
+ library=text(elem, "library") or "",
120
+ name=text(elem, "name") or "",
121
+ version=text(elem, "version") or "",
122
+ )
123
+
124
+
125
+ def parse_configurable_element_values(elem: etree._Element) -> dict[str, str]:
126
+ """ipxact:configurableElementValues - a set of parameterId-keyed override expressions."""
127
+ config_values: dict[str, str] = {}
128
+ values_container = child(elem, "configurableElementValues")
129
+ if values_container is not None:
130
+ for value_elem in children(values_container, "configurableElementValue"):
131
+ reference_id = value_elem.get("referenceId")
132
+ if reference_id is not None:
133
+ config_values[reference_id] = elem_text(value_elem) or ""
134
+ return config_values
135
+
136
+
137
+ def parse_vlnv_ref(elem: etree._Element) -> VLNVRef:
138
+ """libraryRefType/configurableLibraryRefType - vendor/library/name/version as attributes."""
139
+ return VLNVRef(
140
+ vendor=elem.get("vendor", ""),
141
+ library=elem.get("library", ""),
142
+ name=elem.get("name", ""),
143
+ version=elem.get("version", ""),
144
+ config_element_values=parse_configurable_element_values(elem),
145
+ )
146
+
147
+
148
+ def parse_vector(elem: etree._Element) -> Vector:
149
+ return Vector(left=text(elem, "left") or "", right=text(elem, "right") or "", vector_id=elem.get("vectorId"))
150
+
151
+
152
+ def parse_vectors(elem: etree._Element) -> list[Vector]:
153
+ container = child(elem, "vectors")
154
+ if container is None:
155
+ return []
156
+ return [parse_vector(v) for v in children(container, "vector")]
157
+
158
+
159
+ def parse_array_bound(elem: etree._Element) -> ArrayBound:
160
+ return ArrayBound(left=text(elem, "left") or "", right=text(elem, "right") or "", array_id=elem.get("arrayId"))
161
+
162
+
163
+ def parse_array_bounds(elem: etree._Element) -> list[ArrayBound]:
164
+ container = child(elem, "arrays")
165
+ if container is None:
166
+ return []
167
+ return [parse_array_bound(a) for a in children(container, "array")]
168
+
169
+
170
+ def parse_mode_ref(elem: etree._Element) -> ModeRef:
171
+ priority = elem.get("priority")
172
+ return ModeRef(name=elem_text(elem) or "", priority=int(priority) if priority is not None else 0)
173
+
174
+
175
+ def parse_mode_refs(elem: etree._Element, tag: str = "modeRef") -> list[ModeRef]:
176
+ return [parse_mode_ref(m) for m in children(elem, tag)]
177
+
178
+
179
+ def parse_part_select(elem: etree._Element) -> Optional[PartSelect]:
180
+ part_select = child(elem, "partSelect")
181
+ if part_select is None:
182
+ return None
183
+ range_elem = child(part_select, "range")
184
+ return PartSelect(
185
+ range_left=text(range_elem, "left") if range_elem is not None else None,
186
+ range_right=text(range_elem, "right") if range_elem is not None else None,
187
+ indices=parse_texts(part_select, "indices", "index"),
188
+ )
189
+
190
+
191
+ def parse_sub_port_reference(elem: etree._Element) -> SubPortReference:
192
+ return SubPortReference(sub_port_ref=elem.get("subPortRef", ""), part_select=parse_part_select(elem))
193
+
194
+
195
+ def parse_sub_port_references(elem: etree._Element, tag: str = "subPortReference") -> list[SubPortReference]:
196
+ return [parse_sub_port_reference(s) for s in children(elem, tag)]
197
+
198
+
199
+ def parse_vendor_extensions(elem: etree._Element) -> list[str]:
200
+ container = child(elem, "vendorExtensions")
201
+ if container is None:
202
+ return []
203
+ return [etree.tostring(e, encoding="unicode") for e in container]
204
+
205
+
206
+ def parse_parameter(elem: etree._Element) -> Parameter:
207
+ config_groups = elem.get("configGroups")
208
+ order = elem.get("order")
209
+ return Parameter(
210
+ name=text(elem, "name") or "",
211
+ value=text(elem, "value") or "",
212
+ display_name=text(elem, "displayName"),
213
+ short_description=text(elem, "shortDescription"),
214
+ description=text(elem, "description"),
215
+ vectors=parse_vectors(elem),
216
+ arrays=parse_array_bounds(elem),
217
+ parameter_id=elem.get("parameterId"),
218
+ prompt=elem.get("prompt"),
219
+ choice_ref=elem.get("choiceRef"),
220
+ order=float(order) if order is not None else None,
221
+ config_groups=config_groups.split() if config_groups else [],
222
+ minimum=elem.get("minimum"),
223
+ maximum=elem.get("maximum"),
224
+ type=elem.get("type", "string"),
225
+ sign=elem.get("sign"),
226
+ prefix=elem.get("prefix"),
227
+ unit=elem.get("unit"),
228
+ resolve=elem.get("resolve", "immediate"),
229
+ vendor_extensions=parse_vendor_extensions(elem),
230
+ )
231
+
232
+
233
+ def parse_parameters(elem: etree._Element) -> list[Parameter]:
234
+ container = child(elem, "parameters")
235
+ if container is None:
236
+ return []
237
+ return [parse_parameter(p) for p in children(container, "parameter")]
238
+
239
+
240
+ def parse_choice_enumeration(elem: etree._Element) -> ChoiceEnumeration:
241
+ return ChoiceEnumeration(value=elem_text(elem) or "", text=elem.get("text"), help=elem.get("help"))
242
+
243
+
244
+ def parse_choice(elem: etree._Element) -> Choice:
245
+ return Choice(
246
+ name=text(elem, "name") or "",
247
+ enumerations=[parse_choice_enumeration(e) for e in children(elem, "enumeration")],
248
+ )
249
+
250
+
251
+ def parse_choices(elem: etree._Element) -> list[Choice]:
252
+ container = child(elem, "choices")
253
+ if container is None:
254
+ return []
255
+ return [parse_choice(c) for c in children(container, "choice")]
256
+
257
+
258
+ def parse_assertion(elem: etree._Element) -> Assertion:
259
+ return Assertion(
260
+ name=text(elem, "name") or "",
261
+ assert_expression=text(elem, "assert") or "",
262
+ display_name=text(elem, "displayName"),
263
+ description=text(elem, "description"),
264
+ )
265
+
266
+
267
+ def parse_assertions(elem: etree._Element) -> list[Assertion]:
268
+ container = child(elem, "assertions")
269
+ if container is None:
270
+ return []
271
+ return [parse_assertion(a) for a in children(container, "assertion")]
272
+
273
+
274
+ def parse_file_builder(elem: etree._Element) -> FileBuilder:
275
+ return FileBuilder(
276
+ file_type=text(elem, "fileType") or "",
277
+ command=text(elem, "command"),
278
+ flags=text(elem, "flags"),
279
+ replace_default_flags=text(elem, "replaceDefaultFlags"),
280
+ )
281
+
282
+
283
+ def parse_file(elem: etree._Element) -> File:
284
+ is_include = child(elem, "isIncludeFile")
285
+ build_command = child(elem, "buildCommand")
286
+ return File(
287
+ name=text(elem, "name") or "",
288
+ file_types=[elem_text(t) or "" for t in children(elem, "fileType")],
289
+ is_structural=bool_text(elem, "isStructural", False),
290
+ is_include_file=is_include is not None and as_bool(is_include.text, False) is True,
291
+ include_has_external_declarations=is_include is not None
292
+ and attr_bool(is_include, "externalDeclarations", False),
293
+ logical_name=text(elem, "logicalName"),
294
+ exported_names=texts(elem, "exportedName"),
295
+ build_command=text(build_command, "command") if build_command is not None else None,
296
+ build_flags=text(build_command, "flags") if build_command is not None else None,
297
+ vendor_extensions=parse_vendor_extensions(elem),
298
+ )
299
+
300
+
301
+ def parse_file_set(elem: etree._Element) -> FileSet:
302
+ return FileSet(
303
+ name=text(elem, "name") or "",
304
+ groups=texts(elem, "group"),
305
+ files=[parse_file(f) for f in children(elem, "file")],
306
+ default_file_builders=[parse_file_builder(b) for b in children(elem, "defaultFileBuilder")],
307
+ dependencies=texts(elem, "dependency"),
308
+ display_name=text(elem, "displayName"),
309
+ description=text(elem, "description"),
310
+ vendor_extensions=parse_vendor_extensions(elem),
311
+ )
312
+
313
+
314
+ def parse_file_sets(elem: etree._Element) -> list[FileSet]:
315
+ container = child(elem, "fileSets")
316
+ if container is None:
317
+ return []
318
+ return [parse_file_set(fs) for fs in children(container, "fileSet")]
319
+
320
+
321
+ def parse_qualifier(elem: etree._Element) -> Optional[Qualifier]:
322
+ """ipxact:qualifier - shared by component ports and abstraction definition logical ports."""
323
+ container = child(elem, "qualifier")
324
+ if container is None:
325
+ return None
326
+ is_reset_elem = child(container, "isReset")
327
+ is_clock_en_elem = child(container, "isClockEn")
328
+ is_power_en_elem = child(container, "isPowerEn")
329
+ is_flow_control_elem = child(container, "isFlowControl")
330
+ is_user_elem = child(container, "isUser")
331
+ return Qualifier(
332
+ is_address=bool_text(container, "isAddress"),
333
+ is_data=bool_text(container, "isData"),
334
+ is_clock=bool_text(container, "isClock"),
335
+ is_reset=(
336
+ LevelFlag(value=as_bool(is_reset_elem.text) or False, level=is_reset_elem.get("level"))
337
+ if is_reset_elem is not None
338
+ else None
339
+ ),
340
+ is_valid=bool_text(container, "isValid"),
341
+ is_interrupt=bool_text(container, "isInterrupt"),
342
+ is_clock_en=(
343
+ LevelFlag(
344
+ value=as_bool(is_clock_en_elem.text) or False,
345
+ level=is_clock_en_elem.get("level"),
346
+ power_domain_ref=is_clock_en_elem.get("powerDomainRef"),
347
+ )
348
+ if is_clock_en_elem is not None
349
+ else None
350
+ ),
351
+ is_power_en=(
352
+ LevelFlag(
353
+ value=as_bool(is_power_en_elem.text) or False,
354
+ level=is_power_en_elem.get("level"),
355
+ power_domain_ref=is_power_en_elem.get("powerDomainRef"),
356
+ )
357
+ if is_power_en_elem is not None
358
+ else None
359
+ ),
360
+ is_opcode=bool_text(container, "isOpcode"),
361
+ is_protection=bool_text(container, "isProtection"),
362
+ is_flow_control=(
363
+ FlowControlFlag(
364
+ value=as_bool(is_flow_control_elem.text) or False,
365
+ flow_type=is_flow_control_elem.get("flowType"),
366
+ user=is_flow_control_elem.get("user"),
367
+ )
368
+ if is_flow_control_elem is not None
369
+ else None
370
+ ),
371
+ is_user=(
372
+ UserFlag(value=as_bool(is_user_elem.text) or False, user=is_user_elem.get("user"))
373
+ if is_user_elem is not None
374
+ else None
375
+ ),
376
+ is_request=bool_text(container, "isRequest"),
377
+ is_response=bool_text(container, "isResponse"),
378
+ )
379
+
380
+
381
+ def parse_cell_specification(elem: Optional[etree._Element]) -> CellSpecification:
382
+ if elem is None:
383
+ return CellSpecification()
384
+ cell_function_elem = child(elem, "cellFunction")
385
+ cell_class_elem = child(elem, "cellClass")
386
+ return CellSpecification(
387
+ cell_function=elem_text(cell_function_elem),
388
+ cell_class=elem_text(cell_class_elem),
389
+ cell_strength=elem.get("cellStrength"),
390
+ )
391
+
392
+
393
+ def parse_drive_constraint(elem: etree._Element) -> Optional[DriveConstraint]:
394
+ container = child(elem, "driveConstraint")
395
+ if container is None:
396
+ return None
397
+ return DriveConstraint(cell=parse_cell_specification(child(container, "cellSpecification")))
398
+
399
+
400
+ def parse_load_constraint(elem: etree._Element) -> Optional[LoadConstraint]:
401
+ container = child(elem, "loadConstraint")
402
+ if container is None:
403
+ return None
404
+ return LoadConstraint(
405
+ cell=parse_cell_specification(child(container, "cellSpecification")),
406
+ count=text(container, "count") or "3",
407
+ )
408
+
409
+
410
+ def parse_timing_constraints(elem: etree._Element) -> list[TimingConstraint]:
411
+ return [
412
+ TimingConstraint(
413
+ value=elem_text(t) or "",
414
+ clock_name=t.get("clockName", ""),
415
+ clock_edge=t.get("clockEdge"),
416
+ delay_type=t.get("delayType"),
417
+ )
418
+ for t in children(elem, "timingConstraint")
419
+ ]
420
+
421
+
422
+ def parse_payload(elem: Optional[etree._Element]) -> Optional[Payload]:
423
+ if elem is None:
424
+ return None
425
+ extension_elem = child(elem, "extension")
426
+ return Payload(
427
+ type=text(elem, "type") or "",
428
+ name=text(elem, "name"),
429
+ extension=elem_text(extension_elem),
430
+ extension_mandatory=attr_bool(extension_elem, "mandatory", False) if extension_elem is not None else False,
431
+ )
432
+
433
+
434
+ def parse_protocol(elem: etree._Element) -> Optional[Protocol]:
435
+ container = child(elem, "protocol")
436
+ if container is None:
437
+ return None
438
+ protocol_type_elem = child(container, "protocolType")
439
+ return Protocol(
440
+ protocol_type=elem_text(protocol_type_elem) or "",
441
+ custom_type_name=protocol_type_elem.get("custom") if protocol_type_elem is not None else None,
442
+ payload=parse_payload(child(container, "payload")),
443
+ )