odxtools 9.4.1__py3-none-any.whl → 9.6.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.
Files changed (113) hide show
  1. odxtools/additionalaudience.py +2 -2
  2. odxtools/admindata.py +3 -0
  3. odxtools/audience.py +9 -13
  4. odxtools/basecomparam.py +1 -2
  5. odxtools/basevariantpattern.py +38 -0
  6. odxtools/basicstructure.py +34 -35
  7. odxtools/commrelation.py +2 -1
  8. odxtools/companydata.py +1 -2
  9. odxtools/companyspecificinfo.py +3 -0
  10. odxtools/comparam.py +16 -8
  11. odxtools/comparaminstance.py +12 -12
  12. odxtools/comparamspec.py +4 -3
  13. odxtools/comparamsubset.py +26 -24
  14. odxtools/compumethods/compuconst.py +4 -4
  15. odxtools/compumethods/limit.py +9 -9
  16. odxtools/compumethods/linearsegment.py +8 -8
  17. odxtools/dataobjectproperty.py +16 -18
  18. odxtools/description.py +4 -2
  19. odxtools/determinenumberofitems.py +4 -4
  20. odxtools/diagcodedtype.py +20 -20
  21. odxtools/diagcomm.py +61 -41
  22. odxtools/diagdatadictionaryspec.py +51 -55
  23. odxtools/diaglayercontainer.py +25 -25
  24. odxtools/diaglayers/basevariant.py +5 -0
  25. odxtools/diaglayers/basevariantraw.py +7 -1
  26. odxtools/diaglayers/diaglayerraw.py +26 -27
  27. odxtools/diaglayers/ecuvariant.py +16 -0
  28. odxtools/diagnostictroublecode.py +13 -10
  29. odxtools/diagservice.py +48 -50
  30. odxtools/diagvariable.py +10 -8
  31. odxtools/docrevision.py +5 -5
  32. odxtools/dtcdop.py +17 -17
  33. odxtools/dynamicendmarkerfield.py +8 -8
  34. odxtools/dynamiclengthfield.py +2 -0
  35. odxtools/dyndefinedspec.py +21 -8
  36. odxtools/ecuvariantpattern.py +20 -9
  37. odxtools/encodestate.py +3 -3
  38. odxtools/endofpdufield.py +7 -9
  39. odxtools/environmentdatadescription.py +9 -20
  40. odxtools/field.py +21 -21
  41. odxtools/inputparam.py +15 -14
  42. odxtools/leadinglengthinfotype.py +4 -4
  43. odxtools/matchingbasevariantparameter.py +38 -0
  44. odxtools/matchingparameter.py +108 -28
  45. odxtools/minmaxlengthtype.py +6 -6
  46. odxtools/multiplexer.py +38 -39
  47. odxtools/multiplexercase.py +3 -6
  48. odxtools/multiplexerdefaultcase.py +3 -6
  49. odxtools/multiplexerswitchkey.py +4 -4
  50. odxtools/negoutputparam.py +6 -9
  51. odxtools/odxlink.py +21 -5
  52. odxtools/odxtypes.py +7 -6
  53. odxtools/outputparam.py +9 -8
  54. odxtools/parameterinfo.py +1 -1
  55. odxtools/parameters/codedconstparameter.py +28 -27
  56. odxtools/parameters/dynamicparameter.py +9 -9
  57. odxtools/parameters/lengthkeyparameter.py +18 -18
  58. odxtools/parameters/matchingrequestparameter.py +15 -15
  59. odxtools/parameters/nrcconstparameter.py +32 -24
  60. odxtools/parameters/parameter.py +35 -37
  61. odxtools/parameters/parameterwithdop.py +6 -6
  62. odxtools/parameters/physicalconstantparameter.py +19 -20
  63. odxtools/parameters/reservedparameter.py +10 -11
  64. odxtools/parameters/systemparameter.py +10 -11
  65. odxtools/parameters/tableentryparameter.py +19 -20
  66. odxtools/parameters/tablekeyparameter.py +0 -2
  67. odxtools/parameters/tablestructparameter.py +27 -21
  68. odxtools/parameters/valueparameter.py +20 -20
  69. odxtools/parentref.py +6 -7
  70. odxtools/physicaldimension.py +11 -11
  71. odxtools/physicaltype.py +9 -14
  72. odxtools/preconditionstateref.py +85 -0
  73. odxtools/progcode.py +1 -2
  74. odxtools/protstack.py +4 -4
  75. odxtools/relateddoc.py +3 -4
  76. odxtools/scaleconstr.py +0 -1
  77. odxtools/singleecujob.py +8 -4
  78. odxtools/specialdata.py +10 -9
  79. odxtools/specialdatagroup.py +1 -0
  80. odxtools/standardlengthtype.py +18 -18
  81. odxtools/statechart.py +10 -6
  82. odxtools/statemachine.py +186 -0
  83. odxtools/statetransitionref.py +231 -0
  84. odxtools/structure.py +4 -4
  85. odxtools/subcomponent.py +78 -11
  86. odxtools/table.py +23 -13
  87. odxtools/tablerow.py +86 -69
  88. odxtools/teammember.py +4 -4
  89. odxtools/templates/macros/printBaseVariant.xml.jinja2 +4 -9
  90. odxtools/templates/macros/printBaseVariantPattern.xml.jinja2 +32 -0
  91. odxtools/templates/macros/printCompanyData.xml.jinja2 +2 -2
  92. odxtools/templates/macros/printComparam.xml.jinja2 +3 -5
  93. odxtools/templates/macros/printDOP.xml.jinja2 +4 -1
  94. odxtools/templates/macros/printDiagComm.xml.jinja2 +6 -5
  95. odxtools/templates/macros/printEcuVariantPattern.xml.jinja2 +7 -6
  96. odxtools/templates/macros/printParam.xml.jinja2 +5 -5
  97. odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +18 -0
  98. odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +18 -0
  99. odxtools/templates/macros/printTable.xml.jinja2 +13 -9
  100. odxtools/text.py +35 -0
  101. odxtools/unit.py +1 -3
  102. odxtools/unitgroup.py +6 -8
  103. odxtools/variantmatcher.py +209 -0
  104. odxtools/variantpattern.py +38 -0
  105. odxtools/version.py +2 -2
  106. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/METADATA +3 -2
  107. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/RECORD +111 -102
  108. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/WHEEL +1 -1
  109. odxtools/createecuvariantpatterns.py +0 -18
  110. odxtools/ecuvariantmatcher.py +0 -171
  111. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/entry_points.txt +0 -0
  112. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info/licenses}/LICENSE +0 -0
  113. {odxtools-9.4.1.dist-info → odxtools-9.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,231 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
4
+ from xml.etree import ElementTree
5
+
6
+ from .basicstructure import BasicStructure
7
+ from .dataobjectproperty import DataObjectProperty
8
+ from .exceptions import odxassert, odxraise, odxrequire
9
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
10
+ from .odxtypes import ParameterValue, ParameterValueDict
11
+ from .parameters.codedconstparameter import CodedConstParameter
12
+ from .parameters.parameter import Parameter
13
+ from .parameters.physicalconstantparameter import PhysicalConstantParameter
14
+ from .parameters.tablekeyparameter import TableKeyParameter
15
+ from .parameters.tablestructparameter import TableStructParameter
16
+ from .parameters.valueparameter import ValueParameter
17
+ from .snrefcontext import SnRefContext
18
+ from .state import State
19
+ from .statemachine import StateMachine
20
+ from .statetransition import StateTransition
21
+ from .utils import dataclass_fields_asdict
22
+
23
+ if TYPE_CHECKING:
24
+ from .preconditionstateref import PreConditionStateRef
25
+ from .statemachine import StateMachine
26
+ from .tablerow import TableRow
27
+
28
+
29
+ def _resolve_in_param(
30
+ in_param_if_snref: Optional[str],
31
+ in_param_if_snpathref: Optional[str],
32
+ params: List[Parameter],
33
+ param_dict: ParameterValueDict,
34
+ ) -> Tuple[Optional[Parameter], Optional[ParameterValue]]:
35
+
36
+ if in_param_if_snref is not None:
37
+ path_chunks = [in_param_if_snref]
38
+ elif in_param_if_snpathref is not None:
39
+ path_chunks = in_param_if_snpathref.split(".")
40
+ else:
41
+ return None, None
42
+
43
+ return _resolve_in_param_helper(params, param_dict, path_chunks)
44
+
45
+
46
+ def _resolve_in_param_helper(
47
+ params: List[Parameter],
48
+ param_dict: ParameterValueDict,
49
+ path_chunks: List[str],
50
+ ) -> Tuple[Optional[Parameter], Optional[ParameterValue]]:
51
+
52
+ inner_param = resolve_snref(path_chunks[0], params, Parameter, lenient=True)
53
+ if inner_param is None:
54
+ return None, None
55
+ inner_param_value = param_dict.get(path_chunks[0])
56
+
57
+ if len(path_chunks) == 1:
58
+ return inner_param, inner_param_value
59
+
60
+ # deal with table parameters
61
+ if isinstance(inner_param, TableStructParameter):
62
+ if not isinstance(inner_param_value, tuple) or len(inner_param_value) == 2:
63
+ odxraise("Invalid value for table struct parameter")
64
+ return None, None
65
+
66
+ if TYPE_CHECKING:
67
+ tr = resolve_snref(inner_param_value[0], inner_param.table.table_rows, TableRow)
68
+ else:
69
+ tr = resolve_snref(inner_param_value[0], inner_param.table.table_rows)
70
+
71
+ if tr.structure is None:
72
+ odxraise("Table row must reference a structure")
73
+ return None, None
74
+
75
+ inner_param_list = tr.structure.parameters
76
+ inner_param_value = inner_param_value[1]
77
+ else:
78
+ dop = getattr(inner_param, "dop", None)
79
+ if isinstance(dop, BasicStructure):
80
+ inner_param_list = dop.parameters
81
+ else:
82
+ odxraise(f"Expected the referenced parameter to be composite")
83
+
84
+ if not isinstance(inner_param_value, dict):
85
+ odxraise(f"Expected the referenced parameter to be composite")
86
+ return None, None
87
+
88
+ return _resolve_in_param_helper(inner_param_list, inner_param_value, path_chunks[1:])
89
+
90
+
91
+ def _check_applies(ref: Union["StateTransitionRef",
92
+ "PreConditionStateRef"], state_machine: "StateMachine",
93
+ params: List[Parameter], param_value_dict: ParameterValueDict) -> bool:
94
+ if state_machine.active_state != ref.state:
95
+ # if the active state of the state machine is not the
96
+ # specified one, the precondition does not apply
97
+ return False
98
+
99
+ if ref.in_param_if_snref is None and ref.in_param_if_snpathref is None:
100
+ # no parameter given -> only the specified state is relevant
101
+ return True
102
+
103
+ param, param_value = \
104
+ _resolve_in_param(ref.in_param_if_snref, ref.in_param_if_snpathref, params, param_value_dict)
105
+
106
+ if param is None:
107
+ # The referenced parameter does not exist.
108
+ return False
109
+ elif not isinstance(
110
+ param,
111
+ (CodedConstParameter, PhysicalConstantParameter, TableKeyParameter, ValueParameter)):
112
+ # see checker rule 194 in section B.2 of the spec
113
+ odxraise(f"Parameter referenced by state transition ref is of "
114
+ f"invalid type {type(param).__name__}")
115
+ return False
116
+ elif isinstance(param, (CodedConstParameter, PhysicalConstantParameter,
117
+ TableKeyParameter)) and ref.value is not None:
118
+ # see checker rule 193 in section B.2 of the spec. Why can
119
+ # no values for constant parameters be specified? (This
120
+ # seems to be rather inconvenient...)
121
+ odxraise(f"No value may be specified for state transitions referencing "
122
+ f"parameters of type {type(param).__name__}")
123
+ return False
124
+ elif isinstance(param, ValueParameter):
125
+ # see checker rule 193 in section B.2 of the spec
126
+ if ref.value is None:
127
+ odxraise(f"An expected VALUE must be specified for preconditions "
128
+ f"referencing VALUE parameters")
129
+ return False
130
+
131
+ if param_value is None:
132
+ param_value = param.physical_default_value
133
+ if param_value is None:
134
+ odxraise(f"No value can be determined for parameter {param.short_name}")
135
+ return False
136
+
137
+ if param.dop is None or not isinstance(param.dop, DataObjectProperty):
138
+ odxraise(f"Parameter {param.short_name} uses a non-simple DOP")
139
+ return False
140
+
141
+ expected_value = param.dop.physical_type.base_data_type.from_string(ref.value)
142
+
143
+ if expected_value != param_value:
144
+ return False
145
+
146
+ return True
147
+
148
+
149
+ @dataclass
150
+ class StateTransitionRef(OdxLinkRef):
151
+ """Describes a state transition that is to be potentially taken if
152
+ a diagnostic communication is executed
153
+
154
+ Besides the "raw" state transistion, state transition references
155
+ may also be conditional on the observed response of the ECU.
156
+
157
+ """
158
+ value: Optional[str]
159
+
160
+ in_param_if_snref: Optional[str]
161
+ in_param_if_snpathref: Optional[str]
162
+
163
+ @property
164
+ def state_transition(self) -> StateTransition:
165
+ return self._state_transition
166
+
167
+ @property
168
+ def state(self) -> State:
169
+ return self.state_transition.source_state
170
+
171
+ @staticmethod
172
+ def from_et( # type: ignore[override]
173
+ et_element: ElementTree.Element,
174
+ doc_frags: List[OdxDocFragment]) -> "StateTransitionRef":
175
+ kwargs = dataclass_fields_asdict(OdxLinkRef.from_et(et_element, doc_frags))
176
+
177
+ value = et_element.findtext("VALUE")
178
+
179
+ in_param_if_snref = None
180
+ if (in_param_if_snref_elem := et_element.find("IN-PARAM-IF-SNREF")) is not None:
181
+ in_param_if_snref = odxrequire(in_param_if_snref_elem.get("SHORT-NAME"))
182
+
183
+ in_param_if_snpathref = None
184
+ if (in_param_if_snpathref_elem := et_element.find("IN-PARAM-IF-SNPATHREF")) is not None:
185
+ in_param_if_snpathref = odxrequire(in_param_if_snpathref_elem.get("SHORT-NAME-PATH"))
186
+
187
+ return StateTransitionRef(
188
+ value=value,
189
+ in_param_if_snref=in_param_if_snref,
190
+ in_param_if_snpathref=in_param_if_snpathref,
191
+ **kwargs)
192
+
193
+ def __post_init__(self) -> None:
194
+ if self.value is not None:
195
+ odxassert(self.in_param_if_snref is not None or self.in_param_if_snref is not None,
196
+ "If VALUE is specified, a parameter must be referenced")
197
+
198
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
199
+ return {}
200
+
201
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
202
+ self._state_transition = odxlinks.resolve(self, StateTransition)
203
+
204
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
205
+ pass
206
+
207
+ def execute(self, state_machine: StateMachine, params: List[Parameter],
208
+ param_value_dict: ParameterValueDict) -> bool:
209
+ """Update a StateMachine object if the state transition ought
210
+ to be executed based on the response received after executing a
211
+ diagnostic communication
212
+
213
+ Note that the specification is unclear about what the
214
+ parameters are: It says "The optional VALUE together with the
215
+ also optional IN-PARAM-IF snref at STATE-TRANSITION-REF and
216
+ PRE-CONDITION-STATE-REF can be used if the STATE-TRANSITIONs
217
+ and pre-condition STATEs are dependent on the values of the
218
+ referenced PARAMs.", but it does not specify what the
219
+ "referenced PARAMs" are. For the state transition refs, let's
220
+ assume that they are the parameters of a response received
221
+ from the ECU, whilst we assume that they are the parameters of
222
+ the request for PRE-CONDITION-STATE-REFs.
223
+
224
+ Returns:
225
+ ``True´` if the state transition was taken, else ``False``
226
+ """
227
+ if _check_applies(self, state_machine, params, param_value_dict):
228
+ state_machine.active_state = self.state_transition.target_state
229
+ return True
230
+
231
+ return False
odxtools/structure.py CHANGED
@@ -13,6 +13,10 @@ from .utils import dataclass_fields_asdict
13
13
  class Structure(BasicStructure):
14
14
  is_visible_raw: Optional[bool]
15
15
 
16
+ @property
17
+ def is_visible(self) -> bool:
18
+ return self.is_visible_raw in (True, None)
19
+
16
20
  @staticmethod
17
21
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Structure":
18
22
  """Read a STRUCTURE element from XML."""
@@ -21,7 +25,3 @@ class Structure(BasicStructure):
21
25
  is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
22
26
 
23
27
  return Structure(is_visible_raw=is_visible_raw, **kwargs)
24
-
25
- @property
26
- def is_visible(self) -> bool:
27
- return self.is_visible_raw in (True, None)
odxtools/subcomponent.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Dict, List, Optional
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
6
  from .diagnostictroublecode import DiagnosticTroubleCode
@@ -9,8 +9,7 @@ from .dtcdop import DtcDop
9
9
  from .element import IdentifiableElement, NamedElement
10
10
  from .environmentdata import EnvironmentData
11
11
  from .environmentdatadescription import EnvironmentDataDescription
12
- from .exceptions import odxraise, odxrequire
13
- from .matchingparameter import MatchingParameter
12
+ from .exceptions import odxassert, odxraise, odxrequire
14
13
  from .nameditemlist import NamedItemList
15
14
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
16
15
  from .parameters.parameter import Parameter
@@ -19,14 +18,18 @@ from .table import Table
19
18
  from .tablerow import TableRow
20
19
  from .utils import dataclass_fields_asdict
21
20
 
21
+ if TYPE_CHECKING:
22
+ from .matchingparameter import MatchingParameter
23
+
22
24
 
23
25
  @dataclass
24
26
  class SubComponentPattern:
25
- matching_parameters: List[MatchingParameter]
27
+ matching_parameters: List["MatchingParameter"]
26
28
 
27
29
  @staticmethod
28
30
  def from_et(et_element: ElementTree.Element,
29
31
  doc_frags: List[OdxDocFragment]) -> "SubComponentPattern":
32
+ from .matchingparameter import MatchingParameter
30
33
 
31
34
  matching_parameters = [
32
35
  MatchingParameter.from_et(el, doc_frags)
@@ -35,6 +38,21 @@ class SubComponentPattern:
35
38
 
36
39
  return SubComponentPattern(matching_parameters=matching_parameters)
37
40
 
41
+ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
42
+ result = {}
43
+ for mp in self.matching_parameters:
44
+ result.update(mp._build_odxlinks())
45
+
46
+ return result
47
+
48
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
49
+ for mp in self.matching_parameters:
50
+ mp._resolve_odxlinks(odxlinks)
51
+
52
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
53
+ for mp in self.matching_parameters:
54
+ mp._resolve_snrefs(context)
55
+
38
56
 
39
57
  @dataclass
40
58
  class SubComponentParamConnector(IdentifiableElement):
@@ -69,16 +87,18 @@ class SubComponentParamConnector(IdentifiableElement):
69
87
  if elem.tag != "OUT-PARAM-IF-SNREF":
70
88
  odxraise("Currently, only SNREFS are supported for OUT-PARAM-IF-REFS")
71
89
  continue
72
-
73
- out_param_if_refs.append(odxrequire(elem.get("SHORT-NAME")))
90
+ else:
91
+ odxassert(elem.tag == "OUT-PARAM-IF-SNREF")
92
+ out_param_if_refs.append(odxrequire(elem.attrib.get("SHORT-NAME")))
74
93
 
75
94
  in_param_if_refs = []
76
95
  for elem in et_element.find("IN-PARAM-IF-REFS") or []:
77
96
  if elem.tag != "IN-PARAM-IF-SNREF":
78
97
  odxraise("Currently, only SNREFS are supported for IN-PARAM-IF-REFS")
79
98
  continue
80
-
81
- in_param_if_refs.append(odxrequire(elem.get("SHORT-NAME")))
99
+ else:
100
+ odxassert(elem.tag == "IN-PARAM-IF-SNREF")
101
+ in_param_if_refs.append(odxrequire(elem.attrib.get("SHORT-NAME")))
82
102
 
83
103
  return SubComponentParamConnector(
84
104
  diag_comm_snref=diag_comm_snref,
@@ -103,13 +123,19 @@ class SubComponentParamConnector(IdentifiableElement):
103
123
  if not self._service.positive_responses:
104
124
  odxraise()
105
125
  return
106
- request = odxrequire(service.request)
107
- response = service.positive_responses[0]
108
126
 
127
+ request = odxrequire(service.request)
109
128
  in_param_ifs = []
110
129
  for x in self.in_param_if_refs:
111
130
  in_param_ifs.append(resolve_snref(x, request.parameters, Parameter))
112
131
 
132
+ # TODO: The output parameters are probably part of a response
133
+ # (?). If so, they cannot be resolved ahead of time because
134
+ # the service in question can have multiple responses
135
+ # associated with it and each of these has its own set of
136
+ # parameters. In the meantime, we simply use the first
137
+ # positive response specified.
138
+ response = service.positive_responses[0]
113
139
  out_param_ifs = []
114
140
  for x in self.out_param_if_refs:
115
141
  out_param_ifs.append(resolve_snref(x, response.parameters, Parameter))
@@ -232,7 +258,7 @@ class SubComponent(IdentifiableElement):
232
258
 
233
259
  """
234
260
 
235
- #sub_component_patterns: NamedItemList[SubComponentPattern]
261
+ sub_component_patterns: List[SubComponentPattern]
236
262
  sub_component_param_connectors: NamedItemList[SubComponentParamConnector]
237
263
  table_row_connectors: NamedItemList[TableRowConnector]
238
264
  env_data_connectors: NamedItemList[EnvDataConnector]
@@ -246,6 +272,10 @@ class SubComponent(IdentifiableElement):
246
272
 
247
273
  semantic = et_element.get("SEMANTIC")
248
274
 
275
+ sub_component_patterns = [
276
+ SubComponentPattern.from_et(el, doc_frags)
277
+ for el in et_element.iterfind("SUB-COMPONENT-PATTERNS/SUB-COMPONENT-PATTERN")
278
+ ]
249
279
  sub_component_param_connectors = [
250
280
  SubComponentParamConnector.from_et(el, doc_frags) for el in et_element.iterfind(
251
281
  "SUB-COMPONENT-PARAM-CONNECTORS/SUB-COMPONENT-PARAM-CONNECTOR")
@@ -265,6 +295,7 @@ class SubComponent(IdentifiableElement):
265
295
 
266
296
  return SubComponent(
267
297
  semantic=semantic,
298
+ sub_component_patterns=sub_component_patterns,
268
299
  sub_component_param_connectors=NamedItemList(sub_component_param_connectors),
269
300
  table_row_connectors=NamedItemList(table_row_connectors),
270
301
  env_data_connectors=NamedItemList(env_data_connectors),
@@ -274,15 +305,51 @@ class SubComponent(IdentifiableElement):
274
305
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
275
306
  result = {}
276
307
 
308
+ for scp in self.sub_component_patterns:
309
+ result.update(scp._build_odxlinks())
310
+
311
+ for scpc in self.sub_component_param_connectors:
312
+ result.update(scpc._build_odxlinks())
313
+
314
+ for trc in self.table_row_connectors:
315
+ result.update(trc._build_odxlinks())
316
+
317
+ for edc in self.env_data_connectors:
318
+ result.update(edc._build_odxlinks())
319
+
277
320
  for dtc_conn in self.dtc_connectors:
278
321
  result.update(dtc_conn._build_odxlinks())
279
322
 
280
323
  return result
281
324
 
282
325
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
326
+ for scp in self.sub_component_patterns:
327
+ scp._resolve_odxlinks(odxlinks)
328
+
329
+ for scpc in self.sub_component_param_connectors:
330
+ scpc._resolve_odxlinks(odxlinks)
331
+
332
+ for trc in self.table_row_connectors:
333
+ trc._resolve_odxlinks(odxlinks)
334
+
335
+ for edc in self.env_data_connectors:
336
+ edc._resolve_odxlinks(odxlinks)
337
+
283
338
  for dtc_conn in self.dtc_connectors:
284
339
  dtc_conn._resolve_odxlinks(odxlinks)
285
340
 
286
341
  def _resolve_snrefs(self, context: SnRefContext) -> None:
342
+ for scp in self.sub_component_patterns:
343
+ scp._resolve_snrefs(context)
344
+
345
+ for scpc in self.sub_component_param_connectors:
346
+ scpc._resolve_snrefs(context)
347
+
348
+ for trc in self.table_row_connectors:
349
+ trc._resolve_snrefs(context)
350
+
351
+ for edc in self.env_data_connectors:
352
+ edc._resolve_snrefs(context)
353
+
287
354
  for dtc_conn in self.dtc_connectors:
288
355
  dtc_conn._resolve_snrefs(context)
odxtools/table.py CHANGED
@@ -32,6 +32,7 @@ class TableDiagCommConnector:
32
32
  doc_frags: List[OdxDocFragment]) -> "TableDiagCommConnector":
33
33
 
34
34
  semantic = odxrequire(et_element.findtext("SEMANTIC"))
35
+
35
36
  diag_comm_ref = OdxLinkRef.from_et(et_element.find("DIAG-COMM-REF"), doc_frags)
36
37
  diag_comm_snref = None
37
38
  if (dc_snref_elem := et_element.find("DIAG-COMM-SNREF")) is not None:
@@ -56,7 +57,6 @@ class TableDiagCommConnector:
56
57
  @dataclass
57
58
  class Table(IdentifiableElement):
58
59
  """This class represents a TABLE."""
59
- semantic: Optional[str]
60
60
  key_label: Optional[str]
61
61
  struct_label: Optional[str]
62
62
  admin_data: Optional[AdminData]
@@ -64,13 +64,23 @@ class Table(IdentifiableElement):
64
64
  table_rows_raw: List[Union[TableRow, OdxLinkRef]]
65
65
  table_diag_comm_connectors: List[TableDiagCommConnector]
66
66
  sdgs: List[SpecialDataGroup]
67
+ semantic: Optional[str]
68
+
69
+ @property
70
+ def key_dop(self) -> Optional[DataObjectProperty]:
71
+ """The key data object property associated with this table."""
72
+ return self._key_dop
73
+
74
+ @property
75
+ def table_rows(self) -> NamedItemList[TableRow]:
76
+ """The table rows (both local and referenced) in this table."""
77
+ return self._table_rows
67
78
 
68
79
  @staticmethod
69
80
  def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Table":
70
81
  """Reads a TABLE."""
71
82
  kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
72
83
  odx_id = kwargs["odx_id"]
73
- semantic = et_element.get("SEMANTIC")
74
84
  key_label = et_element.findtext("KEY-LABEL")
75
85
  struct_label = et_element.findtext("STRUCT-LABEL")
76
86
  admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
@@ -92,9 +102,9 @@ class Table(IdentifiableElement):
92
102
  sdgs = [
93
103
  SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
94
104
  ]
105
+ semantic = et_element.get("SEMANTIC")
95
106
 
96
107
  return Table(
97
- semantic=semantic,
98
108
  key_label=key_label,
99
109
  struct_label=struct_label,
100
110
  admin_data=admin_data,
@@ -102,18 +112,9 @@ class Table(IdentifiableElement):
102
112
  table_rows_raw=table_rows_raw,
103
113
  table_diag_comm_connectors=table_diag_comm_connectors,
104
114
  sdgs=sdgs,
115
+ semantic=semantic,
105
116
  **kwargs)
106
117
 
107
- @property
108
- def key_dop(self) -> Optional[DataObjectProperty]:
109
- """The key data object property associated with this table."""
110
- return self._key_dop
111
-
112
- @property
113
- def table_rows(self) -> NamedItemList[TableRow]:
114
- """The table rows (both local and referenced) in this table."""
115
- return self._table_rows
116
-
117
118
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
118
119
  result = {self.odx_id: self}
119
120
 
@@ -124,6 +125,9 @@ class Table(IdentifiableElement):
124
125
  for dcc in self.table_diag_comm_connectors:
125
126
  result.update(dcc._build_odxlinks())
126
127
 
128
+ for sdg in self.sdgs:
129
+ result.update(sdg._build_odxlinks())
130
+
127
131
  return result
128
132
 
129
133
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
@@ -147,6 +151,9 @@ class Table(IdentifiableElement):
147
151
  for dcc in self.table_diag_comm_connectors:
148
152
  dcc._resolve_odxlinks(odxlinks)
149
153
 
154
+ for sdg in self.sdgs:
155
+ sdg._resolve_odxlinks(odxlinks)
156
+
150
157
  def _resolve_snrefs(self, context: SnRefContext) -> None:
151
158
  for table_row_wrapper in self.table_rows_raw:
152
159
  if isinstance(table_row_wrapper, TableRow):
@@ -154,3 +161,6 @@ class Table(IdentifiableElement):
154
161
 
155
162
  for dcc in self.table_diag_comm_connectors:
156
163
  dcc._resolve_snrefs(context)
164
+
165
+ for sdg in self.sdgs:
166
+ sdg._resolve_snrefs(context)