From 46a5d58ba1fe3b274df149214fe1bbc7bb97b6e1 Mon Sep 17 00:00:00 2001 From: Ilya Zverev Date: Fri, 15 Jul 2016 18:01:19 +0300 Subject: [PATCH] [generator] Add region info section to mwms --- 3party/jansson/myjansson.cpp | 28 ++++ 3party/jansson/myjansson.hpp | 2 + defines.hpp | 3 +- generator/feature_sorter.cpp | 51 +++----- generator/generator.pro | 5 +- generator/region_meta.cpp | 123 ++++++++++++++++++ generator/region_meta.hpp | 10 ++ generator/search_index_builder.cpp | 2 +- indexer/data_factory.cpp | 11 +- indexer/data_factory.hpp | 4 +- indexer/feature_meta.cpp | 44 +++++++ indexer/feature_meta.hpp | 39 ++++-- indexer/index.cpp | 3 + indexer/index.hpp | 1 + .../indexer_tests/feature_metadata_test.cpp | 23 ++++ indexer/mwm_set.hpp | 6 + tools/python/mwm/dump_mwm.py | 1 + tools/python/mwm/mwm.py | 17 +++ 18 files changed, 319 insertions(+), 54 deletions(-) create mode 100644 generator/region_meta.cpp create mode 100644 generator/region_meta.hpp diff --git a/3party/jansson/myjansson.cpp b/3party/jansson/myjansson.cpp index f1de73f435..5481a93ceb 100644 --- a/3party/jansson/myjansson.cpp +++ b/3party/jansson/myjansson.cpp @@ -81,4 +81,32 @@ void FromJSONObjectOptionalField(json_t * root, string const & field, json_int_t MYTHROW(my::Json::Exception, ("The field", field, "must contain a json number.")); result = json_integer_value(val); } + +void FromJSONObjectOptionalField(json_t * root, string const & field, bool & result, bool def) +{ + if (!json_is_object(root)) + MYTHROW(my::Json::Exception, ("Bad json object when parsing", field)); + json_t * val = json_object_get(root, field.c_str()); + if (!val) + { + result = def; + return; + } + if (!json_is_boolean(val)) + MYTHROW(my::Json::Exception, ("The field", field, "must contain a boolean value.")); + result = json_is_true(val); +} + +void FromJSONObjectOptionalField(json_t * root, string const & field, json_t *& result) +{ + json_t * obj = json_object_get(root, field.c_str()); + if (!obj) + { + result = nullptr; + return; + } + if (!json_is_object(obj)) + MYTHROW(my::Json::Exception, ("The field", field, "must contain a json object.")); + FromJSON(obj, result); +} } // namespace my diff --git a/3party/jansson/myjansson.hpp b/3party/jansson/myjansson.hpp index 9734a06b1f..634996c365 100644 --- a/3party/jansson/myjansson.hpp +++ b/3party/jansson/myjansson.hpp @@ -52,6 +52,8 @@ void FromJSONObject(json_t * root, string const & field, vector & result) void FromJSONObjectOptionalField(json_t * root, string const & field, string & result); void FromJSONObjectOptionalField(json_t * root, string const & field, json_int_t & result); +void FromJSONObjectOptionalField(json_t * root, string const & field, bool & result, bool def = false); +void FromJSONObjectOptionalField(json_t * root, string const & field, json_t *& result); template void FromJSONObjectOptionalField(json_t * root, string const & field, vector & result) diff --git a/defines.hpp b/defines.hpp index 3f25caee41..328e73a938 100644 --- a/defines.hpp +++ b/defines.hpp @@ -27,7 +27,7 @@ #define METADATA_INDEX_FILE_TAG "metaidx" #define FEATURE_OFFSETS_FILE_TAG "offs" #define RANKS_FILE_TAG "ranks" -#define REGION_INFO_FILE_TAG "info" +#define REGION_INFO_FILE_TAG "rgninfo" // Temporary addresses section that is used in search index generation. #define SEARCH_TOKENS_FILE_TAG "addrtags" @@ -52,6 +52,7 @@ #define CELL2FEATURE_TMP_EXT ".c2f.tmp" #define COUNTRIES_FILE "countries.txt" +#define COUNTRIES_META_FILE "countries_meta.txt" #define COUNTRIES_OBSOLETE_FILE "countries_obsolete.txt" #define WORLD_FILE_NAME "World" diff --git a/generator/feature_sorter.cpp b/generator/feature_sorter.cpp index cec1e45307..3e373d900c 100644 --- a/generator/feature_sorter.cpp +++ b/generator/feature_sorter.cpp @@ -1,8 +1,9 @@ #include "generator/feature_sorter.hpp" -#include "generator/feature_generator.hpp" #include "generator/feature_builder.hpp" -#include "generator/tesselator.hpp" +#include "generator/feature_generator.hpp" #include "generator/gen_mwm_info.hpp" +#include "generator/region_meta.hpp" +#include "generator/tesselator.hpp" #include "defines.hpp" @@ -111,14 +112,16 @@ namespace feature vector> m_metadataIndex; DataHeader m_header; + RegionData m_regionData; uint32_t m_versionDate; gen::OsmID2FeatureID m_osm2ft; public: - FeaturesCollector2(string const & fName, DataHeader const & header, uint32_t versionDate) + FeaturesCollector2(string const & fName, DataHeader const & header, + RegionData const & regionData, uint32_t versionDate) : FeaturesCollector(fName + DATA_FILE_TAG), m_writer(fName), - m_header(header), m_versionDate(versionDate) + m_header(header), m_regionData(regionData), m_versionDate(versionDate) { for (size_t i = 0; i < m_header.GetScalesCount(); ++i) { @@ -147,6 +150,12 @@ namespace feature m_header.Save(w); } + // write region info + { + FileWriter w = m_writer.GetWriter(REGION_INFO_FILE_TAG); + m_regionData.Serialize(w); + } + // assume like we close files Flush(); @@ -543,22 +552,6 @@ namespace feature return static_cast(fb); } - class DoStoreLanguages - { - DataHeader & m_header; - public: - DoStoreLanguages(DataHeader & header) : m_header(header) {} - void operator() (string const & s) - { - int8_t const i = StringUtf8Multilang::GetLangIndex(s); - if (i > 0) - { - // 0 index is always 'default' - m_header.AddLanguage(i); - } - } - }; - bool GenerateFinalFeatures(feature::GenerateInfo const & info, string const & name, int mapType) { string const srcFilePath = info.GetTmpFileName(name); @@ -596,23 +589,15 @@ namespace feature // type header.SetType(static_cast(mapType)); - // languages - try - { - FileReader reader(info.m_targetDir + "metainfo/" + name + ".meta"); - string buffer; - reader.ReadAsString(buffer); - strings::Tokenize(buffer, "|", DoStoreLanguages(header)); - } - catch (Reader::Exception const &) - { - LOG(LWARNING, ("No language file for country:", name)); - } + // region data + RegionData regionData; + if (!ReadRegionData(name, regionData)) + LOG(LWARNING, ("No extra data for country:", name)); // Transform features from raw format to optimized format. try { - FeaturesCollector2 collector(datFilePath, header, info.m_versionDate); + FeaturesCollector2 collector(datFilePath, header, regionData, info.m_versionDate); for (size_t i = 0; i < midPoints.m_vec.size(); ++i) { diff --git a/generator/generator.pro b/generator/generator.pro index e7b4df410f..3ac4cdfacd 100644 --- a/generator/generator.pro +++ b/generator/generator.pro @@ -9,7 +9,8 @@ ROOT_DIR = .. include($$ROOT_DIR/common.pri) INCLUDEPATH *= $$ROOT_DIR/3party/gflags/src \ - $$ROOT_DIR/3party/osrm/osrm-backend/include + $$ROOT_DIR/3party/osrm/osrm-backend/include \ + $$ROOT_DIR/3party/jansson/src QT *= core @@ -30,6 +31,7 @@ SOURCES += \ osm_element.cpp \ osm_id.cpp \ osm_source.cpp \ + region_meta.cpp \ routing_generator.cpp \ search_index_builder.cpp \ srtm_parser.cpp \ @@ -63,6 +65,7 @@ HEADERS += \ osm_translator.hpp \ osm_xml_source.hpp \ polygonizer.hpp \ + region_meta.hpp \ routing_generator.hpp \ search_index_builder.hpp \ srtm_parser.hpp \ diff --git a/generator/region_meta.cpp b/generator/region_meta.cpp new file mode 100644 index 0000000000..963ed65642 --- /dev/null +++ b/generator/region_meta.cpp @@ -0,0 +1,123 @@ +#include "region_meta.hpp" + +#include "coding/reader.hpp" + +#include "platform/platform.hpp" + +#include "3party/jansson/myjansson.hpp" + +namespace +{ +int8_t ParseHolidayReference(string const & ref) +{ + if (ref == "easter") + return feature::RegionData::PHReference::PH_EASTER; + if (ref == "orthodox easter") + return feature::RegionData::PHReference::PH_ORTHODOX_EASTER; + if (ref == "victoriaDay") + return feature::RegionData::PHReference::PH_VICTORIA_DAY; + if (ref == "canadaDay") + return feature::RegionData::PHReference::PH_CANADA_DAY; + return 0; +} +} // namespace + +namespace feature +{ +bool ReadRegionDataImpl(string const & countryName, RegionData & data) +{ + try + { + auto reader = GetPlatform().GetReader(COUNTRIES_META_FILE); + string buffer; + reader->ReadAsString(buffer); + my::Json root(buffer.data()); + + json_t * jsonData = nullptr; + my::FromJSONObjectOptionalField(root.get(), countryName, jsonData); + if (!jsonData) + return false; + + vector languages; + my::FromJSONObjectOptionalField(jsonData, "languages", languages); + if (!languages.empty()) + data.SetLanguages(languages); + + string driving; + my::FromJSONObjectOptionalField(jsonData, "driving", driving); + if (driving == "l" || driving == "r") + data.Set(RegionData::Type::RD_DRIVING, driving); + + string timezone; + my::FromJSONObjectOptionalField(jsonData, "timezone", timezone); + if (!timezone.empty()) + data.Set(RegionData::Type::RD_TIMEZONE, timezone); + + bool allow_housenames; + my::FromJSONObjectOptionalField(jsonData, "housenames", allow_housenames, false); + if (allow_housenames) + data.Set(RegionData::Type::RD_ALLOW_HOUSENAMES, "y"); + + // Public holidays: an array of arrays of [string/number, number]. + // See https://github.com/opening-hours/opening_hours.js/blob/master/docs/holidays.md + vector holidays; + my::FromJSONObjectOptionalField(jsonData, "holidays", holidays); + for (json_t * holiday : holidays) + { + if (!json_is_array(holiday) || json_array_size(holiday) != 2) + MYTHROW(my::Json::Exception, ("Holiday must be an array of two elements in", countryName)); + json_t * reference = json_array_get(holiday, 0); + int8_t refId = 0; + if (json_is_integer(reference)) + { + refId = json_integer_value(reference); + } + else if (json_is_string(reference)) + { + refId = ParseHolidayReference(string(json_string_value(reference))); + } + else + { + MYTHROW(my::Json::Exception, + ("Holiday month reference should be either a string or a number in", countryName)); + } + + if (refId <= 0) + MYTHROW(my::Json::Exception, ("Incorrect month reference in", countryName)); + if (!json_is_integer(json_array_get(holiday, 1))) + MYTHROW(my::Json::Exception, ("Holiday day offset should be a number in", countryName)); + data.AddPublicHoliday(refId, json_integer_value(json_array_get(holiday, 1))); + } + + // TODO(@zverik): Implement formats reading when decided on data types. + + return true; + } + catch (Reader::Exception const & e) + { + LOG(LWARNING, ("Error reading", COUNTRIES_META_FILE, ":", e.Msg())); + } + catch (my::Json::Exception const & e) + { + LOG(LERROR, ("Error parsing JSON in", COUNTRIES_META_FILE, ":", e.Msg())); + } + return false; +} + +bool ReadRegionData(string const & countryName, RegionData & data) +{ + // When there is a match for a complete countryName, simply relay the call. + if (ReadRegionDataImpl(countryName, data)) + return true; + + // If not, cut parts of a country name from the tail. E.g. "Russia_Moscow" -> "Russia". + auto p = countryName.find_last_of('_'); + while (p != string::npos) + { + if (ReadRegionDataImpl(countryName.substr(0, p), data)) + return true; + p = p > 0 ? countryName.find_last_of('_', p - 1) : string::npos; + } + return false; +} +} // namespace feature diff --git a/generator/region_meta.hpp b/generator/region_meta.hpp new file mode 100644 index 0000000000..1d46b84aec --- /dev/null +++ b/generator/region_meta.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "generate_info.hpp" + +#include "indexer/feature_meta.hpp" + +namespace feature +{ +bool ReadRegionData(string const & countryName, RegionData & data); +} // namespace feature diff --git a/generator/search_index_builder.cpp b/generator/search_index_builder.cpp index df9903a1cd..016b2a9e2e 100644 --- a/generator/search_index_builder.cpp +++ b/generator/search_index_builder.cpp @@ -385,7 +385,7 @@ bool BuildSearchIndexFromDataFile(string const & filename, bool forceRebuild) { Platform & platform = GetPlatform(); - FilesContainerR readContainer(platform.GetReader(filename)); + FilesContainerR readContainer(platform.GetReader(filename, "f")); if (readContainer.IsExist(SEARCH_INDEX_FILE_TAG) && !forceRebuild) return true; diff --git a/indexer/data_factory.cpp b/indexer/data_factory.cpp index 965f3f1edf..5f1edc82c0 100644 --- a/indexer/data_factory.cpp +++ b/indexer/data_factory.cpp @@ -1,7 +1,9 @@ -#include "coding/file_container.hpp" #include "indexer/data_factory.hpp" #include "indexer/interval_index.hpp" #include "indexer/old/interval_index_101.hpp" + +#include "coding/file_container.hpp" + #include "defines.hpp" @@ -10,8 +12,11 @@ void IndexFactory::Load(FilesContainerR const & cont) ReadVersion(cont, m_version); m_header.Load(cont); - ReaderSource src(cont.GetReader(REGION_INFO_FILE_TAG)); - m_region.Deserialize(src); + if (cont.IsExist(REGION_INFO_FILE_TAG)) + { + ReaderSource src(cont.GetReader(REGION_INFO_FILE_TAG)); + m_regionData.Deserialize(src); + } } IntervalIndexIFace * IndexFactory::CreateIndex(ModelReaderPtr reader) const diff --git a/indexer/data_factory.hpp b/indexer/data_factory.hpp index 3bda00c867..251b1e2323 100644 --- a/indexer/data_factory.hpp +++ b/indexer/data_factory.hpp @@ -12,14 +12,14 @@ class IndexFactory { version::MwmVersion m_version; feature::DataHeader m_header; - feature::RegionData m_region; + feature::RegionData m_regionData; public: void Load(FilesContainerR const & cont); inline version::MwmVersion const & GetMwmVersion() const { return m_version; } inline feature::DataHeader const & GetHeader() const { return m_header; } - inline feature::RegionData const & GetRegion() const { return m_region; } + inline feature::RegionData const & GetRegionData() const { return m_regionData; } IntervalIndexIFace * CreateIndex(ModelReaderPtr reader) const; }; diff --git a/indexer/feature_meta.cpp b/indexer/feature_meta.cpp index 6e614d5cb3..73e9451b2d 100644 --- a/indexer/feature_meta.cpp +++ b/indexer/feature_meta.cpp @@ -99,6 +99,50 @@ bool Metadata::TypeFromString(string const & k, Metadata::EType & outType) return true; } + +void RegionData::SetLanguages(vector const & codes) +{ + string value; + for (string const & code : codes) + { + int8_t const lang = StringUtf8Multilang::GetLangIndex(code); + if (lang != StringUtf8Multilang::kUnsupportedLanguageCode) + value.push_back(lang); + } + MetadataBase::Set(RegionData::Type::RD_LANGUAGES, value); +} + +void RegionData::GetLanguages(vector & langs) const +{ + for (auto const lang : Get(RegionData::Type::RD_LANGUAGES)) + langs.push_back(lang); +} + +bool RegionData::HasLanguage(int8_t const lang) const +{ + for (auto const lng : Get(RegionData::Type::RD_LANGUAGES)) + { + if (lng == lang) + return true; + } + return false; +} + +bool RegionData::IsSingleLanguage(int8_t const lang) const +{ + string const value = Get(RegionData::Type::RD_LANGUAGES); + if (value.size() != 1) + return false; + return value.front() == lang; +} + +void RegionData::AddPublicHoliday(int8_t month, int8_t offset) +{ + string value = Get(RegionData::Type::RD_PUBLIC_HOLIDAYS); + value.push_back(month); + value.push_back(offset); + Set(RegionData::Type::RD_PUBLIC_HOLIDAYS, value); +} } // namespace feature // Warning: exact osm tag keys should be returned for valid enum values. diff --git a/indexer/feature_meta.hpp b/indexer/feature_meta.hpp index 5fccebc38b..85cede6dc5 100644 --- a/indexer/feature_meta.hpp +++ b/indexer/feature_meta.hpp @@ -132,13 +132,8 @@ public: /// Used to normalize tags like "contact:phone" and "phone" to a common metadata enum value. static bool TypeFromString(string const & osmTagKey, feature::Metadata::EType & outType); - void Set(EType type, string const & value) - { - MetadataBase::Set(type, value); - } - + void Set(EType type, string const & value) { MetadataBase::Set(type, value); } void Drop(EType type) { Set(type, string()); } - string GetWikiURL() const; // TODO: Commented code below is now longer neded, but I leave it here @@ -190,22 +185,40 @@ public: class RegionData : public MetadataBase { public: - enum Type + enum Type : int8_t { - RD_LANGUAGES, // list of written languages - RD_DRIVING, // left- or right-hand driving (letter 'l' or 'r') - RD_TIMEZONE, // UTC timezone offset, floating signed number of hours: -3, 4.5 - RD_ADDRESS_FORMAT, // address format, re: mapzen - RD_PHONE_FORMAT, // list of strings in "+N NNN NN-NN-NN" format + RD_LANGUAGES, // list of written languages + RD_DRIVING, // left- or right-hand driving (letter 'l' or 'r') + RD_TIMEZONE, // UTC timezone offset, floating signed number of hours: -3, 4.5 + RD_ADDRESS_FORMAT, // address format, re: mapzen + RD_PHONE_FORMAT, // list of strings in "+N NNN NN-NN-NN" format RD_POSTCODE_FORMAT, // list of strings in "AAA ANN" format RD_PUBLIC_HOLIDAYS, // fixed PH dates RD_ALLOW_HOUSENAMES // 'y' if housenames are commonly used }; - void Add(Type type, string const & s) + // Special values for month references in public holiday definitions. + enum PHReference : int8_t { + PH_EASTER = 20, + PH_ORTHODOX_EASTER = 21, + PH_VICTORIA_DAY = 22, + PH_CANADA_DAY = 23 + }; + + void Set(Type type, string const & s) + { + CHECK_NOT_EQUAL(type, Type::RD_LANGUAGES, ("Please use RegionData::SetLanguages method")); MetadataBase::Set(type, s); } + + void SetLanguages(vector const & codes); + void GetLanguages(vector & langs) const; + bool HasLanguage(int8_t const lang) const; + bool IsSingleLanguage(int8_t const lang) const; + + void AddPublicHoliday(int8_t month, int8_t offset); + // No public holidays getters until we know what to do with these. }; } // namespace feature diff --git a/indexer/index.cpp b/indexer/index.cpp index 6caaa521f7..6811333464 100644 --- a/indexer/index.cpp +++ b/indexer/index.cpp @@ -58,6 +58,9 @@ unique_ptr Index::CreateInfo(platform::LocalCountryFile const & localFi info->m_minScale = static_cast(scaleR.first); info->m_maxScale = static_cast(scaleR.second); info->m_version = value.GetMwmVersion(); + // Copying to drop the const qualifier. + feature::RegionData regionData(value.GetRegionData()); + info->m_data = regionData; return unique_ptr(move(info)); } diff --git a/indexer/index.hpp b/indexer/index.hpp index 6979d7af10..91351a9c6f 100644 --- a/indexer/index.hpp +++ b/indexer/index.hpp @@ -51,6 +51,7 @@ public: void SetTable(MwmInfoEx & info); inline feature::DataHeader const & GetHeader() const { return m_factory.GetHeader(); } + inline feature::RegionData const & GetRegionData() const { return m_factory.GetRegionData(); } inline version::MwmVersion const & GetMwmVersion() const { return m_factory.GetMwmVersion(); } inline string const & GetCountryFileName() const { return m_file.GetCountryFile().GetName(); } }; diff --git a/indexer/indexer_tests/feature_metadata_test.cpp b/indexer/indexer_tests/feature_metadata_test.cpp index 1a1771c634..1f88dc780c 100644 --- a/indexer/indexer_tests/feature_metadata_test.cpp +++ b/indexer/indexer_tests/feature_metadata_test.cpp @@ -100,3 +100,26 @@ UNIT_TEST(Feature_Metadata_GetWikipedia) TEST_EQUAL(m.GetWikiURL(), "https://en.wikipedia.org/wiki/Article", ()); #endif } + +UNIT_TEST(Feature_Metadata_RegionData_Languages) +{ + { + feature::RegionData rd; + vector const langs = {"ru", "en", "et"}; + rd.SetLanguages(langs); + TEST(rd.HasLanguage(StringUtf8Multilang::GetLangIndex("ru")), ()); + TEST(rd.HasLanguage(StringUtf8Multilang::GetLangIndex("en")), ()); + TEST(rd.HasLanguage(StringUtf8Multilang::GetLangIndex("et")), ()); + TEST(!rd.HasLanguage(StringUtf8Multilang::GetLangIndex("es")), ()); + TEST(!rd.IsSingleLanguage(StringUtf8Multilang::GetLangIndex("ru")), ()); + } + { + feature::RegionData rd; + vector const langs = {"et"}; + rd.SetLanguages(langs); + TEST(rd.HasLanguage(StringUtf8Multilang::GetLangIndex("et")), ()); + TEST(rd.IsSingleLanguage(StringUtf8Multilang::GetLangIndex("et")), ()); + TEST(!rd.HasLanguage(StringUtf8Multilang::GetLangIndex("en")), ()); + TEST(!rd.IsSingleLanguage(StringUtf8Multilang::GetLangIndex("en")), ()); + } +} diff --git a/indexer/mwm_set.hpp b/indexer/mwm_set.hpp index c924f5dd97..9a334316e2 100644 --- a/indexer/mwm_set.hpp +++ b/indexer/mwm_set.hpp @@ -8,6 +8,8 @@ #include "base/macros.hpp" +#include "indexer/feature_meta.hpp" + #include "std/atomic.hpp" #include "std/deque.hpp" #include "std/map.hpp" @@ -66,6 +68,8 @@ public: MwmTypeT GetType() const; + inline feature::RegionData const & GetRegionData() const { return m_data; } + /// Returns the lock counter value for test needs. uint8_t GetNumRefs() const { return m_numRefs; } @@ -77,6 +81,8 @@ protected: return result; } + feature::RegionData m_data; + platform::LocalCountryFile m_file; ///< Path to the mwm file. atomic m_status; ///< Current country status. uint32_t m_numRefs; ///< Number of active handles. diff --git a/tools/python/mwm/dump_mwm.py b/tools/python/mwm/dump_mwm.py index 0147c8dc19..edc891dc64 100755 --- a/tools/python/mwm/dump_mwm.py +++ b/tools/python/mwm/dump_mwm.py @@ -17,6 +17,7 @@ for tv in tvv: v = mwm.read_version() print('Format: {0}, version: {1}'.format(v['fmt'], v['version'].strftime('%Y-%m-%d %H:%M'))) print('Header: {0}'.format(mwm.read_header())) +print('Region Info: {0}'.format(mwm.read_region_info())) print('Metadata count: {0}'.format(len(mwm.read_metadata()))) cross = mwm.read_crossmwm() diff --git a/tools/python/mwm/mwm.py b/tools/python/mwm/mwm.py index bb4c941690..65929831bd 100644 --- a/tools/python/mwm/mwm.py +++ b/tools/python/mwm/mwm.py @@ -31,6 +31,8 @@ class MWM: "denomination", "building_levels", "test_id", "ref:sponsored", "price_rate", "rating", "fuel", "routes"] + regiondata = ["languages", "driving", "timezone", "addr_fmt", "phone_fmt", "postcode_fmt", "holidays", "housenames"] + def __init__(self, f): self.f = f self.coord_size = None @@ -112,6 +114,21 @@ class MWM: # COMPLEX READERS + def read_region_info(self): + if not self.has_tag('rgninfo'): + return {} + fields = {} + self.seek_tag('rgninfo') + sz = self.read_varuint() + if sz: + for i in range(sz): + t = self.read_varuint() + t = self.regiondata[t] if t < len(self.regiondata) else str(t) + fields[t] = self.read_string() + if t == 'languages': + fields[t] = [self.languages[ord(x)] for x in fields[t]] + return fields + def read_metadata(self): """Reads 'meta' and 'metaidx' sections.""" if not self.has_tag('metaidx'):