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;
66 optional<LanguageTag::SubtagData>
67 find_in_list (vector<LanguageTag::SubtagData> const& list, string subtag)
69 for (auto const& i: list) {
70 if (boost::iequals(i.subtag, subtag)) {
79 LanguageTag::Subtag::Subtag (string subtag, SubtagType type)
82 if (!get_subtag_data(type, subtag)) {
83 throw LanguageTagError(String::compose("Unknown %1 string %2", subtag_type_name(type), subtag));
88 LanguageTag::LanguageTag (string tag)
91 boost::split (parts, tag, boost::is_any_of("-"));
93 throw LanguageTagError (String::compose("Could not parse language tag %1", tag));
96 vector<string>::size_type p = 0;
97 _language = LanguageSubtag (parts[p]);
100 if (p == parts.size()) {
105 _script = ScriptSubtag (parts[p]);
109 if (p == parts.size()) {
114 _region = RegionSubtag (parts[p]);
118 if (p == parts.size()) {
124 _variants.push_back (VariantSubtag(parts[p]));
126 if (p == parts.size()) {
134 _extlangs.push_back (ExtlangSubtag(parts[p]));
136 if (p == parts.size()) {
142 if (p < parts.size()) {
143 throw LanguageTagError (String::compose("Unrecognised subtag %1", parts[p]));
149 LanguageTag::to_string () const
152 throw LanguageTagError("No language set up");
155 auto s = _language->subtag();
158 s += "-" + _script->subtag();
162 s += "-" + _region->subtag();
165 for (auto i: _variants) {
166 s += "-" + i.subtag();
169 for (auto i: _extlangs) {
170 s += "-" + i.subtag();
178 LanguageTag::set_language (LanguageSubtag language)
180 _language = language;
185 LanguageTag::set_script (ScriptSubtag script)
192 LanguageTag::set_region (RegionSubtag region)
199 LanguageTag::add_variant (VariantSubtag variant)
201 if (find(_variants.begin(), _variants.end(), variant) != _variants.end()) {
202 throw LanguageTagError (String::compose("Duplicate Variant subtag %1", variant.subtag()));
205 _variants.push_back (variant);
211 check_for_duplicates (vector<T> const& subtags, dcp::LanguageTag::SubtagType type)
213 vector<T> sorted = subtags;
214 sort (sorted.begin(), sorted.end());
216 for (auto const& i: sorted) {
217 if (last && i == *last) {
218 throw LanguageTagError (String::compose("Duplicate %1 subtag %2", dcp::LanguageTag::subtag_type_name(type), i.subtag()));
226 LanguageTag::set_variants (vector<VariantSubtag> variants)
228 check_for_duplicates (variants, SubtagType::VARIANT);
229 _variants = variants;
234 LanguageTag::add_extlang (ExtlangSubtag extlang)
236 if (find(_extlangs.begin(), _extlangs.end(), extlang) != _extlangs.end()) {
237 throw LanguageTagError (String::compose("Duplicate Extlang subtag %1", extlang.subtag()));
240 _extlangs.push_back (extlang);
245 LanguageTag::set_extlangs (vector<ExtlangSubtag> extlangs)
247 check_for_duplicates (extlangs, SubtagType::EXTLANG);
248 _extlangs = extlangs;
253 LanguageTag::description () const
256 throw LanguageTagError("No language set up");
261 for (auto const& i: _variants) {
262 optional<SubtagData> variant = get_subtag_data (SubtagType::VARIANT, i.subtag());
263 DCP_ASSERT (variant);
264 d += variant->description + " dialect of ";
267 auto language = get_subtag_data (SubtagType::LANGUAGE, _language->subtag());
268 DCP_ASSERT (language);
269 d += language->description;
272 auto script = get_subtag_data (SubtagType::SCRIPT, _script->subtag());
274 d += " written using the " + script->description + " script";
278 auto region = get_subtag_data (SubtagType::REGION, _region->subtag());
280 d += " for " + region->description;
283 for (auto const& i: _extlangs) {
284 auto extlang = get_subtag_data (SubtagType::EXTLANG, i.subtag());
285 DCP_ASSERT (extlang);
286 d += ", " + extlang->description;
293 vector<LanguageTag::SubtagData> const &
294 LanguageTag::get_all (SubtagType type)
297 case SubtagType::LANGUAGE:
298 return language_list;
299 case SubtagType::SCRIPT:
301 case SubtagType::REGION:
303 case SubtagType::VARIANT:
305 case SubtagType::EXTLANG:
309 return language_list;
314 LanguageTag::subtag_type_name (SubtagType type)
317 case SubtagType::LANGUAGE:
319 case SubtagType::SCRIPT:
321 case SubtagType::REGION:
323 case SubtagType::VARIANT:
325 case SubtagType::EXTLANG:
333 dcp::LanguageTag::VariantSubtag::operator== (VariantSubtag const & other) const
335 return subtag() == other.subtag();
340 dcp::LanguageTag::VariantSubtag::operator< (VariantSubtag const & other) const
342 return subtag() < other.subtag();
347 dcp::LanguageTag::ExtlangSubtag::operator== (ExtlangSubtag const & other) const
349 return subtag() == other.subtag();
354 dcp::LanguageTag::ExtlangSubtag::operator< (ExtlangSubtag const & other) const
356 return subtag() < other.subtag();
361 dcp::operator== (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
363 return a.to_string() == b.to_string();
368 dcp::operator<< (ostream& os, dcp::LanguageTag const& tag)
370 os << tag.to_string();
375 vector<pair<LanguageTag::SubtagType, LanguageTag::SubtagData> >
376 LanguageTag::subtags () const
378 vector<pair<SubtagType, SubtagData>> s;
381 s.push_back (make_pair(SubtagType::LANGUAGE, *get_subtag_data(SubtagType::LANGUAGE, _language->subtag())));
385 s.push_back (make_pair(SubtagType::SCRIPT, *get_subtag_data(SubtagType::SCRIPT, _script->subtag())));
389 s.push_back (make_pair(SubtagType::REGION, *get_subtag_data(SubtagType::REGION, _region->subtag())));
392 for (auto const& i: _variants) {
393 s.push_back (make_pair(SubtagType::VARIANT, *get_subtag_data(SubtagType::VARIANT, i.subtag())));
396 for (auto const& i: _extlangs) {
397 s.push_back (make_pair(SubtagType::EXTLANG, *get_subtag_data(SubtagType::EXTLANG, i.subtag())));
404 optional<LanguageTag::SubtagData>
405 LanguageTag::get_subtag_data (LanguageTag::SubtagType type, string subtag)
408 case SubtagType::LANGUAGE:
409 return find_in_list(language_list, subtag);
410 case SubtagType::SCRIPT:
411 return find_in_list(script_list, subtag);
412 case SubtagType::REGION:
413 return find_in_list(region_list, subtag);
414 case SubtagType::VARIANT:
415 return find_in_list(variant_list, subtag);
416 case SubtagType::EXTLANG:
417 return find_in_list(extlang_list, subtag);
425 LanguageTag::get_subtag_description (LanguageTag::SubtagType type, string subtag)
427 auto data = get_subtag_data (type, subtag);
432 return data->description;
437 load_language_tag_list (boost::filesystem::path tags_directory, string name, vector<LanguageTag::SubtagData>& list)
439 auto f = fopen_boost (tags_directory / name, "r");
441 throw FileError ("Could not open tags file", tags_directory / name, errno);
447 char* r = fgets (buffer, sizeof(buffer), f);
453 r = fgets (buffer, sizeof(buffer), f);
456 throw FileError ("Bad tags file", tags_directory / name, -1);
460 list.push_back (LanguageTag::SubtagData(a, b));
469 dcp::load_language_tag_lists (boost::filesystem::path tags_directory)
471 load_language_tag_list (tags_directory, "language", language_list);
472 load_language_tag_list (tags_directory, "variant", variant_list);
473 load_language_tag_list (tags_directory, "region", region_list);
474 load_language_tag_list (tags_directory, "script", script_list);
475 load_language_tag_list (tags_directory, "extlang", extlang_list);