/** * @RDE_HEADER@ * Exportieren von SAAB-Dateien * $Id$ * $HeadURL$ * $Author$ */ #include "saab_export.h" #include "ui_saab_export.h" #include "saab_xml_strings.h" #include "saab_map_type.h" #include "saab_map_party.h" #include #include #include #include #include #include #include #include #include /* * QUuid::QUuid(uint l, ushort w1, ushort w2, uchar b1, uchar b2, uchar b3, uchar b4, uchar b5, uchar b6, uchar b7, uchar b8) * Creates a UUID with the value specified by the parameters, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8. * General QUuid format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx * The vaues will be visualized in HEX-Code. * Used QUuid Format: aaaaaaaa-bbbb-xxxx-ccxx-xxxxxxxxxxdd * a = Participant ID, Parameter: l * b = Exercise ID, Parameter: w1 * c = Party ID, Parameter: b1 * d = GUID Context, Parameter: b8 * 1 = Exercise * 2 = Party * 3 = Participant * x = Unused, curretly filled with '0' * * Exercise format: xxxxxxxx-bbbb-xxxx-xxxx-xxxxxxxxxx01 * Party format: xxxxxxxx-bbbb-xxxx-ccxx-xxxxxxxxxx02 * Participant format: aaaaaaaa-bbbb.xxxx.ccxx-xxxxxxxxxx03 */ namespace saab { static const QString xmlnsValue(QStringLiteral("http://www.saab.se/training/schemas/ExerciseOrbat/1.1.1")); static const uint l = 0; static const ushort w1 = 0; static const ushort w2 = 0x88; static const uchar b1 = 0; static const uchar b2 = 0xde; static const uchar b3 = 0xad; static const uchar b4 = 0xbe; static const uchar b5 = 0xef; static const uchar b6 = 0x33; static const uchar b7 = 0x44; static const uchar b8 = 0; ExportSaab::ExportSaab(LtsSession::LtsSession *session, const std::shared_ptr &ppmDtc, const ExerciseManagement::Exercise &exercise, QWidget *parent) : LtsFramelessDialog(parent, LtsWidgets::Behaviour::Modal) , m_currentExercise(exercise) , m_ppmDtc(ppmDtc) , m_session(session) , m_configFile(nullptr) , m_ui(new Ui_SAABExport) , m_logger(getLogger("ExportSaab")) { m_ui->setupUi(this); connect(m_ui->m_pushbutton_SelectConfig, &QToolButton::clicked, this, &ExportSaab::onSelectConfigFile); connect(m_ui->m_pushbutton_SelectFile, &QToolButton::clicked, this, &ExportSaab::onSelectExportFile); connect(m_ui->m_pushButton_StartImport, &QPushButton::clicked, this, &ExportSaab::onStartExport); QString strExportFile = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("SAAB"), QStringLiteral("pathSelectedExportFile"), QString()).toString(); QString strConfigFile = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("SAAB"), QStringLiteral("pathSelectedConfigFile"), QString()).toString(); m_ui->m_lineedit_ExportFile->setText(strExportFile); m_ui->m_lineedit_ConfigFile->setText(strConfigFile); m_ui->m_pushButton_StartImport->setEnabled(strExportFile.size() && strConfigFile.size()); m_parties = m_ppmDtc->partys(); m_types = m_ppmDtc->types(); } ExportSaab::~ExportSaab() { delete m_ui; } void ExportSaab::onStartExport() { QString strExportFile = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("SAAB"), QStringLiteral("pathSelectedExportFile"), QString()).toString(); QString strConfigFile = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("SAAB"), QStringLiteral("pathSelectedConfigFile"), QString()).toString(); QFile file(strExportFile); if (m_configFile) { m_configFile->close(); delete m_configFile; m_configFile = nullptr; } m_configFile = new QFile(strConfigFile, this); if (!file.open(QIODevice::WriteOnly)) { LtsWidgets::LtsMessageDialog::showMessage(this, tr("Not possible to start export. You do not have the permission to write the selected file.")); ERROR_LOG(m_logger) << "Could not export exercise: \"" << strExportFile << "\" could not be opened for writing"; return; } if (!isValidConfigFile()) { LtsWidgets::LtsMessageDialog::showMessage(this, tr("Not possible to start export. Config file invalid.")); ERROR_LOG(m_logger) << "Could not export exercise: \"" << strConfigFile << "\" invalid"; return; } importConfig(); QXmlStreamWriter writer(&file); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeStartElement(XmlStrings::SAAB_EXERCISE_ORBAT()); writer.writeAttribute(XmlStrings::SAAB_XMLNS_XSI(), QStringLiteral("http://www.w3.org/2001/XMLSchema-instance")); writer.writeAttribute(XmlStrings::SAAB_XMLNS_XSD(), QStringLiteral("http://www.w3.org/2001/XMLSchema")); writer.writeAttribute(XmlStrings::SAAB_XMLNS(), xmlnsValue); writeExercise(&writer); writeOrbat(&writer); writeFooter(&writer); writer.writeEndElement(); writer.writeEndDocument(); file.close(); LtsWidgets::LtsMessageDialog::showMessage(this, tr("Export finished")); close(); } void ExportSaab::onSelectConfigFile() { QString strPathSelectedConfigFolder = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("SAAB"), QStringLiteral("pathSelectedConfigFolder"), QStringLiteral("/shd/CTC/TOOLS/TDS/")).toString(); QString strPathSelectedConfigFile = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("SAAB"), QStringLiteral("pathSelectedConfigFile"), QStringLiteral("/shd/CTC/TOOLS/TDS/")).toString(); QList urls; urls << QUrl::fromLocalFile(strPathSelectedConfigFile); QFileDialog fileDialog(this, tr("Select File"), strPathSelectedConfigFolder, tr("XML files (*.xml)")); fileDialog.setSidebarUrls(urls); fileDialog.setOptions(QFileDialog::ReadOnly); fileDialog.setFileMode(QFileDialog::ExistingFile); if (fileDialog.exec()) // TODO: use show(), use signals & slots ! { strPathSelectedConfigFile = fileDialog.selectedFiles().constFirst(); LtsUtils::OperatorSettings::getInstance()->setValue(QStringLiteral("SAAB"), QStringLiteral("pathSelectedConfigFolder"), strPathSelectedConfigFolder); if (!strPathSelectedConfigFile.size()) return; LtsUtils::OperatorSettings::getInstance()->setValue(QStringLiteral("SAAB"), QStringLiteral("pathSelectedConfigFile"), strPathSelectedConfigFile); m_ui->m_lineedit_ConfigFile->setText(strPathSelectedConfigFile); m_ui->m_pushButton_StartImport->setEnabled(LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("SAAB"), QStringLiteral("pathSelectedExportFile"), QString()).toString().size()); } } void ExportSaab::onSelectExportFile() { QString strPathSelectedExportFolder = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("SAAB"), QStringLiteral("pathSelectedExportFolder"), QStringLiteral("/tmp")).toString(); QString strPathSelectedExportFile = QFileDialog::getSaveFileName(this, tr("Export file"), strPathSelectedExportFolder, tr("XML files (*.xml)")); if (strPathSelectedExportFile.isEmpty()) return; strPathSelectedExportFolder = strPathSelectedExportFile.mid(0, strPathSelectedExportFile.lastIndexOf(QLatin1Char('/'))); QString suffix = QStringLiteral(".xml"); if (!strPathSelectedExportFile.endsWith(suffix)) strPathSelectedExportFile += suffix; LtsUtils::OperatorSettings::getInstance()->setValue(QStringLiteral("SAAB"), QStringLiteral("pathSelectedExportFolder"), strPathSelectedExportFolder); LtsUtils::OperatorSettings::getInstance()->setValue(QStringLiteral("SAAB"), QStringLiteral("pathSelectedExportFile"), strPathSelectedExportFile); m_ui->m_lineedit_ExportFile->setText(strPathSelectedExportFile); m_ui->m_pushButton_StartImport->setEnabled(LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("SAAB"), QStringLiteral("pathSelectedConfigFile"), QString()).toString().size()); } bool ExportSaab::isValidConfigFile() { if (!m_configFile) { ERROR_LOG(m_logger) << "Not possible to read config: No file selected"; return false; } if (!m_configFile->open(QIODevice::ReadOnly)) { ERROR_LOG(m_logger) << "Not possible to read config: Could not open file \"" << m_configFile->fileName() << "\" for reading."; return false; } using namespace XmlStrings; QXmlStreamReader reader(m_configFile); bool hasSymbols = false; bool hasEchelons = false; bool hasForces = false; while (!reader.atEnd() && !(hasSymbols && hasEchelons && hasForces)) { if (reader.readNextStartElement()) { if (reader.name() == SAAB_SYMBOLS()) hasSymbols = true; else if (reader.name() == SAAB_ECHELONS()) hasEchelons = true; else if (reader.name() == SAAB_FORCES()) hasForces = true; } } m_configFile->close(); if (!hasEchelons) ERROR_LOG(m_logger) << "Could not find " << SAAB_ECHELONS() << " in file \"" << m_configFile->fileName() << '\"'; if (!hasSymbols) ERROR_LOG(m_logger) << "Could not find " << SAAB_SYMBOLS() << " in file \"" << m_configFile->fileName() << '\"'; if (!hasForces) ERROR_LOG(m_logger) << "Could not find " << SAAB_FORCES() << " in file \"" << m_configFile->fileName() << '\"'; return hasEchelons && hasSymbols && hasForces; } void ExportSaab::importConfig() { if (!m_mapSymbols.isEmpty() || !m_mapEchelons.isEmpty()) { using namespace LtsWidgets; Button b = LtsMessageDialog::showMessage(this, tr("A config is already imported. Overwrite?"), tr("Overwrite config?"), Behaviour::Modal, Button::Yes | Button::No); if (b != Button::Yes) return; } if (!m_configFile) { ERROR_LOG(m_logger) << "Not possible to read config: No file selected"; return; } if (!m_configFile->open(QIODevice::ReadOnly)) { ERROR_LOG(m_logger) << "Not possible to read config: Could not open file \"" << m_configFile->fileName() << "\" for reading."; return; } m_mapSymbols.clear(); m_mapEchelons.clear(); m_mapTypeToSaabSymbol.clear(); m_mapPartyToForce.clear(); m_mapForces.clear(); QXmlStreamReader reader(m_configFile); using namespace XmlStrings; while (!reader.atEnd()) { if (reader.readNextStartElement()) { if (reader.name() == SAAB_SYMBOL()) { SaabSymbol newSymbol = readSymbol(&reader); m_mapSymbols.insert(newSymbol.code, newSymbol); } else if (reader.name() == SAAB_ECHELON()) { SaabEchelon newEchelon = readEchelon(&reader); m_mapEchelons.insert(newEchelon.code, newEchelon); } else if (reader.name() == SAAB_FORCE()) { SaabForce newForce = readForce(&reader); m_mapForces.insert(newForce.text, newForce.code); } } } m_configFile->close(); for (auto it = m_parties.constBegin(); it != m_parties.constEnd(); ++it) { if (m_mapForces.isEmpty()) { LtsWidgets::LtsMessageDialog::showMessage(this, tr("All Forces are mapped.")); break; } mapPartyToForce(it.value()); } } SaabSymbol ExportSaab::readSymbol(QXmlStreamReader *reader) const { SaabSymbol result; using namespace XmlStrings; while (reader->readNext() != QXmlStreamReader::EndElement && reader->name() != SAAB_SYMBOL()) { if (reader->name() == SAAB_CODE()) result.code = reader->readElementText(); else if (reader->name() == SAAB_TEXT()) result.text = reader->readElementText(); else if (reader->name() == SAAB_SYMBOL_ID()) result.symbolId = reader->readElementText(); else if (reader->name() == SAAB_MODEL_ID()) result.modelId = reader->readElementText(); else if (reader->name() == SAAB_ECHELON_ID()) result.echelonId = reader->readElementText(); else if (reader->name() == SAAB_PLAYER_TYPE_ID()) result.playerTypeId = reader->readElementText(); else if (reader->name() == SAAB_COLOR()) result.color = reader->readElementText(); } return result; } SaabEchelon ExportSaab::readEchelon(QXmlStreamReader *reader) const { SaabEchelon result; using namespace XmlStrings; while (reader->readNext() != QXmlStreamReader::EndElement && reader->name() != SAAB_ECHELON()) { if (reader->name() == SAAB_CODE()) result.code = reader->readElementText(); else if (reader->name() == SAAB_TEXT()) result.text = reader->readElementText(); else if (reader->name() == SAAB_ID()) result.id = reader->readElementText(); } return result; } SaabForce ExportSaab::readForce(QXmlStreamReader *reader) const { SaabForce result; using namespace XmlStrings; while (reader->readNext() != QXmlStreamReader::EndElement && reader->name() != SAAB_FORCE()) { if (reader->name() == SAAB_CODE()) result.code = reader->readElementText(); else if (reader->name() == SAAB_TEXT()) result.text = reader->readElementText(); } return result; } void ExportSaab::writeExercise(QXmlStreamWriter *writer) const { using namespace XmlStrings; writer->writeStartElement(SAAB_EXERCISE()); writer->writeAttribute(SAAB_ID(), QUuid(l, m_currentExercise.id(), w2, b1, b2, b3, b4, b5, b6, b7, GuidContext::GuidExercise).toString(QUuid::WithoutBraces)); writer->writeTextElement(SAAB_EXPORT_TIME(), toSaabTime(m_session->partitionTime())); writer->writeTextElement(SAAB_PLANNED_START_TIME(), toSaabTime(m_currentExercise.beginTime())); writer->writeTextElement(SAAB_PLANNED_STOP_TIME(), toSaabTime(m_currentExercise.endTime())); writer->writeTextElement(SAAB_DESIGNATION(), QString::fromStdString(m_currentExercise.name())); writer->writeTextElement(SAAB_DESCRIPTION(), QString::fromStdString(m_currentExercise.description())); writer->writeTextElement(SAAB_WIND_SPEED(), QStringLiteral("0")); writer->writeTextElement(SAAB_WIND_DIRECTION(), QStringLiteral("0")); writer->writeEmptyElement(SAAB_EVALUATION_PACKAGE_FILE_NAME()); writer->writeTextElement(SAAB_EXERCISE_STATE(), QStringLiteral("0")); writer->writeEndElement(); } void ExportSaab::writeOrbat(QXmlStreamWriter *writer) { using namespace XmlStrings; writer->writeStartElement(SAAB_ORBAT()); QMap participants = m_ppmDtc->participants(); for (auto it = m_mapPartyToForce.constBegin(); it != m_mapPartyToForce.constEnd(); ++it) { SaabPlayer *player = toSaabPlayer(m_parties.value(it.key()), participants); player->writeToXml(writer); delete player; player = nullptr; } writer->writeEndElement(); } void ExportSaab::writeFooter(QXmlStreamWriter *writer) const { using namespace XmlStrings; writer->writeEmptyElement(SAAB_PERSONS()); writer->writeEmptyElement(SAAB_VEHICLES()); writer->writeEmptyElement(SAAB_HELICOPTERS()); } SaabPlayer *ExportSaab::toSaabPlayer(const ParticipantManagement::Party &party, QMap &remainingParticipants) { SaabPlayer *result; result = new SaabUnit; result->symbol = m_mapPartyToForce.value(party.id()).symbolCode; static_cast(result)->unitSize = m_mapPartyToForce.value(party.id()).echelonCode; result->designation = QString::fromStdString(party.name()); result->trashcan = false; result->isExternalPlayer = true; result->force = m_mapPartyToForce.value(party.id()).forceCode; result->guid = QUuid(l, m_currentExercise.id(), w2, static_cast(party.id()), b2, b3, b4, b5, b6, b7, GuidContext::GuidParty).toString(QUuid::WithoutBraces); QList keys = remainingParticipants.keys(); // add the root units/participants for (int key : qAsConst(keys)) { if (!remainingParticipants.contains(key)) continue; ParticipantManagement::Participant participant = remainingParticipants.value(key); if (!(participant.partyId() == party.id() && participant.superiorId() == 0)) continue; SaabPlayer *player = toSaabPlayer(participant, remainingParticipants); if (!player) continue; static_cast(result)->inferiors.append(player); } return result; } SaabPlayer *ExportSaab::toSaabPlayer(const ParticipantManagement::Participant &participant, QMap &remainingParticipants) { if (participant.isDeleted()) { DEBUG_LOG(m_logger) << "Participant with id " << participant.id() << " id deleted --> skipping"; return nullptr; } if (participant.externalId()) { DEBUG_LOG(m_logger) << "Particpant with id " << participant.id() << " has external ID set to " << participant.externalId() << " --> not being exported."; return nullptr; } SaabPlayer *result; ParticipantManagement::Type type = m_types.value(participant.typeId()); if (!m_mapTypeToSaabSymbol.contains(type.id())) mapType(type); switch (type.typeClass().underlying()) { case ParticipantManagement::TypeClass::SupportEntity: // OC Player result = new SaabOcPlayer; break; case ParticipantManagement::TypeClass::Human: // Person Player result = new SaabPersonPlayer; break; case ParticipantManagement::TypeClass::GroundVehicle: // Vehicle Player case ParticipantManagement::TypeClass::VehicleWithGear: case ParticipantManagement::TypeClass::WeaponSystem: result = new SaabVehiclePlayer; break; case ParticipantManagement::TypeClass::Unit: // Unit case ParticipantManagement::TypeClass::SupportUnit: { result = new SaabUnit; DEBUG_LOG(m_logger) << "Reading unit with " << participant.inferiorIds().size() << " inferiors"; for (int32_t id : participant.inferiorIds()) { SaabPlayer *player = toSaabPlayer(remainingParticipants.value(id), remainingParticipants); if (player == nullptr) { DEBUG_LOG(m_logger) << "Skipping inferior with id " << id; continue; } static_cast(result)->inferiors.append(player); } // do not export empty units if (!static_cast(result)->inferiors.size()) { DEBUG_LOG(m_logger) << "Unit " << participant.id() << " has no inferiors."; return nullptr; } break; } case ParticipantManagement::TypeClass::Vessel: // we do not support that for export atm case ParticipantManagement::TypeClass::Aircraft: case ParticipantManagement::TypeClass::Unknown: default: DEBUG_LOG(m_logger) << "Type " << participant.typeId() << " is not supported."; return nullptr; } QString symbol = m_mapTypeToSaabSymbol.value(type.id()); if (typeid(*result) == typeid(SaabUnit)) { QStringList symbolUnitSize = symbol.split(QLatin1Char(';')); result->symbol = symbolUnitSize.at(0); static_cast(result)->unitSize = symbolUnitSize.at(1); } else result->symbol = symbol; result->designation = QString::fromStdString(participant.designator()); result->trashcan = participant.isDeleted(); result->rank = QStringLiteral("OF1"); result->isExternalPlayer = true; if (participant.equipmentNumber().is_set()) result->simulatorId = participant.equipmentNumber().get(); result->force = m_mapPartyToForce.value(participant.partyId()).forceCode; result->guid = QUuid(participant.id(), m_currentExercise.id(), w2, static_cast(participant.partyId()), b2, b3, b4, b5, b6, b7, GuidContext::GuidParticipant).toString(QUuid::WithoutBraces); return result; } ParticipantManagement::Participant ExportSaab::rootParticipant(const ParticipantManagement::Participant &participant, const QMap &participants) const { if (participant.superiorId()) return rootParticipant(participants.value(participant.superiorId()), participants); return participant; } QString ExportSaab::toSaabTime(qint64 msecs) const { return QDateTime::fromMSecsSinceEpoch(msecs).toString(QStringLiteral("yyyy-MM-ddThh:mm:ssZ")); } void ExportSaab::mapType(const ParticipantManagement::Type &type) { using namespace ParticipantManagement; MapType dialog(ParticipantManagement::trTypeName(type.name()), m_mapEchelons, m_mapSymbols, type.typeClass() == TypeClass::Unit || type.typeClass() == TypeClass::SupportUnit, this); dialog.exec(); QString saabTypeStr = dialog.symbolCode(); if (!dialog.echelonCode().isEmpty()) { saabTypeStr.append(QLatin1Char(';')); saabTypeStr.append(dialog.echelonCode()); } m_mapTypeToSaabSymbol.insert(type.id(), saabTypeStr); } void ExportSaab::mapPartyToForce(const ParticipantManagement::Party &party) { MapParty dialog(QString::fromStdString(party.name()), m_mapForces, m_mapEchelons, m_mapSymbols, this); dialog.exec(); if (dialog.isSkipped()) return; m_mapPartyToForce.insert(party.id(), { m_mapForces.take(dialog.force()), dialog.echelonCode(), dialog.symbolCode() }); } }