1
0
mirror of https://bitbucket.org/librepilot/librepilot.git synced 2024-11-29 07:24:13 +01:00
LibrePilot/ground/uavobjgenerator/uavobjectparser.cpp
Fredrik Arvidsson b73cde1787 OP-642 Changed from using an xml element for category to have it defined
as an attribute on the uav object definition. This attribute is
optional. If absent no category (empty string) is assigned to the uav
object type.
Added category attribute to a small selection of uavo definition files
to test parsing.
2012-06-20 22:23:06 +02:00

550 lines
18 KiB
C++

/**
******************************************************************************
*
* @file uavobjectparser.cpp
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
* @brief Parses XML files and extracts object information.
*
* @see The GNU Public License (GPL) Version 3
*
*****************************************************************************/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "uavobjectparser.h"
/**
* Constructor
*/
UAVObjectParser::UAVObjectParser()
{
fieldTypeStrXML << "int8" << "int16" << "int32" << "uint8"
<< "uint16" << "uint32" <<"float" << "enum";
updateModeStrXML << "periodic" << "onchange" << "throttled" << "manual";
accessModeStr << "ACCESS_READWRITE" << "ACCESS_READONLY";
fieldTypeNumBytes << int(1) << int(2) << int(4) <<
int(1) << int(2) << int(4) <<
int(4) << int(1);
accessModeStrXML << "readwrite" << "readonly";
}
/**
* Get number of objects
*/
int UAVObjectParser::getNumObjects()
{
return objInfo.length();
}
/**
* Get the detailed object information
*/
QList<ObjectInfo*> UAVObjectParser::getObjectInfo()
{
return objInfo;
}
ObjectInfo* UAVObjectParser::getObjectByIndex(int objIndex)
{
return objInfo[objIndex];
}
/**
* Get the name of the object
*/
QString UAVObjectParser::getObjectName(int objIndex)
{
ObjectInfo* info = objInfo[objIndex];
if (info == NULL)
return QString();
return info->name;
}
/**
* Get the ID of the object
*/
quint32 UAVObjectParser::getObjectID(int objIndex)
{
ObjectInfo* info = objInfo[objIndex];
if (info == NULL)
return 0;
return info->id;
}
/**
* Get the number of bytes in the data fields of this object
*/
int UAVObjectParser::getNumBytes(int objIndex)
{
ObjectInfo* info = objInfo[objIndex];
if (info == NULL)
{
return 0;
}
else
{
int numBytes = 0;
for (int n = 0; n < info->fields.length(); ++n)
{
numBytes += info->fields[n]->numBytes * info->fields[n]->numElements;
}
return numBytes;
}
}
bool fieldTypeLessThan(const FieldInfo* f1, const FieldInfo* f2)
{
return f1->numBytes > f2->numBytes;
}
/**
* Parse supplied XML file
* @param xml The xml text
* @param filename The xml filename
* @returns Null QString() on success, error message on failure
*/
QString UAVObjectParser::parseXML(QString& xml, QString& filename)
{
// Create DOM document and parse it
QDomDocument doc("UAVObjects");
bool parsed = doc.setContent(xml);
if (!parsed) return QString("Improperly formated XML file");
// Read all objects contained in the XML file, creating an new ObjectInfo for each
QDomElement docElement = doc.documentElement();
QDomNode node = docElement.firstChild();
while ( !node.isNull() ) {
// Create new object entry
ObjectInfo* info = new ObjectInfo;
info->filename=filename;
// Process object attributes
QString status = processObjectAttributes(node, info);
if (!status.isNull())
return status;
// Process child elements (fields and metadata)
QDomNode childNode = node.firstChild();
bool fieldFound = false;
bool accessFound = false;
bool telGCSFound = false;
bool telFlightFound = false;
bool logFound = false;
bool descriptionFound = false;
while ( !childNode.isNull() ) {
// Process element depending on its type
if ( childNode.nodeName().compare(QString("field")) == 0 ) {
QString status = processObjectFields(childNode, info);
if (!status.isNull())
return status;
fieldFound = true;
}
else if ( childNode.nodeName().compare(QString("access")) == 0 ) {
QString status = processObjectAccess(childNode, info);
if (!status.isNull())
return status;
accessFound = true;
}
else if ( childNode.nodeName().compare(QString("telemetrygcs")) == 0 ) {
QString status = processObjectMetadata(childNode, &info->gcsTelemetryUpdateMode,
&info->gcsTelemetryUpdatePeriod, &info->gcsTelemetryAcked);
if (!status.isNull())
return status;
telGCSFound = true;
}
else if ( childNode.nodeName().compare(QString("telemetryflight")) == 0 ) {
QString status = processObjectMetadata(childNode, &info->flightTelemetryUpdateMode,
&info->flightTelemetryUpdatePeriod, &info->flightTelemetryAcked);
if (!status.isNull())
return status;
telFlightFound = true;
}
else if ( childNode.nodeName().compare(QString("logging")) == 0 ) {
QString status = processObjectMetadata(childNode, &info->loggingUpdateMode,
&info->loggingUpdatePeriod, NULL);
if (!status.isNull())
return status;
logFound = true;
}
else if ( childNode.nodeName().compare(QString("description")) == 0 ) {
QString status = processObjectDescription(childNode, &info->description);
if (!status.isNull())
return status;
descriptionFound = true;
}
else if (!childNode.isComment()) {
return QString("Unknown object element");
}
// Get next element
childNode = childNode.nextSibling();
}
// Sort all fields according to size
qStableSort(info->fields.begin(), info->fields.end(), fieldTypeLessThan);
// Sort all fields according to size
qStableSort(info->fields.begin(), info->fields.end(), fieldTypeLessThan);
// Make sure that required elements were found
if ( !accessFound )
return QString("Object::access element is missing");
if ( !telGCSFound )
return QString("Object::telemetrygcs element is missing");
if ( !telFlightFound )
return QString("Object::telemetryflight element is missing");
if ( !logFound )
return QString("Object::logging element is missing");
// TODO: Make into error once all objects updated
if ( !descriptionFound )
return QString("Object::description element is missing");
// Calculate ID
calculateID(info);
// Add object
objInfo.append(info);
// Get next object
node = node.nextSibling();
}
all_units.removeDuplicates();
// Done, return null string
return QString();
}
/**
* Calculate the unique object ID based on the object information.
* The ID will change if the object definition changes, this is intentional
* and is used to avoid connecting objects with incompatible configurations.
* The LSB is set to zero and is reserved for metadata
*/
void UAVObjectParser::calculateID(ObjectInfo* info)
{
// Hash object name
quint32 hash = updateHash(info->name, 0);
// Hash object attributes
hash = updateHash(info->isSettings, hash);
hash = updateHash(info->isSingleInst, hash);
// Hash field information
for (int n = 0; n < info->fields.length(); ++n) {
hash = updateHash(info->fields[n]->name, hash);
hash = updateHash(info->fields[n]->numElements, hash);
hash = updateHash(info->fields[n]->type, hash);
if(info->fields[n]->type == FIELDTYPE_ENUM) {
QStringList options = info->fields[n]->options;
for (int m = 0; m < options.length(); m++)
hash = updateHash(options[m], hash);
}
}
// Done
info->id = hash & 0xFFFFFFFE;
}
/**
* Shift-Add-XOR hash implementation. LSB is set to zero, it is reserved
* for the ID of the metaobject.
*
* http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx
*/
quint32 UAVObjectParser::updateHash(quint32 value, quint32 hash)
{
return (hash ^ ((hash<<5) + (hash>>2) + value));
}
/**
* Update the hash given a string
*/
quint32 UAVObjectParser::updateHash(QString& value, quint32 hash)
{
QByteArray bytes = value.toAscii();
quint32 hashout = hash;
for (int n = 0; n < bytes.length(); ++n)
hashout = updateHash(bytes[n], hashout);
return hashout;
}
/**
* Process the metadata part of the XML
*/
QString UAVObjectParser::processObjectMetadata(QDomNode& childNode, UpdateMode* mode, int* period, bool* acked)
{
// Get updatemode attribute
QDomNamedNodeMap elemAttributes = childNode.attributes();
QDomNode elemAttr = elemAttributes.namedItem("updatemode");
if ( elemAttr.isNull() )
return QString("Object:telemetrygcs:updatemode attribute is missing");
int index = updateModeStrXML.indexOf( elemAttr.nodeValue() );
if (index<0)
return QString("Object:telemetrygcs:updatemode attribute value is invalid");
*mode = (UpdateMode)index;
// Get period attribute
elemAttr = elemAttributes.namedItem("period");
if ( elemAttr.isNull() )
return QString("Object:telemetrygcs:period attribute is missing");
*period = elemAttr.nodeValue().toInt();
// Get acked attribute (only if acked parameter is not null, not applicable for logging metadata)
if ( acked != NULL) {
elemAttr = elemAttributes.namedItem("acked");
if ( elemAttr.isNull())
return QString("Object:telemetrygcs:acked attribute is missing");
if ( elemAttr.nodeValue().compare(QString("true")) == 0 )
*acked = true;
else if ( elemAttr.nodeValue().compare(QString("false")) == 0 )
*acked = false;
else
return QString("Object:telemetrygcs:acked attribute value is invalid");
}
// Done
return QString();
}
/**
* Process the object access tag of the XML
*/
QString UAVObjectParser::processObjectAccess(QDomNode& childNode, ObjectInfo* info)
{
// Get gcs attribute
QDomNamedNodeMap elemAttributes = childNode.attributes();
QDomNode elemAttr = elemAttributes.namedItem("gcs");
if ( elemAttr.isNull() )
return QString("Object:access:gcs attribute is missing");
int index = accessModeStrXML.indexOf( elemAttr.nodeValue() );
if (index >= 0)
info->gcsAccess = (AccessMode)index;
else
return QString("Object:access:gcs attribute value is invalid");
// Get flight attribute
elemAttr = elemAttributes.namedItem("flight");
if ( elemAttr.isNull() )
return QString("Object:access:flight attribute is missing");
index = accessModeStrXML.indexOf( elemAttr.nodeValue() );
if (index >= 0)
info->flightAccess = (AccessMode)index;
else
return QString("Object:access:flight attribute value is invalid");
// Done
return QString();
}
/**
* Process the object fields of the XML
*/
QString UAVObjectParser::processObjectFields(QDomNode& childNode, ObjectInfo* info)
{
// Create field
FieldInfo* field = new FieldInfo;
// Get name attribute
QDomNamedNodeMap elemAttributes = childNode.attributes();
QDomNode elemAttr = elemAttributes.namedItem("name");
if ( elemAttr.isNull() )
return QString("Object:field:name attribute is missing");
field->name = elemAttr.nodeValue();
// Get units attribute
elemAttr = elemAttributes.namedItem("units");
if ( elemAttr.isNull() )
return QString("Object:field:units attribute is missing");
field->units = elemAttr.nodeValue();
all_units << field->units;
// Get type attribute
elemAttr = elemAttributes.namedItem("type");
if ( elemAttr.isNull() )
return QString("Object:field:type attribute is missing");
int index = fieldTypeStrXML.indexOf(elemAttr.nodeValue());
if (index >= 0) {
field->type = (FieldType)index;
field->numBytes = fieldTypeNumBytes[index];
}
else {
return QString("Object:field:type attribute value is invalid");
}
// Get numelements or elementnames attribute
elemAttr = elemAttributes.namedItem("elementnames");
if ( !elemAttr.isNull() ) {
// Get element names
QStringList names = elemAttr.nodeValue().split(",", QString::SkipEmptyParts);
for (int n = 0; n < names.length(); ++n)
names[n] = names[n].trimmed();
field->elementNames = names;
field->numElements = names.length();
field->defaultElementNames = false;
}
else {
elemAttr = elemAttributes.namedItem("elements");
if ( elemAttr.isNull() ) {
return QString("Object:field:elements and Object:field:elementnames attribute is missing");
}
else {
field->numElements = elemAttr.nodeValue().toInt();
for (int n = 0; n < field->numElements; ++n)
field->elementNames.append(QString("%1").arg(n));
field->defaultElementNames = true;
}
}
// Get options attribute (only if an enum type)
if (field->type == FIELDTYPE_ENUM) {
// Get options attribute
elemAttr = elemAttributes.namedItem("options");
if ( elemAttr.isNull() )
return QString("Object:field:options attribute is missing");
QStringList options = elemAttr.nodeValue().split(",", QString::SkipEmptyParts);
for (int n = 0; n < options.length(); ++n)
options[n] = options[n].trimmed();
field->options = options;
}
// Get the default value attribute (required for settings objects, optional for the rest)
elemAttr = elemAttributes.namedItem("defaultvalue");
if ( elemAttr.isNull() ) {
if ( info->isSettings )
return QString("Object:field:defaultvalue attribute is missing (required for settings objects)");
field->defaultValues = QStringList();
}
else {
QStringList defaults = elemAttr.nodeValue().split(",", QString::SkipEmptyParts);
for (int n = 0; n < defaults.length(); ++n)
defaults[n] = defaults[n].trimmed();
if(defaults.length() != field->numElements) {
if(defaults.length() != 1)
return QString("Object:field:incorrect number of default values");
/*support legacy single default for multiple elements
We sould really issue a warning*/
for(int ct=1; ct< field->numElements; ct++)
defaults.append(defaults[0]);
}
field->defaultValues = defaults;
}
elemAttr = elemAttributes.namedItem("limits");
if ( elemAttr.isNull() ) {
field->limitValues=QString();
}
else{
field->limitValues=elemAttr.nodeValue();
}
// Add field to object
info->fields.append(field);
// Done
return QString();
}
/**
* Process the object attributes from the XML
*/
QString UAVObjectParser::processObjectAttributes(QDomNode& node, ObjectInfo* info)
{
// Get name attribute
QDomNamedNodeMap attributes = node.attributes();
QDomNode attr = attributes.namedItem("name");
if ( attr.isNull() )
return QString("Object:name attribute is missing");
info->name = attr.nodeValue();
info->namelc = attr.nodeValue().toLower();
// Get category attribute if present
attr = attributes.namedItem("category");
if ( !attr.isNull() )
{
info->category = attr.nodeValue();
}
// Get singleinstance attribute
attr = attributes.namedItem("singleinstance");
if ( attr.isNull() )
return QString("Object:singleinstance attribute is missing");
if ( attr.nodeValue().compare(QString("true")) == 0 )
info->isSingleInst = true;
else if ( attr.nodeValue().compare(QString("false")) == 0 )
info->isSingleInst = false;
else
return QString("Object:singleinstance attribute value is invalid");
// Get settings attribute
attr = attributes.namedItem("settings");
if ( attr.isNull() )
return QString("Object:settings attribute is missing");
if ( attr.nodeValue().compare(QString("true")) == 0 )
info->isSettings = true;
else if ( attr.nodeValue().compare(QString("false")) == 0 )
info->isSettings = false;
else
return QString("Object:settings attribute value is invalid");
// Settings objects can only have a single instance
if ( info->isSettings && !info->isSingleInst )
return QString("Object: Settings objects can not have multiple instances");
// Done
return QString();
}
/**
* Process the description field from the XML file
*/
QString UAVObjectParser::processObjectDescription(QDomNode& childNode, QString * description)
{
description->append(childNode.firstChild().nodeValue());
return QString();
}