stepxmlclient 1.0.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.
- stepxmlclient/__init__.py +22 -0
- stepxmlclient/client.py +373 -0
- stepxmlclient/creator.py +317 -0
- stepxmlclient/model/__init__.py +418 -0
- stepxmlclient/model/asset.py +250 -0
- stepxmlclient/model/attribute.py +288 -0
- stepxmlclient/model/business.py +233 -0
- stepxmlclient/model/classification.py +409 -0
- stepxmlclient/model/complex_elements.py +260 -0
- stepxmlclient/model/deletion.py +87 -0
- stepxmlclient/model/entity.py +368 -0
- stepxmlclient/model/metadata.py +199 -0
- stepxmlclient/model/organization.py +167 -0
- stepxmlclient/model/product.py +519 -0
- stepxmlclient/model/relations.py +105 -0
- stepxmlclient/model/replacement.py +417 -0
- stepxmlclient/parser.py +2805 -0
- stepxmlclient/rest_client/__init__.py +16 -0
- stepxmlclient/rest_client/base.py +388 -0
- stepxmlclient/rest_client/client.py +127 -0
- stepxmlclient/rest_client/exceptions.py +97 -0
- stepxmlclient/rest_client/models/__init__.py +175 -0
- stepxmlclient/rest_client/models/asset.py +253 -0
- stepxmlclient/rest_client/models/attribute.py +112 -0
- stepxmlclient/rest_client/models/background_process.py +23 -0
- stepxmlclient/rest_client/models/batch.py +41 -0
- stepxmlclient/rest_client/models/classification.py +57 -0
- stepxmlclient/rest_client/models/collection.py +26 -0
- stepxmlclient/rest_client/models/common.py +192 -0
- stepxmlclient/rest_client/models/context.py +16 -0
- stepxmlclient/rest_client/models/data_type_group.py +15 -0
- stepxmlclient/rest_client/models/entity.py +63 -0
- stepxmlclient/rest_client/models/event_processor.py +15 -0
- stepxmlclient/rest_client/models/import_export.py +43 -0
- stepxmlclient/rest_client/models/integration_endpoint.py +16 -0
- stepxmlclient/rest_client/models/product.py +200 -0
- stepxmlclient/rest_client/models/relations.py +280 -0
- stepxmlclient/rest_client/models/system_information.py +11 -0
- stepxmlclient/rest_client/models/user.py +28 -0
- stepxmlclient/rest_client/models/workflow.py +30 -0
- stepxmlclient/rest_client/operations/__init__.py +54 -0
- stepxmlclient/rest_client/operations/assets.py +652 -0
- stepxmlclient/rest_client/operations/attributes.py +252 -0
- stepxmlclient/rest_client/operations/background_processes.py +84 -0
- stepxmlclient/rest_client/operations/batch.py +28 -0
- stepxmlclient/rest_client/operations/classifications.py +248 -0
- stepxmlclient/rest_client/operations/collection_groups.py +46 -0
- stepxmlclient/rest_client/operations/collections.py +67 -0
- stepxmlclient/rest_client/operations/contexts.py +88 -0
- stepxmlclient/rest_client/operations/data_type_groups.py +68 -0
- stepxmlclient/rest_client/operations/entities.py +295 -0
- stepxmlclient/rest_client/operations/event_processors.py +109 -0
- stepxmlclient/rest_client/operations/exports.py +153 -0
- stepxmlclient/rest_client/operations/gateway.py +53 -0
- stepxmlclient/rest_client/operations/inbound.py +472 -0
- stepxmlclient/rest_client/operations/outbound.py +64 -0
- stepxmlclient/rest_client/operations/products.py +489 -0
- stepxmlclient/rest_client/operations/relations.py +349 -0
- stepxmlclient/rest_client/operations/reports.py +27 -0
- stepxmlclient/rest_client/operations/system_information.py +28 -0
- stepxmlclient/rest_client/operations/user_groups.py +79 -0
- stepxmlclient/rest_client/operations/users.py +119 -0
- stepxmlclient/rest_client/operations/workflow_tasks.py +61 -0
- stepxmlclient/rest_client/operations/workflows.py +50 -0
- stepxmlclient/upload.py +293 -0
- stepxmlclient-1.0.0.dist-info/METADATA +666 -0
- stepxmlclient-1.0.0.dist-info/RECORD +70 -0
- stepxmlclient-1.0.0.dist-info/WHEEL +5 -0
- stepxmlclient-1.0.0.dist-info/licenses/LICENSE +21 -0
- stepxmlclient-1.0.0.dist-info/top_level.txt +1 -0
stepxmlclient/parser.py
ADDED
|
@@ -0,0 +1,2805 @@
|
|
|
1
|
+
"""
|
|
2
|
+
STEP-XML Parser module.
|
|
3
|
+
|
|
4
|
+
This module provides the Parser class for deserializing STEP-XML documents into Python dictionaries.
|
|
5
|
+
It handles both namespaced and unnamespaced XML elements and supports parsing all STEP entity types
|
|
6
|
+
including Products, Assets, Classifications, Attributes, Business Rules, and advanced configuration elements.
|
|
7
|
+
|
|
8
|
+
The parser uses a declarative approach with separate lists for each entity type, following the
|
|
9
|
+
STEP-XML schema structure. It handles namespace-aware parsing automatically, supporting both
|
|
10
|
+
prefixed namespaces (e.g., step:Product) and default namespaces.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
Basic parsing workflow::
|
|
14
|
+
|
|
15
|
+
from stepxmlclient.parser import Parser
|
|
16
|
+
|
|
17
|
+
parser = Parser()
|
|
18
|
+
parser.parse_file('step_data.xml')
|
|
19
|
+
|
|
20
|
+
# Access parsed entities
|
|
21
|
+
for product in parser.products:
|
|
22
|
+
print(product['ID'])
|
|
23
|
+
|
|
24
|
+
for asset in parser.assets:
|
|
25
|
+
print(asset['ID'])
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import xml.etree.ElementTree as ET
|
|
29
|
+
from typing import Any, Callable, List, Optional
|
|
30
|
+
from . import STEPXML_NS
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _str_to_bool(value: str) -> bool:
|
|
34
|
+
"""Convert XML boolean string ('true'/'false') to Python bool.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
value: String value from XML ('true' or 'false', case-insensitive)
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Boolean representation of the string value
|
|
41
|
+
"""
|
|
42
|
+
return value.lower() == 'true'
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _get_local_tag(element: ET.Element) -> str:
|
|
46
|
+
"""Extract local tag name from potentially namespaced tag.
|
|
47
|
+
|
|
48
|
+
Handles both namespaced tags (e.g., '{http://...}Value') and
|
|
49
|
+
unnamespaced tags (e.g., 'Value'). This is essential for robust XML parsing
|
|
50
|
+
since different XML sources may or may not include namespace prefixes.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
element: XML Element to extract tag name from
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Local tag name without namespace prefix
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
For an element with tag '{http://www.stibosystems.com/step}Product',
|
|
60
|
+
returns 'Product'
|
|
61
|
+
"""
|
|
62
|
+
tag = element.tag
|
|
63
|
+
if '}' in tag:
|
|
64
|
+
return tag.split('}', 1)[1]
|
|
65
|
+
return tag
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _get_namespace_from_tag(tag: str) -> Optional[str]:
|
|
69
|
+
"""Extract namespace URL from a namespaced tag.
|
|
70
|
+
|
|
71
|
+
Handles both namespaced tags (e.g., '{http://...}Value') and
|
|
72
|
+
unnamespaced tags (e.g., 'Value'). Returns None if tag is unnamespaced.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
tag: Tag string from XML Element
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Namespace URL if tag is namespaced, None otherwise
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
For tag '{http://www.stibosystems.com/step}Product',
|
|
82
|
+
returns 'http://www.stibosystems.com/step'
|
|
83
|
+
"""
|
|
84
|
+
if not tag or '}' not in tag:
|
|
85
|
+
return None
|
|
86
|
+
# Extract namespace from {namespace}localname format
|
|
87
|
+
return tag[1:tag.index('}')]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Parser:
|
|
91
|
+
"""Parser for STEP-XML documents.
|
|
92
|
+
|
|
93
|
+
Deserializes STEP-XML documents into Python dictionaries organized by entity type.
|
|
94
|
+
The parser maintains separate lists for each STEP entity type (products, assets,
|
|
95
|
+
classifications, etc.) and handles namespace-aware XML parsing automatically.
|
|
96
|
+
|
|
97
|
+
Attributes:
|
|
98
|
+
products: List of parsed Product entities
|
|
99
|
+
assets: List of parsed Asset entities
|
|
100
|
+
classifications: List of parsed Classification entities
|
|
101
|
+
entities: List of parsed Entity (business entity) objects
|
|
102
|
+
attributes: List of parsed Attribute definitions
|
|
103
|
+
attribute_groups: List of parsed Attribute groups
|
|
104
|
+
list_of_values_groups: List of parsed List-of-Values group definitions
|
|
105
|
+
list_of_values: List of parsed List-of-Values items
|
|
106
|
+
user_types: List of parsed User Type definitions
|
|
107
|
+
contexts: List of parsed STEP Contexts
|
|
108
|
+
link_types: List of parsed Link Type definitions
|
|
109
|
+
delete_products: List of Product deletion specifications
|
|
110
|
+
delete_assets: List of Asset deletion specifications
|
|
111
|
+
delete_classifications: List of Classification deletion specifications
|
|
112
|
+
delete_entities: List of Entity deletion specifications
|
|
113
|
+
business_rules: List of parsed Business Rule definitions
|
|
114
|
+
event_queues: List of parsed Event Queue configurations
|
|
115
|
+
workflows: List of parsed Workflow definitions
|
|
116
|
+
export_configurations: List of parsed Export Configuration definitions
|
|
117
|
+
cross_reference_types: List of parsed Cross Reference Type definitions
|
|
118
|
+
qualifiers: List of parsed Qualifier definitions
|
|
119
|
+
dimensions: List of parsed Dimension definitions
|
|
120
|
+
tags: List of parsed Tag definitions
|
|
121
|
+
tag_groups: List of parsed Tag Group definitions
|
|
122
|
+
units: List of parsed Unit definitions
|
|
123
|
+
replacement_rules: Dictionary of replacement rules for various entity types
|
|
124
|
+
collections: List of parsed Collection entities
|
|
125
|
+
collection_groups: List of parsed Collection Group entities
|
|
126
|
+
bulk_update_configurations: List of parsed Bulk Update Configuration definitions
|
|
127
|
+
derived_event_types: List of parsed Derived Event Type definitions
|
|
128
|
+
global_settings: Dictionary of global system settings
|
|
129
|
+
import_configurations: List of parsed Import Configuration definitions
|
|
130
|
+
portal_configurations: List of parsed Portal Configuration definitions
|
|
131
|
+
keys: List of parsed Key definitions (Phase 14)
|
|
132
|
+
cross_contexts: List of parsed Cross-Context definitions (Phase 14)
|
|
133
|
+
business_libraries: List of parsed Business Library definitions (Phase 14)
|
|
134
|
+
setup_groups: List of parsed Setup Group definitions (Phase 14)
|
|
135
|
+
action_sets: List of parsed Action Set definitions (Phase 14)
|
|
136
|
+
status_flags: List of parsed Status Flag definitions (Phase 14)
|
|
137
|
+
integration_endpoints: List of parsed Integration Endpoint definitions (Phase 14)
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self):
|
|
141
|
+
"""Initialize a new Parser instance with empty entity lists.
|
|
142
|
+
|
|
143
|
+
Creates a fresh parser with all entity collection lists initialized as empty.
|
|
144
|
+
Call parse_file(), parse_str(), or parse() to populate these lists with parsed data.
|
|
145
|
+
"""
|
|
146
|
+
self._tree = None
|
|
147
|
+
self._root: Optional[ET.Element] = None
|
|
148
|
+
self.products: List[dict] = []
|
|
149
|
+
self.assets: List[dict] = []
|
|
150
|
+
self.classifications: List[dict] = []
|
|
151
|
+
self.entities: List[dict] = []
|
|
152
|
+
self.attributes: List[dict] = []
|
|
153
|
+
self.attribute_groups: List[dict] = []
|
|
154
|
+
self.list_of_values_groups: List[dict] = []
|
|
155
|
+
self.list_of_values: List[dict] = []
|
|
156
|
+
self.user_types: List[dict] = []
|
|
157
|
+
self.contexts: List[dict] = []
|
|
158
|
+
self.link_types: List[dict] = []
|
|
159
|
+
self.delete_products: List[dict] = []
|
|
160
|
+
self.delete_assets: List[dict] = []
|
|
161
|
+
self.delete_classifications: List[dict] = []
|
|
162
|
+
self.delete_entities: List[dict] = []
|
|
163
|
+
self.business_rules: List[dict] = []
|
|
164
|
+
self.event_queues: List[dict] = []
|
|
165
|
+
self.workflows: List[dict] = []
|
|
166
|
+
self.export_configurations: List[dict] = []
|
|
167
|
+
self.cross_reference_types: List[dict] = []
|
|
168
|
+
self.qualifiers: List[dict] = []
|
|
169
|
+
self.dimensions: List[dict] = []
|
|
170
|
+
self.tags: List[dict] = []
|
|
171
|
+
self.tag_groups: List[dict] = []
|
|
172
|
+
self.units: List[dict] = []
|
|
173
|
+
self.replacement_rules: dict = {}
|
|
174
|
+
self.collections: List[dict] = []
|
|
175
|
+
self.collection_groups: List[dict] = []
|
|
176
|
+
self.bulk_update_configurations: List[dict] = []
|
|
177
|
+
self.derived_event_types: List[dict] = []
|
|
178
|
+
self.global_settings: dict = {}
|
|
179
|
+
self.import_configurations: List[dict] = []
|
|
180
|
+
self.portal_configurations: List[dict] = []
|
|
181
|
+
# Phase 14 - Advanced Configuration & System Elements
|
|
182
|
+
self.keys: List[dict] = []
|
|
183
|
+
self.cross_contexts: List[dict] = []
|
|
184
|
+
self.business_libraries: List[dict] = []
|
|
185
|
+
self.setup_groups: List[dict] = []
|
|
186
|
+
self.action_sets: List[dict] = []
|
|
187
|
+
self.status_flags: List[dict] = []
|
|
188
|
+
self.integration_endpoints: List[dict] = []
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
def parse_file(self, file_path: str):
|
|
192
|
+
"""Parse a STEP-XML file from disk.
|
|
193
|
+
|
|
194
|
+
Reads and parses an XML file, populating all entity lists from the file contents.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
file_path: Path to the STEP-XML file to parse
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
None (populates instance attributes with parsed data)
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
FileNotFoundError: If the file does not exist
|
|
204
|
+
ParseError: If the XML is malformed
|
|
205
|
+
AssertionError: If the root element is not STEP-ProductInformation
|
|
206
|
+
|
|
207
|
+
Example:
|
|
208
|
+
parser = Parser()
|
|
209
|
+
parser.parse_file('/path/to/step_data.xml')
|
|
210
|
+
print(f"Parsed {len(parser.products)} products")
|
|
211
|
+
"""
|
|
212
|
+
self._tree = ET.parse(file_path)
|
|
213
|
+
return self.parse(self._tree.getroot())
|
|
214
|
+
|
|
215
|
+
def parse_str(self, xml_str: str):
|
|
216
|
+
"""Parse a STEP-XML document from a string.
|
|
217
|
+
|
|
218
|
+
Parses XML content provided as a string, populating all entity lists.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
xml_str: XML content as a string
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
None (populates instance attributes with parsed data)
|
|
225
|
+
|
|
226
|
+
Raises:
|
|
227
|
+
ParseError: If the XML is malformed
|
|
228
|
+
AssertionError: If the root element is not STEP-ProductInformation
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
parser = Parser()
|
|
232
|
+
parser.parse_str(xml_content)
|
|
233
|
+
assets = parser.assets
|
|
234
|
+
"""
|
|
235
|
+
return self.parse(ET.fromstring(xml_str))
|
|
236
|
+
|
|
237
|
+
def parse(self, root: ET.Element):
|
|
238
|
+
"""Parse a STEP-XML ElementTree root element.
|
|
239
|
+
|
|
240
|
+
Deserializes a STEP-XML document from an ElementTree root element, parsing all
|
|
241
|
+
entity types and organizing them into their respective lists.
|
|
242
|
+
|
|
243
|
+
This is the core parsing method called by parse_file() and parse_str().
|
|
244
|
+
It orchestrates parsing of all STEP entity types in the correct order.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
root: The root ElementTree Element of a STEP-XML document
|
|
248
|
+
|
|
249
|
+
Raises:
|
|
250
|
+
AssertionError: If the root element is not STEP-ProductInformation
|
|
251
|
+
|
|
252
|
+
Note:
|
|
253
|
+
The root element must be 'STEP-ProductInformation'. Namespace handling is
|
|
254
|
+
automatic - the parser works with both namespaced and unnamespaced XML.
|
|
255
|
+
"""
|
|
256
|
+
# Handle both namespaced and unnamespaced root elements
|
|
257
|
+
local_tag = _get_local_tag(root)
|
|
258
|
+
assert local_tag == 'STEP-ProductInformation', f"Expected STEP-ProductInformation, got {local_tag}"
|
|
259
|
+
self._root = root
|
|
260
|
+
self.assets = self._parse_item_list(
|
|
261
|
+
'Assets', 'Asset', _parse_asset)
|
|
262
|
+
self.products = self._parse_item_list(
|
|
263
|
+
'Products', 'Product', _parse_product)
|
|
264
|
+
self.classifications = self._parse_item_list(
|
|
265
|
+
'Classifications', 'Classification', _parse_classification
|
|
266
|
+
)
|
|
267
|
+
self.entities = self._parse_item_list(
|
|
268
|
+
'Entities', 'Entity', _parse_entity
|
|
269
|
+
)
|
|
270
|
+
self.attributes = self._parse_item_list(
|
|
271
|
+
'AttributeList', 'Attribute', _parse_attribute
|
|
272
|
+
)
|
|
273
|
+
self.attribute_groups = self._parse_item_list(
|
|
274
|
+
'AttributeGroupList', 'AttributeGroup', _parse_attribute_group
|
|
275
|
+
)
|
|
276
|
+
self.list_of_values_groups = self._parse_item_list(
|
|
277
|
+
'ListOfValuesGroupList', 'ListOfValuesGroup', _parse_list_of_values_group
|
|
278
|
+
)
|
|
279
|
+
self.list_of_values = self._parse_item_list(
|
|
280
|
+
'ListsOfValues', 'ListOfValue', _parse_list_of_value
|
|
281
|
+
)
|
|
282
|
+
self.user_types = self._parse_item_list(
|
|
283
|
+
'UserTypes', 'UserType', _parse_user_type
|
|
284
|
+
)
|
|
285
|
+
self.contexts = self._parse_item_list(
|
|
286
|
+
'ContextList', 'Context', _parse_context
|
|
287
|
+
)
|
|
288
|
+
self.link_types = self._parse_item_list(
|
|
289
|
+
'EdgeTypes', 'LinkType', _parse_link_type
|
|
290
|
+
)
|
|
291
|
+
self.delete_assets = self._parse_item_list(
|
|
292
|
+
'DeleteAssets', 'DeleteAsset', _parse_delete_asset
|
|
293
|
+
)
|
|
294
|
+
self.delete_products = self._parse_item_list(
|
|
295
|
+
'DeleteProducts', 'DeleteProduct', _parse_delete_product
|
|
296
|
+
)
|
|
297
|
+
self.delete_classifications = self._parse_item_list(
|
|
298
|
+
'DeleteClassifications', 'DeleteClassification', _parse_delete_classification
|
|
299
|
+
)
|
|
300
|
+
self.delete_entities = self._parse_item_list(
|
|
301
|
+
'DeleteEntities', 'DeleteEntity', _parse_delete_entity
|
|
302
|
+
)
|
|
303
|
+
self.business_rules = self._parse_item_list(
|
|
304
|
+
'BusinessRules', 'BusinessRule', _parse_business_rule
|
|
305
|
+
)
|
|
306
|
+
self.event_queues = self._parse_item_list(
|
|
307
|
+
'EventQueues', 'EventQueue', _parse_event_queue
|
|
308
|
+
)
|
|
309
|
+
self.workflows = self._parse_item_list(
|
|
310
|
+
'STEPWorkflows', 'STEPWorkflow', _parse_workflow
|
|
311
|
+
)
|
|
312
|
+
self.export_configurations = self._parse_item_list(
|
|
313
|
+
'ExportConfigurations', 'ExportConfiguration', _parse_export_configuration
|
|
314
|
+
)
|
|
315
|
+
self.cross_reference_types = self._parse_item_list(
|
|
316
|
+
'CrossReferenceTypes', 'CrossReferenceType', _parse_cross_reference_type
|
|
317
|
+
)
|
|
318
|
+
self.qualifiers = self._parse_item_list(
|
|
319
|
+
'Qualifiers', 'Qualifier', _parse_qualifier
|
|
320
|
+
)
|
|
321
|
+
self.dimensions = self._parse_item_list(
|
|
322
|
+
'DimensionList', 'Dimension', _parse_dimension
|
|
323
|
+
)
|
|
324
|
+
self.tags = self._parse_item_list(
|
|
325
|
+
'TagList', 'Tag', _parse_tag
|
|
326
|
+
)
|
|
327
|
+
self.tag_groups = self._parse_item_list(
|
|
328
|
+
'TagGroupList', 'TagGroup', _parse_tag_group
|
|
329
|
+
)
|
|
330
|
+
self.units = self._parse_item_list(
|
|
331
|
+
'UnitList', 'Unit', _parse_unit
|
|
332
|
+
)
|
|
333
|
+
self.replacement_rules = self._parse_replacement_rules()
|
|
334
|
+
self.collections, self.collection_groups = self._parse_collections()
|
|
335
|
+
self.bulk_update_configurations = self._parse_item_list(
|
|
336
|
+
'BulkUpdateConfigurations', 'BulkUpdateConfiguration', _parse_bulk_update_configuration
|
|
337
|
+
)
|
|
338
|
+
self.derived_event_types = self._parse_derived_event_types()
|
|
339
|
+
self.global_settings = self._parse_global_settings()
|
|
340
|
+
self.import_configurations = self._parse_item_list(
|
|
341
|
+
'ImportConfigurations', 'ImportConfiguration', _parse_import_configuration
|
|
342
|
+
)
|
|
343
|
+
self.portal_configurations = self._parse_item_list(
|
|
344
|
+
'PortalConfigurations', 'PortalConfiguration', _parse_portal_configuration
|
|
345
|
+
)
|
|
346
|
+
# Phase 14 - Advanced Configuration & System Elements
|
|
347
|
+
self.keys = self._parse_item_list(
|
|
348
|
+
'Keys', 'Key', _parse_key
|
|
349
|
+
)
|
|
350
|
+
self.cross_contexts = self._parse_item_list(
|
|
351
|
+
'CrossContexts', 'Context', _parse_cross_context
|
|
352
|
+
)
|
|
353
|
+
self.business_libraries = self._parse_item_list(
|
|
354
|
+
'BusinessLibraries', 'BusinessRule', _parse_business_library
|
|
355
|
+
)
|
|
356
|
+
self.setup_groups = self._parse_item_list(
|
|
357
|
+
'SetupGroups', 'SetupGroup', _parse_setup_group
|
|
358
|
+
)
|
|
359
|
+
self.action_sets = self._parse_item_list(
|
|
360
|
+
'ActionSetList', 'ActionSet', _parse_action_set
|
|
361
|
+
)
|
|
362
|
+
self.status_flags = self._parse_item_list(
|
|
363
|
+
'StatusFlags', 'StatusFlag', _parse_status_flag
|
|
364
|
+
)
|
|
365
|
+
self.integration_endpoints = self._parse_integration_endpoints()
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
def _parse_item_list(self,
|
|
369
|
+
list_name: str,
|
|
370
|
+
item_name: str,
|
|
371
|
+
parse_fct: Callable[[Any], Any]
|
|
372
|
+
) -> List[Any]:
|
|
373
|
+
"""Parse a list of items from the XML document.
|
|
374
|
+
|
|
375
|
+
Generic method for parsing repeating elements in the STEP-XML document.
|
|
376
|
+
Handles namespace-aware lookup and applies a custom parser function to each item.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
list_name: XML element name containing the items (e.g., 'Products')
|
|
380
|
+
item_name: XML element name for individual items (e.g., 'Product')
|
|
381
|
+
parse_fct: Callable that parses a single item element and returns a dictionary
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
List of parsed item dictionaries, empty list if the container element not found
|
|
385
|
+
|
|
386
|
+
Note:
|
|
387
|
+
This method automatically handles both namespaced and unnamespaced XML by:
|
|
388
|
+
1. First attempting namespace-aware lookup using STEPXML_NS
|
|
389
|
+
2. Falling back to local tag name matching for default namespaces
|
|
390
|
+
"""
|
|
391
|
+
results: List[Any] = []
|
|
392
|
+
if self._root is None:
|
|
393
|
+
return results
|
|
394
|
+
|
|
395
|
+
# Try to find the item list by local tag name, ignoring namespace
|
|
396
|
+
item_list = None
|
|
397
|
+
|
|
398
|
+
# First try with explicit namespace (for prefixed XML like step:Products)
|
|
399
|
+
item_list = self._root.find(list_name, STEPXML_NS)
|
|
400
|
+
|
|
401
|
+
# If not found, search by local tag name (handles default namespaces)
|
|
402
|
+
if item_list is None:
|
|
403
|
+
for child in self._root:
|
|
404
|
+
if _get_local_tag(child) == list_name:
|
|
405
|
+
item_list = child
|
|
406
|
+
break
|
|
407
|
+
|
|
408
|
+
if item_list is not None:
|
|
409
|
+
# Collect items, trying both namespace-aware and namespace-agnostic approaches
|
|
410
|
+
items = []
|
|
411
|
+
|
|
412
|
+
# Try with explicit namespace first
|
|
413
|
+
items = list(item_list.findall(item_name, STEPXML_NS))
|
|
414
|
+
|
|
415
|
+
# If no results, search by local tag name for each child
|
|
416
|
+
if not items:
|
|
417
|
+
for child in item_list:
|
|
418
|
+
if _get_local_tag(child) == item_name:
|
|
419
|
+
items.append(child)
|
|
420
|
+
|
|
421
|
+
for item in items:
|
|
422
|
+
result = parse_fct(item)
|
|
423
|
+
if result is not None:
|
|
424
|
+
results.append(result)
|
|
425
|
+
|
|
426
|
+
return results
|
|
427
|
+
|
|
428
|
+
def _parse_replacement_rules(self) -> dict:
|
|
429
|
+
"""Parse replacement rules for products, classifications, entities, assets, attributes, etc."""
|
|
430
|
+
replacement_rules_elem = None
|
|
431
|
+
|
|
432
|
+
if self._root is None:
|
|
433
|
+
return {}
|
|
434
|
+
|
|
435
|
+
for child in self._root:
|
|
436
|
+
local_tag = _get_local_tag(child)
|
|
437
|
+
if local_tag == "ReplacementRules":
|
|
438
|
+
replacement_rules_elem = child
|
|
439
|
+
break
|
|
440
|
+
|
|
441
|
+
if replacement_rules_elem is None:
|
|
442
|
+
return {}
|
|
443
|
+
|
|
444
|
+
result = {}
|
|
445
|
+
|
|
446
|
+
for child in replacement_rules_elem:
|
|
447
|
+
local_tag = _get_local_tag(child)
|
|
448
|
+
|
|
449
|
+
if local_tag == "Attributes":
|
|
450
|
+
if "Attributes" not in result:
|
|
451
|
+
result["Attributes"] = []
|
|
452
|
+
result["Attributes"].append(_parse_replace_attributes(child))
|
|
453
|
+
|
|
454
|
+
elif local_tag == "Products":
|
|
455
|
+
if "Products" not in result:
|
|
456
|
+
result["Products"] = []
|
|
457
|
+
result["Products"].append(_parse_replace_products(child))
|
|
458
|
+
|
|
459
|
+
elif local_tag == "Classifications":
|
|
460
|
+
if "Classifications" not in result:
|
|
461
|
+
result["Classifications"] = []
|
|
462
|
+
result["Classifications"].append(_parse_replace_classifications(child))
|
|
463
|
+
|
|
464
|
+
elif local_tag == "Entities":
|
|
465
|
+
if "Entities" not in result:
|
|
466
|
+
result["Entities"] = []
|
|
467
|
+
result["Entities"].append(_parse_replace_entities(child))
|
|
468
|
+
|
|
469
|
+
elif local_tag == "Assets":
|
|
470
|
+
if "Assets" not in result:
|
|
471
|
+
result["Assets"] = []
|
|
472
|
+
result["Assets"].append(_parse_replace_assets(child))
|
|
473
|
+
|
|
474
|
+
elif local_tag == "LinkTypes":
|
|
475
|
+
if "LinkTypes" not in result:
|
|
476
|
+
result["LinkTypes"] = []
|
|
477
|
+
result["LinkTypes"].append(_parse_replace_link_types(child))
|
|
478
|
+
|
|
479
|
+
elif local_tag == "UserTypes":
|
|
480
|
+
if "UserTypes" not in result:
|
|
481
|
+
result["UserTypes"] = []
|
|
482
|
+
result["UserTypes"].append(_parse_replace_user_types(child))
|
|
483
|
+
|
|
484
|
+
elif local_tag == "UserGroups":
|
|
485
|
+
if "UserGroups" not in result:
|
|
486
|
+
result["UserGroups"] = []
|
|
487
|
+
result["UserGroups"].append(_parse_replace_user_groups(child))
|
|
488
|
+
|
|
489
|
+
return result
|
|
490
|
+
|
|
491
|
+
def _parse_collections(self) -> tuple:
|
|
492
|
+
"""Parse collections and collection groups, returns (collections, collection_groups)."""
|
|
493
|
+
collection_list_elem = None
|
|
494
|
+
|
|
495
|
+
if self._root is None:
|
|
496
|
+
return [], []
|
|
497
|
+
|
|
498
|
+
for child in self._root:
|
|
499
|
+
local_tag = _get_local_tag(child)
|
|
500
|
+
if local_tag == "CollectionList":
|
|
501
|
+
collection_list_elem = child
|
|
502
|
+
break
|
|
503
|
+
|
|
504
|
+
collections = []
|
|
505
|
+
collection_groups = []
|
|
506
|
+
|
|
507
|
+
if collection_list_elem is not None:
|
|
508
|
+
for child in collection_list_elem:
|
|
509
|
+
local_tag = _get_local_tag(child)
|
|
510
|
+
if local_tag == "Collection":
|
|
511
|
+
collections.append(_parse_collection_item(child, is_group=False))
|
|
512
|
+
elif local_tag == "CollectionGroup":
|
|
513
|
+
collection_groups.append(_parse_collection_item(child, is_group=True))
|
|
514
|
+
|
|
515
|
+
return collections, collection_groups
|
|
516
|
+
|
|
517
|
+
def _parse_derived_event_types(self) -> list:
|
|
518
|
+
"""Parse derived event types (simple text elements)."""
|
|
519
|
+
result = []
|
|
520
|
+
|
|
521
|
+
if self._root is None:
|
|
522
|
+
return result
|
|
523
|
+
|
|
524
|
+
for child in self._root:
|
|
525
|
+
local_tag = _get_local_tag(child)
|
|
526
|
+
if local_tag == "DerivedEventTypes":
|
|
527
|
+
# DerivedEventTypes contains DerivedEventType text elements
|
|
528
|
+
for event_child in child:
|
|
529
|
+
event_tag = _get_local_tag(event_child)
|
|
530
|
+
if event_tag == "DerivedEventType":
|
|
531
|
+
event_text = event_child.text
|
|
532
|
+
if event_text:
|
|
533
|
+
result.append({"value": event_text})
|
|
534
|
+
break
|
|
535
|
+
|
|
536
|
+
return result
|
|
537
|
+
|
|
538
|
+
def _parse_global_settings(self) -> dict:
|
|
539
|
+
"""Parse global settings (system configuration)."""
|
|
540
|
+
if self._root is None:
|
|
541
|
+
return {}
|
|
542
|
+
|
|
543
|
+
for child in self._root:
|
|
544
|
+
local_tag = _get_local_tag(child)
|
|
545
|
+
if local_tag == "GlobalSettings":
|
|
546
|
+
result = {}
|
|
547
|
+
# Parse CalculatedAttributeSettings
|
|
548
|
+
for settings_child in child:
|
|
549
|
+
settings_tag = _get_local_tag(settings_child)
|
|
550
|
+
if settings_tag == "CalculatedAttributeSettings":
|
|
551
|
+
result["CalculatedAttributeSettings"] = _parse_element(settings_child, attributes=[], texts=[])
|
|
552
|
+
elif settings_tag == "AssetSettings":
|
|
553
|
+
result["AssetSettings"] = _parse_element(settings_child, attributes=[], texts=[])
|
|
554
|
+
return result
|
|
555
|
+
|
|
556
|
+
return {}
|
|
557
|
+
|
|
558
|
+
def _parse_integration_endpoints(self) -> list:
|
|
559
|
+
"""Parse integration endpoints (OutBound, InBound, Gateway variants)."""
|
|
560
|
+
results = []
|
|
561
|
+
if self._root is None:
|
|
562
|
+
return results
|
|
563
|
+
|
|
564
|
+
for child in self._root:
|
|
565
|
+
local_tag = _get_local_tag(child)
|
|
566
|
+
if local_tag == "IntegrationEndpoints":
|
|
567
|
+
for endpoint_child in child:
|
|
568
|
+
endpoint_tag = _get_local_tag(endpoint_child)
|
|
569
|
+
if endpoint_tag in ("OutBoundIntegrationEndpoint", "InBoundIntegrationEndpoint", "GatewayIntegrationEndpoint"):
|
|
570
|
+
endpoint_data = _parse_element(
|
|
571
|
+
endpoint_child,
|
|
572
|
+
attributes=["ID"],
|
|
573
|
+
texts=[]
|
|
574
|
+
)
|
|
575
|
+
endpoint_data["Type"] = endpoint_tag # Add type to distinguish variants
|
|
576
|
+
|
|
577
|
+
# Parse Name elements (multiple)
|
|
578
|
+
names = []
|
|
579
|
+
for ep_child in endpoint_child:
|
|
580
|
+
ep_tag = _get_local_tag(ep_child)
|
|
581
|
+
if ep_tag == "Name":
|
|
582
|
+
name_text = ep_child.text
|
|
583
|
+
if name_text:
|
|
584
|
+
names.append({"value": name_text})
|
|
585
|
+
if names:
|
|
586
|
+
endpoint_data["Name"] = names
|
|
587
|
+
|
|
588
|
+
results.append(endpoint_data)
|
|
589
|
+
|
|
590
|
+
return results
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _parse_element(
|
|
594
|
+
element, attributes = [], texts = [],
|
|
595
|
+
specials = {}, conversions={}):
|
|
596
|
+
result = {}
|
|
597
|
+
for attr_name in attributes:
|
|
598
|
+
result[attr_name] = element.get(attr_name)
|
|
599
|
+
# Apply conversions to attributes after parsing
|
|
600
|
+
for attr_name in attributes:
|
|
601
|
+
if attr_name in conversions:
|
|
602
|
+
conv_fct, default = conversions[attr_name]
|
|
603
|
+
if result[attr_name] is not None:
|
|
604
|
+
result[attr_name] = conv_fct(result[attr_name])
|
|
605
|
+
elif default is not None:
|
|
606
|
+
result[attr_name] = default
|
|
607
|
+
for child in element:
|
|
608
|
+
# Use local tag name to handle both namespaced and unnamespaced XML
|
|
609
|
+
local_tag = _get_local_tag(child)
|
|
610
|
+
key = local_tag
|
|
611
|
+
|
|
612
|
+
if key in texts:
|
|
613
|
+
result[key] = child.text
|
|
614
|
+
elif key in specials:
|
|
615
|
+
result[key] = specials[key](child)
|
|
616
|
+
# Apply conversions to child elements
|
|
617
|
+
if key in conversions:
|
|
618
|
+
conv_fct, default = conversions[key]
|
|
619
|
+
if result.get(key):
|
|
620
|
+
result[key] = conv_fct(result[key])
|
|
621
|
+
elif default is not None:
|
|
622
|
+
result[key] = default
|
|
623
|
+
return result
|
|
624
|
+
|
|
625
|
+
def _parse_leaf_element(element, name=None, attributes = []):
|
|
626
|
+
result = {}
|
|
627
|
+
for attr_name in attributes:
|
|
628
|
+
result[attr_name] = element.get(attr_name)
|
|
629
|
+
if name is None:
|
|
630
|
+
name = element.tag
|
|
631
|
+
result[name] = element.text
|
|
632
|
+
return result
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def _parse_asset(element):
|
|
636
|
+
return _parse_element(
|
|
637
|
+
element,
|
|
638
|
+
attributes=("ID",),
|
|
639
|
+
texts= ('Name', 'KeyValue'),
|
|
640
|
+
specials= {"Values" : _parse_values}
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
def _parse_delete_asset(element):
|
|
644
|
+
return _parse_element(
|
|
645
|
+
element,
|
|
646
|
+
attributes=("ID",)
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
def _parse_product(element):
|
|
650
|
+
"""Parse Product element with support for multiple cross-references.
|
|
651
|
+
|
|
652
|
+
Handles:
|
|
653
|
+
- Attributes: ID, ParentId, UserTypeID
|
|
654
|
+
- Child: Name (text)
|
|
655
|
+
- Child: Values (complex)
|
|
656
|
+
- Children: Multiple AssetCrossReference, ProductCrossReference, etc.
|
|
657
|
+
"""
|
|
658
|
+
result = _parse_element(
|
|
659
|
+
element,
|
|
660
|
+
attributes = ("ID", "ParentId", "UserTypeID"),
|
|
661
|
+
texts = ("Name",),
|
|
662
|
+
specials = {
|
|
663
|
+
"Values": _parse_values,
|
|
664
|
+
}
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
# Handle cross-references that can appear multiple times
|
|
668
|
+
cross_ref_types = {
|
|
669
|
+
"AssetCrossReference": _parse_asset_reference,
|
|
670
|
+
"DeleteAssetCrossReference": _parse_asset_reference,
|
|
671
|
+
"ProductCrossReference": _parse_product_cross_reference,
|
|
672
|
+
"DeleteProductCrossReference": _parse_product_cross_reference,
|
|
673
|
+
"ClassificationReference": _parse_classification_reference,
|
|
674
|
+
"DeleteClassificationReference": _parse_classification_reference,
|
|
675
|
+
"ClassificationCrossReference": _parse_classification_reference,
|
|
676
|
+
"DeleteClassificationCrossReference": _parse_classification_reference,
|
|
677
|
+
"EntityCrossReference": _parse_entity_cross_reference,
|
|
678
|
+
"DeleteEntityCrossReference": _parse_entity_cross_reference,
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
# Process cross-references, aggregating multiples into lists
|
|
682
|
+
for child in element:
|
|
683
|
+
# Use local tag name to handle both namespaced and unnamespaced XML
|
|
684
|
+
local_tag = _get_local_tag(child)
|
|
685
|
+
if local_tag in cross_ref_types:
|
|
686
|
+
parser_func = cross_ref_types[local_tag]
|
|
687
|
+
parsed = parser_func(child)
|
|
688
|
+
|
|
689
|
+
if local_tag in result:
|
|
690
|
+
# Multiple elements of same type - convert to list
|
|
691
|
+
if not isinstance(result[local_tag], list):
|
|
692
|
+
result[local_tag] = [result[local_tag]]
|
|
693
|
+
result[local_tag].append(parsed)
|
|
694
|
+
else:
|
|
695
|
+
# First element of this type
|
|
696
|
+
result[local_tag] = parsed
|
|
697
|
+
|
|
698
|
+
return result
|
|
699
|
+
|
|
700
|
+
def _parse_delete_product(element):
|
|
701
|
+
return _parse_element(
|
|
702
|
+
element,
|
|
703
|
+
attributes=("ID",)
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
def _parse_values(element):
|
|
707
|
+
values = {}
|
|
708
|
+
for child in element:
|
|
709
|
+
attribute_id = child.get('AttributeID')
|
|
710
|
+
if attribute_id is not None:
|
|
711
|
+
local_tag = _get_local_tag(child)
|
|
712
|
+
if local_tag == "Value":
|
|
713
|
+
values[attribute_id] = _parse_value(child)
|
|
714
|
+
elif local_tag == "MultiValue":
|
|
715
|
+
multi_value_list = []
|
|
716
|
+
for sub_child in child.findall('Value'):
|
|
717
|
+
multi_value_list.append(_parse_value(sub_child))
|
|
718
|
+
# Also check for namespaced Value elements
|
|
719
|
+
for sub_child in child.findall('{http://www.stibosystems.com/step}Value'):
|
|
720
|
+
multi_value_list.append(_parse_value(sub_child))
|
|
721
|
+
if multi_value_list:
|
|
722
|
+
values[attribute_id] = multi_value_list
|
|
723
|
+
return values
|
|
724
|
+
|
|
725
|
+
def _parse_value(element):
|
|
726
|
+
return _parse_leaf_element(
|
|
727
|
+
element,
|
|
728
|
+
name = "value",
|
|
729
|
+
attributes = ("ID",))
|
|
730
|
+
|
|
731
|
+
def _parse_asset_reference(element):
|
|
732
|
+
"""Parse AssetCrossReference or DeleteAssetCrossReference elements.
|
|
733
|
+
|
|
734
|
+
Handles:
|
|
735
|
+
- Attributes: AssetID, Type, Changed, QualifierID
|
|
736
|
+
- Children: KeyValue (multiple), MetaData
|
|
737
|
+
"""
|
|
738
|
+
result = _parse_element(
|
|
739
|
+
element,
|
|
740
|
+
attributes=("AssetID", "Changed", "Type", "QualifierID"),
|
|
741
|
+
specials={"MetaData": _parse_metadata},
|
|
742
|
+
conversions={"Changed": (_str_to_bool, False)}
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
# Parse KeyValue elements (can be multiple)
|
|
746
|
+
key_values = []
|
|
747
|
+
for child in element:
|
|
748
|
+
if child.tag == "KeyValue":
|
|
749
|
+
key_values.append(_parse_key_value(child))
|
|
750
|
+
if key_values:
|
|
751
|
+
result["KeyValue"] = key_values
|
|
752
|
+
|
|
753
|
+
return result
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def _parse_product_cross_reference(element):
|
|
757
|
+
"""Parse ProductCrossReference or DeleteProductCrossReference elements.
|
|
758
|
+
|
|
759
|
+
Handles:
|
|
760
|
+
- Attributes: ProductID, Type, Changed, QualifierID, MountLocation
|
|
761
|
+
- Children: KeyValue (multiple), MetaData
|
|
762
|
+
"""
|
|
763
|
+
result = _parse_element(
|
|
764
|
+
element,
|
|
765
|
+
attributes=("ProductID", "Changed", "Type", "QualifierID", "MountLocation"),
|
|
766
|
+
specials={"MetaData": _parse_metadata},
|
|
767
|
+
conversions={"Changed": (_str_to_bool, False)}
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
# Parse KeyValue elements (can be multiple)
|
|
771
|
+
key_values = []
|
|
772
|
+
for child in element:
|
|
773
|
+
if child.tag == "KeyValue":
|
|
774
|
+
key_values.append(_parse_key_value(child))
|
|
775
|
+
if key_values:
|
|
776
|
+
result["KeyValue"] = key_values
|
|
777
|
+
|
|
778
|
+
return result
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def _parse_classification_reference(element):
|
|
782
|
+
"""Parse ClassificationReference or DeleteClassificationReference elements.
|
|
783
|
+
|
|
784
|
+
Handles:
|
|
785
|
+
- Attributes: ClassificationID, Type, Changed, QualifierID
|
|
786
|
+
- Children: KeyValue (multiple), MetaData
|
|
787
|
+
"""
|
|
788
|
+
result = _parse_element(
|
|
789
|
+
element,
|
|
790
|
+
attributes=("ClassificationID", "Changed", "Type", "QualifierID"),
|
|
791
|
+
specials={"MetaData": _parse_metadata},
|
|
792
|
+
conversions={"Changed": (_str_to_bool, False)}
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
# Parse KeyValue elements (can be multiple)
|
|
796
|
+
key_values = []
|
|
797
|
+
for child in element:
|
|
798
|
+
if child.tag == "KeyValue":
|
|
799
|
+
key_values.append(_parse_key_value(child))
|
|
800
|
+
if key_values:
|
|
801
|
+
result["KeyValue"] = key_values
|
|
802
|
+
|
|
803
|
+
return result
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def _parse_entity_cross_reference(element):
|
|
807
|
+
"""Parse EntityCrossReference or DeleteEntityCrossReference elements.
|
|
808
|
+
|
|
809
|
+
Handles:
|
|
810
|
+
- Attributes: EntityID, Type, Changed, QualifierID
|
|
811
|
+
- Children: KeyValue (multiple), MetaData
|
|
812
|
+
"""
|
|
813
|
+
result = _parse_element(
|
|
814
|
+
element,
|
|
815
|
+
attributes=("EntityID", "Changed", "Type", "QualifierID"),
|
|
816
|
+
specials={"MetaData": _parse_metadata},
|
|
817
|
+
conversions={"Changed": (_str_to_bool, False)}
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
# Parse KeyValue elements (can be multiple)
|
|
821
|
+
key_values = []
|
|
822
|
+
for child in element:
|
|
823
|
+
if child.tag == "KeyValue":
|
|
824
|
+
key_values.append(_parse_key_value(child))
|
|
825
|
+
if key_values:
|
|
826
|
+
result["KeyValue"] = key_values
|
|
827
|
+
|
|
828
|
+
return result
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
def _parse_key_value(element):
|
|
832
|
+
"""Parse KeyValue element used for alternate object identification.
|
|
833
|
+
|
|
834
|
+
Returns dict with KeyID attribute and text value.
|
|
835
|
+
"""
|
|
836
|
+
return {
|
|
837
|
+
"KeyID": element.get("KeyID"),
|
|
838
|
+
"value": element.text
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
def _parse_metadata(element):
|
|
843
|
+
"""Parse MetaData element with nested Value/MultiValue/ValueGroup.
|
|
844
|
+
|
|
845
|
+
Returns dict with attribute IDs as keys and values.
|
|
846
|
+
"""
|
|
847
|
+
metadata = {}
|
|
848
|
+
for child in element:
|
|
849
|
+
attribute_id = child.get('AttributeID')
|
|
850
|
+
if attribute_id is not None:
|
|
851
|
+
if child.tag == "Value":
|
|
852
|
+
metadata[attribute_id] = _parse_value(child)
|
|
853
|
+
elif child.tag == "MultiValue":
|
|
854
|
+
multi_value_list = []
|
|
855
|
+
for sub_child in child.findall('Value', STEPXML_NS):
|
|
856
|
+
multi_value_list.append(_parse_value(sub_child))
|
|
857
|
+
if multi_value_list:
|
|
858
|
+
metadata[attribute_id] = multi_value_list
|
|
859
|
+
return metadata if metadata else None
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
def _parse_classification(element):
|
|
863
|
+
"""Parse Classification element with support for nested classifications and cross-references.
|
|
864
|
+
|
|
865
|
+
Handles:
|
|
866
|
+
- Attributes: ID, ParentID, UserTypeID, QualifierID, Changed, Republished, Selected, Referenced
|
|
867
|
+
- Children: KeyValue (multiple), ParentKeyValue (multiple)
|
|
868
|
+
- Children: Name (text), nested Classification (hierarchical)
|
|
869
|
+
- Cross-references: ProductReference, ProductCrossReference (suppressed/delete variants)
|
|
870
|
+
- Cross-references: ClassificationCrossReference (classification-to-classification)
|
|
871
|
+
- Cross-references: EntityCrossReference
|
|
872
|
+
- Cross-references: AssetCrossReference
|
|
873
|
+
- Other: MetaData, AttributeLink, DataContainerTypeLink
|
|
874
|
+
"""
|
|
875
|
+
result = _parse_element(
|
|
876
|
+
element,
|
|
877
|
+
attributes=("ID", "ParentID", "UserTypeID", "QualifierID", "Changed", "Republished", "Selected", "Referenced"),
|
|
878
|
+
texts=("Name",),
|
|
879
|
+
conversions={
|
|
880
|
+
"Changed": (_str_to_bool, False),
|
|
881
|
+
"Republished": (_str_to_bool, False),
|
|
882
|
+
"Selected": (_str_to_bool, True),
|
|
883
|
+
"Referenced": (_str_to_bool, False)
|
|
884
|
+
}
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
# Parse KeyValue and ParentKeyValue elements (can be multiple)
|
|
888
|
+
key_values = []
|
|
889
|
+
parent_key_values = []
|
|
890
|
+
nested_classifications = []
|
|
891
|
+
|
|
892
|
+
# Define all cross-reference types supported by classifications
|
|
893
|
+
cross_ref_types = {
|
|
894
|
+
"ProductReference": _parse_product_cross_reference,
|
|
895
|
+
"DeleteProductReference": _parse_product_cross_reference,
|
|
896
|
+
"SuppressedProductReference": _parse_product_cross_reference,
|
|
897
|
+
"DeleteSuppressedProductReference": _parse_product_cross_reference,
|
|
898
|
+
"ProductCrossReference": _parse_product_cross_reference,
|
|
899
|
+
"DeleteProductCrossReference": _parse_product_cross_reference,
|
|
900
|
+
"SuppressedProductCrossReference": _parse_product_cross_reference,
|
|
901
|
+
"DeleteSuppressedProductCrossReference": _parse_product_cross_reference,
|
|
902
|
+
"ClassificationCrossReference": _parse_classification_reference,
|
|
903
|
+
"DeleteClassificationCrossReference": _parse_classification_reference,
|
|
904
|
+
"SuppressedClassificationCrossReference": _parse_classification_reference,
|
|
905
|
+
"DeleteSuppressedClassificationCrossReference": _parse_classification_reference,
|
|
906
|
+
"EntityCrossReference": _parse_entity_cross_reference,
|
|
907
|
+
"DeleteEntityCrossReference": _parse_entity_cross_reference,
|
|
908
|
+
"SuppressedEntityCrossReference": _parse_entity_cross_reference,
|
|
909
|
+
"DeleteSuppressedEntityCrossReference": _parse_entity_cross_reference,
|
|
910
|
+
"AssetCrossReference": _parse_asset_reference,
|
|
911
|
+
"DeleteAssetCrossReference": _parse_asset_reference,
|
|
912
|
+
"SuppressedAssetCrossReference": _parse_asset_reference,
|
|
913
|
+
"DeleteSuppressedAssetCrossReference": _parse_asset_reference,
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
# Process child elements, handling special cases
|
|
917
|
+
for child in element:
|
|
918
|
+
if child.tag == "KeyValue":
|
|
919
|
+
key_values.append(_parse_key_value(child))
|
|
920
|
+
elif child.tag == "ParentKeyValue":
|
|
921
|
+
parent_key_values.append(_parse_key_value(child))
|
|
922
|
+
elif child.tag == "Classification":
|
|
923
|
+
# Nested classifications (hierarchical)
|
|
924
|
+
nested_classifications.append(_parse_classification(child))
|
|
925
|
+
elif child.tag == "MetaData":
|
|
926
|
+
result["MetaData"] = _parse_metadata(child)
|
|
927
|
+
elif child.tag in cross_ref_types:
|
|
928
|
+
# Cross-references - aggregate multiple into lists
|
|
929
|
+
parser_func = cross_ref_types[child.tag]
|
|
930
|
+
parsed = parser_func(child)
|
|
931
|
+
|
|
932
|
+
if child.tag in result:
|
|
933
|
+
# Multiple elements of same type - convert to list
|
|
934
|
+
if not isinstance(result[child.tag], list):
|
|
935
|
+
result[child.tag] = [result[child.tag]]
|
|
936
|
+
result[child.tag].append(parsed)
|
|
937
|
+
else:
|
|
938
|
+
# First element of this type
|
|
939
|
+
result[child.tag] = parsed
|
|
940
|
+
|
|
941
|
+
# Add aggregated lists to result if present
|
|
942
|
+
if key_values:
|
|
943
|
+
result["KeyValue"] = key_values
|
|
944
|
+
if parent_key_values:
|
|
945
|
+
result["ParentKeyValue"] = parent_key_values
|
|
946
|
+
if nested_classifications:
|
|
947
|
+
result["Classification"] = nested_classifications
|
|
948
|
+
|
|
949
|
+
return result
|
|
950
|
+
|
|
951
|
+
def _parse_entity(element):
|
|
952
|
+
"""Parse Entity element with support for nested entities and cross-references.
|
|
953
|
+
|
|
954
|
+
Handles:
|
|
955
|
+
- Attributes: ID, ParentID, UserTypeID, QualifierID, Changed, Republished, Selected, Referenced
|
|
956
|
+
- Children: KeyValue (multiple), ParentKeyValue (multiple), Name (text)
|
|
957
|
+
- Children: Nested Entity (hierarchical)
|
|
958
|
+
- Cross-references: ProductCrossReference (suppressed/delete variants)
|
|
959
|
+
- Cross-references: ClassificationCrossReference (suppressed/delete variants)
|
|
960
|
+
- Cross-references: EntityCrossReference (entity-to-entity)
|
|
961
|
+
- Cross-references: AssetCrossReference (suppressed/delete variants)
|
|
962
|
+
- Cross-references: ContextCrossReference, WorkspaceCrossReference
|
|
963
|
+
- Other: MetaData, Values, DataContainers
|
|
964
|
+
"""
|
|
965
|
+
result = _parse_element(
|
|
966
|
+
element,
|
|
967
|
+
attributes=("ID", "ParentID", "UserTypeID", "QualifierID", "Changed", "Republished", "Selected", "Referenced"),
|
|
968
|
+
texts=("Name",),
|
|
969
|
+
conversions={
|
|
970
|
+
"Changed": (_str_to_bool, False),
|
|
971
|
+
"Republished": (_str_to_bool, False),
|
|
972
|
+
"Selected": (_str_to_bool, True),
|
|
973
|
+
"Referenced": (_str_to_bool, False)
|
|
974
|
+
}
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
# Parse KeyValue and ParentKeyValue elements (can be multiple)
|
|
978
|
+
key_values = []
|
|
979
|
+
parent_key_values = []
|
|
980
|
+
nested_entities = []
|
|
981
|
+
|
|
982
|
+
# Define all cross-reference types supported by entities
|
|
983
|
+
cross_ref_types = {
|
|
984
|
+
"ProductCrossReference": _parse_product_cross_reference,
|
|
985
|
+
"DeleteProductCrossReference": _parse_product_cross_reference,
|
|
986
|
+
"SuppressedProductCrossReference": _parse_product_cross_reference,
|
|
987
|
+
"DeleteSuppressedProductCrossReference": _parse_product_cross_reference,
|
|
988
|
+
"ClassificationCrossReference": _parse_classification_reference,
|
|
989
|
+
"DeleteClassificationCrossReference": _parse_classification_reference,
|
|
990
|
+
"SuppressedClassificationCrossReference": _parse_classification_reference,
|
|
991
|
+
"DeleteSuppressedClassificationCrossReference": _parse_classification_reference,
|
|
992
|
+
"EntityCrossReference": _parse_entity_cross_reference,
|
|
993
|
+
"DeleteEntityCrossReference": _parse_entity_cross_reference,
|
|
994
|
+
"SuppressedEntityCrossReference": _parse_entity_cross_reference,
|
|
995
|
+
"DeleteSuppressedEntityCrossReference": _parse_entity_cross_reference,
|
|
996
|
+
"AssetCrossReference": _parse_asset_reference,
|
|
997
|
+
"DeleteAssetCrossReference": _parse_asset_reference,
|
|
998
|
+
"SuppressedAssetCrossReference": _parse_asset_reference,
|
|
999
|
+
"DeleteSuppressedAssetCrossReference": _parse_asset_reference,
|
|
1000
|
+
"ContextCrossReference": _parse_context_cross_reference,
|
|
1001
|
+
"SuppressedContextCrossReference": _parse_context_cross_reference,
|
|
1002
|
+
"WorkspaceCrossReference": _parse_workspace_cross_reference,
|
|
1003
|
+
"SuppressedWorkspaceCrossReference": _parse_workspace_cross_reference,
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
# Process child elements, handling special cases
|
|
1007
|
+
for child in element:
|
|
1008
|
+
if child.tag == "KeyValue":
|
|
1009
|
+
key_values.append(_parse_key_value(child))
|
|
1010
|
+
elif child.tag == "ParentKeyValue":
|
|
1011
|
+
parent_key_values.append(_parse_key_value(child))
|
|
1012
|
+
elif child.tag == "Entity":
|
|
1013
|
+
# Nested entities (hierarchical)
|
|
1014
|
+
nested_entities.append(_parse_entity(child))
|
|
1015
|
+
elif child.tag == "MetaData":
|
|
1016
|
+
result["MetaData"] = _parse_metadata(child)
|
|
1017
|
+
elif child.tag in cross_ref_types:
|
|
1018
|
+
# Cross-references - aggregate multiple into lists
|
|
1019
|
+
parser_func = cross_ref_types[child.tag]
|
|
1020
|
+
parsed = parser_func(child)
|
|
1021
|
+
|
|
1022
|
+
if child.tag in result:
|
|
1023
|
+
# Multiple elements of same type - convert to list
|
|
1024
|
+
if not isinstance(result[child.tag], list):
|
|
1025
|
+
result[child.tag] = [result[child.tag]]
|
|
1026
|
+
result[child.tag].append(parsed)
|
|
1027
|
+
else:
|
|
1028
|
+
# First element of this type
|
|
1029
|
+
result[child.tag] = parsed
|
|
1030
|
+
|
|
1031
|
+
# Add aggregated lists to result if present
|
|
1032
|
+
if key_values:
|
|
1033
|
+
result["KeyValue"] = key_values
|
|
1034
|
+
if parent_key_values:
|
|
1035
|
+
result["ParentKeyValue"] = parent_key_values
|
|
1036
|
+
if nested_entities:
|
|
1037
|
+
result["Entity"] = nested_entities
|
|
1038
|
+
|
|
1039
|
+
return result
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
def _parse_context_cross_reference(element):
|
|
1043
|
+
"""Parse ContextCrossReference or SuppressedContextCrossReference elements.
|
|
1044
|
+
|
|
1045
|
+
Handles:
|
|
1046
|
+
- Attributes: ContextID, Type, QualifierID
|
|
1047
|
+
- Children: KeyValue (multiple), MetaData
|
|
1048
|
+
"""
|
|
1049
|
+
result = _parse_element(
|
|
1050
|
+
element,
|
|
1051
|
+
attributes=("ContextID", "Type", "QualifierID"),
|
|
1052
|
+
specials={"MetaData": _parse_metadata}
|
|
1053
|
+
)
|
|
1054
|
+
|
|
1055
|
+
# Parse KeyValue elements (can be multiple)
|
|
1056
|
+
key_values = []
|
|
1057
|
+
for child in element:
|
|
1058
|
+
if child.tag == "KeyValue":
|
|
1059
|
+
key_values.append(_parse_key_value(child))
|
|
1060
|
+
if key_values:
|
|
1061
|
+
result["KeyValue"] = key_values
|
|
1062
|
+
|
|
1063
|
+
return result
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
def _parse_workspace_cross_reference(element):
|
|
1067
|
+
"""Parse WorkspaceCrossReference or SuppressedWorkspaceCrossReference elements.
|
|
1068
|
+
|
|
1069
|
+
Handles:
|
|
1070
|
+
- Attributes: WorkspaceID, Type, QualifierID
|
|
1071
|
+
- Children: KeyValue (multiple), MetaData
|
|
1072
|
+
"""
|
|
1073
|
+
result = _parse_element(
|
|
1074
|
+
element,
|
|
1075
|
+
attributes=("WorkspaceID", "Type", "QualifierID"),
|
|
1076
|
+
specials={"MetaData": _parse_metadata}
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
# Parse KeyValue elements (can be multiple)
|
|
1080
|
+
key_values = []
|
|
1081
|
+
for child in element:
|
|
1082
|
+
if child.tag == "KeyValue":
|
|
1083
|
+
key_values.append(_parse_key_value(child))
|
|
1084
|
+
if key_values:
|
|
1085
|
+
result["KeyValue"] = key_values
|
|
1086
|
+
|
|
1087
|
+
return result
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def _parse_attribute(element: ET.Element) -> dict:
|
|
1091
|
+
"""Parse Attribute element with ID, names, validation/LOV link, and metadata.
|
|
1092
|
+
|
|
1093
|
+
Attributes:
|
|
1094
|
+
- ID, DefaultUnitID, ProductMode (Normal/Property), CryptographicKeyID
|
|
1095
|
+
- HierarchicalFiltering, ClassificationHierarchicalFiltering, ExternallyMaintained
|
|
1096
|
+
- FullTextIndexed, MultiValued, Mandatory, Derived
|
|
1097
|
+
- Selected (default True), Referenced (default False), Changed, Deleted
|
|
1098
|
+
|
|
1099
|
+
Children:
|
|
1100
|
+
- Name (text), Validation OR ListOfValueLink
|
|
1101
|
+
- ValueTemplate/UnitTemplate (multiple), DimensionLink (multiple)
|
|
1102
|
+
- MetaData, AttributeGroupLink (multiple), UserTypeLink (multiple)
|
|
1103
|
+
- LinkType (multiple), ValueFilter, CurrentTasks
|
|
1104
|
+
"""
|
|
1105
|
+
result = _parse_element(
|
|
1106
|
+
element,
|
|
1107
|
+
attributes=("ID", "DefaultUnitID", "ProductMode", "CryptographicKeyID",
|
|
1108
|
+
"HierarchicalFiltering", "ClassificationHierarchicalFiltering",
|
|
1109
|
+
"ExternallyMaintained", "FullTextIndexed", "MultiValued",
|
|
1110
|
+
"Mandatory", "Derived", "Selected", "Referenced", "Changed", "Deleted"),
|
|
1111
|
+
texts=("Name",),
|
|
1112
|
+
conversions={
|
|
1113
|
+
"HierarchicalFiltering": (_str_to_bool, False),
|
|
1114
|
+
"ClassificationHierarchicalFiltering": (_str_to_bool, False),
|
|
1115
|
+
"ExternallyMaintained": (_str_to_bool, False),
|
|
1116
|
+
"FullTextIndexed": (_str_to_bool, False),
|
|
1117
|
+
"MultiValued": (_str_to_bool, False),
|
|
1118
|
+
"Mandatory": (_str_to_bool, False),
|
|
1119
|
+
"Derived": (_str_to_bool, False),
|
|
1120
|
+
"Selected": (_str_to_bool, True),
|
|
1121
|
+
"Referenced": (_str_to_bool, False),
|
|
1122
|
+
"Changed": (_str_to_bool, False),
|
|
1123
|
+
"Deleted": (_str_to_bool, False),
|
|
1124
|
+
}
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1127
|
+
# Parse Validation or ListOfValueLink (choice)
|
|
1128
|
+
for child in element:
|
|
1129
|
+
if child.tag.endswith("Validation"):
|
|
1130
|
+
result["Validation"] = child.attrib.get("Base", "")
|
|
1131
|
+
elif child.tag.endswith("ListOfValueLink"):
|
|
1132
|
+
result["ListOfValueLink"] = child.attrib.get("LOVID", "")
|
|
1133
|
+
|
|
1134
|
+
# Parse DimensionLink elements (multiple)
|
|
1135
|
+
dim_links = []
|
|
1136
|
+
for child in element:
|
|
1137
|
+
if child.tag.endswith("DimensionLink"):
|
|
1138
|
+
dim_links.append(child.attrib.get("DimensionID", ""))
|
|
1139
|
+
if dim_links:
|
|
1140
|
+
result["DimensionLink"] = dim_links
|
|
1141
|
+
|
|
1142
|
+
# Parse MetaData
|
|
1143
|
+
for child in element:
|
|
1144
|
+
if child.tag.endswith("MetaData"):
|
|
1145
|
+
result["MetaData"] = _parse_metadata(child)
|
|
1146
|
+
break
|
|
1147
|
+
|
|
1148
|
+
# Parse AttributeGroupLink elements (multiple)
|
|
1149
|
+
attr_group_links = []
|
|
1150
|
+
for child in element:
|
|
1151
|
+
if child.tag.endswith("AttributeGroupLink"):
|
|
1152
|
+
attr_group_links.append(child.attrib.get("AttributeGroupID", ""))
|
|
1153
|
+
if attr_group_links:
|
|
1154
|
+
result["AttributeGroupLink"] = attr_group_links
|
|
1155
|
+
|
|
1156
|
+
# Parse UserTypeLink elements (multiple)
|
|
1157
|
+
user_type_links = []
|
|
1158
|
+
for child in element:
|
|
1159
|
+
if child.tag.endswith("UserTypeLink"):
|
|
1160
|
+
user_type_links.append(child.attrib.get("UserTypeID", ""))
|
|
1161
|
+
if user_type_links:
|
|
1162
|
+
result["UserTypeLink"] = user_type_links
|
|
1163
|
+
|
|
1164
|
+
# Parse LinkType elements (multiple)
|
|
1165
|
+
link_types = []
|
|
1166
|
+
for child in element:
|
|
1167
|
+
if child.tag.endswith("LinkType"):
|
|
1168
|
+
link_types.append(child.attrib.get("ID", ""))
|
|
1169
|
+
if link_types:
|
|
1170
|
+
result["LinkType"] = link_types
|
|
1171
|
+
|
|
1172
|
+
return result
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
def _parse_attribute_group(element: ET.Element) -> dict:
|
|
1176
|
+
"""Parse AttributeGroup element with ID, ParentID, names, and nested groups.
|
|
1177
|
+
|
|
1178
|
+
Attributes:
|
|
1179
|
+
- ID, ParentID
|
|
1180
|
+
- ShowInWorkbench (default True), ExcludeFromProfiling
|
|
1181
|
+
- Referenced (default False), ManuallySorted (default False)
|
|
1182
|
+
- Selected (default True), Changed (default False), Deleted (default False)
|
|
1183
|
+
|
|
1184
|
+
Children:
|
|
1185
|
+
- Name (text), MetaData, Configuration
|
|
1186
|
+
- AttributeSequence, nested AttributeGroup (multiple)
|
|
1187
|
+
"""
|
|
1188
|
+
result = _parse_element(
|
|
1189
|
+
element,
|
|
1190
|
+
attributes=("ID", "ParentID", "ShowInWorkbench", "ExcludeFromProfiling",
|
|
1191
|
+
"Referenced", "ManuallySorted", "Selected", "Changed", "Deleted"),
|
|
1192
|
+
texts=("Name",),
|
|
1193
|
+
conversions={
|
|
1194
|
+
"ShowInWorkbench": (_str_to_bool, True),
|
|
1195
|
+
"ExcludeFromProfiling": (_str_to_bool, False),
|
|
1196
|
+
"Referenced": (_str_to_bool, False),
|
|
1197
|
+
"ManuallySorted": (_str_to_bool, False),
|
|
1198
|
+
"Selected": (_str_to_bool, True),
|
|
1199
|
+
"Changed": (_str_to_bool, False),
|
|
1200
|
+
"Deleted": (_str_to_bool, False),
|
|
1201
|
+
}
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
# Parse MetaData
|
|
1205
|
+
for child in element:
|
|
1206
|
+
if child.tag.endswith("MetaData"):
|
|
1207
|
+
result["MetaData"] = _parse_metadata(child)
|
|
1208
|
+
break
|
|
1209
|
+
|
|
1210
|
+
# Parse Configuration (encoded view definition)
|
|
1211
|
+
for child in element:
|
|
1212
|
+
if child.tag.endswith("Configuration") and child.text:
|
|
1213
|
+
result["Configuration"] = child.text
|
|
1214
|
+
break
|
|
1215
|
+
|
|
1216
|
+
# Parse nested AttributeGroup elements (hierarchical)
|
|
1217
|
+
nested_groups = []
|
|
1218
|
+
for child in element:
|
|
1219
|
+
if child.tag.endswith("AttributeGroup"):
|
|
1220
|
+
nested_groups.append(_parse_attribute_group(child))
|
|
1221
|
+
if nested_groups:
|
|
1222
|
+
result["AttributeGroup"] = nested_groups
|
|
1223
|
+
|
|
1224
|
+
return result
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
def _parse_list_of_values_group(element: ET.Element) -> dict:
|
|
1228
|
+
"""Parse ListOfValuesGroup element with ID, ParentID, names, and nested groups.
|
|
1229
|
+
|
|
1230
|
+
Attributes:
|
|
1231
|
+
- ID, ParentID
|
|
1232
|
+
- Selected (default True), Referenced (default False), Changed (default False)
|
|
1233
|
+
|
|
1234
|
+
Children:
|
|
1235
|
+
- Name (text), MetaData
|
|
1236
|
+
- AttributeGroupLink (multiple), nested ListOfValuesGroup (multiple)
|
|
1237
|
+
"""
|
|
1238
|
+
result = _parse_element(
|
|
1239
|
+
element,
|
|
1240
|
+
attributes=("ID", "ParentID", "Selected", "Referenced", "Changed"),
|
|
1241
|
+
texts=("Name",),
|
|
1242
|
+
conversions={
|
|
1243
|
+
"Selected": (_str_to_bool, True),
|
|
1244
|
+
"Referenced": (_str_to_bool, False),
|
|
1245
|
+
"Changed": (_str_to_bool, False),
|
|
1246
|
+
}
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
# Parse MetaData
|
|
1250
|
+
for child in element:
|
|
1251
|
+
if child.tag.endswith("MetaData"):
|
|
1252
|
+
result["MetaData"] = _parse_metadata(child)
|
|
1253
|
+
break
|
|
1254
|
+
|
|
1255
|
+
# Parse AttributeGroupLink elements (multiple)
|
|
1256
|
+
attr_group_links = []
|
|
1257
|
+
for child in element:
|
|
1258
|
+
if child.tag.endswith("AttributeGroupLink"):
|
|
1259
|
+
attr_group_links.append(child.attrib.get("AttributeGroupID", ""))
|
|
1260
|
+
if attr_group_links:
|
|
1261
|
+
result["AttributeGroupLink"] = attr_group_links
|
|
1262
|
+
|
|
1263
|
+
# Parse nested ListOfValuesGroup elements (hierarchical)
|
|
1264
|
+
nested_groups = []
|
|
1265
|
+
for child in element:
|
|
1266
|
+
if child.tag.endswith("ListOfValuesGroup"):
|
|
1267
|
+
nested_groups.append(_parse_list_of_values_group(child))
|
|
1268
|
+
if nested_groups:
|
|
1269
|
+
result["ListOfValuesGroup"] = nested_groups
|
|
1270
|
+
|
|
1271
|
+
return result
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
def _parse_list_of_value(element: ET.Element) -> dict:
|
|
1275
|
+
"""Parse ListOfValue element with ID, ParentID, validation, and value items.
|
|
1276
|
+
|
|
1277
|
+
Attributes:
|
|
1278
|
+
- ID, ParentID
|
|
1279
|
+
- AllowUserValueAddition (default: False), UseValueID (default: False)
|
|
1280
|
+
- IDPattern, ReplaceListOfValuesValues (default: False), DefaultUnitID
|
|
1281
|
+
|
|
1282
|
+
Children:
|
|
1283
|
+
- Name (text, multiple), Validation
|
|
1284
|
+
- DimensionLink (multiple), MetaData, AttributeGroupLink (multiple)
|
|
1285
|
+
- Value (multiple), ValueGroup (multiple) - choice
|
|
1286
|
+
- DeleteValue (multiple)
|
|
1287
|
+
"""
|
|
1288
|
+
result = _parse_element(
|
|
1289
|
+
element,
|
|
1290
|
+
attributes=("ID", "ParentID", "AllowUserValueAddition", "UseValueID",
|
|
1291
|
+
"IDPattern", "ReplaceListOfValuesValues", "DefaultUnitID"),
|
|
1292
|
+
texts=("Name",),
|
|
1293
|
+
conversions={
|
|
1294
|
+
"AllowUserValueAddition": (_str_to_bool, False),
|
|
1295
|
+
"UseValueID": (_str_to_bool, False),
|
|
1296
|
+
"ReplaceListOfValuesValues": (_str_to_bool, False),
|
|
1297
|
+
}
|
|
1298
|
+
)
|
|
1299
|
+
|
|
1300
|
+
# Parse Validation element
|
|
1301
|
+
validation_elem = None
|
|
1302
|
+
for child in element:
|
|
1303
|
+
if child.tag.endswith("Validation"):
|
|
1304
|
+
result["Validation"] = child.attrib.get("Base", "")
|
|
1305
|
+
validation_elem = child
|
|
1306
|
+
break
|
|
1307
|
+
|
|
1308
|
+
# Parse DimensionLink elements (multiple)
|
|
1309
|
+
dim_links = []
|
|
1310
|
+
for child in element:
|
|
1311
|
+
if child.tag.endswith("DimensionLink"):
|
|
1312
|
+
dim_links.append(child.attrib.get("DimensionID", ""))
|
|
1313
|
+
if dim_links:
|
|
1314
|
+
result["DimensionLink"] = dim_links
|
|
1315
|
+
|
|
1316
|
+
# Parse MetaData
|
|
1317
|
+
metadata_elem = None
|
|
1318
|
+
for child in element:
|
|
1319
|
+
if child.tag.endswith("MetaData"):
|
|
1320
|
+
result["MetaData"] = _parse_metadata(child)
|
|
1321
|
+
metadata_elem = child
|
|
1322
|
+
break
|
|
1323
|
+
|
|
1324
|
+
# Parse AttributeGroupLink elements (multiple)
|
|
1325
|
+
attr_group_links = []
|
|
1326
|
+
for child in element:
|
|
1327
|
+
if child.tag.endswith("AttributeGroupLink"):
|
|
1328
|
+
attr_group_links.append(child.attrib.get("AttributeGroupID", ""))
|
|
1329
|
+
if attr_group_links:
|
|
1330
|
+
result["AttributeGroupLink"] = attr_group_links
|
|
1331
|
+
|
|
1332
|
+
# Parse Value and ValueGroup elements (choice - not from MetaData or other containers)
|
|
1333
|
+
# We need to exclude Value elements that are direct children of containers like MetaData
|
|
1334
|
+
values = []
|
|
1335
|
+
value_groups = []
|
|
1336
|
+
for child in element:
|
|
1337
|
+
# Skip elements we've already processed above
|
|
1338
|
+
if child.tag.endswith(("Validation", "DimensionLink", "MetaData", "AttributeGroupLink",
|
|
1339
|
+
"Name", "DeleteValue")):
|
|
1340
|
+
continue
|
|
1341
|
+
|
|
1342
|
+
# Now parse Value and ValueGroup from direct children only
|
|
1343
|
+
if child.tag.endswith("Value"):
|
|
1344
|
+
values.append(_parse_list_of_value_item(child))
|
|
1345
|
+
elif child.tag.endswith("ValueGroup"):
|
|
1346
|
+
value_groups.append(_parse_value_group(child))
|
|
1347
|
+
|
|
1348
|
+
if values:
|
|
1349
|
+
result["Value"] = values
|
|
1350
|
+
if value_groups:
|
|
1351
|
+
result["ValueGroup"] = value_groups
|
|
1352
|
+
|
|
1353
|
+
# Parse DeleteValue elements (multiple)
|
|
1354
|
+
delete_values = []
|
|
1355
|
+
for child in element:
|
|
1356
|
+
if child.tag.endswith("DeleteValue"):
|
|
1357
|
+
delete_values.append(_parse_delete_value(child))
|
|
1358
|
+
if delete_values:
|
|
1359
|
+
result["DeleteValue"] = delete_values
|
|
1360
|
+
|
|
1361
|
+
return result
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
def _parse_list_of_value_item(element: ET.Element) -> dict:
|
|
1365
|
+
"""Parse Value element within a LOV.
|
|
1366
|
+
|
|
1367
|
+
Attributes:
|
|
1368
|
+
- ID (value ID), UnitID, UnitName
|
|
1369
|
+
- AttributeID, LOVQualifierID, QualifierID
|
|
1370
|
+
- Changed (default: False), Derived (default: False)
|
|
1371
|
+
- DerivedContextID, InheritedFrom, Inherited
|
|
1372
|
+
|
|
1373
|
+
Text Content:
|
|
1374
|
+
- The actual value (mixed content)
|
|
1375
|
+
"""
|
|
1376
|
+
result = _parse_element(
|
|
1377
|
+
element,
|
|
1378
|
+
attributes=("ID", "UnitID", "UnitName", "AttributeID", "LOVQualifierID",
|
|
1379
|
+
"QualifierID", "DerivedContextID", "InheritedFrom", "Inherited"),
|
|
1380
|
+
conversions={
|
|
1381
|
+
"Changed": (_str_to_bool, False),
|
|
1382
|
+
"Derived": (_str_to_bool, False),
|
|
1383
|
+
}
|
|
1384
|
+
)
|
|
1385
|
+
|
|
1386
|
+
# Capture text content (mixed type element)
|
|
1387
|
+
if element.text and element.text.strip():
|
|
1388
|
+
result["value"] = element.text.strip()
|
|
1389
|
+
|
|
1390
|
+
return result
|
|
1391
|
+
|
|
1392
|
+
|
|
1393
|
+
def _parse_value_group(element: ET.Element) -> dict:
|
|
1394
|
+
"""Parse ValueGroup element (container for qualifier-specific values).
|
|
1395
|
+
|
|
1396
|
+
Attributes:
|
|
1397
|
+
- ID (group ID)
|
|
1398
|
+
- Changed (default: False), Inherited, AttributeID
|
|
1399
|
+
|
|
1400
|
+
Children:
|
|
1401
|
+
- Value (multiple) - values within this group
|
|
1402
|
+
"""
|
|
1403
|
+
result = _parse_element(
|
|
1404
|
+
element,
|
|
1405
|
+
attributes=("ID", "Inherited", "AttributeID"),
|
|
1406
|
+
conversions={
|
|
1407
|
+
"Changed": (_str_to_bool, False),
|
|
1408
|
+
}
|
|
1409
|
+
)
|
|
1410
|
+
|
|
1411
|
+
# Parse Value elements (multiple within the group)
|
|
1412
|
+
values = []
|
|
1413
|
+
for child in element:
|
|
1414
|
+
if child.tag.endswith("Value"):
|
|
1415
|
+
values.append(_parse_list_of_value_item(child))
|
|
1416
|
+
|
|
1417
|
+
if values:
|
|
1418
|
+
result["Value"] = values
|
|
1419
|
+
|
|
1420
|
+
return result
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
def _parse_delete_value(element: ET.Element) -> dict:
|
|
1424
|
+
"""Parse DeleteValue element (deletion instruction for LOV value).
|
|
1425
|
+
|
|
1426
|
+
Attributes:
|
|
1427
|
+
- Force (default: False) - delete despite usage
|
|
1428
|
+
- GenerateEvents (default: False) - generate events during deletion
|
|
1429
|
+
|
|
1430
|
+
Text Content:
|
|
1431
|
+
- The value ID to delete (mixed content)
|
|
1432
|
+
"""
|
|
1433
|
+
result = _parse_element(
|
|
1434
|
+
element,
|
|
1435
|
+
attributes=(),
|
|
1436
|
+
conversions={
|
|
1437
|
+
"Force": (_str_to_bool, False),
|
|
1438
|
+
"GenerateEvents": (_str_to_bool, False),
|
|
1439
|
+
}
|
|
1440
|
+
)
|
|
1441
|
+
|
|
1442
|
+
# Parse boolean attributes manually since they're not in attributes tuple
|
|
1443
|
+
result["Force"] = _str_to_bool(element.attrib.get("Force", "false"))
|
|
1444
|
+
result["GenerateEvents"] = _str_to_bool(element.attrib.get("GenerateEvents", "false"))
|
|
1445
|
+
|
|
1446
|
+
# Capture text content (value ID to delete)
|
|
1447
|
+
if element.text and element.text.strip():
|
|
1448
|
+
result["value"] = element.text.strip()
|
|
1449
|
+
|
|
1450
|
+
return result
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
def _parse_user_type(element: ET.Element) -> dict:
|
|
1454
|
+
"""Parse UserType element (object type definition).
|
|
1455
|
+
|
|
1456
|
+
Attributes (15 total):
|
|
1457
|
+
- ID (required): Object type identifier
|
|
1458
|
+
- AllowOverlappingTerms, AllowQuarkTemplate, AllowInDesignTemplate (optional booleans)
|
|
1459
|
+
- Revisability (optional enum)
|
|
1460
|
+
- IsCategory (optional boolean, default False)
|
|
1461
|
+
- DefaultProfileConfiguration, NamePattern, IDPattern (optional strings)
|
|
1462
|
+
- ClassificationOwnsProductLinks (optional boolean)
|
|
1463
|
+
- ManuallySorted (optional boolean, default False)
|
|
1464
|
+
- ReferenceTargetLockPolicy (optional enum)
|
|
1465
|
+
- Selected (optional boolean, default True)
|
|
1466
|
+
- Referenced (optional boolean, default False)
|
|
1467
|
+
- Changed (optional boolean, default False)
|
|
1468
|
+
- Deleted (optional boolean, default False)
|
|
1469
|
+
|
|
1470
|
+
Child Elements:
|
|
1471
|
+
- Name (text, multiple) - UserType display name
|
|
1472
|
+
- Icon (base64) - Custom icon
|
|
1473
|
+
- VisibleTermFields (element) - Term configuration
|
|
1474
|
+
- MetaData (element) - Metadata
|
|
1475
|
+
- DimensionLink (multiple) - Associated dimensions
|
|
1476
|
+
- UserTypeLink (multiple) - Parent object type links
|
|
1477
|
+
- AttributeLink (multiple) - Valid attributes for this type
|
|
1478
|
+
- Validation (element) - Validation rules
|
|
1479
|
+
- MIMEType (multiple) - Supported MIME types
|
|
1480
|
+
"""
|
|
1481
|
+
result = _parse_element(
|
|
1482
|
+
element,
|
|
1483
|
+
attributes=("ID", "AllowOverlappingTerms", "AllowQuarkTemplate",
|
|
1484
|
+
"AllowInDesignTemplate", "Revisability", "IsCategory",
|
|
1485
|
+
"DefaultProfileConfiguration", "ClassificationOwnsProductLinks",
|
|
1486
|
+
"NamePattern", "IDPattern", "ManuallySorted",
|
|
1487
|
+
"ReferenceTargetLockPolicy", "Selected", "Referenced",
|
|
1488
|
+
"Changed", "Deleted"),
|
|
1489
|
+
specials={
|
|
1490
|
+
"MetaData": _parse_metadata,
|
|
1491
|
+
"Validation": lambda elem: elem.attrib.get("Base", "")
|
|
1492
|
+
},
|
|
1493
|
+
conversions={
|
|
1494
|
+
"AllowOverlappingTerms": (_str_to_bool, False),
|
|
1495
|
+
"AllowQuarkTemplate": (_str_to_bool, False),
|
|
1496
|
+
"AllowInDesignTemplate": (_str_to_bool, False),
|
|
1497
|
+
"IsCategory": (_str_to_bool, False),
|
|
1498
|
+
"ClassificationOwnsProductLinks": (_str_to_bool, False),
|
|
1499
|
+
"ManuallySorted": (_str_to_bool, False),
|
|
1500
|
+
"Selected": (_str_to_bool, True),
|
|
1501
|
+
"Referenced": (_str_to_bool, False),
|
|
1502
|
+
"Changed": (_str_to_bool, False),
|
|
1503
|
+
"Deleted": (_str_to_bool, False),
|
|
1504
|
+
}
|
|
1505
|
+
)
|
|
1506
|
+
|
|
1507
|
+
# Parse Name elements (text, multiple)
|
|
1508
|
+
names = []
|
|
1509
|
+
for child in element:
|
|
1510
|
+
if child.tag.endswith("Name"):
|
|
1511
|
+
if child.text:
|
|
1512
|
+
names.append(child.text)
|
|
1513
|
+
if names:
|
|
1514
|
+
result["Name"] = names
|
|
1515
|
+
|
|
1516
|
+
# Parse Icon (base64)
|
|
1517
|
+
for child in element:
|
|
1518
|
+
if child.tag.endswith("Icon"):
|
|
1519
|
+
if child.text:
|
|
1520
|
+
result["Icon"] = child.text
|
|
1521
|
+
break
|
|
1522
|
+
|
|
1523
|
+
# Parse VisibleTermFields
|
|
1524
|
+
for child in element:
|
|
1525
|
+
if child.tag.endswith("VisibleTermFields"):
|
|
1526
|
+
result["VisibleTermFields"] = child.attrib.copy()
|
|
1527
|
+
break
|
|
1528
|
+
|
|
1529
|
+
# Parse DimensionLink (multiple)
|
|
1530
|
+
dim_links = []
|
|
1531
|
+
for child in element:
|
|
1532
|
+
if child.tag.endswith("DimensionLink"):
|
|
1533
|
+
dim_links.append(child.attrib.get("DimensionID", ""))
|
|
1534
|
+
if dim_links:
|
|
1535
|
+
result["DimensionLink"] = dim_links
|
|
1536
|
+
|
|
1537
|
+
# Parse UserTypeLink (multiple) - parent type links
|
|
1538
|
+
user_type_links = []
|
|
1539
|
+
for child in element:
|
|
1540
|
+
if child.tag.endswith("UserTypeLink"):
|
|
1541
|
+
user_type_links.append(child.attrib.get("UserTypeID", ""))
|
|
1542
|
+
if user_type_links:
|
|
1543
|
+
result["UserTypeLink"] = user_type_links
|
|
1544
|
+
|
|
1545
|
+
# Parse AttributeLink (multiple) - valid attributes for this type
|
|
1546
|
+
attr_links = []
|
|
1547
|
+
for child in element:
|
|
1548
|
+
if child.tag.endswith("AttributeLink"):
|
|
1549
|
+
attr_links.append(child.attrib.get("AttributeID", ""))
|
|
1550
|
+
if attr_links:
|
|
1551
|
+
result["AttributeLink"] = attr_links
|
|
1552
|
+
|
|
1553
|
+
# Parse MIMEType (multiple)
|
|
1554
|
+
mime_types = []
|
|
1555
|
+
for child in element:
|
|
1556
|
+
if child.tag.endswith("MIMEType"):
|
|
1557
|
+
mime_type_dict = {"MIMEType": child.text or ""}
|
|
1558
|
+
mime_type_dict.update(child.attrib.copy())
|
|
1559
|
+
mime_types.append(mime_type_dict)
|
|
1560
|
+
if mime_types:
|
|
1561
|
+
result["MIMEType"] = mime_types
|
|
1562
|
+
|
|
1563
|
+
return result
|
|
1564
|
+
|
|
1565
|
+
|
|
1566
|
+
def _parse_context(element: ET.Element) -> dict:
|
|
1567
|
+
"""Parse Context element (multi-language/multi-dimensional context).
|
|
1568
|
+
|
|
1569
|
+
Attributes (5 total):
|
|
1570
|
+
- ID (required): Context identifier (e.g., "en_US", "fr_FR")
|
|
1571
|
+
- Priority (optional, default "0") - Cross-context priority
|
|
1572
|
+
- Locale (optional string) - Locale code for language/region
|
|
1573
|
+
- Selected (optional boolean, default True)
|
|
1574
|
+
- Referenced (optional boolean, default False)
|
|
1575
|
+
- Changed (optional boolean, default False)
|
|
1576
|
+
- Deleted (optional boolean, default False)
|
|
1577
|
+
|
|
1578
|
+
Child Elements:
|
|
1579
|
+
- Name (text, multiple) - Context display name
|
|
1580
|
+
- DimensionPointLink (multiple) - Associated dimension points
|
|
1581
|
+
"""
|
|
1582
|
+
result = _parse_element(
|
|
1583
|
+
element,
|
|
1584
|
+
attributes=("ID", "Priority", "Locale", "Selected", "Referenced",
|
|
1585
|
+
"Changed", "Deleted"),
|
|
1586
|
+
conversions={
|
|
1587
|
+
"Priority": (lambda x: x, "0"),
|
|
1588
|
+
"Selected": (_str_to_bool, True),
|
|
1589
|
+
"Referenced": (_str_to_bool, False),
|
|
1590
|
+
"Changed": (_str_to_bool, False),
|
|
1591
|
+
"Deleted": (_str_to_bool, False),
|
|
1592
|
+
}
|
|
1593
|
+
)
|
|
1594
|
+
|
|
1595
|
+
# Parse Name elements (text, multiple)
|
|
1596
|
+
names = []
|
|
1597
|
+
for child in element:
|
|
1598
|
+
if child.tag.endswith("Name"):
|
|
1599
|
+
if child.text:
|
|
1600
|
+
names.append(child.text)
|
|
1601
|
+
if names:
|
|
1602
|
+
result["Name"] = names
|
|
1603
|
+
|
|
1604
|
+
# Parse DimensionPointLink (multiple)
|
|
1605
|
+
dim_point_links = []
|
|
1606
|
+
for child in element:
|
|
1607
|
+
if child.tag.endswith("DimensionPointLink"):
|
|
1608
|
+
dim_point_links.append(child.attrib.get("DimensionPointID", ""))
|
|
1609
|
+
if dim_point_links:
|
|
1610
|
+
result["DimensionPointLink"] = dim_point_links
|
|
1611
|
+
|
|
1612
|
+
return result
|
|
1613
|
+
|
|
1614
|
+
def _parse_link_type(element: ET.Element) -> dict:
|
|
1615
|
+
"""Parse LinkType element (cross-reference type definition).
|
|
1616
|
+
|
|
1617
|
+
Note: In STEP XML, LinkType elements appear within other structures
|
|
1618
|
+
to indicate which link types (CA=Classification-to-Attribute or
|
|
1619
|
+
PA=Product-to-Attribute) are valid for an attribute.
|
|
1620
|
+
|
|
1621
|
+
Attributes (1 total):
|
|
1622
|
+
- LinkTypeID (required): Either "CA" or "PA"
|
|
1623
|
+
- CA: Classification-to-Attribute link type
|
|
1624
|
+
- PA: Product-to-Attribute link type
|
|
1625
|
+
"""
|
|
1626
|
+
return _parse_element(
|
|
1627
|
+
element,
|
|
1628
|
+
attributes=("LinkTypeID",)
|
|
1629
|
+
)
|
|
1630
|
+
|
|
1631
|
+
def _parse_business_rule(element: ET.Element) -> dict:
|
|
1632
|
+
"""Parse BusinessRule element (business rule definition).
|
|
1633
|
+
|
|
1634
|
+
Business rules can be Actions, Conditions, Functions, or Libraries.
|
|
1635
|
+
They contain business logic that executes within STEP.
|
|
1636
|
+
|
|
1637
|
+
Attributes (4 total):
|
|
1638
|
+
- ID (required): Unique identifier for the business rule
|
|
1639
|
+
- Type (required): Rule type - Action, Condition, Function, or Library
|
|
1640
|
+
- Scope (optional): Local or Global (only for Actions/Conditions)
|
|
1641
|
+
- RunPrivileged (optional): Boolean, default False
|
|
1642
|
+
|
|
1643
|
+
Child elements:
|
|
1644
|
+
- Name (multiple): Display name in different languages
|
|
1645
|
+
- Description (optional): Rule description
|
|
1646
|
+
- Dependency (multiple): Dependencies on other rules
|
|
1647
|
+
- FunctionDependency (multiple): Dependencies on functions
|
|
1648
|
+
- OnApprove (optional): Execution on approval
|
|
1649
|
+
- Configuration (optional): Encoded configuration data
|
|
1650
|
+
- ValidObjectTypes (optional): Object types this rule applies to
|
|
1651
|
+
- SetupGroupLink (optional): Setup group link
|
|
1652
|
+
"""
|
|
1653
|
+
result = _parse_element(
|
|
1654
|
+
element,
|
|
1655
|
+
attributes=("ID", "Type", "Scope", "RunPrivileged"),
|
|
1656
|
+
conversions={
|
|
1657
|
+
"RunPrivileged": (lambda x: x.lower() == "true", False)
|
|
1658
|
+
}
|
|
1659
|
+
)
|
|
1660
|
+
|
|
1661
|
+
# Parse Name elements (multiple)
|
|
1662
|
+
names = []
|
|
1663
|
+
for child in element:
|
|
1664
|
+
local_tag = _get_local_tag(child)
|
|
1665
|
+
if local_tag == "Name" and child.text:
|
|
1666
|
+
names.append(child.text)
|
|
1667
|
+
if names:
|
|
1668
|
+
result["Name"] = names
|
|
1669
|
+
|
|
1670
|
+
# Parse Description
|
|
1671
|
+
for child in element:
|
|
1672
|
+
local_tag = _get_local_tag(child)
|
|
1673
|
+
if local_tag == "Description" and child.text:
|
|
1674
|
+
result["Description"] = child.text
|
|
1675
|
+
break
|
|
1676
|
+
|
|
1677
|
+
return result
|
|
1678
|
+
|
|
1679
|
+
def _parse_event_queue(element: ET.Element) -> dict:
|
|
1680
|
+
"""Parse EventQueue element (event queue configuration).
|
|
1681
|
+
|
|
1682
|
+
Event queues store events generated by STEP system operations.
|
|
1683
|
+
They can be read/processed or discarded.
|
|
1684
|
+
|
|
1685
|
+
Attributes (7 total):
|
|
1686
|
+
- ID (optional): Unique identifier for the event queue
|
|
1687
|
+
- QueueStatus (optional): Read or Discard, default Read
|
|
1688
|
+
- DaysRetained (optional): Number of days to retain events
|
|
1689
|
+
- Selected (optional): Boolean, default True
|
|
1690
|
+
- Referenced (optional): Boolean, default False
|
|
1691
|
+
- Changed (optional): Boolean, default False
|
|
1692
|
+
- Deleted (optional): Boolean, default False
|
|
1693
|
+
|
|
1694
|
+
Child elements:
|
|
1695
|
+
- Name (multiple): Display name in different languages
|
|
1696
|
+
- MetaData (optional): Metadata attributes and values
|
|
1697
|
+
- Configuration (required): Encoded configuration data
|
|
1698
|
+
"""
|
|
1699
|
+
result = _parse_element(
|
|
1700
|
+
element,
|
|
1701
|
+
attributes=("ID", "QueueStatus", "DaysRetained", "Selected", "Referenced", "Changed", "Deleted"),
|
|
1702
|
+
conversions={
|
|
1703
|
+
"Selected": (lambda x: x.lower() == "true", True),
|
|
1704
|
+
"Referenced": (lambda x: x.lower() == "true", False),
|
|
1705
|
+
"Changed": (lambda x: x.lower() == "true", False),
|
|
1706
|
+
"Deleted": (lambda x: x.lower() == "true", False)
|
|
1707
|
+
}
|
|
1708
|
+
)
|
|
1709
|
+
|
|
1710
|
+
# Parse Name elements (multiple)
|
|
1711
|
+
names = []
|
|
1712
|
+
for child in element:
|
|
1713
|
+
local_tag = _get_local_tag(child)
|
|
1714
|
+
if local_tag == "Name" and child.text:
|
|
1715
|
+
names.append(child.text)
|
|
1716
|
+
if names:
|
|
1717
|
+
result["Name"] = names
|
|
1718
|
+
|
|
1719
|
+
# Parse MetaData
|
|
1720
|
+
for child in element:
|
|
1721
|
+
local_tag = _get_local_tag(child)
|
|
1722
|
+
if local_tag == "MetaData":
|
|
1723
|
+
result["MetaData"] = _parse_metadata(child)
|
|
1724
|
+
break
|
|
1725
|
+
|
|
1726
|
+
return result
|
|
1727
|
+
|
|
1728
|
+
def _parse_workflow(element: ET.Element) -> dict:
|
|
1729
|
+
"""Parse STEPWorkflow element (workflow definition).
|
|
1730
|
+
|
|
1731
|
+
Workflows orchestrate business processes in STEP.
|
|
1732
|
+
They define states, transitions, and validations.
|
|
1733
|
+
|
|
1734
|
+
Attributes (3 total):
|
|
1735
|
+
- ID (required): Unique identifier for the workflow
|
|
1736
|
+
- Selected (optional): Boolean, default True
|
|
1737
|
+
- Referenced (optional): Boolean, default False
|
|
1738
|
+
- Changed (optional): Boolean, default False
|
|
1739
|
+
|
|
1740
|
+
Child elements:
|
|
1741
|
+
- Name (multiple): Display name in different languages
|
|
1742
|
+
- MetaData (optional): Metadata attributes and values
|
|
1743
|
+
- ValidUserTypeLink (multiple): Object types valid in workflow
|
|
1744
|
+
- StartCondition (optional): Condition for workflow start
|
|
1745
|
+
- Configuration (optional): Encoded configuration data
|
|
1746
|
+
- LocalBusinessRule (multiple): Embedded business rules
|
|
1747
|
+
- BusinessRuleLink (multiple): References to business rules
|
|
1748
|
+
- SetupGroupLink (optional): Setup group link
|
|
1749
|
+
"""
|
|
1750
|
+
result = _parse_element(
|
|
1751
|
+
element,
|
|
1752
|
+
attributes=("ID", "Selected", "Referenced", "Changed"),
|
|
1753
|
+
conversions={
|
|
1754
|
+
"Selected": (lambda x: x.lower() == "true", True),
|
|
1755
|
+
"Referenced": (lambda x: x.lower() == "true", False),
|
|
1756
|
+
"Changed": (lambda x: x.lower() == "true", False)
|
|
1757
|
+
}
|
|
1758
|
+
)
|
|
1759
|
+
|
|
1760
|
+
# Parse Name elements (multiple)
|
|
1761
|
+
names = []
|
|
1762
|
+
for child in element:
|
|
1763
|
+
local_tag = _get_local_tag(child)
|
|
1764
|
+
if local_tag == "Name" and child.text:
|
|
1765
|
+
names.append(child.text)
|
|
1766
|
+
if names:
|
|
1767
|
+
result["Name"] = names
|
|
1768
|
+
|
|
1769
|
+
# Parse MetaData
|
|
1770
|
+
for child in element:
|
|
1771
|
+
local_tag = _get_local_tag(child)
|
|
1772
|
+
if local_tag == "MetaData":
|
|
1773
|
+
result["MetaData"] = _parse_metadata(child)
|
|
1774
|
+
break
|
|
1775
|
+
|
|
1776
|
+
# Parse ValidUserTypeLink elements (multiple)
|
|
1777
|
+
user_type_links = []
|
|
1778
|
+
for child in element:
|
|
1779
|
+
local_tag = _get_local_tag(child)
|
|
1780
|
+
if local_tag == "ValidUserTypeLink":
|
|
1781
|
+
user_type_links.append(child.attrib.get("UserTypeID", ""))
|
|
1782
|
+
if user_type_links:
|
|
1783
|
+
result["ValidUserTypeLink"] = user_type_links
|
|
1784
|
+
|
|
1785
|
+
return result
|
|
1786
|
+
|
|
1787
|
+
def _parse_export_configuration(element: ET.Element) -> dict:
|
|
1788
|
+
"""Parse ExportConfiguration element (export/integration configuration).
|
|
1789
|
+
|
|
1790
|
+
Export configurations define how data is exported from STEP.
|
|
1791
|
+
They specify output format, target systems, and mapping rules.
|
|
1792
|
+
|
|
1793
|
+
Attributes (1 total):
|
|
1794
|
+
- ID (optional): Unique identifier for the configuration
|
|
1795
|
+
|
|
1796
|
+
Child elements:
|
|
1797
|
+
- Name (multiple): Display name in different languages
|
|
1798
|
+
- MetaData (optional): Metadata attributes and values
|
|
1799
|
+
- ClassificationReference (multiple): Classifications this applies to
|
|
1800
|
+
- Configuration (optional): Encoded configuration data
|
|
1801
|
+
"""
|
|
1802
|
+
result = _parse_element(
|
|
1803
|
+
element,
|
|
1804
|
+
attributes=("ID",)
|
|
1805
|
+
)
|
|
1806
|
+
|
|
1807
|
+
# Parse Name elements (multiple)
|
|
1808
|
+
names = []
|
|
1809
|
+
for child in element:
|
|
1810
|
+
local_tag = _get_local_tag(child)
|
|
1811
|
+
if local_tag == "Name" and child.text:
|
|
1812
|
+
names.append(child.text)
|
|
1813
|
+
if names:
|
|
1814
|
+
result["Name"] = names
|
|
1815
|
+
|
|
1816
|
+
# Parse MetaData
|
|
1817
|
+
for child in element:
|
|
1818
|
+
local_tag = _get_local_tag(child)
|
|
1819
|
+
if local_tag == "MetaData":
|
|
1820
|
+
result["MetaData"] = _parse_metadata(child)
|
|
1821
|
+
break
|
|
1822
|
+
|
|
1823
|
+
# Parse ClassificationReference elements (multiple)
|
|
1824
|
+
class_refs = []
|
|
1825
|
+
for child in element:
|
|
1826
|
+
local_tag = _get_local_tag(child)
|
|
1827
|
+
if local_tag == "ClassificationReference":
|
|
1828
|
+
class_refs.append(child.attrib.get("ClassificationID", ""))
|
|
1829
|
+
if class_refs:
|
|
1830
|
+
result["ClassificationReference"] = class_refs
|
|
1831
|
+
|
|
1832
|
+
return result
|
|
1833
|
+
|
|
1834
|
+
def _parse_delete_classification(element: ET.Element) -> dict:
|
|
1835
|
+
"""Parse DeleteClassification element (Phase 10: Organizational Structure).
|
|
1836
|
+
|
|
1837
|
+
Specifies deletion of a classification and optionally its values/subtypes.
|
|
1838
|
+
Attributes include the classification identifier and its type.
|
|
1839
|
+
|
|
1840
|
+
Attributes (2 total):
|
|
1841
|
+
- ID (required): Classification identifier to delete
|
|
1842
|
+
- Type (required): Type of deletion ('All', 'SubTypes', or 'Values')
|
|
1843
|
+
|
|
1844
|
+
Child elements:
|
|
1845
|
+
- KeyValue (multiple): Specific values/keys to delete
|
|
1846
|
+
"""
|
|
1847
|
+
result = _parse_element(
|
|
1848
|
+
element,
|
|
1849
|
+
attributes=("ID", "Type")
|
|
1850
|
+
)
|
|
1851
|
+
|
|
1852
|
+
# Parse KeyValue elements (multiple)
|
|
1853
|
+
key_values = []
|
|
1854
|
+
for child in element:
|
|
1855
|
+
local_tag = _get_local_tag(child)
|
|
1856
|
+
if local_tag == "KeyValue":
|
|
1857
|
+
key_values.append(_parse_key_value(child))
|
|
1858
|
+
if key_values:
|
|
1859
|
+
result["KeyValue"] = key_values
|
|
1860
|
+
|
|
1861
|
+
return result
|
|
1862
|
+
|
|
1863
|
+
def _parse_delete_entity(element: ET.Element) -> dict:
|
|
1864
|
+
"""Parse DeleteEntity element (Phase 10: Organizational Structure).
|
|
1865
|
+
|
|
1866
|
+
Specifies deletion of an entity and optionally its records/relationships.
|
|
1867
|
+
Attributes include the entity identifier.
|
|
1868
|
+
|
|
1869
|
+
Attributes (1 total):
|
|
1870
|
+
- ID (required): Entity identifier to delete
|
|
1871
|
+
|
|
1872
|
+
Child elements:
|
|
1873
|
+
- KeyValue (multiple): Entity records/keys to delete
|
|
1874
|
+
"""
|
|
1875
|
+
result = _parse_element(
|
|
1876
|
+
element,
|
|
1877
|
+
attributes=("ID",)
|
|
1878
|
+
)
|
|
1879
|
+
|
|
1880
|
+
# Parse KeyValue elements (multiple)
|
|
1881
|
+
key_values = []
|
|
1882
|
+
for child in element:
|
|
1883
|
+
local_tag = _get_local_tag(child)
|
|
1884
|
+
if local_tag == "KeyValue":
|
|
1885
|
+
key_values.append(_parse_key_value(child))
|
|
1886
|
+
if key_values:
|
|
1887
|
+
result["KeyValue"] = key_values
|
|
1888
|
+
|
|
1889
|
+
return result
|
|
1890
|
+
|
|
1891
|
+
def _parse_cross_reference_type(element: ET.Element) -> dict:
|
|
1892
|
+
"""Parse CrossReferenceType element (Phase 10: Organizational Structure).
|
|
1893
|
+
|
|
1894
|
+
Defines a type of cross-reference relationship between objects.
|
|
1895
|
+
Cross-reference types specify how different entities relate to each other.
|
|
1896
|
+
|
|
1897
|
+
Attributes (1 total):
|
|
1898
|
+
- ID (required): Unique identifier for the cross-reference type
|
|
1899
|
+
|
|
1900
|
+
Child elements:
|
|
1901
|
+
- Name (multiple): Display name in different languages
|
|
1902
|
+
- MetaData (optional): Metadata attributes and values
|
|
1903
|
+
"""
|
|
1904
|
+
result = _parse_element(
|
|
1905
|
+
element,
|
|
1906
|
+
attributes=("ID",)
|
|
1907
|
+
)
|
|
1908
|
+
|
|
1909
|
+
# Parse Name elements (multiple)
|
|
1910
|
+
names = []
|
|
1911
|
+
for child in element:
|
|
1912
|
+
local_tag = _get_local_tag(child)
|
|
1913
|
+
if local_tag == "Name" and child.text:
|
|
1914
|
+
names.append(child.text)
|
|
1915
|
+
if names:
|
|
1916
|
+
result["Name"] = names
|
|
1917
|
+
|
|
1918
|
+
# Parse MetaData
|
|
1919
|
+
for child in element:
|
|
1920
|
+
local_tag = _get_local_tag(child)
|
|
1921
|
+
if local_tag == "MetaData":
|
|
1922
|
+
result["MetaData"] = _parse_metadata(child)
|
|
1923
|
+
break
|
|
1924
|
+
|
|
1925
|
+
return result
|
|
1926
|
+
|
|
1927
|
+
def _parse_qualifier(element: ET.Element) -> dict:
|
|
1928
|
+
"""Parse Qualifier element (Phase 10: Organizational Structure).
|
|
1929
|
+
|
|
1930
|
+
Represents a dimension qualifier or multi-dimensional data point.
|
|
1931
|
+
Qualifiers enable multi-dimensional attribute management in STEP.
|
|
1932
|
+
|
|
1933
|
+
Attributes (1 total):
|
|
1934
|
+
- ID (required): Unique identifier for the qualifier
|
|
1935
|
+
|
|
1936
|
+
Child elements:
|
|
1937
|
+
- Name (multiple): Display name in different languages
|
|
1938
|
+
- MetaData (optional): Metadata attributes and values
|
|
1939
|
+
- DimensionPointLink (multiple): Dimension point references
|
|
1940
|
+
"""
|
|
1941
|
+
result = _parse_element(
|
|
1942
|
+
element,
|
|
1943
|
+
attributes=("ID",)
|
|
1944
|
+
)
|
|
1945
|
+
|
|
1946
|
+
# Parse Name elements (multiple)
|
|
1947
|
+
names = []
|
|
1948
|
+
for child in element:
|
|
1949
|
+
local_tag = _get_local_tag(child)
|
|
1950
|
+
if local_tag == "Name" and child.text:
|
|
1951
|
+
names.append(child.text)
|
|
1952
|
+
if names:
|
|
1953
|
+
result["Name"] = names
|
|
1954
|
+
|
|
1955
|
+
# Parse MetaData
|
|
1956
|
+
for child in element:
|
|
1957
|
+
local_tag = _get_local_tag(child)
|
|
1958
|
+
if local_tag == "MetaData":
|
|
1959
|
+
result["MetaData"] = _parse_metadata(child)
|
|
1960
|
+
break
|
|
1961
|
+
|
|
1962
|
+
# Parse DimensionPointLink elements (multiple)
|
|
1963
|
+
dim_links = []
|
|
1964
|
+
for child in element:
|
|
1965
|
+
local_tag = _get_local_tag(child)
|
|
1966
|
+
if local_tag == "DimensionPointLink":
|
|
1967
|
+
dim_links.append({
|
|
1968
|
+
"DimensionID": child.attrib.get("DimensionID", ""),
|
|
1969
|
+
"DimensionPointID": child.attrib.get("DimensionPointID", "")
|
|
1970
|
+
})
|
|
1971
|
+
if dim_links:
|
|
1972
|
+
result["DimensionPointLink"] = dim_links
|
|
1973
|
+
|
|
1974
|
+
return result
|
|
1975
|
+
|
|
1976
|
+
def _parse_dimension(element: ET.Element) -> dict:
|
|
1977
|
+
"""Parse Dimension element (Phase 11: Foundational Elements).
|
|
1978
|
+
|
|
1979
|
+
Represents a dimension for multi-dimensional data organization.
|
|
1980
|
+
Dimensions define axes along which data can be organized (e.g., Market, Color).
|
|
1981
|
+
|
|
1982
|
+
Attributes (2 total):
|
|
1983
|
+
- ID (required): Unique identifier for the dimension
|
|
1984
|
+
- AllowDuplicates (optional): Whether duplicate dimension points are allowed
|
|
1985
|
+
|
|
1986
|
+
Child elements:
|
|
1987
|
+
- Name (multiple): Display name in different languages
|
|
1988
|
+
- MetaData (optional): Metadata attributes and values
|
|
1989
|
+
- DimensionPoint (multiple): Points/values for this dimension
|
|
1990
|
+
"""
|
|
1991
|
+
result = _parse_element(
|
|
1992
|
+
element,
|
|
1993
|
+
attributes=("ID", "AllowDuplicates")
|
|
1994
|
+
)
|
|
1995
|
+
|
|
1996
|
+
# Parse Name elements (multiple)
|
|
1997
|
+
names = []
|
|
1998
|
+
for child in element:
|
|
1999
|
+
local_tag = _get_local_tag(child)
|
|
2000
|
+
if local_tag == "Name" and child.text:
|
|
2001
|
+
names.append(child.text)
|
|
2002
|
+
if names:
|
|
2003
|
+
result["Name"] = names
|
|
2004
|
+
|
|
2005
|
+
# Parse MetaData
|
|
2006
|
+
for child in element:
|
|
2007
|
+
local_tag = _get_local_tag(child)
|
|
2008
|
+
if local_tag == "MetaData":
|
|
2009
|
+
result["MetaData"] = _parse_metadata(child)
|
|
2010
|
+
break
|
|
2011
|
+
|
|
2012
|
+
# Parse DimensionPoint elements (multiple)
|
|
2013
|
+
dim_points = []
|
|
2014
|
+
for child in element:
|
|
2015
|
+
local_tag = _get_local_tag(child)
|
|
2016
|
+
if local_tag == "DimensionPoint":
|
|
2017
|
+
dim_points.append(_parse_dimension_point(child))
|
|
2018
|
+
if dim_points:
|
|
2019
|
+
result["DimensionPoint"] = dim_points
|
|
2020
|
+
|
|
2021
|
+
return result
|
|
2022
|
+
|
|
2023
|
+
def _parse_dimension_point(element: ET.Element) -> dict:
|
|
2024
|
+
"""Parse DimensionPoint element (Phase 11: Foundational Elements).
|
|
2025
|
+
|
|
2026
|
+
Represents a point/value within a dimension.
|
|
2027
|
+
|
|
2028
|
+
Attributes (1 total):
|
|
2029
|
+
- ID (required): Unique identifier for this dimension point
|
|
2030
|
+
|
|
2031
|
+
Child elements:
|
|
2032
|
+
- Name (multiple): Display name in different languages
|
|
2033
|
+
- MetaData (optional): Metadata attributes and values
|
|
2034
|
+
"""
|
|
2035
|
+
result = _parse_element(
|
|
2036
|
+
element,
|
|
2037
|
+
attributes=("ID",)
|
|
2038
|
+
)
|
|
2039
|
+
|
|
2040
|
+
# Parse Name elements (multiple)
|
|
2041
|
+
names = []
|
|
2042
|
+
for child in element:
|
|
2043
|
+
local_tag = _get_local_tag(child)
|
|
2044
|
+
if local_tag == "Name" and child.text:
|
|
2045
|
+
names.append(child.text)
|
|
2046
|
+
if names:
|
|
2047
|
+
result["Name"] = names
|
|
2048
|
+
|
|
2049
|
+
# Parse MetaData
|
|
2050
|
+
for child in element:
|
|
2051
|
+
local_tag = _get_local_tag(child)
|
|
2052
|
+
if local_tag == "MetaData":
|
|
2053
|
+
result["MetaData"] = _parse_metadata(child)
|
|
2054
|
+
break
|
|
2055
|
+
|
|
2056
|
+
return result
|
|
2057
|
+
|
|
2058
|
+
def _parse_tag(element: ET.Element) -> dict:
|
|
2059
|
+
"""Parse Tag element (Phase 11: Foundational Elements).
|
|
2060
|
+
|
|
2061
|
+
Represents a flexible tag for object categorization.
|
|
2062
|
+
Tags enable custom classification independent of the fixed hierarchy.
|
|
2063
|
+
|
|
2064
|
+
Attributes (1 total):
|
|
2065
|
+
- ID (required): Unique identifier for the tag
|
|
2066
|
+
|
|
2067
|
+
Child elements:
|
|
2068
|
+
- Name (multiple): Display name in different languages
|
|
2069
|
+
- MetaData (optional): Metadata attributes and values
|
|
2070
|
+
"""
|
|
2071
|
+
result = _parse_element(
|
|
2072
|
+
element,
|
|
2073
|
+
attributes=("ID",)
|
|
2074
|
+
)
|
|
2075
|
+
|
|
2076
|
+
# Parse Name elements (multiple)
|
|
2077
|
+
names = []
|
|
2078
|
+
for child in element:
|
|
2079
|
+
local_tag = _get_local_tag(child)
|
|
2080
|
+
if local_tag == "Name" and child.text:
|
|
2081
|
+
names.append(child.text)
|
|
2082
|
+
if names:
|
|
2083
|
+
result["Name"] = names
|
|
2084
|
+
|
|
2085
|
+
# Parse MetaData
|
|
2086
|
+
for child in element:
|
|
2087
|
+
local_tag = _get_local_tag(child)
|
|
2088
|
+
if local_tag == "MetaData":
|
|
2089
|
+
result["MetaData"] = _parse_metadata(child)
|
|
2090
|
+
break
|
|
2091
|
+
|
|
2092
|
+
return result
|
|
2093
|
+
|
|
2094
|
+
def _parse_tag_group(element: ET.Element) -> dict:
|
|
2095
|
+
"""Parse TagGroup element (Phase 11: Foundational Elements).
|
|
2096
|
+
|
|
2097
|
+
Represents a hierarchical grouping of tags.
|
|
2098
|
+
Tag groups organize tags into logical collections.
|
|
2099
|
+
|
|
2100
|
+
Attributes (1 total):
|
|
2101
|
+
- ID (required): Unique identifier for the tag group
|
|
2102
|
+
|
|
2103
|
+
Child elements:
|
|
2104
|
+
- Name (multiple): Display name in different languages
|
|
2105
|
+
- MetaData (optional): Metadata attributes and values
|
|
2106
|
+
- TagGroup (multiple): Nested tag groups (hierarchical)
|
|
2107
|
+
- Tag (multiple): Tags in this group
|
|
2108
|
+
"""
|
|
2109
|
+
result = _parse_element(
|
|
2110
|
+
element,
|
|
2111
|
+
attributes=("ID",)
|
|
2112
|
+
)
|
|
2113
|
+
|
|
2114
|
+
# Parse Name elements (multiple)
|
|
2115
|
+
names = []
|
|
2116
|
+
for child in element:
|
|
2117
|
+
local_tag = _get_local_tag(child)
|
|
2118
|
+
if local_tag == "Name" and child.text:
|
|
2119
|
+
names.append(child.text)
|
|
2120
|
+
if names:
|
|
2121
|
+
result["Name"] = names
|
|
2122
|
+
|
|
2123
|
+
# Parse MetaData
|
|
2124
|
+
for child in element:
|
|
2125
|
+
local_tag = _get_local_tag(child)
|
|
2126
|
+
if local_tag == "MetaData":
|
|
2127
|
+
result["MetaData"] = _parse_metadata(child)
|
|
2128
|
+
break
|
|
2129
|
+
|
|
2130
|
+
# Parse nested TagGroup elements (multiple)
|
|
2131
|
+
tag_groups = []
|
|
2132
|
+
for child in element:
|
|
2133
|
+
local_tag = _get_local_tag(child)
|
|
2134
|
+
if local_tag == "TagGroup":
|
|
2135
|
+
tag_groups.append(_parse_tag_group(child))
|
|
2136
|
+
if tag_groups:
|
|
2137
|
+
result["TagGroup"] = tag_groups
|
|
2138
|
+
|
|
2139
|
+
# Parse Tag elements (multiple)
|
|
2140
|
+
tags = []
|
|
2141
|
+
for child in element:
|
|
2142
|
+
local_tag = _get_local_tag(child)
|
|
2143
|
+
if local_tag == "Tag":
|
|
2144
|
+
tags.append(_parse_tag(child))
|
|
2145
|
+
if tags:
|
|
2146
|
+
result["Tag"] = tags
|
|
2147
|
+
|
|
2148
|
+
return result
|
|
2149
|
+
|
|
2150
|
+
def _parse_unit(element: ET.Element) -> dict:
|
|
2151
|
+
"""Parse Unit element (Phase 11: Foundational Elements).
|
|
2152
|
+
|
|
2153
|
+
Represents a measurement unit (e.g., meters, kilograms, liters).
|
|
2154
|
+
Units are used for quantitative attribute values.
|
|
2155
|
+
|
|
2156
|
+
Attributes (4 total):
|
|
2157
|
+
- ID (required): Unique identifier for the unit
|
|
2158
|
+
- Name (optional): Display name of the unit
|
|
2159
|
+
- Symbol (optional): Unit symbol (e.g., 'm', 'kg', 'l')
|
|
2160
|
+
- Formula (optional): Conversion formula to base unit
|
|
2161
|
+
|
|
2162
|
+
Child elements:
|
|
2163
|
+
- MetaData (optional): Metadata attributes and values
|
|
2164
|
+
"""
|
|
2165
|
+
result = _parse_element(
|
|
2166
|
+
element,
|
|
2167
|
+
attributes=("ID", "Name", "Symbol", "Formula")
|
|
2168
|
+
)
|
|
2169
|
+
|
|
2170
|
+
# Parse MetaData
|
|
2171
|
+
for child in element:
|
|
2172
|
+
local_tag = _get_local_tag(child)
|
|
2173
|
+
if local_tag == "MetaData":
|
|
2174
|
+
result["MetaData"] = _parse_metadata(child)
|
|
2175
|
+
break
|
|
2176
|
+
|
|
2177
|
+
return result
|
|
2178
|
+
|
|
2179
|
+
|
|
2180
|
+
# Phase 12: Replacement Rules and Collections
|
|
2181
|
+
|
|
2182
|
+
|
|
2183
|
+
|
|
2184
|
+
def _parse_replace_attributes(element: ET.Element) -> dict:
|
|
2185
|
+
"""Parse replacement rules for attributes."""
|
|
2186
|
+
return _parse_element(element, attributes=[], texts=[], specials={})
|
|
2187
|
+
|
|
2188
|
+
|
|
2189
|
+
def _parse_replace_products(element: ET.Element) -> dict:
|
|
2190
|
+
"""Parse replacement rules for products."""
|
|
2191
|
+
result = {}
|
|
2192
|
+
|
|
2193
|
+
for child in element:
|
|
2194
|
+
local_tag = _get_local_tag(child)
|
|
2195
|
+
|
|
2196
|
+
if local_tag == "ReplaceValues":
|
|
2197
|
+
if "ReplaceValues" not in result:
|
|
2198
|
+
result["ReplaceValues"] = []
|
|
2199
|
+
result["ReplaceValues"].append(_parse_replace_values(child))
|
|
2200
|
+
|
|
2201
|
+
elif local_tag == "ReplaceCrossReferences":
|
|
2202
|
+
if "ReplaceCrossReferences" not in result:
|
|
2203
|
+
result["ReplaceCrossReferences"] = []
|
|
2204
|
+
result["ReplaceCrossReferences"].append(_parse_replace_cross_references(child))
|
|
2205
|
+
|
|
2206
|
+
elif local_tag == "ReplaceAttributeLinks":
|
|
2207
|
+
if "ReplaceAttributeLinks" not in result:
|
|
2208
|
+
result["ReplaceAttributeLinks"] = []
|
|
2209
|
+
result["ReplaceAttributeLinks"].append(_parse_replace_attribute_links(child))
|
|
2210
|
+
|
|
2211
|
+
elif local_tag == "ReplaceClassificationReferences":
|
|
2212
|
+
if "ReplaceClassificationReferences" not in result:
|
|
2213
|
+
result["ReplaceClassificationReferences"] = []
|
|
2214
|
+
result["ReplaceClassificationReferences"].append(_parse_replace_classification_references(child))
|
|
2215
|
+
|
|
2216
|
+
return result
|
|
2217
|
+
|
|
2218
|
+
|
|
2219
|
+
def _parse_replace_classifications(element: ET.Element) -> dict:
|
|
2220
|
+
"""Parse replacement rules for classifications."""
|
|
2221
|
+
result = {}
|
|
2222
|
+
|
|
2223
|
+
for child in element:
|
|
2224
|
+
local_tag = _get_local_tag(child)
|
|
2225
|
+
|
|
2226
|
+
if local_tag == "ReplaceValues":
|
|
2227
|
+
if "ReplaceValues" not in result:
|
|
2228
|
+
result["ReplaceValues"] = []
|
|
2229
|
+
result["ReplaceValues"].append(_parse_replace_values(child))
|
|
2230
|
+
|
|
2231
|
+
elif local_tag == "ReplaceCrossReferences":
|
|
2232
|
+
if "ReplaceCrossReferences" not in result:
|
|
2233
|
+
result["ReplaceCrossReferences"] = []
|
|
2234
|
+
result["ReplaceCrossReferences"].append(_parse_replace_cross_references(child))
|
|
2235
|
+
|
|
2236
|
+
elif local_tag == "ReplaceAttributeLinks":
|
|
2237
|
+
if "ReplaceAttributeLinks" not in result:
|
|
2238
|
+
result["ReplaceAttributeLinks"] = []
|
|
2239
|
+
result["ReplaceAttributeLinks"].append(_parse_replace_attribute_links(child))
|
|
2240
|
+
|
|
2241
|
+
return result
|
|
2242
|
+
|
|
2243
|
+
|
|
2244
|
+
def _parse_replace_entities(element: ET.Element) -> dict:
|
|
2245
|
+
"""Parse replacement rules for entities."""
|
|
2246
|
+
result = {}
|
|
2247
|
+
|
|
2248
|
+
for child in element:
|
|
2249
|
+
local_tag = _get_local_tag(child)
|
|
2250
|
+
|
|
2251
|
+
if local_tag == "ReplaceValues":
|
|
2252
|
+
if "ReplaceValues" not in result:
|
|
2253
|
+
result["ReplaceValues"] = []
|
|
2254
|
+
result["ReplaceValues"].append(_parse_replace_values(child))
|
|
2255
|
+
|
|
2256
|
+
elif local_tag == "ReplaceCrossReferences":
|
|
2257
|
+
if "ReplaceCrossReferences" not in result:
|
|
2258
|
+
result["ReplaceCrossReferences"] = []
|
|
2259
|
+
result["ReplaceCrossReferences"].append(_parse_replace_cross_references(child))
|
|
2260
|
+
|
|
2261
|
+
return result
|
|
2262
|
+
|
|
2263
|
+
|
|
2264
|
+
def _parse_replace_assets(element: ET.Element) -> dict:
|
|
2265
|
+
"""Parse replacement rules for assets."""
|
|
2266
|
+
result = {}
|
|
2267
|
+
|
|
2268
|
+
for child in element:
|
|
2269
|
+
local_tag = _get_local_tag(child)
|
|
2270
|
+
|
|
2271
|
+
if local_tag == "ReplaceValues":
|
|
2272
|
+
if "ReplaceValues" not in result:
|
|
2273
|
+
result["ReplaceValues"] = []
|
|
2274
|
+
result["ReplaceValues"].append(_parse_replace_values(child))
|
|
2275
|
+
|
|
2276
|
+
elif local_tag == "ReplaceCrossReferences":
|
|
2277
|
+
if "ReplaceCrossReferences" not in result:
|
|
2278
|
+
result["ReplaceCrossReferences"] = []
|
|
2279
|
+
result["ReplaceCrossReferences"].append(_parse_replace_cross_references(child))
|
|
2280
|
+
|
|
2281
|
+
return result
|
|
2282
|
+
|
|
2283
|
+
|
|
2284
|
+
def _parse_replace_link_types(element: ET.Element) -> dict:
|
|
2285
|
+
"""Parse replacement rules for link types."""
|
|
2286
|
+
return _parse_element(element, attributes=[], texts=[], specials={})
|
|
2287
|
+
|
|
2288
|
+
|
|
2289
|
+
def _parse_replace_user_types(element: ET.Element) -> dict:
|
|
2290
|
+
"""Parse replacement rules for user types."""
|
|
2291
|
+
return _parse_element(element, attributes=[], texts=[], specials={})
|
|
2292
|
+
|
|
2293
|
+
|
|
2294
|
+
def _parse_replace_user_groups(element: ET.Element) -> dict:
|
|
2295
|
+
"""Parse replacement rules for user groups."""
|
|
2296
|
+
return _parse_element(element, attributes=[], texts=[], specials={})
|
|
2297
|
+
|
|
2298
|
+
|
|
2299
|
+
def _parse_replace_values(element: ET.Element) -> dict:
|
|
2300
|
+
"""Parse replacement values instruction for attributes."""
|
|
2301
|
+
return _parse_element(
|
|
2302
|
+
element,
|
|
2303
|
+
attributes=["AttributeID", "AttributeGroupID", "ContextID"],
|
|
2304
|
+
texts=[]
|
|
2305
|
+
)
|
|
2306
|
+
|
|
2307
|
+
|
|
2308
|
+
def _parse_replace_cross_references(element: ET.Element) -> dict:
|
|
2309
|
+
"""Parse replacement cross references instruction."""
|
|
2310
|
+
return _parse_element(
|
|
2311
|
+
element,
|
|
2312
|
+
attributes=["CrossReferenceID", "ContextID", "MountLocation"],
|
|
2313
|
+
texts=[]
|
|
2314
|
+
)
|
|
2315
|
+
|
|
2316
|
+
|
|
2317
|
+
def _parse_replace_attribute_links(element: ET.Element) -> dict:
|
|
2318
|
+
"""Parse replacement attribute links instruction."""
|
|
2319
|
+
return _parse_element(
|
|
2320
|
+
element,
|
|
2321
|
+
attributes=["AttributeID"],
|
|
2322
|
+
texts=[]
|
|
2323
|
+
)
|
|
2324
|
+
|
|
2325
|
+
|
|
2326
|
+
def _parse_replace_classification_references(element: ET.Element) -> dict:
|
|
2327
|
+
"""Parse replacement classification references instruction."""
|
|
2328
|
+
return _parse_element(element, attributes=[], texts=[])
|
|
2329
|
+
|
|
2330
|
+
|
|
2331
|
+
def _parse_collection_item(element: ET.Element, is_group: bool = False) -> dict:
|
|
2332
|
+
"""Parse collection or collection group element."""
|
|
2333
|
+
result = _parse_element(
|
|
2334
|
+
element,
|
|
2335
|
+
attributes=["ID", "ParentID"] + (["QueryURL"] if not is_group else []),
|
|
2336
|
+
texts=[]
|
|
2337
|
+
)
|
|
2338
|
+
|
|
2339
|
+
# Parse Name elements (multiple)
|
|
2340
|
+
names = []
|
|
2341
|
+
for child in element:
|
|
2342
|
+
local_tag = _get_local_tag(child)
|
|
2343
|
+
if local_tag == "Name":
|
|
2344
|
+
name_text = child.text
|
|
2345
|
+
if name_text:
|
|
2346
|
+
name_dict = {"value": name_text}
|
|
2347
|
+
# Get attributes (Locale, etc.)
|
|
2348
|
+
for attr_name, attr_value in child.attrib.items():
|
|
2349
|
+
local_attr = attr_name
|
|
2350
|
+
if '}' in local_attr:
|
|
2351
|
+
local_attr = local_attr.split('}', 1)[1]
|
|
2352
|
+
name_dict[local_attr] = attr_value
|
|
2353
|
+
names.append(name_dict)
|
|
2354
|
+
|
|
2355
|
+
if names:
|
|
2356
|
+
result["Name"] = names
|
|
2357
|
+
|
|
2358
|
+
# Parse MetaData
|
|
2359
|
+
for child in element:
|
|
2360
|
+
local_tag = _get_local_tag(child)
|
|
2361
|
+
if local_tag == "MetaData":
|
|
2362
|
+
result["MetaData"] = _parse_metadata(child)
|
|
2363
|
+
break
|
|
2364
|
+
|
|
2365
|
+
# For collection groups, parse nested groups and collections
|
|
2366
|
+
if is_group:
|
|
2367
|
+
collection_groups = []
|
|
2368
|
+
collections = []
|
|
2369
|
+
for child in element:
|
|
2370
|
+
local_tag = _get_local_tag(child)
|
|
2371
|
+
if local_tag == "CollectionGroup":
|
|
2372
|
+
collection_groups.append(_parse_collection_item(child, is_group=True))
|
|
2373
|
+
elif local_tag == "Collection":
|
|
2374
|
+
collections.append(_parse_collection_item(child, is_group=False))
|
|
2375
|
+
|
|
2376
|
+
if collection_groups:
|
|
2377
|
+
result["CollectionGroup"] = collection_groups
|
|
2378
|
+
if collections:
|
|
2379
|
+
result["Collection"] = collections
|
|
2380
|
+
|
|
2381
|
+
return result
|
|
2382
|
+
|
|
2383
|
+
|
|
2384
|
+
# Phase 13: Advanced Configuration Elements
|
|
2385
|
+
|
|
2386
|
+
def _parse_bulk_update_configuration(element: ET.Element) -> dict:
|
|
2387
|
+
"""Parse bulk update configuration."""
|
|
2388
|
+
result = _parse_element(element, attributes=["ID"], texts=[])
|
|
2389
|
+
|
|
2390
|
+
# Parse Name elements (multiple)
|
|
2391
|
+
names = []
|
|
2392
|
+
for child in element:
|
|
2393
|
+
local_tag = _get_local_tag(child)
|
|
2394
|
+
if local_tag == "Name":
|
|
2395
|
+
name_text = child.text
|
|
2396
|
+
if name_text:
|
|
2397
|
+
names.append({"value": name_text})
|
|
2398
|
+
|
|
2399
|
+
if names:
|
|
2400
|
+
result["Name"] = names
|
|
2401
|
+
|
|
2402
|
+
# Parse MetaData
|
|
2403
|
+
for child in element:
|
|
2404
|
+
local_tag = _get_local_tag(child)
|
|
2405
|
+
if local_tag == "MetaData":
|
|
2406
|
+
result["MetaData"] = _parse_metadata(child)
|
|
2407
|
+
break
|
|
2408
|
+
|
|
2409
|
+
# Parse ClassificationReference elements (multiple)
|
|
2410
|
+
class_refs = []
|
|
2411
|
+
for child in element:
|
|
2412
|
+
local_tag = _get_local_tag(child)
|
|
2413
|
+
if local_tag == "ClassificationReference":
|
|
2414
|
+
class_refs.append(_parse_classification_reference(child))
|
|
2415
|
+
|
|
2416
|
+
if class_refs:
|
|
2417
|
+
result["ClassificationReference"] = class_refs
|
|
2418
|
+
|
|
2419
|
+
# Parse Configuration element
|
|
2420
|
+
for child in element:
|
|
2421
|
+
local_tag = _get_local_tag(child)
|
|
2422
|
+
if local_tag == "Configuration":
|
|
2423
|
+
result["Configuration"] = _parse_element(child, attributes=[], texts=[])
|
|
2424
|
+
break
|
|
2425
|
+
|
|
2426
|
+
return result
|
|
2427
|
+
|
|
2428
|
+
|
|
2429
|
+
def _parse_derived_event_type(element: ET.Element) -> dict:
|
|
2430
|
+
"""Parse derived event type (simple text)."""
|
|
2431
|
+
event_text = element.text
|
|
2432
|
+
if event_text:
|
|
2433
|
+
return {"value": event_text}
|
|
2434
|
+
return {}
|
|
2435
|
+
|
|
2436
|
+
|
|
2437
|
+
def _parse_import_configuration(element: ET.Element) -> dict:
|
|
2438
|
+
"""Parse import configuration."""
|
|
2439
|
+
result = _parse_element(element, attributes=["ID"], texts=[])
|
|
2440
|
+
|
|
2441
|
+
# Parse Name elements (multiple)
|
|
2442
|
+
names = []
|
|
2443
|
+
for child in element:
|
|
2444
|
+
local_tag = _get_local_tag(child)
|
|
2445
|
+
if local_tag == "Name":
|
|
2446
|
+
name_text = child.text
|
|
2447
|
+
if name_text:
|
|
2448
|
+
names.append({"value": name_text})
|
|
2449
|
+
|
|
2450
|
+
if names:
|
|
2451
|
+
result["Name"] = names
|
|
2452
|
+
|
|
2453
|
+
# Parse MetaData
|
|
2454
|
+
for child in element:
|
|
2455
|
+
local_tag = _get_local_tag(child)
|
|
2456
|
+
if local_tag == "MetaData":
|
|
2457
|
+
result["MetaData"] = _parse_metadata(child)
|
|
2458
|
+
break
|
|
2459
|
+
|
|
2460
|
+
# Parse ClassificationReference elements (multiple)
|
|
2461
|
+
class_refs = []
|
|
2462
|
+
for child in element:
|
|
2463
|
+
local_tag = _get_local_tag(child)
|
|
2464
|
+
if local_tag == "ClassificationReference":
|
|
2465
|
+
class_refs.append(_parse_classification_reference(child))
|
|
2466
|
+
|
|
2467
|
+
if class_refs:
|
|
2468
|
+
result["ClassificationReference"] = class_refs
|
|
2469
|
+
|
|
2470
|
+
# Parse Configuration element
|
|
2471
|
+
for child in element:
|
|
2472
|
+
local_tag = _get_local_tag(child)
|
|
2473
|
+
if local_tag == "Configuration":
|
|
2474
|
+
result["Configuration"] = _parse_element(child, attributes=[], texts=[])
|
|
2475
|
+
break
|
|
2476
|
+
|
|
2477
|
+
return result
|
|
2478
|
+
|
|
2479
|
+
|
|
2480
|
+
def _parse_portal_configuration(element: ET.Element) -> dict:
|
|
2481
|
+
"""Parse portal/web UI configuration."""
|
|
2482
|
+
result = _parse_element(
|
|
2483
|
+
element,
|
|
2484
|
+
attributes=["ID", "Selected", "Referenced", "Changed"],
|
|
2485
|
+
texts=[],
|
|
2486
|
+
conversions={
|
|
2487
|
+
"Selected": (bool, True),
|
|
2488
|
+
"Referenced": (bool, False),
|
|
2489
|
+
"Changed": (bool, False)
|
|
2490
|
+
}
|
|
2491
|
+
)
|
|
2492
|
+
|
|
2493
|
+
# Parse SetupGroupLink
|
|
2494
|
+
for child in element:
|
|
2495
|
+
local_tag = _get_local_tag(child)
|
|
2496
|
+
if local_tag == "SetupGroupLink":
|
|
2497
|
+
result["SetupGroupLink"] = _parse_element(child, attributes=["ID"], texts=[])
|
|
2498
|
+
break
|
|
2499
|
+
|
|
2500
|
+
# Parse Name elements (multiple)
|
|
2501
|
+
names = []
|
|
2502
|
+
for child in element:
|
|
2503
|
+
local_tag = _get_local_tag(child)
|
|
2504
|
+
if local_tag == "Name":
|
|
2505
|
+
name_text = child.text
|
|
2506
|
+
if name_text:
|
|
2507
|
+
names.append({"value": name_text})
|
|
2508
|
+
|
|
2509
|
+
if names:
|
|
2510
|
+
result["Name"] = names
|
|
2511
|
+
|
|
2512
|
+
# Parse MetaData
|
|
2513
|
+
for child in element:
|
|
2514
|
+
local_tag = _get_local_tag(child)
|
|
2515
|
+
if local_tag == "MetaData":
|
|
2516
|
+
result["MetaData"] = _parse_metadata(child)
|
|
2517
|
+
break
|
|
2518
|
+
|
|
2519
|
+
# Parse Configuration element
|
|
2520
|
+
for child in element:
|
|
2521
|
+
local_tag = _get_local_tag(child)
|
|
2522
|
+
if local_tag == "Configuration":
|
|
2523
|
+
result["Configuration"] = _parse_element(child, attributes=[], texts=[])
|
|
2524
|
+
break
|
|
2525
|
+
|
|
2526
|
+
return result
|
|
2527
|
+
|
|
2528
|
+
|
|
2529
|
+
# ===== PHASE 14: Advanced Configuration & System Elements =====
|
|
2530
|
+
|
|
2531
|
+
def _parse_key(element: ET.Element) -> dict:
|
|
2532
|
+
"""Parse key definition (unique identifier configuration)."""
|
|
2533
|
+
result = _parse_element(
|
|
2534
|
+
element,
|
|
2535
|
+
attributes=["ID", "State", "Selected", "Referenced", "Changed", "Deleted"],
|
|
2536
|
+
texts=[],
|
|
2537
|
+
conversions={
|
|
2538
|
+
"Selected": (_str_to_bool, True),
|
|
2539
|
+
"Referenced": (_str_to_bool, False),
|
|
2540
|
+
"Changed": (_str_to_bool, False),
|
|
2541
|
+
"Deleted": (_str_to_bool, False)
|
|
2542
|
+
}
|
|
2543
|
+
)
|
|
2544
|
+
|
|
2545
|
+
# Parse Name elements (multiple)
|
|
2546
|
+
names = []
|
|
2547
|
+
for child in element:
|
|
2548
|
+
local_tag = _get_local_tag(child)
|
|
2549
|
+
if local_tag == "Name":
|
|
2550
|
+
name_text = child.text
|
|
2551
|
+
if name_text:
|
|
2552
|
+
names.append({"value": name_text})
|
|
2553
|
+
if names:
|
|
2554
|
+
result["Name"] = names
|
|
2555
|
+
|
|
2556
|
+
# Parse KeyFormula
|
|
2557
|
+
for child in element:
|
|
2558
|
+
local_tag = _get_local_tag(child)
|
|
2559
|
+
if local_tag == "KeyFormula":
|
|
2560
|
+
result["KeyFormula"] = child.text or ""
|
|
2561
|
+
break
|
|
2562
|
+
|
|
2563
|
+
# Parse MetaData
|
|
2564
|
+
for child in element:
|
|
2565
|
+
local_tag = _get_local_tag(child)
|
|
2566
|
+
if local_tag == "MetaData":
|
|
2567
|
+
result["MetaData"] = _parse_element(child, attributes=[], texts=[])
|
|
2568
|
+
break
|
|
2569
|
+
|
|
2570
|
+
# Parse UserTypeLink (multiple)
|
|
2571
|
+
user_type_links = []
|
|
2572
|
+
for child in element:
|
|
2573
|
+
local_tag = _get_local_tag(child)
|
|
2574
|
+
if local_tag == "UserTypeLink":
|
|
2575
|
+
user_type_links.append(_parse_element(child, attributes=["ID"], texts=[]))
|
|
2576
|
+
if user_type_links:
|
|
2577
|
+
result["UserTypeLink"] = user_type_links
|
|
2578
|
+
|
|
2579
|
+
# Parse AttributeLink (multiple)
|
|
2580
|
+
attribute_links = []
|
|
2581
|
+
for child in element:
|
|
2582
|
+
local_tag = _get_local_tag(child)
|
|
2583
|
+
if local_tag == "AttributeLink":
|
|
2584
|
+
attribute_links.append(_parse_element(child, attributes=["ID"], texts=[]))
|
|
2585
|
+
if attribute_links:
|
|
2586
|
+
result["AttributeLink"] = attribute_links
|
|
2587
|
+
|
|
2588
|
+
return result
|
|
2589
|
+
|
|
2590
|
+
|
|
2591
|
+
def _parse_cross_context(element: ET.Element) -> dict:
|
|
2592
|
+
"""Parse cross-context definition (context wrapper for cross-context exports)."""
|
|
2593
|
+
result = _parse_element(
|
|
2594
|
+
element,
|
|
2595
|
+
attributes=["ID", "Priority", "Locale", "Selected", "Referenced", "Changed", "Deleted"],
|
|
2596
|
+
texts=[],
|
|
2597
|
+
conversions={
|
|
2598
|
+
"Selected": (_str_to_bool, True),
|
|
2599
|
+
"Referenced": (_str_to_bool, False),
|
|
2600
|
+
"Changed": (_str_to_bool, False),
|
|
2601
|
+
"Deleted": (_str_to_bool, False)
|
|
2602
|
+
}
|
|
2603
|
+
)
|
|
2604
|
+
|
|
2605
|
+
# Parse Name elements (multiple)
|
|
2606
|
+
names = []
|
|
2607
|
+
for child in element:
|
|
2608
|
+
local_tag = _get_local_tag(child)
|
|
2609
|
+
if local_tag == "Name":
|
|
2610
|
+
name_text = child.text
|
|
2611
|
+
if name_text:
|
|
2612
|
+
names.append({"value": name_text})
|
|
2613
|
+
if names:
|
|
2614
|
+
result["Name"] = names
|
|
2615
|
+
|
|
2616
|
+
# Parse DimensionPointLink (multiple)
|
|
2617
|
+
dimension_point_links = []
|
|
2618
|
+
for child in element:
|
|
2619
|
+
local_tag = _get_local_tag(child)
|
|
2620
|
+
if local_tag == "DimensionPointLink":
|
|
2621
|
+
dimension_point_links.append(_parse_element(child, attributes=["ID"], texts=[]))
|
|
2622
|
+
if dimension_point_links:
|
|
2623
|
+
result["DimensionPointLink"] = dimension_point_links
|
|
2624
|
+
|
|
2625
|
+
return result
|
|
2626
|
+
|
|
2627
|
+
|
|
2628
|
+
def _parse_business_library(element: ET.Element) -> dict:
|
|
2629
|
+
"""Parse business library (reusable business rule collection)."""
|
|
2630
|
+
result = _parse_element(
|
|
2631
|
+
element,
|
|
2632
|
+
attributes=["ID", "Type", "RunPrivileged", "Scope", "Selected", "Referenced", "Changed", "Deleted"],
|
|
2633
|
+
texts=[],
|
|
2634
|
+
conversions={
|
|
2635
|
+
"RunPrivileged": (_str_to_bool, False),
|
|
2636
|
+
"Selected": (_str_to_bool, True),
|
|
2637
|
+
"Referenced": (_str_to_bool, False),
|
|
2638
|
+
"Changed": (_str_to_bool, False),
|
|
2639
|
+
"Deleted": (_str_to_bool, False)
|
|
2640
|
+
}
|
|
2641
|
+
)
|
|
2642
|
+
|
|
2643
|
+
# Parse SetupGroupLink
|
|
2644
|
+
for child in element:
|
|
2645
|
+
local_tag = _get_local_tag(child)
|
|
2646
|
+
if local_tag == "SetupGroupLink":
|
|
2647
|
+
result["SetupGroupLink"] = _parse_element(child, attributes=["ID"], texts=[])
|
|
2648
|
+
break
|
|
2649
|
+
|
|
2650
|
+
# Parse Name elements (multiple)
|
|
2651
|
+
names = []
|
|
2652
|
+
for child in element:
|
|
2653
|
+
local_tag = _get_local_tag(child)
|
|
2654
|
+
if local_tag == "Name":
|
|
2655
|
+
name_text = child.text
|
|
2656
|
+
if name_text:
|
|
2657
|
+
names.append({"value": name_text})
|
|
2658
|
+
if names:
|
|
2659
|
+
result["Name"] = names
|
|
2660
|
+
|
|
2661
|
+
# Parse Description
|
|
2662
|
+
for child in element:
|
|
2663
|
+
local_tag = _get_local_tag(child)
|
|
2664
|
+
if local_tag == "Description":
|
|
2665
|
+
result["Description"] = child.text or ""
|
|
2666
|
+
break
|
|
2667
|
+
|
|
2668
|
+
# Parse Dependencies
|
|
2669
|
+
dependencies = []
|
|
2670
|
+
for child in element:
|
|
2671
|
+
local_tag = _get_local_tag(child)
|
|
2672
|
+
if local_tag == "Dependencies":
|
|
2673
|
+
dependencies.append(_parse_element(child, attributes=["LibraryID", "LibraryAlias"], texts=[]))
|
|
2674
|
+
if dependencies:
|
|
2675
|
+
result["Dependencies"] = dependencies
|
|
2676
|
+
|
|
2677
|
+
# Parse MetaData
|
|
2678
|
+
for child in element:
|
|
2679
|
+
local_tag = _get_local_tag(child)
|
|
2680
|
+
if local_tag == "MetaData":
|
|
2681
|
+
result["MetaData"] = _parse_element(child, attributes=[], texts=[])
|
|
2682
|
+
break
|
|
2683
|
+
|
|
2684
|
+
return result
|
|
2685
|
+
|
|
2686
|
+
|
|
2687
|
+
def _parse_setup_group(element: ET.Element) -> dict:
|
|
2688
|
+
"""Parse setup group (hierarchical organization of setup components)."""
|
|
2689
|
+
result = _parse_element(
|
|
2690
|
+
element,
|
|
2691
|
+
attributes=["ID", "UserTypeID", "ParentID", "Referenced", "Selected", "Changed"],
|
|
2692
|
+
texts=[],
|
|
2693
|
+
conversions={
|
|
2694
|
+
"Referenced": (_str_to_bool, False),
|
|
2695
|
+
"Selected": (_str_to_bool, True),
|
|
2696
|
+
"Changed": (_str_to_bool, False)
|
|
2697
|
+
}
|
|
2698
|
+
)
|
|
2699
|
+
|
|
2700
|
+
# Parse Name elements (multiple)
|
|
2701
|
+
names = []
|
|
2702
|
+
for child in element:
|
|
2703
|
+
local_tag = _get_local_tag(child)
|
|
2704
|
+
if local_tag == "Name":
|
|
2705
|
+
name_text = child.text
|
|
2706
|
+
if name_text:
|
|
2707
|
+
names.append({"value": name_text})
|
|
2708
|
+
if names:
|
|
2709
|
+
result["Name"] = names
|
|
2710
|
+
|
|
2711
|
+
# Parse MetaData
|
|
2712
|
+
for child in element:
|
|
2713
|
+
local_tag = _get_local_tag(child)
|
|
2714
|
+
if local_tag == "MetaData":
|
|
2715
|
+
result["MetaData"] = _parse_element(child, attributes=[], texts=[])
|
|
2716
|
+
break
|
|
2717
|
+
|
|
2718
|
+
# Parse nested SetupGroup (hierarchical)
|
|
2719
|
+
nested_groups = []
|
|
2720
|
+
for child in element:
|
|
2721
|
+
local_tag = _get_local_tag(child)
|
|
2722
|
+
if local_tag == "SetupGroup":
|
|
2723
|
+
nested_groups.append(_parse_setup_group(child))
|
|
2724
|
+
if nested_groups:
|
|
2725
|
+
result["SetupGroup"] = nested_groups
|
|
2726
|
+
|
|
2727
|
+
return result
|
|
2728
|
+
|
|
2729
|
+
|
|
2730
|
+
def _parse_action_set(element: ET.Element) -> dict:
|
|
2731
|
+
"""Parse action set (collection of actions for privilege control)."""
|
|
2732
|
+
result = _parse_element(
|
|
2733
|
+
element,
|
|
2734
|
+
attributes=["ID", "Type", "Selected", "Changed", "Referenced"],
|
|
2735
|
+
texts=[],
|
|
2736
|
+
conversions={
|
|
2737
|
+
"Selected": (_str_to_bool, True),
|
|
2738
|
+
"Changed": (_str_to_bool, False),
|
|
2739
|
+
"Referenced": (_str_to_bool, False)
|
|
2740
|
+
}
|
|
2741
|
+
)
|
|
2742
|
+
|
|
2743
|
+
# Parse Name elements (multiple)
|
|
2744
|
+
names = []
|
|
2745
|
+
for child in element:
|
|
2746
|
+
local_tag = _get_local_tag(child)
|
|
2747
|
+
if local_tag == "Name":
|
|
2748
|
+
name_text = child.text
|
|
2749
|
+
if name_text:
|
|
2750
|
+
names.append({"value": name_text})
|
|
2751
|
+
if names:
|
|
2752
|
+
result["Name"] = names
|
|
2753
|
+
|
|
2754
|
+
# Parse MetaData
|
|
2755
|
+
for child in element:
|
|
2756
|
+
local_tag = _get_local_tag(child)
|
|
2757
|
+
if local_tag == "MetaData":
|
|
2758
|
+
result["MetaData"] = _parse_element(child, attributes=[], texts=[])
|
|
2759
|
+
break
|
|
2760
|
+
|
|
2761
|
+
# Parse ActionLink (multiple)
|
|
2762
|
+
action_links = []
|
|
2763
|
+
for child in element:
|
|
2764
|
+
local_tag = _get_local_tag(child)
|
|
2765
|
+
if local_tag == "ActionLink":
|
|
2766
|
+
action_links.append(_parse_element(child, attributes=["ID"], texts=[]))
|
|
2767
|
+
if action_links:
|
|
2768
|
+
result["ActionLink"] = action_links
|
|
2769
|
+
|
|
2770
|
+
return result
|
|
2771
|
+
|
|
2772
|
+
|
|
2773
|
+
def _parse_status_flag(element: ET.Element) -> dict:
|
|
2774
|
+
"""Parse status flag (flag definition for status tracking)."""
|
|
2775
|
+
result = _parse_element(
|
|
2776
|
+
element,
|
|
2777
|
+
attributes=["ID", "Selected", "Referenced", "Changed"],
|
|
2778
|
+
texts=[],
|
|
2779
|
+
conversions={
|
|
2780
|
+
"Selected": (_str_to_bool, True),
|
|
2781
|
+
"Referenced": (_str_to_bool, False),
|
|
2782
|
+
"Changed": (_str_to_bool, False)
|
|
2783
|
+
}
|
|
2784
|
+
)
|
|
2785
|
+
|
|
2786
|
+
# Parse SetupGroupLink
|
|
2787
|
+
for child in element:
|
|
2788
|
+
local_tag = _get_local_tag(child)
|
|
2789
|
+
if local_tag == "SetupGroupLink":
|
|
2790
|
+
result["SetupGroupLink"] = _parse_element(child, attributes=["ID"], texts=[])
|
|
2791
|
+
break
|
|
2792
|
+
|
|
2793
|
+
# Parse Name elements (multiple)
|
|
2794
|
+
names = []
|
|
2795
|
+
for child in element:
|
|
2796
|
+
local_tag = _get_local_tag(child)
|
|
2797
|
+
if local_tag == "Name":
|
|
2798
|
+
name_text = child.text
|
|
2799
|
+
if name_text:
|
|
2800
|
+
names.append({"value": name_text})
|
|
2801
|
+
if names:
|
|
2802
|
+
result["Name"] = names
|
|
2803
|
+
|
|
2804
|
+
return result
|
|
2805
|
+
|