* Copyright (c) 2026 Huawei Device Co., Ltd.
* 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 <iostream>
#include <cmath>
#include <cstring>
#include <sys/stat.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "gpx_parser.h"
#include "common_utils.h"
namespace OHOS::testhelper {
constexpr double DEFAULT_ALTITUDE = 0.0;
constexpr double DEFAULT_ACCURACY = 10.0;
constexpr double DEFAULT_SPEED = 0.0;
constexpr double DEFAULT_DIRECTION = 0.0;
constexpr double INVALID_LATITUDE = 999.0;
constexpr double INVALID_LONGITUDE = 999.0;
constexpr double LATITUDE_MIN = -90.0;
constexpr double LATITUDE_MAX = 90.0;
constexpr double LONGITUDE_MIN = -180.0;
constexpr double LONGITUDE_MAX = 180.0;
constexpr double DIRECTION_MIN = 0.0;
constexpr double DIRECTION_MAX = 360.0;
bool SafeStod(const std::string& str, double& value)
{
if (str.empty()) {
return false;
}
char* end = nullptr;
value = std::strtod(str.c_str(), &end);
if (end != str.c_str() && *end == '\0') {
if (std::isnan(value)) {
return false;
}
return true;
}
return false;
}
LocationList GPXParser::ParseFile(const std::string& filePath)
{
LocationList locations;
LOG_I("GPXParser: Processing GPX file %{public}s", filePath.c_str());
xmlDocPtr doc = xmlReadFile(filePath.c_str(), nullptr, XML_PARSE_NOBLANKS | XML_PARSE_NONET);
if (doc == nullptr) {
PrintToConsole("Error: The parameter validation in gpx file has failed. Valid XML format is required.");
LOG_E("GPXParser: xmlReadFile returned nullptr");
return locations;
}
xmlNodePtr root = xmlDocGetRootElement(doc);
if (root == nullptr) {
PrintToConsole("Error: The parameter validation in gpx file has failed. Root element is required.");
xmlFreeDoc(doc);
LOG_E("GPXParser: Root element is null");
return locations;
}
if (!ValidateGPXRoot(root, locations)) {
xmlFreeDoc(doc);
return locations;
}
if (!ProcessGPXChildren(root, locations)) {
locations.clear();
xmlFreeDoc(doc);
return locations;
}
xmlFreeDoc(doc);
if (locations.empty()) {
PrintToConsole(
"Error: The parameter validation in gpx file has failed. Valid location points are required.");
LOG_E("GPXParser: No valid location points found");
} else {
LOG_I("GPXParser: Successfully parsed %{public}zu points", locations.size());
}
return locations;
}
bool GPXParser::ValidateGPXRoot(xmlNodePtr root, LocationList& locations)
{
const char* rootName = reinterpret_cast<const char*>(root->name);
LOG_I("GPXParser: Root element: %{public}s", rootName);
if (xmlStrcasecmp(root->name, BAD_CAST "gpx") != 0) {
PrintToConsole("Error: The parameter validation in gpx file has failed. <gpx> root element is required.");
LOG_E("GPXParser: Invalid root element");
return false;
}
return true;
}
bool GPXParser::ProcessGPXChildren(xmlNodePtr root, LocationList& locations)
{
for (xmlNodePtr child = root->children; child != nullptr; child = child->next) {
if (child->type != XML_ELEMENT_NODE) continue;
if (child->name == nullptr) continue;
const char* childName = reinterpret_cast<const char*>(child->name);
if (!ProcessChildElement(child, childName, locations)) {
return false;
}
}
return true;
}
bool GPXParser::ProcessChildElement(xmlNodePtr child, const char* childName, LocationList& locations)
{
if (child->name == nullptr) return true;
if (xmlStrcasecmp(child->name, BAD_CAST "wpt") == 0) {
LOG_D("GPXParser: Found waypoint element");
return ProcessWaypoint(child, locations);
} else if (xmlStrcasecmp(child->name, BAD_CAST "trk") == 0) {
LOG_D("GPXParser: Found track element");
return ProcessTrack(child, locations);
} else if (xmlStrcasecmp(child->name, BAD_CAST "rte") == 0) {
LOG_D("GPXParser: Found route element");
return ProcessRoute(child, locations);
}
return true;
}
int32_t GPXParser::ValidateCoordinates(const std::string& latStr, const std::string& lonStr,
double& lat, double& lon)
{
lat = INVALID_LATITUDE;
lon = INVALID_LONGITUDE;
if (!latStr.empty() && !SafeStod(latStr, lat)) {
PrintToConsole("Error: The parameter validation in gpx file has failed. latitude is not a valid number.");
LOG_E("GPXParser: Failed to parse latitude");
return PARSE_GPX_INVALID_LAT;
}
if (!lonStr.empty() && !SafeStod(lonStr, lon)) {
PrintToConsole("Error: The parameter validation in gpx file has failed. longitude is not a valid number.");
LOG_E("GPXParser: Failed to parse longitude");
return PARSE_GPX_INVALID_LON;
}
if (lat < LATITUDE_MIN || lat > LATITUDE_MAX) {
PrintToConsole("Error: The parameter validation in gpx file has failed. latitude must be in [-90, 90].");
LOG_E("GPXParser: Invalid latitude: %{public}f", lat);
return PARSE_GPX_INVALID_COORDS;
}
if (lon < LONGITUDE_MIN || lon > LONGITUDE_MAX) {
PrintToConsole("Error: The parameter validation in gpx file has failed. longitude must be in [-180, 180].");
LOG_E("GPXParser: Invalid longitude: %{public}f", lon);
return PARSE_GPX_INVALID_COORDS;
}
return PARSE_GPX_SUCCESS;
}
int32_t GPXParser::ParseOptionalFields(const GPXElementData& data, double& ele, double& speed, double& direction)
{
ele = DEFAULT_ALTITUDE;
speed = DEFAULT_SPEED;
direction = DEFAULT_DIRECTION;
if (!data.ele.empty() && !SafeStod(data.ele, ele)) {
ele = DEFAULT_ALTITUDE;
}
if (!data.speed.empty()) {
if (!SafeStod(data.speed, speed) || speed < 0) {
PrintToConsole(
"Error: The parameter validation in gpx file has failed. speed must be a valid number and >= 0.");
LOG_E("GPXParser: Invalid speed: %{public}s", data.speed.c_str());
return PARSE_GPX_INVALID_SPEED;
}
}
if (!data.direction.empty()) {
if (!SafeStod(data.direction, direction) ||
direction < DIRECTION_MIN || direction > DIRECTION_MAX) {
PrintToConsole(
"Error: The parameter validation in gpx file has failed. "
"direction must be a valid number in [0, 360].");
LOG_E("GPXParser: Invalid direction: %{public}s", data.direction.c_str());
return PARSE_GPX_INVALID_DIRECTION;
}
}
return PARSE_GPX_SUCCESS;
}
void GPXParser::FillLocation(testserver::TestServerLocation& location, double lat, double lon,
double ele, double speed, double direction)
{
location.latitude = lat;
location.longitude = lon;
location.altitude = ele;
location.accuracy = DEFAULT_ACCURACY;
location.speed = speed;
location.direction = direction;
location.timeStamp = 0;
location.timeSinceBoot = 0;
location.isFromMock = true;
}
int32_t GPXParser::CreateLocationFromData(const GPXElementData& data, testserver::TestServerLocation& location)
{
double lat = INVALID_LATITUDE;
double lon = INVALID_LONGITUDE;
int32_t coordResult = ValidateCoordinates(data.lat, data.lon, lat, lon);
if (coordResult != PARSE_GPX_SUCCESS) {
return coordResult;
}
double ele = DEFAULT_ALTITUDE;
double speed = DEFAULT_SPEED;
double direction = DEFAULT_DIRECTION;
int32_t optionalResult = ParseOptionalFields(data, ele, speed, direction);
if (optionalResult != PARSE_GPX_SUCCESS) {
return optionalResult;
}
FillLocation(location, lat, lon, ele, speed, direction);
return PARSE_GPX_SUCCESS;
}
void GPXParser::ParseExtensions(xmlNodePtr node, std::string& speedStr, std::string& directionStr)
{
if (node == nullptr) return;
for (xmlNodePtr child = node->children; child != nullptr; child = child->next) {
if (child->type != XML_ELEMENT_NODE) continue;
if (child->name == nullptr) continue;
const char* childName = reinterpret_cast<const char*>(child->name);
if (strcasestr(childName, "speed") != nullptr) {
xmlChar* content = xmlNodeGetContent(child);
if (content != nullptr) {
speedStr = reinterpret_cast<char*>(content);
xmlFree(content);
}
}
if (strcasestr(childName, "course") != nullptr || strcasestr(childName, "direction") != nullptr) {
xmlChar* content = xmlNodeGetContent(child);
if (content != nullptr) {
directionStr = reinterpret_cast<char*>(content);
xmlFree(content);
}
}
}
}
void GPXParser::ParsePointData(xmlNodePtr node, GPXElementData& data, std::string& speedStr,
std::string& directionStr)
{
if (node == nullptr) return;
for (xmlNodePtr child = node->children; child != nullptr; child = child->next) {
if (child->type != XML_ELEMENT_NODE) continue;
if (child->name == nullptr) continue;
const char* childName = reinterpret_cast<const char*>(child->name);
bool isExtensionsContainer = xmlStrcasecmp(child->name, BAD_CAST "extensions") == 0 ||
strcasestr(childName, "extensions") != nullptr;
if (isExtensionsContainer) {
ParseExtensions(child, speedStr, directionStr);
continue;
}
if (xmlStrcasecmp(child->name, BAD_CAST "ele") == 0) {
xmlChar* content = xmlNodeGetContent(child);
if (content != nullptr) {
data.ele = reinterpret_cast<char*>(content);
xmlFree(content);
}
} else if (xmlStrcasecmp(child->name, BAD_CAST "time") == 0) {
xmlChar* content = xmlNodeGetContent(child);
if (content != nullptr) {
data.time = reinterpret_cast<char*>(content);
xmlFree(content);
}
} else if (strcasestr(childName, "speed") != nullptr && speedStr.empty()) {
xmlChar* content = xmlNodeGetContent(child);
if (content != nullptr) {
speedStr = reinterpret_cast<char*>(content);
xmlFree(content);
}
} else if ((strcasestr(childName, "course") != nullptr || strcasestr(childName, "direction") != nullptr) &&
directionStr.empty()) {
xmlChar* content = xmlNodeGetContent(child);
if (content != nullptr) {
directionStr = reinterpret_cast<char*>(content);
xmlFree(content);
}
}
}
}
bool GPXParser::AddLocation(const GPXElementData& data, const std::string& speedStr,
const std::string& directionStr, LocationList& locations, const std::string& pointType)
{
GPXElementData locationData = data;
if (!speedStr.empty()) locationData.speed = speedStr;
if (!directionStr.empty()) locationData.direction = directionStr;
testserver::TestServerLocation location;
if (CreateLocationFromData(locationData, location) != PARSE_GPX_SUCCESS) {
return false;
}
locations.push_back(location);
LOG_I("GPXParser: Added valid %{public}s point", pointType.c_str());
return true;
}
bool GPXParser::ProcessPoint(xmlNodePtr node, LocationList& locations, const std::string& pointType)
{
if (node == nullptr || node->properties == nullptr) {
PrintToConsole("Error: The parameter validation in gpx file has failed. Invalid point node.");
LOG_E("GPXParser: ProcessPoint: Invalid node for %{public}s", pointType.c_str());
return false;
}
xmlChar* latAttr = xmlGetProp(node, BAD_CAST "lat");
xmlChar* lonAttr = xmlGetProp(node, BAD_CAST "lon");
if (latAttr == nullptr || lonAttr == nullptr) {
if (latAttr) xmlFree(latAttr);
if (lonAttr) xmlFree(lonAttr);
PrintToConsole(
"Error: The parameter validation in gpx file has failed. lat and lon attributes are required.");
LOG_E("GPXParser: Missing lat or lon attribute");
return false;
}
GPXElementData data;
data.lat = reinterpret_cast<char*>(latAttr);
data.lon = reinterpret_cast<char*>(lonAttr);
xmlFree(latAttr);
xmlFree(lonAttr);
std::string speedStr;
std::string directionStr;
ParsePointData(node, data, speedStr, directionStr);
return AddLocation(data, speedStr, directionStr, locations, pointType);
}
bool GPXParser::ProcessWaypoint(xmlNodePtr node, LocationList& locations)
{
return ProcessPoint(node, locations, "waypoint");
}
bool GPXParser::ProcessTrackSegment(xmlNodePtr trksegNode, LocationList& locations)
{
if (trksegNode == nullptr) {
PrintToConsole("Error: The parameter validation in gpx file has failed. Invalid track segment node.");
LOG_E("GPXParser: ProcessTrackSegment: Invalid node");
return false;
}
for (xmlNodePtr child = trksegNode->children; child != nullptr; child = child->next) {
if (child->type != XML_ELEMENT_NODE) continue;
if (child->name == nullptr) continue;
if (xmlStrcasecmp(child->name, BAD_CAST "trkpt") == 0) {
if (!ProcessPoint(child, locations, "track")) {
return false;
}
}
}
return true;
}
bool GPXParser::ProcessTrack(xmlNodePtr trkNode, LocationList& locations)
{
if (trkNode == nullptr) {
PrintToConsole("Error: The parameter validation in gpx file has failed. Invalid track node.");
LOG_E("GPXParser: ProcessTrack: Invalid node");
return false;
}
for (xmlNodePtr child = trkNode->children; child != nullptr; child = child->next) {
if (child->type != XML_ELEMENT_NODE) continue;
if (child->name == nullptr) continue;
if (xmlStrcasecmp(child->name, BAD_CAST "trkseg") == 0) {
if (!ProcessTrackSegment(child, locations)) {
return false;
}
}
}
return true;
}
bool GPXParser::ProcessRoute(xmlNodePtr rteNode, LocationList& locations)
{
if (rteNode == nullptr) {
PrintToConsole("Error: The parameter validation in gpx file has failed. Invalid route node.");
LOG_E("GPXParser: ProcessRoute: Invalid node");
return false;
}
for (xmlNodePtr child = rteNode->children; child != nullptr; child = child->next) {
if (child->type != XML_ELEMENT_NODE) continue;
if (child->name == nullptr) continue;
if (xmlStrcasecmp(child->name, BAD_CAST "rtept") == 0) {
if (!ProcessPoint(child, locations, "route")) {
return false;
}
}
}
return true;
}
}