source: josm/trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParserTest.groovy @ 10837

Last change on this file since 10837 was 10837, checked in by Don-vip, 3 years ago

see #11924 - extract MapCSS conditions to new class ConditionFactory (on the same model than ExpressionFactory) - should workaround Groovy bug with Java 9 (https://issues.apache.org/jira/browse/GROOVY-7879 ?)

  • Property svn:eol-style set to native
File size: 21.2 KB
Line 
1package org.openstreetmap.josm.gui.mappaint.mapcss
2
3import java.awt.Color
4
5import org.junit.Before
6import org.junit.Test
7import org.openstreetmap.josm.JOSMFixture
8import org.openstreetmap.josm.Main
9import org.openstreetmap.josm.data.coor.LatLon
10import org.openstreetmap.josm.data.osm.DataSet
11import org.openstreetmap.josm.data.osm.Node
12import org.openstreetmap.josm.data.osm.OsmUtils
13import org.openstreetmap.josm.data.osm.Way
14import org.openstreetmap.josm.gui.mappaint.Environment
15import org.openstreetmap.josm.gui.mappaint.MultiCascade
16import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.ClassCondition
17import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyCondition
18import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyMatchType
19import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.KeyValueCondition
20import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.Op
21import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.PseudoClassCondition
22import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.SimpleKeyValueCondition
23import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser
24import org.openstreetmap.josm.tools.ColorHelper
25
26class MapCSSParserTest {
27
28    protected static Environment getEnvironment(String key, String value) {
29        return new Environment(OsmUtils.createPrimitive("way " + key + "=" + value))
30    }
31
32    protected static MapCSSParser getParser(String stringToParse) {
33        return new MapCSSParser(new StringReader(stringToParse));
34    }
35
36    @Before
37    public void setUp() throws Exception {
38        JOSMFixture.createUnitTestFixture().init();
39    }
40
41    @Test
42    public void testKothicStylesheets() throws Exception {
43        new MapCSSParser(new URL("https://raw.githubusercontent.com/kothic/kothic/master/src/styles/default.mapcss").openStream(), "UTF-8")
44        new MapCSSParser(new URL("https://raw.githubusercontent.com/kothic/kothic/master/src/styles/mapink.mapcss").openStream(), "UTF-8")
45    }
46
47    @Test
48    public void testDeclarations() {
49        getParser("{ opacity: 0.5; color: rgb(1.0, 0.0, 0.0); }").declaration()
50        getParser("{ set tag=value; }").declaration() //set a tag
51        getParser("{ set tag; }").declaration() // set a tag to 'yes'
52        getParser("{ opacity: eval(\"tag('population')/100000\"); }").declaration()
53        getParser("{ set width_in_metres=eval(\"tag('lanes')*3\"); }").declaration()
54    }
55
56    @Test
57    public void testClassCondition() throws Exception {
58        def conditions = ((Selector.GeneralSelector) getParser("way[name=X].highway:closed").selector()).conds
59        assert conditions.get(0) instanceof SimpleKeyValueCondition
60        assert conditions.get(0).applies(getEnvironment("name", "X"))
61        assert conditions.get(1) instanceof ClassCondition
62        assert conditions.get(2) instanceof PseudoClassCondition
63        assert !conditions.get(2).applies(getEnvironment("name", "X"))
64    }
65
66    @Test
67    public void testPseudoClassCondition() throws Exception {
68        def c1 = ((Selector.GeneralSelector) getParser("way!:area-style").selector()).conds.get(0)
69        def c2 = ((Selector.GeneralSelector) getParser("way!:areaStyle").selector()).conds.get(0)
70        def c3 = ((Selector.GeneralSelector) getParser("way!:area_style").selector()).conds.get(0)
71        assert c1.toString() == "!:areaStyle"
72        assert c2.toString() == "!:areaStyle"
73        assert c3.toString() == "!:areaStyle"
74    }
75
76    @Test
77    public void testClassMatching() throws Exception {
78        def css = new MapCSSStyleSource("" +
79                "way[highway=footway] { set .path; color: #FF6644; width: 2; }\n" +
80                "way[highway=path]    { set path; color: brown; width: 2; }\n" +
81                "way[\"set\"=escape]  {  }\n" +
82                "way.path             { text:auto; text-color: green; text-position: line; text-offset: 5; }\n" +
83                "way!.path            { color: orange; }\n"
84        )
85        css.loadStyleSource()
86        assert css.getErrors().isEmpty()
87        def mc1 = new MultiCascade()
88        css.apply(mc1, OsmUtils.createPrimitive("way highway=path"), 1, false);
89        assert "green".equals(mc1.getCascade("default").get("text-color", null, String.class))
90        assert "brown".equals(mc1.getCascade("default").get("color", null, String.class))
91        def mc2 = new MultiCascade()
92        css.apply(mc2, OsmUtils.createPrimitive("way highway=residential"), 1, false);
93        assert "orange".equals(mc2.getCascade("default").get("color", null, String.class))
94        assert mc2.getCascade("default").get("text-color", null, String.class) == null
95        def mc3 = new MultiCascade()
96        css.apply(mc3, OsmUtils.createPrimitive("way highway=footway"), 1, false);
97        assert ColorHelper.html2color("#FF6644").equals(mc3.getCascade("default").get("color", null, Color.class))
98    }
99
100    @Test
101    public void testEqualCondition() throws Exception {
102        def condition = (SimpleKeyValueCondition) getParser("[surface=paved]").condition(Condition.Context.PRIMITIVE)
103        assert condition instanceof SimpleKeyValueCondition
104        assert "surface".equals(condition.k)
105        assert "paved".equals(condition.v)
106        assert condition.applies(getEnvironment("surface", "paved"))
107        assert !condition.applies(getEnvironment("surface", "unpaved"))
108    }
109
110    @Test
111    public void testNotEqualCondition() throws Exception {
112        def condition = (KeyValueCondition) getParser("[surface!=paved]").condition(Condition.Context.PRIMITIVE)
113        assert Op.NEQ.equals(condition.op)
114        assert !condition.applies(getEnvironment("surface", "paved"))
115        assert condition.applies(getEnvironment("surface", "unpaved"))
116    }
117
118    @Test
119    public void testRegexCondition() throws Exception {
120        def condition = (KeyValueCondition) getParser("[surface=~/paved|unpaved/]").condition(Condition.Context.PRIMITIVE)
121        assert Op.REGEX.equals(condition.op)
122        assert condition.applies(getEnvironment("surface", "unpaved"))
123        assert !condition.applies(getEnvironment("surface", "grass"))
124    }
125
126    @Test
127    public void testRegexConditionParenthesis() throws Exception {
128        def condition = (KeyValueCondition) getParser("[name =~ /^\\(foo\\)/]").condition(Condition.Context.PRIMITIVE)
129        assert condition.applies(getEnvironment("name", "(foo)"))
130        assert !condition.applies(getEnvironment("name", "foo"))
131        assert !condition.applies(getEnvironment("name", "((foo))"))
132    }
133
134    @Test
135    public void testNegatedRegexCondition() throws Exception {
136        def condition = (KeyValueCondition) getParser("[surface!~/paved|unpaved/]").condition(Condition.Context.PRIMITIVE)
137        assert Op.NREGEX.equals(condition.op)
138        assert !condition.applies(getEnvironment("surface", "unpaved"))
139        assert condition.applies(getEnvironment("surface", "grass"))
140    }
141
142    @Test
143    public void testBeginsEndsWithCondition() throws Exception {
144        def condition = (KeyValueCondition) getParser('[foo ^= bar]').condition(Condition.Context.PRIMITIVE)
145        assert Op.BEGINS_WITH.equals(condition.op)
146        assert condition.applies(getEnvironment("foo", "bar123"))
147        assert !condition.applies(getEnvironment("foo", "123bar"))
148        assert !condition.applies(getEnvironment("foo", "123bar123"))
149        condition = (KeyValueCondition) getParser('[foo $= bar]').condition(Condition.Context.PRIMITIVE)
150        assert Op.ENDS_WITH.equals(condition.op)
151        assert !condition.applies(getEnvironment("foo", "bar123"))
152        assert condition.applies(getEnvironment("foo", "123bar"))
153        assert !condition.applies(getEnvironment("foo", "123bar123"))
154    }
155
156    @Test
157    public void testOneOfCondition() throws Exception {
158        def condition = getParser('[vending~=stamps]').condition(Condition.Context.PRIMITIVE)
159        assert condition.applies(getEnvironment("vending", "stamps"))
160        assert condition.applies(getEnvironment("vending", "bar;stamps;foo"))
161        assert !condition.applies(getEnvironment("vending", "every;thing;else"))
162        assert !condition.applies(getEnvironment("vending", "or_nothing"))
163    }
164
165    @Test
166    public void testStandardKeyCondition() throws Exception {
167        def c1 = (KeyCondition) getParser("[ highway ]").condition(Condition.Context.PRIMITIVE)
168        assert KeyMatchType.EQ.equals(c1.matchType)
169        assert c1.applies(getEnvironment("highway", "unclassified"))
170        assert !c1.applies(getEnvironment("railway", "rail"))
171        def c2 = (KeyCondition) getParser("[\"/slash/\"]").condition(Condition.Context.PRIMITIVE)
172        assert KeyMatchType.EQ.equals(c2.matchType)
173        assert c2.applies(getEnvironment("/slash/", "yes"))
174        assert !c2.applies(getEnvironment("\"slash\"", "no"))
175    }
176
177    @Test
178    public void testYesNoKeyCondition() throws Exception {
179        def c1 = (KeyCondition) getParser("[oneway?]").condition(Condition.Context.PRIMITIVE)
180        def c2 = (KeyCondition) getParser("[oneway?!]").condition(Condition.Context.PRIMITIVE)
181        def c3 = (KeyCondition) getParser("[!oneway?]").condition(Condition.Context.PRIMITIVE)
182        def c4 = (KeyCondition) getParser("[!oneway?!]").condition(Condition.Context.PRIMITIVE)
183        def yes = getEnvironment("oneway", "yes")
184        def no = getEnvironment("oneway", "no")
185        def none = getEnvironment("no-oneway", "foo")
186        assert c1.applies(yes)
187        assert !c1.applies(no)
188        assert !c1.applies(none)
189        assert !c2.applies(yes)
190        assert c2.applies(no)
191        assert !c2.applies(none)
192        assert !c3.applies(yes)
193        assert c3.applies(no)
194        assert c3.applies(none)
195        assert c4.applies(yes)
196        assert !c4.applies(no)
197        assert c4.applies(none)
198    }
199
200    @Test
201    public void testRegexKeyCondition() throws Exception {
202        def c1 = (KeyCondition) getParser("[/.*:(backward|forward)\$/]").condition(Condition.Context.PRIMITIVE)
203        assert KeyMatchType.REGEX.equals(c1.matchType)
204        assert !c1.applies(getEnvironment("lanes", "3"))
205        assert c1.applies(getEnvironment("lanes:forward", "3"))
206        assert c1.applies(getEnvironment("lanes:backward", "3"))
207        assert !c1.applies(getEnvironment("lanes:foobar", "3"))
208    }
209
210    @Test
211    public void testNRegexKeyConditionSelector() throws Exception {
212        def s1 = getParser("*[sport][tourism != hotel]").selector()
213        assert s1.matches(new Environment(OsmUtils.createPrimitive("node sport=foobar")))
214        assert !s1.matches(new Environment(OsmUtils.createPrimitive("node sport=foobar tourism=hotel")))
215        def s2 = getParser("*[sport][tourism != hotel][leisure !~ /^(sports_centre|stadium|)\$/]").selector()
216        assert s2.matches(new Environment(OsmUtils.createPrimitive("node sport=foobar")))
217        assert !s2.matches(new Environment(OsmUtils.createPrimitive("node sport=foobar tourism=hotel")))
218        assert !s2.matches(new Environment(OsmUtils.createPrimitive("node sport=foobar leisure=stadium")))
219    }
220
221    @Test
222    public void testKeyKeyCondition() throws Exception {
223        def c1 = (KeyValueCondition) getParser("[foo = *bar]").condition(Condition.Context.PRIMITIVE)
224        def w1 = new Way()
225        w1.put("foo", "123")
226        w1.put("bar", "456")
227        assert !c1.applies(new Environment(w1))
228        w1.put("bar", "123")
229        assert c1.applies(new Environment(w1))
230        def c2 = (KeyValueCondition) getParser("[foo =~ */bar/]").condition(Condition.Context.PRIMITIVE)
231        def w2 = new Way(w1)
232        w2.put("bar", "[0-9]{3}")
233        assert c2.applies(new Environment(w2))
234        w2.put("bar", "[0-9]")
235        assert c2.applies(new Environment(w2))
236        w2.put("bar", "^[0-9]\$")
237        assert !c2.applies(new Environment(w2))
238    }
239
240    @Test
241    public void testParentTag() throws Exception {
242        def c1 = getParser("way[foo] > node[tag(\"foo\")=parent_tag(\"foo\")] {}").child_selector()
243        def ds = new DataSet()
244        def w1 = new Way()
245        def w2 = new Way()
246        def n1 = new Node(new LatLon(1, 1))
247        def n2 = new Node(new LatLon(2, 2))
248        w1.put("foo", "123")
249        w2.put("foo", "123")
250        n1.put("foo", "123")
251        n2.put("foo", "0")
252        ds.addPrimitive(w1)
253        ds.addPrimitive(n1)
254        ds.addPrimitive(n2)
255        w1.addNode(n1)
256        w2.addNode(n2)
257        assert c1.matches(new Environment(n1))
258        assert !c1.matches(new Environment(n2))
259        assert !c1.matches(new Environment(w1))
260        assert !c1.matches(new Environment(w2))
261        n1.put("foo", "0")
262        assert !c1.matches(new Environment(n1))
263        n1.put("foo", "123")
264        assert c1.matches(new Environment(n1))
265    }
266
267    @Test
268    public void testTicket8568() throws Exception {
269        def sheet = new MapCSSStyleSource("" +
270                "way { width: 5; }\n" +
271                "way[keyA], way[keyB] { width: eval(prop(width)+10); }")
272        sheet.loadStyleSource()
273        def mc = new MultiCascade()
274        sheet.apply(mc, OsmUtils.createPrimitive("way foo=bar"), 20, false)
275        assert mc.getCascade(Environment.DEFAULT_LAYER).get("width") == 5
276        sheet.apply(mc, OsmUtils.createPrimitive("way keyA=true"), 20, false)
277        assert mc.getCascade(Environment.DEFAULT_LAYER).get("width") == 15
278        sheet.apply(mc, OsmUtils.createPrimitive("way keyB=true"), 20, false)
279        assert mc.getCascade(Environment.DEFAULT_LAYER).get("width") == 15
280        sheet.apply(mc, OsmUtils.createPrimitive("way keyA=true keyB=true"), 20, false)
281        assert mc.getCascade(Environment.DEFAULT_LAYER).get("width") == 15
282    }
283
284    @Test
285    public void testTicket8071() throws Exception {
286        def sheet = new MapCSSStyleSource("" +
287                "*[rcn_ref], *[name] {text: concat(tag(rcn_ref), \" \", tag(name)); }")
288        sheet.loadStyleSource()
289        def mc = new MultiCascade()
290        sheet.apply(mc, OsmUtils.createPrimitive("way name=Foo"), 20, false)
291        assert mc.getCascade(Environment.DEFAULT_LAYER).get("text") == " Foo"
292        sheet.apply(mc, OsmUtils.createPrimitive("way rcn_ref=15"), 20, false)
293        assert mc.getCascade(Environment.DEFAULT_LAYER).get("text") == "15 "
294        sheet.apply(mc, OsmUtils.createPrimitive("way rcn_ref=15 name=Foo"), 20, false)
295        assert mc.getCascade(Environment.DEFAULT_LAYER).get("text") == "15 Foo"
296
297        sheet = new MapCSSStyleSource("" +
298                "*[rcn_ref], *[name] {text: join(\" - \", tag(rcn_ref), tag(ref), tag(name)); }")
299        sheet.loadStyleSource()
300        sheet.apply(mc, OsmUtils.createPrimitive("way rcn_ref=15 ref=1.5 name=Foo"), 20, false)
301        assert mc.getCascade(Environment.DEFAULT_LAYER).get("text") == "15 - 1.5 - Foo"
302    }
303
304    @Test
305    public void testColorNameTicket9191() throws Exception {
306        def e = new Environment(null, new MultiCascade(), Environment.DEFAULT_LAYER, null)
307        getParser("{color: testcolour1#88DD22}").declaration().instructions.get(0).execute(e)
308        def expected = new Color(0x88DD22)
309        assert e.getCascade(Environment.DEFAULT_LAYER).get("color") == expected
310        assert Main.pref.getDefaultColor("mappaint.mapcss.testcolour1") == expected
311    }
312
313    @Test
314    public void testColorNameTicket9191Alpha() throws Exception {
315        def e = new Environment(null, new MultiCascade(), Environment.DEFAULT_LAYER, null)
316        getParser("{color: testcolour2#12345678}").declaration().instructions.get(0).execute(e)
317        def expected = new Color(0x12, 0x34, 0x56, 0x78)
318        assert e.getCascade(Environment.DEFAULT_LAYER).get("color") == expected
319        assert Main.pref.getDefaultColor("mappaint.mapcss.testcolour2") == expected
320    }
321
322    @Test
323    public void testColorParsing() throws Exception {
324        assert ColorHelper.html2color("#12345678") == new Color(0x12, 0x34, 0x56, 0x78)
325    }
326
327    @Test
328    public void testChildSelectorGreaterThanSignIsOptional() throws Exception {
329        assert getParser("relation[type=route] way[highway]").child_selector().toString() ==
330                getParser("relation[type=route] > way[highway]").child_selector().toString()
331    }
332
333    @Test
334    public void testSiblingSelector() throws Exception {
335        def s1 = (Selector.ChildOrParentSelector) getParser("*[a?][parent_tag(\"highway\")=\"unclassified\"] + *[b?]").child_selector()
336        def ds = new DataSet()
337        def n1 = new org.openstreetmap.josm.data.osm.Node(new LatLon(1, 2))
338        n1.put("a", "true")
339        def n2 = new org.openstreetmap.josm.data.osm.Node(new LatLon(1.1, 2.2))
340        n2.put("b", "true")
341        def w = new Way()
342        w.put("highway", "unclassified")
343        ds.addPrimitive(n1)
344        ds.addPrimitive(n2)
345        ds.addPrimitive(w)
346        w.addNode(n1)
347        w.addNode(n2)
348
349        def e = new Environment(n2)
350        assert s1.matches(e)
351        assert e.osm == n2
352        assert e.child == n1
353        assert e.parent == w
354        assert !s1.matches(new Environment(n1))
355        assert !s1.matches(new Environment(w))
356    }
357
358    @Test
359    public void testParentTags() throws Exception {
360        def ds = new DataSet()
361        def n = new org.openstreetmap.josm.data.osm.Node(new LatLon(1, 2))
362        n.put("foo", "bar")
363        def w1 = new Way()
364        w1.put("ref", "x10")
365        def w2 = new Way()
366        w2.put("ref", "x2")
367        def w3 = new Way()
368        ds.addPrimitive(n)
369        ds.addPrimitive(w1)
370        ds.addPrimitive(w2)
371        ds.addPrimitive(w3)
372        w1.addNode(n)
373        w2.addNode(n)
374        w3.addNode(n)
375
376        MapCSSStyleSource source = new MapCSSStyleSource("node[foo=bar] {refs: join_list(\";\", parent_tags(\"ref\"));}")
377        source.loadStyleSource()
378        assert source.rules.size() == 1
379        def e = new Environment(n, new MultiCascade(), Environment.DEFAULT_LAYER, null)
380        assert source.rules.get(0).selector.matches(e)
381        source.rules.get(0).declaration.execute(e)
382        assert e.getCascade(Environment.DEFAULT_LAYER).get("refs", null, String.class) == "x2;x10"
383    }
384
385    @Test
386    public void testSiblingSelectorInterpolation() throws Exception {
387        def s1 = (Selector.ChildOrParentSelector) getParser(
388                "*[tag(\"addr:housenumber\") > child_tag(\"addr:housenumber\")][regexp_test(\"even|odd\", parent_tag(\"addr:interpolation\"))]" +
389                        " + *[addr:housenumber]").child_selector()
390        def ds = new DataSet()
391        def n1 = new org.openstreetmap.josm.data.osm.Node(new LatLon(1, 2))
392        n1.put("addr:housenumber", "10")
393        def n2 = new org.openstreetmap.josm.data.osm.Node(new LatLon(1.1, 2.2))
394        n2.put("addr:housenumber", "100")
395        def n3 = new org.openstreetmap.josm.data.osm.Node(new LatLon(1.2, 2.3))
396        n3.put("addr:housenumber", "20")
397        def w = new Way()
398        w.put("addr:interpolation", "even")
399        ds.addPrimitive(n1)
400        ds.addPrimitive(n2)
401        ds.addPrimitive(n3)
402        ds.addPrimitive(w)
403        w.addNode(n1)
404        w.addNode(n2)
405        w.addNode(n3)
406
407        assert s1.right.matches(new Environment(n3))
408        assert s1.left.matches(new Environment(n2).withChild(n3).withParent(w))
409        assert s1.matches(new Environment(n3))
410        assert !s1.matches(new Environment(n1))
411        assert !s1.matches(new Environment(n2))
412        assert !s1.matches(new Environment(w))
413    }
414
415    @Test
416    public void testInvalidBaseSelector() throws Exception {
417        def css = new MapCSSStyleSource("invalid_base[key=value] {}")
418        css.loadStyleSource()
419        assert !css.getErrors().isEmpty()
420        assert css.getErrors().iterator().next().toString().contains("Unknown MapCSS base selector invalid_base")
421    }
422
423    @Test
424    public void testMinMaxFunctions() throws Exception {
425        def sheet = new MapCSSStyleSource("* {" +
426                "min_value: min(tag(x), tag(y), tag(z)); " +
427                "max_value: max(tag(x), tag(y), tag(z)); " +
428                "max_split: max(split(\";\", tag(widths))); " +
429                "}")
430        sheet.loadStyleSource()
431        def mc = new MultiCascade()
432
433        sheet.apply(mc, OsmUtils.createPrimitive("way x=4 y=6 z=8 u=100"), 20, false)
434        assert mc.getCascade(Environment.DEFAULT_LAYER).get("min_value", Float.NaN, Float.class) == 4.0f
435        assert mc.getCascade(Environment.DEFAULT_LAYER).get("max_value", Float.NaN, Float.class) == 8.0f
436
437        sheet.apply(mc, OsmUtils.createPrimitive("way x=4 y=6 widths=1;2;8;56;3;a"), 20, false)
438        assert mc.getCascade(Environment.DEFAULT_LAYER).get("min_value", -777f, Float.class) == 4
439        assert mc.getCascade(Environment.DEFAULT_LAYER).get("max_value", -777f, Float.class) == 6
440        assert mc.getCascade(Environment.DEFAULT_LAYER).get("max_split", -777f, Float.class) == 56
441    }
442
443    @Test
444    public void testTicket12549() throws Exception {
445        def condition = getParser("[name =~ /^(?i)(?u)fóo\$/]").condition(Condition.Context.PRIMITIVE)
446        assert condition.applies(new Environment(OsmUtils.createPrimitive("way name=fóo")))
447        assert condition.applies(new Environment(OsmUtils.createPrimitive("way name=fÓo")))
448        condition = getParser("[name =~ /^(\\p{Lower})+\$/]").condition(Condition.Context.PRIMITIVE)
449        assert !condition.applies(new Environment(OsmUtils.createPrimitive("way name=fóo")))
450        condition = getParser("[name =~ /^(?U)(\\p{Lower})+\$/]").condition(Condition.Context.PRIMITIVE)
451        assert condition.applies(new Environment(OsmUtils.createPrimitive("way name=fóo")))
452        assert !condition.applies(new Environment(OsmUtils.createPrimitive("way name=fÓo")))
453    }
454}
Note: See TracBrowser for help on using the repository browser.