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>
40 #include <boost/foreach.hpp>
49 using boost::optional;
50 using boost::algorithm::trim;
54 static vector<LanguageTag::SubtagData> language_list;
55 static vector<LanguageTag::SubtagData> variant_list;
56 static vector<LanguageTag::SubtagData> region_list;
57 static vector<LanguageTag::SubtagData> script_list;
58 static vector<LanguageTag::SubtagData> extlang_list;
62 optional<LanguageTag::SubtagData>
63 find_in_list (vector<LanguageTag::SubtagData> const& list, string subtag)
65 BOOST_FOREACH (LanguageTag::SubtagData const& i, list) {
66 if (boost::iequals(i.subtag, subtag)) {
71 return optional<LanguageTag::SubtagData>();
75 LanguageTag::Subtag::Subtag (string subtag, SubtagType type)
78 if (!get_subtag_data(type, subtag)) {
79 throw LanguageTagError(String::compose("Unknown %1 string %2", subtag_type_name(type), subtag));
84 LanguageTag::LanguageTag (string tag)
87 boost::split (parts, tag, boost::is_any_of("-"));
89 throw LanguageTagError (String::compose("Could not parse language tag %1", tag));
92 vector<string>::size_type p = 0;
93 _language = LanguageSubtag (parts[p]);
96 if (p == parts.size()) {
101 _script = ScriptSubtag (parts[p]);
105 if (p == parts.size()) {
110 _region = RegionSubtag (parts[p]);
114 if (p == parts.size()) {
120 _variants.push_back (VariantSubtag(parts[p]));
122 if (p == parts.size()) {
130 _extlangs.push_back (ExtlangSubtag(parts[p]));
132 if (p == parts.size()) {
138 if (p < parts.size()) {
139 throw LanguageTagError (String::compose("Unrecognised subtag %1", parts[p]));
145 LanguageTag::to_string () const
148 throw LanguageTagError("No language set up");
151 string s = _language->subtag();
154 s += "-" + _script->subtag();
158 s += "-" + _region->subtag();
161 BOOST_FOREACH (VariantSubtag i, _variants) {
162 s += "-" + i.subtag();
165 BOOST_FOREACH (ExtlangSubtag i, _extlangs) {
166 s += "-" + i.subtag();
174 LanguageTag::set_language (LanguageSubtag language)
176 _language = language;
181 LanguageTag::set_script (ScriptSubtag script)
188 LanguageTag::set_region (RegionSubtag region)
195 LanguageTag::add_variant (VariantSubtag variant)
197 if (find(_variants.begin(), _variants.end(), variant) != _variants.end()) {
198 throw LanguageTagError (String::compose("Duplicate Variant subtag %1", variant.subtag()));
201 _variants.push_back (variant);
207 check_for_duplicates (vector<T> const& subtags, dcp::LanguageTag::SubtagType type)
209 vector<T> sorted = subtags;
210 sort (sorted.begin(), sorted.end());
212 for (auto const& i: sorted) {
213 if (last && i == *last) {
214 throw LanguageTagError (String::compose("Duplicate %1 subtag %2", dcp::LanguageTag::subtag_type_name(type), i.subtag()));
222 LanguageTag::set_variants (vector<VariantSubtag> variants)
224 check_for_duplicates (variants, SubtagType::VARIANT);
225 _variants = variants;
230 LanguageTag::add_extlang (ExtlangSubtag extlang)
232 if (find(_extlangs.begin(), _extlangs.end(), extlang) != _extlangs.end()) {
233 throw LanguageTagError (String::compose("Duplicate Extlang subtag %1", extlang.subtag()));
236 _extlangs.push_back (extlang);
241 LanguageTag::set_extlangs (vector<ExtlangSubtag> extlangs)
243 check_for_duplicates (extlangs, SubtagType::EXTLANG);
244 _extlangs = extlangs;
249 LanguageTag::description () const
252 throw LanguageTagError("No language set up");
257 BOOST_FOREACH (VariantSubtag const& i, _variants) {
258 optional<SubtagData> variant = get_subtag_data (SubtagType::VARIANT, i.subtag());
259 DCP_ASSERT (variant);
260 d += variant->description + " dialect of ";
263 optional<SubtagData> language = get_subtag_data (SubtagType::LANGUAGE, _language->subtag());
264 DCP_ASSERT (language);
265 d += language->description;
268 optional<SubtagData> script = get_subtag_data (SubtagType::SCRIPT, _script->subtag());
270 d += " written using the " + script->description + " script";
274 optional<SubtagData> region = get_subtag_data (SubtagType::REGION, _region->subtag());
276 d += " for " + region->description;
279 BOOST_FOREACH (ExtlangSubtag const& i, _extlangs) {
280 optional<SubtagData> extlang = get_subtag_data (SubtagType::EXTLANG, i.subtag());
281 DCP_ASSERT (extlang);
282 d += ", " + extlang->description;
289 vector<LanguageTag::SubtagData> const &
290 LanguageTag::get_all (SubtagType type)
293 case SubtagType::LANGUAGE:
294 return language_list;
295 case SubtagType::SCRIPT:
297 case SubtagType::REGION:
299 case SubtagType::VARIANT:
301 case SubtagType::EXTLANG:
305 return language_list;
310 LanguageTag::subtag_type_name (SubtagType type)
313 case SubtagType::LANGUAGE:
315 case SubtagType::SCRIPT:
317 case SubtagType::REGION:
319 case SubtagType::VARIANT:
321 case SubtagType::EXTLANG:
329 dcp::LanguageTag::VariantSubtag::operator== (VariantSubtag const & other) const
331 return subtag() == other.subtag();
336 dcp::LanguageTag::VariantSubtag::operator< (VariantSubtag const & other) const
338 return subtag() < other.subtag();
343 dcp::LanguageTag::ExtlangSubtag::operator== (ExtlangSubtag const & other) const
345 return subtag() == other.subtag();
350 dcp::LanguageTag::ExtlangSubtag::operator< (ExtlangSubtag const & other) const
352 return subtag() < other.subtag();
357 dcp::operator== (dcp::LanguageTag const& a, dcp::LanguageTag const& b)
359 return a.to_string() == b.to_string();
364 dcp::operator<< (ostream& os, dcp::LanguageTag const& tag)
366 os << tag.to_string();
371 vector<pair<LanguageTag::SubtagType, LanguageTag::SubtagData> >
372 LanguageTag::subtags () const
374 vector<pair<SubtagType, SubtagData> > s;
377 s.push_back (make_pair(SubtagType::LANGUAGE, *get_subtag_data(SubtagType::LANGUAGE, _language->subtag())));
381 s.push_back (make_pair(SubtagType::SCRIPT, *get_subtag_data(SubtagType::SCRIPT, _script->subtag())));
385 s.push_back (make_pair(SubtagType::REGION, *get_subtag_data(SubtagType::REGION, _region->subtag())));
388 BOOST_FOREACH (VariantSubtag const& i, _variants) {
389 s.push_back (make_pair(SubtagType::VARIANT, *get_subtag_data(SubtagType::VARIANT, i.subtag())));
392 BOOST_FOREACH (ExtlangSubtag const& i, _extlangs) {
393 s.push_back (make_pair(SubtagType::EXTLANG, *get_subtag_data(SubtagType::EXTLANG, i.subtag())));
400 optional<LanguageTag::SubtagData>
401 LanguageTag::get_subtag_data (LanguageTag::SubtagType type, string subtag)
404 case SubtagType::LANGUAGE:
405 return find_in_list(language_list, subtag);
406 case SubtagType::SCRIPT:
407 return find_in_list(script_list, subtag);
408 case SubtagType::REGION:
409 return find_in_list(region_list, subtag);
410 case SubtagType::VARIANT:
411 return find_in_list(variant_list, subtag);
412 case SubtagType::EXTLANG:
413 return find_in_list(extlang_list, subtag);
416 return optional<LanguageTag::SubtagData>();
421 LanguageTag::get_subtag_description (LanguageTag::SubtagType type, string subtag)
423 optional<SubtagData> data = get_subtag_data (type, subtag);
425 return optional<string>();
428 return data->description;
433 load_language_tag_list (boost::filesystem::path tags_directory, string name, vector<LanguageTag::SubtagData>& list)
435 FILE* f = fopen_boost (tags_directory / name, "r");
437 throw FileError ("Could not open tags file", tags_directory / name, errno);
443 char* r = fgets (buffer, sizeof(buffer), f);
449 r = fgets (buffer, sizeof(buffer), f);
452 throw FileError ("Bad tags file", tags_directory / name, -1);
456 list.push_back (LanguageTag::SubtagData(a, b));
465 dcp::load_language_tag_lists (boost::filesystem::path tags_directory)
467 load_language_tag_list (tags_directory, "language", language_list);
468 load_language_tag_list (tags_directory, "variant", variant_list);
469 load_language_tag_list (tags_directory, "region", region_list);
470 load_language_tag_list (tags_directory, "script", script_list);
471 load_language_tag_list (tags_directory, "extlang", extlang_list);