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"
43 #include "language_tag.h"
44 #include <boost/algorithm/string.hpp>
53 using boost::optional;
54 using boost::algorithm::trim;
58 static vector<LanguageTag::SubtagData> language_list;
59 static vector<LanguageTag::SubtagData> variant_list;
60 static vector<LanguageTag::SubtagData> region_list;
61 static vector<LanguageTag::SubtagData> script_list;
62 static vector<LanguageTag::SubtagData> extlang_list;
64 static vector<pair<string, string>> dcnc_list;
68 optional<LanguageTag::SubtagData>
69 find_in_list (vector<LanguageTag::SubtagData> const& list, string subtag)
71 for (auto const& i: list) {
72 if (boost::iequals(i.subtag, subtag)) {
81 LanguageTag::Subtag::Subtag (string subtag, SubtagType type)
84 if (!get_subtag_data(type, subtag)) {
85 throw LanguageTagError(String::compose("Unknown %1 string %2", subtag_type_name(type), subtag));
90 LanguageTag::LanguageTag (string tag)
93 boost::split (parts, tag, boost::is_any_of("-"));
95 throw LanguageTagError (String::compose("Could not parse language tag %1", tag));
98 vector<string>::size_type p = 0;
99 _language = LanguageSubtag (parts[p]);
102 if (p == parts.size()) {
107 _script = ScriptSubtag (parts[p]);
111 if (p == parts.size()) {
116 _region = RegionSubtag (parts[p]);
120 if (p == parts.size()) {
126 _variants.push_back (VariantSubtag(parts[p]));
128 if (p == parts.size()) {
136 _extlangs.push_back (ExtlangSubtag(parts[p]));
138 if (p == parts.size()) {
144 if (p < parts.size()) {
145 throw LanguageTagError (String::compose("Unrecognised subtag %1", parts[p]));
151 LanguageTag::to_string () const
154 throw LanguageTagError("No language set up");
157 auto s = _language->subtag();
160 s += "-" + _script->subtag();
164 s += "-" + _region->subtag();
167 for (auto i: _variants) {
168 s += "-" + i.subtag();
171 for (auto i: _extlangs) {
172 s += "-" + i.subtag();
180 LanguageTag::set_language (LanguageSubtag language)
182 _language = language;
187 LanguageTag::set_script (ScriptSubtag script)
194 LanguageTag::set_region (RegionSubtag region)
201 LanguageTag::add_variant (VariantSubtag variant)
203 if (find(_variants.begin(), _variants.end(), variant) != _variants.end()) {
204 throw LanguageTagError (String::compose("Duplicate Variant subtag %1", variant.subtag()));
207 _variants.push_back (variant);
213 check_for_duplicates (vector<T> const& subtags, dcp::LanguageTag::SubtagType type)
215 vector<T> sorted = subtags;
216 sort (sorted.begin(), sorted.end());
218 for (auto const& i: sorted) {
219 if (last && i == *last) {
220 throw LanguageTagError (String::compose("Duplicate %1 subtag %2", dcp::LanguageTag::subtag_type_name(type), i.subtag()));
228 LanguageTag::set_variants (vector<VariantSubtag> variants)
230 check_for_duplicates (variants, SubtagType::VARIANT);
231 _variants = variants;
236 LanguageTag::add_extlang (ExtlangSubtag extlang)
238 if (find(_extlangs.begin(), _extlangs.end(), extlang) != _extlangs.end()) {
239 throw LanguageTagError (String::compose("Duplicate Extlang subtag %1", extlang.subtag()));
242 _extlangs.push_back (extlang);
247 LanguageTag::set_extlangs (vector<ExtlangSubtag> extlangs)
249 check_for_duplicates (extlangs, SubtagType::EXTLANG);
250 _extlangs = extlangs;
255 LanguageTag::description () const
258 throw LanguageTagError("No language set up");
263 for (auto const& i: _variants) {
264 optional<SubtagData> variant = get_subtag_data (SubtagType::VARIANT, i.subtag());
265 DCP_ASSERT (variant);
266 d += variant->description + " dialect of ";
269 auto language = get_subtag_data (SubtagType::LANGUAGE, _language->subtag());
270 DCP_ASSERT (language);
271 d += language->description;
274 auto script = get_subtag_data (SubtagType::SCRIPT, _script->subtag());
276 d += " written using the " + script->description + " script";
280 auto region = get_subtag_data (SubtagType::REGION, _region->subtag());
282 d += " for " + region->description;
285 for (auto const& i: _extlangs) {
286 auto extlang = get_subtag_data (SubtagType::EXTLANG, i.subtag());
287 DCP_ASSERT (extlang);
288 d += ", " + extlang->description;
295 vector<LanguageTag::SubtagData> const &
296 LanguageTag::get_all (SubtagType type)
299 case SubtagType::LANGUAGE:
300 return language_list;
301 case SubtagType::SCRIPT:
303 case SubtagType::REGION:
305 case SubtagType::VARIANT:
307 case SubtagType::EXTLANG:
311 return language_list;
316 LanguageTag::subtag_type_name (SubtagType type)
319 case SubtagType::LANGUAGE:
321 case SubtagType::SCRIPT:
323 case SubtagType::REGION:
325 case SubtagType::VARIANT:
327 case SubtagType::EXTLANG:
335 dcp::LanguageTag::VariantSubtag::operator== (VariantSubtag const & other) const
337 return subtag() == other.subtag();
342 dcp::LanguageTag::VariantSubtag::operator< (VariantSubtag const & other) const
344 return subtag() < other.subtag();
349 dcp::LanguageTag::ExtlangSubtag::operator== (ExtlangSubtag const & other) const
351 return subtag() == other.subtag();
356 dcp::LanguageTag::ExtlangSubtag::operator< (ExtlangSubtag const & other) const
358 return subtag() < other.subtag();
363 dcp::operator== (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
365 return a.to_string() == b.to_string();
370 dcp::operator!= (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
372 return a.to_string() != b.to_string();
377 dcp::operator< (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
379 return a.to_string() < b.to_string();
384 dcp::operator<< (ostream& os, dcp::LanguageTag const& tag)
386 os << tag.to_string();
391 vector<pair<LanguageTag::SubtagType, LanguageTag::SubtagData> >
392 LanguageTag::subtags () const
394 vector<pair<SubtagType, SubtagData>> s;
397 s.push_back (make_pair(SubtagType::LANGUAGE, *get_subtag_data(SubtagType::LANGUAGE, _language->subtag())));
401 s.push_back (make_pair(SubtagType::SCRIPT, *get_subtag_data(SubtagType::SCRIPT, _script->subtag())));
405 s.push_back (make_pair(SubtagType::REGION, *get_subtag_data(SubtagType::REGION, _region->subtag())));
408 for (auto const& i: _variants) {
409 s.push_back (make_pair(SubtagType::VARIANT, *get_subtag_data(SubtagType::VARIANT, i.subtag())));
412 for (auto const& i: _extlangs) {
413 s.push_back (make_pair(SubtagType::EXTLANG, *get_subtag_data(SubtagType::EXTLANG, i.subtag())));
420 optional<LanguageTag::SubtagData>
421 LanguageTag::get_subtag_data (LanguageTag::SubtagType type, string subtag)
424 case SubtagType::LANGUAGE:
425 return find_in_list(language_list, subtag);
426 case SubtagType::SCRIPT:
427 return find_in_list(script_list, subtag);
428 case SubtagType::REGION:
429 return find_in_list(region_list, subtag);
430 case SubtagType::VARIANT:
431 return find_in_list(variant_list, subtag);
432 case SubtagType::EXTLANG:
433 return find_in_list(extlang_list, subtag);
441 LanguageTag::get_subtag_description (LanguageTag::SubtagType type, string subtag)
443 auto data = get_subtag_data (type, subtag);
448 return data->description;
453 load_language_tag_list (boost::filesystem::path tags_directory, string name, std::function<void (std::string, std::string)> add)
455 auto f = fopen_boost (tags_directory / name, "r");
457 throw FileError ("Could not open tags file", tags_directory / name, errno);
463 char* r = fgets (buffer, sizeof(buffer), f);
469 r = fgets (buffer, sizeof(buffer), f);
472 throw FileError ("Bad tags file", tags_directory / name, -1);
485 dcp::load_language_tag_lists (boost::filesystem::path tags_directory)
487 auto add_subtag = [](vector<LanguageTag::SubtagData>& list, string a, string b) {
488 list.push_back (LanguageTag::SubtagData(a, b));
491 load_language_tag_list (tags_directory, "language", [&add_subtag](string a, string b) { add_subtag(language_list, a, b); });
492 load_language_tag_list (tags_directory, "variant", [&add_subtag](string a, string b) { add_subtag(variant_list, a, b); });
493 load_language_tag_list (tags_directory, "region", [&add_subtag](string a, string b) { add_subtag(region_list, a, b); });
494 load_language_tag_list (tags_directory, "script", [&add_subtag](string a, string b) { add_subtag(script_list, a, b); });
495 load_language_tag_list (tags_directory, "extlang", [&add_subtag](string a, string b) { add_subtag(extlang_list, a, b); });
497 load_language_tag_list (tags_directory, "dcnc", [](string a, string b) { dcnc_list.push_back(make_pair(a, b)); });
501 vector<pair<string, string>> dcp::dcnc_tags ()