/* * Copyright (C) 2015-2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "GpxParser.h" #include #include #include #include #include "StringParse.h" using std::string; // format an error message template static string formatError(const char *format, Args &&...args) { char buf[100] = {}; snprintf(buf, sizeof(buf) - 1, format, std::forward(args)...); return buf; } static void cleanupXmlDoc(xmlDoc *doc) { xmlFreeDoc(doc); xmlCleanupParser(); } static bool parseLocation(xmlNode *ptNode, xmlDoc *doc, GpsFix *result, string *error) { float latitude; float longitude; xmlAttrPtr attr; xmlChar *tmpStr; // Check for and get the latitude attribute attr = xmlHasProp(ptNode, (const xmlChar *)"lat"); if (!attr || !(tmpStr = xmlGetProp(ptNode, (const xmlChar *)"lat"))) { *error = formatError("Point missing a latitude on line %d.", ptNode->line); return false; // Return error since a point *must* have a latitude } else { int read = SscanfWithCLocale(reinterpret_cast(tmpStr), "%f", &latitude); xmlFree(tmpStr); // Caller-freed if (read != 1) { return false; } } // Check for and get the longitude attribute attr = xmlHasProp(ptNode, (const xmlChar *)"lon"); if (!attr || !(tmpStr = xmlGetProp(ptNode, (const xmlChar *)"lon"))) { *error = formatError("Point missing a longitude on line %d.", ptNode->line); return false; // Return error since a point *must* have a longitude } else { int read = SscanfWithCLocale(reinterpret_cast(tmpStr), "%f", &longitude); xmlFree(tmpStr); // Caller-freed if (read != 1) { return false; } } // The result will be valid if this point is reached result->latitude = latitude; result->longitude = longitude; // Check for potential children nodes (including time, elevation, name, and // description) Note that none are actually required according to the GPX // format. int childCount = 0; for (xmlNode *field = ptNode->children; field; field = field->next) { tmpStr = nullptr; if (!strcmp((const char *)field->name, "time")) { if ((tmpStr = xmlNodeListGetString(doc, field->children, 1))) { // Convert to a number struct tm time = {}; time.tm_isdst = -1; int results = sscanf((const char *)tmpStr, "%u-%u-%uT%u:%u:%u", &time.tm_year, &time.tm_mon, &time.tm_mday, &time.tm_hour, &time.tm_min, &time.tm_sec); if (results != 6) { *error = formatError( "Improperly formatted time on line %d.
" "Times must be in ISO format.", ptNode->line); return false; } // Correct according to the struct tm specification time.tm_year -= 1900; // Years since 1900 time.tm_mon -= 1; // Months since January, 0-11 result->time = mktime(&time); xmlFree(tmpStr); // Caller-freed childCount++; } } else if (!strcmp((const char *)field->name, "ele")) { if ((tmpStr = xmlNodeListGetString(doc, field->children, 1))) { int read = SscanfWithCLocale(reinterpret_cast(tmpStr), "%f", &result->elevation); xmlFree(tmpStr); // Caller-freed if (read != 1) { return false; } childCount++; } } else if (!strcmp((const char *)field->name, "name")) { if ((tmpStr = xmlNodeListGetString(doc, field->children, 1))) { result->name = reinterpret_cast(tmpStr); xmlFree(tmpStr); // Caller-freed childCount++; } } else if (!strcmp((const char *)field->name, "desc")) { if ((tmpStr = xmlNodeListGetString(doc, field->children, 1))) { result->description = reinterpret_cast(tmpStr); xmlFree(tmpStr); // Caller-freed childCount++; } } // We only care about 4 potential child fields, so quit after finding those if (childCount == 4) { break; } } return true; } static bool parse(xmlDoc *doc, GpsFixArray *fixes, string *error) { xmlNode *root = xmlDocGetRootElement(doc); GpsFix location; bool isOk; for (xmlNode *child = root->children; child; child = child->next) { // Individual elements are parsed on their own if (!strcmp((const char *)child->name, "wpt")) { isOk = parseLocation(child, doc, &location, error); if (!isOk) { cleanupXmlDoc(doc); return false; } fixes->push_back(location); } // elements require an additional depth of parsing else if (!strcmp((const char *)child->name, "rte")) { for (xmlNode *rtept = child->children; rtept; rtept = rtept->next) { // elements are parsed just like elements if (!strcmp((const char *)rtept->name, "rtept")) { isOk = parseLocation(rtept, doc, &location, error); if (!isOk) { cleanupXmlDoc(doc); return false; } fixes->push_back(location); } } } // elements require two additional depths of parsing else if (!strcmp((const char *)child->name, "trk")) { for (xmlNode *trkseg = child->children; trkseg; trkseg = trkseg->next) { // Skip non elements if (!strcmp((const char *)trkseg->name, "trkseg")) { // elements an additional depth of parsing for (xmlNode *trkpt = trkseg->children; trkpt; trkpt = trkpt->next) { // elements are parsed just like elements if (!strcmp((const char *)trkpt->name, "trkpt")) { isOk = parseLocation(trkpt, doc, &location, error); if (!isOk) { cleanupXmlDoc(doc); return false; } fixes->push_back(location); } } } } } } // Sort the values by timestamp std::sort(fixes->begin(), fixes->end()); cleanupXmlDoc(doc); return true; } bool GpxParser::parseFile(const char *filePath, GpsFixArray *fixes, string *error) { xmlDocPtr doc = xmlReadFile(filePath, nullptr, 0); if (doc == nullptr) { cleanupXmlDoc(doc); *error = "GPX document not parsed successfully."; return false; } return parse(doc, fixes, error); } bool GpxParser::parseString(const char *str, int len, GpsFixArray *fixes, string *error) { xmlDocPtr doc = xmlReadMemory(str, len, NULL, NULL, 0); if (doc == nullptr) { cleanupXmlDoc(doc); *error = "GPX document not parsed successfully."; return false; } return parse(doc, fixes, error); }