Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mapsme/omim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimofey <t.danshin@corp.mail.ru>2017-02-16 14:45:23 +0300
committerRoman Kuznetsov <r.kuznetsow@gmail.com>2018-07-27 10:03:42 +0300
commit4ef6bac1457b8fd5a17128b15d78a53395a629f4 (patch)
tree554b9d9c8de80e8b5feed001d17cd8c35cd95bef
parent4128185913958967c2411ba2ad276a677c307d51 (diff)
Comparing stylesdynamic_styles
-rwxr-xr-xtools/python/stylesheet/color_matrix_builder.py285
-rw-r--r--tools/python/stylesheet/style_comparator/__init__.py0
-rw-r--r--tools/python/stylesheet/style_comparator/abstract_matrix_builder.py107
l---------tools/python/stylesheet/style_comparator/drules_struct_pb2.py1
-rw-r--r--tools/python/stylesheet/style_comparator/image_maker.py (renamed from tools/python/stylesheet/image_maker.py)0
-rw-r--r--tools/python/stylesheet/style_comparator/matrix_builder.py177
-rw-r--r--tools/python/stylesheet/style_comparator/protobuf_util.py (renamed from tools/python/stylesheet/protobuf_util.py)29
-rw-r--r--tools/python/stylesheet/style_comparator/style_comparator.py95
8 files changed, 415 insertions, 279 deletions
diff --git a/tools/python/stylesheet/color_matrix_builder.py b/tools/python/stylesheet/color_matrix_builder.py
index 33d432a59f..1920d0432c 100755
--- a/tools/python/stylesheet/color_matrix_builder.py
+++ b/tools/python/stylesheet/color_matrix_builder.py
@@ -1,285 +1,11 @@
#!/usr/bin/env python2.7
from __future__ import print_function
+from style_comparator.matrix_builder import MatrixBuilder
+from style_comparator.style_comparator import StyleComparator
-import drules_struct_pb2 as proto
-
-import re
import sys
-from collections import defaultdict
-from image_maker import argb_to_tuple, ImageMaker
-from itertools import chain
-
-from os import path
-from protobuf_util import *
-
-PRIORITY_RE = re.compile("(.*)__(-?\d*)/(.*)")
-
-
-"""
-In order to be able to quickly switch between different styles, we need to do as
-little work for switching as possible. Thus, if the only difference between the
-styles is the colour palette, we can just switch a colour matrix, and the map
-colours will change.
-
-So we need to create colour matrices in such a way that identical features in
-different styles correspond to pixels at the same locations in their
-corresponding colour matrices.
-
-Here is how we do that:
-
- 1) We read the binary style files
-
- 2) We recursively find all the elements that have attributes, whose names end in
- "color", storing the path tho those elements
-
- 3) We combine all the keys (paths) from all the styles in one set and build
- a table:
-
- style_1 | style_2 | style_n
- cont/path/one color_1 | -1 | color_2
- cont/path/two color_2 | color_3 | color_2
- cont/path/three color_2 | color_3 | color_2
-
- Where "color_x" stands for actual colour value, and -1 means that the
- corresponding key is not present in the corresponding style.
-
- 4) We invert this table, making the colours the key of the new table, and
- we append paths to those colour transitions to the list of values:
-
- (color_1, -1, color_2): cont/path/one
- (color_2, color_3, color_2): cont/path/two, cont/path/three,
-
- From this table we infer, that the matrix for style_1 will have pixels with
- color_1 and color_2, for style_2: -1 and color_3, style_3: color_2 and color_2,
-
- so:
-
- 5) We iterate over this table and write the n'th elements to the corresponding
- matrices.
-
-
- 6) Now that we know which of the style elements (or features) got which pixel
- in the resulting colour matrices, we need to write the colour coordinates
- (the coordinates of those pixels) to the initial binary style file. So we
- iterate over the binary file again, we build the path keys again, and ask
- our table for the index of that key in the transformation table. Then we ask
- the image instance to convert that number into the actual coordinates.
-"""
-
-class MatrixBuilder:
- def __init__(self):
- self.transformation_table = defaultdict(list) # {(color1, color2): [Element]}
- self.inverted_transformation_table = {} # {element_key: Number_in_transformation_table}
- self.paths = []
- self.drules = []
- self.imgs = []
- self.all_elements = defaultdict(lambda: defaultdict(lambda: -1)) # {path_index: {[(el.name, el.priority), (...)] : colour}}
- self.key_correspondences = defaultdict(dict) # {path_index: {old_element_path: new_element_path, ...}, ...}
-
-
- # ADDING THE FILE:
- def add_file(self, filepath):
- print("Adding file: {}".format(filepath))
- self.paths.append(filepath)
- self.drules.append(self.read_bin(filepath))
-
-
- def read_bin(self, filepath):
- drules = proto.ContainerProto()
- with open(filepath) as infile:
- drules.ParseFromString(infile.read())
- return drules
-
-
- # READING:
- def reader_factory(self, index):
- def reader(sub_drules, index_list):
- for key, _, color in self.color_key_generator(sub_drules, index_list):
- self.all_elements[index][key] = color
- return reader
-
-
- def parse_all_bins(self):
- print("Parsing the bin files")
- self.foreach_path(self.parse_bin)
-
-
- def parse_bin(self, index):
- self.recursive_iterator(self.drules[index], "", index, self.reader_factory(index))
-
-
- # CALCULATE COLOR MATRIX
- def calculate_color_matrix(self):
- self.normalize_all_keys()
-
- all_keys = chain.from_iterable(self.all_elements.itervalues())
- all_keys = frozenset(all_keys)
- colors_table = defaultdict(list) # {key: [color_0, color_1, -1 if element absent in style 2, ...]}
-
- for key in all_keys:
- colors_table[key] = tuple(
- self.all_elements[path_index][key]
- for path_index in xrange(len(self.paths))
- )
-
- for key, colors in colors_table.iteritems():
- self.transformation_table[colors].append(key)
-
-
- # WRITE COLOR MATRICES
- def write_all_matrices(self):
- print("Writing matrices")
- self.foreach_path(self.write_matrix)
-
-
- def write_matrix(self, index):
- colors = map(lambda x: x[index], self.transformation_table)
-
- imgmk = ImageMaker(colors)
- imgmk.make()
- imgmk.save(self.new_filename(self.paths[index], "png"))
- self.imgs.append(imgmk)
-
-
- def new_filename(self, bin_path, postfix):
- folder = path.dirname(bin_path)
- style_name = self.calculate_style_type_postfix(bin_path)
- return path.join(folder, "static_colors{}.{}".format(style_name, postfix))
-
-
- def calculate_style_type_postfix(self, bin_path):
- return path.basename(bin_path)[len("drules_proto"):-4]
-
-
- # UPDATE BINS
- def invert_transformation_table(self):
- for element, i in zip(
- self.transformation_table.iteritems(),
- xrange(len(self.transformation_table))
- ):
- for key in element[1]:
- self.inverted_transformation_table[key] = i
-
-
- def update_all_bins(self):
- print("Updating bin files")
- self.invert_transformation_table()
- self.foreach_path(self.update_one_bin)
-
-
- def update_one_bin(self, index):
- self.recursive_iterator(
- self.drules[index], "", index, self.updater_factory(index)
- )
-
-
- def write_all_bins(self):
- print("Writing bin files")
- self.foreach_path(self.write_one_bin)
-
-
- def write_one_bin(self, path_index):
- new_bin_name = self.paths[path_index]
- with open(new_bin_name, "wb") as outfile:
- outfile.write(self.drules[path_index].SerializePartialToString())
-
- txt_filename = path.splitext(new_bin_name)[0] + ".txt"
- with open(txt_filename, "wb") as outfile:
- outfile.write(unicode(self.drules[path_index]))
-
-
- def updater_factory(self, path_index):
- def updater(sub_drules, index_list):
- for key, color_key, color in self.color_key_generator(sub_drules, index_list):
- key_2 = self.key_correspondences[path_index][key]
- x, y = self.get_color_coords(path_index, key_2)
- self.verify_color_at_coords(path_index, (x, y), color)
-
- setattr(sub_drules, "{}_x".format(color_key), x)
- setattr(sub_drules, "{}_y".format(color_key), y)
-
- return updater
-
-
- def get_color_coords(self, path_index, key):
- return self.imgs[path_index].linear_index_to_coords(
- self.inverted_transformation_table[key]
- )
-
-
- def verify_color_at_coords(self, path_index, xy, orig_color):
- tup_color = argb_to_tuple(orig_color)
- tup_color_from_img = self.imgs[path_index].img.getpixel(xy)
- assert tup_color == tup_color_from_img, "Colors in the image and the bin file are diffrent"
-
-
- # CORE
- def foreach_path(self, fn):
- map(fn, xrange(len(self.paths)))
-
-
- def color_key_generator(self, sub_drules, index_list):
- return [
- self.path_key_and_color(sub_drules, index_list, color_key)
- for color_key in color_fields(sub_drules)
- ]
-
-
- def path_key_and_color(self, sub_drules, index_list, color_key):
- return (
- index_list + "/" + color_key,
- color_key,
- getattr(sub_drules, color_key)
- )
-
-
- def recursive_iterator(self, sub_drules, index_list, index, worker_fn):
- if has_color_fields(sub_drules):
- worker_fn(sub_drules, index_list)
-
- for key, element in list_message_fields(sub_drules):
- self.recursive_iterator(element, index_list + key, index, worker_fn)
-
-
- def normalize_all_keys(self):
- self.foreach_path(self.normalize_keys_at_index)
-
-
- def normalize_keys_at_index(self, path_index):
- priority_key_transformation_dict = {} # new_key: old_key
- new_priorities_dict = defaultdict(dict) # {rest_of_key: {priority: color}}
-
- for key, val in self.all_elements[path_index].iteritems():
-
- priority_parts = PRIORITY_RE.findall(key)
- if priority_parts:
- rest_of_key, int_priority = self.make_priority_key_parts(priority_parts)
- new_priorities_dict[rest_of_key][int_priority] = val
- priority_key_transformation_dict[self.priority_key(rest_of_key, int_priority)] = key
- else:
- self.key_correspondences[path_index][key] = key
-
- for rest_of_key, priorities in new_priorities_dict.iteritems():
- for i, (priority, color) in enumerate(sorted(priorities.iteritems())):
- final_key = self.priority_key(rest_of_key, i)
- old_key = priority_key_transformation_dict[self.priority_key(rest_of_key, priority)]
- self.key_correspondences[path_index][old_key] = final_key
- del self.all_elements[path_index][old_key]
- self.all_elements[path_index][final_key] = color
-
-
- def make_priority_key_parts(self, priority_parts):
- priority_parts = priority_parts[0]
- rest_of_key = "{}/{}".format(priority_parts[0], priority_parts[2])
- int_priority = int(priority_parts[1])
- return rest_of_key, int_priority
-
-
- def priority_key(self, rest_of_key, i):
- return "{}~{}".format(rest_of_key, i)
-
def main():
if len(sys.argv) < 2:
@@ -287,8 +13,8 @@ def main():
sys.exit(1)
mb = MatrixBuilder()
- for filepath in sys.argv[1:]:
- mb.add_file(filepath)
+ mb.add_files(sys.argv[1:])
+
mb.parse_all_bins()
mb.calculate_color_matrix()
@@ -296,6 +22,9 @@ def main():
mb.update_all_bins()
mb.write_all_bins()
+ sc = StyleComparator(matrix_builder=mb)
+ sc.compare_styles()
+ sc.write_changes_file()
print("\nDone\n")
if __name__ == "__main__":
diff --git a/tools/python/stylesheet/style_comparator/__init__.py b/tools/python/stylesheet/style_comparator/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tools/python/stylesheet/style_comparator/__init__.py
diff --git a/tools/python/stylesheet/style_comparator/abstract_matrix_builder.py b/tools/python/stylesheet/style_comparator/abstract_matrix_builder.py
new file mode 100644
index 0000000000..58095b0b23
--- /dev/null
+++ b/tools/python/stylesheet/style_comparator/abstract_matrix_builder.py
@@ -0,0 +1,107 @@
+from collections import defaultdict
+from itertools import chain
+from protobuf_util import *
+import re
+
+PRIORITY_RE = re.compile("(.*)__(-?\d*)/(.*)")
+
+
+class AbstractMatrixBuilder(object):
+ def __init__(self):
+ self.paths = []
+ self.drules = []
+ self.all_elements = defaultdict(lambda: defaultdict(lambda: -1)) # {path_index: {[(el.name, el.priority), (...)] : colour}}
+ self.key_correspondences = defaultdict(dict) # {path_index: {old_element_path: new_element_path, ...}, ...}
+
+
+ # ADDING THE FILE:
+ def add_file(self, filepath):
+ print("Adding file: {}".format(filepath))
+ self.paths.append(filepath)
+ self.drules.append(read_bin(filepath))
+
+
+ def add_files(self, files):
+ for filepath in files:
+ self.add_file(filepath)
+
+
+ # CALCULATING
+ def calculate_items_table(self):
+ all_keys = chain.from_iterable(self.all_elements.itervalues())
+ all_keys = frozenset(all_keys)
+
+ colors_table = {} # {key: (color_0, color_1, -1 if element absent in style 2, ...)}
+
+ for key in all_keys:
+ colors_table[key] = tuple(
+ self.all_elements[path_index][key]
+ for path_index in xrange(len(self.paths))
+ )
+ return colors_table
+
+
+ # CORE
+ def foreach_path(self, fn):
+ map(fn, xrange(len(self.paths)))
+
+
+ def recursive_iterator(self, sub_drules, index_list, index, worker_fn, filter_fn=has_color_fields):
+ if filter_fn(sub_drules):
+ worker_fn(sub_drules, index_list)
+
+ for key, element in list_message_fields(sub_drules):
+ self.recursive_iterator(element, index_list + key, index, worker_fn)
+
+
+ def normalize_all_keys(self):
+ self.foreach_path(self.normalize_keys_at_index)
+
+
+ def normalize_keys_at_index(self, path_index):
+ priority_key_transformation_dict = {} # new_key: old_key
+ new_priorities_dict = defaultdict(dict) # {rest_of_key: {priority: color}}
+
+ for key, val in self.all_elements[path_index].iteritems():
+
+ priority_parts = PRIORITY_RE.findall(key)
+ if priority_parts:
+ rest_of_key, int_priority = self.make_priority_key_parts(priority_parts)
+ new_priorities_dict[rest_of_key][int_priority] = val
+ priority_key_transformation_dict[self.priority_key(rest_of_key, int_priority)] = key
+ else:
+ self.key_correspondences[path_index][key] = key
+
+ for rest_of_key, priorities in new_priorities_dict.iteritems():
+ for i, (priority, color) in enumerate(sorted(priorities.iteritems())):
+ final_key = self.priority_key(rest_of_key, i)
+ old_key = priority_key_transformation_dict[self.priority_key(rest_of_key, priority)]
+ self.key_correspondences[path_index][old_key] = final_key
+ del self.all_elements[path_index][old_key]
+ self.all_elements[path_index][final_key] = color
+
+
+ def make_priority_key_parts(self, priority_parts):
+ priority_parts = priority_parts[0]
+ rest_of_key = "{}/{}".format(priority_parts[0], priority_parts[2])
+ int_priority = int(priority_parts[1])
+ return rest_of_key, int_priority
+
+
+ def priority_key(self, rest_of_key, i):
+ return "{}~{}".format(rest_of_key, i)
+
+
+ def item_key_generator(self, sub_drules, index_list, fn):
+ return [
+ self.path_key_and_value(sub_drules, index_list, color_key)
+ for color_key in fn(sub_drules)
+ ]
+
+
+ def path_key_and_value(self, sub_drules, index_list, color_key):
+ return (
+ index_list + "/" + color_key,
+ color_key,
+ getattr(sub_drules, color_key)
+ )
diff --git a/tools/python/stylesheet/style_comparator/drules_struct_pb2.py b/tools/python/stylesheet/style_comparator/drules_struct_pb2.py
new file mode 120000
index 0000000000..4595eeb287
--- /dev/null
+++ b/tools/python/stylesheet/style_comparator/drules_struct_pb2.py
@@ -0,0 +1 @@
+../drules_struct_pb2.py \ No newline at end of file
diff --git a/tools/python/stylesheet/image_maker.py b/tools/python/stylesheet/style_comparator/image_maker.py
index 808dd3ee81..808dd3ee81 100644
--- a/tools/python/stylesheet/image_maker.py
+++ b/tools/python/stylesheet/style_comparator/image_maker.py
diff --git a/tools/python/stylesheet/style_comparator/matrix_builder.py b/tools/python/stylesheet/style_comparator/matrix_builder.py
new file mode 100644
index 0000000000..ae77b67ba5
--- /dev/null
+++ b/tools/python/stylesheet/style_comparator/matrix_builder.py
@@ -0,0 +1,177 @@
+from abstract_matrix_builder import AbstractMatrixBuilder
+from collections import defaultdict
+from protobuf_util import *
+from image_maker import ImageMaker, argb_to_tuple
+from os import path
+
+"""
+In order to be able to quickly switch between different styles, we need to do as
+little work for switching as possible. Thus, if the only difference between the
+styles is the colour palette, we can just switch a colour matrix, and the map
+colours will change.
+
+So we need to create colour matrices in such a way that identical features in
+different styles correspond to pixels at the same locations in their
+corresponding colour matrices.
+
+Here is how we do that:
+
+ 1) We read the binary style files
+
+ 2) We recursively find all the elements that have attributes, whose names end in
+ "color", storing the path tho those elements
+
+ 3) We combine all the keys (paths) from all the styles in one set and build
+ a table:
+
+ style_1 | style_2 | style_n
+ cont/path/one color_1 | -1 | color_2
+ cont/path/two color_2 | color_3 | color_2
+ cont/path/three color_2 | color_3 | color_2
+
+ Where "color_x" stands for actual colour value, and -1 means that the
+ corresponding key is not present in the corresponding style.
+
+ 4) We invert this table, making the colours the key of the new table, and
+ we append paths to those colour transitions to the list of values:
+
+ (color_1, -1, color_2): cont/path/one
+ (color_2, color_3, color_2): cont/path/two, cont/path/three,
+
+ From this table we infer, that the matrix for style_1 will have pixels with
+ color_1 and color_2, for style_2: -1 and color_3, style_3: color_2 and color_2,
+
+ so:
+
+ 5) We iterate over this table and write the n'th elements to the corresponding
+ matrices.
+
+
+ 6) Now that we know which of the style elements (or features) got which pixel
+ in the resulting colour matrices, we need to write the colour coordinates
+ (the coordinates of those pixels) to the initial binary style file. So we
+ iterate over the binary file again, we build the path keys again, and ask
+ our table for the index of that key in the transformation table. Then we ask
+ the image instance to convert that number into the actual coordinates.
+"""
+
+
+class MatrixBuilder(AbstractMatrixBuilder):
+ def __init__(self):
+ AbstractMatrixBuilder.__init__(self)
+ self.transformation_table = defaultdict(list) # {(color1, color2): [Element]}
+ self.inverted_transformation_table = {} # {element_key: Number_in_transformation_table}
+ self.imgs = []
+
+
+ # READING:
+ def reader_factory(self, index):
+ def reader(sub_drules, index_list):
+ for key, _, color in self.item_key_generator(sub_drules, index_list, color_fields):
+ self.all_elements[index][key] = color
+ return reader
+
+
+ def parse_all_bins(self):
+ print("Parsing the bin files")
+ self.foreach_path(self.parse_bin)
+
+
+ def parse_bin(self, index):
+ self.recursive_iterator(self.drules[index], "", index, self.reader_factory(index))
+
+
+ # CALCULATE COLOR MATRIX
+ def calculate_color_matrix(self):
+ self.normalize_all_keys()
+
+ colors_table = self.calculate_items_table()
+ for key, colors in colors_table.iteritems():
+ self.transformation_table[colors].append(key)
+
+
+ # WRITE COLOR MATRICES
+ def write_all_matrices(self):
+ print("Writing matrices")
+ self.foreach_path(self.write_matrix)
+
+
+ def write_matrix(self, index):
+ colors = map(lambda x: x[index], self.transformation_table)
+
+ imgmk = ImageMaker(colors)
+ imgmk.make()
+ imgmk.save(self.new_filename(self.paths[index], "png"))
+ self.imgs.append(imgmk)
+
+
+ def new_filename(self, bin_path, postfix):
+ folder = path.dirname(bin_path)
+ style_name = self.calculate_style_type_postfix(bin_path)
+ return path.join(folder, "static_colors{}.{}".format(style_name, postfix))
+
+
+ def calculate_style_type_postfix(self, bin_path):
+ return path.basename(bin_path)[len("drules_proto"):-4]
+
+
+ # UPDATE BINS
+ def invert_transformation_table(self):
+ for element, i in zip(
+ self.transformation_table.iteritems(),
+ xrange(len(self.transformation_table))
+ ):
+ for key in element[1]:
+ self.inverted_transformation_table[key] = i
+
+
+ def update_all_bins(self):
+ print("Updating bin files")
+ self.invert_transformation_table()
+ self.foreach_path(self.update_one_bin)
+
+
+ def update_one_bin(self, index):
+ self.recursive_iterator(
+ self.drules[index], "", index, self.updater_factory(index)
+ )
+
+
+ def write_all_bins(self):
+ print("Writing bin files")
+ self.foreach_path(self.write_one_bin)
+
+
+ def write_one_bin(self, path_index):
+ new_bin_name = self.paths[path_index]
+ with open(new_bin_name, "wb") as outfile:
+ outfile.write(self.drules[path_index].SerializePartialToString())
+
+ txt_filename = path.splitext(new_bin_name)[0] + ".txt"
+ with open(txt_filename, "wb") as outfile:
+ outfile.write(unicode(self.drules[path_index]))
+
+
+ def updater_factory(self, path_index):
+ def updater(sub_drules, index_list):
+ for key, color_key, color in self.item_key_generator(sub_drules, index_list, color_fields):
+ key_2 = self.key_correspondences[path_index][key]
+ x, y = self.get_color_coords(path_index, key_2)
+ self.verify_color_at_coords(path_index, (x, y), color)
+
+ setattr(sub_drules, "{}_x".format(color_key), x)
+ setattr(sub_drules, "{}_y".format(color_key), y)
+
+ return updater
+
+
+ def get_color_coords(self, path_index, key):
+ return self.imgs[path_index].linear_index_to_coords(
+ self.inverted_transformation_table[key]
+ )
+
+
+ def verify_color_at_coords(self, path_index, xy, orig_color):
+ tup_color = argb_to_tuple(orig_color)
+ tup_color_from_img = self.imgs[path_index].img.getpixel(xy)
+ assert tup_color == tup_color_from_img, "Colors in the image and the bin file are different"
diff --git a/tools/python/stylesheet/protobuf_util.py b/tools/python/stylesheet/style_comparator/protobuf_util.py
index c92f4e0285..162768ff08 100644
--- a/tools/python/stylesheet/protobuf_util.py
+++ b/tools/python/stylesheet/style_comparator/protobuf_util.py
@@ -1,5 +1,7 @@
from google.protobuf.descriptor import FieldDescriptor
from operator import attrgetter
+import drules_struct_pb2 as proto
+
def make_key(element, field_name=None):
key = ""
@@ -16,7 +18,15 @@ def make_key(element, field_name=None):
for key_part, fmt in keys_and_formats:
key += make_key_part(element, key_part, fmt)
- return key
+ key += relevant_class(element)
+ return key + "/"
+
+
+def relevant_class(element):
+ class_name = type(element).__name__
+ if class_name.endswith("RuleProto"):
+ return "/{{{}}}".format(class_name)
+ return ""
def make_key_part(element, field, formatr):
@@ -57,6 +67,16 @@ def has_color_fields(sub_drules):
return bool(color_fields(sub_drules))
+def non_color_fields(sub_drules):
+ return map(
+ attrgetter("name"),
+ filter(
+ lambda s: not s.name.endswith("color") and s.name != "priority",
+ list_leaves_fields(sub_drules)
+ )
+ )
+
+
def color_fields(sub_drules):
field_names = map(attrgetter("name"), list_leaves_fields(sub_drules))
# We rely on the convention that all the color field names end in "color".
@@ -64,3 +84,10 @@ def color_fields(sub_drules):
# Here we filter on HasField because an element can have optional fields,
# and when we try to access their fields, they will return their default values.
return filter(lambda x: sub_drules.HasField(x), color_field_names)
+
+
+def read_bin(filepath):
+ drules = proto.ContainerProto()
+ with open(filepath) as infile:
+ drules.ParseFromString(infile.read())
+ return drules
diff --git a/tools/python/stylesheet/style_comparator/style_comparator.py b/tools/python/stylesheet/style_comparator/style_comparator.py
new file mode 100644
index 0000000000..2d8db1a772
--- /dev/null
+++ b/tools/python/stylesheet/style_comparator/style_comparator.py
@@ -0,0 +1,95 @@
+from itertools import combinations
+
+from operator import itemgetter
+from os import path
+import re
+from protobuf_util import *
+from abstract_matrix_builder import AbstractMatrixBuilder
+
+FIND_TYPE_RE = re.compile("\{(.*)\}")
+
+CLASS_BIT_MASKS = {
+ "LineRuleProto": 1 << 2,
+ "AreaRuleProto": 1 << 3,
+ "SymbolRuleProto": 1 << 4,
+ "CaptionRuleProto": 1 << 5,
+ "PathTextRuleProto": 1 << 7,
+ "ShieldRuleProto": 1 << 8
+}
+
+
+class StyleComparator(AbstractMatrixBuilder):
+ def __init__(self, matrix_builder=None):
+ AbstractMatrixBuilder.__init__(self)
+ # This is to avoid reading bin files unnecessarily
+ if matrix_builder:
+ self.paths = matrix_builder.paths
+ self.drules = matrix_builder.drules
+ self.items_table = {}
+ self.changes = []
+
+
+ def comparator_reader_factory(self, index):
+ def reader(sub_drules, index_list):
+ for key, _, color in self.item_key_generator(sub_drules, index_list, non_color_fields):
+ self.all_elements[index][key] = color
+ return reader
+
+
+ def compare_styles(self):
+ print("Comparing styles")
+ self.parse_bins()
+ self.normalize_all_keys()
+ self.items_table = self.calculate_items_table()
+ index_combinations = combinations(range(len(self.paths)), 2)
+
+ for comb in index_combinations:
+ diff = self.compare(comb)
+ self.changes.append(
+ "{} {} {}".format(
+ path.basename(self.paths[comb[0]]),
+ path.basename(self.paths[comb[1]]),
+ diff
+ )
+ )
+
+
+ def write_changes_file(self):
+ print("Writing the changes file")
+ filename = path.join(path.dirname(self.paths[0]), "changes.txt")
+ with open(filename, "wb") as outfile:
+ for ch in self.changes:
+ outfile.write("{}\n".format(ch))
+
+
+ def compare(self, indices):
+ ind_1, ind_2 = indices
+
+ diff_items = map(
+ itemgetter(0),
+ filter(
+ lambda (key, val): val[ind_1] != val[ind_2],
+ self.items_table.iteritems()
+ )
+ )
+
+ all_protos = [
+ x[0] for x in
+ map(lambda s: FIND_TYPE_RE.findall(s), diff_items)
+ if x
+ ]
+
+ return sum(map(lambda x: CLASS_BIT_MASKS[x], frozenset(all_protos)))
+
+
+ def parse_one_bin(self, path_index):
+ def filter_fun(sub_drules):
+ return bool(non_color_fields(sub_drules))
+
+ self.recursive_iterator(
+ self.drules[path_index], "", path_index, self.comparator_reader_factory(path_index), filter_fn=filter_fun
+ )
+
+
+ def parse_bins(self):
+ self.foreach_path(self.parse_one_bin)