174 lines
7.1 KiB
Python
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"
|