1 /*
2 * Copyright (C) 2015-2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "GpxParser.h"
18 #include <libxml/parser.h>
19 #include <string.h>
20 #include <time.h>
21 #include <algorithm>
22 #include "StringParse.h"
23
24 using std::string;
25
26 // format an error message
27 template <class... Args>
formatError(const char * format,Args &&...args)28 static string formatError(const char *format, Args &&...args) {
29 char buf[100] = {};
30 snprintf(buf, sizeof(buf) - 1, format, std::forward<Args>(args)...);
31 return buf;
32 }
33
cleanupXmlDoc(xmlDoc * doc)34 static void cleanupXmlDoc(xmlDoc *doc) {
35 xmlFreeDoc(doc);
36 xmlCleanupParser();
37 }
38
parseLocation(xmlNode * ptNode,xmlDoc * doc,GpsFix * result,string * error)39 static bool parseLocation(xmlNode *ptNode, xmlDoc *doc, GpsFix *result,
40 string *error) {
41 float latitude;
42 float longitude;
43
44 xmlAttrPtr attr;
45 xmlChar *tmpStr;
46
47 // Check for and get the latitude attribute
48 attr = xmlHasProp(ptNode, (const xmlChar *)"lat");
49 if (!attr || !(tmpStr = xmlGetProp(ptNode, (const xmlChar *)"lat"))) {
50 *error = formatError("Point missing a latitude on line %d.", ptNode->line);
51 return false; // Return error since a point *must* have a latitude
52 } else {
53 int read = SscanfWithCLocale(reinterpret_cast<const char *>(tmpStr), "%f",
54 &latitude);
55 xmlFree(tmpStr); // Caller-freed
56 if (read != 1) {
57 return false;
58 }
59 }
60
61 // Check for and get the longitude attribute
62 attr = xmlHasProp(ptNode, (const xmlChar *)"lon");
63 if (!attr || !(tmpStr = xmlGetProp(ptNode, (const xmlChar *)"lon"))) {
64 *error = formatError("Point missing a longitude on line %d.", ptNode->line);
65 return false; // Return error since a point *must* have a longitude
66 } else {
67 int read = SscanfWithCLocale(reinterpret_cast<const char *>(tmpStr), "%f",
68 &longitude);
69 xmlFree(tmpStr); // Caller-freed
70 if (read != 1) {
71 return false;
72 }
73 }
74
75 // The result will be valid if this point is reached
76 result->latitude = latitude;
77 result->longitude = longitude;
78
79 // Check for potential children nodes (including time, elevation, name, and
80 // description) Note that none are actually required according to the GPX
81 // format.
82 int childCount = 0;
83 for (xmlNode *field = ptNode->children; field; field = field->next) {
84 tmpStr = nullptr;
85
86 if (!strcmp((const char *)field->name, "time")) {
87 if ((tmpStr = xmlNodeListGetString(doc, field->children, 1))) {
88 // Convert to a number
89 struct tm time = {};
90 time.tm_isdst = -1;
91 int results = sscanf((const char *)tmpStr, "%u-%u-%uT%u:%u:%u",
92 &time.tm_year, &time.tm_mon, &time.tm_mday,
93 &time.tm_hour, &time.tm_min, &time.tm_sec);
94 if (results != 6) {
95 *error = formatError(
96 "Improperly formatted time on line %d.<br/>"
97 "Times must be in ISO format.",
98 ptNode->line);
99 return false;
100 }
101
102 // Correct according to the struct tm specification
103 time.tm_year -= 1900; // Years since 1900
104 time.tm_mon -= 1; // Months since January, 0-11
105
106 result->time = mktime(&time);
107 xmlFree(tmpStr); // Caller-freed
108 childCount++;
109 }
110 } else if (!strcmp((const char *)field->name, "ele")) {
111 if ((tmpStr = xmlNodeListGetString(doc, field->children, 1))) {
112 int read = SscanfWithCLocale(reinterpret_cast<const char *>(tmpStr),
113 "%f", &result->elevation);
114 xmlFree(tmpStr); // Caller-freed
115 if (read != 1) {
116 return false;
117 }
118 childCount++;
119 }
120 } else if (!strcmp((const char *)field->name, "name")) {
121 if ((tmpStr = xmlNodeListGetString(doc, field->children, 1))) {
122 result->name = reinterpret_cast<const char *>(tmpStr);
123 xmlFree(tmpStr); // Caller-freed
124 childCount++;
125 }
126 } else if (!strcmp((const char *)field->name, "desc")) {
127 if ((tmpStr = xmlNodeListGetString(doc, field->children, 1))) {
128 result->description = reinterpret_cast<const char *>(tmpStr);
129 xmlFree(tmpStr); // Caller-freed
130 childCount++;
131 }
132 }
133
134 // We only care about 4 potential child fields, so quit after finding those
135 if (childCount == 4) {
136 break;
137 }
138 }
139
140 return true;
141 }
142
parse(xmlDoc * doc,GpsFixArray * fixes,string * error)143 static bool parse(xmlDoc *doc, GpsFixArray *fixes, string *error) {
144 xmlNode *root = xmlDocGetRootElement(doc);
145 GpsFix location;
146 bool isOk;
147
148 for (xmlNode *child = root->children; child; child = child->next) {
149 // Individual <wpt> elements are parsed on their own
150 if (!strcmp((const char *)child->name, "wpt")) {
151 isOk = parseLocation(child, doc, &location, error);
152 if (!isOk) {
153 cleanupXmlDoc(doc);
154 return false;
155 }
156 fixes->push_back(location);
157 }
158
159 // <rte> elements require an additional depth of parsing
160 else if (!strcmp((const char *)child->name, "rte")) {
161 for (xmlNode *rtept = child->children; rtept; rtept = rtept->next) {
162 // <rtept> elements are parsed just like <wpt> elements
163 if (!strcmp((const char *)rtept->name, "rtept")) {
164 isOk = parseLocation(rtept, doc, &location, error);
165 if (!isOk) {
166 cleanupXmlDoc(doc);
167 return false;
168 }
169 fixes->push_back(location);
170 }
171 }
172 }
173
174 // <trk> elements require two additional depths of parsing
175 else if (!strcmp((const char *)child->name, "trk")) {
176 for (xmlNode *trkseg = child->children; trkseg; trkseg = trkseg->next) {
177 // Skip non <trkseg> elements
178 if (!strcmp((const char *)trkseg->name, "trkseg")) {
179 // <trkseg> elements an additional depth of parsing
180 for (xmlNode *trkpt = trkseg->children; trkpt; trkpt = trkpt->next) {
181 // <trkpt> elements are parsed just like <wpt> elements
182 if (!strcmp((const char *)trkpt->name, "trkpt")) {
183 isOk = parseLocation(trkpt, doc, &location, error);
184 if (!isOk) {
185 cleanupXmlDoc(doc);
186 return false;
187 }
188 fixes->push_back(location);
189 }
190 }
191 }
192 }
193 }
194 }
195
196 // Sort the values by timestamp
197 std::sort(fixes->begin(), fixes->end());
198
199 cleanupXmlDoc(doc);
200 return true;
201 }
202
parseFile(const char * filePath,GpsFixArray * fixes,string * error)203 bool GpxParser::parseFile(const char *filePath, GpsFixArray *fixes,
204 string *error) {
205 xmlDocPtr doc = xmlReadFile(filePath, nullptr, 0);
206 if (doc == nullptr) {
207 cleanupXmlDoc(doc);
208 *error = "GPX document not parsed successfully.";
209 return false;
210 }
211 return parse(doc, fixes, error);
212 }
213
parseString(const char * str,int len,GpsFixArray * fixes,string * error)214 bool GpxParser::parseString(const char *str, int len, GpsFixArray *fixes,
215 string *error) {
216 xmlDocPtr doc = xmlReadMemory(str, len, NULL, NULL, 0);
217 if (doc == nullptr) {
218 cleanupXmlDoc(doc);
219 *error = "GPX document not parsed successfully.";
220 return false;
221 }
222 return parse(doc, fixes, error);
223 }