2 ******************************************************************************
4 * @file uavobjectparser.cpp
5 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2016.
6 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
7 * @brief Parses XML files and extracts object information.
9 * @see The GNU Public License (GPL) Version 3
11 *****************************************************************************/
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 3 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #include "uavobjectparser.h"
29 #include <QDomDocument>
30 #include <QDomElement>
35 UAVObjectParser::UAVObjectParser()
37 fieldTypeStrXML
<< "int8" << "int16" << "int32" << "uint8"
38 << "uint16" << "uint32" << "float" << "enum";
40 updateModeStrXML
<< "manual" << "periodic" << "onchange" << "throttled";
42 accessModeStr
<< "ACCESS_READWRITE" << "ACCESS_READONLY";
44 fieldTypeNumBytes
<< int(1) << int(2) << int(4) <<
45 int(1) << int(2) << int(4) <<
48 accessModeStrXML
<< "readwrite" << "readonly";
52 * Get number of objects
54 int UAVObjectParser::getNumObjects()
56 return objInfo
.length();
60 * Get the detailed object information
62 QList
<ObjectInfo
*> UAVObjectParser::getObjectInfo()
67 ObjectInfo
*UAVObjectParser::getObjectByIndex(int objIndex
)
69 return objInfo
[objIndex
];
72 ObjectInfo
*UAVObjectParser::getObjectByName(const QString
& objName
)
74 foreach(ObjectInfo
* info
, objInfo
) {
75 if (objName
== info
->name
) {
82 * Get the name of the object
84 QString
UAVObjectParser::getObjectName(int objIndex
)
86 ObjectInfo
*info
= objInfo
[objIndex
];
96 * Get the ID of the object
98 quint32
UAVObjectParser::getObjectID(int objIndex
)
100 ObjectInfo
*info
= objInfo
[objIndex
];
109 * Get the number of bytes in the data fields of this object
111 int UAVObjectParser::getNumBytes(int objIndex
)
113 ObjectInfo
*info
= objInfo
[objIndex
];
119 for (int n
= 0; n
< info
->fields
.length(); ++n
) {
120 numBytes
+= info
->fields
[n
]->numBytes
* info
->fields
[n
]->numElements
;
126 bool fieldTypeLessThan(const FieldInfo
*f1
, const FieldInfo
*f2
)
128 return f1
->numBytes
> f2
->numBytes
;
132 * Parse supplied XML file
133 * @param xml The xml text
134 * @param filename The xml filename
135 * @returns Null QString() on success, error message on failure
137 QString
UAVObjectParser::parseXML(QString
& xml
, QString
& filename
)
139 // Create DOM document and parse it
140 QDomDocument
doc("UAVObjects");
141 bool parsed
= doc
.setContent(xml
);
144 return QString("Improperly formated XML file");
147 // Read all objects contained in the XML file, creating an new ObjectInfo for each
148 QDomElement docElement
= doc
.documentElement();
149 QDomNode node
= docElement
.firstChild();
150 while (!node
.isNull()) {
151 // Create new object entry
152 ObjectInfo
*info
= new ObjectInfo
;
154 info
->filename
= filename
;
155 // Process object attributes
156 QString status
= processObjectAttributes(node
, info
);
157 if (!status
.isNull()) {
161 // Process child elements (fields and metadata)
162 QDomNode childNode
= node
.firstChild();
163 bool fieldFound
= false;
164 bool accessFound
= false;
165 bool telGCSFound
= false;
166 bool telFlightFound
= false;
167 bool logFound
= false;
168 bool descriptionFound
= false;
169 while (!childNode
.isNull()) {
170 // Process element depending on its type
171 if (childNode
.nodeName().compare(QString("field")) == 0) {
172 QString status
= processObjectFields(childNode
, info
);
173 if (!status
.isNull()) {
178 } else if (childNode
.nodeName().compare(QString("access")) == 0) {
179 QString status
= processObjectAccess(childNode
, info
);
180 if (!status
.isNull()) {
185 } else if (childNode
.nodeName().compare(QString("telemetrygcs")) == 0) {
186 QString status
= processObjectMetadata(childNode
, &info
->gcsTelemetryUpdateMode
,
187 &info
->gcsTelemetryUpdatePeriod
, &info
->gcsTelemetryAcked
);
188 if (!status
.isNull()) {
193 } else if (childNode
.nodeName().compare(QString("telemetryflight")) == 0) {
194 QString status
= processObjectMetadata(childNode
, &info
->flightTelemetryUpdateMode
,
195 &info
->flightTelemetryUpdatePeriod
, &info
->flightTelemetryAcked
);
196 if (!status
.isNull()) {
200 telFlightFound
= true;
201 } else if (childNode
.nodeName().compare(QString("logging")) == 0) {
202 QString status
= processObjectMetadata(childNode
, &info
->loggingUpdateMode
,
203 &info
->loggingUpdatePeriod
, NULL
);
204 if (!status
.isNull()) {
209 } else if (childNode
.nodeName().compare(QString("description")) == 0) {
210 QString status
= processObjectDescription(childNode
, &info
->description
);
212 if (!status
.isNull()) {
216 descriptionFound
= true;
217 } else if (!childNode
.isComment()) {
218 return QString("Unknown object element");
222 childNode
= childNode
.nextSibling();
225 // Sort all fields according to size
226 qStableSort(info
->fields
.begin(), info
->fields
.end(), fieldTypeLessThan
);
228 // Make sure that required elements were found
230 return QString("Object::field element is missing");
234 return QString("Object::access element is missing");
238 return QString("Object::telemetrygcs element is missing");
241 if (!telFlightFound
) {
242 return QString("Object::telemetryflight element is missing");
246 return QString("Object::logging element is missing");
249 // TODO: Make into error once all objects updated
250 if (!descriptionFound
) {
251 return QString("Object::description element is missing");
258 objInfo
.append(info
);
261 node
= node
.nextSibling();
264 all_units
.removeDuplicates();
265 // Done, return null string
270 * Calculate the unique object ID based on the object information.
271 * The ID will change if the object definition changes, this is intentional
272 * and is used to avoid connecting objects with incompatible configurations.
273 * The LSB is set to zero and is reserved for metadata
275 void UAVObjectParser::calculateID(ObjectInfo
*info
)
278 quint32 hash
= updateHash(info
->name
, 0);
280 // Hash object attributes
281 hash
= updateHash(info
->isSettings
, hash
);
282 hash
= updateHash(info
->isSingleInst
, hash
);
283 // Hash field information
284 for (int n
= 0; n
< info
->fields
.length(); ++n
) {
285 hash
= updateHash(info
->fields
[n
]->name
, hash
);
286 hash
= updateHash(info
->fields
[n
]->numElements
, hash
);
287 hash
= updateHash(info
->fields
[n
]->type
, hash
);
288 if (info
->fields
[n
]->type
== FIELDTYPE_ENUM
) {
289 QStringList options
= info
->fields
[n
]->options
;
290 for (int m
= 0; m
< options
.length(); m
++) {
291 hash
= updateHash(options
[m
], hash
);
296 info
->id
= hash
& 0xFFFFFFFE;
300 * Shift-Add-XOR hash implementation. LSB is set to zero, it is reserved
301 * for the ID of the metaobject.
303 * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx
305 quint32
UAVObjectParser::updateHash(quint32 value
, quint32 hash
)
307 return hash
^ ((hash
<< 5) + (hash
>> 2) + value
);
311 * Update the hash given a string
313 quint32
UAVObjectParser::updateHash(QString
& value
, quint32 hash
)
315 QByteArray bytes
= value
.toLatin1();
316 quint32 hashout
= hash
;
318 for (int n
= 0; n
< bytes
.length(); ++n
) {
319 hashout
= updateHash(bytes
[n
], hashout
);
326 * Process the metadata part of the XML
328 QString
UAVObjectParser::processObjectMetadata(QDomNode
& childNode
, UpdateMode
*mode
, int *period
, bool *acked
)
330 // Get updatemode attribute
331 QDomNamedNodeMap elemAttributes
= childNode
.attributes();
332 QDomNode elemAttr
= elemAttributes
.namedItem("updatemode");
334 if (elemAttr
.isNull()) {
335 return QString("Object:telemetrygcs:updatemode attribute is missing");
338 int index
= updateModeStrXML
.indexOf(elemAttr
.nodeValue());
341 return QString("Object:telemetrygcs:updatemode attribute value is invalid");
344 *mode
= (UpdateMode
)index
;
346 // Get period attribute
347 elemAttr
= elemAttributes
.namedItem("period");
348 if (elemAttr
.isNull()) {
349 return QString("Object:telemetrygcs:period attribute is missing");
352 *period
= elemAttr
.nodeValue().toInt();
355 // Get acked attribute (only if acked parameter is not null, not applicable for logging metadata)
357 elemAttr
= elemAttributes
.namedItem("acked");
358 if (elemAttr
.isNull()) {
359 return QString("Object:telemetrygcs:acked attribute is missing");
362 if (elemAttr
.nodeValue().compare(QString("true")) == 0) {
364 } else if (elemAttr
.nodeValue().compare(QString("false")) == 0) {
367 return QString("Object:telemetrygcs:acked attribute value is invalid");
375 * Process the object access tag of the XML
377 QString
UAVObjectParser::processObjectAccess(QDomNode
& childNode
, ObjectInfo
*info
)
380 QDomNamedNodeMap elemAttributes
= childNode
.attributes();
381 QDomNode elemAttr
= elemAttributes
.namedItem("gcs");
383 if (elemAttr
.isNull()) {
384 return QString("Object:access:gcs attribute is missing");
387 int index
= accessModeStrXML
.indexOf(elemAttr
.nodeValue());
389 info
->gcsAccess
= (AccessMode
)index
;
391 return QString("Object:access:gcs attribute value is invalid");
394 // Get flight attribute
395 elemAttr
= elemAttributes
.namedItem("flight");
396 if (elemAttr
.isNull()) {
397 return QString("Object:access:flight attribute is missing");
400 index
= accessModeStrXML
.indexOf(elemAttr
.nodeValue());
402 info
->flightAccess
= (AccessMode
)index
;
404 return QString("Object:access:flight attribute value is invalid");
412 * Process the object fields of the XML
414 QString
UAVObjectParser::processObjectFields(QDomNode
& childNode
, ObjectInfo
*info
)
416 bool isClone
= false;
418 FieldInfo
*field
= new FieldInfo
;
419 // Get name attribute
420 QDomNamedNodeMap elemAttributes
= childNode
.attributes();
421 QDomNode elemAttr
= elemAttributes
.namedItem("name");
423 if (elemAttr
.isNull()) {
424 return QString("Object:field:name attribute is missing");
426 QString name
= elemAttr
.nodeValue();
428 // Check to see is this field is a clone of another
429 // field that has already been declared
430 elemAttr
= elemAttributes
.namedItem("cloneof");
431 if (!elemAttr
.isNull()) {
432 QString parentName
= elemAttr
.nodeValue().section('.', 1);
434 ObjectInfo
*parentObject
;
436 if (parentName
.isEmpty()) {
437 parentName
= elemAttr
.nodeValue();
440 QString objName
= elemAttr
.nodeValue().section('.', 0, 0);
441 parentObject
= getObjectByName(objName
);
443 return QString("Object:field:cloneof parent object unknown");
447 if (!parentName
.isEmpty()) {
448 foreach(FieldInfo
* parent
, parentObject
->fields
) {
449 if (parent
->name
== parentName
) {
450 // clone from this parent
451 *field
= *parent
; // safe shallow copy, no ptrs in struct
452 field
->name
= name
; // set our name
453 // Done, but allow certain overrides
454 field
->parentObjectName
= parentObject
->name
;
455 field
->parentFieldName
= parent
->name
;
463 return QString("Object:field:cloneof parent unknown");
466 return QString("Object:field:cloneof attribute is empty");
469 // this field is not a clone, so remember its name
473 // Get description attribute if any
474 elemAttr
= elemAttributes
.namedItem("description");
475 if (!elemAttr
.isNull()) {
476 field
->description
= elemAttr
.nodeValue();
478 // Look for a child description node
479 QDomNode node
= childNode
.firstChildElement("description");
480 if (!node
.isNull()) {
481 QDomNode description
= node
.firstChild();
482 if (!description
.isNull()) {
483 field
->description
= description
.nodeValue();
488 // Get units attribute
489 elemAttr
= elemAttributes
.namedItem("units");
490 if (elemAttr
.isNull()) {
492 return QString("Object:field:units attribute is missing");
496 return QString("Object:field:units attribute is not allowed for cloned fields");
498 field
->units
= elemAttr
.nodeValue();
499 all_units
<< field
->units
;
501 // Get type attribute
502 elemAttr
= elemAttributes
.namedItem("type");
503 if (elemAttr
.isNull()) {
505 return QString("Object:field:type attribute is missing");
509 return QString("Object:field:type attribute is not allowed for cloned fields");
511 int index
= fieldTypeStrXML
.indexOf(elemAttr
.nodeValue());
513 field
->type
= (FieldType
)index
;
514 field
->numBytes
= fieldTypeNumBytes
[index
];
516 return QString("Object:field:type attribute value is invalid");
520 // Get numelements or elementnames attribute
522 field
->numElements
= 0;
524 // Look for element names as an attribute first
525 elemAttr
= elemAttributes
.namedItem("elementnames");
526 if (!elemAttr
.isNull()) {
528 return QString("Object:field:elementnames attribute is not allowed for cloned fields");
531 QStringList names
= elemAttr
.nodeValue().split(",", QString::SkipEmptyParts
);
532 for (int n
= 0; n
< names
.length(); ++n
) {
533 names
[n
] = names
[n
].trimmed();
536 field
->elementNames
= names
;
537 field
->numElements
= names
.length();
538 field
->defaultElementNames
= false;
540 // Look for a list of child elementname nodes
541 QDomNode listNode
= childNode
.firstChildElement("elementnames");
542 if (!listNode
.isNull()) {
544 return QString("Object:field:elementnames element is not allowed for cloned fields");
546 for (QDomElement node
= listNode
.firstChildElement("elementname");
547 !node
.isNull(); node
= node
.nextSiblingElement("elementname")) {
548 QDomNode name
= node
.firstChild();
549 if (!name
.isNull() && name
.isText() && !name
.nodeValue().isEmpty()) {
550 field
->elementNames
.append(name
.nodeValue());
553 field
->numElements
= field
->elementNames
.length();
554 field
->defaultElementNames
= false;
557 // If no element names were found, then fall back to looking
558 // for the number of elements in the 'elements' attribute
559 if (field
->numElements
== 0) {
560 elemAttr
= elemAttributes
.namedItem("elements");
561 if (elemAttr
.isNull()) {
562 return QString("Object:field:elements and Object:field:elementnames attribute/element is missing");
565 return QString("Object:field:elements attribute is not allowed for cloned fields");
567 field
->numElements
= elemAttr
.nodeValue().toInt();
568 for (int n
= 0; n
< field
->numElements
; ++n
) {
569 field
->elementNames
.append(QString("%1").arg(n
));
572 field
->defaultElementNames
= true;
575 // Get options attribute or child elements (only if an enum type)
576 // We allow "options" attribute/element for cloned fields also, but they work slightly different here -
577 // they set limits on parent options.
579 if (field
->type
== FIELDTYPE_ENUM
) {
580 // Look for options attribute
582 elemAttr
= elemAttributes
.namedItem("options");
583 if (!elemAttr
.isNull()) {
584 options
= elemAttr
.nodeValue().split(",", QString::SkipEmptyParts
);
585 for (int n
= 0; n
< options
.length(); ++n
) {
586 options
[n
] = options
[n
].trimmed();
589 // Look for a list of child 'option' nodes
590 QDomNode listNode
= childNode
.firstChildElement("options");
591 if (!listNode
.isNull()) {
592 for (QDomElement node
= listNode
.firstChildElement("option");
593 !node
.isNull(); node
= node
.nextSiblingElement("option")) {
594 QDomNode name
= node
.firstChild();
595 if (!name
.isNull() && name
.isText() && !name
.nodeValue().isEmpty()) {
596 options
.append(name
.nodeValue());
603 if (!options
.isEmpty()) {
604 // Verify options subset and build limits value from it.
605 foreach(const QString
&option
, options
) {
606 if (!field
->options
.contains(option
)) {
607 return QString("Object:field:options is not a subset of parent options");
610 field
->limitValues
= QString("%EQ:") + options
.join(':');
611 qDebug() << "Created field->limitValues: " << field
->limitValues
;
614 field
->numOptions
= options
.size();
615 field
->options
= options
;
618 if (field
->numOptions
== 0) {
619 return QString("Object:field:options attribute/element is missing");
623 // Get the default value attribute (required for settings objects, optional for the rest)
624 elemAttr
= elemAttributes
.namedItem("defaultvalue");
625 if (!elemAttr
.isNull()) {
626 QStringList defaults
= elemAttr
.nodeValue().split(",", QString::SkipEmptyParts
);
627 for (int n
= 0; n
< defaults
.length(); ++n
) {
628 defaults
[n
] = defaults
[n
].trimmed();
631 if (defaults
.length() != field
->numElements
) {
632 if (defaults
.length() != 1) {
633 return QString("Object:field:incorrect number of default values");
636 /*support legacy single default for multiple elements
637 We should really issue a warning*/
638 for (int ct
= 1; ct
< field
->numElements
; ct
++) {
639 defaults
.append(defaults
[0]);
642 field
->defaultValues
= defaults
;
644 if (field
->defaultValues
.isEmpty() && info
->isSettings
) {
645 return QString("Object:field:defaultvalue attribute is missing (required for settings objects)");
649 elemAttr
= elemAttributes
.namedItem("limits");
650 if (elemAttr
.isNull()) {
651 field
->limitValues
= QString();
653 field
->limitValues
= elemAttr
.nodeValue();
655 // Add field to object
656 info
->fields
.append(field
);
662 * Process the object attributes from the XML
664 QString
UAVObjectParser::processObjectAttributes(QDomNode
& node
, ObjectInfo
*info
)
666 // Get name attribute
667 QDomNamedNodeMap attributes
= node
.attributes();
668 QDomNode attr
= attributes
.namedItem("name");
671 return QString("Object:name attribute is missing");
674 info
->name
= attr
.nodeValue();
675 info
->namelc
= attr
.nodeValue().toLower();
677 // Get category attribute if present
678 attr
= attributes
.namedItem("category");
679 if (!attr
.isNull()) {
680 info
->category
= attr
.nodeValue();
683 // Get singleinstance attribute
684 attr
= attributes
.namedItem("singleinstance");
686 return QString("Object:singleinstance attribute is missing");
689 if (attr
.nodeValue().compare(QString("true")) == 0) {
690 info
->isSingleInst
= true;
691 } else if (attr
.nodeValue().compare(QString("false")) == 0) {
692 info
->isSingleInst
= false;
694 return QString("Object:singleinstance attribute value is invalid");
697 // Get settings attribute
698 attr
= attributes
.namedItem("settings");
700 return QString("Object:settings attribute is missing");
703 if (attr
.nodeValue().compare(QString("true")) == 0) {
704 info
->isSettings
= true;
705 } else if (attr
.nodeValue().compare(QString("false")) == 0) {
706 info
->isSettings
= false;
708 return QString("Object:settings attribute value is invalid (true|false)");
711 // Get priority attribute
712 attr
= attributes
.namedItem("priority");
713 info
->isPriority
= false;
714 if (!attr
.isNull()) {
715 if (attr
.nodeValue().compare(QString("true")) == 0) {
716 info
->isPriority
= true;
717 } else if (attr
.nodeValue().compare(QString("false")) != 0) {
718 return QString("Object:priority attribute value is invalid (true|false)");
722 // Settings objects can only have a single instance
723 if (info
->isSettings
&& !info
->isSingleInst
) {
724 return QString("Object: Settings objects can not have multiple instances");
732 * Process the description field from the XML file
734 QString
UAVObjectParser::processObjectDescription(QDomNode
& childNode
, QString
*description
)
736 description
->append(childNode
.firstChild().nodeValue());