pcf-toolkit 0.2.5__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.
@@ -0,0 +1,548 @@
1
+ """Import ControlManifest.Input.xml into manifest data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ import xml.etree.ElementTree as ET
7
+ from typing import Any
8
+
9
+
10
+ def parse_manifest_xml_path(path: str) -> dict[str, Any]:
11
+ """Parses a manifest XML file into a dict suitable for Manifest validation.
12
+
13
+ Args:
14
+ path: Path to the XML file, or '-' to read from stdin.
15
+
16
+ Returns:
17
+ Dictionary representation of the manifest data.
18
+
19
+ Raises:
20
+ ValueError: If the XML structure is invalid.
21
+ """
22
+ if path == "-":
23
+ xml_text = sys.stdin.read()
24
+ return parse_manifest_xml_text(xml_text)
25
+ tree = ET.parse(path)
26
+ return parse_manifest_xml_element(tree.getroot())
27
+
28
+
29
+ def parse_manifest_xml_text(xml_text: str) -> dict[str, Any]:
30
+ """Parses a manifest XML string into a dict suitable for Manifest validation.
31
+
32
+ Args:
33
+ xml_text: XML string content to parse.
34
+
35
+ Returns:
36
+ Dictionary representation of the manifest data.
37
+
38
+ Raises:
39
+ ValueError: If the XML structure is invalid.
40
+ """
41
+ root = ET.fromstring(xml_text)
42
+ return parse_manifest_xml_element(root)
43
+
44
+
45
+ def parse_manifest_xml_element(root: ET.Element) -> dict[str, Any]:
46
+ """Parses the manifest XML root element into a dict.
47
+
48
+ Args:
49
+ root: Root XML element (should be <manifest>).
50
+
51
+ Returns:
52
+ Dictionary representation of the manifest data.
53
+
54
+ Raises:
55
+ ValueError: If the root element is not <manifest> or missing <control>.
56
+ """
57
+ if _strip_ns(root.tag) != "manifest":
58
+ raise ValueError("Root element must be <manifest>.")
59
+
60
+ control_el = _first_child(root, "control")
61
+ if control_el is None:
62
+ raise ValueError("Manifest must contain a <control> element.")
63
+
64
+ control = _parse_control(control_el)
65
+ return {"control": control}
66
+
67
+
68
+ def _parse_control(control_el: ET.Element) -> dict[str, Any]:
69
+ """Parses a control XML element into a dictionary.
70
+
71
+ Args:
72
+ control_el: XML element representing a control.
73
+
74
+ Returns:
75
+ Dictionary containing control data.
76
+ """
77
+ data: dict[str, Any] = {}
78
+ _set_attr(data, control_el, "namespace")
79
+ _set_attr(data, control_el, "constructor")
80
+ _set_attr(data, control_el, "version")
81
+ _set_attr(data, control_el, "display-name-key")
82
+ _set_attr(data, control_el, "description-key")
83
+ _set_attr(data, control_el, "control-type")
84
+ _set_attr(data, control_el, "preview-image")
85
+
86
+ data["property"] = [_parse_property(el) for el in _children(control_el, "property")]
87
+ data["event"] = [_parse_event(el) for el in _children(control_el, "event")]
88
+ data["data-set"] = [_parse_dataset(el) for el in _children(control_el, "data-set")]
89
+ data["type-group"] = [_parse_type_group(el) for el in _children(control_el, "type-group")]
90
+
91
+ prop_deps = _first_child(control_el, "property-dependencies")
92
+ if prop_deps is not None:
93
+ data["property-dependencies"] = _parse_property_dependencies(prop_deps)
94
+
95
+ feature_usage = _first_child(control_el, "feature-usage")
96
+ if feature_usage is not None:
97
+ data["feature-usage"] = _parse_feature_usage(feature_usage)
98
+
99
+ external_usage = _first_child(control_el, "external-service-usage")
100
+ if external_usage is not None:
101
+ data["external-service-usage"] = _parse_external_service_usage(external_usage)
102
+
103
+ platform_action = _first_child(control_el, "platform-action")
104
+ if platform_action is not None:
105
+ data["platform-action"] = _parse_platform_action(platform_action)
106
+
107
+ resources = _first_child(control_el, "resources")
108
+ if resources is not None:
109
+ data["resources"] = _parse_resources(resources)
110
+
111
+ return data
112
+
113
+
114
+ def _parse_property(property_el: ET.Element) -> dict[str, Any]:
115
+ """Parses a property XML element into a dictionary.
116
+
117
+ Args:
118
+ property_el: XML element representing a property.
119
+
120
+ Returns:
121
+ Dictionary containing property data.
122
+ """
123
+ data: dict[str, Any] = {}
124
+ for attr in (
125
+ "name",
126
+ "display-name-key",
127
+ "description-key",
128
+ "of-type",
129
+ "of-type-group",
130
+ "usage",
131
+ "default-value",
132
+ "pfx-default-value",
133
+ ):
134
+ _set_attr(data, property_el, attr)
135
+ _set_bool_attr(data, property_el, "required")
136
+
137
+ types_el = _first_child(property_el, "types")
138
+ if types_el is not None:
139
+ data["types"] = _parse_types(types_el)
140
+
141
+ values = [_parse_value(el) for el in _children(property_el, "value")]
142
+ if values:
143
+ data["value"] = values
144
+
145
+ return data
146
+
147
+
148
+ def _parse_value(value_el: ET.Element) -> dict[str, Any]:
149
+ """Parses a value XML element (enum value) into a dictionary.
150
+
151
+ Args:
152
+ value_el: XML element representing an enum value.
153
+
154
+ Returns:
155
+ Dictionary containing value data with name, display-name-key, and value.
156
+ """
157
+ data: dict[str, Any] = {}
158
+ _set_attr(data, value_el, "name")
159
+ _set_attr(data, value_el, "display-name-key")
160
+ text = (value_el.text or "").strip()
161
+ if text:
162
+ try:
163
+ data["value"] = int(text)
164
+ except ValueError:
165
+ data["value"] = text
166
+ return data
167
+
168
+
169
+ def _parse_event(event_el: ET.Element) -> dict[str, Any]:
170
+ """Parses an event XML element into a dictionary.
171
+
172
+ Args:
173
+ event_el: XML element representing an event.
174
+
175
+ Returns:
176
+ Dictionary containing event data.
177
+ """
178
+ data: dict[str, Any] = {}
179
+ for attr in (
180
+ "name",
181
+ "display-name-key",
182
+ "description-key",
183
+ "pfx-default-value",
184
+ ):
185
+ _set_attr(data, event_el, attr)
186
+ return data
187
+
188
+
189
+ def _parse_dataset(dataset_el: ET.Element) -> dict[str, Any]:
190
+ """Parses a data-set XML element into a dictionary.
191
+
192
+ Args:
193
+ dataset_el: XML element representing a data set.
194
+
195
+ Returns:
196
+ Dictionary containing data set data.
197
+ """
198
+ data: dict[str, Any] = {}
199
+ for attr in (
200
+ "name",
201
+ "display-name-key",
202
+ "description-key",
203
+ "cds-data-set-options",
204
+ ):
205
+ _set_attr(data, dataset_el, attr)
206
+ data["property-set"] = [_parse_property_set(el) for el in _children(dataset_el, "property-set")]
207
+ return data
208
+
209
+
210
+ def _parse_property_set(prop_set_el: ET.Element) -> dict[str, Any]:
211
+ """Parses a property-set XML element into a dictionary.
212
+
213
+ Args:
214
+ prop_set_el: XML element representing a property set.
215
+
216
+ Returns:
217
+ Dictionary containing property set data.
218
+ """
219
+ data: dict[str, Any] = {}
220
+ for attr in (
221
+ "name",
222
+ "display-name-key",
223
+ "description-key",
224
+ "of-type",
225
+ "of-type-group",
226
+ "usage",
227
+ ):
228
+ _set_attr(data, prop_set_el, attr)
229
+ _set_bool_attr(data, prop_set_el, "required")
230
+ types_el = _first_child(prop_set_el, "types")
231
+ if types_el is not None:
232
+ data["types"] = _parse_types(types_el)
233
+ return data
234
+
235
+
236
+ def _parse_types(types_el: ET.Element) -> dict[str, Any]:
237
+ """Parses a types XML element into a dictionary.
238
+
239
+ Args:
240
+ types_el: XML element containing type children.
241
+
242
+ Returns:
243
+ Dictionary with 'type' key containing list of type dictionaries.
244
+ """
245
+ types = []
246
+ for type_el in _children(types_el, "type"):
247
+ value = (type_el.text or "").strip()
248
+ if value:
249
+ types.append({"value": value})
250
+ return {"type": types}
251
+
252
+
253
+ def _parse_type_group(type_group_el: ET.Element) -> dict[str, Any]:
254
+ """Parses a type-group XML element into a dictionary.
255
+
256
+ Args:
257
+ type_group_el: XML element representing a type group.
258
+
259
+ Returns:
260
+ Dictionary containing type group data.
261
+ """
262
+ data: dict[str, Any] = {}
263
+ _set_attr(data, type_group_el, "name")
264
+ types = []
265
+ for type_el in _children(type_group_el, "type"):
266
+ value = (type_el.text or "").strip()
267
+ if value:
268
+ types.append({"value": value})
269
+ data["type"] = types
270
+ return data
271
+
272
+
273
+ def _parse_property_dependencies(deps_el: ET.Element) -> dict[str, Any]:
274
+ """Parses property-dependencies XML element into a dictionary.
275
+
276
+ Args:
277
+ deps_el: XML element containing property-dependency children.
278
+
279
+ Returns:
280
+ Dictionary with 'property-dependency' key containing list of dependencies.
281
+ """
282
+ deps = []
283
+ for dep_el in _children(deps_el, "property-dependency"):
284
+ item: dict[str, Any] = {}
285
+ for attr in ("input", "output", "required-for"):
286
+ _set_attr(item, dep_el, attr)
287
+ deps.append(item)
288
+ return {"property-dependency": deps}
289
+
290
+
291
+ def _parse_feature_usage(feature_el: ET.Element) -> dict[str, Any]:
292
+ """Parses feature-usage XML element into a dictionary.
293
+
294
+ Args:
295
+ feature_el: XML element containing uses-feature children.
296
+
297
+ Returns:
298
+ Dictionary with 'uses-feature' key containing list of features.
299
+ """
300
+ uses_features = []
301
+ for use_el in _children(feature_el, "uses-feature"):
302
+ item: dict[str, Any] = {}
303
+ _set_attr(item, use_el, "name")
304
+ _set_bool_attr(item, use_el, "required")
305
+ uses_features.append(item)
306
+ return {"uses-feature": uses_features}
307
+
308
+
309
+ def _parse_external_service_usage(usage_el: ET.Element) -> dict[str, Any]:
310
+ """Parses external-service-usage XML element into a dictionary.
311
+
312
+ Args:
313
+ usage_el: XML element representing external service usage.
314
+
315
+ Returns:
316
+ Dictionary containing external service usage data.
317
+ """
318
+ data: dict[str, Any] = {}
319
+ _set_bool_attr(data, usage_el, "enabled")
320
+ domains = []
321
+ for dom_el in _children(usage_el, "domain"):
322
+ text = (dom_el.text or "").strip()
323
+ if text:
324
+ domains.append({"value": text})
325
+ if domains:
326
+ data["domain"] = domains
327
+ return data
328
+
329
+
330
+ def _parse_platform_action(action_el: ET.Element) -> dict[str, Any]:
331
+ """Parses platform-action XML element into a dictionary.
332
+
333
+ Args:
334
+ action_el: XML element representing a platform action.
335
+
336
+ Returns:
337
+ Dictionary containing platform action data.
338
+ """
339
+ data: dict[str, Any] = {}
340
+ _set_attr(data, action_el, "action-type")
341
+ return data
342
+
343
+
344
+ def _parse_resources(resources_el: ET.Element) -> dict[str, Any]:
345
+ """Parses resources XML element into a dictionary.
346
+
347
+ Args:
348
+ resources_el: XML element containing resource children.
349
+
350
+ Returns:
351
+ Dictionary containing all resource data (code, css, img, resx, etc.).
352
+ """
353
+ data: dict[str, Any] = {}
354
+ code_el = _first_child(resources_el, "code")
355
+ if code_el is not None:
356
+ data["code"] = _parse_code(code_el)
357
+
358
+ data["css"] = [_parse_css(el) for el in _children(resources_el, "css")]
359
+ data["img"] = [_parse_img(el) for el in _children(resources_el, "img")]
360
+ data["resx"] = [_parse_resx(el) for el in _children(resources_el, "resx")]
361
+ data["platform-library"] = [_parse_platform_library(el) for el in _children(resources_el, "platform-library")]
362
+ data["dependency"] = [_parse_dependency(el) for el in _children(resources_el, "dependency")]
363
+ return data
364
+
365
+
366
+ def _parse_code(code_el: ET.Element) -> dict[str, Any]:
367
+ """Parses a code XML element into a dictionary.
368
+
369
+ Args:
370
+ code_el: XML element representing a code resource.
371
+
372
+ Returns:
373
+ Dictionary containing code resource data (path, order).
374
+ """
375
+ data: dict[str, Any] = {}
376
+ _set_attr(data, code_el, "path")
377
+ _set_int_attr(data, code_el, "order")
378
+ return data
379
+
380
+
381
+ def _parse_css(css_el: ET.Element) -> dict[str, Any]:
382
+ """Parses a css XML element into a dictionary.
383
+
384
+ Args:
385
+ css_el: XML element representing a CSS resource.
386
+
387
+ Returns:
388
+ Dictionary containing CSS resource data (path, order).
389
+ """
390
+ data: dict[str, Any] = {}
391
+ _set_attr(data, css_el, "path")
392
+ _set_int_attr(data, css_el, "order")
393
+ return data
394
+
395
+
396
+ def _parse_img(img_el: ET.Element) -> dict[str, Any]:
397
+ """Parses an img XML element into a dictionary.
398
+
399
+ Args:
400
+ img_el: XML element representing an image resource.
401
+
402
+ Returns:
403
+ Dictionary containing image resource data (path).
404
+ """
405
+ data: dict[str, Any] = {}
406
+ _set_attr(data, img_el, "path")
407
+ return data
408
+
409
+
410
+ def _parse_resx(resx_el: ET.Element) -> dict[str, Any]:
411
+ """Parses a resx XML element into a dictionary.
412
+
413
+ Args:
414
+ resx_el: XML element representing a resx resource.
415
+
416
+ Returns:
417
+ Dictionary containing resx resource data (path, version).
418
+ """
419
+ data: dict[str, Any] = {}
420
+ _set_attr(data, resx_el, "path")
421
+ _set_attr(data, resx_el, "version")
422
+ return data
423
+
424
+
425
+ def _parse_platform_library(lib_el: ET.Element) -> dict[str, Any]:
426
+ """Parses a platform-library XML element into a dictionary.
427
+
428
+ Args:
429
+ lib_el: XML element representing a platform library.
430
+
431
+ Returns:
432
+ Dictionary containing platform library data (name, version).
433
+ """
434
+ data: dict[str, Any] = {}
435
+ _set_attr(data, lib_el, "name")
436
+ _set_attr(data, lib_el, "version")
437
+ return data
438
+
439
+
440
+ def _parse_dependency(dep_el: ET.Element) -> dict[str, Any]:
441
+ """Parses a dependency XML element into a dictionary.
442
+
443
+ Args:
444
+ dep_el: XML element representing a dependency.
445
+
446
+ Returns:
447
+ Dictionary containing dependency data (type, name, order, load-type).
448
+ """
449
+ data: dict[str, Any] = {}
450
+ _set_attr(data, dep_el, "type")
451
+ _set_attr(data, dep_el, "name")
452
+ _set_int_attr(data, dep_el, "order")
453
+ _set_attr(data, dep_el, "load-type")
454
+ return data
455
+
456
+
457
+ def _strip_ns(tag: str) -> str:
458
+ """Strips XML namespace prefix from a tag name.
459
+
460
+ Args:
461
+ tag: Tag name potentially with namespace prefix (e.g., "{ns}tag").
462
+
463
+ Returns:
464
+ Tag name without namespace prefix.
465
+ """
466
+ if "}" in tag:
467
+ return tag.split("}", 1)[1]
468
+ return tag
469
+
470
+
471
+ def _children(parent: ET.Element, tag: str) -> list[ET.Element]:
472
+ """Finds all child elements with a given tag name.
473
+
474
+ Args:
475
+ parent: Parent XML element.
476
+ tag: Tag name to search for (namespace prefixes are ignored).
477
+
478
+ Returns:
479
+ List of matching child elements.
480
+ """
481
+ return [child for child in parent if _strip_ns(child.tag) == tag]
482
+
483
+
484
+ def _first_child(parent: ET.Element, tag: str) -> ET.Element | None:
485
+ """Finds the first child element with a given tag name.
486
+
487
+ Args:
488
+ parent: Parent XML element.
489
+ tag: Tag name to search for (namespace prefixes are ignored).
490
+
491
+ Returns:
492
+ First matching child element, or None if not found.
493
+ """
494
+ for child in parent:
495
+ if _strip_ns(child.tag) == tag:
496
+ return child
497
+ return None
498
+
499
+
500
+ def _set_attr(target: dict[str, Any], element: ET.Element, attr: str) -> None:
501
+ """Sets an attribute from XML element to target dict if present and non-empty.
502
+
503
+ Args:
504
+ target: Dictionary to update.
505
+ element: XML element to read attribute from.
506
+ attr: Attribute name to read.
507
+ """
508
+ if attr in element.attrib:
509
+ value = element.attrib[attr]
510
+ if value != "":
511
+ target[attr] = value
512
+
513
+
514
+ def _set_bool_attr(target: dict[str, Any], element: ET.Element, attr: str) -> None:
515
+ """Sets a boolean attribute from XML element to target dict.
516
+
517
+ Converts "true"/"false" strings to boolean values.
518
+
519
+ Args:
520
+ target: Dictionary to update.
521
+ element: XML element to read attribute from.
522
+ attr: Attribute name to read.
523
+ """
524
+ if attr in element.attrib:
525
+ value = element.attrib[attr].strip().lower()
526
+ if value in {"true", "false"}:
527
+ target[attr] = value == "true"
528
+
529
+
530
+ def _set_int_attr(target: dict[str, Any], element: ET.Element, attr: str) -> None:
531
+ """Sets an integer attribute from XML element to target dict.
532
+
533
+ Attempts to parse the attribute value as an integer. Falls back to string
534
+ if parsing fails.
535
+
536
+ Args:
537
+ target: Dictionary to update.
538
+ element: XML element to read attribute from.
539
+ attr: Attribute name to read.
540
+ """
541
+ if attr in element.attrib:
542
+ raw = element.attrib[attr].strip()
543
+ if raw == "":
544
+ return
545
+ try:
546
+ target[attr] = int(raw)
547
+ except ValueError:
548
+ target[attr] = raw