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