osbot-utils 1.89.0__py3-none-any.whl → 1.90.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.
@@ -0,0 +1,7 @@
1
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
2
+
3
+
4
+ class Xml__Attribute(Type_Safe):
5
+ name : str
6
+ value : str
7
+ namespace: str
@@ -0,0 +1,15 @@
1
+ from typing import Dict, List, Union
2
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
3
+ from osbot_utils.helpers.xml.Xml__Attribute import Xml__Attribute
4
+
5
+ class XML__Element(Type_Safe):
6
+ tag : str # Element's local name
7
+ namespace : str # Element's namespace URI
8
+ namespace_prefix: str # Element's namespace prefix
9
+ attributes : Dict[str, Xml__Attribute] # Element attributes
10
+ children : List[Union[str, 'XML__Element']] # Child elements/text
11
+
12
+ # def qualified_name(self) -> str: # Get fully qualified name with prefix
13
+ # if self.namespace_prefix:
14
+ # return f"{self.namespace_prefix}:{self.tag}"
15
+ # return self.tag
@@ -0,0 +1,9 @@
1
+ from typing import Dict
2
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
3
+ from osbot_utils.helpers.xml.Xml__Element import XML__Element
4
+
5
+ class Xml__File(Type_Safe):
6
+ xml_data : str # Raw XML content
7
+ root_element: XML__Element # Parsed root element
8
+ namespaces : Dict[str, str] # XML namespace mappings
9
+
@@ -0,0 +1,88 @@
1
+ from io import StringIO
2
+ from typing import List, Union, Dict
3
+ from xml.etree.ElementTree import iterparse, Element, fromstring, ParseError
4
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
5
+ from osbot_utils.helpers.xml.Xml__Attribute import Xml__Attribute
6
+ from osbot_utils.helpers.xml.Xml__Element import XML__Element
7
+ from osbot_utils.helpers.xml.Xml__File import Xml__File
8
+
9
+
10
+ class Xml__File__Load(Type_Safe):
11
+
12
+ def load_from_string(self, xml_data: str) -> Xml__File: # Create Xml__File from string
13
+ xml_file = Xml__File(xml_data=xml_data)
14
+ self.load_namespaces(xml_file)
15
+ self.parse_xml (xml_file)
16
+ return xml_file
17
+
18
+ def load_namespaces(self, xml_file: Xml__File): # Extract namespaces from XML
19
+ if not xml_file.xml_data:
20
+ raise ValueError("XML data cannot be empty")
21
+
22
+ for event, elem in iterparse(StringIO(xml_file.xml_data), events=("start-ns",)):
23
+ prefix, uri = elem
24
+ xml_file.namespaces[prefix] = uri
25
+
26
+ def parse_xml(self, xml_file: Xml__File): # Parse XML into type-safe structure
27
+ if not xml_file.xml_data:
28
+ raise ValueError("XML data cannot be empty")
29
+
30
+ try:
31
+ root = fromstring(xml_file.xml_data)
32
+ namespaces = xml_file.namespaces
33
+ xml_file.root_element = self.convert_element(namespaces,root)
34
+ except ParseError as error:
35
+ raise ValueError(f"Invalid XML: {str(error)}")
36
+
37
+ def convert_element(self, namespaces: Dict[str,str], element: Element) -> XML__Element:
38
+ attributes = self.convert_attributes(element)
39
+ children: List[Union[str, XML__Element]] = []
40
+
41
+ tag = element.tag
42
+ namespace = ''
43
+ namespace_prefix = ''
44
+
45
+ if '}' in tag:
46
+ namespace, tag = tag.split('}', 1) # Split namespace and tag
47
+ namespace = namespace[1:] # Remove the '{' prefix
48
+
49
+ for prefix, uri in namespaces.items(): # Find prefix for this namespace
50
+ if uri == namespace:
51
+ namespace_prefix = prefix
52
+ break
53
+
54
+ # Handle text content
55
+ if element.text and element.text.strip():
56
+ children.append(element.text.strip())
57
+
58
+ # Process child elements
59
+ for child in element:
60
+ child_element = self.convert_element(namespaces, child)
61
+ children.append(child_element)
62
+
63
+ if child.tail and child.tail.strip():
64
+ children.append(child.tail.strip())
65
+
66
+ return XML__Element(tag=tag,
67
+ namespace=namespace,
68
+ namespace_prefix=namespace_prefix,
69
+ attributes=attributes,
70
+ children=children)
71
+
72
+ def convert_attributes(self, element: Element) -> Dict[str, Xml__Attribute]: # Convert element attributes
73
+ attributes = {}
74
+ for key, value in element.attrib.items():
75
+ if '}' in key: # Handle namespaced attributes
76
+ namespace, name = key.split('}', 1)
77
+ namespace = namespace[1:] # Remove the '{' prefix
78
+ else:
79
+ namespace = ''
80
+ name = key
81
+
82
+ attribute = Xml__Attribute(
83
+ name=name,
84
+ value=value,
85
+ namespace=namespace
86
+ )
87
+ attributes[key] = attribute
88
+ return attributes
@@ -0,0 +1,41 @@
1
+ from typing import Dict, Any
2
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
3
+ from osbot_utils.helpers.xml.Xml__Element import XML__Element
4
+ from osbot_utils.helpers.xml.Xml__File import Xml__File
5
+
6
+
7
+ class Xml__File__To_Dict(Type_Safe):
8
+ def to_dict(self, xml_file: Xml__File) -> Dict[str, Any]: # Convert Xml__File to dictionary
9
+ if not xml_file.root_element:
10
+ return {}
11
+ return self.element_to_dict(xml_file.root_element)
12
+
13
+ def element_to_dict(self, element: XML__Element) -> Dict[str, Any]: # Convert XML__Element to dictionary
14
+ result = {}
15
+
16
+ for key, attr in element.attributes.items(): # Convert attributes first
17
+ result[key] = attr.value
18
+
19
+
20
+ child_nodes: Dict[str, Any] = {} # Process children and collect text content
21
+ text_content = []
22
+ for child in element.children:
23
+ if isinstance(child, str):
24
+ text_content.append(child)
25
+ else:
26
+ if child.tag in child_nodes: # Handle child elements
27
+ if not isinstance(child_nodes[child.tag], list):
28
+ child_nodes[child.tag] = [child_nodes[child.tag]]
29
+ child_nodes[child.tag].append(self.element_to_dict(child))
30
+ else:
31
+ child_nodes[child.tag] = self.element_to_dict(child)
32
+
33
+ if text_content: # Handle text content
34
+ text_value = ' '.join(text_content)
35
+ if child_nodes or result: # If we have attributes or child nodes
36
+ result['#text'] = text_value # Add text as a special key
37
+ else:
38
+ return text_value # Return just the text if no attributes/children
39
+
40
+ result.update(child_nodes) # Merge child nodes into result
41
+ return result
@@ -0,0 +1,72 @@
1
+ from typing import Optional
2
+ from xml.etree.ElementTree import Element, SubElement, tostring
3
+ from xml.dom import minidom
4
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
5
+ from osbot_utils.helpers.xml.Xml__Element import XML__Element
6
+ from osbot_utils.helpers.xml.Xml__File import Xml__File
7
+
8
+ class Xml__File__To_Xml(Type_Safe):
9
+
10
+ def convert_to_xml(self, xml_file: Xml__File, pretty_print: bool = True) -> str:
11
+ if not xml_file.root_element:
12
+ raise ValueError("XML file must have a root element")
13
+
14
+ # Create XML element tree
15
+ root = self.create_element(xml_file.root_element, xml_file.namespaces)
16
+
17
+ # Add all namespace declarations to root element
18
+ for prefix, uri in xml_file.namespaces.items():
19
+ if prefix == '':
20
+ root.set('xmlns', uri)
21
+ else:
22
+ root.set(f'xmlns:{prefix}', uri)
23
+
24
+ # Convert to string
25
+ xml_string = tostring(root, encoding='unicode', method='xml')
26
+
27
+ # Pretty print if requested
28
+ if pretty_print:
29
+ return self.pretty_print(xml_string)
30
+ return xml_string
31
+
32
+ def create_element(self, xml_element: XML__Element, namespaces: Optional[dict] = None, parent: Optional[Element] = None) -> Element:
33
+ # Create tag with namespace if applicable
34
+ tag = xml_element.tag
35
+ if xml_element.namespace_prefix:
36
+ tag = f"{xml_element.namespace_prefix}:{tag}"
37
+ # Don't add namespace URI for default namespace - let it be inherited
38
+
39
+ # Create new element or sub-element
40
+ if parent is not None:
41
+ element = SubElement(parent, tag)
42
+ else:
43
+ element = Element(tag)
44
+
45
+ # Add attributes including namespace declarations
46
+ for attr_key, attr in xml_element.attributes.items():
47
+ if attr.namespace:
48
+ attr_name = f"{{{attr.namespace}}}{attr.name}"
49
+ else:
50
+ attr_name = attr.name
51
+ element.set(attr_name, attr.value)
52
+
53
+ # Process children
54
+ text_parts = []
55
+ for child in xml_element.children:
56
+ if isinstance(child, str):
57
+ text_parts.append(child)
58
+ elif isinstance(child, XML__Element):
59
+ self.create_element(child, namespaces, element)
60
+
61
+ # Set text content if any
62
+ if text_parts:
63
+ element.text = ''.join(text_parts)
64
+
65
+ return element
66
+
67
+ def pretty_print(self, xml_string: str) -> str:
68
+ """Format XML string with proper indentation."""
69
+ parsed = minidom.parseString(xml_string)
70
+ pretty_xml = parsed.toprettyxml(indent=' ').rstrip() + '\n'
71
+ # Remove empty lines (common issue with toprettyxml)
72
+ return '\n'.join(line for line in pretty_xml.split('\n') if line.strip())
File without changes
@@ -0,0 +1,17 @@
1
+ from typing import Any, Dict, List
2
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
3
+ from osbot_utils.helpers.xml.rss.RSS__Image import RSS__Image
4
+ from osbot_utils.helpers.xml.rss.RSS__Item import RSS__Item
5
+
6
+
7
+ class RSS__Channel(Type_Safe):
8
+ description : str
9
+ extensions : Dict[str, Any]
10
+ image : RSS__Image = None
11
+ items : List[RSS__Item]
12
+ language : str
13
+ last_build_date : str
14
+ link : str
15
+ title : str
16
+ update_frequency: str
17
+ update_period : str
@@ -0,0 +1,7 @@
1
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
2
+
3
+
4
+ class RSS__Enclosure(Type_Safe):
5
+ url : str
6
+ type : str
7
+ length : int
@@ -0,0 +1,11 @@
1
+ from typing import Dict, Any
2
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
3
+ from osbot_utils.helpers.xml.rss.RSS__Channel import RSS__Channel
4
+
5
+ DEFAULT__RSS_FEED__VERSION = "2.0"
6
+
7
+ class RSS__Feed(Type_Safe):
8
+ version : str = DEFAULT__RSS_FEED__VERSION
9
+ channel : RSS__Channel = None
10
+ namespaces : Dict[str, str]
11
+ extensions : Dict[str, Any]
@@ -0,0 +1,93 @@
1
+ from typing import Dict, Any
2
+ from osbot_utils.helpers.Guid import Guid
3
+ from osbot_utils.helpers.xml.rss.RSS__Channel import RSS__Channel
4
+ from osbot_utils.helpers.xml.rss.RSS__Feed import RSS__Feed
5
+ from osbot_utils.helpers.xml.rss.RSS__Image import RSS__Image
6
+ from osbot_utils.helpers.xml.rss.RSS__Item import RSS__Item
7
+
8
+
9
+ class RSS__Feed__Parser:
10
+
11
+ def from_dict(self, data: Dict[str, Any]) -> RSS__Feed: # Convert a dictionary (from XML) into an RSS__Feed object
12
+ if 'channel' not in data:
13
+ raise ValueError("Invalid RSS feed: no channel element found")
14
+
15
+ channel_data = data['channel']
16
+ rss_items = []
17
+ items = channel_data.get('item', []) # get raw items data
18
+ if type(items) is not list: # handle case when only one item is present
19
+ items = [items] # by converting it to a list
20
+ for item_data in items: # Process items
21
+
22
+ title = self.element_text(item_data.get('title' ))
23
+ link = self.element_text(item_data.get('link' ))
24
+ description = self.element_text(item_data.get('description'))
25
+ guid = self.extract_guid(item_data.get('guid' ))
26
+ pubDate = self.element_text(item_data.get('pubDate' ))
27
+ creator = self.element_text(item_data.get('creator' ))
28
+ rss_item = RSS__Item(title = title ,
29
+ link = link ,
30
+ description = description ,
31
+ guid = guid ,
32
+ pubDate = pubDate ,
33
+ creator = creator ,
34
+ categories = item_data.get('category' , []),
35
+ content = item_data.get('content' , {}),
36
+ thumbnail = item_data.get('thumbnail' , {}))
37
+
38
+
39
+ known_fields = {'title', 'link', 'description', 'guid', 'pubDate', # Move non-standard elements to extensions
40
+ 'creator', 'category', 'content', 'thumbnail'}
41
+ rss_item.extensions = {k: v for k, v in item_data.items()
42
+ if k not in known_fields}
43
+ rss_items.append(rss_item)
44
+
45
+ # Create channel
46
+ link = self.element_text(channel_data.get('link'))
47
+ channel = RSS__Channel( title = channel_data.get('title' , ''),
48
+ link = link ,
49
+ description = channel_data.get('description' , ''),
50
+ language = channel_data.get('language' , ''),
51
+ last_build_date = channel_data.get('lastBuildDate' ),
52
+ items = rss_items ,
53
+ update_frequency = channel_data.get('updateFrequency', ''),
54
+ update_period = channel_data.get('updatePeriod' , ''))
55
+
56
+
57
+ # Process channel image if present
58
+ if 'image' in channel_data:
59
+ img_data = channel_data['image']
60
+ channel.image = RSS__Image( url = img_data.get('url' , '' ),
61
+ title = img_data.get('title' , '' ),
62
+ link = img_data.get('link' , '' ),
63
+ width = int(img_data.get('width' , 0 )),
64
+ height = int(img_data.get('height', 0 )))
65
+
66
+ known_channel_fields = {'title', 'link', 'description', 'language', # Move non-standard channel elements to extensions
67
+ 'lastBuildDate', 'image', 'item', 'updateFrequency', 'updatePeriod'}
68
+ channel.extensions = {k: v for k, v in channel_data.items()
69
+ if k not in known_channel_fields}
70
+
71
+ rss_feed = RSS__Feed(channel = channel)
72
+ return rss_feed
73
+
74
+ def extract_guid(self, target):
75
+ return Guid(self.extract_text(target))
76
+
77
+ def element_text(self, target):
78
+ if isinstance(target, list):
79
+ for item in target:
80
+ value = self.extract_text(item)
81
+ if value:
82
+ return value
83
+ value = self.extract_text(target)
84
+ return value or f'{target}'
85
+
86
+ def extract_text(self, target):
87
+ if not target:
88
+ return ''
89
+ if isinstance(target, str):
90
+ return target
91
+ if isinstance(target, dict):
92
+ return target.get('#text', '')
93
+ return ''
@@ -0,0 +1,8 @@
1
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
2
+
3
+ class RSS__Image(Type_Safe):
4
+ url : str
5
+ title : str
6
+ link : str
7
+ width : int
8
+ height : int
@@ -0,0 +1,17 @@
1
+ from typing import Dict, List, Any
2
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
3
+ from osbot_utils.helpers.Guid import Guid
4
+ from osbot_utils.helpers.xml.rss.RSS__Enclosure import RSS__Enclosure
5
+
6
+ class RSS__Item(Type_Safe):
7
+ title : str
8
+ link : str
9
+ description : str
10
+ guid : Guid
11
+ pubDate : str
12
+ creator : str
13
+ categories : List[str]
14
+ enclosure : RSS__Enclosure = None
15
+ content : Dict[str, Any]
16
+ thumbnail : Dict[str, Any]
17
+ extensions : Dict[str, Any]
osbot_utils/version CHANGED
@@ -1 +1 @@
1
- v1.89.0
1
+ v1.90.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: osbot_utils
3
- Version: 1.89.0
3
+ Version: 1.90.0
4
4
  Summary: OWASP Security Bot - Utils
