]> git.openstreetmap.org Git - nominatim.git/blob - test/python/test_tokenizer_icu_rule_loader.py
switch to a more flexible variant description format
[nominatim.git] / test / python / test_tokenizer_icu_rule_loader.py
1 """
2 Tests for converting a config file to ICU rules.
3 """
4 import pytest
5 from textwrap import dedent
6
7 from nominatim.tokenizer.icu_rule_loader import ICURuleLoader
8 from nominatim.errors import UsageError
9
10 from icu import Transliterator
11
12 @pytest.fixture
13 def cfgfile(tmp_path, suffix='.yaml'):
14     def _create_config(*variants, **kwargs):
15         content = dedent("""\
16         normalization:
17             - ":: NFD ()"
18             - "[[:Nonspacing Mark:] [:Cf:]] >"
19             - ":: lower ()"
20             - "[[:Punctuation:][:Space:]]+ > ' '"
21             - ":: NFC ()"
22         transliteration:
23             - "::  Latin ()"
24             - "[[:Punctuation:][:Space:]]+ > ' '"
25         """)
26         content += "variants:\n  - words:\n"
27         content += '\n'.join(("      - " + s for s in variants)) + '\n'
28         for k, v in kwargs:
29             content += "    {}: {}\n".format(k, v)
30         fpath = tmp_path / ('test_config' + suffix)
31         fpath.write_text(dedent(content))
32         return fpath
33
34     return _create_config
35
36
37 def test_empty_rule_file(tmp_path):
38     fpath = tmp_path / ('test_config.yaml')
39     fpath.write_text(dedent("""\
40         normalization:
41         transliteration:
42         variants:
43         """))
44
45     rules = ICURuleLoader(fpath)
46     assert rules.get_search_rules() == ''
47     assert rules.get_normalization_rules() == ''
48     assert rules.get_transliteration_rules() == ''
49     assert list(rules.get_replacement_pairs()) == []
50
51 CONFIG_SECTIONS = ('normalization', 'transliteration', 'variants')
52
53 @pytest.mark.parametrize("section", CONFIG_SECTIONS)
54 def test_missing_normalization(tmp_path, section):
55     fpath = tmp_path / ('test_config.yaml')
56     with fpath.open('w') as fd:
57         for name in CONFIG_SECTIONS:
58             if name != section:
59                 fd.write(name + ':\n')
60
61     with pytest.raises(UsageError):
62         ICURuleLoader(fpath)
63
64
65 def test_get_search_rules(cfgfile):
66     loader = ICURuleLoader(cfgfile())
67
68     rules = loader.get_search_rules()
69     trans = Transliterator.createFromRules("test", rules)
70
71     assert trans.transliterate(" Baum straße ") == " baum straße "
72     assert trans.transliterate(" Baumstraße ") == " baumstraße "
73     assert trans.transliterate(" Baumstrasse ") == " baumstrasse "
74     assert trans.transliterate(" Baumstr ") == " baumstr "
75     assert trans.transliterate(" Baumwegstr ") == " baumwegstr "
76     assert trans.transliterate(" Αθήνα ") == " athēna "
77     assert trans.transliterate(" проспект ") == " prospekt "
78
79
80 def test_get_normalization_rules(cfgfile):
81     loader = ICURuleLoader(cfgfile())
82     rules = loader.get_normalization_rules()
83     trans = Transliterator.createFromRules("test", rules)
84
85     assert trans.transliterate(" проспект-Prospekt ") == " проспект prospekt "
86
87
88 def test_get_transliteration_rules(cfgfile):
89     loader = ICURuleLoader(cfgfile())
90     rules = loader.get_transliteration_rules()
91     trans = Transliterator.createFromRules("test", rules)
92
93     assert trans.transliterate(" проспект-Prospekt ") == " prospekt Prospekt "
94
95
96 def test_transliteration_rules_from_file(tmp_path):
97     cfgpath = tmp_path / ('test_config.yaml')
98     cfgpath.write_text(dedent("""\
99         normalization:
100         transliteration:
101             - "'ax' > 'b'"
102             - !include transliteration.yaml
103         variants:
104         """))
105     transpath = tmp_path / ('transliteration.yaml')
106     transpath.write_text('- "x > y"')
107
108     loader = ICURuleLoader(cfgpath)
109     rules = loader.get_transliteration_rules()
110     trans = Transliterator.createFromRules("test", rules)
111
112     assert trans.transliterate(" axxt ") == " byt "
113
114
115 class TestGetReplacements:
116
117     @pytest.fixture(autouse=True)
118     def setup_cfg(self, cfgfile):
119         self.cfgfile = cfgfile
120
121     def get_replacements(self, *variants):
122         loader = ICURuleLoader(self.cfgfile(*variants))
123         rules = loader.get_replacement_pairs()
124
125         return set((v.source, v.replacement) for v in rules)
126
127
128     @pytest.mark.parametrize("variant", ['foo > bar', 'foo -> bar -> bar',
129                                          '~foo~ -> bar', 'fo~ o -> bar'])
130     def test_invalid_variant_description(self, variant):
131         with pytest.raises(UsageError):
132             ICURuleLoader(self.cfgfile(variant))
133
134     def test_add_full(self):
135         repl = self.get_replacements("foo -> bar")
136
137         assert repl == {(' foo ', ' bar '), (' foo ', ' foo ')}
138
139
140     def test_replace_full(self):
141         repl = self.get_replacements("foo => bar")
142
143         assert repl == {(' foo ', ' bar ')}
144
145
146     def test_add_suffix_no_decompose(self):
147         repl = self.get_replacements("~berg |-> bg")
148
149         assert repl == {('berg ', 'berg '), ('berg ', 'bg '),
150                         (' berg ', ' berg '), (' berg ', ' bg ')}
151
152
153     def test_replace_suffix_no_decompose(self):
154         repl = self.get_replacements("~berg |=> bg")
155
156         assert repl == {('berg ', 'bg '), (' berg ', ' bg ')}
157
158
159     def test_add_suffix_decompose(self):
160         repl = self.get_replacements("~berg -> bg")
161
162         assert repl == {('berg ', 'berg '), ('berg ', ' berg '),
163                         (' berg ', ' berg '), (' berg ', 'berg '),
164                         ('berg ', 'bg '), ('berg ', ' bg '),
165                         (' berg ', 'bg '), (' berg ', ' bg ')}
166
167
168     def test_replace_suffix_decompose(self):
169         repl = self.get_replacements("~berg => bg")
170
171         assert repl == {('berg ', 'bg '), ('berg ', ' bg '),
172                         (' berg ', 'bg '), (' berg ', ' bg ')}
173
174
175     def test_add_prefix_no_compose(self):
176         repl = self.get_replacements("hinter~ |-> hnt")
177
178         assert repl == {(' hinter', ' hinter'), (' hinter ', ' hinter '),
179                         (' hinter', ' hnt'), (' hinter ', ' hnt ')}
180
181
182     def test_replace_prefix_no_compose(self):
183         repl = self.get_replacements("hinter~ |=> hnt")
184
185         assert repl ==  {(' hinter', ' hnt'), (' hinter ', ' hnt ')}
186
187
188     def test_add_prefix_compose(self):
189         repl = self.get_replacements("hinter~-> h")
190
191         assert repl == {(' hinter', ' hinter'), (' hinter', ' hinter '),
192                         (' hinter', ' h'), (' hinter', ' h '),
193                         (' hinter ', ' hinter '), (' hinter ', ' hinter'),
194                         (' hinter ', ' h '), (' hinter ', ' h')}
195
196
197     def test_replace_prefix_compose(self):
198         repl = self.get_replacements("hinter~=> h")
199
200         assert repl == {(' hinter', ' h'), (' hinter', ' h '),
201                         (' hinter ', ' h '), (' hinter ', ' h')}
202
203
204     def test_add_beginning_only(self):
205         repl = self.get_replacements("^Premier -> Pr")
206
207         assert repl == {('^ premier ', '^ premier '), ('^ premier ', '^ pr ')}
208
209
210     def test_replace_beginning_only(self):
211         repl = self.get_replacements("^Premier => Pr")
212
213         assert repl == {('^ premier ', '^ pr ')}
214
215
216     def test_add_final_only(self):
217         repl = self.get_replacements("road$ -> rd")
218
219         assert repl == {(' road ^', ' road ^'), (' road ^', ' rd ^')}
220
221
222     def test_replace_final_only(self):
223         repl = self.get_replacements("road$ => rd")
224
225         assert repl == {(' road ^', ' rd ^')}
226
227
228     def test_decompose_only(self):
229         repl = self.get_replacements("~foo -> foo")
230
231         assert repl == {('foo ', 'foo '), ('foo ', ' foo '),
232                         (' foo ', 'foo '), (' foo ', ' foo ')}
233
234
235     def test_add_suffix_decompose_end_only(self):
236         repl = self.get_replacements("~berg |-> bg", "~berg$ -> bg")
237
238         assert repl == {('berg ', 'berg '), ('berg ', 'bg '),
239                         (' berg ', ' berg '), (' berg ', ' bg '),
240                         ('berg ^', 'berg ^'), ('berg ^', ' berg ^'),
241                         ('berg ^', 'bg ^'), ('berg ^', ' bg ^'),
242                         (' berg ^', 'berg ^'), (' berg ^', 'bg ^'),
243                         (' berg ^', ' berg ^'), (' berg ^', ' bg ^')}
244
245
246     def test_replace_suffix_decompose_end_only(self):
247         repl = self.get_replacements("~berg |=> bg", "~berg$ => bg")
248
249         assert repl == {('berg ', 'bg '), (' berg ', ' bg '),
250                         ('berg ^', 'bg ^'), ('berg ^', ' bg ^'),
251                         (' berg ^', 'bg ^'), (' berg ^', ' bg ^')}
252
253
254     def test_add_multiple_suffix(self):
255         repl = self.get_replacements("~berg,~burg -> bg")
256
257         assert repl == {('berg ', 'berg '), ('berg ', ' berg '),
258                         (' berg ', ' berg '), (' berg ', 'berg '),
259                         ('berg ', 'bg '), ('berg ', ' bg '),
260                         (' berg ', 'bg '), (' berg ', ' bg '),
261                         ('burg ', 'burg '), ('burg ', ' burg '),
262                         (' burg ', ' burg '), (' burg ', 'burg '),
263                         ('burg ', 'bg '), ('burg ', ' bg '),
264                         (' burg ', 'bg '), (' burg ', ' bg ')}