praisonaiagents 0.0.23__py3-none-any.whl → 0.0.24__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,498 @@
1
+ """Tools for working with XML files.
2
+
3
+ Usage:
4
+ from praisonaiagents.tools import xml_tools
5
+ tree = xml_tools.read_xml("data.xml")
6
+
7
+ or
8
+ from praisonaiagents.tools import read_xml, write_xml, transform_xml
9
+ tree = read_xml("data.xml")
10
+ """
11
+
12
+ import logging
13
+ from typing import List, Dict, Union, Optional, Any, Tuple
14
+ from importlib import util
15
+ import xml.etree.ElementTree as ET
16
+ import xml.dom.minidom as minidom
17
+ from io import StringIO
18
+ import json
19
+
20
+ class XMLTools:
21
+ """Tools for working with XML files."""
22
+
23
+ def __init__(self):
24
+ """Initialize XMLTools."""
25
+ pass
26
+
27
+ def read_xml(
28
+ self,
29
+ filepath: str,
30
+ encoding: str = 'utf-8',
31
+ validate_schema: Optional[str] = None,
32
+ parser: str = 'lxml'
33
+ ) -> ET.Element:
34
+ """Read an XML file with optional schema validation.
35
+
36
+ Args:
37
+ filepath: Path to XML file
38
+ encoding: File encoding
39
+ validate_schema: Optional path to XSD schema file
40
+ parser: XML parser to use ('lxml' or 'etree')
41
+
42
+ Returns:
43
+ ElementTree root element
44
+ """
45
+ try:
46
+ if parser == 'lxml':
47
+ if util.find_spec('lxml') is None:
48
+ error_msg = "lxml package is not available. Please install it using: pip install lxml"
49
+ logging.error(error_msg)
50
+ return None
51
+ import lxml.etree as lxml_etree
52
+ tree = lxml_etree.parse(filepath)
53
+ root = tree.getroot()
54
+ else:
55
+ tree = ET.parse(filepath)
56
+ root = tree.getroot()
57
+
58
+ if validate_schema:
59
+ if util.find_spec('xmlschema') is None:
60
+ error_msg = "xmlschema package is not available. Please install it using: pip install xmlschema"
61
+ logging.error(error_msg)
62
+ return None
63
+ import xmlschema
64
+ schema = xmlschema.XMLSchema(validate_schema)
65
+ if not schema.is_valid(filepath):
66
+ error_msg = f"XML file does not validate against schema: {schema.validate(filepath)}"
67
+ logging.error(error_msg)
68
+ return None
69
+
70
+ return root
71
+
72
+ except Exception as e:
73
+ error_msg = f"Error reading XML file {filepath}: {str(e)}"
74
+ logging.error(error_msg)
75
+ return None
76
+
77
+ def write_xml(
78
+ self,
79
+ root: ET.Element,
80
+ filepath: str,
81
+ encoding: str = 'utf-8',
82
+ pretty: bool = True,
83
+ xml_declaration: bool = True
84
+ ) -> bool:
85
+ """Write XML Element tree to file.
86
+
87
+ Args:
88
+ root: XML Element tree root
89
+ filepath: Output file path
90
+ encoding: File encoding
91
+ pretty: Format output with proper indentation
92
+ xml_declaration: Include XML declaration
93
+
94
+ Returns:
95
+ True if successful, False otherwise
96
+ """
97
+ try:
98
+ # Convert to string
99
+ if pretty:
100
+ xml_str = minidom.parseString(
101
+ ET.tostring(root, encoding='unicode')
102
+ ).toprettyxml(indent=' ')
103
+ else:
104
+ xml_str = ET.tostring(
105
+ root,
106
+ encoding='unicode'
107
+ )
108
+
109
+ # Add declaration if requested
110
+ if xml_declaration:
111
+ if not xml_str.startswith('<?xml'):
112
+ xml_str = (
113
+ f'<?xml version="1.0" encoding="{encoding}"?>\n'
114
+ + xml_str
115
+ )
116
+
117
+ # Write to file
118
+ with open(filepath, 'w', encoding=encoding) as f:
119
+ f.write(xml_str)
120
+
121
+ return True
122
+ except Exception as e:
123
+ error_msg = f"Error writing XML file {filepath}: {str(e)}"
124
+ logging.error(error_msg)
125
+ return False
126
+
127
+ def transform_xml(
128
+ self,
129
+ xml_file: str,
130
+ xslt_file: str,
131
+ output_file: str,
132
+ params: Optional[Dict[str, str]] = None
133
+ ) -> bool:
134
+ """Transform XML using XSLT stylesheet.
135
+
136
+ Args:
137
+ xml_file: Input XML file
138
+ xslt_file: XSLT stylesheet file
139
+ output_file: Output file path
140
+ params: Optional parameters for transformation
141
+
142
+ Returns:
143
+ True if successful, False otherwise
144
+ """
145
+ try:
146
+ # Parse XML and XSLT
147
+ if util.find_spec('lxml') is None:
148
+ error_msg = "lxml package is not available. Please install it using: pip install lxml"
149
+ logging.error(error_msg)
150
+ return False
151
+ import lxml.etree as lxml_etree
152
+ xml_doc = lxml_etree.parse(xml_file)
153
+ xslt_doc = lxml_etree.parse(xslt_file)
154
+ transform = lxml_etree.XSLT(xslt_doc)
155
+
156
+ # Apply transformation
157
+ if params:
158
+ result = transform(xml_doc, **params)
159
+ else:
160
+ result = transform(xml_doc)
161
+
162
+ # Write result
163
+ result.write(
164
+ output_file,
165
+ pretty_print=True,
166
+ xml_declaration=True,
167
+ encoding='utf-8'
168
+ )
169
+
170
+ return True
171
+ except Exception as e:
172
+ error_msg = f"Error transforming XML: {str(e)}"
173
+ logging.error(error_msg)
174
+ return False
175
+
176
+ def validate_xml(
177
+ self,
178
+ xml_file: str,
179
+ schema_file: str
180
+ ) -> Tuple[bool, Optional[str]]:
181
+ """Validate XML against XSD schema.
182
+
183
+ Args:
184
+ xml_file: XML file to validate
185
+ schema_file: XSD schema file
186
+
187
+ Returns:
188
+ Tuple of (is_valid, error_message)
189
+ """
190
+ try:
191
+ if util.find_spec('xmlschema') is None:
192
+ error_msg = "xmlschema package is not available. Please install it using: pip install xmlschema"
193
+ logging.error(error_msg)
194
+ return False, error_msg
195
+ import xmlschema
196
+ schema = xmlschema.XMLSchema(schema_file)
197
+ schema.validate(xml_file)
198
+ return True, None
199
+ except xmlschema.validators.exceptions.XMLSchemaValidationError as e:
200
+ return False, str(e)
201
+ except Exception as e:
202
+ error_msg = f"Error validating XML: {str(e)}"
203
+ logging.error(error_msg)
204
+ return False, error_msg
205
+
206
+ def xml_to_dict(
207
+ self,
208
+ root: Union[str, ET.Element],
209
+ preserve_attrs: bool = True
210
+ ) -> Dict[str, Any]:
211
+ """Convert XML to dictionary.
212
+
213
+ Args:
214
+ root: XML string or Element tree root
215
+ preserve_attrs: Keep XML attributes in result
216
+
217
+ Returns:
218
+ Dict representation of XML
219
+ """
220
+ try:
221
+ # Parse XML if string
222
+ if isinstance(root, str):
223
+ if root.startswith('<'):
224
+ root = ET.fromstring(root)
225
+ else:
226
+ root = ET.parse(root).getroot()
227
+
228
+ result = {}
229
+
230
+ # Add attributes if present and requested
231
+ if preserve_attrs and root.attrib:
232
+ result['@attributes'] = dict(root.attrib)
233
+
234
+ # Add children
235
+ children = list(root)
236
+ if not children:
237
+ text = root.text
238
+ if text is not None and text.strip():
239
+ result = text.strip()
240
+ else:
241
+ for child in children:
242
+ child_data = self.xml_to_dict(child, preserve_attrs)
243
+ if child.tag in result:
244
+ if not isinstance(result[child.tag], list):
245
+ result[child.tag] = [result[child.tag]]
246
+ result[child.tag].append(child_data)
247
+ else:
248
+ result[child.tag] = child_data
249
+
250
+ return result
251
+ except Exception as e:
252
+ error_msg = f"Error converting XML to dict: {str(e)}"
253
+ logging.error(error_msg)
254
+ return {}
255
+
256
+ def dict_to_xml(
257
+ self,
258
+ data: Dict[str, Any],
259
+ root_tag: str = 'root'
260
+ ) -> Optional[ET.Element]:
261
+ """Convert dictionary to XML.
262
+
263
+ Args:
264
+ data: Dictionary to convert
265
+ root_tag: Tag for root element
266
+
267
+ Returns:
268
+ XML Element tree root
269
+ """
270
+ try:
271
+ def _create_element(
272
+ parent: ET.Element,
273
+ key: str,
274
+ value: Any
275
+ ):
276
+ """Create XML element from key-value pair."""
277
+ if key == '@attributes':
278
+ for attr_key, attr_val in value.items():
279
+ parent.set(attr_key, str(attr_val))
280
+ elif isinstance(value, dict):
281
+ child = ET.SubElement(parent, key)
282
+ for k, v in value.items():
283
+ _create_element(child, k, v)
284
+ elif isinstance(value, list):
285
+ for item in value:
286
+ child = ET.SubElement(parent, key)
287
+ if isinstance(item, dict):
288
+ for k, v in item.items():
289
+ _create_element(child, k, v)
290
+ else:
291
+ child.text = str(item)
292
+ else:
293
+ child = ET.SubElement(parent, key)
294
+ child.text = str(value)
295
+
296
+ root = ET.Element(root_tag)
297
+ for key, value in data.items():
298
+ _create_element(root, key, value)
299
+
300
+ return root
301
+ except Exception as e:
302
+ error_msg = f"Error converting dict to XML: {str(e)}"
303
+ logging.error(error_msg)
304
+ return None
305
+
306
+ def xpath_query(
307
+ self,
308
+ root: Union[str, ET.Element],
309
+ query: str,
310
+ namespaces: Optional[Dict[str, str]] = None
311
+ ) -> List[ET.Element]:
312
+ """Execute XPath query on XML.
313
+
314
+ Args:
315
+ root: XML string or Element tree root
316
+ query: XPath query string
317
+ namespaces: Optional namespace mappings
318
+
319
+ Returns:
320
+ List of matching elements
321
+ """
322
+ try:
323
+ # Parse XML if string
324
+ if isinstance(root, str):
325
+ if root.startswith('<'):
326
+ if util.find_spec('lxml') is None:
327
+ error_msg = "lxml package is not available. Please install it using: pip install lxml"
328
+ logging.error(error_msg)
329
+ return []
330
+ import lxml.etree as lxml_etree
331
+ tree = lxml_etree.fromstring(root)
332
+ else:
333
+ tree = ET.parse(root)
334
+ else:
335
+ if util.find_spec('lxml') is None:
336
+ error_msg = "lxml package is not available. Please install it using: pip install lxml"
337
+ logging.error(error_msg)
338
+ return []
339
+ import lxml.etree as lxml_etree
340
+ tree = lxml_etree.fromstring(
341
+ ET.tostring(root, encoding='unicode')
342
+ )
343
+
344
+ # Execute query
345
+ results = tree.xpath(
346
+ query,
347
+ namespaces=namespaces or {}
348
+ )
349
+
350
+ # Convert results to standard ElementTree elements
351
+ return [
352
+ ET.fromstring(lxml_etree.tostring(elem, encoding='unicode'))
353
+ for elem in results
354
+ ]
355
+ except Exception as e:
356
+ error_msg = f"Error executing XPath query: {str(e)}"
357
+ logging.error(error_msg)
358
+ return []
359
+
360
+ # Create instance for direct function access
361
+ _xml_tools = XMLTools()
362
+ read_xml = _xml_tools.read_xml
363
+ write_xml = _xml_tools.write_xml
364
+ transform_xml = _xml_tools.transform_xml
365
+ validate_xml = _xml_tools.validate_xml
366
+ xml_to_dict = _xml_tools.xml_to_dict
367
+ dict_to_xml = _xml_tools.dict_to_xml
368
+ xpath_query = _xml_tools.xpath_query
369
+
370
+ if __name__ == "__main__":
371
+ print("\n==================================================")
372
+ print("XMLTools Demonstration")
373
+ print("==================================================\n")
374
+
375
+ # Create temporary files
376
+ import tempfile
377
+ import os
378
+
379
+ temp_file = tempfile.mktemp(suffix='.xml')
380
+ try:
381
+ print("1. Creating XML Document")
382
+ print("------------------------------")
383
+ xml_content = """<?xml version="1.0" encoding="UTF-8"?>
384
+ <bookstore>
385
+ <book category="fiction">
386
+ <title>The Great Gatsby</title>
387
+ <author>F. Scott Fitzgerald</author>
388
+ <year>1925</year>
389
+ <price>10.99</price>
390
+ </book>
391
+ <book category="non-fiction">
392
+ <title>A Brief History of Time</title>
393
+ <author>Stephen Hawking</author>
394
+ <year>1988</year>
395
+ <price>15.99</price>
396
+ </book>
397
+ </bookstore>"""
398
+
399
+ with open(temp_file, 'w') as f:
400
+ f.write(xml_content)
401
+ print("Sample XML file created")
402
+ print()
403
+
404
+ print("2. Parsing XML")
405
+ print("------------------------------")
406
+ result = read_xml(temp_file)
407
+ print("XML structure:")
408
+ print(minidom.parseString(ET.tostring(result, encoding='unicode')).toprettyxml(indent=' '))
409
+ print()
410
+
411
+ print("3. Querying XML")
412
+ print("------------------------------")
413
+ xpath = "//book[@category='fiction']/title/text()"
414
+ result = xpath_query(result, xpath)
415
+ print(f"Fiction book titles (XPath: {xpath}):")
416
+ for title in result:
417
+ print(title.text)
418
+ print()
419
+
420
+ print("4. Validating XML")
421
+ print("------------------------------")
422
+ schema_file = tempfile.mktemp(suffix='.xsd')
423
+ schema_content = """<?xml version="1.0" encoding="UTF-8"?>
424
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
425
+ <xs:element name="bookstore">
426
+ <xs:complexType>
427
+ <xs:sequence>
428
+ <xs:element name="book" maxOccurs="unbounded">
429
+ <xs:complexType>
430
+ <xs:sequence>
431
+ <xs:element name="title" type="xs:string"/>
432
+ <xs:element name="author" type="xs:string"/>
433
+ <xs:element name="year" type="xs:integer"/>
434
+ <xs:element name="price" type="xs:decimal"/>
435
+ </xs:sequence>
436
+ <xs:attribute name="category" type="xs:string"/>
437
+ </xs:complexType>
438
+ </xs:element>
439
+ </xs:sequence>
440
+ </xs:complexType>
441
+ </xs:element>
442
+ </xs:schema>"""
443
+ with open(schema_file, 'w') as f:
444
+ f.write(schema_content)
445
+ result, error = validate_xml(temp_file, schema_file)
446
+ print(f"XML validation result: {result}")
447
+ if error:
448
+ print(f"Error: {error}")
449
+ print()
450
+
451
+ print("5. Transforming XML")
452
+ print("------------------------------")
453
+ xslt_content = """<?xml version="1.0" encoding="UTF-8"?>
454
+ <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
455
+ <xsl:template match="/">
456
+ <html>
457
+ <body>
458
+ <h2>Bookstore Inventory</h2>
459
+ <table>
460
+ <tr>
461
+ <th>Title</th>
462
+ <th>Author</th>
463
+ <th>Price</th>
464
+ </tr>
465
+ <xsl:for-each select="bookstore/book">
466
+ <tr>
467
+ <td><xsl:value-of select="title"/></td>
468
+ <td><xsl:value-of select="author"/></td>
469
+ <td><xsl:value-of select="price"/></td>
470
+ </tr>
471
+ </xsl:for-each>
472
+ </table>
473
+ </body>
474
+ </html>
475
+ </xsl:template>
476
+ </xsl:stylesheet>"""
477
+
478
+ xslt_file = tempfile.mktemp(suffix='.xslt')
479
+ with open(xslt_file, 'w') as f:
480
+ f.write(xslt_content)
481
+
482
+ output_file = tempfile.mktemp(suffix='.html')
483
+ result = transform_xml(temp_file, xslt_file, output_file)
484
+ print(f"XML transformation result: {result}")
485
+ if result and os.path.exists(output_file):
486
+ print("\nTransformed HTML content:")
487
+ with open(output_file, 'r') as f:
488
+ print(f.read())
489
+
490
+ finally:
491
+ # Clean up temporary files
492
+ for file in [temp_file, schema_file, xslt_file, output_file]:
493
+ if os.path.exists(file):
494
+ os.unlink(file)
495
+
496
+ print("\n==================================================")
497
+ print("Demonstration Complete")
498
+ print("==================================================")