/**************************************************************************** * * (c) 2009-2020 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ #include "JsonHelper.h" #include "QGCQGeoCoordinate.h" #include "QmlObjectListModel.h" #include "MissionCommandList.h" #include "FactMetaData.h" #include "QGCApplication.h" #include #include #include #include #include #include #include const char* JsonHelper::_enumStringsJsonKey = "enumStrings"; const char* JsonHelper::_enumValuesJsonKey = "enumValues"; const char* JsonHelper::jsonVersionKey = "version"; const char* JsonHelper::jsonGroundStationKey = "groundStation"; const char* JsonHelper::jsonGroundStationValue = "QGroundControl"; const char* JsonHelper::jsonFileTypeKey = "fileType"; const char* JsonHelper::_translateKeysKey = "translateKeys"; const char* JsonHelper::_arrayIDKeysKey = "_arrayIDKeys"; bool JsonHelper::validateRequiredKeys(const QJsonObject& jsonObject, const QStringList& keys, QString& errorString) { QString missingKeys; foreach(const QString& key, keys) { if (!jsonObject.contains(key)) { if (!missingKeys.isEmpty()) { missingKeys += QStringLiteral(", "); } missingKeys += key; } } if (missingKeys.count() != 0) { errorString = QObject::tr("The following required keys are missing: %1").arg(missingKeys); return false; } return true; } bool JsonHelper::_loadGeoCoordinate(const QJsonValue& jsonValue, bool altitudeRequired, QGeoCoordinate& coordinate, QString& errorString, bool geoJsonFormat) { if (!jsonValue.isArray()) { errorString = QObject::tr("value for coordinate is not array"); return false; } QJsonArray coordinateArray = jsonValue.toArray(); int requiredCount = altitudeRequired ? 3 : 2; if (coordinateArray.count() != requiredCount) { errorString = QObject::tr("Coordinate array must contain %1 values").arg(requiredCount); return false; } foreach(const QJsonValue& jsonValue, coordinateArray) { if (jsonValue.type() != QJsonValue::Double && jsonValue.type() != QJsonValue::Null) { errorString = QObject::tr("Coordinate array may only contain double values, found: %1").arg(jsonValue.type()); return false; } } if (geoJsonFormat) { coordinate = QGeoCoordinate(coordinateArray[1].toDouble(), coordinateArray[0].toDouble()); } else { coordinate = QGeoCoordinate(possibleNaNJsonValue(coordinateArray[0]), possibleNaNJsonValue(coordinateArray[1])); } if (altitudeRequired) { coordinate.setAltitude(possibleNaNJsonValue(coordinateArray[2])); } return true; } void JsonHelper::_saveGeoCoordinate(const QGeoCoordinate& coordinate, bool writeAltitude, QJsonValue& jsonValue, bool geoJsonFormat) { QJsonArray coordinateArray; if (geoJsonFormat) { coordinateArray << coordinate.longitude() << coordinate.latitude(); } else { coordinateArray << coordinate.latitude() << coordinate.longitude(); } if (writeAltitude) { coordinateArray << coordinate.altitude(); } jsonValue = QJsonValue(coordinateArray); } bool JsonHelper::loadGeoCoordinate(const QJsonValue& jsonValue, bool altitudeRequired, QGeoCoordinate& coordinate, QString& errorString, bool geoJsonFormat) { return _loadGeoCoordinate(jsonValue, altitudeRequired, coordinate, errorString, geoJsonFormat); } void JsonHelper::saveGeoCoordinate(const QGeoCoordinate& coordinate, bool writeAltitude, QJsonValue& jsonValue) { _saveGeoCoordinate(coordinate, writeAltitude, jsonValue, false /* geoJsonFormat */); } bool JsonHelper::loadGeoJsonCoordinate(const QJsonValue& jsonValue, bool altitudeRequired, QGeoCoordinate& coordinate, QString& errorString) { return _loadGeoCoordinate(jsonValue, altitudeRequired, coordinate, errorString, true /* geoJsonFormat */); } void JsonHelper::saveGeoJsonCoordinate(const QGeoCoordinate& coordinate, bool writeAltitude, QJsonValue& jsonValue) { _saveGeoCoordinate(coordinate, writeAltitude, jsonValue, true /* geoJsonFormat */); } bool JsonHelper::validateKeyTypes(const QJsonObject& jsonObject, const QStringList& keys, const QList& types, QString& errorString) { for (int i=0; i& defineMap, QStringList& enumStrings, QStringList& enumValues, QString& errorString, QString valueName) { if(jsonObject.value(_enumStringsJsonKey).isArray()) { // "enumStrings": ["Auto" , "Manual", "Shutter Priority", "Aperture Priority"], QJsonArray jArray = jsonObject.value(_enumStringsJsonKey).toArray(); for(int i = 0; i < jArray.count(); ++i) { enumStrings << jArray.at(i).toString(); } } else { // "enumStrings": "Auto,Manual,Shutter Priority,Aperture Priority", QString value = jsonObject.value(_enumStringsJsonKey).toString(); #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) enumStrings = defineMap.value(value, value).split(",", QString::SkipEmptyParts); #else enumStrings = defineMap.value(value, value).split(",", Qt::SkipEmptyParts); #endif } if(jsonObject.value(_enumValuesJsonKey).isArray()) { // "enumValues": [0, 1, 2, 3, 4, 5], QJsonArray jArray = jsonObject.value(_enumValuesJsonKey).toArray(); // This should probably be a variant list and not a string list. for(int i = 0; i < jArray.count(); ++i) { if(jArray.at(i).isString()) enumValues << jArray.at(i).toString(); else enumValues << QString::number(jArray.at(i).toDouble()); } } else { // "enumValues": "0,1,2,3,4,5", QString value = jsonObject.value(_enumValuesJsonKey).toString(); #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) enumValues = defineMap.value(value, value).split(",", QString::SkipEmptyParts); #else enumValues = defineMap.value(value, value).split(",", Qt::SkipEmptyParts); #endif } if (enumStrings.count() != enumValues.count()) { errorString = QObject::tr("enum strings/values count mismatch in %3 strings:values %1:%2").arg(enumStrings.count()).arg(enumValues.count()).arg(valueName); return false; } return true; } bool JsonHelper::parseEnum(const QJsonObject& jsonObject, QMap& defineMap, QStringList& enumStrings, QStringList& enumValues, QString& errorString, QString valueName) { return _parseEnumWorker(jsonObject, defineMap, enumStrings, enumValues, errorString, valueName); } bool JsonHelper::parseEnum(const QJsonObject& jsonObject, QStringList& enumStrings, QStringList& enumValues, QString& errorString, QString valueName) { QMap defineMap; return _parseEnumWorker(jsonObject, defineMap, enumStrings, enumValues, errorString, valueName); } bool JsonHelper::isJsonFile(const QByteArray& bytes, QJsonDocument& jsonDoc, QString& errorString) { QJsonParseError parseError; jsonDoc = QJsonDocument::fromJson(bytes, &parseError); if (parseError.error == QJsonParseError::NoError) { return true; } else { int startPos = qMax(0, parseError.offset - 100); int length = qMin(bytes.count() - startPos, 200); qDebug() << QStringLiteral("Json read error '%1'").arg(bytes.mid(startPos, length).constData()); errorString = parseError.errorString(); return false; } } bool JsonHelper::isJsonFile(const QString& fileName, QJsonDocument& jsonDoc, QString& errorString) { QFile jsonFile(fileName); if (!jsonFile.open(QFile::ReadOnly)) { errorString = tr("File open failed: file:error %1 %2").arg(jsonFile.fileName()).arg(jsonFile.errorString()); return false; } QByteArray jsonBytes = jsonFile.readAll(); jsonFile.close(); return isJsonFile(jsonBytes, jsonDoc, errorString); } bool JsonHelper::validateInternalQGCJsonFile(const QJsonObject& jsonObject, const QString& expectedFileType, int minSupportedVersion, int maxSupportedVersion, int& version, QString& errorString) { // Validate required keys QList requiredKeys = { { jsonFileTypeKey, QJsonValue::String, true }, { jsonVersionKey, QJsonValue::Double, true }, }; if (!JsonHelper::validateKeys(jsonObject, requiredKeys, errorString)) { return false; } // Make sure file type is correct QString fileTypeValue = jsonObject[jsonFileTypeKey].toString(); if (fileTypeValue != expectedFileType) { errorString = QObject::tr("Incorrect file type key expected:%1 actual:%2").arg(expectedFileType).arg(fileTypeValue); return false; } // Version check version = jsonObject[jsonVersionKey].toInt(); if (version < minSupportedVersion) { errorString = QObject::tr("File version %1 is no longer supported").arg(version); return false; } if (version > maxSupportedVersion) { errorString = QObject::tr("File version %1 is newer than current supported version %2").arg(version).arg(maxSupportedVersion); return false; } return true; } bool JsonHelper::validateExternalQGCJsonFile(const QJsonObject& jsonObject, const QString& expectedFileType, int minSupportedVersion, int maxSupportedVersion, int& version, QString& errorString) { // Validate required keys QList requiredKeys = { { jsonGroundStationKey, QJsonValue::String, true }, }; if (!JsonHelper::validateKeys(jsonObject, requiredKeys, errorString)) { return false; } return validateInternalQGCJsonFile(jsonObject, expectedFileType, minSupportedVersion, maxSupportedVersion, version, errorString); } QStringList JsonHelper::_addDefaultLocKeys(QJsonObject& jsonObject) { QString translateKeys; QString fileType = jsonObject[jsonFileTypeKey].toString(); if (!fileType.isEmpty()) { if (fileType == MissionCommandList::qgcFileType) { if (jsonObject.contains(_translateKeysKey)) { translateKeys = jsonObject[_translateKeysKey].toString(); } else { translateKeys = "label,enumStrings,friendlyName,description,category"; jsonObject[_translateKeysKey] = translateKeys; } if (!jsonObject.contains(_arrayIDKeysKey)) { jsonObject[_arrayIDKeysKey] = "rawName,comment"; } } else if (fileType == FactMetaData::qgcFileType) { if (jsonObject.contains(_translateKeysKey)) { translateKeys = jsonObject[_translateKeysKey].toString(); } else { translateKeys = "shortDescription,longDescription,enumStrings"; jsonObject[_translateKeysKey] = "shortDescription,longDescription,enumStrings"; } if (!jsonObject.contains(_arrayIDKeysKey)) { jsonObject[_arrayIDKeysKey] = "name"; } } } return translateKeys.split(","); } QJsonObject JsonHelper::_translateObject(QJsonObject& jsonObject, const QString& translateContext, const QStringList& translateKeys) { for (const QString& key: jsonObject.keys()) { if (jsonObject[key].isString()) { QString locString = jsonObject[key].toString(); if (translateKeys.contains(key)) { QString disambiguation; QString disambiguationPrefix("#loc.disambiguation#"); if (locString.startsWith(disambiguationPrefix)) { locString = locString.right(locString.length() - disambiguationPrefix.length()); int commentEndIndex = locString.indexOf("#"); if (commentEndIndex != -1) { disambiguation = locString.left(commentEndIndex); locString = locString.right(locString.length() - disambiguation.length() - 1); } } QString xlatString = qgcApp()->qgcJSONTranslator().translate(translateContext.toUtf8().constData(), locString.toUtf8().constData(), disambiguation.toUtf8().constData()); if (!xlatString.isNull()) { jsonObject[key] = xlatString; } } } else if (jsonObject[key].isArray()) { QJsonArray childJsonArray = jsonObject[key].toArray(); jsonObject[key] = _translateArray(childJsonArray, translateContext, translateKeys); } else if (jsonObject[key].isObject()) { QJsonObject childJsonObject = jsonObject[key].toObject(); jsonObject[key] = _translateObject(childJsonObject, translateContext, translateKeys); } } return jsonObject; } QJsonArray JsonHelper::_translateArray(QJsonArray& jsonArray, const QString& translateContext, const QStringList& translateKeys) { for (int i=0; i& rgPoints, QString& errorString) { QVariantList rgVarPoints; if (!loadGeoCoordinateArray(jsonValue, altitudeRequired, rgVarPoints, errorString)) { return false; } rgPoints.clear(); for (int i=0; i()); } return true; } void JsonHelper::saveGeoCoordinateArray(const QVariantList& rgVarPoints, bool writeAltitude, QJsonValue& jsonValue) { QJsonArray rgJsonPoints; // Add all points to the array for (int i=0; i(), writeAltitude, jsonPoint); rgJsonPoints.append(jsonPoint); } jsonValue = rgJsonPoints; } void JsonHelper::saveGeoCoordinateArray(const QList& rgPoints, bool writeAltitude, QJsonValue& jsonValue) { QVariantList rgVarPoints; for (int i=0; i& keyInfo, QString& errorString) { QStringList keyList; QList typeList; for (int i=0; i(i)->coordinate(); QJsonValue jsonValue; JsonHelper::saveGeoCoordinate(vertex, false /* writeAltitude */, jsonValue); polygonArray.append(jsonValue); } } double JsonHelper::possibleNaNJsonValue(const QJsonValue& value) { if (value.type() == QJsonValue::Null) { return std::numeric_limits::quiet_NaN(); } else { return value.toDouble(); } }