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.
- osbot_utils/helpers/xml/Xml__Attribute.py +7 -0
- osbot_utils/helpers/xml/Xml__Element.py +15 -0
- osbot_utils/helpers/xml/Xml__File.py +9 -0
- osbot_utils/helpers/xml/Xml__File__Load.py +88 -0
- osbot_utils/helpers/xml/Xml__File__To_Dict.py +41 -0
- osbot_utils/helpers/xml/Xml__File__To_Xml.py +72 -0
- osbot_utils/helpers/xml/__init__.py +0 -0
- osbot_utils/helpers/xml/rss/RSS__Channel.py +17 -0
- osbot_utils/helpers/xml/rss/RSS__Enclosure.py +7 -0
- osbot_utils/helpers/xml/rss/RSS__Feed.py +11 -0
- osbot_utils/helpers/xml/rss/RSS__Feed__Parser.py +93 -0
- osbot_utils/helpers/xml/rss/RSS__Image.py +8 -0
- osbot_utils/helpers/xml/rss/RSS__Item.py +17 -0
- osbot_utils/version +1 -1
- {osbot_utils-1.89.0.dist-info → osbot_utils-1.90.0.dist-info}/METADATA +2 -2
- {osbot_utils-1.89.0.dist-info → osbot_utils-1.90.0.dist-info}/RECORD +18 -6
- osbot_utils/helpers/Xml_To_Dict.py +0 -87
- {osbot_utils-1.89.0.dist-info → osbot_utils-1.90.0.dist-info}/LICENSE +0 -0
- {osbot_utils-1.89.0.dist-info → osbot_utils-1.90.0.dist-info}/WHEEL +0 -0
@@ -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,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,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.
|
1
|
+
v1.90.0
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: osbot_utils
|
3
|
-
Version: 1.
|
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
|
-

|
27
27
|
[](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=
|
322
|
-
osbot_utils-1.
|
323
|
-
osbot_utils-1.
|
324
|
-
osbot_utils-1.
|
325
|
-
osbot_utils-1.
|
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
|
File without changes
|
File without changes
|