| // Copyright (C) 2015 The Android Open Source Project |
| // |
| // This software is licensed under the terms of the GNU General Public |
| // License version 2, as published by the Free Software Foundation, and |
| // may be copied, distributed, and modified under those terms. |
| // |
| // This program is distributed in the hope that it will be useful, |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| // GNU General Public License for more details. |
| |
| #include "android/gps/GpxParser.h" |
| |
| #include "android/base/StringParse.h" |
| |
| #include <libxml/parser.h> |
| |
| #include <algorithm> |
| #include <string.h> |
| #include <time.h> |
| |
| using std::string; |
| |
| // format an error message |
| template <class... Args> |
| static string formatError(const char* format, Args&&... args) { |
| char buf[100] = {}; |
| snprintf(buf, sizeof(buf) - 1, format, std::forward<Args>(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 = |
| android::base::SscanfWithCLocale(reinterpret_cast<const char*>(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 = |
| android::base::SscanfWithCLocale(reinterpret_cast<const char*>(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; |
| 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.<br/>" |
| "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 = |
| android::base::SscanfWithCLocale(reinterpret_cast<const char*>(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<const char*>(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<const char*>(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 <wpt> 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); |
| } |
| |
| // <rte> elements require an additional depth of parsing |
| else if ( !strcmp((const char *) child->name, "rte") ) { |
| for (xmlNode *rtept = child->children; rtept; rtept = rtept->next) { |
| |
| // <rtept> elements are parsed just like <wpt> elements |
| if ( !strcmp((const char *) rtept->name, "rtept") ) { |
| isOk = parseLocation(rtept, doc, &location, error); |
| if (!isOk) { |
| cleanupXmlDoc(doc); |
| return false; |
| } |
| fixes->push_back(location); |
| } |
| } |
| } |
| |
| // <trk> 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 <trkseg> elements |
| if ( !strcmp((const char *) trkseg->name, "trkseg") ) { |
| |
| // <trkseg> elements an additional depth of parsing |
| for (xmlNode *trkpt = trkseg->children; trkpt; trkpt = trkpt->next) { |
| |
| // <trkpt> elements are parsed just like <wpt> 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); |
| } |