sysmlpy 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.
Files changed (143) hide show
  1. sysmlpy/__init__.py +226 -0
  2. sysmlpy/__main__.py +75 -0
  3. sysmlpy/antlr/README.md +146 -0
  4. sysmlpy/antlr/SysMLv2Lexer.interp +695 -0
  5. sysmlpy/antlr/SysMLv2Lexer.py +1123 -0
  6. sysmlpy/antlr/SysMLv2Lexer.tokens +444 -0
  7. sysmlpy/antlr/SysMLv2Parser.interp +960 -0
  8. sysmlpy/antlr/SysMLv2Parser.py +40750 -0
  9. sysmlpy/antlr/SysMLv2Parser.tokens +444 -0
  10. sysmlpy/antlr/SysMLv2ParserListener.py +4485 -0
  11. sysmlpy/antlr/SysMLv2ParserVisitor.py +2498 -0
  12. sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.interp +695 -0
  13. sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.py +1123 -0
  14. sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.tokens +444 -0
  15. sysmlpy/antlr_parser.py +185 -0
  16. sysmlpy/antlr_visitor.py +10864 -0
  17. sysmlpy/definition.py +1115 -0
  18. sysmlpy/examples/attribute_values.py +161 -0
  19. sysmlpy/examples/part_attributes.py +139 -0
  20. sysmlpy/examples/tuples_sequences.py +231 -0
  21. sysmlpy/formatting.py +47 -0
  22. sysmlpy/grammar/antlr4/README.md +30 -0
  23. sysmlpy/grammar/antlr4/SysMLv2Lexer.g4 +931 -0
  24. sysmlpy/grammar/antlr4/SysMLv2Lexer.interp +695 -0
  25. sysmlpy/grammar/antlr4/SysMLv2Lexer.py +1123 -0
  26. sysmlpy/grammar/antlr4/SysMLv2Lexer.tokens +444 -0
  27. sysmlpy/grammar/antlr4/SysMLv2Parser.g4 +2432 -0
  28. sysmlpy/grammar/antlr4/SysMLv2Parser.interp +960 -0
  29. sysmlpy/grammar/antlr4/SysMLv2Parser.py +40750 -0
  30. sysmlpy/grammar/antlr4/SysMLv2Parser.tokens +444 -0
  31. sysmlpy/grammar/antlr4/SysMLv2ParserListener.py +4485 -0
  32. sysmlpy/grammar/antlr4/SysMLv2ParserVisitor.py +2498 -0
  33. sysmlpy/grammar/antlr4/desc.xml +7 -0
  34. sysmlpy/grammar/antlr4/examples/camera.sysml +14 -0
  35. sysmlpy/grammar/antlr4/examples/toaster-system.sysml +193 -0
  36. sysmlpy/grammar/antlr4/examples/vehicle-model.sysml +118 -0
  37. sysmlpy/grammar/antlr4/pom.xml +59 -0
  38. sysmlpy/grammar/classes.py +8610 -0
  39. sysmlpy/library/LICENSE +165 -0
  40. sysmlpy/library/README.md +52 -0
  41. sysmlpy/library/__init__.py +0 -0
  42. sysmlpy/library/domain/Analysis/AnalysisTooling.sysml +34 -0
  43. sysmlpy/library/domain/Analysis/SampledFunctions.sysml +119 -0
  44. sysmlpy/library/domain/Analysis/StateSpaceRepresentation.sysml +143 -0
  45. sysmlpy/library/domain/Analysis/TradeStudies.sysml +171 -0
  46. sysmlpy/library/domain/Cause and Effect/CausationConnections.sysml +83 -0
  47. sysmlpy/library/domain/Cause and Effect/CauseAndEffect.sysml +81 -0
  48. sysmlpy/library/domain/Geometry/ShapeItems.sysml +899 -0
  49. sysmlpy/library/domain/Geometry/SpatialItems.sysml +168 -0
  50. sysmlpy/library/domain/Metadata/ImageMetadata.sysml +78 -0
  51. sysmlpy/library/domain/Metadata/ModelingMetadata.sysml +143 -0
  52. sysmlpy/library/domain/Metadata/ParametersOfInterestMetadata.sysml +39 -0
  53. sysmlpy/library/domain/Metadata/RiskMetadata.sysml +100 -0
  54. sysmlpy/library/domain/Quantities and Units/ISQ.sysml +42 -0
  55. sysmlpy/library/domain/Quantities and Units/ISQAcoustics.sysml +439 -0
  56. sysmlpy/library/domain/Quantities and Units/ISQAtomicNuclear.sysml +2726 -0
  57. sysmlpy/library/domain/Quantities and Units/ISQBase.sysml +206 -0
  58. sysmlpy/library/domain/Quantities and Units/ISQCharacteristicNumbers.sysml +1991 -0
  59. sysmlpy/library/domain/Quantities and Units/ISQChemistryMolecular.sysml +1353 -0
  60. sysmlpy/library/domain/Quantities and Units/ISQCondensedMatter.sysml +1223 -0
  61. sysmlpy/library/domain/Quantities and Units/ISQElectromagnetism.sysml +2333 -0
  62. sysmlpy/library/domain/Quantities and Units/ISQInformation.sysml +958 -0
  63. sysmlpy/library/domain/Quantities and Units/ISQLight.sysml +1537 -0
  64. sysmlpy/library/domain/Quantities and Units/ISQMechanics.sysml +1564 -0
  65. sysmlpy/library/domain/Quantities and Units/ISQSpaceTime.sysml +1169 -0
  66. sysmlpy/library/domain/Quantities and Units/ISQThermodynamics.sysml +1256 -0
  67. sysmlpy/library/domain/Quantities and Units/MeasurementRefCalculations.sysml +30 -0
  68. sysmlpy/library/domain/Quantities and Units/MeasurementReferences.sysml +526 -0
  69. sysmlpy/library/domain/Quantities and Units/Quantities.sysml +107 -0
  70. sysmlpy/library/domain/Quantities and Units/QuantityCalculations.sysml +70 -0
  71. sysmlpy/library/domain/Quantities and Units/SI.sysml +378 -0
  72. sysmlpy/library/domain/Quantities and Units/SIPrefixes.sysml +48 -0
  73. sysmlpy/library/domain/Quantities and Units/TensorCalculations.sysml +50 -0
  74. sysmlpy/library/domain/Quantities and Units/Time.sysml +274 -0
  75. sysmlpy/library/domain/Quantities and Units/USCustomaryUnits.sysml +260 -0
  76. sysmlpy/library/domain/Quantities and Units/VectorCalculations.sysml +62 -0
  77. sysmlpy/library/domain/Requirement Derivation/DerivationConnections.sysml +63 -0
  78. sysmlpy/library/domain/Requirement Derivation/RequirementDerivation.sysml +39 -0
  79. sysmlpy/library/kernel/Base.kerml +95 -0
  80. sysmlpy/library/kernel/BaseFunctions.kerml +80 -0
  81. sysmlpy/library/kernel/BooleanFunctions.kerml +22 -0
  82. sysmlpy/library/kernel/Clocks.kerml +156 -0
  83. sysmlpy/library/kernel/CollectionFunctions.kerml +68 -0
  84. sysmlpy/library/kernel/Collections.kerml +147 -0
  85. sysmlpy/library/kernel/ComplexFunctions.kerml +47 -0
  86. sysmlpy/library/kernel/ControlFunctions.kerml +117 -0
  87. sysmlpy/library/kernel/ControlPerformances.kerml +135 -0
  88. sysmlpy/library/kernel/DataFunctions.kerml +43 -0
  89. sysmlpy/library/kernel/FeatureReferencingPerformances.kerml +190 -0
  90. sysmlpy/library/kernel/IntegerFunctions.kerml +43 -0
  91. sysmlpy/library/kernel/KerML.kerml +483 -0
  92. sysmlpy/library/kernel/Links.kerml +67 -0
  93. sysmlpy/library/kernel/Metaobjects.kerml +58 -0
  94. sysmlpy/library/kernel/NaturalFunctions.kerml +27 -0
  95. sysmlpy/library/kernel/NumericalFunctions.kerml +43 -0
  96. sysmlpy/library/kernel/Objects.kerml +212 -0
  97. sysmlpy/library/kernel/Observation.kerml +161 -0
  98. sysmlpy/library/kernel/OccurrenceFunctions.kerml +154 -0
  99. sysmlpy/library/kernel/Occurrences.kerml +992 -0
  100. sysmlpy/library/kernel/Performances.kerml +293 -0
  101. sysmlpy/library/kernel/RationalFunctions.kerml +49 -0
  102. sysmlpy/library/kernel/RealFunctions.kerml +56 -0
  103. sysmlpy/library/kernel/ScalarFunctions.kerml +33 -0
  104. sysmlpy/library/kernel/ScalarValues.kerml +23 -0
  105. sysmlpy/library/kernel/SequenceFunctions.kerml +111 -0
  106. sysmlpy/library/kernel/SpatialFrames.kerml +197 -0
  107. sysmlpy/library/kernel/StatePerformances.kerml +145 -0
  108. sysmlpy/library/kernel/StringFunctions.kerml +25 -0
  109. sysmlpy/library/kernel/Transfers.kerml +281 -0
  110. sysmlpy/library/kernel/TransitionPerformances.kerml +66 -0
  111. sysmlpy/library/kernel/TrigFunctions.kerml +35 -0
  112. sysmlpy/library/kernel/Triggers.kerml +188 -0
  113. sysmlpy/library/kernel/VectorFunctions.kerml +273 -0
  114. sysmlpy/library/kernel/VectorValues.kerml +64 -0
  115. sysmlpy/library/systems/Actions.sysml +552 -0
  116. sysmlpy/library/systems/Allocations.sysml +29 -0
  117. sysmlpy/library/systems/AnalysisCases.sysml +38 -0
  118. sysmlpy/library/systems/Attributes.sysml +25 -0
  119. sysmlpy/library/systems/Calculations.sysml +37 -0
  120. sysmlpy/library/systems/Cases.sysml +71 -0
  121. sysmlpy/library/systems/Connections.sysml +60 -0
  122. sysmlpy/library/systems/Constraints.sysml +44 -0
  123. sysmlpy/library/systems/Flows.sysml +126 -0
  124. sysmlpy/library/systems/Interfaces.sysml +89 -0
  125. sysmlpy/library/systems/Items.sysml +153 -0
  126. sysmlpy/library/systems/Metadata.sysml +32 -0
  127. sysmlpy/library/systems/Parts.sysml +81 -0
  128. sysmlpy/library/systems/Ports.sysml +54 -0
  129. sysmlpy/library/systems/Requirements.sysml +194 -0
  130. sysmlpy/library/systems/StandardViewDefinitions.sysml +123 -0
  131. sysmlpy/library/systems/States.sysml +103 -0
  132. sysmlpy/library/systems/SysML.sysml +539 -0
  133. sysmlpy/library/systems/UseCases.sysml +57 -0
  134. sysmlpy/library/systems/VerificationCases.sysml +103 -0
  135. sysmlpy/library/systems/Views.sysml +164 -0
  136. sysmlpy/navigate.py +272 -0
  137. sysmlpy/store.py +547 -0
  138. sysmlpy/usage.py +2978 -0
  139. sysmlpy-0.1.0.dist-info/METADATA +230 -0
  140. sysmlpy-0.1.0.dist-info/RECORD +143 -0
  141. sysmlpy-0.1.0.dist-info/WHEEL +4 -0
  142. sysmlpy-0.1.0.dist-info/entry_points.txt +3 -0
  143. sysmlpy-0.1.0.dist-info/licenses/LICENSE +21 -0
