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 #include "compose.hpp"
36 #include "dcp_assert.h"
37 #include "exceptions.h"
38 #include "language_tag.h"
39 #include <boost/algorithm/string.hpp>
48 using boost::optional;
49 using boost::algorithm::trim;
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;
61 optional<LanguageTag::SubtagData>
62 find_in_list (vector<LanguageTag::SubtagData> const& list, string subtag)
64 for (auto const& i: list) {
65 if (boost::iequals(i.subtag, subtag)) {
70 return optional<LanguageTag::SubtagData>();
74 LanguageTag::Subtag::Subtag (string subtag, SubtagType type)
77 if (!get_subtag_data(type, subtag)) {
78 throw LanguageTagError(String::compose("Unknown %1 string %2", subtag_type_name(type), subtag));
83 LanguageTag::LanguageTag (string tag)
86 boost::split (parts, tag, boost::is_any_of("-"));
88 throw LanguageTagError (String::compose("Could not parse language tag %1", tag));
91 vector<string>::size_type p = 0;
92 _language = LanguageSubtag (parts[p]);
95 if (p == parts.size()) {
100 _script = ScriptSubtag (parts[p]);
104 if (p == parts.size()) {
109 _region = RegionSubtag (parts[p]);
113 if (p == parts.size()) {
119 _variants.push_back (VariantSubtag(parts[p]));
121 if (p == parts.size()) {
129 _extlangs.push_back (ExtlangSubtag(parts[p]));
131 if (p == parts.size()) {
137 if (p < parts.size()) {
138 throw LanguageTagError (String::compose("Unrecognised subtag %1", parts[p]));
144 LanguageTag::to_string () const
147 throw LanguageTagError("No language set up");
150 string s = _language->subtag();
153 s += "-" + _script->subtag();
157 s += "-" + _region->subtag();
160 for (auto i: _variants) {
161 s += "-" + i.subtag();
164 for (auto i: _extlangs) {
165 s += "-" + i.subtag();
173 LanguageTag::set_language (LanguageSubtag language)
175 _language = language;
180 LanguageTag::set_script (ScriptSubtag script)
187 LanguageTag::set_region (RegionSubtag region)
194 LanguageTag::add_variant (VariantSubtag variant)
196 if (find(_variants.begin(), _variants.end(), variant) != _variants.end()) {
197 throw LanguageTagError (String::compose("Duplicate Variant subtag %1", variant.subtag()));
200 _variants.push_back (variant);
206 check_for_duplicates (vector<T> const& subtags, dcp::LanguageTag::SubtagType type)
208 vector<T> sorted = subtags;
209 sort (sorted.begin(), sorted.end());
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()));
221 LanguageTag::set_variants (vector<VariantSubtag> variants)
223 check_for_duplicates (variants, SubtagType::VARIANT);
224 _variants = variants;
229 LanguageTag::add_extlang (ExtlangSubtag extlang)
231 if (find(_extlangs.begin(), _extlangs.end(), extlang) != _extlangs.end()) {
232 throw LanguageTagError (String::compose("Duplicate Extlang subtag %1", extlang.subtag()));
235 _extlangs.push_back (extlang);
240 LanguageTag::set_extlangs (vector<ExtlangSubtag> extlangs)
242 check_for_duplicates (extlangs, SubtagType::EXTLANG);
243 _extlangs = extlangs;
248 LanguageTag::description () const
251 throw LanguageTagError("No language set up");
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 ";
262 auto language = get_subtag_data (SubtagType::LANGUAGE, _language->subtag());
263 DCP_ASSERT (language);
264 d += language->description;
267 optional<SubtagData> script = get_subtag_data (SubtagType::SCRIPT, _script->subtag());
269 d += " written using the " + script->description + " script";
273 optional<SubtagData> region = get_subtag_data (SubtagType::REGION, _region->subtag());
275 d += " for " + region->description;
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;
288 vector<LanguageTag::SubtagData> const &
289 LanguageTag::get_all (SubtagType type)
292 case SubtagType::LANGUAGE:
293 return language_list;
294 case SubtagType::SCRIPT:
296 case SubtagType::REGION:
298 case SubtagType::VARIANT:
300 case SubtagType::EXTLANG:
304 return language_list;
309 LanguageTag::subtag_type_name (SubtagType type)
312 case SubtagType::LANGUAGE:
314 case SubtagType::SCRIPT:
316 case SubtagType::REGION:
318 case SubtagType::VARIANT:
320 case SubtagType::EXTLANG:
328 dcp::LanguageTag::VariantSubtag::operator== (VariantSubtag const & other) const
330 return subtag() == other.subtag();
335 dcp::LanguageTag::VariantSubtag::operator< (VariantSubtag const & other) const
337 return subtag() < other.subtag();
342 dcp::LanguageTag::ExtlangSubtag::operator== (ExtlangSubtag const & other) const
344 return subtag() == other.subtag();
349 dcp::LanguageTag::ExtlangSubtag::operator< (ExtlangSubtag const & other) const
351 return subtag() < other.subtag();
356 dcp::operator== (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
358 return a.to_string() == b.to_string();
363 dcp::operator<< (ostream& os, dcp::LanguageTag const& tag)
365 os << tag.to_string();
370 vector<pair<LanguageTag::SubtagType, LanguageTag::SubtagData> >
371 LanguageTag::subtags () const
373 vector<pair<SubtagType, SubtagData>> s;
376 s.push_back (make_pair(SubtagType::LANGUAGE, *get_subtag_data(SubtagType::LANGUAGE, _language->subtag())));
380 s.push_back (make_pair(SubtagType::SCRIPT, *get_subtag_data(SubtagType::SCRIPT, _script->subtag())));
384 s.push_back (make_pair(SubtagType::REGION, *get_subtag_data(SubtagType::REGION, _region->subtag())));
387 for (auto const& i: _variants) {
388 s.push_back (make_pair(SubtagType::VARIANT, *get_subtag_data(SubtagType::VARIANT, i.subtag())));
391 for (auto const& i: _extlangs) {
392 s.push_back (make_pair(SubtagType::EXTLANG, *get_subtag_data(SubtagType::EXTLANG, i.subtag())));
399 optional<LanguageTag::SubtagData>
400 LanguageTag::get_subtag_data (LanguageTag::SubtagType type, string subtag)
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);
415 return optional<LanguageTag::SubtagData>();
420 LanguageTag::get_subtag_description (LanguageTag::SubtagType type, string subtag)
422 optional<SubtagData> data = get_subtag_data (type, subtag);
424 return optional<string>();
427 return data->description;
432 load_language_tag_list (boost::filesystem::path tags_directory, string name, vector<LanguageTag::SubtagData>& list)
434 FILE* f = fopen_boost (tags_directory / name, "r");
436 throw FileError ("Could not open tags file", tags_directory / name, errno);
442 char* r = fgets (buffer, sizeof(buffer), f);
448 r = fgets (buffer, sizeof(buffer), f);
451 throw FileError ("Bad tags file", tags_directory / name, -1);
455 list.push_back (LanguageTag::SubtagData(a, b));
464 dcp::load_language_tag_lists (boost::filesystem::path tags_directory)
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);