""" XML file processor for Android resources """ import os from lxml import etree from typing import Dict, Tuple, List, Union from models import TranslationItem from config import Config class XMLProcessor: """XML file processor for Android resources""" def __init__(self, config: Config): self.config = config self.parser = etree.XMLParser(remove_blank_text=False, strip_cdata=False) def load_xml_file(self, file_path: str) -> etree.Element: """Load and parse XML file""" try: tree = etree.parse(file_path, self.parser) return tree.getroot() except Exception as e: print(f"Error loading XML file {file_path}: {e}") return None def save_xml_file(self, root: etree.Element, file_path: str): """Save XML file with formatting""" try: # Ensure proper formatting for the root element if root.text is None: root.text = "\n " if root.tail is None: root.tail = "\n" # Ensure all children have proper tails for formatting for i, child in enumerate(root): if child.tail is None or child.tail.strip() == "": child.tail = "\n " # Make sure the last child ends with proper indentation if len(root) > 0: last_child = root[-1] if not last_child.tail.endswith("\n"): last_child.tail = "\n" # Create backup if enabled if self.config.output_config.get('create_backups', True): backup_path = file_path + self.config.output_config.get('backup_suffix', '.backup') if os.path.exists(file_path): backup_tree = etree.ElementTree(root) backup_tree.write(backup_path, encoding='utf-8', xml_declaration=True, pretty_print=True) # Save the modified file with pretty printing tree = etree.ElementTree(root) tree.write(file_path, encoding='utf-8', xml_declaration=True, pretty_print=True) except Exception as e: print(f"Error saving XML file {file_path}: {e}") def extract_strings(self, root: etree.Element) -> Dict[str, TranslationItem]: """Extract strings and string-arrays from XML root element""" strings = {} for element in root: if element.tag == 'string': name = element.get('name') value = element.text or '' # Skip strings marked as non-translatable translatable = element.get('translatable', 'true') if translatable.lower() == 'false': continue # Handle CDATA and special characters if isinstance(value, str): value = value.strip() # Get comment if exists comment = None if element.tail and element.tail.strip(): comment = element.tail.strip() strings[name] = TranslationItem(name=name, value=value, comment=comment, item_type='string') elif element.tag == 'string-array': name = element.get('name') # Skip arrays marked as non-translatable translatable = element.get('translatable', 'true') if translatable.lower() == 'false': continue # Extract all items from the string-array items = [] for item in element: if item.tag == 'item': item_value = item.text or '' if isinstance(item_value, str): item_value = item_value.strip() items.append(item_value) # Get comment if exists comment = None if element.tail and element.tail.strip(): comment = element.tail.strip() # Store with combined value for display combined_value = " | ".join(items) if items else "" strings[name] = TranslationItem( name=name, value=combined_value, comment=comment, item_type='string-array', items=items ) elif element.tag == 'plurals': # Handle plurals - for now, skip or handle separately name = element.get('name') strings[name] = TranslationItem(name=name, value=f"<{element.tag}>", comment="Complex type", item_type='plurals') return strings def add_missing_strings(self, target_root: etree.Element, missing_strings: List[Tuple]): """Add missing strings and string-arrays to target XML""" for item_data in missing_strings: if len(item_data) == 4: # Extended format: (name, value, item_type, items) name, value, item_type, items = item_data else: # Regular string: (name, value) name, value = item_data[0], item_data[1] item_type = 'string' items = [] if item_type == 'string-array': # Check if string-array already exists existing = target_root.find(f".//string-array[@name='{name}']") if existing is None: # Create new string-array element new_array = etree.SubElement(target_root, 'string-array', name=name) # Add items to the array for i, item_value in enumerate(items): item_elem = etree.SubElement(new_array, 'item') item_elem.text = item_value # Add proper indentation between items item_elem.tail = "\n " # Add proper tail for the array element itself new_array.tail = "\n " else: # Regular string existing = target_root.find(f".//string[@name='{name}']") if existing is None: # Create new string element with proper indentation new_string = etree.SubElement(target_root, 'string', name=name) new_string.text = value # Add proper indentation and newlines new_string.tail = "\n " # Ensure the last element has proper closing if len(target_root) > 0: last_child = target_root[-1] if last_child.tail and not last_child.tail.endswith("\n"): last_child.tail += "\n"