andea_revanced-patches/xml_tools/utils/xml_processor.py

74 lines
2.6 KiB
Python

"""XML Processor."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from xml.etree import ElementTree as ET
from defusedxml import ElementTree as DefusedET
if TYPE_CHECKING:
from pathlib import Path
logger = logging.getLogger("xml_tools")
BYTES: int = 2
class XMLProcessor:
"""Utilities for processing XML files.
This class provides static methods for parsing and writing XML files,
with special handling for elements containing 'name' attributes.
Uses defusedxml for secure XML processing.
"""
@staticmethod
def parse_file(
path: Path,
) -> tuple[ET.ElementTree | None, ET.Element | None, dict[str, dict[str, str]]]:
"""Parse an XML file and extract data from elements with 'name' attributes."""
if not path.exists() or path.stat().st_size < BYTES:
return None, None, {}
try:
# Parse XML using defusedxml for security
tree = DefusedET.parse(str(path))
root = tree.getroot()
strings: dict[str, dict[str, str]] = {}
if root is None:
error_msg = f"Internal error: XML root is None after successful parse for {path}. Expected ET.Element."
logger.error(error_msg)
raise TypeError(error_msg)
for elem in root.findall(".//*[@name]"):
name = elem.get("name")
if name:
attributes = dict(elem.attrib)
attributes["text"] = XMLProcessor.element_to_string(elem)
strings[name] = attributes
except (OSError, DefusedET.ParseError):
logger.exception("Failed to parse %s: ", path)
return None, None, {}
else:
return tree, root, strings
@staticmethod
def element_to_string(element: ET.Element) -> str:
"""Convert an element to its string representation, preserving inner tags."""
return ET.tostring(element, encoding="unicode", method="xml")
@staticmethod
def write_file(path: Path, root: ET.Element) -> None:
"""Write an XML element tree to a file."""
try:
path.parent.mkdir(parents=True, exist_ok=True)
tree = ET.ElementTree(root)
ET.indent(tree, space=" ")
with path.open("wb") as f:
f.write(b'<?xml version="1.0" encoding="utf-8"?>\n')
tree.write(f, encoding="utf-8", xml_declaration=False)
f.write(b"\n")
except OSError:
logger.exception("Failed to write file: %s", path)