Allow LanguageTag to be constructed from a tag string.
[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::string;
45 using std::vector;
46 using boost::optional;
47 using namespace dcp;
48
49
50 #include "language_tag_lists.cc"
51
52
53 static
54 optional<LanguageTag::SubtagData>
55 find_in_list (LanguageTag::SubtagData* list, int length, string subtag)
56 {
57         for (int i = 0; i < length; ++i) {
58                 if (list[i].subtag == subtag) {
59                         return list[i];
60                 }
61         }
62
63         return optional<LanguageTag::SubtagData>();
64 }
65
66
67 static
68 optional<LanguageTag::SubtagData>
69 find_in_list (LanguageTag::SubtagType type, string subtag)
70 {
71         switch (type) {
72         case dcp::LanguageTag::LANGUAGE:
73                 return find_in_list(language_list, sizeof(language_list) / sizeof(LanguageTag::SubtagData), subtag);
74         case dcp::LanguageTag::SCRIPT:
75                 return find_in_list(script_list, sizeof(script_list) / sizeof(LanguageTag::SubtagData), subtag);
76         case dcp::LanguageTag::REGION:
77                 return find_in_list(region_list, sizeof(region_list) / sizeof(LanguageTag::SubtagData), subtag);
78         case dcp::LanguageTag::VARIANT:
79                 return find_in_list(variant_list, sizeof(variant_list) / sizeof(LanguageTag::SubtagData), subtag);
80         case dcp::LanguageTag::EXTLANG:
81                 return find_in_list(extlang_list, sizeof(extlang_list) / sizeof(LanguageTag::SubtagData), subtag);
82         }
83
84         return optional<LanguageTag::SubtagData>();
85 }
86
87
88 LanguageTag::Subtag::Subtag (string subtag, SubtagType type)
89         : _subtag (subtag)
90 {
91         if (!find_in_list(type, subtag)) {
92                 throw LanguageTagError(String::compose("Unknown %1 string %2", subtag_type_name(type), subtag));
93         }
94 }
95
96
97 LanguageTag::LanguageTag (string tag)
98 {
99         vector<string> parts;
100         boost::split (parts, tag, boost::is_any_of("-"));
101         if (parts.empty()) {
102                 throw LanguageTagError (String::compose("Could not parse language tag %1", tag));
103         }
104
105         vector<string>::size_type p = 0;
106         _language = LanguageSubtag (parts[p]);
107         ++p;
108
109         if (p == parts.size()) {
110                 return;
111         }
112
113         try {
114                 _script = ScriptSubtag (parts[p]);
115                 ++p;
116         } catch (...) {}
117
118         if (p == parts.size()) {
119                 return;
120         }
121
122         try {
123                 _region = RegionSubtag (parts[p]);
124                 ++p;
125         } catch (...) {}
126
127         if (p == parts.size()) {
128                 return;
129         }
130
131         try {
132                 while (true) {
133                         _variants.push_back (VariantSubtag(parts[p]));
134                         ++p;
135                         if (p == parts.size()) {
136                                 return;
137                         }
138                 }
139         } catch (...) {}
140
141         try {
142                 while (true) {
143                         _extlangs.push_back (ExtlangSubtag(parts[p]));
144                         ++p;
145                         if (p == parts.size()) {
146                                 return;
147                         }
148                 }
149         } catch (...) {}
150
151         if (p < parts.size()) {
152                 throw LanguageTagError (String::compose("Unrecognised subtag %1", parts[p]));
153         }
154 }
155
156
157 string
158 LanguageTag::to_string () const
159 {
160         if (!_language) {
161                 throw LanguageTagError("No language set up");
162         }
163
164         string s = _language->subtag();
165
166         if (_script) {
167                 s += "-" + _script->subtag();
168         }
169
170         if (_region) {
171                 s += "-" + _region->subtag();
172         }
173
174         BOOST_FOREACH (VariantSubtag i, _variants) {
175                 s += "-" + i.subtag();
176         }
177
178         BOOST_FOREACH (ExtlangSubtag i, _extlangs) {
179                 s += "-" + i.subtag();
180         }
181
182         return s;
183 }
184
185
186 void
187 LanguageTag::set_language (LanguageSubtag language)
188 {
189         _language = language;
190 }
191
192
193 void
194 LanguageTag::set_script (ScriptSubtag script)
195 {
196         _script = script;
197 }
198
199
200 void
201 LanguageTag::set_region (RegionSubtag region)
202 {
203         _region = region;
204 }
205
206
207 void
208 LanguageTag::add_variant (VariantSubtag variant)
209 {
210         if (find(_variants.begin(), _variants.end(), variant) != _variants.end()) {
211                 throw LanguageTagError (String::compose("Duplicate Variant subtag %1", variant.subtag()));
212         }
213
214         _variants.push_back (variant);
215 }
216
217
218 template <class T>
219 void
220 check_for_duplicates (vector<T> const& subtags, dcp::LanguageTag::SubtagType type)
221 {
222         vector<T> sorted = subtags;
223         sort (sorted.begin(), sorted.end());
224         optional<T> last;
225         BOOST_FOREACH (T const& i, sorted) {
226                 if (last && i == *last) {
227                         throw LanguageTagError (String::compose("Duplicate %1 subtag %2", dcp::LanguageTag::subtag_type_name(type), i.subtag()));
228                 }
229                 last = i;
230         }
231 }
232
233
234 void
235 LanguageTag::set_variants (vector<VariantSubtag> variants)
236 {
237         check_for_duplicates (variants, VARIANT);
238         _variants = variants;
239 }
240
241
242 void
243 LanguageTag::add_extlang (ExtlangSubtag extlang)
244 {
245         if (find(_extlangs.begin(), _extlangs.end(), extlang) != _extlangs.end()) {
246                 throw LanguageTagError (String::compose("Duplicate Extlang subtag %1", extlang.subtag()));
247         }
248
249         _extlangs.push_back (extlang);
250 }
251
252
253 void
254 LanguageTag::set_extlangs (vector<ExtlangSubtag> extlangs)
255 {
256         check_for_duplicates (extlangs, EXTLANG);
257         _extlangs = extlangs;
258 }
259
260
261 string
262 LanguageTag::description () const
263 {
264         if (!_language) {
265                 throw LanguageTagError("No language set up");
266         }
267
268         string d;
269
270         BOOST_FOREACH (VariantSubtag const& i, _variants) {
271                 optional<SubtagData> variant = find_in_list (VARIANT, i.subtag());
272                 DCP_ASSERT (variant);
273                 d += variant->description + " dialect of ";
274         }
275
276         optional<SubtagData> language = find_in_list (LANGUAGE, _language->subtag());
277         DCP_ASSERT (language);
278         d += language->description;
279
280         if (_script) {
281                 optional<SubtagData> script = find_in_list (SCRIPT, _script->subtag());
282                 DCP_ASSERT (script);
283                 d += " written using the " + script->description + " script";
284         }
285
286         if (_region) {
287                 optional<SubtagData> region = find_in_list (REGION, _region->subtag());
288                 DCP_ASSERT (region);
289                 d += " for " + region->description;
290         }
291
292         BOOST_FOREACH (ExtlangSubtag const& i, _extlangs) {
293                 optional<SubtagData> extlang = find_in_list (EXTLANG, i.subtag());
294                 DCP_ASSERT (extlang);
295                 d += ", " + extlang->description;
296         }
297
298         return d;
299 }
300
301
302 vector<LanguageTag::SubtagData>
303 LanguageTag::get_all (SubtagType type)
304 {
305         vector<LanguageTag::SubtagData> all;
306
307         switch (type) {
308         case LANGUAGE:
309                 for (size_t i = 0; i < sizeof(language_list) / sizeof(LanguageTag::SubtagData); ++i) {
310                         all.push_back (language_list[i]);
311                 }
312                 break;
313         case SCRIPT:
314                 for (size_t i = 0; i < sizeof(script_list) / sizeof(LanguageTag::SubtagData); ++i) {
315                         all.push_back (script_list[i]);
316                 }
317                 break;
318         case REGION:
319                 for (size_t i = 0; i < sizeof(region_list) / sizeof(LanguageTag::SubtagData); ++i) {
320                         all.push_back (region_list[i]);
321                 }
322                 break;
323         case VARIANT:
324                 for (size_t i = 0; i < sizeof(variant_list) / sizeof(LanguageTag::SubtagData); ++i) {
325                         all.push_back (variant_list[i]);
326                 }
327                 break;
328         case EXTLANG:
329                 for (size_t i = 0; i < sizeof(extlang_list) / sizeof(LanguageTag::SubtagData); ++i) {
330                         all.push_back (extlang_list[i]);
331                 }
332                 break;
333         }
334
335         return all;
336 }
337
338
339 string
340 LanguageTag::subtag_type_name (SubtagType type)
341 {
342         switch (type) {
343                 case LANGUAGE:
344                         return "Language";
345                 case SCRIPT:
346                         return "Script";
347                 case REGION:
348                         return "Region";
349                 case VARIANT:
350                         return "Variant";
351                 case EXTLANG:
352                         return "Extended";
353         }
354
355         return "";
356 }
357
358 bool
359 dcp::LanguageTag::VariantSubtag::operator== (VariantSubtag const & other) const
360 {
361         return subtag() == other.subtag();
362 }
363
364
365 bool
366 dcp::LanguageTag::VariantSubtag::operator< (VariantSubtag const & other) const
367 {
368         return subtag() < other.subtag();
369 }
370
371
372 bool
373 dcp::LanguageTag::ExtlangSubtag::operator== (ExtlangSubtag const & other) const
374 {
375         return subtag() == other.subtag();
376 }
377
378
379 bool
380 dcp::LanguageTag::ExtlangSubtag::operator< (ExtlangSubtag const & other) const
381 {
382         return subtag() < other.subtag();
383 }