From f2446d1e0da24b8566f2dddc426e244f46cc700d Mon Sep 17 00:00:00 2001 From: Alexander Borsuk Date: Fri, 14 Aug 2026 23:04:27 +0200 Subject: [PATCH] Apply a declaration block to every ::object-id it selects updateStyles() took the first rule testChains() matched and applied the whole declaration block to that single ::object-id, so in a selector group that mixes layers only the first one was ever styled: node|z16-[addr:housenumber][addr:street], node|z16-[addr:housenumber][addr:street]::int_name, {text: none;} muted ::default and left ::int_name rendered. The surviving layer then owns the caption drule, so libkomwm asks for a priority of `::int_name`, which no style defines, and generation fails with "ERROR: priority is not set". Which selector wins is positional, so reordering the group only moves the hole. This is what the /* ToDo text: none; doesn't work here .. */ comment in the main repo's vehicle style has been working around. Match each distinct ::object-id of the group instead. Output is byte-identical for all four style families of the main repo (default, outdoors, cycling, vehicle; light and dark), and the block above now produces exactly what the ::* workaround does. test_update_styles_by_class pinned the old behaviour ("This class is ignored by StyleChooser"); it now asserts that both selected classes are styled. Signed-off-by: Alexander Borsuk --- src/mapcss/StyleChooser.py | 23 +++++++++++++++++------ tests/testStyleChooser.py | 13 +++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index d5da1d45..8d5c2608 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -125,14 +125,12 @@ def get_runtime_conditions(self, tags): # TODO: Rename to "applyStyles" def updateStyles(self, sl, tags, xscale, zscale, filter_by_runtime_conditions): # Are any of the ruleChains fulfilled? - rule_and_object_id = self.testChains(tags) - - if not rule_and_object_id: - return sl + for rule, object_id in self.testChainsAll(tags): + self.applyStylesTo(sl, tags, xscale, zscale, filter_by_runtime_conditions, rule, object_id) - rule = rule_and_object_id[0] - object_id = rule_and_object_id[1] + return sl + def applyStylesTo(self, sl, tags, xscale, zscale, filter_by_runtime_conditions, rule, object_id): if (filter_by_runtime_conditions is not None and rule.runtime_conditions is not None and filter_by_runtime_conditions != rule.runtime_conditions): @@ -180,6 +178,19 @@ def updateStyles(self, sl, tags, xscale, zscale, filter_by_runtime_conditions): return sl + 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. + """ + object_ids = set() + for r in self.ruleChains: + tt = r.test(tags) + if tt and tt not in object_ids: + object_ids.add(tt) + yield r, tt + def testChains(self, tags): """ Tests an object against a chain diff --git a/tests/testStyleChooser.py b/tests/testStyleChooser.py index c0784288..86b415ab 100644 --- a/tests/testStyleChooser.py +++ b/tests/testStyleChooser.py @@ -213,11 +213,11 @@ def test_update_styles_by_class(self): sc = StyleChooser((15, 19)) sc.newObject() - sc.addCondition(Condition("eq", ("::class", "::flats") )) # `sc` styles should apply only to `::flats` class + sc.addCondition(Condition("eq", ("::class", "::flats") )) # `sc` styles apply to `::flats` sc.addCondition(parseCondition("oneway?")) sc.newObject() - sc.addCondition(Condition("eq", ("::class", "::bridgeblack") )) # This class is ignored by StyleChooser + sc.addCondition(Condition("eq", ("::class", "::bridgeblack") )) # ... and to `::bridgeblack` sc.addCondition(parseCondition("oneway?")) sc.addStyles([{ @@ -230,16 +230,17 @@ def test_update_styles_by_class(self): # Apply new style to predefined styles with filter by class new_styles = sc.updateStyles(styles, object_tags, 1.0, 1.0, False) - expected_new_styles = [{ # The first style changes + expected_new_styles = [{ # Selected by the first rule "some-width": 1.5, "other-offset": 4.0, "object-id": "::flats" }, - { # Style not changed (class is not `::flats`) - "some-width": 3.5, + { # Selected by the second rule + "some-width": 1.5, + "other-offset": 4.0, "object-id": "::bridgeblack" }, - { # Style not changed (class is not `::flats`) + { # Style not changed (class is neither `::flats` nor `::bridgeblack`) "some-width": 4.5, "object-id": "::default" }]