5
5
  Home-page: https://github.com/owasp-sbot/OSBot-Utils
6
6
  License: MIT
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
23
23
 
24
24
  Powerful Python util methods and classes that simplify common apis and tasks.
25
25
 
26
- ![Current Release](https://img.shields.io/badge/release-v1.89.0-blue)
26
+ ![Current Release](https://img.shields.io/badge/release-v1.90.0-blue)
27
27
  [![codecov](https://codecov.io/gh/owasp-sbot/OSBot-Utils/graph/badge.svg?token=GNVW0COX1N)](https://codecov.io/gh/owasp-sbot/OSBot-Utils)
28
28
 
29
29
 
@@ -77,7 +77,6 @@ osbot_utils/helpers/Str_ASCII.py,sha256=PRqyu449XnKrLn6b9Miii1Hv-GO5OAa1UhhgvlRc
77
77
  osbot_utils/helpers/Timestamp_Now.py,sha256=k3-SUGYx2jLTXvgZYeECqPRJhVxqWPmW7co1l6r12jk,438
78
78
  osbot_utils/helpers/Type_Registry.py,sha256=Ajk3SyMSKDi2g9SJYUtTgg7PZkAgydaHcpbGuEN3S94,311
79
79
  osbot_utils/helpers/Type_Safe_Method.py,sha256=8E88of__An9_ZhJKz6Kp22C1mb9WLED0jWNLOII3fJs,10489
80
- osbot_utils/helpers/Xml_To_Dict.py,sha256=EbuiLi1cElakYyueSKi6LKJXUDz4-oi4QTFI1WTu7Rs,2846
81
80
  osbot_utils/helpers/Zip_Bytes.py,sha256=KB5zWfCf6ET4alNfqNrSp5DxZ3Jp9oDHpc6tK2iO_qg,4320
82
81
  osbot_utils/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
82
  osbot_utils/helpers/ast/Ast.py,sha256=lcPQOSxXI6zgmMnIVF9WM6ISqViWX-sq4d_UC0CDG8s,1155
@@ -270,6 +269,19 @@ osbot_utils/helpers/trace/Trace_Call__Stats.py,sha256=gmiotIrOXe2ssxodzQQ56t8eGT
270
269
  osbot_utils/helpers/trace/Trace_Call__View_Model.py,sha256=a40nn6agCEMd2ecsJ93n8vXij0omh0D69QilqwmN_ao,4545
271
270
  osbot_utils/helpers/trace/Trace_Files.py,sha256=SNpAmuBlSUS9NyVocgZ5vevzqVaIqoh622yZge3a53A,978
272
271
  osbot_utils/helpers/trace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
272
+ osbot_utils/helpers/xml/Xml__Attribute.py,sha256=r29x8ehRug27KuHQKQEGl6Thz_MNaNHy6X3cPRgH4ho,148
273
+ osbot_utils/helpers/xml/Xml__Element.py,sha256=J0hMaJPIBNmZf4wo0xrtZBZd5vWie5FoX9XqtAXQToM,871
274
+ osbot_utils/helpers/xml/Xml__File.py,sha256=c3axXx07rIoK5tGzpLwH-X1B7nusSe1-SZAxJNZJ0KI,420
275
+ osbot_utils/helpers/xml/Xml__File__Load.py,sha256=iAH21HjrqFoZlU5-Kg2RE53RBdNs0mDaASETUTnbhRo,3625
276
+ osbot_utils/helpers/xml/Xml__File__To_Dict.py,sha256=1KGswrpMpUPu4JDYUbkP8GTOQfHGcGONnDYsi-_aqv0,2061
277
+ osbot_utils/helpers/xml/Xml__File__To_Xml.py,sha256=HvUbYOfLz6i160_to90dP3b1uy3Z85nnj7qaGN8LEBI,2884
278
+ osbot_utils/helpers/xml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
279
+ osbot_utils/helpers/xml/rss/RSS__Channel.py,sha256=HPLsGRNIaPh0_8GYE2a53bSV5Bb4E6j6tKGuy4bxg4Y,605
280
+ osbot_utils/helpers/xml/rss/RSS__Enclosure.py,sha256=f75U7nXXJ7hLs2zPDui0WsFAmMJaeaverjlxD4M-Otg,142
281
+ osbot_utils/helpers/xml/rss/RSS__Feed.py,sha256=lhFoeBMWdH1Dp8QnagCGj9bfZHKmB_HkE56hPVZNaM0,425
282
+ osbot_utils/helpers/xml/rss/RSS__Feed__Parser.py,sha256=qG4FbUexMmU6_pTgcxDBmBeZvh9d1dmFZGzQOwVlggk,5088
283
+ osbot_utils/helpers/xml/rss/RSS__Image.py,sha256=4uI0jd17pqb8FJ8HQcERXvn3WjGbiOVI8u1tv-IN59U,171
284
+ osbot_utils/helpers/xml/rss/RSS__Item.py,sha256=y-QI2WBfd9FEsVWc_eNirvZUUslpb2z27hxcm-RVHJQ,611
273
285
  osbot_utils/testing/Catch.py,sha256=HdNoKnrPBjvVj87XYN-Wa1zpo5z3oByURT6TKbd5QpQ,2229
274
286
  osbot_utils/testing/Custom_Handler_For_Http_Tests.py,sha256=LKscFEcuwTQQ9xl4q71PR5FA8U-q8OtfTkCJoIgQIoQ,5358
275
287
  osbot_utils/testing/Duration.py,sha256=iBrczAuw6j3jXtG7ZPraT0PXbCILEcCplJbqei96deA,2217
@@ -318,8 +330,8 @@ osbot_utils/utils/Toml.py,sha256=Rxl8gx7mni5CvBAK-Ai02EKw-GwtJdd3yeHT2kMloik,166
318
330
  osbot_utils/utils/Version.py,sha256=Ww6ChwTxqp1QAcxOnztkTicShlcx6fbNsWX5xausHrg,422
319
331
  osbot_utils/utils/Zip.py,sha256=pR6sKliUY0KZXmqNzKY2frfW-YVQEVbLKiyqQX_lc-8,14052
320
332
  osbot_utils/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
321
- osbot_utils/version,sha256=ty30vvmdD1qw-8a6mVUfcEOT_nStazbsrdXwAHaom5k,8
322
- osbot_utils-1.89.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
323
- osbot_utils-1.89.0.dist-info/METADATA,sha256=fqPYnTXYJMDtR982ODsggiUxzZHnnLBFCaBpnOMtpKw,1317
324
- osbot_utils-1.89.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
325
- osbot_utils-1.89.0.dist-info/RECORD,,
333
+ osbot_utils/version,sha256=BspJJ07wPk_cd1Tk06A-p8nFKYNDb_kD0KykvCZJpMc,8
334
+ osbot_utils-1.90.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
335
+ osbot_utils-1.90.0.dist-info/METADATA,sha256=0FRdshsrS_RyXSve5jzGaHU5JXGt9_X6gDE8-N9frH0,1317
336
+ osbot_utils-1.90.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
337
+ osbot_utils-1.90.0.dist-info/RECORD,,
@@ -1,87 +0,0 @@
1
- from typing import Dict, Any, Union
2
- from xml.etree.ElementTree import Element
3
-
4
- from osbot_utils.base_classes.Type_Safe import Type_Safe
5
-
6
- class XML_Attribute(Type_Safe):
7
- name : str
8
- value : str
9
- namespace: str
10
-
11
- class XML_Element(Type_Safe):
12
- attributes: Dict[str, XML_Attribute]
13
- children : Dict[str, Union[str, 'XML_Element']]
14
-
15
-
16
- class Xml_To_Dict(Type_Safe):
17
- xml_data : str = None # Input XML string
18
- root : Element = None # Root ElementTree.Element
19
- namespaces : Dict[str, str] # XML namespaces
20
- xml_dict : Dict[str, Any] # Parsed XML as dictionary
21
-
22
- def setup(self) :
23
- from xml.etree.ElementTree import ParseError
24
- try:
25
- self.load_namespaces()
26
- self.load_root()
27
-
28
- except ParseError as e:
29
- raise ValueError(f"Invalid XML: {str(e)}")
30
- return self
31
-
32
-
33
- def load_namespaces(self):
34
- from xml.etree.ElementTree import iterparse
35
- from io import StringIO
36
-
37
- for event, elem in iterparse(StringIO(self.xml_data), events=("start-ns",)):
38
- self.namespaces[elem[0]] = elem[1]
39
-
40
- def load_root(self):
41
- from xml.etree.ElementTree import fromstring
42
-
43
- self.root = fromstring(self.xml_data)
44
-
45
- def element_to_dict(self, element: Element) -> Union[Dict[str, Any], str]:
46
- """Convert an ElementTree.Element to a dictionary"""
47
- result: Dict[str, Any] = {}
48
-
49
-
50
- if element.attrib: # Handle attributes
51
- result.update(element.attrib)
52
-
53
- # Handle child elements
54
- child_nodes: Dict[str, Any] = {}
55
- for child in element:
56
- tag = child.tag # Remove namespace prefix if present
57
- if '}' in tag:
58
- tag = tag.split('}', 1)[1]
59
-
60
- child_data = self.element_to_dict(child)
61
-
62
- if tag in child_nodes:
63
- if not isinstance(child_nodes[tag], list):
64
- child_nodes[tag] = [child_nodes[tag]]
65
- child_nodes[tag].append(child_data)
66
- else:
67
- child_nodes[tag] = child_data
68
-
69
- # Handle text content
70
- text = element.text.strip() if element.text else ''
71
- if text:
72
- if child_nodes or result:
73
- result['_text'] = text
74
- else:
75
- return text
76
- elif not child_nodes and not result: # Make sure we return text content even for empty nodes
77
- return text
78
-
79
- # Combine results
80
- if child_nodes:
81
- result.update(child_nodes)
82
-
83
- return result
84
-
85
- def parse(self) -> Dict[str, Any]: # Convert parsed XML to dictionary
86
- self.xml_dict = self.element_to_dict(self.root)
87
- return self