From 3157a46835bb71f134fbfe2a7c99532031a1b34f Mon Sep 17 00:00:00 2001 From: Timofey Date: Sat, 9 Apr 2016 12:11:16 +0300 Subject: [PATCH 01/13] The first iteration of the preprocessor. Produces a representation of the parsed css, warns about duplicate variable declarations. --- src/mapcss/Preprocessor.py | 108 +++++++++++++++++++++++++++++++++++ src/mapcss/logging_config.py | 4 ++ 2 files changed, 112 insertions(+) create mode 100644 src/mapcss/Preprocessor.py create mode 100644 src/mapcss/logging_config.py diff --git a/src/mapcss/Preprocessor.py b/src/mapcss/Preprocessor.py new file mode 100644 index 0000000..1519430 --- /dev/null +++ b/src/mapcss/Preprocessor.py @@ -0,0 +1,108 @@ +from __future__ import print_function +from os.path import exists +import logging +import re +import logging_config # Used to configure logging, you don't have to call anything from it. +from os.path import dirname, realpath, join + +IMPORT = re.compile(r'^\s*?@import\s*?\(\s*?\"(.*?)\"\s*?\)\s*?;', re.DOTALL | re.MULTILINE) +COMMENT = re.compile(r'/\*.*?\*/', re.DOTALL) +VARIABLE = re.compile(r'^\s*(@(?!import).*?)\s*?:\s*?(.*?);', re.DOTALL | re.MULTILINE) +BLOCK = re.compile(r'^\s*([^@{]*\{.*?\})', re.DOTALL | re.MULTILINE) + + + +class Preprocessor: + + def __init__(self, filepath): + self.blocks = [] + self.variables = dict() + self.text = "" + self.base_folder = "" + self.blocks = [] + self.filepath = realpath(filepath) + self.base_folder = realpath(dirname(filepath)) + + def process(self): + self.readfile() + + self.text = COMMENT.sub("", self.text) + self.process_variables() + self.process_imports() + # self._print_vars() + + self.process_blocks() + + def process_blocks(self): + blocks = BLOCK.findall(self.text) + for b in blocks: + self.blocks.append(re.sub("\s+", " ", b)) + + + def _print_vars(self): + for var in self.variables: + print("~{} : {} ({})".format(var, self.variables[var][0], self.variables[var][1])) + print("Finished vars") + + def process_variables(self): + variables = VARIABLE.findall(self.text) + for var in variables: + self.variables[var[0].strip()] = (var[1].strip(), self.filepath) + + + def process_imports(self): + imports = IMPORT.findall(self.text) + for imp in imports: + preprocessor = Preprocessor(realpath(join(self.base_folder, imp))) + preprocessor.process() + self.blocks.extend(preprocessor.blocks) + self.merge_variables(preprocessor.variables) + + + def merge_variables(self, other_variables): + for key in other_variables: + if key in self.variables: + logging.warning("Variable redeclaration, setting the new value: \n{}\nold: {}\nnew: {}\nFirst declared in {}".format(key, self.variables[key][0], other_variables[key][0], self.variables[key][1])) + first_declared = other_variables[key][1] if key not in self.variables else self.variables[key][1] + self.variables[key] = (other_variables[key][0], first_declared) + + + def substitute_variables(self): + substituted_blocks = [] + + for block in self.blocks: + for var in self.variables: + block = block.replace(var, self.variables[var][0]) + substituted_blocks.append(block) + if "@" in block: + logging.warning("Unbound variable found in block {}".format(block)) + + self.blocks = substituted_blocks + + + + + + + + + def readfile(self): + if not exists(self.filepath): + logging.error("The file {file} doesn't exist!".format(file=self.filepath)) + exit(1) + + with open(self.filepath) as f: + self.text = f.read() + + + + +if __name__ == "__main__": + prep = Preprocessor("../../testdata/clear/style-clear/test.mapcss") + + prep.process() + prep.substitute_variables() + + + + diff --git a/src/mapcss/logging_config.py b/src/mapcss/logging_config.py new file mode 100644 index 0000000..b6f1099 --- /dev/null +++ b/src/mapcss/logging_config.py @@ -0,0 +1,4 @@ +import logging.config + +logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG) + From 12dc9d242df2884a3325044c79d6bb7bcedaf832 Mon Sep 17 00:00:00 2001 From: Timofey Date: Sat, 9 Apr 2016 12:26:09 +0300 Subject: [PATCH 02/13] Now we clean up the files and warn if we have unparsed bits left. --- src/mapcss/Preprocessor.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/mapcss/Preprocessor.py b/src/mapcss/Preprocessor.py index 1519430..25bb515 100644 --- a/src/mapcss/Preprocessor.py +++ b/src/mapcss/Preprocessor.py @@ -11,7 +11,6 @@ BLOCK = re.compile(r'^\s*([^@{]*\{.*?\})', re.DOTALL | re.MULTILINE) - class Preprocessor: def __init__(self, filepath): @@ -23,16 +22,28 @@ def __init__(self, filepath): self.filepath = realpath(filepath) self.base_folder = realpath(dirname(filepath)) + + def clean(self): + self.text = COMMENT.sub("", self.text) + self.text = IMPORT.sub("", self.text) + self.text = VARIABLE.sub("", self.text) + self.text = BLOCK.sub("", self.text) + self.text = re.sub("\s*", "", self.text) + + def process(self): self.readfile() self.text = COMMENT.sub("", self.text) self.process_variables() self.process_imports() - # self._print_vars() - self.process_blocks() + self.clean() + if self.text: + logging.warning("Some text in the mapcss file couldn't be parsed:\n{}\n{}".format(self.filepath, self.text)) + + def process_blocks(self): blocks = BLOCK.findall(self.text) for b in blocks: @@ -62,7 +73,8 @@ def process_imports(self): def merge_variables(self, other_variables): for key in other_variables: if key in self.variables: - logging.warning("Variable redeclaration, setting the new value: \n{}\nold: {}\nnew: {}\nFirst declared in {}".format(key, self.variables[key][0], other_variables[key][0], self.variables[key][1])) + logging.warning("Variable redeclaration, setting the new value: \n{}\nold: {}\nnew: {}\nFirst declared in {}\nRedeclared in {}" + .format(key, self.variables[key][0], other_variables[key][0], self.variables[key][1], other_variables[key][1])) first_declared = other_variables[key][1] if key not in self.variables else self.variables[key][1] self.variables[key] = (other_variables[key][0], first_declared) @@ -80,12 +92,6 @@ def substitute_variables(self): self.blocks = substituted_blocks - - - - - - def readfile(self): if not exists(self.filepath): logging.error("The file {file} doesn't exist!".format(file=self.filepath)) @@ -95,11 +101,8 @@ def readfile(self): self.text = f.read() - - if __name__ == "__main__": prep = Preprocessor("../../testdata/clear/style-clear/test.mapcss") - prep.process() prep.substitute_variables() From 5233a88bd9593b765286ae29bd5566c9ba669125 Mon Sep 17 00:00:00 2001 From: Timofey Date: Mon, 11 Apr 2016 20:03:01 +0300 Subject: [PATCH 03/13] Working on putting the preprocessor into the existing flow. --- src/libkomwm.py | 2 +- src/mapcss/Preprocessor.py | 40 +++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 87636f9..602b520 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -120,7 +120,7 @@ def addPattern(dashes): # Get all mapcss dynamic tags from mapcss-dynamic.txt mapcss_dynamic_tags = set([line.rstrip() for line in open(os.path.join(ddir, 'mapcss-dynamic.txt'))]) - + # we can reuse the code above, we don't need to rewrite it. Just refactor it. # Parse style mapcss style = MapCSS(options.minzoom, options.maxzoom + 1) style.parse(filename = options.filename, static_tags = mapcss_static_tags, dynamic_tags = mapcss_dynamic_tags) diff --git a/src/mapcss/Preprocessor.py b/src/mapcss/Preprocessor.py index 25bb515..6531bc4 100644 --- a/src/mapcss/Preprocessor.py +++ b/src/mapcss/Preprocessor.py @@ -15,7 +15,7 @@ class Preprocessor: def __init__(self, filepath): self.blocks = [] - self.variables = dict() + self.variables = {} self.text = "" self.base_folder = "" self.blocks = [] @@ -101,11 +101,49 @@ def readfile(self): self.text = f.read() + +BLOCK_SPLITTER = re.compile(r'([^@{]*)\s*\{(.*?)\}', re.DOTALL | re.MULTILINE) + +class BlockSplitter: + + def __init__(self, preprocessed_blocks): + self.blocks = preprocessed_blocks + self.split_blocks = {} # selector : list of attributes + + def clean_split_by(self, string, separator): + return filter(lambda x: x != "", map(lambda x: x.strip(), string.split(separator))) + + def split_commas(self): + + comma_split_blocs = {} + for block in self.blocks: + found = BLOCK_SPLITTER.findall(block) + for entry in found: + keys = self.clean_split_by(entry[0], ",") + attributes = self.clean_split_by(entry[1], ";") + for key in keys: + if key in comma_split_blocs: + comma_split_blocs[key].extend(attributes) + else: + comma_split_blocs[key] = attributes + + + print(comma_split_blocs) + + pass + + + + + if __name__ == "__main__": prep = Preprocessor("../../testdata/clear/style-clear/test.mapcss") prep.process() prep.substitute_variables() + block_splitter = BlockSplitter(prep.blocks) + block_splitter.split_commas() + From 7dd9f79f2bedd5715c56f77115d971fcc6496a35 Mon Sep 17 00:00:00 2001 From: Timofey Date: Thu, 14 Apr 2016 17:31:47 +0300 Subject: [PATCH 04/13] The preprocessor now has two components. The second one splits the styles by tags with selectors and we get all styles for each tag (incl. selectors and subclasses), but for now we get a lot of duplicate styles. The current goal is to find out why we get so many duplicate styles and either prevent it or (anyway) filter them. After that the resulting css file/structure will be transformed into a tree. --- src/__init__.py | 0 src/csstree/BlockSplitter.py | 238 ++++++++++++++++++++++ src/csstree/CssTree/CssElement.py | 15 ++ src/csstree/CssTree/__init__.py | 35 ++++ src/{mapcss => csstree}/Preprocessor.py | 70 ++----- src/csstree/ToolChain.py | 18 ++ src/csstree/__init__.py | 0 src/{mapcss => csstree}/logging_config.py | 0 src/libkomwm.py | 155 ++++++++++---- test/compare_files.py | 10 + 10 files changed, 445 insertions(+), 96 deletions(-) create mode 100644 src/__init__.py create mode 100644 src/csstree/BlockSplitter.py create mode 100644 src/csstree/CssTree/CssElement.py create mode 100644 src/csstree/CssTree/__init__.py rename src/{mapcss => csstree}/Preprocessor.py (72%) create mode 100644 src/csstree/ToolChain.py create mode 100644 src/csstree/__init__.py rename src/{mapcss => csstree}/logging_config.py (100%) create mode 100644 test/compare_files.py diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/csstree/BlockSplitter.py b/src/csstree/BlockSplitter.py new file mode 100644 index 0000000..1f00a97 --- /dev/null +++ b/src/csstree/BlockSplitter.py @@ -0,0 +1,238 @@ +from __future__ import print_function +import re +import logging +import logging_config #to configure logging, no calls needed +from CssTree.CssElement import CssElement + + + +MIN_ZOOM = 1 +MAX_ZOOM = 19 + + +BLOCK_SPLITTER = re.compile(r'([^@{]*)\s*\{(.*?)\}', re.DOTALL | re.MULTILINE) +ZOOM = re.compile(r'(.*?)(\|z[\d\-]*?)?(\[.*)?') #deprecated +ONE_ZOOM = re.compile(r'(\d{1,2})$') +ZOOM_RANGE = re.compile(r'(\d{1,2})-(\d{1,2})$') +ZOOM_TO_MAX = re.compile(r'(\d{1,2})-$') + + +TAG_RE = re.compile(r'(^.*?)[\|\[$:]', re.MULTILINE) +ZOOM_RE = re.compile(r'.*?\|z([\d\-]*?)[\[$:]') +SELECTORS_RE = re.compile(r'(\[.*?\])') +SUB_RE = re.compile(r'.*:(.*)$') +ONE_SELECTOR_RE = re.compile(r'\[(.*?)\]') + + +class BlockSplitter: + """ + Should also be initializeable by a preprocessed file + """ + def __init__(self, preprocessed_blocks, write_to_file=False): + print("Num of input blocks is: {}".format(len(preprocessed_blocks))) + self.blocks = preprocessed_blocks + self.split_blocks = {} # selector : list of attributes + self.write_to_file = write_to_file + self.blocks_by_zoom_level = self.init_blocks_by_zoom_level() + print("Will write to file! {}".format(self.write_to_file)) + + + def init_blocks_by_zoom_level(self): + ret = {} + for i in range(MIN_ZOOM, MAX_ZOOM + 1): + ret[i] = {} #tag with selectors and subclasses to attributes, selectors must be sorted + return ret + + + def process(self): + self.split_commas() + self.process_blocks_by_zoom_level() + + if self.write_to_file: + self.write() + pass + + def process_blocks_by_zoom_level(self): + for zoom in self.blocks_by_zoom_level: + self.process_zoom_level(zoom, self.blocks_by_zoom_level[zoom]) + + def process_zoom_level(self, zoom, block): + clean_block = [] # another list of tuples + block_keys = sorted(block.keys()) + # block.sort(key=lambda x: x[1]) #sort the tuples by the 0th element + old_block = ("", []) + for tag in block_keys: + attrs = block[tag] + if tag == old_block[0] : + old_block[1].extend(attrs) + else: + if old_block[0]: + clean_block.append(old_block) + old_block = (tag, attrs) + self.blocks_by_zoom_level[zoom] = clean_block + + + def clean_split_by(self, string, separator): + return filter(lambda x: x != "", map(lambda x: x.strip(), string.split(separator))) + + + def all_zooms_in_css_range(self, str_range): + min_zoom = -1 + max_zoom = -1 + + if not str_range: + min_zoom = MIN_ZOOM + max_zoom = MAX_ZOOM + + elif ONE_ZOOM.match(str_range): + min_zoom = int(str_range) + max_zoom = min_zoom + + elif ZOOM_TO_MAX.match(str_range): + min_zoom = int(str_range[:-1]) + max_zoom = MAX_ZOOM + + elif ZOOM_RANGE.match(str_range): + found = ZOOM_RANGE.findall(str_range)[0] + (min_zoom, max_zoom) = map(lambda x: int(x), found) + + if max_zoom < 0 or min_zoom < 0 or max_zoom < min_zoom: + raise Exception("Failed to parse the zoom levels") + + max_zoom = MAX_ZOOM if max_zoom > MAX_ZOOM else max_zoom + min_zoom = MIN_ZOOM if min_zoom < MIN_ZOOM else min_zoom + + ret = [i for i in range(min_zoom, max_zoom + 1)] + + return ret + + + + + def split_keys_by_zoom(self, keys): + ret = [] + for key in keys: + parts_list = ZOOM.findall(key) + if not parts_list: + print("Unparseable key {}".format(key)) + continue + parts = parts_list[0] + if parts: + selector, zoom = parts[0], (parts[1] if not parts else parts[1][2:]) + print(">>>> {} : {}".format(selector, zoom)) + all_zooms = self.all_zooms_in_css_range(zoom) + ret.append(map(lambda x: (selector, x), all_zooms)) + else: + print("Got an unparseable node and zoom level: {}".format(key)) + logging.warning("NOTE THAT THIS TAG HAS BEEN IGNORED AND MUST BE PROCESSED IN THE FUTURE!") + return ret + + + def split_commas(self): + for block in self.blocks: + found = BLOCK_SPLITTER.findall(block) + for entry in found: + keys = self.clean_split_by(entry[0], ",") + attributes = self.clean_split_by(entry[1], ";") + + for key in keys: + elements = self.css_key_factory(key) + attrs = [] + for element in elements: + attrs.extend(attributes) + # subclass = "{}" + subclass = "::{}".format(element.subclass) if element.subclass else "" + resulting_tag = "{}{}{}".format(element.tag, sorted(element.selectors), subclass) + # print("{} -> {}".format(resulting_tag, attrs)) + if resulting_tag in self.blocks_by_zoom_level[element.zoom]: + self.blocks_by_zoom_level[element.zoom][resulting_tag].extend(attributes) + else: + self.blocks_by_zoom_level[element.zoom][resulting_tag] = attributes + # # print("{}::{}{}".format(element.tag, element.subclass, element.selectors)) + # # print("{} ::: {}".format(element, attributes)) + + # exit(2) + + # def sort_selectors(self, selectors): + # # found = ONE_SELECTOR_RE.findall(selectors) + # # if not found: + # # return "" + # if not selectors: + # return [] + # + # return "".join(map(lambda x: "[{}]".format(x), sorted(found))) + + + + + def old_write(self): + print("Writing split blocks, num blocks {}".format(len(self.split_blocks))) + with open("../../out/split_by_commas.mapcss", "w") as out_file: + for block in self.split_blocks: + out_file.write("{} {{\n".format(block)) + for attr in self.split_blocks[block]: + out_file.write(" {};\n".format(attr)) + out_file.write("}\n\n") + + + def write(self): + print("Writing split blocks by zoom, num blocks {}".format(len(self.blocks_by_zoom_level))) + with open("../../out/split_by_commas.mapcss", "w") as out_file: + for zoom in sorted(self.blocks_by_zoom_level.keys()): + blocks = self.blocks_by_zoom_level[zoom] + # for zoom, blocks in self.blocks_by_zoom_level: + out_file.write(" /* ===== ZOOM {} ===== */\n\n".format(zoom)) + + for tag, attrs in blocks: + out_file.write("{} {{\n".format(tag)) + for attr in attrs: + out_file.write(" {};\n".format(attr)) + # out_file.write(attrs) + out_file.write("}\n\n") + + + def css_key_factory(self, str_key): + # type: (str) -> [CssElement] + tag_list = TAG_RE.findall(str_key) + tag = tag_list[0] if tag_list else str_key + + zoom_list = ZOOM_RE.findall(str_key) + zoom = zoom_list[0] if zoom_list else "" + + # if "][" in str_key: + # print("Str key contains ][") + + selectors_list = SELECTORS_RE.findall(str_key) + # selectors = selectors_list[0] if selectors_list else "" + + str_key = TAG_RE.sub("", str_key) + str_key = ZOOM_RE.sub("", str_key) + str_key = SELECTORS_RE.sub("", str_key) + + subclass_list = SUB_RE.findall(str_key) + subclass = subclass_list[0] if subclass_list else "" + all_zooms = self.all_zooms_in_css_range(zoom) + ret = map(lambda z: CssElement(tag, z, selectors_list, subclass), all_zooms) + return ret + + + + + + +if __name__ == "__main__": + blockSplitter = BlockSplitter([]) + # print(blockSplitter.all_zooms_in_css_range("10")) + # print(blockSplitter.all_zooms_in_css_range("10-")) + # print(blockSplitter.all_zooms_in_css_range("10-12")) + # print(blockSplitter.all_zooms_in_css_range("10-25")) + + # print(blockSplitter.split_key_by_components("*::*")) + # print(blockSplitter.split_key_by_components("*")) + # print(blockSplitter.split_key_by_components("*|z12")) + # print(blockSplitter.split_key_by_components("*::int_name ")) + # print(blockSplitter.split_key_by_components("line|z5[highway=world_level]")) + # print(blockSplitter.css_key_factory("line|z17-18[highway=footway][tunnel?]::tunnelBackground")) + + + diff --git a/src/csstree/CssTree/CssElement.py b/src/csstree/CssTree/CssElement.py new file mode 100644 index 0000000..6c2da18 --- /dev/null +++ b/src/csstree/CssTree/CssElement.py @@ -0,0 +1,15 @@ +from __future__ import print_function + +import logging +import logging.config + + +class CssElement: + def __init__(self, tag, zoom, selectors, subclass): + self.tag = tag + self.zoom = zoom + self.selectors = selectors #[] + self.subclass = subclass + + def __repr__(self): + return "{}|z{}::{}".format(self.tag, self.zoom, self.subclass) \ No newline at end of file diff --git a/src/csstree/CssTree/__init__.py b/src/csstree/CssTree/__init__.py new file mode 100644 index 0000000..4f103ec --- /dev/null +++ b/src/csstree/CssTree/__init__.py @@ -0,0 +1,35 @@ +from __future__ import print_function + +import logging +import CssElement + + + + +class CssTree: + def __init__(self, min_zoom=1, max_zoom=19): + self.subtrees_by_zoom = {} + for i in range(min_zoom, max_zoom + 1): + self.subtrees_by_zoom[i] = CssSubtree() + + pass + + def add(self, csselement): + self.subtrees_by_zoom[csselement.zoom].add(csselement) + a = CssElement("a", "b", "c", "d") + + + + +class CssSubtree: + def __init__(self): + self.branches_by_tag = {} + pass + + def add(self, csselement): + pass + + +class CssNode: + def __init__(self): + pass \ No newline at end of file diff --git a/src/mapcss/Preprocessor.py b/src/csstree/Preprocessor.py similarity index 72% rename from src/mapcss/Preprocessor.py rename to src/csstree/Preprocessor.py index 6531bc4..c7e6e5a 100644 --- a/src/mapcss/Preprocessor.py +++ b/src/csstree/Preprocessor.py @@ -13,7 +13,7 @@ class Preprocessor: - def __init__(self, filepath): + def __init__(self, filepath, is_recursive=False, write_to_file=False): self.blocks = [] self.variables = {} self.text = "" @@ -21,6 +21,8 @@ def __init__(self, filepath): self.blocks = [] self.filepath = realpath(filepath) self.base_folder = realpath(dirname(filepath)) + self.is_recursive = is_recursive + self.write_to_file = write_to_file def clean(self): @@ -43,6 +45,16 @@ def process(self): if self.text: logging.warning("Some text in the mapcss file couldn't be parsed:\n{}\n{}".format(self.filepath, self.text)) + if not self.is_recursive: + self.substitute_variables() + + if self.write_to_file: + self.write() + + def write(self): + with open("{}.preprocessed".format(self.filepath), "w") as out_file: + out_file.writelines(map(lambda x : "{}\n\n".format(x), self.blocks)) + def process_blocks(self): blocks = BLOCK.findall(self.text) @@ -64,7 +76,7 @@ def process_variables(self): def process_imports(self): imports = IMPORT.findall(self.text) for imp in imports: - preprocessor = Preprocessor(realpath(join(self.base_folder, imp))) + preprocessor = Preprocessor(realpath(join(self.base_folder, imp)), is_recursive=True) preprocessor.process() self.blocks.extend(preprocessor.blocks) self.merge_variables(preprocessor.variables) @@ -82,8 +94,12 @@ def merge_variables(self, other_variables): def substitute_variables(self): substituted_blocks = [] + variable_keys = self.variables.keys() + variable_keys.sort(key=len, reverse=True) #reverse sort the var names by + # length so that we don't substitute parts of longer var names with values from vars with shorter names + for block in self.blocks: - for var in self.variables: + for var in variable_keys: block = block.replace(var, self.variables[var][0]) substituted_blocks.append(block) if "@" in block: @@ -99,51 +115,3 @@ def readfile(self): with open(self.filepath) as f: self.text = f.read() - - - -BLOCK_SPLITTER = re.compile(r'([^@{]*)\s*\{(.*?)\}', re.DOTALL | re.MULTILINE) - -class BlockSplitter: - - def __init__(self, preprocessed_blocks): - self.blocks = preprocessed_blocks - self.split_blocks = {} # selector : list of attributes - - def clean_split_by(self, string, separator): - return filter(lambda x: x != "", map(lambda x: x.strip(), string.split(separator))) - - def split_commas(self): - - comma_split_blocs = {} - for block in self.blocks: - found = BLOCK_SPLITTER.findall(block) - for entry in found: - keys = self.clean_split_by(entry[0], ",") - attributes = self.clean_split_by(entry[1], ";") - for key in keys: - if key in comma_split_blocs: - comma_split_blocs[key].extend(attributes) - else: - comma_split_blocs[key] = attributes - - - print(comma_split_blocs) - - pass - - - - - -if __name__ == "__main__": - prep = Preprocessor("../../testdata/clear/style-clear/test.mapcss") - prep.process() - prep.substitute_variables() - - block_splitter = BlockSplitter(prep.blocks) - block_splitter.split_commas() - - - - diff --git a/src/csstree/ToolChain.py b/src/csstree/ToolChain.py new file mode 100644 index 0000000..c39d7d7 --- /dev/null +++ b/src/csstree/ToolChain.py @@ -0,0 +1,18 @@ +from __future__ import print_function +from Preprocessor import Preprocessor +from BlockSplitter import BlockSplitter + + +if __name__ == "__main__": + + print("Toolchain") + + prep = Preprocessor("../../testdata/clear/style-clear/test.mapcss", write_to_file=True) + prep.process() + print("Toolchain, num blocks: {}".format(len(prep.blocks))) + # prep.substitute_variables() + + + + block_splitter = BlockSplitter(prep.blocks, write_to_file=True) + block_splitter.process() diff --git a/src/csstree/__init__.py b/src/csstree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mapcss/logging_config.py b/src/csstree/logging_config.py similarity index 100% rename from src/mapcss/logging_config.py rename to src/csstree/logging_config.py diff --git a/src/libkomwm.py b/src/libkomwm.py index 602b520..65d9d20 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -1,8 +1,12 @@ +from __future__ import print_function + from mapcss import MapCSS from optparse import OptionParser import os import csv import sys +import traceback + import mapcss.webcolors whatever_to_hex = mapcss.webcolors.webcolors.whatever_to_hex whatever_to_cairo = mapcss.webcolors.webcolors.whatever_to_cairo @@ -30,6 +34,7 @@ def to_boolean(s): else: return False, False # Invalid + def mwm_encode_color(colors, st, prefix='', default='black'): if prefix: prefix += "-" @@ -39,6 +44,7 @@ def mwm_encode_color(colors, st, prefix='', default='black'): colors.add(result) return result + def mwm_encode_image(st, prefix='icon', bgprefix='symbol'): if prefix: prefix += "-" @@ -50,67 +56,115 @@ def mwm_encode_image(st, prefix='icon', bgprefix='symbol'): handle = st.get(prefix + "image")[:-4] return handle, handle -def komap_mapswithme(options): - ddir = os.path.dirname(options.outfile) - classificator = {} - class_order = [] - class_tree = {} - - colors_file_name = os.path.join(ddir, 'colors.txt') +def read_colors_file(filepath): colors = set() - if os.path.exists(colors_file_name): - colors_in_file = open(colors_file_name, "r") - for colorLine in colors_in_file: - colors.add(int(colorLine)) - colors_in_file.close() + if os.path.exists(filepath): + with open(filepath) as color_file: + colors.update(map(lambda x: int(x), color_file)) + return colors - patterns = [] - def addPattern(dashes): - if dashes and dashes not in patterns: - patterns.append(dashes) - patterns_file_name = os.path.join(ddir, 'patterns.txt') - if os.path.exists(patterns_file_name): - patterns_in_file = open(patterns_file_name, "r") - for patternsLine in patterns_in_file: - addPattern([float(x) for x in patternsLine.split()]) - patterns_in_file.close() +def append_pattern(patterns, to_append): + if to_append: + patterns.add(to_append) + return patterns + + +def read_patterns_file(filepath): + patterns = set() + if os.path.exists(filepath): + with open(filepath, "r") as patterns_in_file: + for string_pattern in map(lambda x: x.split(), patterns_in_file): + patterns = append_pattern(patterns, tuple(map(lambda x: float(x), string_pattern))) + + return patterns + + +def parse_pair(pair): + """ + Args: + pair: list of 1 or 2 elements from the mapccs-mapping.csv + + Returns: + if the list contains 2 elements, returns {1st : 2nd} + if the list contains 1 element that starts with !, trips the ! and the ? from the key and returns : {1st : no} + if 1 element with no ! at the beginning, strips the ? from the key and returns: {1st : yes} + + >>> parse_pair(["one", "two"]) + {'one': 'two'} + + >>> parse_pair(["!one"]) + {'one': 'no'} + + >>> parse_pair(["!one?"]) + {'one': 'no'} + + >>> parse_pair(["one?"]) + {'one': 'yes'} + """ + kv = {} + if len(pair) == 1: + if pair[0]: + if pair[0].startswith("!"): + key = pair[0][1:].strip('?') + kv[key] = "no" + else: + key = pair[0].strip('?') + kv[key] = "yes" + else: + kv[pair[0]] = pair[1] + return kv + + +def read_class_hierarchy(ddir): + classificator = {} + class_order = [] + class_tree = {} - # Build classificator tree from mapcss-mapping.csv file types_file = open(os.path.join(ddir, 'types.txt'), "w") cnt = 1 for row in csv.reader(open(os.path.join(ddir, 'mapcss-mapping.csv')), delimiter=';'): while int(row[5]) > cnt: - print >> types_file, "mapswithme" + print(types_file.name + " row 5 > cnt: {} > {}".format(row[5], cnt)) cnt += 1 cnt += 1 cl = row[0].replace("|", "-") pairs = [i.strip(']').split("=") for i in row[1].split(',')[0].split('[')] kv = {} - for i in pairs: - if len(i) == 1: - if i[0]: - if i[0][0] == "!": - kv[i[0][1:].strip('?')] = "no" - else: - kv[i[0].strip('?')] = "yes" - else: - kv[i[0]] = i[1] + for pair in pairs: + kv.update(parse_pair(pair)) + classificator[cl] = kv if row[2] != "x": class_order.append(cl) - print >> types_file, row[0] + print("Appended {}".format(row[0])) else: # compatibility mode if row[6]: - print >> types_file, row[6] + print("Did not append {}, because {}".format(row[0], row[6])) else: - print >> types_file, "mapswithme" + print("Didn't append {}, col 6 doesnt exist".format(row[0])) class_tree[cl] = row[0] class_order.sort() types_file.close() + return class_order, class_tree, classificator + + +def komap_mapswithme(options): + ddir = os.path.dirname(options.outfile) + + + # debug files. Not quite sure why we read them. + colors_file_name = os.path.join(ddir, "colors.txt") + colors = read_colors_file(colors_file_name) + + patterns_file_name = os.path.join(ddir, 'patterns.txt') + patterns = read_patterns_file(patterns_file_name) + + # Build classificator tree from mapcss-mapping.csv file + (class_order, class_tree, classificator) = read_class_hierarchy(ddir) # Get all mapcss static tags which are used in mapcss-mapping.csv mapcss_static_tags = set() @@ -121,6 +175,7 @@ def addPattern(dashes): # Get all mapcss dynamic tags from mapcss-dynamic.txt mapcss_dynamic_tags = set([line.rstrip() for line in open(os.path.join(ddir, 'mapcss-dynamic.txt'))]) # we can reuse the code above, we don't need to rewrite it. Just refactor it. + # Parse style mapcss style = MapCSS(options.minzoom, options.maxzoom + 1) style.parse(filename = options.filename, static_tags = mapcss_static_tags, dynamic_tags = mapcss_dynamic_tags) @@ -253,7 +308,7 @@ def addPattern(dashes): dr_line.priority = min(int(st.get('z-index', 0) + 999), 20000) dashes = st.get('casing-dashes', st.get('dashes', [])) dr_line.dashdot.dd.extend(dashes) - addPattern(dr_line.dashdot.dd) + patterns = append_pattern(patterns, tuple(dr_line.dashdot.dd)) #debug thing dr_line.cap = dr_linecaps.get(st.get('casing-linecap', 'butt'), BUTTCAP) dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN) dr_element.lines.extend([dr_line]) @@ -277,7 +332,7 @@ def addPattern(dashes): dr_line.color = mwm_encode_color(colors, st) for i in st.get('dashes', []): dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE]) - addPattern(dr_line.dashdot.dd) + patterns = append_pattern(patterns, tuple(dr_line.dashdot.dd)) #debug thing dr_line.cap = dr_linecaps.get(st.get('linecap', 'butt'), BUTTCAP) dr_line.join = dr_linejoins.get(st.get('linejoin', 'round'), ROUNDJOIN) if '-x-me-line-priority' in st: @@ -433,26 +488,32 @@ def cmprepl(a, b): for k in viskeys: offset = " " * (k.count("|") - 1) for i in range(len(oldoffset) / 4, len(offset) / 4, -1): - print >> visibility_file, " " * i + "{}" - print >> classificator_file, " " * i + "{}" + print(visibility_file.name + (" " * i) + "{}") + print(classificator_file.name + (" " * i) + "{}") oldoffset = offset end = "-" if k in visnodes: end = "+" - print >> visibility_file, offset + k.split("|")[-2] + " " + visibility.get(k, "0" * (options.maxzoom + 1)) + " " + end - print >> classificator_file, offset + k.split("|")[-2] + " " + end + print(visibility_file.name + offset + k.split("|")[-2] + " " + visibility.get(k, "0" * (options.maxzoom + 1)) + " " + end) + print(classificator_file.name + offset + k.split("|")[-2] + " " + end) for i in range(len(offset) / 4, 0, -1): - print >> visibility_file, " " * i + "{}" - print >> classificator_file, " " * i + "{}" + print(visibility_file.name + (" " * i) + "{}") + print(classificator_file.name + (" " * i) + "{}") visibility_file.close() classificator_file.close() +# write debug files + write_colors_file(colors_file_name, colors) + write_patterns_file(patterns_file_name, patterns) + +def write_colors_file(colors_file_name, colors): colors_file = open(colors_file_name, "w") for c in sorted(colors): colors_file.write("%d\n" % (c)) colors_file.close() +def write_patterns_file(patterns_file_name, patterns): patterns_file = open(patterns_file_name, "w") for p in patterns: patterns_file.write("%s\n" % (' '.join(str(elem) for elem in p))) @@ -461,6 +522,9 @@ def cmprepl(a, b): # Main try: + # import doctest + # doctest.testmod() + parser = OptionParser() parser.add_option("-s", "--stylesheet", dest="filename", help="read MapCSS stylesheet from FILE", metavar="FILE") @@ -486,5 +550,6 @@ def cmprepl(a, b): exit(0) except Exception as e: - print >> sys.stderr, "Error\n" + str(e) + traceback.print_exc(e) + # print(sys.stderr, "Error\n" + str(e) exit(-1) diff --git a/test/compare_files.py b/test/compare_files.py new file mode 100644 index 0000000..ad6c420 --- /dev/null +++ b/test/compare_files.py @@ -0,0 +1,10 @@ +# a very simple test to compare two resulting files + +import filecmp + +string = "" + +if not filecmp.cmp("../testdata/initial.txt.txt", "../testdata/output.txt.txt"): + string = "NOT " + +print("The files are {}the same".format(string)) From c4dfb7790f748c50371da471dc1b41065a915a18 Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 15 Apr 2016 16:35:51 +0300 Subject: [PATCH 05/13] Small intermediate modifications to be thrown away. --- src/csstree/BlockSplitter.py | 21 ++++++--------------- src/csstree/Preprocessor.py | 4 +++- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/csstree/BlockSplitter.py b/src/csstree/BlockSplitter.py index 1f00a97..9d5941c 100644 --- a/src/csstree/BlockSplitter.py +++ b/src/csstree/BlockSplitter.py @@ -145,24 +145,15 @@ def split_commas(self): resulting_tag = "{}{}{}".format(element.tag, sorted(element.selectors), subclass) # print("{} -> {}".format(resulting_tag, attrs)) if resulting_tag in self.blocks_by_zoom_level[element.zoom]: + + # to remove soon + for a in attributes: + if a in self.blocks_by_zoom_level[element.zoom][resulting_tag]: + print("Duplicate attribute {} for tag {} on zoom {}".format(a, resulting_tag, element.zoom)) + self.blocks_by_zoom_level[element.zoom][resulting_tag].extend(attributes) else: self.blocks_by_zoom_level[element.zoom][resulting_tag] = attributes - # # print("{}::{}{}".format(element.tag, element.subclass, element.selectors)) - # # print("{} ::: {}".format(element, attributes)) - - # exit(2) - - # def sort_selectors(self, selectors): - # # found = ONE_SELECTOR_RE.findall(selectors) - # # if not found: - # # return "" - # if not selectors: - # return [] - # - # return "".join(map(lambda x: "[{}]".format(x), sorted(found))) - - def old_write(self): diff --git a/src/csstree/Preprocessor.py b/src/csstree/Preprocessor.py index c7e6e5a..e9f3a11 100644 --- a/src/csstree/Preprocessor.py +++ b/src/csstree/Preprocessor.py @@ -10,7 +10,9 @@ VARIABLE = re.compile(r'^\s*(@(?!import).*?)\s*?:\s*?(.*?);', re.DOTALL | re.MULTILINE) BLOCK = re.compile(r'^\s*([^@{]*\{.*?\})', re.DOTALL | re.MULTILINE) - +""" +We also need to remember where a certain block comes from so that we could warn about duplicate declarations +""" class Preprocessor: def __init__(self, filepath, is_recursive=False, write_to_file=False): From 812cb1b69cbb882d78687da5a282f725ae3661dc Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 15 Apr 2016 19:22:02 +0300 Subject: [PATCH 06/13] Added + tracing which file a particular style description was imported + warnings if we have several identical style descriptions in one block + in several blocks + in different files + filtering duplicate style descriptions --- src/csstree/BlockSplitter.py | 48 +++++++++++++++++++----------------- src/csstree/Preprocessor.py | 20 +++++++++------ 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/csstree/BlockSplitter.py b/src/csstree/BlockSplitter.py index 9d5941c..97285eb 100644 --- a/src/csstree/BlockSplitter.py +++ b/src/csstree/BlockSplitter.py @@ -128,42 +128,46 @@ def split_keys_by_zoom(self, keys): return ret + # to be refactored def split_commas(self): - for block in self.blocks: + for block, imported_from in self.blocks: found = BLOCK_SPLITTER.findall(block) for entry in found: keys = self.clean_split_by(entry[0], ",") - attributes = self.clean_split_by(entry[1], ";") + attributes = sorted(self.clean_split_by(entry[1], ";")) + + last_attr = "" + clean_attributes = [] + for a in attributes: + if a == last_attr: + logging.warning("Duplicate attribute {} for tag/zoom {} imported from {}".format(a, keys, imported_from)) + continue + clean_attributes.append(a) + + for key in keys: elements = self.css_key_factory(key) - attrs = [] + for element in elements: - attrs.extend(attributes) - # subclass = "{}" subclass = "::{}".format(element.subclass) if element.subclass else "" - resulting_tag = "{}{}{}".format(element.tag, sorted(element.selectors), subclass) - # print("{} -> {}".format(resulting_tag, attrs)) - if resulting_tag in self.blocks_by_zoom_level[element.zoom]: + resulting_tag = "{}{}{}".format(element.tag, "".join(sorted(element.selectors)), subclass) - # to remove soon - for a in attributes: + if resulting_tag in self.blocks_by_zoom_level[element.zoom]: + filtered_attributes = [] + for a in clean_attributes: if a in self.blocks_by_zoom_level[element.zoom][resulting_tag]: - print("Duplicate attribute {} for tag {} on zoom {}".format(a, resulting_tag, element.zoom)) + print("Duplicate attribute {} for tag {} on zoom {} imported from {}".format(a, resulting_tag, element.zoom, imported_from)) + else: + filtered_attributes.append(a) - self.blocks_by_zoom_level[element.zoom][resulting_tag].extend(attributes) + self.blocks_by_zoom_level[element.zoom][resulting_tag].update(self.map_attrs_to_import_source(filtered_attributes, imported_from)) else: - self.blocks_by_zoom_level[element.zoom][resulting_tag] = attributes + self.blocks_by_zoom_level[element.zoom][resulting_tag] = self.map_attrs_to_import_source(clean_attributes, imported_from) - def old_write(self): - print("Writing split blocks, num blocks {}".format(len(self.split_blocks))) - with open("../../out/split_by_commas.mapcss", "w") as out_file: - for block in self.split_blocks: - out_file.write("{} {{\n".format(block)) - for attr in self.split_blocks[block]: - out_file.write(" {};\n".format(attr)) - out_file.write("}\n\n") + def map_attrs_to_import_source(self, attributes, imported_from): + return dict(map(lambda x: (x, imported_from), attributes)) def write(self): @@ -177,7 +181,7 @@ def write(self): for tag, attrs in blocks: out_file.write("{} {{\n".format(tag)) for attr in attrs: - out_file.write(" {};\n".format(attr)) + out_file.write(" {}; /* == {} == */\n".format(attr, attrs[attr])) # out_file.write(attrs) out_file.write("}\n\n") diff --git a/src/csstree/Preprocessor.py b/src/csstree/Preprocessor.py index e9f3a11..0b0d9fa 100644 --- a/src/csstree/Preprocessor.py +++ b/src/csstree/Preprocessor.py @@ -15,16 +15,17 @@ """ class Preprocessor: - def __init__(self, filepath, is_recursive=False, write_to_file=False): + def __init__(self, filepath, is_recursive=False, write_to_file=False, already_imported=[]): self.blocks = [] self.variables = {} self.text = "" self.base_folder = "" - self.blocks = [] + self.blocks = [] # list of tuples (block, imported_from_url) self.filepath = realpath(filepath) self.base_folder = realpath(dirname(filepath)) self.is_recursive = is_recursive self.write_to_file = write_to_file + self.already_imported = already_imported def clean(self): @@ -55,13 +56,13 @@ def process(self): def write(self): with open("{}.preprocessed".format(self.filepath), "w") as out_file: - out_file.writelines(map(lambda x : "{}\n\n".format(x), self.blocks)) + out_file.writelines(map(lambda x : "{} /* {} */\n\n".format(x[0], x[1]), self.blocks)) def process_blocks(self): blocks = BLOCK.findall(self.text) for b in blocks: - self.blocks.append(re.sub("\s+", " ", b)) + self.blocks.append((re.sub("\s+", " ", b), self.filepath)) def _print_vars(self): @@ -78,10 +79,15 @@ def process_variables(self): def process_imports(self): imports = IMPORT.findall(self.text) for imp in imports: - preprocessor = Preprocessor(realpath(join(self.base_folder, imp)), is_recursive=True) + imp_path = realpath(join(self.base_folder, imp)) + if imp_path in self.already_imported: + continue + preprocessor = Preprocessor(imp_path, is_recursive=True, already_imported=self.already_imported) preprocessor.process() self.blocks.extend(preprocessor.blocks) self.merge_variables(preprocessor.variables) + self.already_imported.append(imp_path) + self.already_imported.extend(preprocessor.already_imported) def merge_variables(self, other_variables): @@ -100,10 +106,10 @@ def substitute_variables(self): variable_keys.sort(key=len, reverse=True) #reverse sort the var names by # length so that we don't substitute parts of longer var names with values from vars with shorter names - for block in self.blocks: + for block, imported_from in self.blocks: for var in variable_keys: block = block.replace(var, self.variables[var][0]) - substituted_blocks.append(block) + substituted_blocks.append((block, imported_from)) if "@" in block: logging.warning("Unbound variable found in block {}".format(block)) From e21620c4ffd12bbf831ce73c9df800de9e0d976e Mon Sep 17 00:00:00 2001 From: Timofey Date: Sun, 17 Apr 2016 22:38:11 +0300 Subject: [PATCH 07/13] Made some refactorings: replaced lists of tuples by dictionaries Added sorting and filtering of attributes disregarding their values. --- src/csstree/BlockSplitter.py | 88 +++++++++++++++++++------------ src/csstree/CssTree/CssElement.py | 10 ++-- src/csstree/CssTree/Tag.py | 12 +++++ src/csstree/Preprocessor.py | 16 +++--- src/csstree/Util.py | 3 ++ 5 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 src/csstree/CssTree/Tag.py create mode 100644 src/csstree/Util.py diff --git a/src/csstree/BlockSplitter.py b/src/csstree/BlockSplitter.py index 97285eb..a8e2414 100644 --- a/src/csstree/BlockSplitter.py +++ b/src/csstree/BlockSplitter.py @@ -3,7 +3,10 @@ import logging import logging_config #to configure logging, no calls needed from CssTree.CssElement import CssElement +from collections import namedtuple +from Util import StringWithSource +Block = namedtuple("Block", "name attrs") MIN_ZOOM = 1 @@ -29,12 +32,9 @@ class BlockSplitter: Should also be initializeable by a preprocessed file """ def __init__(self, preprocessed_blocks, write_to_file=False): - print("Num of input blocks is: {}".format(len(preprocessed_blocks))) self.blocks = preprocessed_blocks - self.split_blocks = {} # selector : list of attributes self.write_to_file = write_to_file self.blocks_by_zoom_level = self.init_blocks_by_zoom_level() - print("Will write to file! {}".format(self.write_to_file)) def init_blocks_by_zoom_level(self): @@ -52,26 +52,42 @@ def process(self): self.write() pass + def process_blocks_by_zoom_level(self): for zoom in self.blocks_by_zoom_level: self.process_zoom_level(zoom, self.blocks_by_zoom_level[zoom]) + + def process_zoom_level(self, zoom, block): - clean_block = [] # another list of tuples + clean_block = {} block_keys = sorted(block.keys()) - # block.sort(key=lambda x: x[1]) #sort the tuples by the 0th element - old_block = ("", []) + old_block = Block("", []) for tag in block_keys: attrs = block[tag] - if tag == old_block[0] : - old_block[1].extend(attrs) + if tag == old_block.name: + old_block.attrs.extend(attrs) else: - if old_block[0]: - clean_block.append(old_block) - old_block = (tag, attrs) + if old_block.name: + clean_block[old_block.name] = self.parse_attributes(old_block.attrs, tag, zoom) + old_block = Block(tag, attrs) self.blocks_by_zoom_level[zoom] = clean_block + def parse_attributes(self, list_of_attrs, tag, zoom): + ret = {} #attribute : StringWithSource(value, imported_from) + for attr, source in list_of_attrs.iteritems(): + try: + key, val = map(str.strip, attr.split(":")) + except: + print("attr[0] is {}".format(attr)) + if key in ret: + logging.warning("Duplicate value for attribute {} ({}) for tag {} on zoom {}. First declared in {}".format(key, val, tag, zoom, ret[key].source)) + + ret[key] = StringWithSource(val, source) + return ret + + def clean_split_by(self, string, separator): return filter(lambda x: x != "", map(lambda x: x.strip(), string.split(separator))) @@ -107,8 +123,6 @@ def all_zooms_in_css_range(self, str_range): return ret - - def split_keys_by_zoom(self, keys): ret = [] for key in keys: @@ -118,7 +132,7 @@ def split_keys_by_zoom(self, keys): continue parts = parts_list[0] if parts: - selector, zoom = parts[0], (parts[1] if not parts else parts[1][2:]) + selector, zoom = parts[0], (parts[1] if not parts else parts[1][2:]) # FIXME does this condition work because we have if parts above print(">>>> {} : {}".format(selector, zoom)) all_zooms = self.all_zooms_in_css_range(zoom) ret.append(map(lambda x: (selector, x), all_zooms)) @@ -128,38 +142,47 @@ def split_keys_by_zoom(self, keys): return ret + def clean_up_attribute_block(self, attributes, key, imported_from): + last_attr = "" + clean_attributes = [] + for a in attributes: + if a == last_attr: + logging.warning( + "Duplicate attribute {} for tag/zoom {} imported from {}".format(a, key, imported_from)) + continue + clean_attributes.append(a) + return clean_attributes + + + def filter_attributes_against_processed(self, clean_attributes, zoom, resulting_tag, imported_from): + filtered_attributes = [] + for a in clean_attributes: + if a in self.blocks_by_zoom_level[zoom][resulting_tag]: + print("Duplicate attribute {} for tag {} on zoom {} imported from {}".format(a, resulting_tag, zoom, imported_from)) + else: + filtered_attributes.append(a) + return filtered_attributes + # to be refactored def split_commas(self): for block, imported_from in self.blocks: found = BLOCK_SPLITTER.findall(block) for entry in found: keys = self.clean_split_by(entry[0], ",") - attributes = sorted(self.clean_split_by(entry[1], ";")) - - last_attr = "" - clean_attributes = [] - for a in attributes: - if a == last_attr: - logging.warning("Duplicate attribute {} for tag/zoom {} imported from {}".format(a, keys, imported_from)) - continue - clean_attributes.append(a) - + attributes = sorted(self.clean_split_by(entry[1], ";")) #TODO attributes should also be a dictionary + clean_attributes = self.clean_up_attribute_block(attributes, entry[0], imported_from) for key in keys: elements = self.css_key_factory(key) for element in elements: subclass = "::{}".format(element.subclass) if element.subclass else "" + # TODO Make TAG a separate type resulting_tag = "{}{}{}".format(element.tag, "".join(sorted(element.selectors)), subclass) if resulting_tag in self.blocks_by_zoom_level[element.zoom]: - filtered_attributes = [] - for a in clean_attributes: - if a in self.blocks_by_zoom_level[element.zoom][resulting_tag]: - print("Duplicate attribute {} for tag {} on zoom {} imported from {}".format(a, resulting_tag, element.zoom, imported_from)) - else: - filtered_attributes.append(a) + filtered_attributes = self.filter_attributes_against_processed(clean_attributes, element.zoom, resulting_tag, imported_from) self.blocks_by_zoom_level[element.zoom][resulting_tag].update(self.map_attrs_to_import_source(filtered_attributes, imported_from)) else: @@ -178,10 +201,10 @@ def write(self): # for zoom, blocks in self.blocks_by_zoom_level: out_file.write(" /* ===== ZOOM {} ===== */\n\n".format(zoom)) - for tag, attrs in blocks: + for tag, attrs in blocks.iteritems(): out_file.write("{} {{\n".format(tag)) for attr in attrs: - out_file.write(" {}; /* == {} == */\n".format(attr, attrs[attr])) + out_file.write(" {}: {}; /* == {} == */\n".format(attr, attrs[attr].string, attrs[attr].source)) # out_file.write(attrs) out_file.write("}\n\n") @@ -214,7 +237,6 @@ def css_key_factory(self, str_key): - if __name__ == "__main__": blockSplitter = BlockSplitter([]) # print(blockSplitter.all_zooms_in_css_range("10")) diff --git a/src/csstree/CssTree/CssElement.py b/src/csstree/CssTree/CssElement.py index 6c2da18..a3dd48d 100644 --- a/src/csstree/CssTree/CssElement.py +++ b/src/csstree/CssTree/CssElement.py @@ -1,9 +1,3 @@ -from __future__ import print_function - -import logging -import logging.config - - class CssElement: def __init__(self, tag, zoom, selectors, subclass): self.tag = tag @@ -12,4 +6,6 @@ def __init__(self, tag, zoom, selectors, subclass): self.subclass = subclass def __repr__(self): - return "{}|z{}::{}".format(self.tag, self.zoom, self.subclass) \ No newline at end of file + str_selectors = "".join(map(lambda x: "[{}]".format(x), self.selectors)) if self.selectors else "" + str_subclass = "::{}".format(self.subclass) if self.subclass else "" + return "{}|z{}{}{}".format(self.tag, self.zoom, str_selectors, str_subclass) \ No newline at end of file diff --git a/src/csstree/CssTree/Tag.py b/src/csstree/CssTree/Tag.py new file mode 100644 index 0000000..8c85b1c --- /dev/null +++ b/src/csstree/CssTree/Tag.py @@ -0,0 +1,12 @@ +@DeprecationWarning +class Tag: + def __init__(self, tag, zoom, selectors=[], subclass=None): + self.tag = tag + self.selectors = selectors + self.subclass = subclass + self.zoom = zoom + + def __repr__(self): + str_selectors = "".join(map(lambda x: "[{}]".format(x), self.selectors)) if self.selectors else "" + str_subclass = "::{}".format(self.subclass) if self.subclass else "" + return "{}|z{}{}{}".format(self.tag, self.zoom, str_selectors, str_subclass) diff --git a/src/csstree/Preprocessor.py b/src/csstree/Preprocessor.py index 0b0d9fa..fc717c4 100644 --- a/src/csstree/Preprocessor.py +++ b/src/csstree/Preprocessor.py @@ -4,6 +4,7 @@ import re import logging_config # Used to configure logging, you don't have to call anything from it. from os.path import dirname, realpath, join +from Util import StringWithSource IMPORT = re.compile(r'^\s*?@import\s*?\(\s*?\"(.*?)\"\s*?\)\s*?;', re.DOTALL | re.MULTILINE) COMMENT = re.compile(r'/\*.*?\*/', re.DOTALL) @@ -67,13 +68,16 @@ def process_blocks(self): def _print_vars(self): for var in self.variables: - print("~{} : {} ({})".format(var, self.variables[var][0], self.variables[var][1])) + print("~{} : {} ({})".format(var, self.variables[var].string, self.variables[var].source)) print("Finished vars") + + def process_variables(self): variables = VARIABLE.findall(self.text) for var in variables: - self.variables[var[0].strip()] = (var[1].strip(), self.filepath) + #self.variables[var[0].strip()] = (var[1].strip(), self.filepath) + self.variables[var[0].strip()] = StringWithSource(var[1].strip(), self.filepath) def process_imports(self): @@ -94,9 +98,9 @@ def merge_variables(self, other_variables): for key in other_variables: if key in self.variables: logging.warning("Variable redeclaration, setting the new value: \n{}\nold: {}\nnew: {}\nFirst declared in {}\nRedeclared in {}" - .format(key, self.variables[key][0], other_variables[key][0], self.variables[key][1], other_variables[key][1])) - first_declared = other_variables[key][1] if key not in self.variables else self.variables[key][1] - self.variables[key] = (other_variables[key][0], first_declared) + .format(key, self.variables[key].string, other_variables[key].string, self.variables[key].source, other_variables[key].source)) + first_declared = other_variables[key].source if key not in self.variables else self.variables[key].source + self.variables[key] = StringWithSource(other_variables[key].string, first_declared) def substitute_variables(self): @@ -108,7 +112,7 @@ def substitute_variables(self): for block, imported_from in self.blocks: for var in variable_keys: - block = block.replace(var, self.variables[var][0]) + block = block.replace(var, self.variables[var].string) substituted_blocks.append((block, imported_from)) if "@" in block: logging.warning("Unbound variable found in block {}".format(block)) diff --git a/src/csstree/Util.py b/src/csstree/Util.py new file mode 100644 index 0000000..fb9d985 --- /dev/null +++ b/src/csstree/Util.py @@ -0,0 +1,3 @@ +from collections import namedtuple + +StringWithSource = namedtuple("StringWithSource", "string source") From ec66127faf30bd4476e7edb80ea4b5cb19c0ffc3 Mon Sep 17 00:00:00 2001 From: Timofey Date: Tue, 19 Apr 2016 22:44:51 +0300 Subject: [PATCH 08/13] Fixed some bugs, cleaned up a bit; --- src/csstree/BlockSplitter.py | 31 +++++++++---------------------- src/csstree/CssTree/CssElement.py | 5 +++-- src/csstree/CssTree/Tag.py | 12 ------------ 3 files changed, 12 insertions(+), 36 deletions(-) delete mode 100644 src/csstree/CssTree/Tag.py diff --git a/src/csstree/BlockSplitter.py b/src/csstree/BlockSplitter.py index a8e2414..08723a9 100644 --- a/src/csstree/BlockSplitter.py +++ b/src/csstree/BlockSplitter.py @@ -58,7 +58,6 @@ def process_blocks_by_zoom_level(self): self.process_zoom_level(zoom, self.blocks_by_zoom_level[zoom]) - def process_zoom_level(self, zoom, block): clean_block = {} block_keys = sorted(block.keys()) @@ -77,10 +76,7 @@ def process_zoom_level(self, zoom, block): def parse_attributes(self, list_of_attrs, tag, zoom): ret = {} #attribute : StringWithSource(value, imported_from) for attr, source in list_of_attrs.iteritems(): - try: - key, val = map(str.strip, attr.split(":")) - except: - print("attr[0] is {}".format(attr)) + key, val = map(str.strip, attr.split(":", 1)) if key in ret: logging.warning("Duplicate value for attribute {} ({}) for tag {} on zoom {}. First declared in {}".format(key, val, tag, zoom, ret[key].source)) @@ -154,16 +150,16 @@ def clean_up_attribute_block(self, attributes, key, imported_from): return clean_attributes - def filter_attributes_against_processed(self, clean_attributes, zoom, resulting_tag, imported_from): + def filter_attributes_against_processed(self, clean_attributes, element, imported_from): filtered_attributes = [] for a in clean_attributes: - if a in self.blocks_by_zoom_level[zoom][resulting_tag]: - print("Duplicate attribute {} for tag {} on zoom {} imported from {}".format(a, resulting_tag, zoom, imported_from)) + if a in self.blocks_by_zoom_level[element.zoom][element]: + print("Duplicate attribute {} for tag {} imported from {}".format(a, element, imported_from)) else: filtered_attributes.append(a) return filtered_attributes - # to be refactored + def split_commas(self): for block, imported_from in self.blocks: found = BLOCK_SPLITTER.findall(block) @@ -177,16 +173,11 @@ def split_commas(self): elements = self.css_key_factory(key) for element in elements: - subclass = "::{}".format(element.subclass) if element.subclass else "" - # TODO Make TAG a separate type - resulting_tag = "{}{}{}".format(element.tag, "".join(sorted(element.selectors)), subclass) - - if resulting_tag in self.blocks_by_zoom_level[element.zoom]: - filtered_attributes = self.filter_attributes_against_processed(clean_attributes, element.zoom, resulting_tag, imported_from) - - self.blocks_by_zoom_level[element.zoom][resulting_tag].update(self.map_attrs_to_import_source(filtered_attributes, imported_from)) + if element in self.blocks_by_zoom_level[element.zoom]: + filtered_attributes = self.filter_attributes_against_processed(clean_attributes, element, imported_from) + self.blocks_by_zoom_level[element.zoom][element].update(self.map_attrs_to_import_source(filtered_attributes, imported_from)) else: - self.blocks_by_zoom_level[element.zoom][resulting_tag] = self.map_attrs_to_import_source(clean_attributes, imported_from) + self.blocks_by_zoom_level[element.zoom][element] = self.map_attrs_to_import_source(clean_attributes, imported_from) def map_attrs_to_import_source(self, attributes, imported_from): @@ -217,11 +208,7 @@ def css_key_factory(self, str_key): zoom_list = ZOOM_RE.findall(str_key) zoom = zoom_list[0] if zoom_list else "" - # if "][" in str_key: - # print("Str key contains ][") - selectors_list = SELECTORS_RE.findall(str_key) - # selectors = selectors_list[0] if selectors_list else "" str_key = TAG_RE.sub("", str_key) str_key = ZOOM_RE.sub("", str_key) diff --git a/src/csstree/CssTree/CssElement.py b/src/csstree/CssTree/CssElement.py index a3dd48d..616b69d 100644 --- a/src/csstree/CssTree/CssElement.py +++ b/src/csstree/CssTree/CssElement.py @@ -6,6 +6,7 @@ def __init__(self, tag, zoom, selectors, subclass): self.subclass = subclass def __repr__(self): - str_selectors = "".join(map(lambda x: "[{}]".format(x), self.selectors)) if self.selectors else "" + str_selectors = "".join(self.selectors) if self.selectors else "" str_subclass = "::{}".format(self.subclass) if self.subclass else "" - return "{}|z{}{}{}".format(self.tag, self.zoom, str_selectors, str_subclass) \ No newline at end of file + return "{}|z{}{}{}".format(self.tag, self.zoom, str_selectors, str_subclass) + diff --git a/src/csstree/CssTree/Tag.py b/src/csstree/CssTree/Tag.py deleted file mode 100644 index 8c85b1c..0000000 --- a/src/csstree/CssTree/Tag.py +++ /dev/null @@ -1,12 +0,0 @@ -@DeprecationWarning -class Tag: - def __init__(self, tag, zoom, selectors=[], subclass=None): - self.tag = tag - self.selectors = selectors - self.subclass = subclass - self.zoom = zoom - - def __repr__(self): - str_selectors = "".join(map(lambda x: "[{}]".format(x), self.selectors)) if self.selectors else "" - str_subclass = "::{}".format(self.subclass) if self.subclass else "" - return "{}|z{}{}{}".format(self.tag, self.zoom, str_selectors, str_subclass) From f00051d6564e5c368130b06876cb6be45e442548 Mon Sep 17 00:00:00 2001 From: Timofey Date: Wed, 4 May 2016 18:18:10 +0300 Subject: [PATCH 09/13] Added a sorter for selectors that will be used for creating the tree structure of our mapcss files. --- src/csstree/BlockSplitter.py | 110 +++++++++++++++++++++++++++--- src/csstree/CssTree/CssElement.py | 6 +- src/csstree/CssTree/__init__.py | 97 ++++++++++++++++++++++++-- src/csstree/ToolChain.py | 12 ++++ 4 files changed, 209 insertions(+), 16 deletions(-) diff --git a/src/csstree/BlockSplitter.py b/src/csstree/BlockSplitter.py index 08723a9..d9a86b4 100644 --- a/src/csstree/BlockSplitter.py +++ b/src/csstree/BlockSplitter.py @@ -6,6 +6,8 @@ from collections import namedtuple from Util import StringWithSource +#When we iterate over the selectors, we might count the frequencty of selector per tag and then use that info when creating the tree. + Block = namedtuple("Block", "name attrs") @@ -35,6 +37,8 @@ def __init__(self, preprocessed_blocks, write_to_file=False): self.blocks = preprocessed_blocks self.write_to_file = write_to_file self.blocks_by_zoom_level = self.init_blocks_by_zoom_level() + self.selector_counters_by_zoom_level = dict(zip([i for i in range(MIN_ZOOM, MAX_ZOOM + 1)], [AttributeFrequencyCounter() for i in range(MIN_ZOOM, MAX_ZOOM + 1)])) + print("") def init_blocks_by_zoom_level(self): @@ -171,13 +175,21 @@ def split_commas(self): for key in keys: elements = self.css_key_factory(key) + self.add_elements_to_zoom_level(elements, clean_attributes, imported_from) + + + def add_elements_to_zoom_level(self, elements, clean_attributes, imported_from): + for element in elements: + if element in self.blocks_by_zoom_level[element.zoom]: + filtered_attributes = self.filter_attributes_against_processed(clean_attributes, element, imported_from) + self.blocks_by_zoom_level[element.zoom][element].update(self.map_attrs_to_import_source(filtered_attributes, imported_from)) + else: + self.blocks_by_zoom_level[element.zoom][element] = self.map_attrs_to_import_source(clean_attributes, imported_from) + + #add to the frequency counter: + self.selector_counters_by_zoom_level[element.zoom].add_all(element.selectors) + - for element in elements: - if element in self.blocks_by_zoom_level[element.zoom]: - filtered_attributes = self.filter_attributes_against_processed(clean_attributes, element, imported_from) - self.blocks_by_zoom_level[element.zoom][element].update(self.map_attrs_to_import_source(filtered_attributes, imported_from)) - else: - self.blocks_by_zoom_level[element.zoom][element] = self.map_attrs_to_import_source(clean_attributes, imported_from) def map_attrs_to_import_source(self, attributes, imported_from): @@ -191,8 +203,11 @@ def write(self): blocks = self.blocks_by_zoom_level[zoom] # for zoom, blocks in self.blocks_by_zoom_level: out_file.write(" /* ===== ZOOM {} ===== */\n\n".format(zoom)) - - for tag, attrs in blocks.iteritems(): + keys = blocks.keys() + keys.sort(key=lambda x : x.tag) + for tag in keys: + tag.selectors = self.selector_counters_by_zoom_level[zoom].sort_list_using_frequencies(tag.selectors) + attrs = blocks[tag] out_file.write("{} {{\n".format(tag)) for attr in attrs: out_file.write(" {}: {}; /* == {} == */\n".format(attr, attrs[attr].string, attrs[attr].source)) @@ -221,10 +236,87 @@ def css_key_factory(self, str_key): return ret +class AttributeFrequencyCounter: + """ + The idea is that we should count which pairs are the most numerous, and which element of those pairs are. + """ + + def __init__(self): + self.pairs = {} + + + def __getitem__(self, item): + return self.pairs[item] + + + def add(self, one, two): + self._add_pair(one, two) + self._add_pair(two, one) + + + def __contains__(self, item): + return item in self.pairs + + + def sort_list_using_frequencies(self, selector_list): + tuples = sorted(map(lambda x: (len(self[x]) if x in self else 0, x), selector_list), key=lambda x: (x[0], x[1]), reverse=True) + return map(lambda x: x[1], tuples) + # for tu in tuples: + # print(">> {}".format(tu)) + # + # pass + + + def add_all(self, list_of_items): + i = 1 + for one in list_of_items: + if i >= len(list_of_items): + break + for two in list_of_items[i:]: + self.add(one, two) + i += 1 + + + + def _add_pair(self, one, two): + if one in self.pairs: + self.pairs[one].add_subnode(two) + else: + self.pairs[one] = AttributeFrequencyCounter.CounterNode(two) + + + class CounterNode: + def __init__(self, subnode): + self.subnodes = {} #dictionary of subnodes. Selector to count + self.length = 0 + self.add_subnode(subnode) + + + def add_subnode(self, selector): + if selector in self.subnodes: + self.subnodes[selector] += 1 + else: + self.subnodes[selector] = 1 + self.length += 1 + + def __len__(self): + return self.length + if __name__ == "__main__": + a = AttributeFrequencyCounter() + # a.add("a", "b") + # a.add("b", "c") + # a.add("b", "a") + a.add_all(["one", "two", "three"]) + a.add_all(["two", "three", "four"]) + + print(len(a["two"])) + a.sort_list_using_frequencies(["four", "one", "three", "two", "six"]) + + blockSplitter = BlockSplitter([]) # print(blockSplitter.all_zooms_in_css_range("10")) # print(blockSplitter.all_zooms_in_css_range("10-")) @@ -238,5 +330,5 @@ def css_key_factory(self, str_key): # print(blockSplitter.split_key_by_components("line|z5[highway=world_level]")) # print(blockSplitter.css_key_factory("line|z17-18[highway=footway][tunnel?]::tunnelBackground")) - + pass diff --git a/src/csstree/CssTree/CssElement.py b/src/csstree/CssTree/CssElement.py index 616b69d..144859c 100644 --- a/src/csstree/CssTree/CssElement.py +++ b/src/csstree/CssTree/CssElement.py @@ -2,7 +2,7 @@ class CssElement: def __init__(self, tag, zoom, selectors, subclass): self.tag = tag self.zoom = zoom - self.selectors = selectors #[] + self.selectors = sorted(selectors) #[] self.subclass = subclass def __repr__(self): @@ -10,3 +10,7 @@ def __repr__(self): str_subclass = "::{}".format(self.subclass) if self.subclass else "" return "{}|z{}{}{}".format(self.tag, self.zoom, str_selectors, str_subclass) +# class CssBlock: +# def __init__(self, element, styles): +# self.element = element +# self.styles = styles \ No newline at end of file diff --git a/src/csstree/CssTree/__init__.py b/src/csstree/CssTree/__init__.py index 4f103ec..10de3d6 100644 --- a/src/csstree/CssTree/__init__.py +++ b/src/csstree/CssTree/__init__.py @@ -1,8 +1,8 @@ from __future__ import print_function import logging -import CssElement - +from CssElement import CssElement +# from CssElement import CssBlock @@ -26,10 +26,95 @@ def __init__(self): self.branches_by_tag = {} pass - def add(self, csselement): - pass + def add_all(self, blocks): + + for block in blocks: + pass + + + def add(self, node): + if node.element.tag not in self.branches_by_tag: + self.branches_by_tag[node.element.tag] = node + return True + + if node.can_adopt(self.branches_by_tag[node.element.tag]): + node.add(self.branches_by_tag[node.element.tag]) + self.branches_by_tag[node.element.tag] = node + return True + + return self.branches_by_tag[node.element.tag].add(node) + + def __repr__(self): + ret = [] + for tag in self.branches_by_tag: + ret.append(str(self.branches_by_tag[tag])) + return "\n".join(ret) + + class CssNode: - def __init__(self): - pass \ No newline at end of file + def __init__(self, element, styles): + self.children = [] #list of nodes + self.element = element #CssElement + self.parent = None #if parent is none, i am root. + self.styles = styles # must be StringWithSource + pass + + def can_adopt(self, node): + # or self.element.subclass != node.element.subclass \ + if self.element.tag != node.element.tag and self.element.tag != "*" \ + or self.element.zoom != node.element.zoom \ + or len(self.element.selectors) >= len(node.element.selectors): + return False + + for i, sel in enumerate(self.element.selectors): + if sel != node.element.selectors[i]: + return False + + return True + pass + + def add(self, node): + if self.can_adopt(node): + if not self.children: #self has no children, + self.children.append(node) + return True + else: + for child in self.children: + if child.can_adopt(node): + return child.add(node) # self has children, and one of the children can adopt the new node + + # none of the children could adopt the node, maybe the node can adopt the children (or some of them) + possible_children_for_the_node = [] + for child in self.children: + if node.can_adopt(child): + possible_children_for_the_node.append(child) + + if possible_children_for_the_node: #if there are children that can be adopted by the node + for child in possible_children_for_the_node: + self.children.remove(child) + node.add(child) + self.children.append(node) + return True + + return False + + + def __repr__(self): + children_repr = "".join(map(lambda x: str(x), self.children)) + return "{}{}\n{}".format(" ", self.element, children_repr) + +if __name__ == "__main__": + node1 = CssNode(CssElement("tag", "10", ["a", "b", "c"], None), []) + node2 = CssNode(CssElement("tag", "10", ["a", "b", "c", "d"], None), []) + node3 = CssNode(CssElement("*", "10", ["a", "b", "c", "d"], None), []) + + css_subtree = CssSubtree() + print(css_subtree.add(node2)) + print(css_subtree.add(node1)) + print(css_subtree.add(node3)) + + print(css_subtree) + + # print(node1.can_adopt(node2)) \ No newline at end of file diff --git a/src/csstree/ToolChain.py b/src/csstree/ToolChain.py index c39d7d7..3e266c4 100644 --- a/src/csstree/ToolChain.py +++ b/src/csstree/ToolChain.py @@ -16,3 +16,15 @@ block_splitter = BlockSplitter(prep.blocks, write_to_file=True) block_splitter.process() + + + # for zoom in block_splitter.blocks_by_zoom_level: + # print("Zoom {}".format(zoom)) + # selectors = map(lambda x: x.selectors, block_splitter.blocks_by_zoom_level[zoom]) + # selectors = sorted(selectors, key=len) + # # for block in block_splitter.blocks_by_zoom_level[zoom]: + # # selectors = sorted(block.selectors, key = lambda x: len(x)) + # for selector in selectors: + # print("> {}".format(selector)) + + print("hello") \ No newline at end of file From 3022bf9c30c711fdce85403b61214d756bd1adcc Mon Sep 17 00:00:00 2001 From: Timofey Date: Thu, 5 May 2016 14:19:37 +0300 Subject: [PATCH 10/13] Did some optimizations to elements and block splitter. --- src/csstree/BlockSplitter.py | 16 +++------ src/csstree/CssTree/CssElement.py | 60 +++++++++++++++++++++++++++++-- src/csstree/CssTree/__init__.py | 2 +- 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/src/csstree/BlockSplitter.py b/src/csstree/BlockSplitter.py index d9a86b4..c7926a3 100644 --- a/src/csstree/BlockSplitter.py +++ b/src/csstree/BlockSplitter.py @@ -54,7 +54,6 @@ def process(self): if self.write_to_file: self.write() - pass def process_blocks_by_zoom_level(self): @@ -169,7 +168,7 @@ def split_commas(self): found = BLOCK_SPLITTER.findall(block) for entry in found: keys = self.clean_split_by(entry[0], ",") - attributes = sorted(self.clean_split_by(entry[1], ";")) #TODO attributes should also be a dictionary + attributes = sorted(self.clean_split_by(entry[1], ";")) #TODO attributes should also be a dictionary. Or should they? clean_attributes = self.clean_up_attribute_block(attributes, entry[0], imported_from) @@ -185,11 +184,11 @@ def add_elements_to_zoom_level(self, elements, clean_attributes, imported_from): self.blocks_by_zoom_level[element.zoom][element].update(self.map_attrs_to_import_source(filtered_attributes, imported_from)) else: self.blocks_by_zoom_level[element.zoom][element] = self.map_attrs_to_import_source(clean_attributes, imported_from) - - #add to the frequency counter: - self.selector_counters_by_zoom_level[element.zoom].add_all(element.selectors) + self.add_to_frequency_counter(element) + def add_to_frequency_counter(self, element): + self.selector_counters_by_zoom_level[element.zoom].add_all(element.selectors) def map_attrs_to_import_source(self, attributes, imported_from): @@ -261,10 +260,6 @@ def __contains__(self, item): def sort_list_using_frequencies(self, selector_list): tuples = sorted(map(lambda x: (len(self[x]) if x in self else 0, x), selector_list), key=lambda x: (x[0], x[1]), reverse=True) return map(lambda x: x[1], tuples) - # for tu in tuples: - # print(">> {}".format(tu)) - # - # pass def add_all(self, list_of_items): @@ -277,7 +272,6 @@ def add_all(self, list_of_items): i += 1 - def _add_pair(self, one, two): if one in self.pairs: self.pairs[one].add_subnode(two) @@ -303,8 +297,6 @@ def __len__(self): return self.length - - if __name__ == "__main__": a = AttributeFrequencyCounter() # a.add("a", "b") diff --git a/src/csstree/CssTree/CssElement.py b/src/csstree/CssTree/CssElement.py index 144859c..da3ec0d 100644 --- a/src/csstree/CssTree/CssElement.py +++ b/src/csstree/CssTree/CssElement.py @@ -1,3 +1,5 @@ +WILDCARD = "*" + class CssElement: def __init__(self, tag, zoom, selectors, subclass): self.tag = tag @@ -5,12 +7,66 @@ def __init__(self, tag, zoom, selectors, subclass): self.selectors = sorted(selectors) #[] self.subclass = subclass + def __repr__(self): - str_selectors = "".join(self.selectors) if self.selectors else "" + return self._my_str_repr() + + + def _my_str_repr(self, must_sort=False): + selectors = self.selectors + if must_sort: + selectors = sorted(selectors) + + str_selectors = "".join(selectors) if selectors else "" str_subclass = "::{}".format(self.subclass) if self.subclass else "" return "{}|z{}{}{}".format(self.tag, self.zoom, str_selectors, str_subclass) -# class CssBlock: + + def __eq__(self, other): + return (isinstance(other, self.__class__) + and self.__dict__ == other.__dict__) + + + def __ne__(self, other): + return not self.__eq__(other) + + + def __hash__(self): + return hash(self._my_str_repr(must_sort=True)) + + def can_adopt(self, css_element): + if self.zoom != css_element.zoom: + return False + + #my tag must be * or the same as theirs, my subclass must be * or the same as theirs, and my selectors must count 1 less than theirs and be a subset of theirs. + if self.tag != WILDCARD and self.tag != css_element.tag: + return False + if self.subclass != WILDCARD and self.subclass != css_element.subclass: + return False + if len(self.selectors) <= len(css_element.selectors): + return False + if not set(self.selectors).issubset(css_element.selectors): + return False + + return True + + + def selectors_length(self): + return len(self.selectors) + + + + +if __name__ == "__main__": + e1 = CssElement("*", "12",[], "*") + e2 = CssElement("*", "3", [], "*" ) + e3 = CssElement("*", "3", ["landuse"], "*") + + print(e1.can_adopt(e1)) + print(e1.can_adopt(e2)) + print(e2.can_adopt(e3)) + print(e3.can_adopt(e2)) + # class CssBlock: # def __init__(self, element, styles): # self.element = element # self.styles = styles \ No newline at end of file diff --git a/src/csstree/CssTree/__init__.py b/src/csstree/CssTree/__init__.py index 10de3d6..911388d 100644 --- a/src/csstree/CssTree/__init__.py +++ b/src/csstree/CssTree/__init__.py @@ -14,9 +14,9 @@ def __init__(self, min_zoom=1, max_zoom=19): pass + def add(self, csselement): self.subtrees_by_zoom[csselement.zoom].add(csselement) - a = CssElement("a", "b", "c", "d") From 333c108ff8763ee3afa8d75c6acbdc5e2367c123 Mon Sep 17 00:00:00 2001 From: Timofey Date: Thu, 5 May 2016 18:13:15 +0300 Subject: [PATCH 11/13] Now we have a tree that represents our mapcss; --- src/csstree/CssTree/CssElement.py | 10 +-- src/csstree/CssTree/__init__.py | 121 +++++++++++++++--------------- src/csstree/ToolChain.py | 13 +++- 3 files changed, 76 insertions(+), 68 deletions(-) diff --git a/src/csstree/CssTree/CssElement.py b/src/csstree/CssTree/CssElement.py index da3ec0d..5982721 100644 --- a/src/csstree/CssTree/CssElement.py +++ b/src/csstree/CssTree/CssElement.py @@ -43,7 +43,7 @@ def can_adopt(self, css_element): return False if self.subclass != WILDCARD and self.subclass != css_element.subclass: return False - if len(self.selectors) <= len(css_element.selectors): + if len(self.selectors) >= len(css_element.selectors): return False if not set(self.selectors).issubset(css_element.selectors): return False @@ -58,14 +58,12 @@ def selectors_length(self): if __name__ == "__main__": - e1 = CssElement("*", "12",[], "*") - e2 = CssElement("*", "3", [], "*" ) - e3 = CssElement("*", "3", ["landuse"], "*") + e1 = CssElement("line", 14, ["[piste:type=downhill]", "[piste:difficulty=intermediate]"], "") + e2 = CssElement("line", 14, ["[piste:type=downhill]"], "") print(e1.can_adopt(e1)) print(e1.can_adopt(e2)) - print(e2.can_adopt(e3)) - print(e3.can_adopt(e2)) + print(e2.can_adopt(e1)) # class CssBlock: # def __init__(self, element, styles): # self.element = element diff --git a/src/csstree/CssTree/__init__.py b/src/csstree/CssTree/__init__.py index 911388d..ab33fee 100644 --- a/src/csstree/CssTree/__init__.py +++ b/src/csstree/CssTree/__init__.py @@ -15,15 +15,16 @@ def __init__(self, min_zoom=1, max_zoom=19): pass - def add(self, csselement): - self.subtrees_by_zoom[csselement.zoom].add(csselement) + def add(self, node): + self.subtrees_by_zoom[node.element.zoom].add(node) class CssSubtree: def __init__(self): - self.branches_by_tag = {} + # self.branches_by_tag = {} + self.root = CssNode(None, None, root=True) pass def add_all(self, blocks): @@ -33,77 +34,75 @@ def add_all(self, blocks): def add(self, node): - if node.element.tag not in self.branches_by_tag: - self.branches_by_tag[node.element.tag] = node - return True - - if node.can_adopt(self.branches_by_tag[node.element.tag]): - node.add(self.branches_by_tag[node.element.tag]) - self.branches_by_tag[node.element.tag] = node - return True - - return self.branches_by_tag[node.element.tag].add(node) + return self.root.add(node) + # if node.element.tag not in self.branches_by_tag: + # self.branches_by_tag[node.element.tag] = node + # return True + # + # if node.can_adopt(self.branches_by_tag[node.element.tag]): + # node.add(self.branches_by_tag[node.element.tag]) + # self.branches_by_tag[node.element.tag] = node + # return True + # + # return self.branches_by_tag[node.element.tag].add(node) def __repr__(self): - ret = [] - for tag in self.branches_by_tag: - ret.append(str(self.branches_by_tag[tag])) - return "\n".join(ret) - - + return str(self.root.element) + # ret = [] + # for tag in self.branches_by_tag: + # ret.append(str(self.branches_by_tag[tag])) + # return "\n".join(ret) class CssNode: - def __init__(self, element, styles): - self.children = [] #list of nodes - self.element = element #CssElement - self.parent = None #if parent is none, i am root. - self.styles = styles # must be StringWithSource - pass + def __init__(self, element, styles, root=False): + self.children = [] + self.element = element + self.styles = styles # {: } + self.parent = None + self.am_root = root + + def is_root(self): + return self.am_root def can_adopt(self, node): - # or self.element.subclass != node.element.subclass \ - if self.element.tag != node.element.tag and self.element.tag != "*" \ - or self.element.zoom != node.element.zoom \ - or len(self.element.selectors) >= len(node.element.selectors): - return False + if self.is_root(): + return True + return self.element.can_adopt(node.element) - for i, sel in enumerate(self.element.selectors): - if sel != node.element.selectors[i]: - return False + def add(self, node): + if not self.can_adopt(node): + return False - return True - pass + if not self.children: #self has no children, + self.children.append(node) + node.parent = self + return True + else: + for child in self.children: + if child.can_adopt(node): + return child.add(node) # self has children, and one of the children can adopt the new node + + # none of the children could adopt the new node, maybe the node can adopt the children (or some of them) + possible_children_for_the_node = [] + for child in self.children: + if node.can_adopt(child): # FIXME here the new node's parent is always None!, thus the new node is considered Root, thus, it eats up all the children of the current root node + possible_children_for_the_node.append(child) + + if possible_children_for_the_node: #if there are children that can be adopted by the node + for child in possible_children_for_the_node: + self.children.remove(child) + node.add(child) + child.parent = node + self.children.append(node) + node.parent = self + return True - def add(self, node): - if self.can_adopt(node): - if not self.children: #self has no children, - self.children.append(node) - return True - else: - for child in self.children: - if child.can_adopt(node): - return child.add(node) # self has children, and one of the children can adopt the new node - - # none of the children could adopt the node, maybe the node can adopt the children (or some of them) - possible_children_for_the_node = [] - for child in self.children: - if node.can_adopt(child): - possible_children_for_the_node.append(child) - - if possible_children_for_the_node: #if there are children that can be adopted by the node - for child in possible_children_for_the_node: - self.children.remove(child) - node.add(child) - self.children.append(node) - return True - - return False def __repr__(self): - children_repr = "".join(map(lambda x: str(x), self.children)) - return "{}{}\n{}".format(" ", self.element, children_repr) + # children_repr = "".join(map(lambda x: str(x), self.children)) + return str(self.element) if __name__ == "__main__": node1 = CssNode(CssElement("tag", "10", ["a", "b", "c"], None), []) diff --git a/src/csstree/ToolChain.py b/src/csstree/ToolChain.py index 3e266c4..81bd980 100644 --- a/src/csstree/ToolChain.py +++ b/src/csstree/ToolChain.py @@ -1,7 +1,9 @@ from __future__ import print_function from Preprocessor import Preprocessor from BlockSplitter import BlockSplitter - +from BlockSplitter import Block +from CssTree import CssTree +from CssTree import CssNode if __name__ == "__main__": @@ -17,6 +19,15 @@ block_splitter = BlockSplitter(prep.blocks, write_to_file=True) block_splitter.process() + css_tree = CssTree() + for zoom in block_splitter.blocks_by_zoom_level: + for block in block_splitter.blocks_by_zoom_level[zoom]: + css_node = CssNode(block, block_splitter.blocks_by_zoom_level[zoom][block]) + css_tree.add(css_node) + + + + # for zoom in block_splitter.blocks_by_zoom_level: # print("Zoom {}".format(zoom)) From 4b9d8db351678597d285ed00176dc57d6c89dce3 Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 6 May 2016 17:49:54 +0300 Subject: [PATCH 12/13] Tree is here! We have finally finished writing the tree, and it seems to work so far. --- src/csstree/CssTree/__init__.py | 85 +++++++++++++++++++++++++-------- src/csstree/ToolChain.py | 3 ++ 2 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/csstree/CssTree/__init__.py b/src/csstree/CssTree/__init__.py index ab33fee..9228d00 100644 --- a/src/csstree/CssTree/__init__.py +++ b/src/csstree/CssTree/__init__.py @@ -1,9 +1,11 @@ from __future__ import print_function - +from copy import deepcopy import logging from CssElement import CssElement # from CssElement import CssBlock +WILDCARD = "*" +TAGS = ["node", "line", "way", "area"] class CssTree: @@ -12,46 +14,53 @@ def __init__(self, min_zoom=1, max_zoom=19): for i in range(min_zoom, max_zoom + 1): self.subtrees_by_zoom[i] = CssSubtree() - pass def add(self, node): self.subtrees_by_zoom[node.element.zoom].add(node) + # self.tags.add(node.element.tag) + # self.subclasses.add(node.element.subclass) + + + def finilize_tree(self): + for tag in TAGS: + for zoom, subtree in self.subtrees_by_zoom.iteritems(): + added = subtree.root.add(CssNode(CssElement(tag, zoom, [], None), [])) + logging.info("Just added empty element: {}, {}".format(tag, added)) + + #lets recursively traverse the tree and copy all the styles from higher elements to lower ones + for zoom, subtree in self.subtrees_by_zoom.iteritems(): + subtree.tricle_down_styles() + def write(self): + with open("../../out/inflated.mapcss", "w") as out_file: + for zoom, subtree in self.subtrees_by_zoom.iteritems(): + out_file.write(" /* === ZOOM {} === */\n\n".format(zoom)) + subtree.write(out_file) + class CssSubtree: def __init__(self): - # self.branches_by_tag = {} self.root = CssNode(None, None, root=True) pass - def add_all(self, blocks): - for block in blocks: - pass + def write(self, out_file): + self.root.write(out_file) def add(self, node): return self.root.add(node) - # if node.element.tag not in self.branches_by_tag: - # self.branches_by_tag[node.element.tag] = node - # return True - # - # if node.can_adopt(self.branches_by_tag[node.element.tag]): - # node.add(self.branches_by_tag[node.element.tag]) - # self.branches_by_tag[node.element.tag] = node - # return True - # - # return self.branches_by_tag[node.element.tag].add(node) + def __repr__(self): return str(self.root.element) - # ret = [] - # for tag in self.branches_by_tag: - # ret.append(str(self.branches_by_tag[tag])) - # return "\n".join(ret) + + + def tricle_down_styles(self): + self.root.tricle_down_styles() class CssNode: @@ -62,14 +71,30 @@ def __init__(self, element, styles, root=False): self.parent = None self.am_root = root + + def write(self, out_file): + if not self.is_root(): + out_file.write("{} {{\n".format(self.element)) + if self.styles: + for attr, val in self.styles.iteritems(): + #out_file.write(" {}: {}; /* {} */\n".format(attr, val.string, val.source)) + out_file.write(" {}: {};\n".format(attr, val.string)) + out_file.write("}\n\n") + + for child in self.children: + child.write(out_file) + + def is_root(self): return self.am_root + def can_adopt(self, node): if self.is_root(): return True return self.element.can_adopt(node.element) + def add(self, node): if not self.can_adopt(node): return False @@ -86,7 +111,7 @@ def add(self, node): # none of the children could adopt the new node, maybe the node can adopt the children (or some of them) possible_children_for_the_node = [] for child in self.children: - if node.can_adopt(child): # FIXME here the new node's parent is always None!, thus the new node is considered Root, thus, it eats up all the children of the current root node + if node.can_adopt(child): possible_children_for_the_node.append(child) if possible_children_for_the_node: #if there are children that can be adopted by the node @@ -99,6 +124,22 @@ def add(self, node): return True + def tricle_down_styles(self): + for child in self.children: + child.inherit_styles(self.styles) + child.tricle_down_styles() + + + def inherit_styles(self, styles): + if not styles: + return + for attr, value in styles.iteritems(): + if attr in self.styles: + logging.info("Rewriting a value from a higher element, {}: {} -> {}".format(attr, value.string, self.styles[attr].string)) + continue + self.styles[attr] = value + + def __repr__(self): # children_repr = "".join(map(lambda x: str(x), self.children)) @@ -109,6 +150,8 @@ def __repr__(self): node2 = CssNode(CssElement("tag", "10", ["a", "b", "c", "d"], None), []) node3 = CssNode(CssElement("*", "10", ["a", "b", "c", "d"], None), []) +# node|z19 + css_subtree = CssSubtree() print(css_subtree.add(node2)) print(css_subtree.add(node1)) diff --git a/src/csstree/ToolChain.py b/src/csstree/ToolChain.py index 81bd980..4197eae 100644 --- a/src/csstree/ToolChain.py +++ b/src/csstree/ToolChain.py @@ -25,6 +25,9 @@ css_node = CssNode(block, block_splitter.blocks_by_zoom_level[zoom][block]) css_tree.add(css_node) + css_tree.finilize_tree() + css_tree.write() + From e967dde5fc845e7587741fdc01d29b20c432fcc3 Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 6 May 2016 18:38:23 +0300 Subject: [PATCH 13/13] Did some cleaning up and refactoring. Removed some unnecessary classes. --- src/csstree/BlockSplitter.py | 104 ++---------------------------- src/csstree/CssTree/CssElement.py | 11 ++-- src/csstree/CssTree/__init__.py | 56 ++++------------ src/csstree/ToolChain.py | 1 - 4 files changed, 25 insertions(+), 147 deletions(-) diff --git a/src/csstree/BlockSplitter.py b/src/csstree/BlockSplitter.py index c7926a3..1335473 100644 --- a/src/csstree/BlockSplitter.py +++ b/src/csstree/BlockSplitter.py @@ -6,7 +6,6 @@ from collections import namedtuple from Util import StringWithSource -#When we iterate over the selectors, we might count the frequencty of selector per tag and then use that info when creating the tree. Block = namedtuple("Block", "name attrs") @@ -36,16 +35,7 @@ class BlockSplitter: def __init__(self, preprocessed_blocks, write_to_file=False): self.blocks = preprocessed_blocks self.write_to_file = write_to_file - self.blocks_by_zoom_level = self.init_blocks_by_zoom_level() - self.selector_counters_by_zoom_level = dict(zip([i for i in range(MIN_ZOOM, MAX_ZOOM + 1)], [AttributeFrequencyCounter() for i in range(MIN_ZOOM, MAX_ZOOM + 1)])) - print("") - - - def init_blocks_by_zoom_level(self): - ret = {} - for i in range(MIN_ZOOM, MAX_ZOOM + 1): - ret[i] = {} #tag with selectors and subclasses to attributes, selectors must be sorted - return ret + self.blocks_by_zoom_level = dict(map(lambda i: (i, {}), range(MIN_ZOOM, MAX_ZOOM +1))) def process(self): @@ -117,9 +107,7 @@ def all_zooms_in_css_range(self, str_range): max_zoom = MAX_ZOOM if max_zoom > MAX_ZOOM else max_zoom min_zoom = MIN_ZOOM if min_zoom < MIN_ZOOM else min_zoom - ret = [i for i in range(min_zoom, max_zoom + 1)] - - return ret + return range(min_zoom, max_zoom + 1) def split_keys_by_zoom(self, keys): @@ -127,16 +115,16 @@ def split_keys_by_zoom(self, keys): for key in keys: parts_list = ZOOM.findall(key) if not parts_list: - print("Unparseable key {}".format(key)) + logging.error("Unparseable key {}".format(key)) continue parts = parts_list[0] if parts: - selector, zoom = parts[0], (parts[1] if not parts else parts[1][2:]) # FIXME does this condition work because we have if parts above + selector, zoom = parts[0], parts[1][2:] print(">>>> {} : {}".format(selector, zoom)) all_zooms = self.all_zooms_in_css_range(zoom) ret.append(map(lambda x: (selector, x), all_zooms)) else: - print("Got an unparseable node and zoom level: {}".format(key)) + logging.error("Got an unparseable node and zoom level: {}".format(key)) logging.warning("NOTE THAT THIS TAG HAS BEEN IGNORED AND MUST BE PROCESSED IN THE FUTURE!") return ret @@ -146,8 +134,7 @@ def clean_up_attribute_block(self, attributes, key, imported_from): clean_attributes = [] for a in attributes: if a == last_attr: - logging.warning( - "Duplicate attribute {} for tag/zoom {} imported from {}".format(a, key, imported_from)) + logging.warning("Duplicate attribute {} for tag/zoom {} imported from {}".format(a, key, imported_from)) continue clean_attributes.append(a) return clean_attributes @@ -157,7 +144,7 @@ def filter_attributes_against_processed(self, clean_attributes, element, importe filtered_attributes = [] for a in clean_attributes: if a in self.blocks_by_zoom_level[element.zoom][element]: - print("Duplicate attribute {} for tag {} imported from {}".format(a, element, imported_from)) + logging.warning("Duplicate attribute {} for tag {} imported from {}".format(a, element, imported_from)) else: filtered_attributes.append(a) return filtered_attributes @@ -184,11 +171,6 @@ def add_elements_to_zoom_level(self, elements, clean_attributes, imported_from): self.blocks_by_zoom_level[element.zoom][element].update(self.map_attrs_to_import_source(filtered_attributes, imported_from)) else: self.blocks_by_zoom_level[element.zoom][element] = self.map_attrs_to_import_source(clean_attributes, imported_from) - self.add_to_frequency_counter(element) - - - def add_to_frequency_counter(self, element): - self.selector_counters_by_zoom_level[element.zoom].add_all(element.selectors) def map_attrs_to_import_source(self, attributes, imported_from): @@ -205,12 +187,10 @@ def write(self): keys = blocks.keys() keys.sort(key=lambda x : x.tag) for tag in keys: - tag.selectors = self.selector_counters_by_zoom_level[zoom].sort_list_using_frequencies(tag.selectors) attrs = blocks[tag] out_file.write("{} {{\n".format(tag)) for attr in attrs: out_file.write(" {}: {}; /* == {} == */\n".format(attr, attrs[attr].string, attrs[attr].source)) - # out_file.write(attrs) out_file.write("}\n\n") @@ -235,78 +215,8 @@ def css_key_factory(self, str_key): return ret -class AttributeFrequencyCounter: - """ - The idea is that we should count which pairs are the most numerous, and which element of those pairs are. - """ - - def __init__(self): - self.pairs = {} - - - def __getitem__(self, item): - return self.pairs[item] - - - def add(self, one, two): - self._add_pair(one, two) - self._add_pair(two, one) - - - def __contains__(self, item): - return item in self.pairs - - - def sort_list_using_frequencies(self, selector_list): - tuples = sorted(map(lambda x: (len(self[x]) if x in self else 0, x), selector_list), key=lambda x: (x[0], x[1]), reverse=True) - return map(lambda x: x[1], tuples) - - - def add_all(self, list_of_items): - i = 1 - for one in list_of_items: - if i >= len(list_of_items): - break - for two in list_of_items[i:]: - self.add(one, two) - i += 1 - - - def _add_pair(self, one, two): - if one in self.pairs: - self.pairs[one].add_subnode(two) - else: - self.pairs[one] = AttributeFrequencyCounter.CounterNode(two) - - - class CounterNode: - def __init__(self, subnode): - self.subnodes = {} #dictionary of subnodes. Selector to count - self.length = 0 - self.add_subnode(subnode) - - - def add_subnode(self, selector): - if selector in self.subnodes: - self.subnodes[selector] += 1 - else: - self.subnodes[selector] = 1 - self.length += 1 - - def __len__(self): - return self.length - if __name__ == "__main__": - a = AttributeFrequencyCounter() - # a.add("a", "b") - # a.add("b", "c") - # a.add("b", "a") - a.add_all(["one", "two", "three"]) - a.add_all(["two", "three", "four"]) - - print(len(a["two"])) - a.sort_list_using_frequencies(["four", "one", "three", "two", "six"]) blockSplitter = BlockSplitter([]) diff --git a/src/csstree/CssTree/CssElement.py b/src/csstree/CssTree/CssElement.py index 5982721..b36a711 100644 --- a/src/csstree/CssTree/CssElement.py +++ b/src/csstree/CssTree/CssElement.py @@ -4,7 +4,7 @@ class CssElement: def __init__(self, tag, zoom, selectors, subclass): self.tag = tag self.zoom = zoom - self.selectors = sorted(selectors) #[] + self.selectors = selectors #[] self.subclass = subclass @@ -34,11 +34,11 @@ def __ne__(self, other): def __hash__(self): return hash(self._my_str_repr(must_sort=True)) + def can_adopt(self, css_element): if self.zoom != css_element.zoom: return False - - #my tag must be * or the same as theirs, my subclass must be * or the same as theirs, and my selectors must count 1 less than theirs and be a subset of theirs. + #my tag must be * or the same as theirs, my subclass must be * or the same as theirs, and my selectors must count less than theirs and be a subset of theirs. if self.tag != WILDCARD and self.tag != css_element.tag: return False if self.subclass != WILDCARD and self.subclass != css_element.subclass: @@ -51,9 +51,6 @@ def can_adopt(self, css_element): return True - def selectors_length(self): - return len(self.selectors) - @@ -67,4 +64,4 @@ def selectors_length(self): # class CssBlock: # def __init__(self, element, styles): # self.element = element -# self.styles = styles \ No newline at end of file +# self.styles = styles diff --git a/src/csstree/CssTree/__init__.py b/src/csstree/CssTree/__init__.py index 9228d00..459c7d5 100644 --- a/src/csstree/CssTree/__init__.py +++ b/src/csstree/CssTree/__init__.py @@ -1,8 +1,6 @@ from __future__ import print_function -from copy import deepcopy import logging from CssElement import CssElement -# from CssElement import CssBlock WILDCARD = "*" TAGS = ["node", "line", "way", "area"] @@ -10,59 +8,34 @@ class CssTree: def __init__(self, min_zoom=1, max_zoom=19): - self.subtrees_by_zoom = {} + self.root_nodes_by_zoom = {} for i in range(min_zoom, max_zoom + 1): - self.subtrees_by_zoom[i] = CssSubtree() - + self.root_nodes_by_zoom[i] = CssNode(None, None, root=True) def add(self, node): - self.subtrees_by_zoom[node.element.zoom].add(node) - # self.tags.add(node.element.tag) - # self.subclasses.add(node.element.subclass) + self.root_nodes_by_zoom[node.element.zoom].add(node) def finilize_tree(self): for tag in TAGS: - for zoom, subtree in self.subtrees_by_zoom.iteritems(): - added = subtree.root.add(CssNode(CssElement(tag, zoom, [], None), [])) - logging.info("Just added empty element: {}, {}".format(tag, added)) + for zoom, subtree in self.root_nodes_by_zoom.iteritems(): + subtree.add(CssNode(CssElement(tag, zoom, [], None), [])) + self.trickle_down_styles() - #lets recursively traverse the tree and copy all the styles from higher elements to lower ones - for zoom, subtree in self.subtrees_by_zoom.iteritems(): - subtree.tricle_down_styles() + def trickle_down_styles(self): + for zoom, subtree in self.root_nodes_by_zoom.iteritems(): + subtree.trickle_down_styles() def write(self): with open("../../out/inflated.mapcss", "w") as out_file: - for zoom, subtree in self.subtrees_by_zoom.iteritems(): + for zoom, subtree in self.root_nodes_by_zoom.iteritems(): out_file.write(" /* === ZOOM {} === */\n\n".format(zoom)) subtree.write(out_file) -class CssSubtree: - def __init__(self): - self.root = CssNode(None, None, root=True) - pass - - - def write(self, out_file): - self.root.write(out_file) - - - def add(self, node): - return self.root.add(node) - - - def __repr__(self): - return str(self.root.element) - - - def tricle_down_styles(self): - self.root.tricle_down_styles() - - class CssNode: def __init__(self, element, styles, root=False): self.children = [] @@ -77,7 +50,7 @@ def write(self, out_file): out_file.write("{} {{\n".format(self.element)) if self.styles: for attr, val in self.styles.iteritems(): - #out_file.write(" {}: {}; /* {} */\n".format(attr, val.string, val.source)) + # out_file.write(" {}: {}; /* {} */\n".format(attr, val.string, val.source)) #our old Kothic doesn't like comments on the same line as an attribute out_file.write(" {}: {};\n".format(attr, val.string)) out_file.write("}\n\n") @@ -124,10 +97,10 @@ def add(self, node): return True - def tricle_down_styles(self): + def trickle_down_styles(self): for child in self.children: child.inherit_styles(self.styles) - child.tricle_down_styles() + child.trickle_down_styles() def inherit_styles(self, styles): @@ -140,7 +113,6 @@ def inherit_styles(self, styles): self.styles[attr] = value - def __repr__(self): # children_repr = "".join(map(lambda x: str(x), self.children)) return str(self.element) @@ -152,7 +124,7 @@ def __repr__(self): # node|z19 - css_subtree = CssSubtree() + css_subtree = CssNode(None, None, root=True) print(css_subtree.add(node2)) print(css_subtree.add(node1)) print(css_subtree.add(node3)) diff --git a/src/csstree/ToolChain.py b/src/csstree/ToolChain.py index 4197eae..40cd403 100644 --- a/src/csstree/ToolChain.py +++ b/src/csstree/ToolChain.py @@ -1,7 +1,6 @@ from __future__ import print_function from Preprocessor import Preprocessor from BlockSplitter import BlockSplitter -from BlockSplitter import Block from CssTree import CssTree from CssTree import CssNode