1aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski#!/usr/bin/env python 2aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski 3aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski""" 4aa0255da28675431c93593a8dd2d225538cbb3acAdam LesinskiLooks for strings with multiple substitution arguments (%d, &s, etc) 5aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinskiand replaces them with positional arguments (%1$d, %2$s). 6aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski""" 7aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski 8aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinskiimport os.path 9aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinskiimport re 10aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinskiimport xml.parsers.expat 11aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski 12aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinskiclass PositionalArgumentFixer: 13aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski def matches(self, file_path): 14aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski dirname, basename = os.path.split(file_path) 15aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski dirname = os.path.split(dirname)[1] 16aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski return dirname.startswith("values") and basename.endswith(".xml") 17aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski 18aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski def consume(self, xml_path, input): 19aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski parser = xml.parsers.expat.ParserCreate("utf-8") 20aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski locator = SubstitutionArgumentLocator(parser) 21aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski parser.returns_unicode = True 22aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski parser.StartElementHandler = locator.start_element 23aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski parser.EndElementHandler = locator.end_element 24aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski parser.CharacterDataHandler = locator.character_data 25aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski parser.Parse(input) 26aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski 27aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski if len(locator.arguments) > 0: 28aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski output = "" 29aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski last_index = 0 30aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski for arg in locator.arguments: 31aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski output += input[last_index:arg.start] 32aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski output += "%{0}$".format(arg.number) 33aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski last_index = arg.start + 1 34aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski output += input[last_index:] 35aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski print "fixed {0}".format(xml_path) 36aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski return output 37aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski return input 38aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski 39aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinskiclass Argument: 40aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski def __init__(self, start, number): 41aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self.start = start 42aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self.number = number 43aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski 44aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinskiclass SubstitutionArgumentLocator: 45aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski """Callback class for xml.parsers.expat which records locations of 46aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski substitution arguments in strings when there are more than 1 of 47aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski them in a single <string> tag (and they are not positional). 48aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski """ 49aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski def __init__(self, parser): 50aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self.arguments = [] 51aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._parser = parser 52aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._depth = 0 53aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._within_string = False 54aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._current_arguments = [] 55aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._next_number = 1 56aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski 57aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski def start_element(self, tag_name, attrs): 58aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._depth += 1 59aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski if self._depth == 2 and tag_name == "string" and "translateable" not in attrs: 60aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._within_string = True 61aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski 62aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski def character_data(self, data): 63aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski if self._within_string: 64aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski for m in re.finditer("%[-#+ 0,(]?\d*[bBhHsScCdoxXeEfgGaAtTn]", data): 65aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski start, end = m.span() 66aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._current_arguments.append(\ 67aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski Argument(self._parser.CurrentByteIndex + start, self._next_number)) 68aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._next_number += 1 69aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski 70aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski def end_element(self, tag_name): 71aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski if self._within_string and self._depth == 2: 72aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski if len(self._current_arguments) > 1: 73aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self.arguments += self._current_arguments 74aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._current_arguments = [] 75aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._within_string = False 76aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._next_number = 1 77aa0255da28675431c93593a8dd2d225538cbb3acAdam Lesinski self._depth -= 1 78