Fix thinko.
[libdcp.git] / src / language_tag.cc
1 /*
2     Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34
35 #include "compose.hpp"
36 #include "dcp_assert.h"
37 #include "exceptions.h"
38 #include "language_tag.h"
39 #include <boost/algorithm/string.hpp>
40 #include <boost/foreach.hpp>
41 #include <string>
42
43
44 using std::make_pair;
45 using std::ostream;
46 using std::pair;
47 using std::string;
48 using std::vector;
49 using boost::optional;
50 using namespace dcp;
51
52
53 #include "language_tag_lists.cc"
54
55
56 static
57 optional<LanguageTag::SubtagData>
58 find_in_list (LanguageTag::SubtagData* list, int length, string subtag)
59 {
60         for (int i = 0; i < length; ++i) {
61                 if (list[i].subtag == subtag) {
62                         return list[i];
63                 }
64         }
65
66         return optional<LanguageTag::SubtagData>();
67 }
68
69
70 LanguageTag::Subtag::Subtag (string subtag, SubtagType type)
71         : _subtag (subtag)
72 {
73         if (!get_subtag_data(type, subtag)) {
74                 throw LanguageTagError(String::compose("Unknown %1 string %2", subtag_type_name(type), subtag));
75         }
76 }
77
78
79 LanguageTag::LanguageTag (string tag)
80 {
81         vector<string> parts;
82         boost::split (parts, tag, boost::is_any_of("-"));
83         if (parts.empty()) {
84                 throw LanguageTagError (String::compose("Could not parse language tag %1", tag));
85         }
86
87         vector<string>::size_type p = 0;
88         _language = LanguageSubtag (parts[p]);
89         ++p;
90
91         if (p == parts.size()) {
92                 return;
93         }
94
95         try {
96                 _script = ScriptSubtag (parts[p]);
97                 ++p;
98         } catch (...) {}
99
100         if (p == parts.size()) {
101                 return;
102         }
103
104         try {
105                 _region = RegionSubtag (parts[p]);
106                 ++p;
107         } catch (...) {}
108
109         if (p == parts.size()) {
110                 return;
111         }
112
113         try {
114                 while (true) {
115                         _variants.push_back (VariantSubtag(parts[p]));
116                         ++p;
117                         if (p == parts.size()) {
118                                 return;
119                         }
120                 }
121         } catch (...) {}
122
123         try {
124                 while (true) {
125                         _extlangs.push_back (ExtlangSubtag(parts[p]));
126                         ++p;
127                         if (p == parts.size()) {
128                                 return;
129                         }
130                 }
131         } catch (...) {}
132
133         if (p < parts.size()) {
134                 throw LanguageTagError (String::compose("Unrecognised subtag %1", parts[p]));
135         }
136 }
137
138
139 string
140 LanguageTag::to_string () const
141 {
142         if (!_language) {
143                 throw LanguageTagError("No language set up");
144         }
145
146         string s = _language->subtag();
147
148         if (_script) {
149                 s += "-" + _script->subtag();
150         }
151
152         if (_region) {
153                 s += "-" + _region->subtag();
154         }
155
156         BOOST_FOREACH (VariantSubtag i, _variants) {
157                 s += "-" + i.subtag();
158         }
159
160         BOOST_FOREACH (ExtlangSubtag i, _extlangs) {
161                 s += "-" + i.subtag();
162         }
163
164         return s;
165 }
166
167
168 void
169 LanguageTag::set_language (LanguageSubtag language)
170 {
171         _language = language;
172 }
173
174
175 void
176 LanguageTag::set_script (ScriptSubtag script)
177 {
178         _script = script;
179 }
180
181
182 void
183 LanguageTag::set_region (RegionSubtag region)
184 {
185         _region = region;
186 }
187
188
189 void
190 LanguageTag::add_variant (VariantSubtag variant)
191 {
192         if (find(_variants.begin(), _variants.end(), variant) != _variants.end()) {
193                 throw LanguageTagError (String::compose("Duplicate Variant subtag %1", variant.subtag()));
194         }
195
196         _variants.push_back (variant);
197 }
198
199
200 template <class T>
201 void
202 check_for_duplicates (vector<T> const& subtags, dcp::LanguageTag::SubtagType type)
203 {
204         vector<T> sorted = subtags;
205         sort (sorted.begin(), sorted.end());
206         optional<T> last;
207         BOOST_FOREACH (T const& i, sorted) {
208                 if (last && i == *last) {
209                         throw LanguageTagError (String::compose("Duplicate %1 subtag %2", dcp::LanguageTag::subtag_type_name(type), i.subtag()));
210                 }
211                 last = i;
212         }
213 }
214
215
216 void
217 LanguageTag::set_variants (vector<VariantSubtag> variants)
218 {
219         check_for_duplicates (variants, VARIANT);
220         _variants = variants;
221 }
222
223
224 void
225 LanguageTag::add_extlang (ExtlangSubtag extlang)
226 {
227         if (find(_extlangs.begin(), _extlangs.end(), extlang) != _extlangs.end()) {
228                 throw LanguageTagError (String::compose("Duplicate Extlang subtag %1", extlang.subtag()));
229         }
230
231         _extlangs.push_back (extlang);
232 }
233
234
235 void
236 LanguageTag::set_extlangs (vector<ExtlangSubtag> extlangs)
237 {
238         check_for_duplicates (extlangs, EXTLANG);
239         _extlangs = extlangs;
240 }
241
242
243 string
244 LanguageTag::description () const
245 {
246         if (!_language) {
247                 throw LanguageTagError("No language set up");
248         }
249
250         string d;
251
252         BOOST_FOREACH (VariantSubtag const& i, _variants) {
253                 optional<SubtagData> variant = get_subtag_data (VARIANT, i.subtag());
254                 DCP_ASSERT (variant);
255                 d += variant->description + " dialect of ";
256         }
257
258         optional<SubtagData> language = get_subtag_data (LANGUAGE, _language->subtag());
259         DCP_ASSERT (language);
260         d += language->description;
261
262         if (_script) {
263                 optional<SubtagData> script = get_subtag_data (SCRIPT, _script->subtag());
264                 DCP_ASSERT (script);
265                 d += " written using the " + script->description + " script";
266         }
267
268         if (_region) {
269                 optional<SubtagData> region = get_subtag_data (REGION, _region->subtag());
270                 DCP_ASSERT (region);
271                 d += " for " + region->description;
272         }
273
274         BOOST_FOREACH (ExtlangSubtag const& i, _extlangs) {
275                 optional<SubtagData> extlang = get_subtag_data (EXTLANG, i.subtag());
276                 DCP_ASSERT (extlang);
277                 d += ", " + extlang->description;
278         }
279
280         return d;
281 }
282
283
284 vector<LanguageTag::SubtagData>
285 LanguageTag::get_all (SubtagType type)
286 {
287         vector<LanguageTag::SubtagData> all;
288
289         switch (type) {
290         case LANGUAGE:
291                 for (size_t i = 0; i < sizeof(language_list) / sizeof(LanguageTag::SubtagData); ++i) {
292                         all.push_back (language_list[i]);
293                 }
294                 break;
295         case SCRIPT:
296                 for (size_t i = 0; i < sizeof(script_list) / sizeof(LanguageTag::SubtagData); ++i) {
297                         all.push_back (script_list[i]);
298                 }
299                 break;
300         case REGION:
301                 for (size_t i = 0; i < sizeof(region_list) / sizeof(LanguageTag::SubtagData); ++i) {
302                         all.push_back (region_list[i]);
303                 }
304                 break;
305         case VARIANT:
306                 for (size_t i = 0; i < sizeof(variant_list) / sizeof(LanguageTag::SubtagData); ++i) {
307                         all.push_back (variant_list[i]);
308                 }
309                 break;
310         case EXTLANG:
311                 for (size_t i = 0; i < sizeof(extlang_list) / sizeof(LanguageTag::SubtagData); ++i) {
312                         all.push_back (extlang_list[i]);
313                 }
314                 break;
315         }
316
317         return all;
318 }
319
320
321 string
322 LanguageTag::subtag_type_name (SubtagType type)
323 {
324         switch (type) {
325                 case LANGUAGE:
326                         return "Language";
327                 case SCRIPT:
328                         return "Script";
329                 case REGION:
330                         return "Region";
331                 case VARIANT:
332                         return "Variant";
333                 case EXTLANG:
334                         return "Extended";
335         }
336
337         return "";
338 }
339
340 bool
341 dcp::LanguageTag::VariantSubtag::operator== (VariantSubtag const & other) const
342 {
343         return subtag() == other.subtag();
344 }
345
346
347 bool
348 dcp::LanguageTag::VariantSubtag::operator< (VariantSubtag const & other) const
349 {
350         return subtag() < other.subtag();
351 }
352
353
354 bool
355 dcp::LanguageTag::ExtlangSubtag::operator== (ExtlangSubtag const & other) const
356 {
357         return subtag() == other.subtag();
358 }
359
360
361 bool
362 dcp::LanguageTag::ExtlangSubtag::operator< (ExtlangSubtag const & other) const
363 {
364         return subtag() < other.subtag();
365 }
366
367
368 bool
369 dcp::operator== (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
370 {
371         return a.to_string() == b.to_string();
372 }
373
374
375 ostream&
376 dcp::operator<< (ostream& os, dcp::LanguageTag const& tag)
377 {
378         os << tag.to_string();
379         return os;
380 }
381
382
383 vector<pair<LanguageTag::SubtagType, LanguageTag::SubtagData> >
384 LanguageTag::subtags () const
385 {
386         vector<pair<SubtagType, SubtagData> > s;
387
388         if (_language) {
389                 s.push_back (make_pair(LANGUAGE, *get_subtag_data(LANGUAGE, _language->subtag())));
390         }
391
392         if (_script) {
393                 s.push_back (make_pair(SCRIPT, *get_subtag_data(SCRIPT, _script->subtag())));
394         }
395
396         if (_region) {
397                 s.push_back (make_pair(REGION, *get_subtag_data(REGION, _region->subtag())));
398         }
399
400         BOOST_FOREACH (VariantSubtag const& i, _variants) {
401                 s.push_back (make_pair(VARIANT, *get_subtag_data(VARIANT, i.subtag())));
402         }
403
404         BOOST_FOREACH (ExtlangSubtag const& i, _extlangs) {
405                 s.push_back (make_pair(EXTLANG, *get_subtag_data(EXTLANG, i.subtag())));
406         }
407
408         return s;
409 }
410
411
412 optional<LanguageTag::SubtagData>
413 LanguageTag::get_subtag_data (LanguageTag::SubtagType type, string subtag)
414 {
415         switch (type) {
416         case dcp::LanguageTag::LANGUAGE:
417                 return find_in_list(language_list, sizeof(language_list) / sizeof(LanguageTag::SubtagData), subtag);
418         case dcp::LanguageTag::SCRIPT:
419                 return find_in_list(script_list, sizeof(script_list) / sizeof(LanguageTag::SubtagData), subtag);
420         case dcp::LanguageTag::REGION:
421                 return find_in_list(region_list, sizeof(region_list) / sizeof(LanguageTag::SubtagData), subtag);
422         case dcp::LanguageTag::VARIANT:
423                 return find_in_list(variant_list, sizeof(variant_list) / sizeof(LanguageTag::SubtagData), subtag);
424         case dcp::LanguageTag::EXTLANG:
425                 return find_in_list(extlang_list, sizeof(extlang_list) / sizeof(LanguageTag::SubtagData), subtag);
426         }
427
428         return optional<LanguageTag::SubtagData>();
429 }
430
431
432 optional<string>
433 LanguageTag::get_subtag_description (LanguageTag::SubtagType type, string subtag)
434 {
435         optional<SubtagData> data = get_subtag_data (type, subtag);
436         if (!data) {
437                 return optional<string>();
438         }
439
440         return data->description;
441 }
442