Files
ResourceTranslate/xml_processor.py
2026-02-14 18:12:28 +01:00

174 lines
7.1 KiB
Python

"""
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"