QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
KMLSchemaValidator.cc
Go to the documentation of this file.
2#include "KMLDomDocument.h"
4
5#include <QtCore/QFile>
6#include <QtXml/QDomDocument>
7
8QGC_LOGGING_CATEGORY(KMLSchemaValidatorLog, "Utilities.KMLSchemaValidator")
9
10namespace {
11 // XSD namespace
12 [[maybe_unused]] constexpr const char *XS_NS = "http://www.w3.org/2001/XMLSchema";
13
14 // Schema resource path (PREFIX "/kml" + FILES "ogckml22.xsd")
15 constexpr const char *SCHEMA_RESOURCE = ":/kml/ogckml22.xsd";
16}
17
19{
20 static KMLSchemaValidator validator;
21 return &validator;
22}
23
24KMLSchemaValidator::KMLSchemaValidator()
25{
26 loadSchema();
27}
28
29void KMLSchemaValidator::loadSchema()
30{
31 QFile schemaFile(SCHEMA_RESOURCE);
32 if (!schemaFile.open(QIODevice::ReadOnly)) {
33 qCWarning(KMLSchemaValidatorLog) << "Failed to open KML schema resource:" << SCHEMA_RESOURCE;
34 return;
35 }
36
37 QDomDocument schemaDoc;
38 const QDomDocument::ParseResult result = schemaDoc.setContent(&schemaFile);
39 if (!result) {
40 qCWarning(KMLSchemaValidatorLog) << "Failed to parse KML schema:" << result.errorMessage;
41 return;
42 }
43
44 parseSchemaDocument(schemaDoc);
45 _loaded = true;
46 qCDebug(KMLSchemaValidatorLog) << "Loaded KML schema with" << _enumTypes.size() << "enum types and"
47 << _validElements.size() << "elements";
48}
49
50void KMLSchemaValidator::parseSchemaDocument(const QDomDocument &schemaDoc)
51{
52 const QDomElement root = schemaDoc.documentElement();
53 extractEnumTypes(root);
54 extractElements(root);
55}
56
57void KMLSchemaValidator::extractEnumTypes(const QDomElement &root)
58{
59 // Find all xs:simpleType elements with xs:restriction/xs:enumeration children
60 const QDomNodeList simpleTypes = root.elementsByTagName("simpleType");
61
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()) {
66 continue;
67 }
68
69 // Look for restriction element
70 const QDomElement restriction = simpleType.firstChildElement("restriction");
71 if (restriction.isNull()) {
72 continue;
73 }
74
75 // Collect enumeration values
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);
82 }
83 enumEl = enumEl.nextSiblingElement("enumeration");
84 }
85
86 if (!enumValues.isEmpty()) {
87 _enumTypes.insert(typeName, enumValues);
88 qCDebug(KMLSchemaValidatorLog) << "Extracted enum type:" << typeName << "with values:" << enumValues;
89 }
90 }
91}
92
93void KMLSchemaValidator::extractElements(const QDomElement &root)
94{
95 // Find all xs:element definitions
96 const QDomNodeList elements = root.elementsByTagName("element");
97
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);
103 }
104 }
105}
106
107bool KMLSchemaValidator::isValidEnumValue(const QString &enumTypeName, const QString &value) const
108{
109 const auto it = _enumTypes.constFind(enumTypeName);
110 if (it == _enumTypes.constEnd()) {
111 return true; // Unknown enum type, don't reject
112 }
113 return it->contains(value);
114}
115
116QStringList KMLSchemaValidator::validEnumValues(const QString &enumTypeName) const
117{
118 return _enumTypes.value(enumTypeName);
119}
120
121bool KMLSchemaValidator::isValidElement(const QString &elementName) const
122{
123 return _validElements.contains(elementName);
124}
125
127{
128 return QStringList(_validElements.begin(), _validElements.end());
129}
130
132{
133 ValidationResult result;
134
135 QFile file(kmlFile);
136 if (!file.open(QIODevice::ReadOnly)) {
137 result.addError(QString("Failed to open file: %1").arg(kmlFile));
138 return result;
139 }
140
141 QDomDocument doc;
142 const QDomDocument::ParseResult parseResult = doc.setContent(&file);
143 if (!parseResult) {
144 result.addError(QString("XML parse error at line %1: %2")
145 .arg(parseResult.errorLine)
146 .arg(parseResult.errorMessage));
147 return result;
148 }
149
150 return validate(doc);
151}
152
154{
155 ValidationResult result;
156
157 const QDomElement root = doc.documentElement();
158
159 // Check root element is <kml>
160 if (root.tagName() != "kml") {
161 result.addError(QString("Root element must be 'kml', found '%1'").arg(root.tagName()));
162 return result;
163 }
164
165 // Check namespace
166 const QString ns = root.namespaceURI();
167 if (!ns.isEmpty() && ns != KMLDomDocument::kmlNamespace) {
168 result.addWarning(QString("Expected namespace '%1', found '%2'").arg(KMLDomDocument::kmlNamespace, ns));
169 }
170
171 // Recursively validate all elements
172 validateElement(root, result);
173
174 return result;
175}
176
177void KMLSchemaValidator::validateElement(const QDomElement &element, ValidationResult &result) const
178{
179 const QString tagName = element.tagName();
180
181 // Check if element is known (skip namespace-prefixed elements)
182 if (!tagName.contains(':') && !_validElements.isEmpty() && !isValidElement(tagName)) {
183 result.addWarning(QString("Unknown element: '%1'").arg(tagName));
184 }
185
186 // Validate specific elements
187 if (tagName == "altitudeMode") {
188 const QString value = element.text();
189 if (!value.isEmpty() && !isValidEnumValue("altitudeModeEnumType", value)) {
190 result.addError(QString("Invalid altitudeMode value: '%1'. Valid values: %2")
191 .arg(value, validEnumValues("altitudeModeEnumType").join(", ")));
192 } else if (value != "absolute" && !value.isEmpty()) {
193 result.addWarning(QString("altitudeMode '%1' - QGC treats coordinates as absolute (AMSL)").arg(value));
194 }
195 } else if (tagName == "colorMode") {
196 const QString value = element.text();
197 if (!value.isEmpty() && !isValidEnumValue("colorModeEnumType", value)) {
198 result.addError(QString("Invalid colorMode value: '%1'. Valid values: %2")
199 .arg(value, validEnumValues("colorModeEnumType").join(", ")));
200 }
201 } else if (tagName == "coordinates") {
202 validateCoordinates(element.text(), result);
203 }
204
205 // Validate children recursively
206 QDomElement child = element.firstChildElement();
207 while (!child.isNull()) {
208 validateElement(child, result);
209 child = child.nextSiblingElement();
210 }
211}
212
213void KMLSchemaValidator::validateCoordinates(const QString &coordString, ValidationResult &result) const
214{
215 const QString simplified = coordString.simplified();
216 if (simplified.isEmpty()) {
217 result.addError("Empty coordinates string");
218 return;
219 }
220
221 const QStringList coords = simplified.split(' ');
222 for (const QString &coord : coords) {
223 if (coord.isEmpty()) {
224 continue;
225 }
226
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));
230 continue;
231 }
232
233 bool lonOk = false, latOk = false;
234 const double lon = parts[0].toDouble(&lonOk);
235 const double lat = parts[1].toDouble(&latOk);
236
237 if (!lonOk || !latOk) {
238 result.addError(QString("Failed to parse coordinate values: '%1'").arg(coord));
239 continue;
240 }
241
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));
244 }
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));
247 }
248 }
249}
#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.