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