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