sysmlpy/definition.py ADDED
@@ -0,0 +1,1115 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Tue Jul 11 10:14:18 2023
5
+
6
+ @author: christophercox
7
+ """
8
+
9
+ import uuid as uuidlib
10
+
11
+ from typing import TypeVar
12
+
13
+ from sysmlpy.formatting import classtree
14
+ from sysmlpy.navigate import Searchable
15
+ from sysmlpy.grammar.classes import (
16
+ Identification,
17
+ PackageMember,
18
+ PackageBody,
19
+ RootNamespace,
20
+ )
21
+ from sysmlpy.grammar.classes import Package as PackageGrammar
22
+
23
+ from sysmlpy import Part, Item, Port, Requirement, UseCase, Attribute, Action, Case, AnalysisCase, VerificationCase
24
+ from sysmlpy.usage import (
25
+ State, Constraint, Connection, Flow, Calculation, Enumeration,
26
+ Allocation, Metadata, Rendering, Individual, FlowDef,
27
+ View, Viewpoint, Concern,
28
+ )
29
+
30
+ ModelType = TypeVar("Model", bound="Model")
31
+
32
+
33
+ class Model(Searchable):
34
+ """Root model container. Exposes typed accessors and ``find()`` / ``all()``
35
+ across all child packages and their contents."""
36
+
37
+ sysml_type = None # Model is the root, not a SysML element itself
38
+
39
+ def __init__(self):
40
+ self.name = str(uuidlib.uuid4())
41
+ self.children = []
42
+ self.typedby = None
43
+ self.grammar = None
44
+ self.parent = None
45
+
46
+ def __repr__(self):
47
+ cls_name = self.__class__.__name__
48
+ if self.children:
49
+ children_repr = ', '.join(repr(c) for c in self.children)
50
+ return f"{cls_name}(children=[{children_repr}])"
51
+ return f"{cls_name}()"
52
+
53
+ def load(self: type[ModelType], s: str, library=None) -> ModelType:
54
+ """Load a SysML model from a string using ANTLR4 parser.
55
+
56
+ Parameters
57
+ ----------
58
+ s : str
59
+ The SysML source code to parse.
60
+ library : str or Path, optional
61
+ Path to SysML v2 library files for resolving imports.
62
+
63
+ Returns
64
+ -------
65
+ Model
66
+ The loaded model.
67
+ """
68
+ from sysmlpy import load_grammar_antlr
69
+
70
+ # Model requires the input to be a package declaration
71
+ # Check if the string starts with a valid SysML keyword that's not 'package' or 'library'
72
+ s_stripped = s.strip() if isinstance(s, str) else s
73
+ if isinstance(s_stripped, str):
74
+ # List of known SysML top-level keywords (definitions and usages that must be wrapped)
75
+ invalid_starts = ['item', 'part', 'port', 'attribute', 'action', 'state',
76
+ 'connection', 'interface', 'requirement', 'case', 'use',
77
+ 'calc', 'constraint', 'concern', 'ref', 'flow', 'allocation',
78
+ 'view', 'viewpoint', 'rendering', 'metadata', 'individual']
79
+ first_word = s_stripped.split(None, 1)[0] if s_stripped.split() else ''
80
+ if first_word in invalid_starts:
81
+ raise ValueError("Base Model must be encapsulated by a package.")
82
+
83
+ definition = load_grammar_antlr(s, library=library)["ownedRelationship"]
84
+
85
+ member_grammar = []
86
+ found_package = False
87
+ for member in definition:
88
+ if "ownedRelatedElement" not in member:
89
+ if member.get("name") in ("Import", "AliasMember"):
90
+ member_grammar.append(member)
91
+ continue
92
+ if member["ownedRelatedElement"]["name"] == "DefinitionElement":
93
+ de = member["ownedRelatedElement"]
94
+ if de["ownedRelatedElement"]["name"] == "Package":
95
+ found_package = True
96
+ p = Package().load_from_grammar(
97
+ PackageGrammar(de["ownedRelatedElement"])
98
+ )
99
+ self.children.append(p)
100
+ member_grammar.append(p._get_definition(child="PackageBody"))
101
+
102
+ if not found_package:
103
+ # Wrap bare top-level definitions in a synthetic package
104
+ synthetic_relationships = []
105
+ for member in definition:
106
+ if "ownedRelatedElement" not in member:
107
+ if member.get("name") in ("Import", "AliasMember"):
108
+ member_grammar.append(member)
109
+ continue
110
+ if member["ownedRelatedElement"]["name"] == "DefinitionElement":
111
+ de = member["ownedRelatedElement"]
112
+ if de["ownedRelatedElement"]["name"] == "AnnotatingElement":
113
+ continue
114
+ synthetic_relationships.append({
115
+ "name": "PackageMember",
116
+ "prefix": None,
117
+ "ownedRelatedElement": de
118
+ })
119
+
120
+ if synthetic_relationships:
121
+ synthetic_definition = {
122
+ "name": "Package",
123
+ "declaration": {
124
+ "name": "PackageDeclaration",
125
+ "identification": {
126
+ "name": "Identification",
127
+ "declaredName": None,
128
+ "declaredShortName": None,
129
+ }
130
+ },
131
+ "body": {
132
+ "name": "PackageBody",
133
+ "ownedRelationship": synthetic_relationships
134
+ },
135
+ "ownedRelationship": []
136
+ }
137
+ p = Package().load_from_grammar(
138
+ PackageGrammar(synthetic_definition)
139
+ )
140
+ self.children.append(p)
141
+ member_grammar.append(p._get_definition(child="PackageBody"))
142
+ else:
143
+ raise ValueError("Base Model must be encapsulated by a package.")
144
+
145
+ self.grammar = RootNamespace(
146
+ {"name": "PackageBodyElement", "ownedRelationship": member_grammar}
147
+ )
148
+
149
+ return self
150
+
151
+ def _ensure_body(self):
152
+ body = []
153
+ for abc in self.children:
154
+ v = abc._get_definition(child="PackageBody")
155
+ if isinstance(v, list):
156
+ for subchild in v:
157
+ body.append(PackageMember(subchild).get_definition())
158
+ else:
159
+ body.append(PackageMember(v).get_definition())
160
+
161
+ if hasattr(self, 'grammar') and self.grammar:
162
+ for child in self.grammar.children:
163
+ if child.__class__.__name__ in ('Import', 'AliasMember'):
164
+ body.append(child.get_definition())
165
+
166
+ if len(body) > 0:
167
+ self.grammar = RootNamespace(
168
+ {"name": "PackageBodyElement", "ownedRelationship": body}
169
+ )
170
+
171
+ return self
172
+
173
+ def _get_definition(self):
174
+ return self.grammar.get_definition()
175
+
176
+ def dump(self):
177
+ if len(self.children) == 0:
178
+ raise ValueError("Base Model has no elements to output.")
179
+
180
+ self._ensure_body()
181
+ return classtree(self._get_definition()).dump()
182
+
183
+ def _set_child(self, child):
184
+ self.children.append(child)
185
+ child.parent = self
186
+ return self
187
+
188
+ def _get_child(self, featurechain):
189
+ # 'x.y.z'
190
+ if isinstance(featurechain, str):
191
+ fc = featurechain.split(".")
192
+ else:
193
+ raise TypeError
194
+
195
+ if fc[0] == self.name:
196
+ # This first one must match self name, otherwise pass it all
197
+ featurechain = ".".join(fc[1:])
198
+
199
+ for child in self.children:
200
+ fcs = featurechain.split(".")
201
+ if child.name == fcs[0]:
202
+ if len(fcs) == 1:
203
+ return child
204
+ else:
205
+ return child._get_child(featurechain)
206
+
207
+ # ── Convenience functions ───────────────────────────────────────────
208
+
209
+ def find_all(self, type=None, name=None):
210
+ """Recursively find all matching elements across all packages.
211
+
212
+ Parameters
213
+ ----------
214
+ type : str or class, optional
215
+ Filter by SysML type (e.g. 'part', Part).
216
+ name : str, optional
217
+ Filter by element name.
218
+
219
+ Returns
220
+ -------
221
+ list
222
+ All matching elements across the full tree.
223
+ """
224
+ results = []
225
+ for child in self.children:
226
+ if hasattr(child, "find_all"):
227
+ results.extend(child.find_all(type=type, name=name))
228
+ return results
229
+
230
+ def count(self, type=None):
231
+ """Count elements by type across all packages.
232
+
233
+ Parameters
234
+ ----------
235
+ type : str or class, optional
236
+ Count only this type. If None, returns a dict of all types.
237
+
238
+ Returns
239
+ -------
240
+ int or dict
241
+ Count of matching elements, or dict of {type: count}.
242
+ """
243
+ if type is not None:
244
+ return len(self.find_all(type=type))
245
+
246
+ counts = {}
247
+ for child in self.children:
248
+ if hasattr(child, "count"):
249
+ child_counts = child.count()
250
+ for k, v in child_counts.items():
251
+ counts[k] = counts.get(k, 0) + v
252
+ return counts
253
+
254
+ def traverse(self, callback, depth=0):
255
+ """Walk all packages and their trees, calling callback for each element."""
256
+ for child in self.children:
257
+ if hasattr(child, "traverse"):
258
+ child.traverse(callback, depth)
259
+
260
+ def to_dict(self):
261
+ """Export the model as a nested dictionary."""
262
+ return {
263
+ "name": "Model",
264
+ "children": [c.to_dict() if hasattr(c, "to_dict") else {"name": getattr(c, "name", None)} for c in self.children],
265
+ }
266
+
267
+ def to_graph(self):
268
+ """Export the model as a NetworkX graph.
269
+
270
+ Requires: pip install networkx (or: pip install sysmlpy[graph])
271
+ """
272
+ from sysmlpy.store import NetworkXStore, new_id
273
+
274
+ store = NetworkXStore()
275
+
276
+ def _add_element(elem, parent_id=None):
277
+ eid = new_id()
278
+ data = {
279
+ "name": getattr(elem, "name", None),
280
+ "sysml_type": getattr(elem, "sysml_type", None),
281
+ "python_type": type(elem).__name__,
282
+ }
283
+ store.put(eid, data, parent_id=parent_id)
284
+ if hasattr(elem, "children"):
285
+ for child in elem.children:
286
+ _add_element(child, eid)
287
+
288
+ for child in self.children:
289
+ _add_element(child)
290
+ return store
291
+
292
+ def path_between(self, source_name, target_name):
293
+ """Find the path between two elements by name across all packages."""
294
+ for child in self.children:
295
+ if hasattr(child, "path_between"):
296
+ path = child.path_between(source_name, target_name)
297
+ if path is not None:
298
+ return path
299
+ return None
300
+
301
+
302
+ class Package(Searchable):
303
+ """SysML v2 Package. Exposes typed accessors and ``find()`` / ``all()``
304
+ across its direct and nested children."""
305
+
306
+ sysml_type = "package"
307
+
308
+ def __init__(self, name=None, shortname=None):
309
+ self.name = str(uuidlib.uuid4())
310
+ self.children = []
311
+ self.typedby = None
312
+ self.grammar = PackageGrammar()
313
+ self.parent = None
314
+
315
+ if name is not None:
316
+ self._set_name(name)
317
+ if shortname is not None:
318
+ self._set_name(shortname, short=True)
319
+
320
+ def __repr__(self):
321
+ name = getattr(self, 'name', None)
322
+ shortname = getattr(self.grammar.declaration.identification, 'declaredShortName', None)
323
+
324
+ cls_name = self.__class__.__name__
325
+ if name and shortname:
326
+ return f"{cls_name}(name={name!r}, shortname={shortname!r})"
327
+ elif name:
328
+ return f"{cls_name}(name={name!r})"
329
+ elif shortname:
330
+ return f"{cls_name}(shortname={shortname!r})"
331
+ else:
332
+ return f"{cls_name}()"
333
+
334
+ def _set_name(self, name, short=False):
335
+ if short:
336
+ if self.grammar.declaration.identification is None:
337
+ self.grammar.declaration.identification = Identification()
338
+ self.grammar.declaration.identification.declaredShortName = name
339
+ else:
340
+ self.name = name
341
+ if self.grammar.declaration.identification is None:
342
+ self.grammar.declaration.identification = Identification()
343
+ self.grammar.declaration.identification.declaredName = name
344
+
345
+ return self
346
+
347
+ def _get_name(self):
348
+ return self.grammar.declaration.identification.declaredName
349
+
350
+ def _set_child(self, child):
351
+ self.children.append(child)
352
+ child.parent = self
353
+ return self
354
+
355
+ def _get_child(self, featurechain):
356
+ # 'x.y.z'
357
+ if isinstance(featurechain, str):
358
+ fc = featurechain.split(".")
359
+ else:
360
+ raise TypeError
361
+
362
+ if fc[0] == self.name:
363
+ # This first one must match self name, otherwise pass it all
364
+ featurechain = ".".join(fc[1:])
365
+
366
+ for child in self.children:
367
+ fcs = featurechain.split(".")
368
+ if child.name == fcs[0]:
369
+ if len(fcs) == 1:
370
+ return child
371
+ else:
372
+ return child._get_child(featurechain)
373
+
374
+ def _ensure_body(self):
375
+ body = []
376
+ for abc in self.children:
377
+ v = abc._get_definition(child="PackageBody")
378
+ if isinstance(v, list):
379
+ for subchild in v:
380
+ body.append(PackageMember(subchild).get_definition())
381
+ else:
382
+ body.append(PackageMember(v).get_definition())
383
+
384
+ if hasattr(self.grammar, 'body') and self.grammar.body:
385
+ for child in self.grammar.body.children:
386
+ if child.__class__.__name__ in ('Import', 'AliasMember'):
387
+ body.append(child.get_definition())
388
+
389
+ if len(body) > 0:
390
+ self.grammar.body = PackageBody(
391
+ {"name": "PackageBody", "ownedRelationship": body}
392
+ )
393
+
394
+ def _get_definition(self, child=None):
395
+ self._ensure_body()
396
+
397
+ package = {
398
+ "name": "DefinitionElement",
399
+ "ownedRelatedElement": self.grammar.get_definition(),
400
+ }
401
+ package = {
402
+ "name": "PackageMember",
403
+ "ownedRelatedElement": package,
404
+ "prefix": None,
405
+ }
406
+ if not child:
407
+ package = {
408
+ "name": "PackageBodyElement",
409
+ "ownedRelationship": [package],
410
+ "prefix": None,
411
+ }
412
+
413
+ # Add the typed by definition to the package output
414
+ if self.typedby is not None:
415
+ # Packages cannot be typed, they should import from other packages
416
+ raise NotImplementedError
417
+
418
+ return package
419
+
420
+ def dump(self, child=None):
421
+ return classtree(self._get_definition(child=False)).dump()
422
+
423
+ def load_from_grammar(self, grammar):
424
+ # Get the identification values
425
+ declared_name = grammar.declaration.identification.declaredName
426
+ declared_shortname = grammar.declaration.identification.declaredShortName if hasattr(grammar.declaration.identification, 'declaredShortName') else None
427
+
428
+ # Handle the different cases:
429
+ if declared_shortname and declared_name:
430
+ # Both short and long name provided
431
+ self.name = declared_name
432
+ self._set_name(declared_shortname, short=True)
433
+ elif declared_shortname and not declared_name:
434
+ # Only short name is provided - use it as name
435
+ self.name = declared_shortname
436
+ self._set_name(declared_shortname, short=True)
437
+ elif declared_name:
438
+ # Only long name provided
439
+ self.name = declared_name
440
+
441
+ self.grammar = grammar
442
+ for child in grammar.body.children:
443
+ # Navigate the nested structure:
444
+ # DefinitionBodyItem -> NonOccurrenceUsageMember -> NonOccurrenceUsageElement -> StructureUsageElement/BehaviorUsageElement
445
+ # Or: DefinitionBodyItem -> PackageMember -> DefinitionElement -> ItemDefinition/PartDefinition
446
+ # Or: DefinitionBodyItem -> PackageMember -> UsageElement -> OccurrenceUsageElement -> StructureUsageElement -> ItemUsage/PartUsage
447
+ # Or: PackageBody -> UsageElement directly (for analysis cases in packages)
448
+ if not hasattr(child, 'children') or not child.children:
449
+ continue
450
+
451
+ # Handle case where child.children is a single object (not a list)
452
+ if isinstance(child.children, list):
453
+ first_child = child.children[0]
454
+ else:
455
+ first_child = child.children
456
+
457
+ # Handle NonOccurrenceUsageMember (for usages like item, attribute)
458
+ if first_child.__class__.__name__ == 'NonOccurrenceUsageMember':
459
+ if hasattr(first_child, 'children') and first_child.children:
460
+ second = first_child.children[0]
461
+ if hasattr(second, 'children'):
462
+ # NonOccurrenceUsageElement -> StructureUsageElement or BehaviorUsageElement
463
+ inner_element = second.children
464
+ else:
465
+ inner_element = second
466
+ else:
467
+ continue
468
+ # Handle DefinitionElement (for definitions like item def, part def)
469
+ elif first_child.__class__.__name__ == 'DefinitionElement':
470
+ if hasattr(first_child, 'children') and first_child.children:
471
+ inner_element = first_child.children[0]
472
+ else:
473
+ continue
474
+ # Handle UsageElement (for usages like item Hydrogen : Fuel, action Act1)
475
+ elif first_child.__class__.__name__ == 'UsageElement':
476
+ if hasattr(first_child, 'children') and first_child.children:
477
+ # Navigate: UsageElement -> OccurrenceUsageElement/NonOccurrenceUsageElement -> Usage
478
+ # first_child.children can be an object (not a list)
479
+ occ = first_child.children
480
+
481
+ # Check if it's OccurrenceUsageElement (has children containing Structure/Behavior element)
482
+ if hasattr(occ, 'children') and occ.children:
483
+ inner = occ.children
484
+ # Check if it's StructureUsageElement or BehaviorUsageElement
485
+ if hasattr(inner, 'children'):
486
+ inner_element = inner.children
487
+ else:
488
+ inner_element = inner
489
+ # Check if it's NonOccurrenceUsageElement
490
+ elif hasattr(occ, 'ownedRelationship') or hasattr(occ, 'declaration'):
491
+ inner_element = occ
492
+ else:
493
+ continue
494
+ else:
495
+ continue
496
+ else:
497
+ continue
498
+
499
+ inner_class = inner_element.__class__.__name__
500
+
501
+ if inner_class == "ItemUsage":
502
+ child = Item().load_from_grammar(inner_element)
503
+ child.parent = self
504
+ self.children.append(child)
505
+ elif inner_class == "PartUsage":
506
+ child = Part().load_from_grammar(inner_element)
507
+ child.parent = self
508
+ self.children.append(child)
509
+ elif inner_class == "PortUsage":
510
+ child = Port().load_from_grammar(inner_element)
511
+ child.parent = self
512
+ self.children.append(child)
513
+ elif inner_class == "Package":
514
+ child = Package().load_from_grammar(inner_element)
515
+ child.parent = self
516
+ self.children.append(child)
517
+ elif inner_class == "ItemDefinition":
518
+ child = Item().load_from_grammar(inner_element)
519
+ child.parent = self
520
+ self.children.append(child)
521
+ elif inner_class == "PartDefinition":
522
+ child = Part(definition=True).load_from_grammar(inner_element)
523
+ child.parent = self
524
+ self.children.append(child)
525
+ elif inner_class == "PortDefinition":
526
+ child = Port(definition=True).load_from_grammar(inner_element)
527
+ child.parent = self
528
+ self.children.append(child)
529
+ elif inner_class == "AttributeDefinition":
530
+ child = Attribute(definition=True).load_from_grammar(inner_element)
531
+ child.parent = self
532
+ self.children.append(child)
533
+ elif inner_class == "AttributeUsage":
534
+ child = Attribute().load_from_grammar(inner_element)
535
+ child.parent = self
536
+ self.children.append(child)
537
+ elif inner_class == "ActionDefinition":
538
+ child = Action(definition=True).load_from_grammar(inner_element)
539
+ child.parent = self
540
+ self.children.append(child)
541
+ elif inner_class == "RequirementDefinition":
542
+ child = Requirement(definition=True).load_from_grammar(inner_element)
543
+ child.parent = self
544
+ self.children.append(child)
545
+ elif inner_class == "UseCaseDefinition":
546
+ child = UseCase(definition=True).load_from_grammar(inner_element)
547
+ child.parent = self
548
+ self.children.append(child)
549
+ elif inner_class == "ActionUsage":
550
+ child = Action(grammar=inner_element).load_from_grammar(inner_element)
551
+ child.parent = self
552
+ self.children.append(child)
553
+ elif inner_class == "ActionUsage":
554
+ child = Action().load_from_grammar(inner_element)
555
+ child.parent = self
556
+ self.children.append(child)
557
+ elif inner_class == "ConstraintDefinition":
558
+ c = Constraint(definition=True)
559
+ c.grammar = inner_element
560
+ # Extract the name from the grammar
561
+ if hasattr(inner_element, 'declaration') and hasattr(inner_element.declaration, 'identification') and inner_element.declaration.identification:
562
+ c.name = inner_element.declaration.identification.declaredName
563
+ c.parent = self
564
+ self.children.append(c)
565
+ elif inner_class == "ConstraintUsage":
566
+ c = Constraint()
567
+ c.grammar = inner_element
568
+ # Navigate to get name from nested declaration structure
569
+ try:
570
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
571
+ decl = inner_element.declaration
572
+ if hasattr(decl, 'declaration') and decl.declaration:
573
+ inner_decl = decl.declaration
574
+ if hasattr(inner_decl, 'declaration') and inner_decl.declaration:
575
+ feat_decl = inner_decl.declaration
576
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
577
+ c.name = feat_decl.identification.declaredName
578
+ except AttributeError:
579
+ pass
580
+ c.parent = self
581
+ self.children.append(c)
582
+ elif inner_class == "StateDefinition":
583
+ s = State(definition=True).load_from_grammar(inner_element)
584
+ s.parent = self
585
+ self.children.append(s)
586
+ elif inner_class == "StateUsage":
587
+ s = State().load_from_grammar(inner_element)
588
+ s.parent = self
589
+ self.children.append(s)
590
+ elif inner_class == "CalculationDefinition":
591
+ c = Calculation(definition=True)
592
+ c.grammar = inner_element
593
+ if hasattr(inner_element, 'declaration') and hasattr(inner_element.declaration, 'identification') and inner_element.declaration.identification:
594
+ c.name = inner_element.declaration.identification.declaredName
595
+ c.parent = self
596
+ self.children.append(c)
597
+ elif inner_class == "CalculationUsage":
598
+ c = Calculation()
599
+ c.grammar = inner_element
600
+ try:
601
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
602
+ decl = inner_element.declaration
603
+ if hasattr(decl, 'declaration') and decl.declaration:
604
+ inner_decl = decl.declaration
605
+ if hasattr(inner_decl, 'declaration') and inner_decl.declaration:
606
+ feat_decl = inner_decl.declaration
607
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
608
+ c.name = feat_decl.identification.declaredName
609
+ except AttributeError:
610
+ pass
611
+ c.parent = self
612
+ self.children.append(c)
613
+ elif inner_class == "ConnectionDefinition":
614
+ c = Connection(definition=True)
615
+ c.grammar = inner_element
616
+ if hasattr(inner_element, 'definition') and hasattr(inner_element.definition, 'declaration') and inner_element.definition.declaration:
617
+ decl = inner_element.definition.declaration
618
+ if hasattr(decl, 'identification') and decl.identification:
619
+ c.name = decl.identification.declaredName
620
+ c.parent = self
621
+ self.children.append(c)
622
+ elif inner_class == "ConnectionUsage":
623
+ c = Connection()
624
+ c.grammar = inner_element
625
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
626
+ decl = inner_element.declaration
627
+ if hasattr(decl, 'declaration') and decl.declaration:
628
+ feat_decl = decl.declaration
629
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
630
+ c.name = feat_decl.identification.declaredName
631
+ c.parent = self
632
+ self.children.append(c)
633
+ elif inner_class == "FlowConnectionDefinition":
634
+ f = Flow(definition=True)
635
+ f.grammar = inner_element
636
+ if hasattr(inner_element, 'definition') and hasattr(inner_element.definition, 'declaration') and inner_element.definition.declaration:
637
+ decl = inner_element.definition.declaration
638
+ if hasattr(decl, 'identification') and decl.identification:
639
+ f.name = decl.identification.declaredName
640
+ f.parent = self
641
+ self.children.append(f)
642
+ elif inner_class == "FlowConnectionUsage":
643
+ f = Flow()
644
+ f.grammar = inner_element
645
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
646
+ decl = inner_element.declaration
647
+ if hasattr(decl, 'declaration') and decl.declaration:
648
+ feat_decl = decl.declaration
649
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
650
+ f.name = feat_decl.identification.declaredName
651
+ f.parent = self
652
+ self.children.append(f)
653
+ elif inner_class == "EnumerationDefinition":
654
+ e = Enumeration(name=None) # will set below
655
+ e.grammar = inner_element
656
+ if hasattr(inner_element, 'declaration') and hasattr(inner_element.declaration, 'identification') and inner_element.declaration.identification:
657
+ e.name = inner_element.declaration.identification.declaredName
658
+ e.parent = self
659
+ self.children.append(e)
660
+ elif inner_class == "AllocationDefinition":
661
+ a = Allocation(definition=True)
662
+ a.grammar = inner_element
663
+ # AllocationDefinition uses 'definition' -> Definition -> DefinitionDeclaration
664
+ if hasattr(inner_element, 'definition') and inner_element.definition:
665
+ if hasattr(inner_element.definition, 'declaration') and inner_element.definition.declaration:
666
+ decl = inner_element.definition.declaration
667
+ if hasattr(decl, 'identification') and decl.identification:
668
+ a.name = decl.identification.declaredName
669
+ a.parent = self
670
+ self.children.append(a)
671
+ elif inner_class == "AllocationUsage":
672
+ a = Allocation()
673
+ a.grammar = inner_element
674
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
675
+ decl = inner_element.declaration
676
+ if hasattr(decl, 'declaration') and decl.declaration:
677
+ feat_decl = decl.declaration
678
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
679
+ a.name = feat_decl.identification.declaredName
680
+ a.parent = self
681
+ self.children.append(a)
682
+ elif inner_class == "MetadataDefinition":
683
+ m = Metadata(definition=True)
684
+ m.grammar = inner_element
685
+ if hasattr(inner_element, 'definition') and inner_element.definition:
686
+ if hasattr(inner_element.definition, 'declaration') and inner_element.definition.declaration:
687
+ decl = inner_element.definition.declaration
688
+ if hasattr(decl, 'identification') and decl.identification:
689
+ m.name = decl.identification.declaredName
690
+ m.parent = self
691
+ self.children.append(m)
692
+ elif inner_class == "MetadataUsage":
693
+ m = Metadata()
694
+ m.grammar = inner_element
695
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
696
+ decl = inner_element.declaration
697
+ if hasattr(decl, 'declaration') and decl.declaration:
698
+ feat_decl = decl.declaration
699
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
700
+ m.name = feat_decl.identification.declaredName
701
+ m.parent = self
702
+ self.children.append(m)
703
+ elif inner_class == "RenderingDefinition":
704
+ r = Rendering(definition=True)
705
+ r.grammar = inner_element
706
+ if hasattr(inner_element, 'definition') and inner_element.definition:
707
+ if hasattr(inner_element.definition, 'declaration') and inner_element.definition.declaration:
708
+ decl = inner_element.definition.declaration
709
+ if hasattr(decl, 'identification') and decl.identification:
710
+ r.name = decl.identification.declaredName
711
+ r.parent = self
712
+ self.children.append(r)
713
+ elif inner_class == "RenderingUsage":
714
+ r = Rendering()
715
+ r.grammar = inner_element
716
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
717
+ decl = inner_element.declaration
718
+ if hasattr(decl, 'declaration') and decl.declaration:
719
+ feat_decl = decl.declaration
720
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
721
+ r.name = feat_decl.identification.declaredName
722
+ r.parent = self
723
+ self.children.append(r)
724
+ elif inner_class == "IndividualDefinition":
725
+ i = Individual(definition=True)
726
+ i.grammar = inner_element
727
+ if hasattr(inner_element, 'definition') and inner_element.definition:
728
+ if hasattr(inner_element.definition, 'declaration') and inner_element.definition.declaration:
729
+ decl = inner_element.definition.declaration
730
+ if hasattr(decl, 'identification') and decl.identification:
731
+ i.name = decl.identification.declaredName
732
+ i.parent = self
733
+ self.children.append(i)
734
+ elif inner_class == "IndividualUsage" or inner_class == "IndividualUsageSimple":
735
+ i = Individual()
736
+ i.grammar = inner_element
737
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
738
+ decl = inner_element.declaration
739
+ if hasattr(decl, 'declaration') and decl.declaration:
740
+ feat_decl = decl.declaration
741
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
742
+ i.name = feat_decl.identification.declaredName
743
+ i.parent = self
744
+ self.children.append(i)
745
+ elif inner_class == "FlowDefinition":
746
+ f = FlowDef(name=None)
747
+ f.grammar = inner_element
748
+ if hasattr(inner_element, 'definition') and inner_element.definition:
749
+ if hasattr(inner_element.definition, 'declaration') and inner_element.definition.declaration:
750
+ decl = inner_element.definition.declaration
751
+ if hasattr(decl, 'identification') and decl.identification:
752
+ f.name = decl.identification.declaredName
753
+ f.parent = self
754
+ self.children.append(f)
755
+ elif inner_class == "ViewDefinition":
756
+ v = View(definition=True)
757
+ v.grammar = inner_element
758
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
759
+ decl = inner_element.declaration
760
+ if hasattr(decl, 'identification') and decl.identification:
761
+ v.name = decl.identification.declaredName
762
+ v.parent = self
763
+ self.children.append(v)
764
+ elif inner_class == "ViewUsage":
765
+ v = View()
766
+ v.grammar = inner_element
767
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
768
+ decl = inner_element.declaration
769
+ if hasattr(decl, 'declaration') and decl.declaration:
770
+ feat_decl = decl.declaration
771
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
772
+ v.name = feat_decl.identification.declaredName
773
+ v.parent = self
774
+ self.children.append(v)
775
+ elif inner_class == "ViewpointDefinition":
776
+ vp = Viewpoint(definition=True)
777
+ vp.grammar = inner_element
778
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
779
+ decl = inner_element.declaration
780
+ if hasattr(decl, 'identification') and decl.identification:
781
+ vp.name = decl.identification.declaredName
782
+ vp.parent = self
783
+ self.children.append(vp)
784
+ elif inner_class == "ViewpointUsage":
785
+ vp = Viewpoint()
786
+ vp.grammar = inner_element
787
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
788
+ decl = inner_element.declaration
789
+ if hasattr(decl, 'declaration') and decl.declaration:
790
+ feat_decl = decl.declaration
791
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
792
+ vp.name = feat_decl.identification.declaredName
793
+ vp.parent = self
794
+ self.children.append(vp)
795
+ elif inner_class == "ConcernDefinition":
796
+ c = Concern(definition=True)
797
+ c.grammar = inner_element
798
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
799
+ decl = inner_element.declaration
800
+ if hasattr(decl, 'identification') and decl.identification:
801
+ c.name = decl.identification.declaredName
802
+ c.parent = self
803
+ self.children.append(c)
804
+ elif inner_class == "ConcernUsage":
805
+ c = Concern()
806
+ c.grammar = inner_element
807
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
808
+ decl = inner_element.declaration
809
+ if hasattr(decl, 'declaration') and decl.declaration:
810
+ feat_decl = decl.declaration
811
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
812
+ c.name = feat_decl.identification.declaredName
813
+ c.parent = self
814
+ self.children.append(c)
815
+ elif inner_class == "CaseDefinition":
816
+ c = Case(definition=True)
817
+ c.grammar = inner_element
818
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
819
+ decl = inner_element.declaration
820
+ if hasattr(decl, 'identification') and decl.identification:
821
+ c.name = decl.identification.declaredName
822
+ c.parent = self
823
+ self.children.append(c)
824
+ elif inner_class == "AnalysisCaseDefinition":
825
+ c = AnalysisCase(definition=True)
826
+ c.grammar = inner_element
827
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
828
+ decl = inner_element.declaration
829
+ if hasattr(decl, 'identification') and decl.identification:
830
+ c.name = decl.identification.declaredName
831
+ c.parent = self
832
+ self.children.append(c)
833
+ elif inner_class == "VerificationCaseDefinition":
834
+ c = VerificationCase(definition=True)
835
+ c.grammar = inner_element
836
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
837
+ decl = inner_element.declaration
838
+ if hasattr(decl, 'identification') and decl.identification:
839
+ c.name = decl.identification.declaredName
840
+ c.parent = self
841
+ self.children.append(c)
842
+ elif inner_class == "AnnotatingElement":
843
+ wrapper = _GrammarAnnotationWrapper(inner_element)
844
+ wrapper.parent = self
845
+ self.children.append(wrapper)
846
+ elif inner_class == "InterfaceDefinition":
847
+ wrapper = _GrammarAnnotationWrapper(inner_element)
848
+ wrapper.parent = self
849
+ self.children.append(wrapper)
850
+ elif inner_class == "InterfaceUsage":
851
+ wrapper = _GrammarAnnotationWrapper(inner_element)
852
+ wrapper.parent = self
853
+ self.children.append(wrapper)
854
+ elif inner_class == "AssertConstraintUsage":
855
+ c = Constraint()
856
+ c.grammar = inner_element
857
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
858
+ decl = inner_element.declaration
859
+ if hasattr(decl, 'declaration') and decl.declaration:
860
+ feat_decl = decl.declaration
861
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
862
+ c.name = feat_decl.identification.declaredName
863
+ c.parent = self
864
+ self.children.append(c)
865
+ elif inner_class == "RequirementUsage":
866
+ r = Requirement()
867
+ r.grammar = inner_element
868
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
869
+ decl = inner_element.declaration
870
+ if hasattr(decl, 'declaration') and decl.declaration:
871
+ feat_decl = decl.declaration
872
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
873
+ r.name = feat_decl.identification.declaredName
874
+ r.parent = self
875
+ self.children.append(r)
876
+ elif inner_class == "SatisfyRequirementUsage":
877
+ r = Requirement()
878
+ r.grammar = inner_element
879
+ if hasattr(inner_element, 'declaration') and inner_element.declaration:
880
+ decl = inner_element.declaration
881
+ if hasattr(decl, 'declaration') and decl.declaration:
882
+ feat_decl = decl.declaration
883
+ if hasattr(feat_decl, 'identification') and feat_decl.identification:
884
+ r.name = feat_decl.identification.declaredName
885
+ r.parent = self
886
+ self.children.append(r)
887
+ else:
888
+ print(f"Unknown class: {inner_class}")
889
+ raise NotImplementedError
890
+
891
+ return self
892
+
893
+ def _get_grammar(self):
894
+ # Force updates to grammar if something has changed.
895
+ self._ensure_body()
896
+ return self.grammar
897
+
898
+ # ── Convenience functions ───────────────────────────────────────────
899
+
900
+ def find_all(self, type=None, name=None):
901
+ """Recursively find all matching elements in this package and children.
902
+
903
+ Parameters
904
+ ----------
905
+ type : str or class, optional
906
+ Filter by SysML type (e.g. 'part', Part).
907
+ name : str, optional
908
+ Filter by element name.
909
+
910
+ Returns
911
+ -------
912
+ list
913
+ All matching elements across the full tree.
914
+ """
915
+ results = []
916
+ for child in self.children:
917
+ match = True
918
+ if name is not None and getattr(child, "name", None) != name:
919
+ match = False
920
+ if type is not None:
921
+ child_type = getattr(child, "sysml_type", None)
922
+ if isinstance(type, str):
923
+ if child_type != type:
924
+ match = False
925
+ else:
926
+ if not isinstance(child, type):
927
+ match = False
928
+ if match:
929
+ results.append(child)
930
+ if hasattr(child, "find_all"):
931
+ results.extend(child.find_all(type=type, name=name))
932
+ return results
933
+
934
+ def count(self, type=None):
935
+ """Count elements by type across the full tree.
936
+
937
+ Parameters
938
+ ----------
939
+ type : str or class, optional
940
+ Count only this type. If None, returns a dict of all types.
941
+
942
+ Returns
943
+ -------
944
+ int or dict
945
+ Count of matching elements, or dict of {type: count}.
946
+ """
947
+ if type is not None:
948
+ return len(self.find_all(type=type))
949
+
950
+ counts = {}
951
+ for child in self.children:
952
+ t = getattr(child, "sysml_type", "unknown")
953
+ counts[t] = counts.get(t, 0) + 1
954
+ if hasattr(child, "count"):
955
+ child_counts = child.count()
956
+ for k, v in child_counts.items():
957
+ counts[k] = counts.get(k, 0) + v
958
+ return counts
959
+
960
+ def traverse(self, callback, depth=0):
961
+ """Walk the element tree, calling callback for each element.
962
+
963
+ Parameters
964
+ ----------
965
+ callback : callable
966
+ Called as callback(element, depth) for each element.
967
+ depth : int
968
+ Current depth level (internal use).
969
+ """
970
+ for child in self.children:
971
+ callback(child, depth)
972
+ if hasattr(child, "traverse"):
973
+ child.traverse(callback, depth + 1)
974
+
975
+ def to_dict(self):
976
+ """Export the package and all children as a nested dictionary.
977
+
978
+ Returns
979
+ -------
980
+ dict
981
+ Nested dict representation of the package tree.
982
+ """
983
+ result = {
984
+ "name": getattr(self, "name", None),
985
+ "sysml_type": getattr(self, "sysml_type", None),
986
+ "children": [],
987
+ }
988
+ for child in self.children:
989
+ if hasattr(child, "to_dict"):
990
+ result["children"].append(child.to_dict())
991
+ else:
992
+ result["children"].append({
993
+ "name": getattr(child, "name", None),
994
+ "sysml_type": getattr(child, "sysml_type", None),
995
+ })
996
+ return result
997
+
998
+ def to_graph(self):
999
+ """Export the package tree as a NetworkX graph.
1000
+
1001
+ Requires: pip install networkx (or: pip install sysmlpy[graph])
1002
+
1003
+ Returns
1004
+ -------
1005
+ NetworkXStore
1006
+ A graph store with elements as nodes and parent-child
1007
+ relationships as edges.
1008
+ """
1009
+ from sysmlpy.store import NetworkXStore, new_id, REL_PARENT_CHILD
1010
+
1011
+ store = NetworkXStore()
1012
+ id_map = {}
1013
+
1014
+ def _add_element(elem, parent_id=None):
1015
+ eid = new_id()
1016
+ id_map[id(elem)] = eid
1017
+ data = {
1018
+ "name": getattr(elem, "name", None),
1019
+ "sysml_type": getattr(elem, "sysml_type", None),
1020
+ "python_type": type(elem).__name__,
1021
+ }
1022
+ store.put(eid, data, parent_id=parent_id)
1023
+ if hasattr(elem, "children"):
1024
+ for child in elem.children:
1025
+ child_id = _add_element(child, eid)
1026
+ return eid
1027
+
1028
+ _add_element(self)
1029
+ return store
1030
+
1031
+ def path_between(self, source_name, target_name):
1032
+ """Find the path between two elements by name.
1033
+
1034
+ Parameters
1035
+ ----------
1036
+ source_name : str
1037
+ Name of the source element.
1038
+ target_name : str
1039
+ Name of the target element.
1040
+
1041
+ Returns
1042
+ -------
1043
+ list or None
1044
+ List of element names forming the path, or None if no path exists.
1045
+ """
1046
+ source = self.find(name=source_name, recursive=True)
1047
+ target = self.find(name=target_name, recursive=True)
1048
+ if source is None or target is None:
1049
+ return None
1050
+ # find() returns a list
1051
+ if isinstance(source, list):
1052
+ source = source[0] if source else None
1053
+ if isinstance(target, list):
1054
+ target = target[0] if target else None
1055
+ if source is None or target is None:
1056
+ return None
1057
+
1058
+ # Build parent chain for both
1059
+ def parent_chain(elem):
1060
+ chain = [elem]
1061
+ current = elem
1062
+ while hasattr(current, "parent") and current.parent is not None:
1063
+ chain.append(current.parent)
1064
+ current = current.parent
1065
+ return chain
1066
+
1067
+ source_chain = parent_chain(source)
1068
+ target_chain = parent_chain(target)
1069
+
1070
+ # Find common ancestor
1071
+ source_set = {id(e): e for e in source_chain}
1072
+ for e in target_chain:
1073
+ if id(e) in source_set:
1074
+ common = e
1075
+ break
1076
+ else:
1077
+ return None
1078
+
1079
+ # Build path: source -> ... -> common -> ... -> target
1080
+ path = []
1081
+ current = source
1082
+ while current is not common:
1083
+ path.append(getattr(current, "name", "?"))
1084
+ current = getattr(current, "parent", None)
1085
+ path.append(getattr(common, "name", "?"))
1086
+
1087
+ target_to_common = []
1088
+ current = target
1089
+ while current is not common:
1090
+ target_to_common.append(getattr(current, "name", "?"))
1091
+ current = getattr(current, "parent", None)
1092
+ path.extend(reversed(target_to_common))
1093
+
1094
+ return path
1095
+
1096
+
1097
+ class _GrammarAnnotationWrapper:
1098
+ """Minimal wrapper for grammar-only elements (doc/comment) that need _get_definition."""
1099
+ def __init__(self, grammar_obj):
1100
+ self.grammar = grammar_obj
1101
+ self.name = None
1102
+
1103
+ def _get_definition(self, child=None):
1104
+ if hasattr(self.grammar, 'get_definition'):
1105
+ inner = self.grammar.get_definition()
1106
+ else:
1107
+ inner = {"name": type(self.grammar).__name__}
1108
+ return {
1109
+ "name": "PackageMember",
1110
+ "prefix": None,
1111
+ "ownedRelatedElement": {
1112
+ "name": "DefinitionElement",
1113
+ "ownedRelatedElement": inner
1114
+ }
1115
+ }