Files
qt5-legatus-legacy/Lts/Plugins/ParticipantManagement/Gui/MSDL/msdl/msdl_import.cpp
T
2026-08-13 18:33:10 +02:00

918 lines
32 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Importieren von MSDL-Dateien
* $Id$
* $HeadURL$
* $Author$
*/
// at first to avoid compiler errors on windows (CONST as enum value)
#include <xsd/MilitaryScenario_1.0.0.hxx>
#include <xsd/MsdlComplexTypes_1.0.0.hxx>
#include "msdl_import.h"
#include "ui_msdl_import.h"
#include "msdl_logging.h"
#include "msdl_import_show.h"
#include "msdl_mapping_dialog.h"
#include <QFileDialog>
#include <QFile>
#include <QUuid>
#include <QTextStream>
#include <QUrl>
#include <QAbstractMessageHandler>
#include <QDomDocument>
#include <QMimeData>
#include <QProcess>
#include <QtXmlPatterns/QXmlSchema>
#include <QtXmlPatterns/QXmlSchemaValidator>
#include <ngltools/ngltools_filesystem.h>
#include <Common/participant_dis_helper.h>
#include <Plugins/ParticipantManagement/ppm_data_plugin_interface.h>
#include <LtsSession/lts_session.h>
#include <LtsUtilsSettings/settings.h>
#include <LtsWidgets/lts_message_dialog.h>
#include <QDir>
namespace msdl
{
static QString gStrMSDLWorkingFolderPath()
{
return QDir::tempPath() + QLatin1String("/excon_msdl/");
}
static QString gStrMSDLWorkingFile()
{
return gStrMSDLWorkingFolderPath() + QStringLiteral("msdl_user_input.xml");
}
using descriptionType = schemas::modelID::modelIdentificationType::description_type;
using organizationsType = msdl_1::MilitaryScenarioType::Organizations_type;
using equipType = organizationsType::Equipment_type;
using equipItemType = equipType::EquipmentItem_type;
using equipOptional = organizationsType::Equipment_optional;
using equipRelationsType = equipItemType::Relations_type;
using unitsType = organizationsType::Units_type;
using unitType = unitsType::Unit_type;
using relationsType = unitType::Relations_type;
using forceRelationDataType = relationsType::ForceRelation_type::ForceRelationData_type;
using forceSidesType = msdl_1::MilitaryScenarioType::ForceSides_type;
using forceSideType = forceSidesType::ForceSide_type;
using ownerChoiceType = equipRelationsType::HoldingOrganization_type::OwnerChoice_type;
using ownerDataType = equipRelationsType::HoldingOrganization_type::OwnerData_type;
using purposeOptional = schemas::modelID::modelIdentificationType::purpose_optional;
ImportMsdl::ImportMsdl(ParticipantManagement::ParticipantEditModel *pModel, const QModelIndex &index, QWidget *parent, LtsSession::LtsSession *pSession)
: NGLDialog(parent)
, m_indexRoot(index)
, m_pModel(pModel)
, m_requestHelper(new LtsUtils::RequestHelper(this))
, m_pMsdlImportShow(nullptr)
, m_pUi(new Ui_MSDLImport)
, mLogger(msdl::getLogger("ImportMsdl"))
{
m_pUi->setupUi(this);
setWindowTitle(tr("Import Exercise Scenario"));
if (!pSession->getDtc<ParticipantManagement::DataInterface>(m_ppmDtc) || !pSession->getFtc<ParticipantManagement::DataInterface>(m_ppmFtc))
{
ERROR_LOG(mLogger) << "Fetching m_ppmDtc or m_ppmFtc failed!";
setEnabled(false);
}
else
{
m_mapTypes = m_ppmDtc->types();
/*
* Store all DIS ids of each type in 'm_mapDisStr2TypeId' to allow a mapping between
* DIS ids and types when importing units.
*
* Note: A type may have more than one DIS id.
* Note: Each type should have an assigned DIS id! Otherwise it will not be available for mapping!
*/
for (const ParticipantManagement::Type &type : qAsConst(m_mapTypes))
{
for (const auto &disId : type.disEntities())
{
QString strMSDL_DISId = ParticipantManagement::DIS::convertDISTypeInteger2MSDLString(disId);
if (m_mapMSDL_DIS_2TypeId.find(strMSDL_DISId) != m_mapMSDL_DIS_2TypeId.end())
{
WARNING_LOG(mLogger) << "DIS id \"" << strMSDL_DISId << "\" which is assigned to type id \"" << type.id() << "\" is already assigned to type id \"" << m_mapMSDL_DIS_2TypeId[strMSDL_DISId] << "\"";
continue;
}
m_mapMSDL_DIS_2TypeId[strMSDL_DISId] = type.id();
}
}
}
QDir dir(gStrMSDLWorkingFolderPath());
dir.removeRecursively();
if (!dir.mkpath(QStringLiteral(".")))
{
ERROR_LOG(mLogger) << "Could not create path \"" << gStrMSDLWorkingFolderPath() << "\" which is required to parse MSDL data";
setEnabled(false);
}
if (!NGL::copyDirContentsToDir(QStringLiteral("msdl"), gStrMSDLWorkingFolderPath(), true))
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Not possible to start MSDL import. Could not copy validation files."));
ERROR_LOG(mLogger) << "Could not copy xsd files to " << gStrMSDLWorkingFolderPath();
return;
}
connect(m_pUi->m_pushbutton_SelectFile, &QPushButton::clicked,
this, &ImportMsdl::slotOpenImportDialog);
connect(m_pUi->m_pushButton_StartImport, &QPushButton::clicked,
this, &ImportMsdl::slotStartImport);
QString strImportFile = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("MSDL"), QStringLiteral("pathSelectedImportFile"), QString()).toString();
m_pUi->m_lineedit_ImportFile->setText(strImportFile);
m_pUi->m_pushButton_StartImport->setEnabled(strImportFile.size());
}
ImportMsdl::~ImportMsdl()
{
delete m_pUi;
}
void ImportMsdl::slotOpenImportDialog()
{
QString strPathSelectedImportFolder = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("MSDL"), QStringLiteral("pathSelectedImportFolder"), QStringLiteral("/shd/CTC/TOOLS/TDS/")).toString();
QString strPathSelectedImportFile = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("MSDL"), QStringLiteral("pathSelectedImportFile"), QStringLiteral("/shd/CTC/TOOLS/TDS/")).toString();
QList<QUrl> urls;
urls << QUrl::fromLocalFile(strPathSelectedImportFile);
QFileDialog fileDialog(this, tr("Select File"), strPathSelectedImportFolder, tr("XML files (*.xml)"));
fileDialog.setSidebarUrls(urls);
fileDialog.setOptions(QFileDialog::ReadOnly);
fileDialog.setFileMode(QFileDialog::ExistingFile);
if (fileDialog.exec()) // TODO: use show(), use signals & slots !
{
strPathSelectedImportFile = fileDialog.selectedFiles().constFirst();
LtsUtils::OperatorSettings::getInstance()->setValue(QStringLiteral("MSDL"), QStringLiteral("pathSelectedImportFolder"), strPathSelectedImportFolder);
if (!strPathSelectedImportFile.size())
return;
LtsUtils::OperatorSettings::getInstance()->setValue(QStringLiteral("MSDL"), QStringLiteral("pathSelectedImportFile"), strPathSelectedImportFile);
m_pUi->m_lineedit_ImportFile->setText(strPathSelectedImportFile);
m_pUi->m_pushButton_StartImport->setEnabled(true);
}
}
void ImportMsdl::slotStartImport()
{
QString strPathSelectedImportFile = m_pUi->m_lineedit_ImportFile->text();
createWorkingCopy(strPathSelectedImportFile);
if (!schemaValidation())
return;
#ifdef LTS_DEBUG
delete m_pMsdlImportShow;
m_pMsdlImportShow = new msdl::ImportMsdlShow(nullptr, gStrMSDLWorkingFile());
m_pMsdlImportShow->show();
#endif
QFile file(gStrMSDLWorkingFile());
if (!file.open(QIODevice::ReadOnly))
{
ERROR_LOG(mLogger) << "Could not open msdl working copy: " << gStrMSDLWorkingFile();
return;
}
QDomDocument doc(QStringLiteral("mydocument"));
if (!doc.setContent(&file))
{
file.close();
return;
}
try
{
importExerciseScenario();
}
catch (std::exception &e)
{
// There is so much that could be wrong when importing or exporting MSDL files (missing tags, optional tags, ...)
LtsWidgets::LtsMessageDialog::showMessage(this, tr("MSDL import failed!"));
ERROR_LOG(mLogger) << "MSDL import failed with an exception, e.what(): " << e.what();
}
close();
}
void ImportMsdl::createWorkingCopy(const QString &strImportFile)
{
// Input File
QFile file(strImportFile);
if (!file.open(QIODevice::ReadOnly))
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Selected input file doesn't exist on filesystem. Has it been deleted?"), tr("Error"));
return;
}
// Output File
QFile fileWithNamespace(gStrMSDLWorkingFile());
if (!fileWithNamespace.open(QIODevice::WriteOnly | QIODevice::Text))
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Cannot create working copy of input file on filesystem:") + QLatin1Char('\n') + gStrMSDLWorkingFile(), tr("Error"));
return;
}
QTextStream inputTextStream(&file);
QTextStream outputTextStream(&fileWithNamespace);
QString line;
bool kommentar = false;
do
{
line = inputTextStream.readLine();
if (kommentar && !line.contains(QStringLiteral("-->")))
continue;
if (line.contains(QStringLiteral("-->")))
{
kommentar = false;
continue;
}
if (line.contains(QStringLiteral("<!--")))
{
kommentar = true;
continue;
}
if ((!line.contains(QStringLiteral("msdl:"))) &&
(!line.contains(QStringLiteral("?xml"))) &&
(!line.contains(QStringLiteral("<!"))) &&
(!line.contains(QStringLiteral("<modelID"))) &&
(!line.contains(QStringLiteral("</modelID"))) &&
(!line.contains(QStringLiteral("<?xml"))))
{
line.replace(QStringLiteral("<"), QStringLiteral("<msdl:"));
line.replace(QStringLiteral("<msdl:/"), QStringLiteral("</msdl:"));
}
if (line.contains(QStringLiteral("<msdl:MilitaryScenario")))
line = QStringLiteral("<msdl:MilitaryScenario xmlns:msdl=\"urn:sisostds:scenario:military:data:draft:msdl:1\" xmlns:modelID=\"http://www.sisostds.org/schemas/modelID\" xmlns:jc3=\"urn:int:nato:standard:mip:jc3iedm:3.1:oo:2.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:sisostds:scenario:military:data:draft:msdl:1 ./MilitaryScenario_1.0.0.xsd\">");
outputTextStream << line << "\n";
}
while (!line.isNull());
}
bool ImportMsdl::schemaValidation()
{
QString strPathToSchemaFile = NGL::findFile(NGL::FileType::ConfigFile, QStringLiteral("MilitaryScenario_1.0.0.xsd"));
QFile schemaFile(strPathToSchemaFile);
if (!schemaFile.open(QIODevice::ReadOnly))
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("MilitaryScenario_1.0.0.xsd doesn't exist on filesystem"), tr("Validation Error"));
return false;
}
QXmlSchema schema;
msdl::SchemaMessageHandler smh;
schema.setMessageHandler(&smh);
schema.load(&schemaFile, QUrl::fromLocalFile(schemaFile.fileName()));
// Erst einmal das Schema (*.xsd) ueberpruefen
if (!schema.isValid())
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Schema is invalid"));
return false;
}
QProcess xmllint_cmd;
xmllint_cmd.setProcessChannelMode(QProcess::MergedChannels);
xmllint_cmd.start(QStringLiteral("xmllint"), QStringList()
<< QStringLiteral("--noout")
<< QStringLiteral("--schema")
<< schemaFile.fileName()
<< gStrMSDLWorkingFile());
if (!xmllint_cmd.waitForFinished())
{
ERROR_LOG(mLogger) << "Validation Fault cmd";
return false;
}
#ifdef LTS_DEBUG
QByteArray validation = xmllint_cmd.readAllStandardOutput();
LtsWidgets::LtsMessageDialog::showMessage(this, QString::fromLatin1(validation), tr("Info"));
if (!validation.contains("validates"))
return false;
#endif
// Nun das Schema (*.xsd) gegen das zu importierende MilitaryScenario (*.xml) validieren
QXmlSchemaValidator validator(schema);
if (false /*|| validator.validate(QUrl::fromLocalFile(mImportFile))*/)
{
#ifdef LTS_DEBUG
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Instance document is invalid"));
#endif
return false;
}
return true;
}
ParticipantManagement::ParticipantData ImportMsdl::createParticipant(const MSDLParticipant &unit)
{
ParticipantManagement::ParticipantData p;
// Designator
p.designator(unit.m_designator.toStdString());
p.tacticalIdentifier(unit.m_tacticalIdentifier.toStdString());
// State and Equipment
p.equipmentNumber() = 0;
p.assignState() = ParticipantManagement::ParticipantAssignState::HandoutExcon;
/* Symbol respectively sidc
* We are not using the imported 2525c symbol (that would be: p.symbol(unit.m_symbolIdentifier.toStdString());)
* Instead, we use the symbol of the mapped type!
*/
p.symbol(m_mapTypes[unit.m_mappedTypeId].symbol());
p.typeId(unit.m_mappedTypeId); // Class type is internally derived from typeid, no need (and possibility) to set it here
return p;
}
void ImportMsdl::insertParticipant(const MSDLParticipant &unit, const QModelIndex &index)
{
ParticipantManagement::ParticipantData newParticipant = createParticipant(unit);
createNode(newParticipant, index, unit.m_ownHandle);
}
/*
* All parties respectively forces are display in one treewidget. Hence, they represent root nodes which means
* they are directly appended to the QModelIndex 'm_indexRoot'.
*/
void ImportMsdl::insertParty(const MSDLParty &party)
{
// Create "Force"-Node -> No symbol, no tact. identifier, no classtype
ParticipantManagement::ParticipantData newParty;
newParty.designator(party.m_forceSideName.toStdString());
createNode(newParty, m_indexRoot, party.m_ownHandle);
}
void ImportMsdl::createNode(const ParticipantManagement::ParticipantData &participant, const QModelIndex &index, const QString &handle)
{
int newRow = m_pModel->onInsertRow(index, participant);
QModelIndex newIdx = m_pModel->index(newRow, 0, index);
m_parentIndexCache[handle] = newIdx;
}
/*
* Should be called after each 'insertParticipant' since the newly added participant
* might be the parent of one of the so far parentless units.
*/
void ImportMsdl::checkParentlessUnits()
{
// Check all Units which have not been added so far because their parent is missing
bool parentFound = false;
do
{
parentFound = false;
for (auto itParentless = m_parentlessItems.begin(); itParentless != m_parentlessItems.end();)
{
const MSDLParticipant &parentlessUnit = (*itParentless);
auto itParent = m_parentIndexCache.find(parentlessUnit.m_parentHandle);
// Parent now available in tree?
if (itParent != m_parentIndexCache.end())
{
parentFound = true;
insertParticipant(parentlessUnit, (*itParent));
itParentless = m_parentlessItems.erase(itParentless);
}
else
++itParentless;
}
}
while (parentFound);
}
void ImportMsdl::importExerciseScenario()
{
importPurpose();
QVector<MSDLParty> vecParties = importParties();
for (auto &party : vecParties)
insertParty(party);
QMap<QString, MSDLParticipant> mapUnits = importParticipants();
importEquipment(mapUnits);
QVector<MSDLParticipant> vecUnitsWithValidDISTypes = resolveImportMapping(mapUnits);
if (!vecUnitsWithValidDISTypes.size())
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("There are no units or participants which can be imported."), tr("Import Warning"));
return;
}
buildImportTreeView(vecUnitsWithValidDISTypes);
if (m_setTouchedTypes.size())
{
LtsWidgets::Button button = LtsWidgets::LtsMessageDialog::showMessage(this, tr("You have applied manual DIS mappings between imported units/participants and available database types.<br/>Do you want to save this mapping to database?<br/><br/> If you choose \"No\" you will have to perform the DIS mapping again when importing the same input file at a later timepoint."), tr("Import Information"),
LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes | LtsWidgets::Button::No);
if (button == LtsWidgets::Button::Yes)
{
while (!applyDISMappingChangesToDatabase())
{
LtsWidgets::Button button = LtsWidgets::LtsMessageDialog::showMessage(this, tr("Applied DIS mapping changes could not be saved to database.<br/>Do you want to retry?"), tr("Import Warning"),
LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes | LtsWidgets::Button::No);
if (button == LtsWidgets::Button::No)
break;
}
}
}
}
void ImportMsdl::importPurpose()
{
try
{
std::unique_ptr<msdl_1::MilitaryScenarioType> p(msdl_1::MilitaryScenario(gStrMSDLWorkingFile().toStdString()));
msdl_1::MilitaryScenarioType::ScenarioID_type scIDType = p->ScenarioID();
// Purpose
QString strPurpose;
QString strDescription;
purposeOptional pO = scIDType.purpose();
if (!pO)
{
#ifdef LTS_DEBUG
LtsWidgets::LtsMessageDialog::showMessage(this, tr("No purpose in xml-file"), windowTitle());
#endif
}
else
strPurpose = QString::fromStdString(pO.get());
// Description
descriptionType dT = scIDType.description();
if (dT.empty())
{
#ifdef LTS_DEBUG
LtsWidgets::LtsMessageDialog::showMessage(this, tr("No description in xml-file"), windowTitle());
#endif
}
else
strDescription = QString::fromStdString(dT);
WARNING_LOG(mLogger) << "importPurpose: ToDo: Unused imported data (purpose: \"" << strPurpose << "\", description: \"" << strDescription << "\")";
}
catch (std::exception &e)
{
WARNING_LOG(mLogger) << "importPurpose: exception occurred: " << e.what();
}
}
QVector<MSDLParty> ImportMsdl::importParties()
{
std::unique_ptr<msdl_1::MilitaryScenarioType> p(msdl_1::MilitaryScenario(gStrMSDLWorkingFile().toStdString()));
forceSidesType foSides = p->ForceSides();
forceSidesType::ForceSide_sequence foSiSeq = foSides.ForceSide();
QVector<MSDLParty> vecForces;
vecForces.reserve(foSiSeq.size());
for (const auto &foSiTy : foSiSeq)
{
MSDLParty party; // Destination
party.m_ownHandle = QString::fromStdString(foSiTy.ObjectHandle());
party.m_forceSideName = QString::fromStdString(foSiTy.ForceSideName());
vecForces.push_back(std::move(party));
}
return vecForces;
}
QMap<QString, MSDLParticipant> ImportMsdl::importParticipants()
{
QMap<QString, MSDLParticipant> mapParticipants; // Handle to Participant
std::unique_ptr<msdl_1::MilitaryScenarioType> p(msdl_1::MilitaryScenario(gStrMSDLWorkingFile().toStdString()));
msdl_1::MilitaryScenarioType::Organizations_optional orgOpt = p->Organizations();
if (!orgOpt)
{
WARNING_LOG(mLogger) << "Optional msdl xml tag \"Organizations\" missing. There are not participants to import!";
return mapParticipants;
}
organizationsType orgType = orgOpt.get();
unitsType units = orgType.Units();
unitsType::Unit_sequence unitSeq = units.Unit();
QSet<QString> setParents; // All parents are of type class "Unit"
for (const auto &uTy : unitSeq)
{
try
{
MSDLParticipant tUnit; // Destination
// Designator
QString designator(QStringLiteral("Unknown"));
unitType::Name_optional nameOpt = uTy.Name();
if (nameOpt)
designator = QString::fromStdString(nameOpt.get());
tUnit.m_designator = designator.left(9);
tUnit.m_tacticalIdentifier = designator;
// Symbol respectively sidc
unitType::SymbolIdentifier_type symIdTy = uTy.SymbolIdentifier();
tUnit.m_symbolIdentifier = QString::fromStdString(symIdTy);
// Currently ignoring "hierarchy"
// Element 'ForceSideHandle' stellt den Bezug zur Truppe her
// Element 'CommandingSuperiorHandle' stellt den Bezug zu einer uebergeordneten Einheit her
relationsType reTyps = uTy.Relations();
relationsType::ForceRelation_type reTyp = reTyps.ForceRelation();
relationsType::ForceRelation_type::ForceRelationChoice_type foRelChTy = reTyp.ForceRelationChoice();
unitType::ObjectHandle_type obHaTy = uTy.ObjectHandle();
tUnit.m_ownHandle = QString::fromStdString(obHaTy);
// Einheit ist direkt unter der Truppe
if (foRelChTy == relationsType::ForceRelation_type::ForceRelationChoice_type::FORCE_SIDE)
{
// obHaTy ist eigener Object-Handle. "0", weil Vorgesetzter die Pooltruppe
forceRelationDataType::ForceSideHandle_optional foSiHaOp = uTy.Relations().ForceRelation().ForceRelationData().ForceSideHandle();
tUnit.m_parentForce = QString::fromStdString(foSiHaOp.get());
tUnit.m_parentHandle = QStringLiteral("0"); // 0 for "FORCE_SIDE" types since forces are always on top without parent
setParents.insert(tUnit.m_ownHandle);
DEBUG_LOG(mLogger) << "Party: handle: " << tUnit.m_ownHandle << ", parentHandle: " << tUnit.m_parentHandle << ", parent force: " << tUnit.m_parentForce;
}
else if (foRelChTy == relationsType::ForceRelation_type::ForceRelationChoice_type::UNIT)
{
// Einheit ist unter einer anderen Einheit
// Vorgesetzte Einheit ermitteln
forceRelationDataType::CommandRelation_optional cmdRelOpt = uTy.Relations().ForceRelation().ForceRelationData().CommandRelation();
forceRelationDataType::CommandRelation_type cmdRelTyp = cmdRelOpt.get();
tUnit.m_parentHandle = QString::fromStdString(cmdRelTyp.CommandingSuperiorHandle());
WARNING_LOG(mLogger) << "Participant: handle: " << tUnit.m_ownHandle.toStdString() << ", parentHandle: " << tUnit.m_parentHandle;
setParents.insert(tUnit.m_parentHandle);
}
mapParticipants[tUnit.m_ownHandle] = tUnit;
}
catch (std::exception &e)
{
ERROR_LOG(mLogger) << "Exception occured when importing unit, skipping unit! " << e.what();
}
}
// Each parent is of type class 'Unit' or 'SupportUnit'
for (auto &e : setParents)
mapParticipants[e].m_typeClassIsUnit = true;
return mapParticipants;
}
void ImportMsdl::importEquipment(QMap<QString, MSDLParticipant> &mapUnits)
{
std::unique_ptr<msdl_1::MilitaryScenarioType> p(msdl_1::MilitaryScenario(gStrMSDLWorkingFile().toStdString()));
msdl_1::MilitaryScenarioType::Organizations_optional orgOpt = p->Organizations();
if (!orgOpt)
{
WARNING_LOG(mLogger) << "Optional msdl xml tag \"Organizations\" missing. There is no equipment to import!";
return;
}
organizationsType orgType = orgOpt.get();
equipOptional equipOpt = orgType.Equipment();
if (!equipOpt)
{
WARNING_LOG(mLogger) << "Optional msdl xml tag \"Equipment\" missing: There is no equipment to import!";
return;
}
equipType eqTy = equipOpt.get();
equipType::EquipmentItem_sequence eqItSeq = eqTy.EquipmentItem();
for (const auto &eqItType : eqItSeq)
{
// Equipment handle
equipItemType::ObjectHandle_type obHaTy = eqItType.ObjectHandle();
QString equipmentHandle = QString::fromStdString(obHaTy);
// Owner (unit) handle
equipRelationsType reTyps = eqItType.Relations();
ownerDataType::UnitOwnerHandle_optional unOwHa = reTyps.HoldingOrganization().OwnerData().UnitOwnerHandle();
if (!unOwHa)
{
WARNING_LOG(mLogger) << "No owner handle found for equipment handle " << equipmentHandle << ", ignoring equipment";
continue;
}
QString ownerHandle = QString::fromStdString(unOwHa.get());
if (mapUnits.find(ownerHandle) == mapUnits.end())
{
WARNING_LOG(mLogger) << "Equipment owner handle " << ownerHandle << " for equipment handle " << equipmentHandle << " does not exist, ignoring equipment";
continue;
}
equipItemType::EquipmentSymbolModifiers_optional eqSymModifiers = eqItType.EquipmentSymbolModifiers();
if (!eqSymModifiers)
{
WARNING_LOG(mLogger) << "No EquipmentSymbolModifier found for equipment handle " << equipmentHandle << ", not importing equipment and therefore skipping associated unit";
continue;
}
// prüfen ob vorhanden bzw. 21 zeichen?
QString strDisType = QString::fromStdString(eqSymModifiers.get().UniqueDesignation());
if (strDisType.size() != 21)
{
WARNING_LOG(mLogger) << "IGNORING Equipment handle " << equipmentHandle << ": MSDL DIS string length does not equal 21 characters: " << strDisType << " (ownerHandle: " << ownerHandle << ")";
continue;
}
WARNING_LOG(mLogger) << "Equipment: Handle " << equipmentHandle << ". DIS string " << strDisType << " ownerHandle: " << ownerHandle;
mapUnits[ownerHandle].m_DISTypeMSDL = strDisType;
// Equipment name
// equipItemType::Name_optional nameOpt = eqItType.Name();
// if(nameOpt)
// QString::fromStdString(nameOpt.get());
// Equipment Symbolidentifier
// equipItemType::SymbolIdentifier_type symIdTy = eqItType.SymbolIdentifier();
// symbolIdentifier = QString::fromStdString(symIdTy);
}
}
void ImportMsdl::buildImportTreeView(QVector<MSDLParticipant> &vecUnitsWithValidDISTypes)
{
// Sort units by their designator which allows to create a treeview that has ordered elements
std::sort(vecUnitsWithValidDISTypes.begin(), vecUnitsWithValidDISTypes.end(), [](const MSDLParticipant & unit1, const MSDLParticipant & unit2)
{
return QString::localeAwareCompare(unit1.m_designator, unit2.m_designator) < 0;
});
for (const MSDLParticipant &p : vecUnitsWithValidDISTypes)
{
/*
* At this point we have to distinguish between 'UNIT' and 'FORCE_SIDE'.
* Force sides do not have a 'm_parentHandle' (they are a root node within the xml-file).
* Hence, we have to append them to a actual force which is stored in 'm_parentForce'.
* A 'UNIT' however as always a parent.
*/
auto it = (p.m_parentHandle != QStringLiteral("0")) ? m_parentIndexCache.find(p.m_parentHandle) : m_parentIndexCache.find(p.m_parentForce);
if (it == m_parentIndexCache.end())
{
m_parentlessItems[p.m_ownHandle] = p;
continue;
}
QModelIndex &parentIdx = (*it);
insertParticipant(p, parentIdx);
checkParentlessUnits();
}
for (auto &unit : m_parentlessItems)
WARNING_LOG(mLogger) << "Parent missing: Could not import unit with handle " << unit.m_ownHandle << ", missing parent: " << unit.m_parentHandle;
}
bool ImportMsdl::applyDISMappingChangesToDatabase()
{
// Apply changes (=user mapped imported unknown DIS ids to database type ids) to database
for (int32_t typeId : qAsConst(m_setTouchedTypes))
{
const ParticipantManagement::Type &touchedType = m_mapTypes.value(typeId);
m_requestHelper->addRequest([this, touchedType]()
{
return m_ppmFtc->modifyTypeRequest(touchedType, this);
});
}
return m_requestHelper->startRequests();
}
void ImportMsdl::mapParticipant2Type(MSDLParticipant &participant, qint32 mappedTypeId)
{
participant.m_mappedTypeId = mappedTypeId;
/*
* We can map non existing DIS id (=empty QString) to type ids.
* However, we cannot extend the type disEntities()-vector within the database since in this case since there is
* simply no DIS id which could be appended to the vector.
* Hence, mapping units/participants with no DIS id to a type id is only temporary and needs to be done each time
* a MSDL file is used for import.
*/
if (!participant.hasDISType())
return;
/*
* It may be possible that several participants have the same unknown DIS id.
* Prevent adding the same DIS id again and again to the same type.
*/
if (!m_mapMSDL_DIS_2TypeId.contains(participant.m_DISTypeMSDL))
{
m_mapMSDL_DIS_2TypeId[participant.m_DISTypeMSDL] = mappedTypeId;
m_mapTypes[mappedTypeId].disEntities().push_back(ParticipantManagement::DIS::convertMSDLString2Integer(participant.m_DISTypeMSDL));
m_setTouchedTypes.insert(mappedTypeId);
}
}
QVector<MSDLParticipant> ImportMsdl::resolveImportMapping(const QMap<QString, MSDLParticipant> &mapUnits)
{
QVector<MSDLParticipant> vecObjectsWithValidDISTypes;
/*
* Identify possible conflicts. Each conflict needs to be solved or ignored by the user.
*
* Note: We have to distinguish between actual participants (which will be later leaves in the treemodel) and units such as "Platoon".
* A MSDLParticipant can be both. If a MSDLUnit has no childs it is a participant otherwise an actual unit.
*
* - Participants can only be edited and appended to units.
* - Units can additionally be extended adding new participants.
*
* Hence, if the user needs to manually specify the type only the classes "Unit" and "SupportUnit" should be available for units
* to create a valid treemodel.
*/
QMap<QString, QVector<MSDLParticipant>> mapParticipantsWithUnknownDisType, mapUnitsWithUnknownDisType;
for (const MSDLParticipant &unit : mapUnits)
{
/*
* No DIS type: User has to perform a manual mapping
* DIS type available but there is no type in our database that matches: User has to perform a manual mapping
*/
if (!unit.hasDISType() || !m_mapMSDL_DIS_2TypeId.contains(unit.m_DISTypeMSDL))
{
if (unit.m_typeClassIsUnit)
mapUnitsWithUnknownDisType[unit.m_DISTypeMSDL].push_back(unit);
else
mapParticipantsWithUnknownDisType[unit.m_DISTypeMSDL].push_back(unit);
}
else
{
// A match between the imported unit (its DIS id) and one of the database types exists: Perform mapping, everything is fine.
vecObjectsWithValidDISTypes.push_back(unit);
vecObjectsWithValidDISTypes.back().m_mappedTypeId = m_mapMSDL_DIS_2TypeId[unit.m_DISTypeMSDL];
}
}
if (mapUnitsWithUnknownDisType.size())
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("<b>Units</b> exist whose DIS type is unknown.<br/>You have to solve this by manually mapping<br/> the appropriate DIS types to existing <b>unit types</b>."), tr("Import Error"));
vecObjectsWithValidDISTypes.append(showMappingDialog(mapUnitsWithUnknownDisType, true));
}
if (mapParticipantsWithUnknownDisType.size())
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("<b>Participants</b> exist whose DIS type is unknown.<br/>You have to solve this by manually mapping<br/> the appropriate DIS types to existing <b>participant types</b>."), tr("Import Error"));
vecObjectsWithValidDISTypes.append(showMappingDialog(mapParticipantsWithUnknownDisType, false));
}
return vecObjectsWithValidDISTypes;
}
QVector<MSDLParticipant> ImportMsdl::showMappingDialog(const QMap<QString, QVector<MSDLParticipant>> &mapConflictMap, bool typeClassIsUnit)
{
QVector<MSDLParticipant> vecUnitsWithValidSymbols;
// Let user solve conflicts
bool userWantsAutoSolve = false;
int32_t mappedTypeId = 0;
auto it = mapConflictMap.begin();
for (; it != mapConflictMap.end(); ++it)
{
const QString &strDisType = it.key();
const QVector<MSDLParticipant> &vecData = it.value();
QString strReason;
bool offerDatabaseEntriesWithoutDISType;
if (!strDisType.size())
{
strReason = QString(tr("No DIS type specified."));
// Imported unit/participant has no DIS type. Mapping "no DIS type" to database elements without a DIS type does not make sense
offerDatabaseEntriesWithoutDISType = false;
}
else
{
strReason = QString(tr("Specified DIS type \"%1\" is unknown.<br/>Affected %2:\nDesignator: %3\nTact. Identifier: %4")).arg(ParticipantManagement::DIS::convertMSDLString2DISString(strDisType), typeClassIsUnit ? QStringLiteral("unit") : QStringLiteral("participant"), vecData[0].m_designator, vecData[0].m_tacticalIdentifier);
offerDatabaseEntriesWithoutDISType = true;
}
QString strType = typeClassIsUnit ? tr("unit(s)") : tr("participant(s)");
strReason += tr("<br/>This applies for %1 %2. Please choose an option:").arg(QString::number(vecData.size()), strType);
msdl::MappingDialog dialog(this, typeClassIsUnit, strReason, m_ppmDtc, offerDatabaseEntriesWithoutDISType);
dialog.exec(); // TODO: use show(), use signals & slots !
if (dialog.skipAllUnmappedElements())
break;
mappedTypeId = dialog.getMappedTypeId();
if (dialog.autoMapAllUnmappedElements())
{
userWantsAutoSolve = true;
break;
}
if (dialog.mappingApplied())
{
for (auto participant : vecData)
{
mapParticipant2Type(participant, mappedTypeId);
vecUnitsWithValidSymbols.push_back(participant);
}
}
}
if (userWantsAutoSolve)
{
// Use selected symbol for all remaining conflicts
for (; it != mapConflictMap.end(); ++it)
{
const QVector<MSDLParticipant> &vecData = it.value();
for (auto participant : vecData)
{
mapParticipant2Type(participant, mappedTypeId);
vecUnitsWithValidSymbols.push_back(participant);
}
}
}
return vecUnitsWithValidSymbols;
}
}