diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index 8d5c260..7a56198 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -110,17 +110,13 @@ def extract_tags(self): return a def get_runtime_conditions(self, tags): + """ + Returns the runtime conditions of every ::object-id this chooser selects, + to match the testChainsAll() contract used by updateStyles(). + """ if not self.has_runtime_conditions: - return None - - rule_and_object_id = self.testChains(tags) - - if not rule_and_object_id: - return None - - rule = rule_and_object_id[0] - - return rule.runtime_conditions + return [] + return [rule.runtime_conditions for rule, _ in self.testChainsAll(tags) if rule.runtime_conditions] # TODO: Rename to "applyStyles" def updateStyles(self, sl, tags, xscale, zscale, filter_by_runtime_conditions): @@ -180,9 +176,9 @@ def applyStylesTo(self, sl, tags, xscale, zscale, filter_by_runtime_conditions, def testChainsAll(self, tags): """ - Like testChains(), but yields the first matching rule for *each* distinct - ::object-id of the selector group, so that a declaration block applies to - every layer it selects instead of just the first one. + Yields the first matching rule for *each* distinct ::object-id of the + selector group, so that a declaration block applies to every layer it + selects instead of just the first one. """ object_ids = set() for r in self.ruleChains: @@ -191,16 +187,6 @@ def testChainsAll(self, tags): object_ids.add(tt) yield r, tt - def testChains(self, tags): - """ - Tests an object against a chain - """ - for r in self.ruleChains: - tt = r.test(tags) - if tt: - return r, tt - return False - def newGroup(self): """ starts a new ruleChain in this.ruleChains diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index 3bd622f..609920f 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -164,9 +164,7 @@ def get_runtime_rules(self, clname, type, tags, zoom): runtime_rules = [] if type in self.choosers_by_type_zoom_tag: for chooser in self.choosers_by_type_zoom_tag[type][zoom][clname]: - runtime_conditions = chooser.get_runtime_conditions(tags) - if runtime_conditions: - runtime_rules.append(runtime_conditions) + runtime_rules.extend(chooser.get_runtime_conditions(tags)) return runtime_rules # TODO: Renamed to `get_styles` because it returns a list of styles for each class `::XXX` diff --git a/tests/testMapCSS.py b/tests/testMapCSS.py index c5ffa6d..6ef7927 100644 --- a/tests/testMapCSS.py +++ b/tests/testMapCSS.py @@ -114,8 +114,7 @@ def test_parse_basic_chooser_2(self): self.assertEqual(parser.choosers[0].selzooms, [6, 6]) self.assertEqual(parser.choosers[1].selzooms, [7, 9]) - rule, object_id = parser.choosers[0].testChains({"highway": "trunk"}) - self.assertEqual(object_id, "::default") + self.assertEqual(parser.choosers[0].ruleChains[0].test({"highway": "trunk"}), "::default") def test_parse_basic_chooser_3(self): parser = MapCSS() @@ -137,10 +136,9 @@ def test_parse_basic_chooser_3(self): styleChooser = parser.choosers[0] self.assertEqual(len(styleChooser.ruleChains), 1) self.assertEqual(styleChooser.selzooms, [18, 19]) - rule, object_id = styleChooser.testChains(building_tags) - self.assertEqual(object_id, "::int_name") rule = styleChooser.ruleChains[0] + self.assertEqual(rule.test(building_tags), "::int_name") self.assertEqual(rule.subject, 'node') self.assertEqual(rule.extract_tags(), {'addr:housenumber', 'addr:street'}) @@ -158,10 +156,9 @@ def test_parse_basic_chooser_class(self): styleChooser = parser.choosers[0] self.assertEqual(len(styleChooser.ruleChains), 1) self.assertEqual(styleChooser.selzooms, [0, 13]) - rule, object_id = styleChooser.testChains({}) - self.assertEqual(object_id, "::*") rule = styleChooser.ruleChains[0] + self.assertEqual(rule.test({}), "::*") self.assertEqual(rule.subject, 'way') self.assertEqual(rule.extract_tags(), {'*'}) @@ -179,10 +176,9 @@ def test_parse_basic_chooser_class_2(self): styleChooser = parser.choosers[0] self.assertEqual(len(styleChooser.ruleChains), 1) self.assertEqual(styleChooser.selzooms, [10, 19]) - rule, object_id = styleChooser.testChains({}) - self.assertEqual(object_id, "::*") rule = styleChooser.ruleChains[0] + self.assertEqual(rule.test({}), "::*") self.assertEqual(rule.subject, 'way') self.assertEqual(rule.extract_tags(), {'*'}) diff --git a/tests/testStyleChooser.py b/tests/testStyleChooser.py index 86b415a..f333fe3 100644 --- a/tests/testStyleChooser.py +++ b/tests/testStyleChooser.py @@ -23,15 +23,13 @@ def test_rules_chain(self): sc.addCondition(parseCondition("footway=crossing")) sc.addCondition(Condition("eq", ("::class", "::*"))) - self.assertTrue( sc.testChains({ "highway": "footway", "footway": "sidewalk" }) ) - self.assertTrue( sc.testChains({ "highway": "footway", "footway": "crossing" }) ) - self.assertFalse( sc.testChains({ "highway": "footway"}) ) - self.assertFalse( sc.testChains({ "highway": "residential", "footway": "crossing" }) ) + self.assertEqual( list(sc.testChainsAll({ "highway": "footway" })), [] ) + self.assertEqual( list(sc.testChainsAll({ "highway": "residential", "footway": "crossing" })), [] ) - rule1, tt = sc.testChains({ "highway": "footway", "footway": "sidewalk" }) + rule1, tt = next(sc.testChainsAll({ "highway": "footway", "footway": "sidewalk" })) self.assertEqual(tt, "::default") - rule2, tt = sc.testChains({ "highway": "footway", "footway": "crossing" }) + rule2, tt = next(sc.testChainsAll({ "highway": "footway", "footway": "crossing" })) self.assertEqual(tt, "::*") self.assertNotEqual(rule1, rule2) @@ -51,11 +49,11 @@ def test_zoom(self): sc.addCondition(parseCondition("transport=subway")) sc.addCondition(parseCondition("city=yokohama")) - rule1, tt = sc.testChains({ "railway": "station", "transport": "subway", "city": "yerevan" }) + rule1, tt = next(sc.testChainsAll({ "railway": "station", "transport": "subway", "city": "yerevan" })) self.assertEqual(rule1.minZoom, 10) self.assertEqual(rule1.maxZoom, 19) - rule2, tt = sc.testChains({ "railway": "station", "transport": "subway", "city": "yokohama" }) + rule2, tt = next(sc.testChainsAll({ "railway": "station", "transport": "subway", "city": "yokohama" })) self.assertEqual(rule2.minZoom, 4) self.assertEqual(rule2.maxZoom, 15) @@ -289,8 +287,31 @@ def test_update_styles_by_class_all(self): def test_runtime_conditions(self): - # TODO: Create test with sc.addRuntimeCondition(Condition(condType, ('extra_tag', cond))) - pass + # libkomwm builds one drule variant per reported condition set, so every + # selected `::object-id` must report its own: the styles of an object-id + # left out are dropped by the filter_by_runtime_conditions check. + sc = StyleChooser((4, 19)) + + sc.newObject() + sc.addCondition(Condition("eq", ("::class", "::default"))) + sc.addCondition(parseCondition("place=city")) + sc.addRuntimeCondition(parseCondition("population>=1000")) + + sc.newObject() + sc.addCondition(Condition("eq", ("::class", "::int_name"))) + sc.addCondition(parseCondition("place=city")) + sc.addRuntimeCondition(parseCondition("population>=500")) + + object_tags = {"place": "city"} + + self.assertEqual([[str(c) for c in rc] for rc in sc.get_runtime_conditions(object_tags)], + [["population>=1000"], ["population>=500"]]) + + # A chooser without runtime conditions reports none. + sc_plain = StyleChooser((4, 19)) + sc_plain.newObject() + sc_plain.addCondition(parseCondition("place=city")) + self.assertEqual(sc_plain.get_runtime_conditions(object_tags), []) if __name__ == '__main__': unittest.main()