Assorted c++11 cleanups.
[libdcp.git] / src / language_tag.cc
1 /*
2     Copyright (C) 2020-2021 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 <string>
41
42
43 using std::make_pair;
44 using std::ostream;
45 using std::pair;
46 using std::string;
47 using std::vector;
48 using boost::optional;
49 using boost::algorithm::trim;
50 using namespace dcp;
51
52
53 static vector<LanguageTag::SubtagData> language_list;
54 static vector<LanguageTag::SubtagData> variant_list;
55 static vector<LanguageTag::SubtagData> region_list;
56 static vector<LanguageTag::SubtagData> script_list;
57 static vector<LanguageTag::SubtagData> extlang_list;
58
59
60 static
61 optional<LanguageTag::SubtagData>
62 find_in_list (vector<LanguageTag::SubtagData> const& list, string subtag)
63 {
64         for (auto const& i: list) {
65                 if (boost::iequals(i.subtag, subtag)) {
66                         return i;
67                 }
68         }
69
70         return optional<LanguageTag::SubtagData>();
71 }
72
73
74 LanguageTag::Subtag::Subtag (string subtag, SubtagType type)
75         : _subtag (subtag)
76 {
77         if (!get_subtag_data(type, subtag)) {
78                 throw LanguageTagError(String::compose("Unknown %1 string %2", subtag_type_name(type), subtag));
79         }
80 }
81
82
83 LanguageTag::LanguageTag (string tag)
84 {
85         vector<string> parts;
86         boost::split (parts, tag, boost::is_any_of("-"));
87         if (parts.empty()) {
88                 throw LanguageTagError (String::compose("Could not parse language tag %1", tag));
89         }
90
91         vector<string>::size_type p = 0;
92         _language = LanguageSubtag (parts[p]);
93         ++p;
94
95         if (p == parts.size()) {
96                 return;
97         }
98
99         try {
100                 _script = ScriptSubtag (parts[p]);
101                 ++p;
102         } catch (...) {}
103
104         if (p == parts.size()) {
105                 return;
106         }
107
108         try {
109                 _region = RegionSubtag (parts[p]);
110                 ++p;
111         } catch (...) {}
112
113         if (p == parts.size()) {
114                 return;
115         }
116
117         try {
118                 while (true) {
119                         _variants.push_back (VariantSubtag(parts[p]));
120                         ++p;
121                         if (p == parts.size()) {
122                                 return;
123                         }
124                 }
125         } catch (...) {}
126
127         try {
128                 while (true) {
129                         _extlangs.push_back (ExtlangSubtag(parts[p]));
130                         ++p;
131                         if (p == parts.size()) {
132                                 return;
133                         }
134                 }
135         } catch (...) {}
136
137         if (p < parts.size()) {
138                 throw LanguageTagError (String::compose("Unrecognised subtag %1", parts[p]));
139         }
140 }
141
142
143 string
144 LanguageTag::to_string () const
145 {
146         if (!_language) {
147                 throw LanguageTagError("No language set up");
148         }
149
150         string s = _language->subtag();
151
152         if (_script) {
153                 s += "-" + _script->subtag();
154         }
155
156         if (_region) {
157                 s += "-" + _region->subtag();
158         }
159
160         for (auto i: _variants) {
161                 s += "-" + i.subtag();
162         }
163
164         for (auto i: _extlangs) {
165                 s += "-" + i.subtag();
166         }
167
168         return s;
169 }
170
171
172 void
173 LanguageTag::set_language (LanguageSubtag language)
174 {
175         _language = language;
176 }
177
178
179 void
180 LanguageTag::set_script (ScriptSubtag script)
181 {
182         _script = script;
183 }
184
185
186 void
187 LanguageTag::set_region (RegionSubtag region)
188 {
189         _region = region;
190 }
191
192
193 void
194 LanguageTag::add_variant (VariantSubtag variant)
195 {
196         if (find(_variants.begin(), _variants.end(), variant) != _variants.end()) {
197                 throw LanguageTagError (String::compose("Duplicate Variant subtag %1", variant.subtag()));
198         }
199
200         _variants.push_back (variant);
201 }
202
203
204 template <class T>
205 void
206 check_for_duplicates (vector<T> const& subtags, dcp::LanguageTag::SubtagType type)
207 {
208         vector<T> sorted = subtags;
209         sort (sorted.begin(), sorted.end());
210         optional<T> last;
211         for (auto const& i: sorted) {
212                 if (last && i == *last) {
213                         throw LanguageTagError (String::compose("Duplicate %1 subtag %2", dcp::LanguageTag::subtag_type_name(type), i.subtag()));
214                 }
215                 last = i;
216         }
217 }
218
219
220 void
221 LanguageTag::set_variants (vector<VariantSubtag> variants)
222 {
223         check_for_duplicates (variants, SubtagType::VARIANT);
224         _variants = variants;
225 }
226
227
228 void
229 LanguageTag::add_extlang (ExtlangSubtag extlang)
230 {
231         if (find(_extlangs.begin(), _extlangs.end(), extlang) != _extlangs.end()) {
232                 throw LanguageTagError (String::compose("Duplicate Extlang subtag %1", extlang.subtag()));
233         }
234
235         _extlangs.push_back (extlang);
236 }
237
238
239 void
240 LanguageTag::set_extlangs (vector<ExtlangSubtag> extlangs)
241 {
242         check_for_duplicates (extlangs, SubtagType::EXTLANG);
243         _extlangs = extlangs;
244 }
245
246
247 string
248 LanguageTag::description () const
249 {
250         if (!_language) {
251                 throw LanguageTagError("No language set up");
252         }
253
254         string d;
255
256         for (auto const& i: _variants) {
257                 optional<SubtagData> variant = get_subtag_data (SubtagType::VARIANT, i.subtag());
258                 DCP_ASSERT (variant);
259                 d += variant->description + " dialect of ";
260         }
261
262         auto language = get_subtag_data (SubtagType::LANGUAGE, _language->subtag());
263         DCP_ASSERT (language);
264         d += language->description;
265
266         if (_script) {
267                 optional<SubtagData> script = get_subtag_data (SubtagType::SCRIPT, _script->subtag());
268                 DCP_ASSERT (script);
269                 d += " written using the " + script->description + " script";
270         }
271
272         if (_region) {
273                 optional<SubtagData> region = get_subtag_data (SubtagType::REGION, _region->subtag());
274                 DCP_ASSERT (region);
275                 d += " for " + region->description;
276         }
277
278         for (auto const& i: _extlangs) {
279                 optional<SubtagData> extlang = get_subtag_data (SubtagType::EXTLANG, i.subtag());
280                 DCP_ASSERT (extlang);
281                 d += ", " + extlang->description;
282         }
283
284         return d;
285 }
286
287
288 vector<LanguageTag::SubtagData> const &
289 LanguageTag::get_all (SubtagType type)
290 {
291         switch (type) {
292         case SubtagType::LANGUAGE:
293                 return language_list;
294         case SubtagType::SCRIPT:
295                 return script_list;
296         case SubtagType::REGION:
297                 return region_list;
298         case SubtagType::VARIANT:
299                 return variant_list;
300         case SubtagType::EXTLANG:
301                 return extlang_list;
302         }
303
304         return language_list;
305 }
306
307
308 string
309 LanguageTag::subtag_type_name (SubtagType type)
310 {
311         switch (type) {
312                 case SubtagType::LANGUAGE:
313                         return "Language";
314                 case SubtagType::SCRIPT:
315                         return "Script";
316                 case SubtagType::REGION:
317                         return "Region";
318                 case SubtagType::VARIANT:
319                         return "Variant";
320                 case SubtagType::EXTLANG:
321                         return "Extended";
322         }
323
324         return "";
325 }
326
327 bool
328 dcp::LanguageTag::VariantSubtag::operator== (VariantSubtag const & other) const
329 {
330         return subtag() == other.subtag();
331 }
332
333
334 bool
335 dcp::LanguageTag::VariantSubtag::operator< (VariantSubtag const & other) const
336 {
337         return subtag() < other.subtag();
338 }
339
340
341 bool
342 dcp::LanguageTag::ExtlangSubtag::operator== (ExtlangSubtag const & other) const
343 {
344         return subtag() == other.subtag();
345 }
346
347
348 bool
349 dcp::LanguageTag::ExtlangSubtag::operator< (ExtlangSubtag const & other) const
350 {
351         return subtag() < other.subtag();
352 }
353
354
355 bool
356 dcp::operator== (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
357 {
358         return a.to_string() == b.to_string();
359 }
360
361
362 ostream&
363 dcp::operator<< (ostream& os, dcp::LanguageTag const& tag)
364 {
365         os << tag.to_string();
366         return os;
367 }
368
369
370 vector<pair<LanguageTag::SubtagType, LanguageTag::SubtagData> >
371 LanguageTag::subtags () const
372 {
373         vector<pair<SubtagType, SubtagData>> s;
374
375         if (_language) {
376                 s.push_back (make_pair(SubtagType::LANGUAGE, *get_subtag_data(SubtagType::LANGUAGE, _language->subtag())));
377         }
378
379         if (_script) {
380                 s.push_back (make_pair(SubtagType::SCRIPT, *get_subtag_data(SubtagType::SCRIPT, _script->subtag())));
381         }
382
383         if (_region) {
384                 s.push_back (make_pair(SubtagType::REGION, *get_subtag_data(SubtagType::REGION, _region->subtag())));
385         }
386
387         for (auto const& i: _variants) {
388                 s.push_back (make_pair(SubtagType::VARIANT, *get_subtag_data(SubtagType::VARIANT, i.subtag())));
389         }
390
391         for (auto const& i: _extlangs) {
392                 s.push_back (make_pair(SubtagType::EXTLANG, *get_subtag_data(SubtagType::EXTLANG, i.subtag())));
393         }
394
395         return s;
396 }
397
398
399 optional<LanguageTag::SubtagData>
400 LanguageTag::get_subtag_data (LanguageTag::SubtagType type, string subtag)
401 {
402         switch (type) {
403         case SubtagType::LANGUAGE:
404                 return find_in_list(language_list, subtag);
405         case SubtagType::SCRIPT:
406                 return find_in_list(script_list, subtag);
407         case SubtagType::REGION:
408                 return find_in_list(region_list, subtag);
409         case SubtagType::VARIANT:
410                 return find_in_list(variant_list, subtag);
411         case SubtagType::EXTLANG:
412                 return find_in_list(extlang_list, subtag);
413         }
414
415         return optional<LanguageTag::SubtagData>();
416 }
417
418
419 optional<string>
420 LanguageTag::get_subtag_description (LanguageTag::SubtagType type, string subtag)
421 {
422         optional<SubtagData> data = get_subtag_data (type, subtag);
423         if (!data) {
424                 return optional<string>();
425         }
426
427         return data->description;
428 }
429
430
431 void
432 load_language_tag_list (boost::filesystem::path tags_directory, string name, vector<LanguageTag::SubtagData>& list)
433 {
434         FILE* f = fopen_boost (tags_directory / name, "r");
435         if (!f) {
436                 throw FileError ("Could not open tags file", tags_directory / name, errno);
437         }
438         char buffer[512];
439
440         int i = 0;
441         while (!feof(f)) {
442                 char* r = fgets (buffer, sizeof(buffer), f);
443                 if (r == 0) {
444                         break;
445                 }
446                 string a = buffer;
447                 trim (a);
448                 r = fgets (buffer, sizeof(buffer), f);
449                 if (r == 0) {
450                         fclose (f);
451                         throw FileError ("Bad tags file", tags_directory / name, -1);
452                 }
453                 string b = buffer;
454                 trim (b);
455                 list.push_back (LanguageTag::SubtagData(a, b));
456                 ++i;
457         }
458
459         fclose (f);
460 }
461
462
463 void
464 dcp::load_language_tag_lists (boost::filesystem::path tags_directory)
465 {
466         load_language_tag_list (tags_directory, "language", language_list);
467         load_language_tag_list (tags_directory, "variant", variant_list);
468         load_language_tag_list (tags_directory, "region", region_list);
469         load_language_tag_list (tags_directory, "script", script_list);
470         load_language_tag_list (tags_directory, "extlang", extlang_list);
471 }
472
473