diff --git a/tz.cpp b/tz.cpp index 1529f29..8bdaecb 100644 --- a/tz.cpp +++ b/tz.cpp @@ -84,7 +84,6 @@ #include #include #include -#include #include #include @@ -123,13 +122,7 @@ #include #endif -#if TIMEZONE_MAPPING -// See comments in tz.h regarding the XML mapping file. -#include "tinyxml2.h" -#endif - #if _WIN32 - static CONSTDATA char folder_delimiter = '\\'; namespace @@ -352,36 +345,160 @@ get_download_mapping_file(const std::string& version) return file; } +// Parse this XML file: +// http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml +// The parsing method is designed to be simple and quick. It is not overly +// forgiving of change but it should diagnose basic format issues. +// See timezone_mapping structure for more info. static -std::vector -load_zone_mappings_from_xml_file(const std::string& input_path) +std::vector +load_timezone_mappings_from_xml_file(const std::string& input_path) { - using tinyxml2::XMLDocument; - std::vector zone_map_list; - XMLDocument doc; + std::size_t line_num = 0; + std::vector mappings; + std::string line; - auto result = doc.LoadFile(input_path.c_str()); - if (result != tinyxml2::XML_SUCCESS) + std::ifstream is(input_path); + if (!is.is_open()) { - std::string msg = "Could not load the timezone mapping file at \""; + // We don't emit file exceptions because that's an implementation detail. + std::string msg = "Error opening time zone mapping file \""; msg += input_path; - msg += '\"'; + msg += "\"."; throw std::runtime_error(msg); } - auto supplementalData = doc.FirstChildElement("supplementalData"); - auto windowsZones = supplementalData->FirstChildElement("windowsZones"); - auto mapTimeZones = windowsZones->FirstChildElement("mapTimezones"); - - for (auto mapZone = mapTimeZones->FirstChildElement("mapZone"); - mapZone != nullptr; mapZone = mapZone->NextSiblingElement()) + auto error = [&input_path, &line_num](const char* info) { - auto other = mapZone->Attribute("other"); - auto territory = mapZone->Attribute("territory"); - auto type = mapZone->Attribute("type"); - zone_map_list.emplace_back(other, territory, type); + std::string msg = "Error loading time zone mapping file \""; + msg += input_path; + msg += "\" at line "; + msg += std::to_string(line_num); + msg += ": "; + msg += info; + throw std::runtime_error(msg); + }; + // [optional space]a="b" + auto read_attribute = [&line_num, &line, &error] + (const char* name, std::string& value, std::size_t startPos) + ->std::size_t + { + value.clear(); + // Skip leading space before attribute name. + std::size_t spos = line.find_first_not_of(' ', startPos); + if (spos == std::string::npos) + spos = startPos; + // Assume everything up to next = is the attribute name + // and that an = will always delimit that. + std::size_t epos = line.find('=', spos); + if (epos == std::string::npos) + error("Expected \'=\' right after attribute name."); + std::size_t name_len = epos - spos; + // Expect the name we find matches the name we expect. + if (line.compare(spos, name_len, name) != 0) + { + std::string msg; + msg = "Expected attribute name \'"; + msg += name; + msg += "\' around position "; + msg += std::to_string(spos); + msg += " but found something else."; + error(msg.c_str()); + } + ++epos; // Skip the '=' that is after the attribute name. + spos = epos; + if (spos < line.length() && line[spos] == '\"') + ++spos; // Skip the quote that is before the attribute value. + else + { + std::string msg = "Expected '\"' to begin value of attribute \'"; + msg += name; + msg += "\'."; + error(msg.c_str()); + } + epos = line.find('\"', spos); + if (epos == std::string::npos) + { + std::string msg = "Expected '\"' to end value of attribute \'"; + msg += name; + msg += "\'."; + error(msg.c_str()); + } + // Extract everything in between the quotes. Note no escaping is done. + std::size_t value_len = epos - spos; + value.assign(line, spos, value_len); + ++epos; // Skip the quote that is after the attribute value; + return epos; + }; + + // Quick but not overly forgiving XML mapping file processing. + bool mapTimezonesOpenTagFound = false; + bool mapTimezonesCloseTagFound = false; + bool mapZoneOpenTagFound = false; + bool mapTZoneCloseTagFound = false; + std::size_t mapZonePos = std::string::npos; + std::size_t mapTimezonesPos = std::string::npos; + CONSTDATA char mapTimeZonesOpeningTag[] = { ""); + mapTimezonesCloseTagFound = (mapTimezonesPos != std::string::npos); + if (!mapTimezonesCloseTagFound) + { + std::size_t commentPos = line.find("