Files
qt5-legatus-legacy/Lts/Common/CodeGen/XmlReader.cpp
2026-08-13 18:33:10 +02:00

942 lines
35 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Read Xml service files
* $Id$
* $HeadURL$
* $Author$
*/
#include "XmlReader.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QDirIterator>
#include <QFile>
#include <QDomDocument>
#include <QXmlStreamReader>
#include <ngltools/ngltools_filesystem.h>
using namespace CodeGen;
class XmlReaderException : public std::exception
{
public:
explicit XmlReaderException(const QString &msg) noexcept
: m_msg(msg.toUtf8())
{}
~XmlReaderException() = default;
virtual const char *what() const noexcept
{
return m_msg.data();
}
private:
const QByteArray m_msg;
};
XmlReader::XmlReader()
: m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.XmlReader"))
{}
bool XmlReader::readServiceFile(const QString &svcFile,
const QString &idlServiceInfoFile,
const XmlReader *parent)
{
m_externalXmlInformations.clear();
m_idlFiles.clear();
m_xmlFiles.clear();
m_usedServices.clear();
m_fullServiceName2Abbreviation.clear();
// clean up
m_svcNamespace.clear();
m_svcAbbr.clear();
m_svcProcNames.clear();
m_customRecordReplay = false;
m_exerciseDependency = ExerciseDependency::Exercise;
m_dbClassTables.clear();
m_struct2BaseClass.clear();
m_structsToGenerate.clear();
m_idlStructsToGenerate.clear();
m_tables.clear();
m_views.clear();
m_enumerations.clear();
m_functionTopics.clear();
m_externalTopicTypes.clear();
m_customFunctions.clear();
m_prio = 0;
m_svcDevMode = false;
// avoid parsing unneeded files
if (parent)
{
m_idlFiles = parent->m_idlFiles;
m_xmlFiles = parent->m_xmlFiles;
m_fullServiceName2Abbreviation = parent->m_fullServiceName2Abbreviation;
m_externalXmlInformations = parent->m_externalXmlInformations;
}
// load informations about the location of all idl files
loadIdlInfofile(idlServiceInfoFile);
// try to open the service file
QFile f(svcFile);
if (!f.exists())
{
ERROR_LOG(m_logger) << "Service file '" << svcFile << "' not found!";
return false;
}
if (!f.open(QIODevice::ReadOnly))
{
m_errorString = QLatin1String("Can not open service file '") + svcFile +
QLatin1String("' for reading: ") + f.errorString();
ERROR_LOG(m_logger) << m_errorString;
return false;
}
INFO_LOG(m_logger) << "SvcFile: " << svcFile;
// read content
QString errMsg;
int errLine = 0;
int errColumn = 0;
QDomDocument doc;
if (!doc.setContent(&f, &errMsg, &errLine, &errColumn))
{
m_errorString = QLatin1String("Error parsing service file '") + svcFile + QLatin1String("': ") +
errMsg + QLatin1String(" at ") + QString::number(errLine) +
QLatin1Char(':') + QString::number(errColumn);
ERROR_LOG(m_logger) << m_errorString;
return false;
}
// now finally read the data
// what do we expect?
// - a service name ( plus service abbreviation i.e. AES)
// - service classes to generate (dbServiceStructs)
// - database tables to generate (tables)
// - static enumerations to generate (enumerations)
// - idl structures for topicTypes (idlStructs)
// - a devMode attribute which is optional
QDomElement domRoot = doc.documentElement();
if (!domRoot.hasAttribute(QStringLiteral("namespace")))
{
m_errorString = QLatin1String("Xml file '") + svcFile +
QLatin1String("' is not a valid service file: service name/namespace is missing");
ERROR_LOG(m_logger) << m_errorString;
return false;
}
m_svcNamespace = getAttribute(domRoot, QStringLiteral("namespace"));
m_svcAbbr = getAttribute(domRoot, QStringLiteral("abbreviation")).toLower();
m_svcProcNames = getAttribute(domRoot, QStringLiteral("procNames")).split(QLatin1Char(','));
m_customRecordReplay = getAttributeBool(domRoot, QStringLiteral("customRecordReplay"), true, false);
QString devModeStr = getAttribute(domRoot, QStringLiteral("devMode"), true);
m_svcDevMode = devModeStr == QStringLiteral("1") ||
devModeStr == QStringLiteral("true");
INFO_LOG(m_logger) << "devMode: " << m_svcDevMode;
const QString groupNamesString = getAttribute(domRoot, QStringLiteral("groups"), true);
if (!groupNamesString.isEmpty())
{
m_groupNames = groupNamesString.split(QLatin1Char(','), QString::SkipEmptyParts);
INFO_LOG(m_logger) << "groups: " << groupNamesString;
}
else
m_groupNames.clear();
m_exerciseDependency = string2ExerciseDependency(getAttribute(domRoot, QStringLiteral("exerciseDependency"), true));
// exerciseIndepenent only if in core/master group AND no other groups
// core + other groups => one instance of service is svc_0
// other instances are svc_<exerciseId>
if (m_groupNames.contains(QStringLiteral("Core")) ||
m_groupNames.contains(QStringLiteral("Master")))
{
if (m_exerciseDependency != ExerciseDependency::Global &&
m_exerciseDependency != ExerciseDependency::GlobalAndExercise)
{
WARNING_LOG(m_logger) << "Service " << getServiceNamespace()
<< " is not marked as exercise independent but should be started in group Core/Master";
}
}
INFO_LOG(m_logger) << "exerciseDependency: " << (int)m_exerciseDependency;
if ( domRoot.hasAttribute(QStringLiteral("prio")))
{
m_prio = getAttribute(domRoot, QStringLiteral("prio"), 0).toInt();
}
try
{
parseDBSserviceStructs(domRoot.firstChildElement(QStringLiteral("dbServiceStructs")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing dbServiceStructs: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseDBTables(domRoot.firstChildElement(QStringLiteral("tables")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing dbtables: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseDBViews(domRoot.firstChildElement(QStringLiteral("views")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing dbviews: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseEnumerations(domRoot.firstChildElement(QStringLiteral("enumerations")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing enumerations: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseCustomDtcFunctions(domRoot.firstChildElement(QStringLiteral("customDtcFunctions")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing custom dtc functions: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseTopicTypes(domRoot.firstChildElement(QStringLiteral("topicTypes")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing idl strucs: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseFunctionTopics(domRoot.firstChildElement(QStringLiteral("functionTopics")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing function topics: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
return true;
}
QString XmlReader::getAttribute(const QDomElement &element,
const QString &attributeName,
bool bOptional)
{
if (!element.hasAttribute(attributeName))
{
if (bOptional)
return QString();
throw XmlReaderException(QLatin1String("Element ") + element.tagName() +
QLatin1String(" has no attribute ") + attributeName);
}
return element.attribute(attributeName);
}
bool XmlReader::getAttributeBool(const QDomElement &element,
const QString &attributeName,
bool bOptional, bool defaultValue)
{
QString str = getAttribute(element, attributeName, bOptional);
if (str.isEmpty())
return defaultValue;
bool bOk;
bool ret = (str.toInt(&bOk) != 0);
if (!bOk)
throw XmlReaderException(QLatin1String("Can not parse boolean value: ") + str);
return ret;
}
int XmlReader::getAttributeInt(const QDomElement &element,
const QString &attributeName,
bool bOptional, int defaultValue)
{
QString str = getAttribute(element, attributeName, bOptional);
if (str.isEmpty())
return defaultValue;
bool bOk;
int ret = str.toInt(&bOk);
if (!bOk)
{
//try from base16
ret = str.toUInt(&bOk,16);
if(!bOk)
throw XmlReaderException(QLatin1String("Can not parse int value: ") + str);
}
return ret;
}
void XmlReader::parseDBSserviceStructs(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
// service struct -> table relations
QDomElement domDbStruct = rootElement.firstChildElement(QStringLiteral("serviceStruct"));
while (!domDbStruct.isNull())
{
ServiceStructInformation ssi;
ssi.name = getAttribute(domDbStruct, QStringLiteral("name"));
ssi.primaryTable = getAttribute(domDbStruct, QStringLiteral("table"));
ssi.hasStaticContent = getAttributeBool(domDbStruct, QStringLiteral("static"), true, false);
QDomElement domFunc = domDbStruct.firstChildElement(QStringLiteral("function"));
while (!domFunc.isNull())
{
m_customFunctions += parseCustomFunction(domFunc, CustomServiceFunctionInformation::Type::Db);
domFunc = domFunc.nextSiblingElement(domFunc.tagName());
}
QDomElement domNotify = domDbStruct.firstChildElement(QStringLiteral("notification"));
while (!domNotify.isNull())
{
ssi.dbNotifications.append(parseDbNotification(domNotify));
domNotify = domNotify.nextSiblingElement(domNotify.tagName());
}
m_structsToGenerate.push_back(ssi);
domDbStruct = domDbStruct.nextSiblingElement(domDbStruct.tagName());
}
std::sort(m_structsToGenerate.begin(), m_structsToGenerate.end());
// table -> parent table relations
domDbStruct = rootElement.firstChildElement(QStringLiteral("table"));
while (!domDbStruct.isNull())
{
const QString table = getAttribute(domDbStruct, QStringLiteral("name"));
const QString parentTable = getAttribute(domDbStruct, QStringLiteral("base"), true);
m_struct2BaseClass[table] = parentTable;
domDbStruct = domDbStruct.nextSiblingElement(domDbStruct.tagName());
}
INFO_LOG(m_logger) << "Read " << m_structsToGenerate.size() << " database service struct(s)";
}
void XmlReader::parseDBTables(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
QDomElement domDbTable = rootElement.firstChildElement(QStringLiteral("table"));
while (!domDbTable.isNull())
{
QSharedPointer<TableInformation> ti(new TableInformation);
ti->name = getAttribute(domDbTable, QStringLiteral("name"));
ti->structName = QLatin1String("t_") + ti->name;
ti->comment = getAttribute(domDbTable, QStringLiteral("comment"), true);
ti->exerciseDependency = string2ExerciseDependency(getAttribute(domDbTable, QStringLiteral("exerciseDependency"), true));
QDomElement domColumn = domDbTable.firstChildElement(QStringLiteral("column"));
while (!domColumn.isNull())
{
ti->plainColumns.append(parseTableColumn(domColumn, ti->name));
domColumn = domColumn.nextSiblingElement(domColumn.tagName());
}
QDomElement domForeignKey = domDbTable.firstChildElement(QStringLiteral("foreignKey"));
while (!domForeignKey.isNull())
{
ti->foreignKeys.append(parseForeignKey(domForeignKey));
domForeignKey = domForeignKey.nextSiblingElement(domForeignKey.tagName());
}
QDomElement domConstraint = domDbTable.firstChildElement(QStringLiteral("constraint"));
while (!domConstraint.isNull())
{
ti->constraints.append(parseConstraint(domConstraint));
domConstraint = domConstraint.nextSiblingElement(domConstraint.tagName());
}
m_tables.insert(ti->name, ti);
domDbTable = domDbTable.nextSiblingElement(domDbTable.tagName());
}
INFO_LOG(m_logger) << "Read " << m_tables.size() << " database table(s)";
}
void XmlReader::parseDBViews(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
QDomElement domDbView = rootElement.firstChildElement(QStringLiteral("view"));
while (!domDbView.isNull())
{
QSharedPointer<ViewInformation> vi(new ViewInformation);
vi->name = getAttribute(domDbView, QStringLiteral("name"));
vi->structName = QLatin1String("v_") + vi->name;
vi->baseSchema = m_svcAbbr;
vi->baseTable = getAttribute(domDbView, QStringLiteral("base"));
vi->comment = getAttribute(domDbView, QStringLiteral("comment"), true);
vi->filter = getAttribute(domDbView, QStringLiteral("filter"));
vi->exerciseDependency = string2ExerciseDependency(getAttribute(domDbView, QStringLiteral("exerciseDependency"), true));
DEBUG_LOG(m_logger) << "Base table = " << vi->baseTable;
/*
QDomElement domConstraint = domDbView.firstChildElement(QStringLiteral("constraint"));
while (!domConstraint.isNull())
{
vi->constraints.append(parseConstraint(domConstraint));
domConstraint = domConstraint.nextSiblingElement(domConstraint.tagName());
}
*/
m_views.insert(vi->name, vi);
domDbView = domDbView.nextSiblingElement(domDbView.tagName());
}
INFO_LOG(m_logger) << "Read " << m_views.size() << " database view(s)";
}
void XmlReader::parseEnumerations(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
QDomElement domEnum = rootElement.firstChildElement(QStringLiteral("enumeration"));
while (!domEnum.isNull())
{
EnumInformation enumeration;
enumeration.serviceName = getServiceNamespace();
enumeration.enumName = getAttribute(domEnum, QStringLiteral("name"));
enumeration.tableName = getAttribute(domEnum, QStringLiteral("table"), true);
QDomElement enumVal = domEnum.firstChildElement(QStringLiteral("attribute"));
while (!enumVal.isNull())
{
EnumKeyValuePair evp;
evp.key = getAttribute(enumVal, QStringLiteral("key"));
evp.value = getAttributeInt(enumVal, QStringLiteral("value"));
enumeration.values.append(evp);
enumVal = enumVal.nextSiblingElement(enumVal.tagName());
}
m_enumerations.push_back(enumeration);
domEnum = domEnum.nextSiblingElement(domEnum.tagName());
}
INFO_LOG(m_logger) << "Read " << m_enumerations.size() << " enumeration(s)";
}
void XmlReader::parseCustomDtcFunctions(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
QDomElement domElement = rootElement.firstChildElement(QStringLiteral("customDtcFunction"));
while (!domElement.isNull())
{
QDomElement domFunc = domElement.firstChildElement(QStringLiteral("function"));
while (!domFunc.isNull())
{
m_customFunctions += parseCustomFunction(domFunc, CustomServiceFunctionInformation::Type::Dtc);
domFunc = domFunc.nextSiblingElement(domFunc.tagName());
}
domElement = domElement.nextSiblingElement(domElement.tagName());
}
INFO_LOG(m_logger) << "Read " << m_enumerations.size() << " customDtcFunction(s)";
}
/**
* @brief Parse idl structure (TopicType Information)
* (IDL = Interface Definition Language)
* @param rootElement
*/
void XmlReader::parseTopicTypes(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
// idl struct
QDomElement domIdlStruct = rootElement.firstChildElement(QStringLiteral("topicType"));
while (!domIdlStruct.isNull())
{
TopicTypeInformation isi;
isi.name = getAttribute(domIdlStruct, QStringLiteral("name"));
isi.baseValueType = getAttribute(domIdlStruct, QStringLiteral("baseValueType"), true);
// preload some external topics to be able to know which external services we're using
const QVector<QStringRef> sl = isi.baseValueType.splitRef(QStringLiteral("::"));
if (sl.size() == 2)
loadExternalTopicType(sl.at(0).toString(), sl.at(1).toString());
const QString tmp = getAttribute(domIdlStruct, QStringLiteral("extensibility"), true);
if (tmp.compare(QLatin1String("FINAL"), Qt::CaseInsensitive) == 0) isi.extensibility = dds::core::xtypes::ExtensibilityKind::FINAL;
if (tmp.compare(QLatin1String("MUTABLE"), Qt::CaseInsensitive) == 0) isi.extensibility = dds::core::xtypes::ExtensibilityKind::MUTABLE;
if (tmp.compare(QLatin1String("EXTENSIBLE"), Qt::CaseInsensitive) == 0) isi.extensibility = dds::core::xtypes::ExtensibilityKind::EXTENSIBLE;
isi.comment = getAttribute(domIdlStruct, QStringLiteral("comment"), true);
isi.isDataObject = getAttributeBool(domIdlStruct, QStringLiteral("isDataObject"), true, false);
isi.dtsAutogen = getAttributeBool(domIdlStruct, QStringLiteral("autogenDTS"), true, true);
isi.dtcAutogen = getAttributeBool(domIdlStruct, QStringLiteral("autogenDTC"), true, true);
isi.recordTopic = getAttributeBool(domIdlStruct, QStringLiteral("recordTopic"), true, false);
isi.routeToClient = getAttributeBool(domIdlStruct, QStringLiteral("routeToClient"), true, true);
isi.routeToServer = getAttributeBool(domIdlStruct, QStringLiteral("routeToServer"), true, false);
isi.routeInReplay = getAttributeBool(domIdlStruct, QStringLiteral("routeInReplay"), true, false);
isi.autosubscribe = getAttributeBool(domIdlStruct, QStringLiteral("autosubscribe"), true, true);
QDomElement domColumn = domIdlStruct.firstChildElement(QStringLiteral("column"));
while (!domColumn.isNull())
{
isi.columns.append(parseIdlStructColumn(domColumn));
domColumn = domColumn.nextSiblingElement(domColumn.tagName());
}
//INFO_LOG(m_logger) << "# TopicType: " << isi.name;
m_idlStructsToGenerate.insert(isi.name, isi);
domIdlStruct = domIdlStruct.nextSiblingElement(domIdlStruct.tagName());
}
INFO_LOG(m_logger) << "Read " << m_idlStructsToGenerate.size() << " idl struct(s)";
}
void XmlReader::parseFunctionTopics(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
// idl struct
QDomElement domFunctionTopic = rootElement.firstChildElement(QStringLiteral("functionTopic"));
while (!domFunctionTopic.isNull())
{
FunctionTopic ft;
ft.name = getAttribute(domFunctionTopic, QStringLiteral("name"));
ft.requestTopicType = getAttribute(domFunctionTopic, QStringLiteral("requestTopicType"));
ft.replyTopicType = getAttribute(domFunctionTopic, QStringLiteral("replyTopicType"));
ft.requestTopicName = getAttribute(domFunctionTopic, QStringLiteral("requestTopicName"));
ft.replyTopicName = getAttribute(domFunctionTopic, QStringLiteral("replyTopicName"));
ft.routeInReplay = getAttributeBool(domFunctionTopic, QStringLiteral("routeInReplay"), true, false);
m_functionTopics.append(ft);
domFunctionTopic = domFunctionTopic.nextSiblingElement(domFunctionTopic.tagName());
}
for (const FunctionTopic &ft : qAsConst(m_functionTopics))
{
const QVector<QStringRef> sl1 = ft.requestTopicType.splitRef(QStringLiteral("::"));
if (sl1.size() == 2)
loadExternalTopicType(sl1.at(0).toString(), sl1.at(1).toString());
const QVector<QStringRef> sl2 = ft.replyTopicType.splitRef(QStringLiteral("::"));
if (sl2.size() == 2)
loadExternalTopicType(sl2.at(0).toString(), sl2.at(1).toString());
}
}
ColumnInformation XmlReader::parseTableColumn(const QDomElement &column, const QString &tableName) const
{
ColumnInformation ci;
ci.columnName = getAttribute(column, QStringLiteral("name"));
ci.datatype = getAttribute(column, QStringLiteral("datatype"));
ci.defaultValue = getAttribute(column, QStringLiteral("default"), true);
ci.comment = getAttribute(column, QStringLiteral("comment"), true);
ci.isPrimaryKey = getAttributeBool(column, QStringLiteral("isPrimaryKey"), true, false);
ci.isForeignKey = getAttributeBool(column, QStringLiteral("isForeignKey"), true, false);
ci.isOptional = getAttributeBool(column, QStringLiteral("isOptional"), true, false);
const QDomElement seq = column.firstChildElement(QStringLiteral("sequence"));
if (!seq.isNull())
{
ci.sequenceStart = getAttributeInt(seq, QStringLiteral("start"), true, -1);
ci.sequenceIncrement = getAttributeInt(seq, QStringLiteral("increment"), true, -1);
ci.hasSequence = ci.sequenceStart >= 0 && ci.sequenceIncrement > 0;
}
if (ci.columnName.toLower() != ci.columnName)
WARNING_LOG(m_logger) << "Column " << ci.columnName << " in Table "
<< tableName << " has has upper-case chars which is not allowed!";
return ci;
}
IdlColumnInformation XmlReader::parseIdlStructColumn(const QDomElement &column) const
{
IdlColumnInformation ci;
ci.name = getAttribute(column, QStringLiteral("name"));
ci.type = getAttribute(column, QStringLiteral("idlType"));
ci.comment = getAttribute(column, QStringLiteral("comment"), true);
ci.isKey = getAttributeBool(column, QStringLiteral("isKey"), true, false);
ci.isOptional = getAttributeBool(column, QStringLiteral("isOptional"), true, false);
ci.isTranslatable = getAttributeBool(column, QStringLiteral("isTranslatable"), true, false);
return ci;
}
QSharedPointer<ForeignKeyInformation> XmlReader::parseForeignKey(const QDomElement &foreignKey) const
{
QSharedPointer<ForeignKeyInformation> fk(new ForeignKeyInformation);
fk->name = getAttribute(foreignKey, QStringLiteral("name"));
// Default-Value of isOnDeleteCascade => true
const QString deleteAction = getAttribute(foreignKey, QStringLiteral("referential_integrity"), true);
if (deleteAction == QLatin1String("DeleteNoAction"))
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::NoAction;
else if (deleteAction == QLatin1String("DeleteCascade"))
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Cascade;
else if (deleteAction == QLatin1String("DeleteRestrict"))
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Restrict;
else if (deleteAction == QLatin1String("DeleteSetNull"))
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::SetNull;
else if (deleteAction == QLatin1String("DeleteSetDefault"))
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::SetDefault;
else
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Cascade; // default
fk->source = parseForeignKeyEndpoint(fk->name, foreignKey.firstChildElement(QStringLiteral("source")));
fk->destination = parseForeignKeyEndpoint(fk->name, foreignKey.firstChildElement(QStringLiteral("destination")));
return fk;
}
ForeignKeyInformation::Endpoint XmlReader::parseForeignKeyEndpoint(const QString &foreignKeyName,
const QDomElement &endpoint) const
{
if (endpoint.isNull())
throw XmlReaderException(QLatin1String("No endpoint for foreign key ") + foreignKeyName);
ForeignKeyInformation::Endpoint fkEndpoint;
fkEndpoint.service = getAttribute(endpoint, QStringLiteral("service"), true);
fkEndpoint.serviceAbbr= getAttribute(endpoint, QStringLiteral("serviceAbbr"), true);
fkEndpoint.schema = getAttribute(endpoint, QStringLiteral("schema"), true);
fkEndpoint.tableName = getAttribute(endpoint, QStringLiteral("table"));
fkEndpoint.columnName = getAttribute(endpoint, QStringLiteral("column"));
return fkEndpoint;
}
ConstraintInformation XmlReader::parseConstraint(const QDomElement &constraint) const
{
ConstraintInformation ret;
ret.name = getAttribute(constraint, QStringLiteral("name"), true);
const QString type = getAttribute(constraint, QStringLiteral("type")).toLower();
if (type == QLatin1String("unique"))
ret.type = ConstraintInformation::Type::unique;
else if (type == QLatin1String("index"))
ret.type = ConstraintInformation::Type::index;
QDomElement domColumn = constraint.firstChildElement(QStringLiteral("column"));
if (domColumn.isNull())
throw XmlReaderException(QLatin1String("No column for constraint") + ret.name);
while (!domColumn.isNull())
{
ret.affectedColumns += getAttribute(domColumn, QStringLiteral("name"));
domColumn = domColumn.nextSiblingElement(domColumn.tagName());
}
return ret;
}
CustomServiceFunctionInformation XmlReader::parseCustomFunction(const QDomElement &customFunc,
CustomServiceFunctionInformation::Type funcType) const
{
CustomServiceFunctionInformation ret;
ret.methodName = getAttribute(customFunc, QStringLiteral("name"));
ret.returnValue = getAttribute(customFunc, QStringLiteral("returns"), true);
ret.isConst = getAttributeBool(customFunc, QStringLiteral("isConst"), true, false);
if (ret.returnValue.isEmpty())
ret.returnValue = QStringLiteral("bool");
QMap<int, QPair<QString, QString>> paramHelper;
QDomElement domParam = customFunc.firstChildElement(QStringLiteral("parameter"));
while (!domParam.isNull())
{
int pos = getAttributeInt(domParam, QStringLiteral("pos"));
if (paramHelper.contains(pos))
throw XmlReaderException(QLatin1String("Doubled position entry for custom function ") + ret.methodName);
QString paramName = getAttribute(domParam, QStringLiteral("name"));
const QString paramType = getAttribute(domParam, QStringLiteral("type"));
const QString paramDefault = getAttribute(domParam, QStringLiteral("default"), true);
if (!paramDefault.isEmpty())
paramName.append(QLatin1String(" = ") + paramDefault);
paramHelper[pos] = qMakePair(paramName, paramType);
domParam = domParam.nextSiblingElement(domParam.tagName());
}
ret.paramNames.reserve(paramHelper.size());
ret.paramTypes.reserve(paramHelper.size());
ret.type = funcType;
QStringList signature;
for (auto it = paramHelper.constBegin(); it != paramHelper.constEnd(); ++it)
{
ret.paramNames += it.value().first;
ret.paramTypes += it.value().second;
QString tmp = it.value().second;
if (!it.value().second.endsWith(QLatin1Char('&')))
tmp += QLatin1Char(' ');
tmp += it.value().first;
signature += tmp;
}
ret.signature += signature.join(QStringLiteral(", "));
return ret;
}
DbNotifcationInformation XmlReader::parseDbNotification(const QDomElement &customFunc) const
{
DbNotifcationInformation ret(parseCustomFunction(customFunc, CustomServiceFunctionInformation::Type::DbNotify));
const QString attribute(getAttribute(customFunc, QStringLiteral("notificationTypes")).toLower());
const QStringList nfTypes = attribute.split(QLatin1Char(','));
static const QMap<QString, DbNotifcationInformation::NotificationType> s_nfTypeMap =
{
{ QLatin1String("beforeinsert"), DbNotifcationInformation::NotificationType::BeforeInsert },
{ QLatin1String("afterinsert"), DbNotifcationInformation::NotificationType::AfterInsert },
{ QLatin1String("beforeupdate"), DbNotifcationInformation::NotificationType::BeforeUpdate },
{ QLatin1String("afterupdate"), DbNotifcationInformation::NotificationType::AfterUpdate },
{ QLatin1String("beforedelete"), DbNotifcationInformation::NotificationType::BeforeDelete },
{ QLatin1String("afterdelete"), DbNotifcationInformation::NotificationType::AfterDelete }
};
for (const QString &nfType : nfTypes)
{
const auto it = s_nfTypeMap.find(nfType);
if (it == s_nfTypeMap.end())
throw XmlReaderException(QStringLiteral("Can not find notification type %2 in type map for function %1").arg(ret.methodName, nfType));
ret.notificationType += it.value();
}
if (ret.notificationType.isEmpty())
throw XmlReaderException(QStringLiteral("Notification function '%1' without a valid notification type").arg(ret.methodName));
if (ret.paramTypes.size() != 1)
throw XmlReaderException(QStringLiteral("Notification function '%1' must have exactly one parameter").arg(ret.methodName));
return ret;
}
TopicTypeInformation XmlReader::loadExternalTopicType(const QString &externalServiceName, const QString &topicTypeName)
{
DEBUG_LOG(m_logger) << "externalServiceName: " << externalServiceName << ", topicTypeName: " << topicTypeName;
if (!loadExternalService(externalServiceName))
{
WARNING_LOG(m_logger) << "Could not find service " << externalServiceName
<< " referenced by external topic " << topicTypeName;
return TopicTypeInformation();
}
auto extXmlReader = m_externalXmlInformations.value(externalServiceName);
if (!extXmlReader)
{
ERROR_LOG(m_logger) << "Could not find service xml file for service '" << externalServiceName << "'";
return TopicTypeInformation();
}
TopicTypeInformation tti = extXmlReader->getIdlStructInformation().value(topicTypeName);
if (tti.isValid())
{
tti.name = externalServiceName + QLatin1String("::") + tti.name;
m_externalTopicTypes += tti;
m_usedServices[externalServiceName] = true;
return tti;
}
WARNING_LOG(m_logger) << "Could not find topic " << topicTypeName
<< " in external service definition for service "
<< externalServiceName;
return TopicTypeInformation();
}
EnumInformation XmlReader::getExternalEnumeration(const QString &externalServiceName, const QString &enumName, bool verbose)
{
DEBUG_LOG(m_logger) << "externalServiceName: " << externalServiceName << ", topicTypeName: " << enumName;
if (!loadExternalService(externalServiceName))
{
WARNING_LOG(m_logger) << "Could not find service '" << externalServiceName << "'"
<< " referenced by external enumeration " << enumName;
return EnumInformation();
}
auto extXmlReader = m_externalXmlInformations.value(externalServiceName);
if (!extXmlReader)
{
ERROR_LOG(m_logger) << "Could not find service xml file for service '" << externalServiceName << "'";
return EnumInformation();
}
const QVector<EnumInformation> enums = extXmlReader->getEnumerations();
for (const EnumInformation &ei : enums)
{
if (ei.enumName == enumName)
{
m_usedServices[externalServiceName] = true;
return ei;
}
}
if (verbose)
{
WARNING_LOG(m_logger) << "Could not find enumeration " << enumName
<< " in external service definition for '"
<< externalServiceName << "'";
}
return EnumInformation();
}
bool XmlReader::loadExternalService(const QString &serviceName)
{
if (serviceName == getServiceNamespace())
return false;
if (m_externalXmlInformations.contains(serviceName))
return true;
QString fileName = xmlFileForService(serviceName);
if (fileName.isEmpty())
{
const QVector<QString> dirs = NGL::directories(NGL::FileType::ConfigFile);
for (const QString &dirname : dirs)
{
QDirIterator dirIt(dirname + QStringLiteral("/xml"), QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
while (dirIt.hasNext())
{
dirIt.next();
if (!dirIt.fileName().endsWith(QLatin1String(".xml")))
continue;
fileName = dirIt.filePath();
const QString svcNamespace = serviceNamespaceFromFile(fileName);
if (svcNamespace == serviceName)
break;
}
}
}
if (fileName.isEmpty())
return false;
DEBUG_LOG(m_logger) << "xml file for " << serviceName << ": " << fileName;
auto extXmlReader = QSharedPointer<XmlReader>(new XmlReader);
if (!extXmlReader->readServiceFile(fileName, QString(), this))
{
WARNING_LOG(m_logger) << "Error reading service file " << fileName
<< " for service " << serviceName;
return false;
}
m_externalXmlInformations[serviceName] = extXmlReader;
if (!m_usedServices.contains(serviceName))
m_usedServices[serviceName] = false;
return true;
}
void XmlReader::loadIdlInfofile(const QString &idlServiceInfoFile)
{
if (idlServiceInfoFile.isEmpty())
return;
QFile f(idlServiceInfoFile);
if (!f.open(QIODevice::ReadOnly))
{
WARNING_LOG(m_logger) << "Could not read idl service info file " << idlServiceInfoFile;
return;
}
const QList<QByteArray> files = f.readAll().split('\n');
for (const QByteArray &filePath : files)
{
// comment
if (filePath.startsWith("--") || filePath.trimmed().isEmpty())
continue;
const QString fileName = QString::fromLocal8Bit(filePath.trimmed());
const QVector<QStringRef> parts = fileName.splitRef(QLatin1Char('/'));
getServiceNameInfoFromFilePath(parts); // what/ever/ServiceFooBar/sfb_service.xml -> ServiceFooBar <-> sfb
const auto fn = parts.last().trimmed();
if (fn.endsWith(QLatin1String(".xml")))
{
const auto svcAbbreveation = fn.split(QLatin1Char('.')).at(0).split(QLatin1Char('_')).at(0);
m_xmlFiles[svcAbbreveation.toString()] = fileName;
DEBUG_LOG(m_logger) << "xml: " << svcAbbreveation.toString() << " = " << fileName;
}
else
{
const auto svcAbbreveation = fn.split(QLatin1Char('.')).at(0).toString().toLower();
if (svcAbbreveation.endsWith(QLatin1String("_enums")))
continue;
m_idlFiles[svcAbbreveation] = fileName;
DEBUG_LOG(m_logger) << "idl: " << svcAbbreveation << " = " << fileName;
}
}
}
QString XmlReader::getIdlFileForService(const QMap<QString, QString> &data,
const QString &serviceName) const
{
const QString serviceAbbr = serviceNameAbbreviation(serviceName);
return data.value(serviceAbbr);
}
QString XmlReader::serviceNameAbbreviation(const QString &serviceName) const
{
return m_fullServiceName2Abbreviation.value(serviceName);
}
void XmlReader::getServiceNameInfoFromFilePath(const QVector<QStringRef> &parts)
{
if (parts.size() < 2)
return;
// foo_service.xml -> foo
const QString abbreviation = parts.last().split(QLatin1Char('.')).first().split(QLatin1Char('_')).first().toString();
const QString serviceName = parts.at(parts.size() - 2).toString();
DEBUG_LOG(m_logger) << "serviceName: " << serviceName << " = " << abbreviation;
m_fullServiceName2Abbreviation[serviceName] = abbreviation;
}
ExerciseDependency XmlReader::string2ExerciseDependency(const QString &str)
{
if (str.isEmpty())
return ExerciseDependency::Undefined;
const QStringList sd = str.toLower().split(QLatin1Char(','));
int ret = ExerciseDependency::Undefined;
for (const QString &s : sd)
{
if (s == QLatin1String("global"))
ret |= ExerciseDependency::Global;
else if (s == QLatin1String("exercise"))
ret |= ExerciseDependency::Exercise;
else if (s == QLatin1String("globalandexercise"))
ret |= ExerciseDependency::GlobalAndExercise;
}
return static_cast<ExerciseDependency>(ret);
}
QString XmlReader::serviceNamespaceFromFile(const QString &fn)
{
QFile f(fn);
if (!f.open(QIODevice::ReadOnly))
return QString();
QXmlStreamReader sr(&f);
while (!sr.atEnd())
{
sr.readNext();
if (sr.isStartElement() && sr.name() == QLatin1String("service"))
return (sr.attributes().value(QLatin1String("namespace"))).toString();
}
return QString();
}