diff --git a/src/mapcss/Condition.py b/src/mapcss/Condition.py index 69c29b7..e89b39d 100644 --- a/src/mapcss/Condition.py +++ b/src/mapcss/Condition.py @@ -20,210 +20,210 @@ import re INVERSIONS = {"eq":"ne", "true":"false", "set":"unset", "<":">=", ">":"<="} in2 = {} for a,b in INVERSIONS.iteritems(): - in2[b] = a + in2[b] = a INVERSIONS.update(in2) del in2 class Condition: - def __init__(self, typez, params): - self.type=typez # eq, regex, lt, gt etc. - if type(params) == type(str()): - params = (params,) - self.params=params # e.g. ('highway','primary') - if typez == "regex": - self.regex = re.compile(self.params[0], re.I) + def __init__(self, typez, params): + self.type=typez # eq, regex, lt, gt etc. + if type(params) == type(str()): + params = (params,) + self.params=params # e.g. ('highway','primary') + if typez == "regex": + self.regex = re.compile(self.params[0], re.I) - self.compiled_regex = "" + self.compiled_regex = "" - def get_interesting_tags(self): - if self.params[0][:2] == "::": - return [] - return set([self.params[0]]) + def get_interesting_tags(self): + if self.params[0][:2] == "::": + return [] + return set([self.params[0]]) - def get_numerics(self): - if self.type in ("<", ">", ">=", "<="): - return self.params[0] - else: - return False + def get_numerics(self): + if self.type in ("<", ">", ">=", "<="): + return self.params[0] + else: + return False - def test(self, tags): - """ - Test a hash against this condition - """ - t = self.type - params = self.params - if t == 'eq': # don't compare tags against sublayers - if params[0][:2] == "::": - return params[1] - try: - if t == 'eq': - return tags[params[0]]==params[1] - if t == 'ne': - return tags.get(params[0], "")!=params[1] - if t == 'regex': - return bool(self.regex.match(tags[params[0]])) - if t == 'true': - return tags.get(params[0]) == 'yes' - if t == 'untrue': - return tags.get(params[0]) == 'no' - if t == 'set': - if params[0] in tags: - return tags[params[0]] != '' - return False - if t == 'unset': - if params[0] in tags: - return tags[params[0]] == '' - return True + def test(self, tags): + """ + Test a hash against this condition + """ + t = self.type + params = self.params + if t == 'eq': # don't compare tags against sublayers + if params[0][:2] == "::": + return params[1] + try: + if t == 'eq': + return tags[params[0]]==params[1] + if t == 'ne': + return tags.get(params[0], "")!=params[1] + if t == 'regex': + return bool(self.regex.match(tags[params[0]])) + if t == 'true': + return tags.get(params[0]) == 'yes' + if t == 'untrue': + return tags.get(params[0]) == 'no' + if t == 'set': + if params[0] in tags: + return tags[params[0]] != '' + return False + if t == 'unset': + if params[0] in tags: + return tags[params[0]] == '' + return True - if t == '<': - return (Number(tags[params[0]])< Number(params[1])) - if t == '<=': - return (Number(tags[params[0]])<=Number(params[1])) - if t == '>': - return (Number(tags[params[0]])> Number(params[1])) - if t == '>=': - return (Number(tags[params[0]])>=Number(params[1])) - except KeyError: - pass - return False; - def inverse(self): - """ - Get a not-A for condition A - """ - t = self.type - params = self.params - try: - return Condition(INVERSIONS[t], params) - if t == 'regex': - ### FIXME: learn how to invert regexes - return Condition("regex", params) - except KeyError: - pass - return self; + if t == '<': + return (Number(tags[params[0]])< Number(params[1])) + if t == '<=': + return (Number(tags[params[0]])<=Number(params[1])) + if t == '>': + return (Number(tags[params[0]])> Number(params[1])) + if t == '>=': + return (Number(tags[params[0]])>=Number(params[1])) + except KeyError: + pass + return False; + def inverse(self): + """ + Get a not-A for condition A + """ + t = self.type + params = self.params + try: + return Condition(INVERSIONS[t], params) + if t == 'regex': + ### FIXME: learn how to invert regexes + return Condition("regex", params) + except KeyError: + pass + return self; - def get_sql(self): - #params = [re.escape(x) for x in self.params] - params = self.params - t = self.type - if t == 'eq': # don't compare tags against sublayers - if params[0][:2] == "::": - return ("","") - try: - if t == 'eq': - return params[0], '"%s" = \'%s\''%(params[0], params[1]) - if t == 'ne': - return params[0], '("%s" != \'%s\' or "%s" IS NULL)'%(params[0], params[1],params[0]) - if t == 'regex': - return params[0], '"%s" ~ \'%s\''%(params[0],params[1].replace("'","\\'")) - if t == 'true': - return params[0], '"%s" = \'yes\''%(params[0]) - if t == 'untrue': - return params[0], '"%s" = \'no\''%(params[0]) - if t == 'set': - return params[0], '"%s" IS NOT NULL'%(params[0]) - if t == 'unset': - return params[0], '"%s" IS NULL'%(params[0]) - - if t == '<': - return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) < %s ELSE false END) """%(params[0],params[0],params[1]) - if t == '<=': - return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) <= %s ELSE false END)"""%(params[0],params[0],params[1]) - if t == '>': - return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) > %s ELSE false END) """%(params[0],params[0],params[1]) - if t == '>=': - return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) >= %s ELSE false END) """%(params[0],params[0],params[1]) - except KeyError: - pass - def get_mapnik_filter(self): - #params = [re.escape(x) for x in self.params] - params = self.params - t = self.type - if t == 'eq': # don't compare tags against sublayers - if params[0][:2] == "::": - return '' - try: - if t == 'eq': - return '[%s] = \'%s\''%(params[0], params[1]) - if t == 'ne': - return 'not([%s] = \'%s\')'%(params[0], params[1]) - if t == 'regex': - return '[%s].match(\'%s\')'%(params[0], params[1].replace("'","\\'")) - if t == 'true': - return '[%s] = \'yes\''%(params[0]) - if t == 'untrue': - return '[%s] = \'no\''%(params[0]) - if t == 'set': - return '[%s] != \'\''%(params[0]) - if t == 'unset': - return 'not([%s] != \'\')'%(params[0]) + def get_sql(self): + #params = [re.escape(x) for x in self.params] + params = self.params + t = self.type + if t == 'eq': # don't compare tags against sublayers + if params[0][:2] == "::": + return ("","") + try: + if t == 'eq': + return params[0], '"%s" = \'%s\''%(params[0], params[1]) + if t == 'ne': + return params[0], '("%s" != \'%s\' or "%s" IS NULL)'%(params[0], params[1],params[0]) + if t == 'regex': + return params[0], '"%s" ~ \'%s\''%(params[0],params[1].replace("'","\\'")) + if t == 'true': + return params[0], '"%s" = \'yes\''%(params[0]) + if t == 'untrue': + return params[0], '"%s" = \'no\''%(params[0]) + if t == 'set': + return params[0], '"%s" IS NOT NULL'%(params[0]) + if t == 'unset': + return params[0], '"%s" IS NULL'%(params[0]) - if t == '<': - return '[%s__num] < %s'%(params[0], float(params[1])) - if t == '<=': - return '[%s__num] <= %s'%(params[0], float(params[1])) - if t == '>': - return '[%s__num] > %s'%(params[0], float(params[1])) - if t == '>=': - return '[%s__num] >= %s'%(params[0], float(params[1])) - #return "" - except KeyError: - pass - def __repr__(self): - return "%s %s "%(self.type, repr(self.params)) - def __eq__(self, a): - return (self.params == a.params) and (self.type == a.type) + if t == '<': + return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) < %s ELSE false END) """%(params[0],params[0],params[1]) + if t == '<=': + return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) <= %s ELSE false END)"""%(params[0],params[0],params[1]) + if t == '>': + return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) > %s ELSE false END) """%(params[0],params[0],params[1]) + if t == '>=': + return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) >= %s ELSE false END) """%(params[0],params[0],params[1]) + except KeyError: + pass + def get_mapnik_filter(self): + #params = [re.escape(x) for x in self.params] + params = self.params + t = self.type + if t == 'eq': # don't compare tags against sublayers + if params[0][:2] == "::": + return '' + try: + if t == 'eq': + return '[%s] = \'%s\''%(params[0], params[1]) + if t == 'ne': + return 'not([%s] = \'%s\')'%(params[0], params[1]) + if t == 'regex': + return '[%s].match(\'%s\')'%(params[0], params[1].replace("'","\\'")) + if t == 'true': + return '[%s] = \'yes\''%(params[0]) + if t == 'untrue': + return '[%s] = \'no\''%(params[0]) + if t == 'set': + return '[%s] != \'\''%(params[0]) + if t == 'unset': + return 'not([%s] != \'\')'%(params[0]) - def and_with(self, c2): - """ - merges two rules with AND. - """ - #TODO: possible other minimizations - if c2.params[0] == self.params[0]: - if c2.params == self.params: - if c2.type == INVERSIONS[self.type]: # for example, eq AND ne = 0 - return False - if c2.type == self.type: - return (self,) + if t == '<': + return '[%s__num] < %s'%(params[0], float(params[1])) + if t == '<=': + return '[%s__num] <= %s'%(params[0], float(params[1])) + if t == '>': + return '[%s__num] > %s'%(params[0], float(params[1])) + if t == '>=': + return '[%s__num] >= %s'%(params[0], float(params[1])) + #return "" + except KeyError: + pass + def __repr__(self): + return "%s %s "%(self.type, repr(self.params)) + def __eq__(self, a): + return (self.params == a.params) and (self.type == a.type) - if self.type == ">=" and c2.type == "<=": # a<=2 and a>=2 --> a=2 - return (Condition ("eq", self.params),) - if self.type == "<=" and c2.type == ">=": - return (Condition ("eq", self.params),) - if self.type == ">" and c2.type == "<": - return False - if self.type == "<" and c2.type == ">": - return False + def and_with(self, c2): + """ + merges two rules with AND. + """ + #TODO: possible other minimizations + if c2.params[0] == self.params[0]: + if c2.params == self.params: + if c2.type == INVERSIONS[self.type]: # for example, eq AND ne = 0 + return False + if c2.type == self.type: + return (self,) - if c2.type == "eq" and self.type in ("ne", "<", ">"): - if c2.params[1] != self.params[1]: - return (c2,) - if self.type == "eq" and c2.type in ("ne", "<", ">"): - if c2.params[1] != self.params[1]: - return (self,) - if self.type == "eq" and c2.type == "eq": - if c2.params[1] != self.params[1]: - return False - if c2.type == "set" and self.type in ("eq","ne","regex", "<", "<=", ">", ">="): # a is set and a == b -> a == b - return (self,) - if c2.type == "unset" and self.type in ("eq","ne","regex", "<", "<=", ">", ">="): # a is unset and a == b -> impossible - return False - if self.type == "set" and c2.type in ("eq","ne","regex", "<", "<=", ">", ">="): - return (c2,) - if self.type == "unset" and c2.type in ("eq","ne","regex", "<", "<=", ">", ">="): - return False + if self.type == ">=" and c2.type == "<=": # a<=2 and a>=2 --> a=2 + return (Condition ("eq", self.params),) + if self.type == "<=" and c2.type == ">=": + return (Condition ("eq", self.params),) + if self.type == ">" and c2.type == "<": + return False + if self.type == "<" and c2.type == ">": + return False + + if c2.type == "eq" and self.type in ("ne", "<", ">"): + if c2.params[1] != self.params[1]: + return (c2,) + if self.type == "eq" and c2.type in ("ne", "<", ">"): + if c2.params[1] != self.params[1]: + return (self,) + if self.type == "eq" and c2.type == "eq": + if c2.params[1] != self.params[1]: + return False + if c2.type == "set" and self.type in ("eq","ne","regex", "<", "<=", ">", ">="): # a is set and a == b -> a == b + return (self,) + if c2.type == "unset" and self.type in ("eq","ne","regex", "<", "<=", ">", ">="): # a is unset and a == b -> impossible + return False + if self.type == "set" and c2.type in ("eq","ne","regex", "<", "<=", ">", ">="): + return (c2,) + if self.type == "unset" and c2.type in ("eq","ne","regex", "<", "<=", ">", ">="): + return False - return self, c2 + return self, c2 def Number(tt): - """ - Wrap float() not to produce exceptions - """ - - try: - return float(tt) - except ValueError: - return 0 \ No newline at end of file + """ + Wrap float() not to produce exceptions + """ + + try: + return float(tt) + except ValueError: + return 0 diff --git a/src/mapcss/Eval.py b/src/mapcss/Eval.py index c4f12a2..f073d56 100644 --- a/src/mapcss/Eval.py +++ b/src/mapcss/Eval.py @@ -18,157 +18,157 @@ NONE = "" class Eval(): - def __init__(self, s='eval()'): - """ - Parse expression and convert it into Python - """ - s = s.strip()[5:-1].strip() - self.expr_text = s - try: - self.expr = compile (s, "MapCSS expression", "eval") - except: - #print "Can't compile %s" % s - self.expr = compile ("0", "MapCSS expression", "eval") + def __init__(self, s='eval()'): + """ + Parse expression and convert it into Python + """ + s = s.strip()[5:-1].strip() + self.expr_text = s + try: + self.expr = compile (s, "MapCSS expression", "eval") + except: + #print "Can't compile %s" % s + self.expr = compile ("0", "MapCSS expression", "eval") - def extract_tags(self): - """ - Extracts list of tags that might be used in calculation - """ - def fake_compute(*x): - """ - Perform a fake computation. Always computes all the parameters, always returns 0. - WARNING: Might not cope with complex statements. - """ - for t in x: - q = x - return 0 - tags = set([]) - #print self.expr_text + def extract_tags(self): + """ + Extracts list of tags that might be used in calculation + """ + def fake_compute(*x): + """ + Perform a fake computation. Always computes all the parameters, always returns 0. + WARNING: Might not cope with complex statements. + """ + for t in x: + q = x + return 0 + tags = set([]) + #print self.expr_text - a = eval(self.expr,{},{ - "tag":lambda x: max([tags.add(x), " "]), - "prop": lambda x: "", - "num": lambda x: 0, - "metric": fake_compute, - "zmetric": fake_compute, - "str": lambda x: "", - "any": fake_compute, - "min": fake_compute, - "max": fake_compute, - }) + a = eval(self.expr,{},{ + "tag":lambda x: max([tags.add(x), " "]), + "prop": lambda x: "", + "num": lambda x: 0, + "metric": fake_compute, + "zmetric": fake_compute, + "str": lambda x: "", + "any": fake_compute, + "min": fake_compute, + "max": fake_compute, + }) - return tags + return tags - - def compute(self, tags={}, props = {}, xscale = 1., zscale = 0.5 ): - """ - Compute this eval() - """ - for k,v in tags.iteritems(): - try: - tag[k] = float(v) - except: - pass - try: - return str(eval(self.expr, {}, { - "tag":lambda x: tags.get(x,""), - "prop":lambda x: props.get(x,""), - "num": m_num, - "metric": lambda x: m_metric(x, xscale), - "zmetric": lambda x: m_metric(x, zscale), - "str": str, - "any": m_any, - "min": m_min, - "max": m_max, - "cond": m_cond, - "boolean": m_boolean - })) - except: - return "" - - def __repr__(self): - return "eval(%s)"%repr(self.expr) + def compute(self, tags={}, props = {}, xscale = 1., zscale = 0.5 ): + """ + Compute this eval() + """ + for k,v in tags.iteritems(): + try: + tag[k] = float(v) + except: + pass + try: + return str(eval(self.expr, {}, { + "tag":lambda x: tags.get(x,""), + "prop":lambda x: props.get(x,""), + "num": m_num, + "metric": lambda x: m_metric(x, xscale), + "zmetric": lambda x: m_metric(x, zscale), + "str": str, + "any": m_any, + "min": m_min, + "max": m_max, + "cond": m_cond, + "boolean": m_boolean + })) + except: + return "" + + + def __repr__(self): + return "eval(%s)"%self.expr_text def m_boolean(expr): - expr = str(expr) - if expr in ("", "0", "no", "false", "False"): - return False - else: - return True + expr = str(expr) + if expr in ("", "0", "no", "false", "False"): + return False + else: + return True def m_cond(why, yes, no): - if m_boolean(why): - return yes - else: - return no + if m_boolean(why): + return yes + else: + return no def m_min(*x): - """ - min() MapCSS Feature - """ - try: - return min([m_num(t) for t in x]) - except: - return 0 + """ + min() MapCSS Feature + """ + try: + return min([m_num(t) for t in x]) + except: + return 0 def m_max(*x): - """ - max() MapCSS Feature - """ - try: - return max([m_num(t) for t in x]) - except: - return 0 + """ + max() MapCSS Feature + """ + try: + return max([m_num(t) for t in x]) + except: + return 0 def m_any(*x): - """ - any() MapCSS feature - """ - for t in x: - if t: - return t - else: - return "" + """ + any() MapCSS feature + """ + for t in x: + if t: + return t + else: + return "" def m_num(x): - """ - num() MapCSS feature - """ - try: - return float(str(x)) - except ValueError: - return 0 -def m_metric(x, t): - """ - metric() and zmetric() function. - """ - x = str(x) - try: - return float(x)*float(t) - except: - "Heuristics." - # FIXME: add ft, m and friends - x = x.strip() + """ + num() MapCSS feature + """ try: - if x[-2:] in ("cm", "CM", "см"): - return float(x[0:-2])*float(t)/100 - if x[-2:] in ("mm", "MM", "мм"): - return float(x[0:-2])*float(t)/1000 - if x[-1] in ("m", "M", "м"): - return float(x[0:-1])*float(t) + return float(str(x)) + except ValueError: + return 0 +def m_metric(x, t): + """ + metric() and zmetric() function. + """ + x = str(x) + try: + return float(x)*float(t) except: - return "" + "Heuristics." + # FIXME: add ft, m and friends + x = x.strip() + try: + if x[-2:] in ("cm", "CM", "см"): + return float(x[0:-2])*float(t)/100 + if x[-2:] in ("mm", "MM", "мм"): + return float(x[0:-2])*float(t)/1000 + if x[-1] in ("m", "M", "м"): + return float(x[0:-1])*float(t) + except: + return "" #def str(x): - #""" - #str() MapCSS feature - #""" - #return __builtins__.str(x) + #""" + #str() MapCSS feature + #""" + #return __builtins__.str(x) if __name__ == "__main__": - a = Eval(""" eval( any( metric(tag("height")), metric ( num(tag("building:levels")) * 3), metric("1m"))) """) - print repr(a) - print a.compute({"building:levels":"3"}) - print a.extract_tags() + a = Eval(""" eval( any( metric(tag("height")), metric ( num(tag("building:levels")) * 3), metric("1m"))) """) + print repr(a) + print a.compute({"building:levels":"3"}) + print a.extract_tags() diff --git a/src/mapcss/Rule.py b/src/mapcss/Rule.py index 8bb56b2..52dfc2a 100644 --- a/src/mapcss/Rule.py +++ b/src/mapcss/Rule.py @@ -18,92 +18,92 @@ class Rule(): - def __init__(self, s=''): - self.conditions = [] - #self.isAnd = True - self.minZoom = 0 - self.maxZoom = 19 - if s == "*": - s = "" - self.subject = s # "", "way", "node" or "relation" + def __init__(self, s=''): + self.conditions = [] + #self.isAnd = True + self.minZoom = 0 + self.maxZoom = 19 + if s == "*": + s = "" + self.subject = s # "", "way", "node" or "relation" - def __repr__(self): - return "%s|z%s-%s %s"%(self.subject, self.minZoom, self.maxZoom, self.conditions) + def __repr__(self): + return "%s|z%s-%s %s"%(self.subject, self.minZoom, self.maxZoom, self.conditions) - def test(self, obj, tags, zoom): - if not self.test_zoom(zoom): - return False - if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, tags): - return False - subpart = "::default" - for condition in self.conditions: - res = condition.test(tags) - if not res: + def test(self, obj, tags, zoom): + if not ((zoom >= self.minZoom) and (zoom <= self.maxZoom)): return False - if type(res) != bool: - subpart = res - return subpart + if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, tags): + return False + subpart = "::default" + for condition in self.conditions: + res = condition.test(tags) + if not res: + return False + if type(res) != bool: + subpart = res + return subpart - def test_zoom(self, zoom): - return (zoom >= self.minZoom) and (zoom <= self.maxZoom) + def test_zoom(self, zoom): + return (zoom >= self.minZoom) and (zoom <= self.maxZoom) - def get_interesting_tags(self, obj, zoom): - if obj: - if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, {}): - return set() + def get_interesting_tags(self, obj, zoom): + if obj: + if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, {}): + return set() - if zoom and not self.test_zoom(zoom): - return set() + if zoom and not self.test_zoom(zoom): + return set() - a = set() - for condition in self.conditions: - a.update(condition.get_interesting_tags()) - return a + a = set() + for condition in self.conditions: + a.update(condition.get_interesting_tags()) + return a - def get_numerics(self): - a = set() - for condition in self.conditions: - a.add(condition.get_numerics()) - a.discard(False) - return a + def get_numerics(self): + a = set() + for condition in self.conditions: + a.add(condition.get_numerics()) + a.discard(False) + return a - def get_sql_hints(self, obj, zoom): - if obj: - if (self.subject!='') and not _test_feature_compatibility(obj, self.subject, {":area":"yes"}): - return set() - if not self.test_zoom(zoom): - return set() - a = set() - b = set() - for condition in self.conditions: - q = condition.get_sql() - if q: - if q[1]: - a.add(q[0]) - b.add(q[1]) - b = " AND ".join(b) - return a,b + def get_sql_hints(self, obj, zoom): + if obj: + if (self.subject!='') and not _test_feature_compatibility(obj, self.subject, {":area":"yes"}): + return set() + if not self.test_zoom(zoom): + return set() + a = set() + b = set() + for condition in self.conditions: + q = condition.get_sql() + if q: + if q[1]: + a.add(q[0]) + b.add(q[1]) + b = " AND ".join(b) + return a,b def _test_feature_compatibility (f1, f2, tags={}): """ Checks if feature of type f1 is compatible with f2. """ if f2 == f1: - return True + return True + if f2 not in ("way", "area", "line"): + return False elif f2 == "way" and f1 == "line": - return True + return True elif f2 == "way" and f1 == "area": - return True - elif f2 == "area" and f1 in ("way", "area", "POLYGON"): + return True + elif f2 == "area" and f1 in ("way", "area"): # if ":area" in tags: return True # else: # return False - elif f2 == "line" and f1 in ("way", "line", "LINESTRING"): - return True - elif f2 == "point" and f1 in ("node", "POINT"): - return True + elif f2 == "line" and f1 in ("way", "line"): + return True else: - return False + return False #print f1, f2, True - return True \ No newline at end of file + return True diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index 0b9e66c..e72b387 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with kothic. If not, see . - + from Rule import Rule from webcolors.webcolors import whatever_to_cairo as colorparser @@ -22,225 +22,233 @@ from webcolors.webcolors import cairo_to_hex from Eval import Eval class StyleChooser: - """ - A StyleChooser object is equivalent to one CSS selector+declaration. - - Its ruleChains property is an array of all the selectors, which would - traditionally be comma-separated. For example: - h1, h2, h3 em - is three ruleChains. - - Each ruleChain is itself an array of nested selectors. So the above - example would roughly be encoded as: - [[h1],[h2],[h3,em]] - ^^ ^^ ^^ ^^ each of these is a Rule - - The styles property is an array of all the style objects to be drawn - if any of the ruleChains evaluate to true. - """ - def __repr__(self): - return "{(%s) : [%s] }\n"%(self.ruleChains, self.styles) - def __init__(self, scalepair): - self.ruleChains = [[],] - self.styles = [] - self.eval_type = type(Eval()) - self.scalepair = scalepair - self.rcpos=0 - self.stylepos=0 - - - def get_numerics(self): """ - Returns a set of number-compared values. - """ - a = set() - for c in self.ruleChains: - for r in c: - a.update(r.get_numerics()) - a.discard(False) - return a + A StyleChooser object is equivalent to one CSS selector+declaration. - def get_interesting_tags(self, type, zoom): - """ - Returns a set of tags that were used in here. - """ - ### FIXME - a = set() - for c in self.ruleChains: - for r in c: - a.update(r.get_interesting_tags(type, zoom)) - if a: ## FIXME: semi-illegal optimization, may wreck in future on tagless matches - - for r in self.styles: - for c,b in r.iteritems(): - if __builtins__["type"](b) == self.eval_type: - a.update(b.extract_tags()) - return a + Its ruleChains property is an array of all the selectors, which would + traditionally be comma-separated. For example: + h1, h2, h3 em + is three ruleChains. - def get_sql_hints(self, type, zoom): - """ - Returns a set of tags that were used in here in form of SQL-hints. - """ - a = set() - b = "" - needed = set(["width", "casing-width", "fill-color", "fill-image", "icon-image", "text", "extrude", "background-image", "background-color", "pattern-image", "shield-text"]) - - if not needed.isdisjoint(set(self.styles[0].keys())): - for c in self.ruleChains: - for r in c: - p = r.get_sql_hints(type, zoom) - if p: - q = "("+p[1] + ")"#[t[1] for t in p] - if q == "()": - q = "" - if b and q: - b += " OR "+ q - else: - b = q - a.update(p[0]) - # no need to check for eval's - return a,b - - # // Update the current StyleList from this StyleChooser + Each ruleChain is itself an array of nested selectors. So the above + example would roughly be encoded as: + [[h1],[h2],[h3,em]] + ^^ ^^ ^^ ^^ each of these is a Rule - def updateStyles(self, sl, ftype, tags, zoom, scale, zscale): - # Are any of the ruleChains fulfilled? - object_id = False - for c in self.ruleChains: - object_id = self.testChain(c,ftype,tags,zoom) - if object_id: - break - else: - return sl - w = 0 - for r in self.styles: - ra = {} - for a,b in r.iteritems(): - "calculating eval()'s" - if type(b) == self.eval_type: - combined_style = {} - for t in sl: - combined_style.update(t) - for p,q in combined_style.iteritems(): - if "color" in p: + The styles property is an array of all the style objects to be drawn + if any of the ruleChains evaluate to true. + """ + def __repr__(self): + return "{(%s) : [%s] }\n"%(self.ruleChains, self.styles) + def __init__(self, scalepair): + self.ruleChains = [[],] + self.styles = [] + self.eval_type = type(Eval()) + self.scalepair = scalepair + self.selzooms = None + self.rcpos=0 + self.stylepos=0 + + + def get_numerics(self): + """ + Returns a set of number-compared values. + """ + a = set() + for c in self.ruleChains: + for r in c: + a.update(r.get_numerics()) + a.discard(False) + return a + + def get_interesting_tags(self, ztype, zoom): + """ + Returns a set of tags that were used in here. + """ + ### FIXME + a = set() + for c in self.ruleChains: + for r in c: + a.update(r.get_interesting_tags(ztype, zoom)) + if a: ## FIXME: semi-illegal optimization, may wreck in future on tagless matches + for r in self.styles: + for c,b in r.iteritems(): + if type(b) == self.eval_type: + a.update(b.extract_tags()) + return a + + def get_sql_hints(self, type, zoom): + """ + Returns a set of tags that were used in here in form of SQL-hints. + """ + a = set() + b = "" + needed = set(["width", "casing-width", "fill-color", "fill-image", "icon-image", "text", "extrude", "background-image", "background-color", "pattern-image", "shield-text"]) + + if not needed.isdisjoint(set(self.styles[0].keys())): + for c in self.ruleChains: + for r in c: + p = r.get_sql_hints(type, zoom) + if p: + q = "("+p[1] + ")"#[t[1] for t in p] + if q == "()": + q = "" + if b and q: + b += " OR "+ q + else: + b = q + a.update(p[0]) + # no need to check for eval's + return a,b + + # // Update the current StyleList from this StyleChooser + + def updateStyles(self, sl, ftype, tags, zoom, scale, zscale): + # Are any of the ruleChains fulfilled? + if self.selzooms: + if zoom < self.selzooms[0] or zoom > self.selzooms[1]: + return sl + object_id = False + for c in self.ruleChains: + object_id = self.testChain(c,ftype,tags,zoom) + if object_id: + break + else: + return sl + w = 0 + for r in self.styles: + ra = {} + for a,b in r.iteritems(): + "calculating eval()'s" + if type(b) == self.eval_type: + combined_style = {} + for t in sl: + combined_style.update(t) + for p,q in combined_style.iteritems(): + if "color" in p: combined_style[p] = cairo_to_hex(q) - b = b.compute(tags, combined_style, scale, zscale) - ra[a] = b - r = ra - ra = {} - for a, b in r.iteritems(): - "checking and nicifying style table" - if "color" in a: - "parsing color value to 3-tuple" - ra[a] = colorparser(b) - elif any(x in a for x in ("width", "z-index", "opacity", "offset", "radius", "extrude")): - "these things are float's or not in table at all" + b = b.compute(tags, combined_style, scale, zscale) + ra[a] = b + r = ra + ra = {} + for a, b in r.iteritems(): + "checking and nicifying style table" + if "color" in a: + "parsing color value to 3-tuple" + ra[a] = colorparser(b) + elif any(x in a for x in ("width", "z-index", "opacity", "offset", "radius", "extrude")): + "these things are float's or not in table at all" + try: + ra[a] = float(b) + except ValueError: + pass + elif "dashes" in a: + "these things are arrays of float's or not in table at all" + try: + b = b.split(",") + b = [float(x) for x in b] + ra[a] = b + except ValueError: + ra[a] = [] + else: + ra[a]=b + ra["layer"] = float(tags.get("layer",0))*2000+ra.get("z-index",1) # calculating z-index + #for k,v in ra.items(): # if a value is empty, we don't need it - renderer will do as default. + # if not v: + # del ra[k] + ra["object-id"] = str(object_id) + hasall = False + allinit = {} + for x in sl: + if x.get("object-id") == "::*": + allinit = x.copy() + if ra["object-id"] == "::*": + oid = x.get("object-id") + x.update(ra) + x["object-id"] = oid + if oid == "::*": + hasall = True + if x.get("object-id") == ra["object-id"]: + x.update(ra) + break + else: + if not hasall: + allinit.update(ra) + sl.append(allinit) + # + return sl + + def testChain(self,chain, obj, tags, zoom): + """ + Tests an object against a chain + """ + for r in chain: + tt = r.test(obj,tags,zoom) + if tt: + return tt + return False + + + def newGroup(self): + """ + starts a new ruleChain in this.ruleChains + """ + if self.ruleChains[self.rcpos]: + self.ruleChains.append([]) + + + def newObject(self,e=''): + """ + adds into the current ruleChain (starting a new Rule) + """ + rule = Rule(e) + rule.minZoom=float(self.scalepair[0]) + rule.maxZoom=float(self.scalepair[1]) + self.ruleChains[self.rcpos].append(rule) + + def addZoom(self,z): + """ + adds into the current ruleChain (existing Rule) + """ + + self.ruleChains[self.rcpos][-1].minZoom=float(z[0]) + self.ruleChains[self.rcpos][-1].maxZoom=float(z[1]) + if not self.selzooms: + self.selzooms = list(z) + else: + self.selzooms[0] = min(self.selzooms[0], z[0]) + self.selzooms[1] = min(self.selzooms[1], z[1]) + + def addCondition(self,c): + """ + adds into the current ruleChain (existing Rule) + """ + self.ruleChains[self.rcpos][-1].conditions.append(c) + + + def addStyles(self, a): + """ + adds to this.styles + """ + rb = [] + for r in a: + ra = {} + for a,b in r.iteritems(): + a = a.strip() + b = b.strip() + if a == "casing-width": + "josm support" + if b[0] == "+": try: - ra[a] = float(b) - except ValueError: - pass - elif "dashes" in a: - "these things are arrays of float's or not in table at all" - try: - b = b.split(",") - b = [float(x) for x in b] - ra[a]= b - except ValueError: - pass - else: - ra[a]=b - ra["layer"] = float(tags.get("layer",0))*2000+ra.get("z-index",1) # calculating z-index - for k,v in ra.items(): # if a value is empty, we don't need it - renderer will do as default. - if not v: - del ra[k] - ra["object-id"] = str(object_id) - hasall = False - allinit = {} - for x in sl: - if x.get("object-id") == "::*": - allinit = x.copy() - if ra["object-id"] == "::*": - oid = x.get("object-id") - x.update(ra) - x["object-id"] = oid - if oid == "::*": - hasall = True - if x.get("object-id") == ra["object-id"]: - x.update(ra) - break - else: - if not hasall: - allinit.update(ra) - sl.append(allinit) - # - return sl - - def testChain(self,chain, obj, tags, zoom): - """ - Tests an object against a chain - """ - ### FIXME: total MapCSS misreading - for r in chain: - return r.test(obj,tags,zoom) - return False - - - def newGroup(self): - """ - starts a new ruleChain in this.ruleChains - """ - if self.ruleChains[self.rcpos]: - self.ruleChains.append([]) - - - def newObject(self,e=''): - """ - adds into the current ruleChain (starting a new Rule) - """ - rule = Rule(e) - rule.minZoom=float(self.scalepair[0]) - rule.maxZoom=float(self.scalepair[1]) - self.ruleChains[self.rcpos].append(rule) - - def addZoom(self,z): - """ - adds into the current ruleChain (existing Rule) - """ - - self.ruleChains[self.rcpos][-1].minZoom=float(z[0]) - self.ruleChains[self.rcpos][-1].maxZoom=float(z[1]) - - - def addCondition(self,c): - """ - adds into the current ruleChain (existing Rule) - """ - self.ruleChains[self.rcpos][-1].conditions.append(c) - - - def addStyles(self, a): - """ - adds to this.styles - """ - rb = [] - for r in a: - ra = {} - for a,b in r.iteritems(): - a = a.strip() - b = b.strip() - if a == "casing-width": - "josm support" - if b[0] == "+": - try: - b = str(float(b)/2) - except: - pass - if "text" == a[-4:]: - if b[:5] != "eval(": - b = "eval(tag(\""+b+"\"))" - if b[:5] == "eval(": - b = Eval(b) - ra[a] = b - rb.append(ra) - # print rb - self.styles = self.styles + rb + b = str(float(b)/2) + except: + pass + if "text" == a[-4:]: + if b[:5] != "eval(": + b = "eval(tag(\""+b+"\"))" + if b[:5] == "eval(": + b = Eval(b) + ra[a] = b + rb.append(ra) + # print rb + self.styles = self.styles + rb diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index 49fe45a..05581da 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -90,33 +90,33 @@ way {width: 1; casing-width:1; casing-color: white} class MapCSS(): def __init__(self,minscale=0,maxscale=19): - """ - """ - self.cache = {} - self.cache["style"] = {} - self.minscale=minscale - self.maxscale=maxscale - self.scalepair = (minscale, maxscale) - self.choosers = [] - self.style_loaded = False - self.parse(builtin_style) - self.style_loaded = False #override one after loading + """ + """ + self.cache = {} + self.cache["style"] = {} + self.minscale=minscale + self.maxscale=maxscale + self.scalepair = (minscale, maxscale) + self.choosers = [] + self.style_loaded = False + self.parse(builtin_style) + self.style_loaded = False #override one after loading def parseZoom(self, s): - if ZOOM_MINMAX.match(s): - return tuple([float(i) for i in ZOOM_MINMAX.match(s).groups()]) - elif ZOOM_MIN.match(s): - return float(ZOOM_MIN.match(s).groups()[0]), self.maxscale - elif ZOOM_MAX.match(s): - return float(self.minscale),float(ZOOM_MAX.match(s).groups()[0]) - elif ZOOM_SINGLE.match(s): - return float(ZOOM_SINGLE.match(s).groups()[0]),float(ZOOM_SINGLE.match(s).groups()[0]) - else: - logging.error("unparsed zoom: %s" %s) + if ZOOM_MINMAX.match(s): + return tuple([float(i) for i in ZOOM_MINMAX.match(s).groups()]) + elif ZOOM_MIN.match(s): + return float(ZOOM_MIN.match(s).groups()[0]), self.maxscale + elif ZOOM_MAX.match(s): + return float(self.minscale),float(ZOOM_MAX.match(s).groups()[0]) + elif ZOOM_SINGLE.match(s): + return float(ZOOM_SINGLE.match(s).groups()[0]),float(ZOOM_SINGLE.match(s).groups()[0]) + else: + logging.error("unparsed zoom: %s" %s) def precompile_style (self): - # TODO: styleshees precompilation + # TODO: styleshees precompilation subjs = {"canvas": ("canvas",),"way": ("Polygon","LineString"), "line":("Polygon","LineString"), "area": ("Polygon",), "node": ("Point",), "*":("Point","Polygon","LineString") } mfile.write("function restyle (prop, zoom, type){") mfile.write("style = new Object;") @@ -166,7 +166,7 @@ class MapCSS(): #if subclass != "default": #styles = 'if(!("%s" in style)){style["%s"] = new Object;}'%(subclass,subclass) #for k, v in chooser.styles[0].iteritems(): - + if type(v) == str: try: v = str(float(v)) @@ -178,73 +178,70 @@ class MapCSS(): mfile.write("return style;}") def get_style (self, type, tags={}, zoom=0, scale=1, zscale=.5): - """ - Kothic styling API - """ - shash = md5(repr(type)+repr(tags)+repr(zoom)).digest() - if shash in self.cache["style"]: - return self.cache["style"][shash] - style = [] - dbg = False - if tags.get('highway') == 'footway' and zoom == 17: - dbg = True - for chooser in self.choosers: - style = chooser.updateStyles(style, type, tags, zoom, scale, zscale) - style = [x for x in style if x["object-id"] != "::*"] - self.cache["style"][shash] = style - return style + """ + Kothic styling API + """ + shash = md5(repr(type)+repr(tags)+repr(zoom)).digest() + if shash in self.cache["style"]: + return self.cache["style"][shash] + style = [] + for chooser in self.choosers: + style = chooser.updateStyles(style, type, tags, zoom, scale, zscale) + style = [x for x in style if x["object-id"] != "::*"] + self.cache["style"][shash] = style + return style def get_interesting_tags(self, type=None, zoom=None): - """ - Get set of interesting tags. - """ - tags = set() - for chooser in self.choosers: - tags.update(chooser.get_interesting_tags(type, zoom)) - return tags + """ + Get set of interesting tags. + """ + tags = set() + for chooser in self.choosers: + tags.update(chooser.get_interesting_tags(type, zoom)) + return tags def get_sql_hints(self, type=None, zoom=None): - """ - Get set of interesting tags. - """ - hints = [] - for chooser in self.choosers: - p = chooser.get_sql_hints(type, zoom) - if p: - if p[0] and p[1]: - hints.append(p) - return hints + """ + Get set of interesting tags. + """ + hints = [] + for chooser in self.choosers: + p = chooser.get_sql_hints(type, zoom) + if p: + if p[0] and p[1]: + hints.append(p) + return hints def parse(self, css): - """ - Parses MapCSS given as string - """ - if not self.style_loaded: - self.choosers = [] - log = logging.getLogger('mapcss.parser') - previous = 0 # what was the previous CSS word? - sc=StyleChooser(self.scalepair) #currently being assembled - #choosers=[] - #o = [] - while (css): + """ + Parses MapCSS given as string + """ + if not self.style_loaded: + self.choosers = [] + log = logging.getLogger('mapcss.parser') + previous = 0 # what was the previous CSS word? + sc=StyleChooser(self.scalepair) #currently being assembled + #choosers=[] + #o = [] + while (css): - # CSS comment - if COMMENT.match(css): + # CSS comment + if COMMENT.match(css): log.debug("comment found") css=COMMENT.sub("", css) - #// Whitespace (probably only at beginning of file) - elif WHITESPACE.match(css): + #// Whitespace (probably only at beginning of file) + elif WHITESPACE.match(css): log.debug("whitespace found") css=WHITESPACE.sub("",css) - #// Class - .motorway, .builtup, :hover - elif CLASS.match(css): + #// Class - .motorway, .builtup, :hover + elif CLASS.match(css): if previous==oDECLARATION: - self.choosers.append(sc) - sc = StyleChooser(self.scalepair) - + self.choosers.append(sc) + sc = StyleChooser(self.scalepair) + cond = CLASS.match(css).groups()[0] log.debug("class found: %s"% (cond)) css = CLASS.sub("", css) @@ -252,11 +249,11 @@ class MapCSS(): sc.addCondition(Condition('eq',("::class",cond))) previous=oCONDITION; - #// Not class - !.motorway, !.builtup, !:hover - elif NOT_CLASS.match(css): + #// Not class - !.motorway, !.builtup, !:hover + elif NOT_CLASS.match(css): if (previous==oDECLARATION): - self.choosers.append(sc) - sc = StyleChooser(self.scalepair) + self.choosers.append(sc) + sc = StyleChooser(self.scalepair) cond = NOT_CLASS.match(css).groups()[0] log.debug("not_class found: %s"% (cond)) @@ -267,10 +264,10 @@ class MapCSS(): #sc.addCondition(new Condition('unset',o[1])); #previous=oCONDITION; - #// Zoom - elif ZOOM.match(css): + #// Zoom + elif ZOOM.match(css): if (previous!=oOBJECT & previous!=oCONDITION): - sc.newObject() + sc.newObject() cond = ZOOM.match(css).groups()[0] log.debug("zoom found: %s"% (cond)) @@ -278,144 +275,144 @@ class MapCSS(): sc.addZoom(self.parseZoom(cond)) previous=oZOOM; - #// Grouping - just a comma - elif GROUP.match(css): + #// Grouping - just a comma + elif GROUP.match(css): css=GROUP.sub("",css) sc.newGroup() previous=oGROUP - #// Condition - [highway=primary] - elif CONDITION.match(css): + #// Condition - [highway=primary] + elif CONDITION.match(css): if (previous==oDECLARATION): - self.choosers.append(sc) - sc = StyleChooser(self.scalepair) + self.choosers.append(sc) + sc = StyleChooser(self.scalepair) if (previous!=oOBJECT) and (previous!=oZOOM) and (previous!=oCONDITION): - sc.newObject() + sc.newObject() cond = CONDITION.match(css).groups()[0] log.debug("condition found: %s"% (cond)) css=CONDITION.sub("",css) sc.addCondition(parseCondition(cond)) previous=oCONDITION; - #// Object - way, node, relation - elif OBJECT.match(css): + #// Object - way, node, relation + elif OBJECT.match(css): if (previous==oDECLARATION): - self.choosers.append(sc) - sc = StyleChooser(self.scalepair) + self.choosers.append(sc) + sc = StyleChooser(self.scalepair) obj = OBJECT.match(css).groups()[0] log.debug("object found: %s"% (obj)) css=OBJECT.sub("",css) sc.newObject(obj) previous=oOBJECT - #// Declaration - {...} - elif DECLARATION.match(css): + #// Declaration - {...} + elif DECLARATION.match(css): decl = DECLARATION.match(css).groups()[0] log.debug("declaration found: %s"% (decl)) sc.addStyles(parseDeclaration(decl)) css=DECLARATION.sub("",css) previous=oDECLARATION - #// Unknown pattern - elif UNKNOWN.match(css): + #// Unknown pattern + elif UNKNOWN.match(css): log.warning("unknown thing found: %s"%(UNKNOWN.match(css).group())) css=UNKNOWN.sub("",css) - else: + else: log.warning("choked on: %s"%(css)) - return + return - if (previous==oDECLARATION): - self.choosers.append(sc) - sc= StyleChooser(self.scalepair) + if (previous==oDECLARATION): + self.choosers.append(sc) + sc= StyleChooser(self.scalepair) def parseCondition(s): - log = logging.getLogger('mapcss.parser.condition') - if CONDITION_TRUE.match(s): - a = CONDITION_TRUE.match(s).groups() - log.debug("condition true: %s"%(a[0])) - return Condition('true' ,a) - if CONDITION_invTRUE.match(s): - a = CONDITION_invTRUE.match(s).groups() - log.debug("condition invtrue: %s"%(a[0])) - return Condition('ne' ,(a[0],"yes")) + log = logging.getLogger('mapcss.parser.condition') + if CONDITION_TRUE.match(s): + a = CONDITION_TRUE.match(s).groups() + log.debug("condition true: %s"%(a[0])) + return Condition('true' ,a) + if CONDITION_invTRUE.match(s): + a = CONDITION_invTRUE.match(s).groups() + log.debug("condition invtrue: %s"%(a[0])) + return Condition('ne' ,(a[0],"yes")) - if CONDITION_FALSE.match(s): - a = CONDITION_FALSE.match(s).groups() - log.debug("condition false: %s"%(a[0])) - return Condition('false' ,a) + if CONDITION_FALSE.match(s): + a = CONDITION_FALSE.match(s).groups() + log.debug("condition false: %s"%(a[0])) + return Condition('false' ,a) - if CONDITION_SET.match(s): - a = CONDITION_SET.match(s).groups() - log.debug("condition set: %s"%(a)) - return Condition('set' ,a) + if CONDITION_SET.match(s): + a = CONDITION_SET.match(s).groups() + log.debug("condition set: %s"%(a)) + return Condition('set' ,a) - if CONDITION_UNSET.match(s): - a = CONDITION_UNSET.match(s).groups() - log.debug("condition unset: %s"%(a)) - return Condition('unset' ,a) + if CONDITION_UNSET.match(s): + a = CONDITION_UNSET.match(s).groups() + log.debug("condition unset: %s"%(a)) + return Condition('unset' ,a) - if CONDITION_NE.match(s): - a = CONDITION_NE.match(s).groups() - log.debug("condition NE: %s = %s"%(a[0], a[1])) - return Condition('ne' ,a) - ## FIXME: convert other conditions to python + if CONDITION_NE.match(s): + a = CONDITION_NE.match(s).groups() + log.debug("condition NE: %s = %s"%(a[0], a[1])) + return Condition('ne' ,a) + ## FIXME: convert other conditions to python - if CONDITION_LE.match(s): - a = CONDITION_LE.match(s).groups() - log.debug("condition LE: %s <= %s"%(a[0], a[1])) - return Condition('<=' ,a) + if CONDITION_LE.match(s): + a = CONDITION_LE.match(s).groups() + log.debug("condition LE: %s <= %s"%(a[0], a[1])) + return Condition('<=' ,a) - if CONDITION_GE.match(s): - a = CONDITION_GE.match(s).groups() - log.debug("condition GE: %s >= %s"%(a[0], a[1])) - return Condition('>=' ,a) + if CONDITION_GE.match(s): + a = CONDITION_GE.match(s).groups() + log.debug("condition GE: %s >= %s"%(a[0], a[1])) + return Condition('>=' ,a) - if CONDITION_LT.match(s): - a = CONDITION_LT.match(s).groups() - log.debug("condition LT: %s < %s"%(a[0], a[1])) - return Condition('<' ,a) + if CONDITION_LT.match(s): + a = CONDITION_LT.match(s).groups() + log.debug("condition LT: %s < %s"%(a[0], a[1])) + return Condition('<' ,a) - if CONDITION_GT.match(s): - a = CONDITION_GT.match(s).groups() - log.debug("condition GT: %s > %s"%(a[0], a[1])) - return Condition('>' ,a) + if CONDITION_GT.match(s): + a = CONDITION_GT.match(s).groups() + log.debug("condition GT: %s > %s"%(a[0], a[1])) + return Condition('>' ,a) - if CONDITION_REGEX.match(s): - a = CONDITION_REGEX.match(s).groups() - log.debug("condition REGEX: %s = %s"%(a[0], a[1])) - return Condition('regex' ,a) + if CONDITION_REGEX.match(s): + a = CONDITION_REGEX.match(s).groups() + log.debug("condition REGEX: %s = %s"%(a[0], a[1])) + return Condition('regex' ,a) - if CONDITION_EQ.match(s): - a = CONDITION_EQ.match(s).groups() - log.debug("condition EQ: %s = %s"%(a[0], a[1])) - return Condition('eq' ,a) + if CONDITION_EQ.match(s): + a = CONDITION_EQ.match(s).groups() + log.debug("condition EQ: %s = %s"%(a[0], a[1])) + return Condition('eq' ,a) - else: - log.warning("condition UNKNOWN: %s"%(s)) + else: + log.warning("condition UNKNOWN: %s"%(s)) def parseDeclaration(s): - """ - Parse declaration string into list of styles - """ - styles=[] - t = {} + """ + Parse declaration string into list of styles + """ + styles=[] + t = {} - for a in s.split(';'): - #if ((o=ASSIGNMENT_EVAL.exec(a))) { t[o[1].replace(DASH,'_')]=new Eval(o[2]); } - if ASSIGNMENT.match(a): - tzz = ASSIGNMENT.match(a).groups() - t[tzz[0]]=tzz[1].strip().strip('"') - logging.debug("%s == %s" % (tzz[0],tzz[1]) ) - else: - logging.debug("unknown %s" % (a) ) - return [t] + for a in s.split(';'): + #if ((o=ASSIGNMENT_EVAL.exec(a))) { t[o[1].replace(DASH,'_')]=new Eval(o[2]); } + if ASSIGNMENT.match(a): + tzz = ASSIGNMENT.match(a).groups() + t[tzz[0]]=tzz[1].strip().strip('"') + logging.debug("%s == %s" % (tzz[0],tzz[1]) ) + else: + logging.debug("unknown %s" % (a) ) + return [t] if __name__ == "__main__": - logging.basicConfig(level=logging.WARNING) - mc = MapCSS(0,19) \ No newline at end of file + logging.basicConfig(level=logging.WARNING) + mc = MapCSS(0,19) diff --git a/src/mapcss/webcolors/webcolors.py b/src/mapcss/webcolors/webcolors.py index f3641ba..d210616 100644 --- a/src/mapcss/webcolors/webcolors.py +++ b/src/mapcss/webcolors/webcolors.py @@ -196,7 +196,7 @@ def _reversedict(d): """ Internal helper for generating reverse mappings; given a dictionary, returns a new dictionary with keys and values swapped. - + """ return dict(zip(d.values(), d.keys())) @@ -407,22 +407,22 @@ def normalize_hex(hex_value): """ Normalize a hexadecimal color value to the following form and return the result:: - + #[a-f0-9]{6} - + In other words, the following transformations are applied as needed: - + * If the value contains only three hexadecimal digits, it is expanded to six. - + * The value is normalized to lower-case. - + If the supplied value cannot be interpreted as a hexadecimal color value, ``ValueError`` is raised. - + Examples: - + >>> normalize_hex('#09c') '#0099cc' >>> normalize_hex('#0099cc') @@ -447,7 +447,7 @@ def normalize_hex(hex_value): Traceback (most recent call last): ... ValueError: '#0' is not a valid hexadecimal color value. - + """ try: hex_digits = HEX_COLOR_RE.match(hex_value).groups()[0] @@ -466,18 +466,18 @@ def normalize_hex(hex_value): def name_to_hex(name, spec='css3'): """ Convert a color name to a normalized hexadecimal color value. - + The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. - + The color name will be normalized to lower-case before being looked up, and when no color of that name exists in the given specification, ``ValueError`` is raised. - + Examples: - + >>> name_to_hex('deepskyblue') '#00bfff' >>> name_to_hex('DeepSkyBlue') @@ -498,7 +498,7 @@ def name_to_hex(name, spec='css3'): Traceback (most recent call last): ... ValueError: 'deepskyblue' is not defined as a named color in css2. - + """ if spec not in SUPPORTED_SPECIFICATIONS: raise TypeError("'%s' is not a supported specification for color name lookups; supported specifications are: %s." % (spec, @@ -514,18 +514,18 @@ def name_to_rgb(name, spec='css3'): """ Convert a color name to a 3-tuple of integers suitable for use in an ``rgb()`` triplet specifying that color. - + The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. - + The color name will be normalized to lower-case before being looked up, and when no color of that name exists in the given specification, ``ValueError`` is raised. - + Examples: - + >>> name_to_rgb('navy') (0, 0, 128) >>> name_to_rgb('cadetblue') @@ -534,7 +534,7 @@ def name_to_rgb(name, spec='css3'): Traceback (most recent call last): ... ValueError: 'cadetblue' is not defined as a named color in html4. - + """ return hex_to_rgb(name_to_hex(name, spec=spec)) @@ -542,25 +542,25 @@ def name_to_rgb_percent(name, spec='css3'): """ Convert a color name to a 3-tuple of percentages suitable for use in an ``rgb()`` triplet specifying that color. - + The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. - + The color name will be normalized to lower-case before being looked up, and when no color of that name exists in the given specification, ``ValueError`` is raised. - + Examples: - + >>> name_to_rgb_percent('white') ('100%', '100%', '100%') >>> name_to_rgb_percent('navy') ('0%', '0%', '50%') >>> name_to_rgb_percent('goldenrod') ('85.49%', '64.71%', '12.5%') - + """ return rgb_to_rgb_percent(name_to_rgb(name, spec=spec)) @@ -569,23 +569,23 @@ def name_to_rgb_percent(name, spec='css3'): # Conversions from hexadecimal color values to various formats. ###################################################################### - + def hex_to_name(hex_value, spec='css3'): """ Convert a hexadecimal color value to its corresponding normalized color name, if any such name exists. - + The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. - + The hexadecimal value will be normalized before being looked up, and when no color name for the value is found in the given specification, ``ValueError`` is raised. - + Examples: - + >>> hex_to_name('#000080') 'navy' >>> hex_to_name('#000080', spec='html4') @@ -604,7 +604,7 @@ def hex_to_name(hex_value, spec='css3'): Traceback (most recent call last): ... TypeError: 'css4' is not a supported specification for color name lookups; supported specifications are: html4, css2, css21, css3. - + """ if spec not in SUPPORTED_SPECIFICATIONS: raise TypeError("'%s' is not a supported specification for color name lookups; supported specifications are: %s." % (spec, @@ -627,11 +627,11 @@ def hex_to_rgb(hex_value): """ Convert a hexadecimal color value to a 3-tuple of integers suitable for use in an ``rgb()`` triplet specifying that color. - + The hexadecimal value will be normalized before being converted. - + Examples: - + >>> hex_to_rgb('#000080') (0, 0, 128) >>> hex_to_rgb('#ffff00') @@ -640,7 +640,7 @@ def hex_to_rgb(hex_value): (255, 0, 0) >>> hex_to_rgb('#deb887') (222, 184, 135) - + """ hex_digits = normalize_hex(hex_value) return tuple(map(lambda s: int(s, 16), @@ -650,16 +650,16 @@ def hex_to_rgb_percent(hex_value): """ Convert a hexadecimal color value to a 3-tuple of percentages suitable for use in an ``rgb()`` triplet representing that color. - + The hexadecimal value will be normalized before converting. - + Examples: >>> hex_to_rgb_percent('#ffffff') ('100%', '100%', '100%') >>> hex_to_rgb_percent('#000080') ('0%', '0%', '50%') - + """ return rgb_to_rgb_percent(hex_to_rgb(hex_value)) @@ -674,23 +674,23 @@ def rgb_to_name(rgb_triplet, spec='css3'): Convert a 3-tuple of integers, suitable for use in an ``rgb()`` color triplet, to its corresponding normalized color name, if any such name exists. - + The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. - + If there is no matching name, ``ValueError`` is raised. - + Examples: - + >>> rgb_to_name((0, 0, 0)) 'black' >>> rgb_to_name((0, 0, 128)) 'navy' >>> rgb_to_name((95, 158, 160)) 'cadetblue' - + """ return hex_to_name(rgb_to_hex(rgb_triplet), spec=spec) @@ -698,16 +698,16 @@ def rgb_to_hex(rgb_triplet): """ Convert a 3-tuple of integers, suitable for use in an ``rgb()`` color triplet, to a normalized hexadecimal value for that color. - + Examples: - + >>> rgb_to_hex((255, 255, 255)) '#ffffff' >>> rgb_to_hex((0, 0, 128)) '#000080' >>> rgb_to_hex((33, 56, 192)) '#2138c0' - + """ return '#%02x%02x%02x' % rgb_triplet @@ -716,7 +716,7 @@ def rgb_to_rgb_percent(rgb_triplet): Convert a 3-tuple of integers, suitable for use in an ``rgb()`` color triplet, to a 3-tuple of percentages suitable for use in representing that color. - + This function makes some trade-offs in terms of the accuracy of the final representation; for some common integer values, special-case logic is used to ensure a precise result (e.g., @@ -724,9 +724,9 @@ def rgb_to_rgb_percent(rgb_triplet): convert to '12.5%'), but for all other values a standard Python ``float`` is used and rounded to two decimal places, which may result in a loss of precision for some values. - + Examples: - + >>> rgb_to_rgb_percent((255, 255, 255)) ('100%', '100%', '100%') >>> rgb_to_rgb_percent((0, 0, 128)) @@ -735,7 +735,7 @@ def rgb_to_rgb_percent(rgb_triplet): ('12.94%', '21.96%', '75.29%') >>> rgb_to_rgb_percent((64, 32, 16)) ('25%', '12.5%', '6.25%') - + """ # In order to maintain precision for common values, # 256 / 2**n is special-cased for values of n @@ -756,23 +756,23 @@ def rgb_percent_to_name(rgb_percent_triplet, spec='css3'): Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` color triplet, to its corresponding normalized color name, if any such name exists. - + The optional keyword argument ``spec`` determines which specification's list of color names will be used; valid values are ``html4``, ``css2``, ``css21`` and ``css3``, and the default is ``css3``. - + If there is no matching name, ``ValueError`` is raised. - + Examples: - + >>> rgb_percent_to_name(('0%', '0%', '0%')) 'black' >>> rgb_percent_to_name(('0%', '0%', '50%')) 'navy' >>> rgb_percent_to_name(('85.49%', '64.71%', '12.5%')) 'goldenrod' - + """ return rgb_to_name(rgb_percent_to_rgb(rgb_percent_triplet), spec=spec) @@ -781,7 +781,7 @@ def rgb_percent_to_hex(rgb_percent_triplet): Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` color triplet, to a normalized hexadecimal color value for that color. - + Examples: >>> rgb_percent_to_hex(('100%', '100%', '0%')) @@ -790,7 +790,7 @@ def rgb_percent_to_hex(rgb_percent_triplet): '#000080' >>> rgb_percent_to_hex(('85.49%', '64.71%', '12.5%')) '#daa520' - + """ return rgb_to_hex(rgb_percent_to_rgb(rgb_percent_triplet)) @@ -798,7 +798,7 @@ def _percent_to_integer(percent): """ Internal helper for converting a percentage value to an integer between 0 and 255 inclusive. - + """ num = float(percent.split('%')[0]) / 100.0 * 255 e = num - math.floor(num) @@ -809,16 +809,16 @@ def rgb_percent_to_rgb(rgb_percent_triplet): Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` color triplet, to a 3-tuple of integers suitable for use in representing that color. - + Some precision may be lost in this conversion. See the note regarding precision for ``rgb_to_rgb_percent()`` for details; generally speaking, the following is true for any 3-tuple ``t`` of integers in the range 0...255 inclusive:: - + t == rgb_percent_to_rgb(rgb_to_rgb_percent(t)) - + Examples: - + >>> rgb_percent_to_rgb(('100%', '100%', '100%')) (255, 255, 255) >>> rgb_percent_to_rgb(('0%', '0%', '50%')) @@ -827,39 +827,39 @@ def rgb_percent_to_rgb(rgb_percent_triplet): (64, 32, 16) >>> rgb_percent_to_rgb(('12.94%', '21.96%', '75.29%')) (33, 56, 192) - + """ return tuple(map(_percent_to_integer, rgb_percent_triplet)) def whatever_to_rgb(string): - """ - Converts CSS3 color or a hex into rgb triplet; hash of string if fails. - """ - string = string.strip().lower() - try: - return name_to_rgb(string) - except ValueError: + """ + Converts CSS3 color or a hex into rgb triplet; hash of string if fails. + """ + string = string.strip().lower() try: - return hex_to_rgb(string) + return name_to_rgb(string) except ValueError: - try: - if string[:3] == "rgb": - return tuple([float(i) for i in string[4:-1].split(",")][0:3]) - except: - return hex_to_rgb("#"+md5(string).hexdigest()[:6]) + try: + return hex_to_rgb(string) + except ValueError: + try: + if string[:3] == "rgb": + return tuple([float(i) for i in string[4:-1].split(",")][0:3]) + except: + return hex_to_rgb("#"+md5(string).hexdigest()[:6]) def whatever_to_hex(string): - if type(string) == tuple: - return cairo_to_hex(string).upper() - return rgb_to_hex(whatever_to_rgb(string)).upper() + if type(string) == tuple: + return cairo_to_hex(string).upper() + return rgb_to_hex(whatever_to_rgb(string)).upper() def whatever_to_cairo(string): - a = whatever_to_rgb(string) - return a[0]/255.,a[1]/255.,a[2]/255., + a = whatever_to_rgb(string) + return a[0]/255.,a[1]/255.,a[2]/255., def cairo_to_hex (cairo): - return rgb_to_hex((cairo[0]*255,cairo[1]*255,cairo[2]*255,)) - + return rgb_to_hex((cairo[0]*255,cairo[1]*255,cairo[2]*255,)) +