6#include <QtXml/QDomDocument>
12 [[maybe_unused]]
constexpr const char *XS_NS =
"http://www.w3.org/2001/XMLSchema";
15 constexpr const char *SCHEMA_RESOURCE =
":/kml/ogckml22.xsd";
24KMLSchemaValidator::KMLSchemaValidator()
29void KMLSchemaValidator::loadSchema()
31 QFile schemaFile(SCHEMA_RESOURCE);
32 if (!schemaFile.open(QIODevice::ReadOnly)) {
33 qCWarning(KMLSchemaValidatorLog) <<
"Failed to open KML schema resource:" << SCHEMA_RESOURCE;
37 QDomDocument schemaDoc;
38 const QDomDocument::ParseResult result = schemaDoc.setContent(&schemaFile);
40 qCWarning(KMLSchemaValidatorLog) <<
"Failed to parse KML schema:" << result.errorMessage;
44 parseSchemaDocument(schemaDoc);
46 qCDebug(KMLSchemaValidatorLog) <<
"Loaded KML schema with" << _enumTypes.size() <<
"enum types and"
47 << _validElements.size() <<
"elements";
50void KMLSchemaValidator::parseSchemaDocument(
const QDomDocument &schemaDoc)
52 const QDomElement root = schemaDoc.documentElement();
53 extractEnumTypes(root);
54 extractElements(root);
57void KMLSchemaValidator::extractEnumTypes(
const QDomElement &root)
60 const QDomNodeList simpleTypes = root.elementsByTagName(
"simpleType");
62 for (
int i = 0; i < simpleTypes.count(); i++) {
63 const QDomElement simpleType = simpleTypes.item(i).toElement();
64 const QString typeName = simpleType.attribute(
"name");
65 if (typeName.isEmpty()) {
70 const QDomElement restriction = simpleType.firstChildElement(
"restriction");
71 if (restriction.isNull()) {
76 QStringList enumValues;
77 QDomElement enumEl = restriction.firstChildElement(
"enumeration");
78 while (!enumEl.isNull()) {
79 const QString value = enumEl.attribute(
"value");
80 if (!value.isEmpty()) {
81 enumValues.append(value);
83 enumEl = enumEl.nextSiblingElement(
"enumeration");
86 if (!enumValues.isEmpty()) {
87 _enumTypes.insert(typeName, enumValues);
88 qCDebug(KMLSchemaValidatorLog) <<
"Extracted enum type:" << typeName <<
"with values:" << enumValues;
93void KMLSchemaValidator::extractElements(
const QDomElement &root)
96 const QDomNodeList elements = root.elementsByTagName(
"element");
98 for (
int i = 0; i < elements.count(); i++) {
99 const QDomElement element = elements.item(i).toElement();
100 const QString name = element.attribute(
"name");
101 if (!name.isEmpty()) {
102 _validElements.insert(name);
109 const auto it = _enumTypes.constFind(enumTypeName);
110 if (it == _enumTypes.constEnd()) {
113 return it->contains(value);
118 return _enumTypes.value(enumTypeName);
123 return _validElements.contains(elementName);
128 return QStringList(_validElements.begin(), _validElements.end());
136 if (!file.open(QIODevice::ReadOnly)) {
137 result.
addError(QString(
"Failed to open file: %1").arg(kmlFile));
142 const QDomDocument::ParseResult parseResult = doc.setContent(&file);
144 result.
addError(QString(
"XML parse error at line %1: %2")
145 .arg(parseResult.errorLine)
146 .arg(parseResult.errorMessage));
157 const QDomElement root = doc.documentElement();
160 if (root.tagName() !=
"kml") {
161 result.
addError(QString(
"Root element must be 'kml', found '%1'").arg(root.tagName()));
166 const QString ns = root.namespaceURI();
172 validateElement(root, result);
177void KMLSchemaValidator::validateElement(
const QDomElement &element, ValidationResult &result)
const
179 const QString tagName = element.tagName();
182 if (!tagName.contains(
':') && !_validElements.isEmpty() && !
isValidElement(tagName)) {
183 result.addWarning(QString(
"Unknown element: '%1'").arg(tagName));
187 if (tagName ==
"altitudeMode") {
188 const QString value = element.text();
190 result.addError(QString(
"Invalid altitudeMode value: '%1'. Valid values: %2")
192 }
else if (value !=
"absolute" && !value.isEmpty()) {
193 result.addWarning(QString(
"altitudeMode '%1' - QGC treats coordinates as absolute (AMSL)").arg(value));
195 }
else if (tagName ==
"colorMode") {
196 const QString value = element.text();
198 result.addError(QString(
"Invalid colorMode value: '%1'. Valid values: %2")
201 }
else if (tagName ==
"coordinates") {
202 validateCoordinates(element.text(), result);
206 QDomElement child = element.firstChildElement();
207 while (!child.isNull()) {
208 validateElement(child, result);
209 child = child.nextSiblingElement();
213void KMLSchemaValidator::validateCoordinates(
const QString &coordString, ValidationResult &result)
const
215 const QString simplified = coordString.simplified();
216 if (simplified.isEmpty()) {
217 result.addError(
"Empty coordinates string");
221 const QStringList coords = simplified.split(
' ');
222 for (
const QString &coord : coords) {
223 if (coord.isEmpty()) {
227 const QStringList parts = coord.split(
',');
228 if (parts.size() < 2) {
229 result.addError(QString(
"Invalid coordinate format (expected lon,lat[,alt]): '%1'").arg(coord));
233 bool lonOk =
false, latOk =
false;
234 const double lon = parts[0].toDouble(&lonOk);
235 const double lat = parts[1].toDouble(&latOk);
237 if (!lonOk || !latOk) {
238 result.addError(QString(
"Failed to parse coordinate values: '%1'").arg(coord));
242 if (lat < -90.0 || lat > 90.0) {
243 result.addError(QString(
"Latitude out of range [-90, 90]: %1 in '%2'").arg(lat).arg(coord));
245 if (lon < -180.0 || lon > 180.0) {
246 result.addError(QString(
"Longitude out of range [-180, 180]: %1 in '%2'").arg(lon).arg(coord));
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static constexpr const char * kmlNamespace
static KMLSchemaValidator * instance()
ValidationResult validate(const QDomDocument &doc) const
Validate a KML document.
ValidationResult validateFile(const QString &kmlFile) const
Validate a KML file.
bool isValidElement(const QString &elementName) const
Check if an element name is defined in the schema.
QStringList validEnumValues(const QString &enumTypeName) const
Get all valid values for an enum type.
bool isValidEnumValue(const QString &enumTypeName, const QString &value) const
Check if a value is valid for a given XSD enum type (e.g., "altitudeModeEnumType")
QStringList validElements() const
Get all valid KML element names.
void addError(const QString &msg)
void addWarning(const QString &msg)