2 Copyright (C) 2020-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
35 /** @file src/language_tag.cc
36 * @brief LanguageTag class
40 #include "compose.hpp"
41 #include "dcp_assert.h"
42 #include "exceptions.h"
44 #include "language_tag.h"
45 #include <boost/algorithm/string.hpp>
54 using boost::algorithm::trim;
55 using boost::optional;
59 static vector<LanguageTag::SubtagData> language_list;
60 static vector<LanguageTag::SubtagData> variant_list;
61 static vector<LanguageTag::SubtagData> region_list;
62 static vector<LanguageTag::SubtagData> script_list;
63 static vector<LanguageTag::SubtagData> extlang_list;
65 static vector<pair<string, string>> dcnc_list;
69 optional<LanguageTag::SubtagData>
70 find_in_list (vector<LanguageTag::SubtagData> const& list, string subtag)
72 for (auto const& i: list) {
73 if (boost::iequals(i.subtag, subtag)) {
82 LanguageTag::Subtag::Subtag (string subtag, SubtagType type)
85 if (!get_subtag_data(type, subtag)) {
86 throw LanguageTagError(String::compose("Unknown %1 string %2", subtag_type_name(type), subtag));
91 LanguageTag::LanguageTag (string tag)
94 boost::split (parts, tag, boost::is_any_of("-"));
96 throw LanguageTagError (String::compose("Could not parse language tag %1", tag));
99 vector<string>::size_type p = 0;
100 _language = LanguageSubtag (parts[p]);
103 if (p == parts.size()) {
108 _script = ScriptSubtag (parts[p]);
112 if (p == parts.size()) {
117 _region = RegionSubtag (parts[p]);
121 if (p == parts.size()) {
127 _variants.push_back (VariantSubtag(parts[p]));
129 if (p == parts.size()) {
137 _extlangs.push_back (ExtlangSubtag(parts[p]));
139 if (p == parts.size()) {
145 if (p < parts.size()) {
146 throw LanguageTagError (String::compose("Unrecognised subtag %1", parts[p]));
152 LanguageTag::to_string () const
155 throw LanguageTagError("No language set up");
158 auto s = _language->subtag();
161 s += "-" + _script->subtag();
165 s += "-" + _region->subtag();
168 for (auto i: _variants) {
169 s += "-" + i.subtag();
172 for (auto i: _extlangs) {
173 s += "-" + i.subtag();
181 LanguageTag::set_language (LanguageSubtag language)
183 _language = language;
188 LanguageTag::set_script (ScriptSubtag script)
195 LanguageTag::set_region (RegionSubtag region)
202 LanguageTag::add_variant (VariantSubtag variant)
204 if (find(_variants.begin(), _variants.end(), variant) != _variants.end()) {
205 throw LanguageTagError (String::compose("Duplicate Variant subtag %1", variant.subtag()));
208 _variants.push_back (variant);
214 check_for_duplicates (vector<T> const& subtags, dcp::LanguageTag::SubtagType type)
216 vector<T> sorted = subtags;
217 sort (sorted.begin(), sorted.end());
219 for (auto const& i: sorted) {
220 if (last && i == *last) {
221 throw LanguageTagError (String::compose("Duplicate %1 subtag %2", dcp::LanguageTag::subtag_type_name(type), i.subtag()));
229 LanguageTag::set_variants (vector<VariantSubtag> variants)
231 check_for_duplicates (variants, SubtagType::VARIANT);
232 _variants = variants;
237 LanguageTag::add_extlang (ExtlangSubtag extlang)
239 if (find(_extlangs.begin(), _extlangs.end(), extlang) != _extlangs.end()) {
240 throw LanguageTagError (String::compose("Duplicate Extlang subtag %1", extlang.subtag()));
243 _extlangs.push_back (extlang);
248 LanguageTag::set_extlangs (vector<ExtlangSubtag> extlangs)
250 check_for_duplicates (extlangs, SubtagType::EXTLANG);
251 _extlangs = extlangs;
256 LanguageTag::description () const
259 throw LanguageTagError("No language set up");
264 for (auto const& i: _variants) {
265 optional<SubtagData> variant = get_subtag_data (SubtagType::VARIANT, i.subtag());
266 DCP_ASSERT (variant);
267 d += variant->description + " dialect of ";
270 auto language = get_subtag_data (SubtagType::LANGUAGE, _language->subtag());
271 DCP_ASSERT (language);
272 d += language->description;
275 auto script = get_subtag_data (SubtagType::SCRIPT, _script->subtag());
277 d += " written using the " + script->description + " script";
281 auto region = get_subtag_data (SubtagType::REGION, _region->subtag());
283 d += " for " + region->description;
286 for (auto const& i: _extlangs) {
287 auto extlang = get_subtag_data (SubtagType::EXTLANG, i.subtag());
288 DCP_ASSERT (extlang);
289 d += ", " + extlang->description;
296 vector<LanguageTag::SubtagData> const &
297 LanguageTag::get_all (SubtagType type)
300 case SubtagType::LANGUAGE:
301 return language_list;
302 case SubtagType::SCRIPT:
304 case SubtagType::REGION:
306 case SubtagType::VARIANT:
308 case SubtagType::EXTLANG:
312 return language_list;
317 LanguageTag::subtag_type_name (SubtagType type)
320 case SubtagType::LANGUAGE:
322 case SubtagType::SCRIPT:
324 case SubtagType::REGION:
326 case SubtagType::VARIANT:
328 case SubtagType::EXTLANG:
337 dcp::operator== (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
339 return a.to_string() == b.to_string();
344 dcp::operator!= (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
346 return a.to_string() != b.to_string();
351 dcp::operator< (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
353 return a.to_string() < b.to_string();
358 dcp::operator<< (ostream& os, dcp::LanguageTag const& tag)
360 os << tag.to_string();
365 vector<pair<LanguageTag::SubtagType, LanguageTag::SubtagData>>
366 LanguageTag::subtags () const
368 vector<pair<SubtagType, SubtagData>> s;
371 s.push_back (make_pair(SubtagType::LANGUAGE, *get_subtag_data(SubtagType::LANGUAGE, _language->subtag())));
375 s.push_back (make_pair(SubtagType::SCRIPT, *get_subtag_data(SubtagType::SCRIPT, _script->subtag())));
379 s.push_back (make_pair(SubtagType::REGION, *get_subtag_data(SubtagType::REGION, _region->subtag())));
382 for (auto const& i: _variants) {
383 s.push_back (make_pair(SubtagType::VARIANT, *get_subtag_data(SubtagType::VARIANT, i.subtag())));
386 for (auto const& i: _extlangs) {
387 s.push_back (make_pair(SubtagType::EXTLANG, *get_subtag_data(SubtagType::EXTLANG, i.subtag())));
394 optional<LanguageTag::SubtagData>
395 LanguageTag::get_subtag_data (LanguageTag::SubtagType type, string subtag)
398 case SubtagType::LANGUAGE:
399 return find_in_list(language_list, subtag);
400 case SubtagType::SCRIPT:
401 return find_in_list(script_list, subtag);
402 case SubtagType::REGION:
403 return find_in_list(region_list, subtag);
404 case SubtagType::VARIANT:
405 return find_in_list(variant_list, subtag);
406 case SubtagType::EXTLANG:
407 return find_in_list(extlang_list, subtag);
415 LanguageTag::get_subtag_description (LanguageTag::SubtagType type, string subtag)
417 auto data = get_subtag_data (type, subtag);
422 return data->description;
427 load_language_tag_list (boost::filesystem::path tags_directory, string name, std::function<void (std::string, std::string)> add)
429 File f(tags_directory / name, "r");
431 throw FileError ("Could not open tags file", tags_directory / name, errno);
436 char* r = f.gets(buffer, sizeof(buffer));
442 r = f.gets(buffer, sizeof(buffer));
444 throw FileError ("Bad tags file", tags_directory / name, -1);
454 dcp::load_language_tag_lists (boost::filesystem::path tags_directory)
456 auto add_subtag = [](vector<LanguageTag::SubtagData>& list, string a, string b) {
457 list.push_back (LanguageTag::SubtagData(a, b));
460 load_language_tag_list (tags_directory, "language", [&add_subtag](string a, string b) { add_subtag(language_list, a, b); });
461 load_language_tag_list (tags_directory, "variant", [&add_subtag](string a, string b) { add_subtag(variant_list, a, b); });
462 load_language_tag_list (tags_directory, "region", [&add_subtag](string a, string b) { add_subtag(region_list, a, b); });
463 load_language_tag_list (tags_directory, "script", [&add_subtag](string a, string b) { add_subtag(script_list, a, b); });
464 load_language_tag_list (tags_directory, "extlang", [&add_subtag](string a, string b) { add_subtag(extlang_list, a, b); });
466 load_language_tag_list (tags_directory, "dcnc", [](string a, string b) { dcnc_list.push_back(make_pair(a, b)); });
470 vector<pair<string, string>> dcp::dcnc_tags ()