diff options
Diffstat (limited to 'tools/datatypecodegenerator')
29 files changed, 3994 insertions, 0 deletions
diff --git a/tools/datatypecodegenerator/CMakeLists.txt b/tools/datatypecodegenerator/CMakeLists.txt new file mode 100644 index 0000000..98bc5c3 --- /dev/null +++ b/tools/datatypecodegenerator/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (C) 2023 basysKom GmbH +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## qopcuaxmldatatypes2cpp Tool: +##################################################################### + +qt_get_tool_target_name(target_name qopcuaxmldatatypes2cpp) +qt_internal_add_tool(${target_name} + TOOLS_TARGET + QtOpcUa + SOURCES + main.cpp + util.cpp util.h + datatypefilewriter.cpp datatypefilewriter.h + dependencydatatypevalidator.cpp dependencydatatypevalidator.h + enumeratedtype.cpp enumeratedtype.h + enumeratedvalue.cpp enumeratedvalue.h + field.cpp field.h + import.cpp import.h + mappingfilegenerator.cpp mappingfilegenerator.h + recursivedescentparser.cpp recursivedescentparser.h + stringidentifier.cpp stringidentifier.h + structuredtype.cpp structuredtype.h + typedictionary.cpp typedictionary.h + visitor.h + xmlelement.cpp xmlelement.h +) +qt_internal_return_unless_building_tools() diff --git a/tools/datatypecodegenerator/datatypefilewriter.cpp b/tools/datatypecodegenerator/datatypefilewriter.cpp new file mode 100644 index 0000000..aaa3139 --- /dev/null +++ b/tools/datatypecodegenerator/datatypefilewriter.cpp @@ -0,0 +1,1287 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "util.h" +#include "datatypefilewriter.h" +#include "enumeratedtype.h" +#include "enumeratedvalue.h" +#include "field.h" +#include "stringidentifier.h" +#include "structuredtype.h" + +#include <QtCore/qdir.h> +#include <QtCore/qfile.h> +#include <QtCore/qset.h> + +DataTypeFileWriter::DataTypeFileWriter(const QString &path, + const QString &prefix, + const QString &header) + : m_path(path) + , m_prefix(prefix) + , m_header(header) +{} + +void DataTypeFileWriter::visit(XmlElement *xmlElement) +{ + Q_UNUSED(xmlElement); +} + +void DataTypeFileWriter::visit(EnumeratedType *enumeratedType) +{ + bool isPrecodedType = false; + for (const auto &precodedType : StringIdentifier::opcUaPrecodedTypes) { + if (precodedType.contains(enumeratedType->name())) { + isPrecodedType = true; + break; + } + } + if (!isPrecodedType) { + m_generateMapping.append(enumeratedType); + } +} + +void DataTypeFileWriter::visit(EnumeratedValue *enumeratedValue) +{ + Q_UNUSED(enumeratedValue); +} + +void DataTypeFileWriter::visit(Field *field) +{ + Q_UNUSED(field); +} + +void DataTypeFileWriter::visit(Import *import) +{ + Q_UNUSED(import); +} + +void DataTypeFileWriter::visit(StructuredType *structuredType) +{ + if (!structuredType->fields().empty()) { + bool isPrecodedType = false; + for (const auto &precodedType : StringIdentifier::opcUaPrecodedTypes) { + if (precodedType.contains(structuredType->name())) { + isPrecodedType = true; + break; + } + } + if (!isPrecodedType) { + m_generateMapping.append(structuredType); + } + } +} + +void DataTypeFileWriter::visit(TypeDictionary *typeDictionary) +{ + Q_UNUSED(typeDictionary); +} + +void DataTypeFileWriter::writeLicenseHeader(QTextStream &output) +{ + if (!m_header.isEmpty()) + output << m_header << Util::lineBreak(2); +} + +void DataTypeFileWriter::writeStructuredTypeCpp(const StructuredType *structuredType, + QTextStream &output) +{ + if (!m_generatedStructuredTypeFilenames.contains( + QStringLiteral("%1%2").arg(m_prefix, structuredType->name()))) + m_generatedStructuredTypeFilenames.push_back( + QStringLiteral("%1%2").arg(m_prefix, structuredType->name())); + output << "#include \"" << m_prefix.toLower() << structuredType->name().toLower() << ".h\""; + + QList<QString> mutualIncludes; + for (const auto field : structuredType->fields()) { + const auto typeName = field->typeNameSecondPart(); + for (const auto &type : m_generateMapping) { + StructuredType *tempStructuredType = dynamic_cast<StructuredType *>(type); + if (tempStructuredType) { + if (tempStructuredType == structuredType) + continue; + for (const auto &tempField : tempStructuredType->fields()) { + if (typeName == tempStructuredType->name() + && tempField->typeNameSecondPart() == structuredType->name()) { + if (!mutualIncludes.contains( + QStringLiteral("#include \"%1%2.h\"\n") + .arg(m_prefix.toLower(), tempStructuredType->name().toLower()))) + mutualIncludes.push_back( + QStringLiteral("#include \"%1%2.h\"\n") + .arg(m_prefix.toLower(), tempStructuredType->name().toLower())); + } + } + } + } + } + if (!mutualIncludes.isEmpty()) { + output << Util::lineBreak(); + std::sort(mutualIncludes.begin(), mutualIncludes.end()); + for (const auto &include : mutualIncludes) { + output << include; + } + } + if (structuredType->recursive()) { + output << Util::lineBreak(); + output << "#include <optional>"; + output << Util::lineBreak(); + } + output << Util::lineBreak(2); + + writeStructuredTypeCppClassComment(structuredType, output); + writeStructuredTypeCppDataClass(structuredType, output); + writeStructuredTypeCppConstructors(structuredType, output); + writeStructuredTypeCppOperatorAssignment(structuredType, output); + writeStructuredTypeCppOperatorEquality(structuredType, output); + writeStructuredTypeCppQVariant(structuredType, output); + writeStructuredTypeCppDestructor(structuredType, output); + writeStructuredTypeCppGetterSetter(structuredType, output); + writeStructuredTypeCppDebug(structuredType, output); +} + +void DataTypeFileWriter::writeStructuredTypeCppClassComment(const StructuredType *structuredType, + QTextStream &output) +{ + output << "/*!" + << Util::lineBreak(); + output << Util::indent(1) << "\\class " << m_prefix << structuredType->name() << Util::lineBreak(); + output << Util::indent(1) << "\\brief the OPC UA " << structuredType->name() << "." + << Util::lineBreak(); + output << "*/" + << Util::lineBreak(); +} + +void DataTypeFileWriter::writeStructuredTypeCppDataClass(const StructuredType *structuredType, + QTextStream &output) +{ + output << "class " << m_prefix << structuredType->name() << "Data : public QSharedData" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << "public:" + << Util::lineBreak(); + + writeStructuredTypeCppDataClassMember(structuredType, output); + + output << "};" + << Util::lineBreak(2); +} + +void DataTypeFileWriter::writeStructuredTypeCppDataClassMember(const StructuredType *structuredType, + QTextStream &output) +{ + QList<Field *> unionMember; + if (structuredType->hasUnion()) { + for (const auto &possibleUnionMember : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (field->isUnion()) { + if (field->switchField() == possibleUnionMember->name() + && !unionMember.contains(possibleUnionMember)) + unionMember.push_back(possibleUnionMember); + } + } + } + } + QList<Field *> arrayLengthfields; + for (const auto &possibleArrayLengthField : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (possibleArrayLengthField->name() == field->lengthField() + && !arrayLengthfields.contains(possibleArrayLengthField)) + arrayLengthfields.push_back(possibleArrayLengthField); + } + } + + QList<Field *> switchFields; + if (structuredType->hasSwitchfield()) { + for (const auto &possibleOptionalMember : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (!possibleOptionalMember->switchField().isEmpty() && possibleOptionalMember->switchField() == field->name()) + switchFields.push_back(field); + } + } + } + + for (const auto &field : structuredType->fields()) { + if (field->isInStructuredTypeBitMask() && !switchFields.contains(field)) + continue; + + bool isEnumeration = false; + for (const auto &enumeratedType : m_enumeratedTypes) { + if (enumeratedType->name() == field->typeName().split(":").at(1)) { + isEnumeration = true; + field->setIsEnum(true); + } + } + + bool isPreCoded = false; + if (!arrayLengthfields.contains(field)) { + if (!unionMember.contains(field)) { + output << Util::indent(1); + if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) { + if (!field->needContainer()) { + output << StringIdentifier::typeNameDataTypeConverter.value( + field->typeName()) + << " "; + } else { + output << "QList<" + << StringIdentifier::typeNameDataTypeConverter.value( + field->typeName()) + << "> "; + } + } else { + const auto typeName = field->typeNameSecondPart(); + for (const auto &preCodedType : StringIdentifier::opcUaPrecodedTypes) { + if (preCodedType.contains(typeName)) { + isPreCoded = true; + if (field->needContainer()) + output << "QList<"; + if (preCodedType.deEncoderName().isEmpty()) + output << preCodedType.className(); + else + output << preCodedType.deEncoderName(); + if (field->needContainer()) + output << ">"; + output << " "; + break; + } + } + if (!isPreCoded) { + if (field->needContainer()) + output << "QList<" << m_prefix << typeName << "> "; + else if (field->recursive()) + output << "std::optional<" << m_prefix << typeName << "> "; + else if (isEnumeration) + output << m_prefix << "::" << typeName << " "; + else + output << m_prefix << typeName << " "; + } + } + + const auto lowerCaseFieldName = field->lowerFirstName(); + output << lowerCaseFieldName; + if (!field->needContainer()) { + if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) { + if (field->typeName().contains(StringIdentifier::integerIdentifier) + || field->typeName().contains(StringIdentifier::doubleIdentifier) + || field->typeName().contains(StringIdentifier::floatIdentifier) + || field->typeName().contains(StringIdentifier::byteIdentifier) + || field->typeName().contains(StringIdentifier::sbyteIdentifier)) + output << " {0}"; + else if (field->typeName().contains(StringIdentifier::booleanIdentifier) + || field->typeName().contains(StringIdentifier::bitIdentifier)) + output << " {false}"; + } else if (field->isEnum()) { + const auto enumType = std::find_if(m_enumeratedTypes.constBegin(), m_enumeratedTypes.constEnd(), + [field](EnumeratedType *e) { return e->name() == field->typeNameSecondPart(); }); + const auto firstValueName = enumType != m_enumeratedTypes.constEnd() && !(*enumType)->values().empty() + ? (*enumType)->values().first()->name() : "Unknown"; + if (!firstValueName.isEmpty()) + output << " {" << m_prefix << "::" << field->typeNameSecondPart() << "::" << firstValueName << "}"; + else + output << " {}"; + } else { + if (field->typeName().contains(StringIdentifier::uaStatusCodeIdentifier)) + output << " {QOpcUa::UaStatusCode::Good}"; + } + } + output << ";" + << Util::lineBreak(); + } else { + const auto fieldNameLowerCase = field->lowerFirstName(); + output << Util::indent(1) << m_prefix << structuredType->name() << "::" << field->name() + << " " << fieldNameLowerCase << " = " << m_prefix << structuredType->name() + << "::" << field->name() << "::" + << "None"; + + output << ";" + << Util::lineBreak(); + } + } + } +} + +void DataTypeFileWriter::writeStructuredTypeCppConstructors(const StructuredType *structuredType, + QTextStream &output) +{ + output << m_prefix << structuredType->name() << "::" << m_prefix << structuredType->name() + << "()" + << Util::lineBreak(); + output << " : data(new " << m_prefix << structuredType->name() << "Data)\n"; + output << "{" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(2); + + output << "/*!" + << Util::lineBreak(); + output << " Constructs a " << structuredType->name() << " from \\a rhs" + << Util::lineBreak(); + output << "*/" + << Util::lineBreak(); + output << m_prefix << structuredType->name() << "::" << m_prefix << structuredType->name() + << "(const " << m_prefix << structuredType->name() << " &rhs)" + << Util::lineBreak(); + output << " : data(rhs.data)" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(2); +} + +void DataTypeFileWriter::writeStructuredTypeCppOperatorAssignment( + const StructuredType *structuredType, QTextStream &output) +{ + output << "/*!" + << Util::lineBreak(); + output << " Sets the values from \\a rhs in this " << structuredType->name() << Util::lineBreak(); + output << "*/" + << Util::lineBreak(); + output << m_prefix << structuredType->name() << " &" << m_prefix << structuredType->name() + << "::operator=(const " << m_prefix << structuredType->name() << " &rhs)" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << Util::indent(1) << "if (this != &rhs)" + << Util::lineBreak(); + output << Util::indent(2) << "data.operator=(rhs.data);" + << Util::lineBreak(); + output << Util::indent(1) << "return *this;" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(2); +} + +void DataTypeFileWriter::writeStructuredTypeCppOperatorEquality(const StructuredType *structuredType, + QTextStream &output) +{ + output << "/*!" + << Util::lineBreak(); + output << Util::indent(1) << "Returns \\c true if this " << structuredType->name() + << " has the same value as \\a rhs" + << Util::lineBreak(); + output << "*/" + << Util::lineBreak(); + + output << "bool " << m_prefix << structuredType->name() << "::operator==(const " << m_prefix + << structuredType->name() << " &rhs) const" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + QList<Field *> unionSwitchfield; + QList<Field *> arrayLengthfields; + for (const auto &possibleMember : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (possibleMember->name() == field->lengthField() + && !arrayLengthfields.contains(possibleMember)) + arrayLengthfields.push_back(possibleMember); + if (possibleMember->name() == field->switchField() + && !unionSwitchfield.contains(possibleMember) && field->isUnion()) + unionSwitchfield.push_back(possibleMember); + } + } + + if (!unionSwitchfield.isEmpty()) { + const auto switchField = unionSwitchfield.first()->lowerFirstName(); + output << Util::indent(1) << "if (data->" << switchField << " != " << " rhs.data->" << switchField << ")" << Util::lineBreak(); + output << Util::indent(2) << "return false;" << Util::lineBreak(2); + } + + if (structuredType->containsBitMask()) { + for (const auto &field : structuredType->fields()) { + if (!field->switchField().isEmpty() && !arrayLengthfields.contains(field)) { + const auto switchField = Util::lowerFirstLetter(field->switchField()); + output << Util::indent(1) << "if (data->" << switchField << " != rhs.data->" << switchField << " || (data->" << switchField << " && "; + output << "!(data->" << field->lowerFirstName() << " == rhs.data->" << field->lowerFirstName() << ")"; + output << "))"; + output << Util::lineBreak() << Util::indent(2) << "return false;" << Util::lineBreak(); + } + } + output << Util::lineBreak(); + } + + auto counterGeneratedTypes = 0; + if (structuredType->hasUnion()) { + for (const auto &field : structuredType->fields()) { + if (!arrayLengthfields.contains(field) && !unionSwitchfield.contains(field) && !field->isInStructuredTypeBitMask()) { + if (structuredType->containsBitMask() && !field->switchField().isEmpty()) + continue; + + + output << Util::indent(1) << "if (static_cast<qint32>(data->" << unionSwitchfield.first()->lowerFirstName() << ") == " << field->switchValue() << " && "; + output << "!(data->" << field->lowerFirstName() << " == rhs.data->" << field->lowerFirstName() << ")"; + output << ")" << Util::lineBreak(); + output << Util::indent(2) << "return false;" << Util::lineBreak(2); + } + } + + output << Util::indent(1) << "return true;" << Util::lineBreak(); + } else { + output << Util::indent(1) << "return "; + + for (const auto &field : structuredType->fields()) { + if (!arrayLengthfields.contains(field) && !unionSwitchfield.contains(field) && !field->isInStructuredTypeBitMask()) { + if (structuredType->containsBitMask() && !field->switchField().isEmpty()) + continue; + + const auto memberName = field->lowerFirstName(); + if (counterGeneratedTypes != 0) + output << Util::lineBreak() << Util::indent(2) << " && "; + output << "data->" << memberName << " == rhs.data->" << memberName; + counterGeneratedTypes++; + } + } + + output << ";" << Util::lineBreak(); + } + + output << "}" + << Util::lineBreak(2); +} + +void DataTypeFileWriter::writeStructuredTypeCppQVariant(const StructuredType *structuredType, + QTextStream &output) +{ + output << "/*!" + << Util::lineBreak(); + output << " Converts this " << structuredType->name() << " to \\l QVariant" + << Util::lineBreak(); + output << "*/" + << Util::lineBreak(); + + output << m_prefix << structuredType->name() << "::operator QVariant() const" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << Util::indent(1) << "return QVariant::fromValue(*this);" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(2); +} + +void DataTypeFileWriter::writeStructuredTypeCppDestructor(const StructuredType *structuredType, + QTextStream &output) +{ + output << m_prefix << structuredType->name() << "::~" << m_prefix << structuredType->name() + << "()" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(2); +} + +void DataTypeFileWriter::writeStructuredTypeCppGetterSetter(const StructuredType *structuredType, + QTextStream &output) +{ + writeStructuredTypeCppGetter(structuredType, output); +} + +void DataTypeFileWriter::writeStructuredTypeCppGetter(const StructuredType *structuredType, + QTextStream &output) +{ + QList<Field *> unionMember; + if (structuredType->hasUnion()) { + for (const auto &possibleUnionMember : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (field->isUnion()) { + if (field->switchField() == possibleUnionMember->name() + && !unionMember.contains(possibleUnionMember)) + unionMember.push_back(possibleUnionMember); + } + } + } + } + QList<Field *> arrayLengthfields; + for (const auto &possibleArrayLengthField : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (possibleArrayLengthField->name() == field->lengthField() + && !arrayLengthfields.contains(possibleArrayLengthField)) + arrayLengthfields.push_back(possibleArrayLengthField); + } + } + + QList<Field *> switchFields; + if (structuredType->hasSwitchfield()) { + for (const auto &possibleOptionalMember : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (!possibleOptionalMember->switchField().isEmpty() && possibleOptionalMember->switchField() == field->name()) + switchFields.push_back(field); + } + } + } + + for (const auto &field : structuredType->fields()) { + if (!arrayLengthfields.contains(field) && !(field->isInStructuredTypeBitMask() && !switchFields.contains(field))) { + const auto tmpFunctionName = field->lowerFirstName(); + + output << "/*!\n"; + output << Util::indent(1) << "Returns the field " << field->name() << " of this " << structuredType->name() << " object" + << Util::lineBreak(); + output << "*/\n"; + + const auto typeName = field->typeNameSecondPart(); + if (!unionMember.contains(field)) { + if (field->needContainer()) + output << "QList<"; + if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) { + output << StringIdentifier::typeNameDataTypeConverter.value(field->typeName()); + } else { + bool isPreCoded = false; + for (const auto &preCodedType : StringIdentifier::opcUaPrecodedTypes) { + if (preCodedType.contains(typeName)) { + isPreCoded = true; + if (preCodedType.deEncoderName().isEmpty()) + output << preCodedType.className(); + else + output << preCodedType.deEncoderName(); + break; + } + } + if (!isPreCoded) { + bool isEnum = false; + for (const auto &enumeratedType : m_enumeratedTypes) { + if (enumeratedType->name() == field->typeName().split(":").at(1)) + isEnum = true; + } + if (isEnum) + output << m_prefix << "::" << field->typeName().split(":").at(1); + else + output << m_prefix << typeName; + } + } + if (field->needContainer()) + output << ">"; + output << " "; + + } else { + output << m_prefix << structuredType->name() << "::" << field->name() << " "; + } + + output << m_prefix << structuredType->name() << "::" << tmpFunctionName << "() const" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + if (!field->isUnion()) { + if (field->recursive()) + if (field->needContainer()) { + output << Util::indent(1) << "return data->" << tmpFunctionName << ";" + << Util::lineBreak(); + } else { + output << Util::indent(1) << "return data->" << tmpFunctionName << ".value_or(" << m_prefix + << field->typeName().split(QChar::fromLatin1(':')).at(1) << "()" + << ");" + << Util::lineBreak(); + } + else + output << Util::indent(1) << "return data->" << tmpFunctionName << ";" + << Util::lineBreak(); + } else { + const auto switchfieldToLower = Util::lowerFirstLetter(field->switchField()); + output << Util::indent(1) << "if (data->" << switchfieldToLower << " == " << m_prefix + << structuredType->name() << "::" << field->switchField() + << "::" << field->name() << ")" + << Util::lineBreak(); + output << Util::indent(2) << "return data->" << tmpFunctionName << ";" + << Util::lineBreak(); + output << Util::indent(1) << "else" + << Util::lineBreak(); + output << Util::indent(2) << "return {};" << Util::lineBreak(); + } + output << "}" + << Util::lineBreak(2); + if (!unionMember.contains(field)) + writeStructuredTypeCppSetter(field, structuredType, output); + } + } +} + +void DataTypeFileWriter::writeStructuredTypeCppSetter(const Field *field, + const StructuredType *structuredType, + QTextStream &output) +{ + output << "/*!" + << Util::lineBreak(); + output << Util::indent(1) << "Sets the field " << field->name() << " of this " << structuredType->name() << " object to \\a " + << field->lowerFirstName() << Util::lineBreak(); + output << "*/" + << Util::lineBreak(); + + output << "void " << m_prefix << structuredType->name() << "::set" << field->name() << "("; + const auto tmpFunctionName = field->lowerFirstName(); + if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) { + if (field->typeName().contains(StringIdentifier::booleanIdentifier) + || field->typeName().contains(StringIdentifier::integerIdentifier) + || field->typeName().contains(StringIdentifier::floatIdentifier) + || field->typeName().contains(StringIdentifier::doubleIdentifier)) { + if (field->needContainer()) + output << "QList<" + << StringIdentifier::typeNameDataTypeConverter.value(field->typeName()) + << "> "; + else + output << StringIdentifier::typeNameDataTypeConverter.value(field->typeName()) + << " "; + } else { + output << "const "; + if (field->needContainer()) + output << "QList<" + << StringIdentifier::typeNameDataTypeConverter.value(field->typeName()) + << "> &"; + else + output << StringIdentifier::typeNameDataTypeConverter.value(field->typeName()) + << " &"; + } + } else { + const auto typeName = field->typeNameSecondPart(); + bool isPreCoded = false; + for (const auto &preCodedType : StringIdentifier::opcUaPrecodedTypes) { + if (preCodedType.contains(typeName)) { + isPreCoded = true; + output << "const "; + if (field->needContainer()) + output << "QList<"; + if (preCodedType.deEncoderName().isEmpty()) + output << preCodedType.className(); + else + output << preCodedType.deEncoderName(); + if (field->needContainer()) + output << ">"; + output << " &"; + break; + } + } + + if (!isPreCoded) { + bool isEnum = false; + for (const auto &enumeratedType : m_enumeratedTypes) { + if (enumeratedType->name() == field->typeName().split(":").at(1)) + isEnum = true; + } + output << "const "; + if (field->needContainer()) + output << "QList <" << m_prefix << typeName << "> &"; + else if (isEnum) + output << m_prefix << "::" << field->typeName().split(":").at(1) << " &"; + else + output << m_prefix << typeName << " &"; + } + } + output << tmpFunctionName << ")" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << Util::indent(1) << "data->" << tmpFunctionName << " = " << tmpFunctionName << ";" + << Util::lineBreak(); + if (field->isUnion()) { + const auto switchfieldToLower = Util::lowerFirstLetter(field->switchField()); + output << Util::indent(1) << "data->" << switchfieldToLower << " = " << m_prefix << structuredType->name() + << "::" << field->switchField() << "::" << field->name() << ";" + << Util::lineBreak(); + } + output << "}" + << Util::lineBreak(2); +} + +void DataTypeFileWriter::writeStructuredTypeHeader(const StructuredType *structuredType, + QTextStream &output) +{ + output << "#pragma once" + << Util::lineBreak(); + + writeStructuredTypeHeaderIncludes(structuredType, output); + + output << "class " << m_prefix << structuredType->name() << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << "public:" + << Util::lineBreak(); + + writeStructuredTypeHeaderUnion(structuredType, output); + + output << Util::indent(1) << m_prefix << structuredType->name() << "();" + << Util::lineBreak(); + output << Util::indent(1) << m_prefix << structuredType->name() << "(const " << m_prefix + << structuredType->name() << " &);" + << Util::lineBreak(); + output << Util::indent(1) << m_prefix << structuredType->name() << " &operator=(const " << m_prefix + << structuredType->name() << " &rhs);" + << Util::lineBreak(); + output << Util::indent(1) << "bool operator==(const " << m_prefix << structuredType->name() << " &rhs) const;" + << Util::lineBreak(); + output << Util::indent(1) << "inline bool operator!=(const " << m_prefix << structuredType->name() << " &rhs) const" + << Util::lineBreak(1) + << Util::indent(2) << "{ return !(*this == rhs); }" + << Util::lineBreak(); + output << Util::indent(1) << "operator QVariant() const;" + << Util::lineBreak(); + output << Util::indent(1) << "~" << m_prefix << structuredType->name() << "();" + << Util::lineBreak(2); + + writeStructuredTypeHeaderGetterSetter(structuredType, output); + + writeStructuredTypeHeaderDebug(structuredType, output); + + output << "private:" + << Util::lineBreak(); + output << Util::indent(1) << "QSharedDataPointer<" << m_prefix << structuredType->name() << "Data> data;" + << Util::lineBreak(); + output << "};" + << Util::lineBreak(2); + output << "Q_DECLARE_METATYPE(" << m_prefix << structuredType->name() << ")" + << Util::lineBreak(); +} + +void DataTypeFileWriter::writeStructuredTypeHeaderIncludes(const StructuredType *structuredType, + QTextStream &output) +{ + QList<QString> qtCoreIncludes{"#include <QSharedData>\n", + "#include <QVariant>\n"}; + QList<QString> qtOpcUaIncludes{}; + QList<QString> qtClassIncludes; + QList<QString> qtPredifinedClasses; + + for (const auto &field : structuredType->fields()) { + const auto typeName = field->typeNameSecondPart(); + QString include; + if (field->typeName().startsWith(QStringLiteral("%1:").arg(StringIdentifier::binaryTypeIdentifier))) { + if (typeName == StringIdentifier::datetimeIdentifier) { + include = "#include <QDateTime>\n"; + } else if (typeName == StringIdentifier::byteStringIdentifier) { + include = "#include <QByteArray>\n"; + } else if (typeName == StringIdentifier::charArrayIdentifier) { + include = "#include <QString>\n"; + } else if (typeName == StringIdentifier::guidIdentifier) { + include = "#include <QUuid>\n"; + } + + if (!qtCoreIncludes.contains(include)) { + qtCoreIncludes.push_back(include); + } + } else { + bool isPrecoded = false; + for (const auto &precodedType : StringIdentifier::opcUaPrecodedTypes) { + if (precodedType.contains(typeName)) { + isPrecoded = true; + if (!precodedType.filename().isEmpty()) { + include = QStringLiteral("#include <QtOpcUa/%1>\n") + .arg(precodedType.filename()); + break; + } + } + } + if (typeName != structuredType->name()) { + if (!isPrecoded) { + bool isEnum = false; + for (const auto &enumeratedType : m_enumeratedTypes) { + if (enumeratedType->name() == typeName) + isEnum = true; + } + if (isEnum) { + if (!qtClassIncludes.contains( + QStringLiteral("#include \"%1enumerations.h\"\n") + .arg(m_prefix.toLower()))) + qtClassIncludes.push_back( + QStringLiteral("#include \"%1enumerations.h\"\n") + .arg(m_prefix.toLower())); + + } else { + bool isMutualInclude = false; + for (const auto &type : m_generateMapping) { + StructuredType *tempStructuredType = dynamic_cast<StructuredType *>( + type); + if (tempStructuredType) { + if (tempStructuredType == structuredType) + continue; + for (const auto &tempField : tempStructuredType->fields()) { + const auto tempTypeName = tempField->typeNameSecondPart(); + if (typeName == tempStructuredType->name() + && tempTypeName == structuredType->name()) { + isMutualInclude = true; + if (!qtPredifinedClasses.contains( + QStringLiteral("%1%2") + .arg(m_prefix, tempStructuredType->name()))) + qtPredifinedClasses.push_back( + QStringLiteral("%1%2") + .arg(m_prefix, tempStructuredType->name())); + } + } + } + } + + if (!qtClassIncludes.contains( + QStringLiteral("#include \"%1%2.h\"\n") + .arg(m_prefix.toLower(), typeName.toLower())) + && !isMutualInclude + && typeName != StringIdentifier::xmlElementIdentifier) + qtClassIncludes.push_back( + QStringLiteral("#include \"%1%2.h\"\n") + .arg(m_prefix.toLower(), typeName.toLower())); + } + } else { + if (!qtOpcUaIncludes.contains(include)) { + qtOpcUaIncludes.push_back(include); + } + } + } + } + if (field->needContainer()) { + include = "#include <QList>\n"; + if (!qtCoreIncludes.contains(include)) { + qtCoreIncludes.push_back(include); + } + } + } + if (!qtClassIncludes.isEmpty()) { + std::sort(qtClassIncludes.begin(), qtClassIncludes.end()); + for (const auto &include : qtClassIncludes) { + output << include; + } + output << Util::lineBreak(); + } + std::sort(qtOpcUaIncludes.begin(), qtOpcUaIncludes.end()); + for (const auto &include : qtOpcUaIncludes) { + output << include; + } + output << Util::lineBreak(); + std::sort(qtCoreIncludes.begin(), qtCoreIncludes.end()); + for (const auto &include : qtCoreIncludes) { + output << include; + } + output << Util::lineBreak(); + + if (!qtPredifinedClasses.isEmpty()) { + std::sort(qtPredifinedClasses.begin(), qtPredifinedClasses.end()); + for (const auto &predefinedClass : qtPredifinedClasses) { + output << "class " << predefinedClass << ";" + << Util::lineBreak(); + } + } + + output << "class " << m_prefix << structuredType->name() << "Data;" + << Util::lineBreak(); +} + +void DataTypeFileWriter::writeStructuredTypeHeaderUnion(const StructuredType *structuredType, + QTextStream &output) +{ + if (structuredType->hasUnion()) { + QList<Field *> unions; + QList<Field *> switchFields; + for (const auto &field : structuredType->fields()) { + if (field->isUnion()) + switchFields.push_back(field); + } + for (const auto &switchField : switchFields) { + for (const auto &structuredTypeField : structuredType->fields()) { + if (switchField->switchField() == structuredTypeField->name()) { + if (!unions.contains(structuredTypeField)) + unions.push_back(structuredTypeField); + } + } + } + QList<Field *> arrayLengthfields; + for (const auto &possibleArrayLengthField : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (possibleArrayLengthField->name() == field->lengthField() + && !arrayLengthfields.contains(possibleArrayLengthField)) + arrayLengthfields.push_back(possibleArrayLengthField); + } + } + for (const auto &field : unions) { + output << Util::indent(1) << "enum class " << field->name() << " {" + << Util::lineBreak(); + output << Util::indent(2) << "None = 0,\n"; + for (int i = 0; i < switchFields.size(); i++) { + const int nextField = i + 1; + if (!arrayLengthfields.contains(switchFields.at(i))) { + if (nextField >= switchFields.size()) { + if (switchFields.at(i)->switchField() == field->name()) { + output << Util::indent(2) << switchFields.at(i)->name() << " = " + << switchFields.at(i)->switchValue() << Util::lineBreak(); + output << Util::indent(1) << "};" + << Util::lineBreak(2); + } + } else { + if (field->name() == switchFields.at(nextField)->switchField()) { + output << Util::indent(2) << switchFields.at(i)->name() << " = " + << switchFields.at(i)->switchValue() << "," + << Util::lineBreak(); + } else { + output << Util::indent(2) << switchFields.at(i)->name() << " = " + << switchFields.at(i)->switchValue() << Util::lineBreak(); + output << Util::indent(1) << "};" + << Util::lineBreak(2); + } + } + } + } + } + } +} + +void DataTypeFileWriter::writeStructuredTypeHeaderGetterSetter(const StructuredType *structuredType, + QTextStream &output) +{ + QList<Field *> unionMember; + QList<Field *> arrayLengthfields; + QList<Field *> switchFields; + for (const auto &possibleMember : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (possibleMember->name() == field->lengthField() + && !arrayLengthfields.contains(possibleMember)) + arrayLengthfields.push_back(possibleMember); + if (field->isUnion() && field->switchField() == possibleMember->name() + && !unionMember.contains(possibleMember)) + unionMember.push_back(possibleMember); + if (!possibleMember->switchField().isEmpty() && field->name() == possibleMember->switchField()) + switchFields.push_back(field); + } + } + for (const auto &field : structuredType->fields()) { + if (!arrayLengthfields.contains(field) && !(field->isInStructuredTypeBitMask() && !switchFields.contains(field))) { + const auto typeName = field->typeNameSecondPart(); + const auto tmpFunctionName = field->lowerFirstName(); + if (!unionMember.contains(field)) { + if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) { + if (field->typeName().contains(StringIdentifier::booleanIdentifier) + || field->typeName().contains(StringIdentifier::integerIdentifier) + || field->typeName().contains(StringIdentifier::floatIdentifier) + || field->typeName().contains(StringIdentifier::doubleIdentifier)) { + if (!field->needContainer()) { + output << Util::indent(1) + << StringIdentifier::typeNameDataTypeConverter.value( + field->typeName()) + << " " << tmpFunctionName << "() const;" + << Util::lineBreak(); + output << Util::indent(1) << "void set" << field->name() << "(" + << StringIdentifier::typeNameDataTypeConverter.value( + field->typeName()) + << " " << tmpFunctionName << ");" + << Util::lineBreak(2); + } else { + output << Util::indent(1) << "QList<" + << StringIdentifier::typeNameDataTypeConverter.value( + field->typeName()) + << "> " << tmpFunctionName << "() const;" + << Util::lineBreak(); + output << Util::indent(1) << "void set" << field->name() << "(QList<" + << StringIdentifier::typeNameDataTypeConverter.value( + field->typeName()) + << "> " << tmpFunctionName << ");" + << Util::lineBreak(2); + } + } else { + if (!field->needContainer()) { + output << Util::indent(1) + << StringIdentifier::typeNameDataTypeConverter.value( + field->typeName()) + << " " << tmpFunctionName << "() const;" + << Util::lineBreak(); + output << Util::indent(1) << "void set" << field->name() << "(const " + << StringIdentifier::typeNameDataTypeConverter.value( + field->typeName()) + << " &" << tmpFunctionName << ");" + << Util::lineBreak(2); + } else { + output << Util::indent(1) << "QList<" + << StringIdentifier::typeNameDataTypeConverter.value( + field->typeName()) + << "> " << tmpFunctionName << "() const;" + << Util::lineBreak(); + output << Util::indent(1) << "void set" << field->name() << "(const QList<" + << StringIdentifier::typeNameDataTypeConverter.value( + field->typeName()) + << "> &" << tmpFunctionName << ");" + << Util::lineBreak(2); + } + } + } else { + bool isPreCoded = false; + for (const auto &preCodedType : StringIdentifier::opcUaPrecodedTypes) { + if (preCodedType.contains(typeName)) { + isPreCoded = true; + if (preCodedType.deEncoderName().isEmpty()) { + output << Util::indent(1); + if (field->needContainer()) + output << "QList<"; + output << preCodedType.className(); + if (field->needContainer()) + output << ">"; + output << " " << tmpFunctionName << "() const;" + << Util::lineBreak(); + output << Util::indent(1) << "void set" << field->name() << "(const "; + if (field->needContainer()) + output << "QList<"; + output << preCodedType.className(); + if (field->needContainer()) + output << ">"; + output << " &" << tmpFunctionName << ");" + << Util::lineBreak(2); + } else { + output << Util::indent(1); + if (field->needContainer()) + output << "QList<"; + output << preCodedType.deEncoderName(); + if (field->needContainer()) + output << ">"; + output << " " << tmpFunctionName << "() const;" + << Util::lineBreak(); + output << Util::indent(1) << "void set" << field->name() << "(const "; + if (field->needContainer()) + output << "QList<"; + output << preCodedType.deEncoderName(); + if (field->needContainer()) + output << ">"; + output << " &" << tmpFunctionName << ");" + << Util::lineBreak(2); + } + break; + } + } + + if (!isPreCoded) { + if (field->needContainer()) { + output << Util::indent(1) << "QList <" << m_prefix << typeName << "> " + << tmpFunctionName << "() const;" + << Util::lineBreak(); + output << Util::indent(1) << "void set" << field->name() << "(const QList <" + << m_prefix << typeName << "> &" << tmpFunctionName + << ");" + << Util::lineBreak(2); + } else { + bool isEnum = false; + for (const auto &enumeratedType : m_enumeratedTypes) { + if (enumeratedType->name() == field->typeName().split(":").at(1)) + isEnum = true; + } + if (isEnum) { + output << Util::indent(1) << m_prefix << "::" << typeName << " " + << tmpFunctionName << "() const;" + << Util::lineBreak(); + output << Util::indent(1) << "void set" << field->name() << "(const " << m_prefix + << "::" << typeName << " &" << tmpFunctionName << ");" + << Util::lineBreak(2); + } else { + output << Util::indent(1) << m_prefix << typeName << " " + << tmpFunctionName << "() const;" + << Util::lineBreak(); + output << Util::indent(1) << "void set" << field->name() << "(const " << m_prefix + << typeName << " &" << tmpFunctionName << ");" + << Util::lineBreak(2); + } + } + } + } + } else { // is union switchfield, create just getter + output << Util::indent(1) << field->name() << " " << tmpFunctionName << "() const;" + << Util::lineBreak(); + output << Util::lineBreak(); + } + } + } +} + +void DataTypeFileWriter::writeStructuredTypeHeaderDebug(const StructuredType *structuredType, QTextStream &output) +{ + output << Util::indent(1) << "friend QDebug operator<<(QDebug debug, const " << m_prefix << structuredType->name() << " &v);"; + output << Util::lineBreak(2); +} + +void DataTypeFileWriter::writeStructuredTypeCppDebug(const StructuredType *structuredType, QTextStream &output) +{ + const auto typeName = QStringLiteral("%1%2").arg(m_prefix, structuredType->name()); + + output << "/*!" << Util::lineBreak(); + output << Util::indent(1) << "Prints the field values of object \\a v to \\a debug" << Util::lineBreak(); + output << "*/" << Util::lineBreak(); + output << "QDebug operator<<(QDebug debug, const " << typeName << " &v)" << Util::lineBreak(); + output << "{" << Util::lineBreak(); + output << Util::indent(1) << "QDebugStateSaver saver(debug);" << Util::lineBreak(); + output << Util::indent(1) << "debug.nospace().noquote();" << Util::lineBreak(2); + output << Util::indent(1) << "debug << \"" << typeName << " (\";" << Util::lineBreak(2); + + if (!structuredType->hasUnion() && structuredType->hasSwitchfield()) + output << Util::indent(1) << "bool firstFieldPrinted = false;" << Util::lineBreak(2); + + bool isFirst = true; + + QSet<Field *> lengthFields; + + for (const auto &field : structuredType->fields()) { + for (const auto &innerField : structuredType->fields()) { + if (!field->lengthField().isEmpty() && field->lengthField() == innerField->name()) + lengthFields.insert(innerField); + } + } + + for (const auto &field : structuredType->fields()) { + if (structuredType->hasUnion() && field == structuredType->fields().constFirst()) + continue; + + if (field->isInStructuredTypeBitMask()) + continue; + + if (lengthFields.contains(field)) + continue; + + if (structuredType->hasUnion()) + output << Util::indent(1) << "if (static_cast<quint32>(v." << Util::lowerFirstLetter(field->switchField()) << "()) == " << field->switchValue() << ") {" << Util::lineBreak(); + else if (!field->switchField().isEmpty()) + output << Util::indent(1) << "if (v." << Util::lowerFirstLetter(field->switchField()) << "()) {" << Util::lineBreak(); + + int indentOffset = 0; + if (structuredType->hasUnion() || !field->switchField().isEmpty()) + indentOffset = 1; + + if (!structuredType->hasUnion() && !isFirst) { + if (structuredType->hasSwitchfield()) + output << Util::indent(1 + indentOffset) << "if (firstFieldPrinted)" << Util::lineBreak(); + output << Util::indent(1 + (structuredType->hasSwitchfield() ? indentOffset + 1 : 0)) << "debug << \", \";" << Util::lineBreak(); + } + + output << Util::indent(1 + indentOffset) << "debug << \"" << field->name() << ": \";" << Util::lineBreak(); + + // Not all types have an operator<< for QDebug... + const auto isPrecoded = std::find_if(StringIdentifier::opcUaPrecodedTypes.constBegin(), StringIdentifier::opcUaPrecodedTypes.constEnd(), + [&](const auto &entry) { return entry.name() == field->typeNameSecondPart(); }); + + if (isPrecoded == StringIdentifier::opcUaPrecodedTypes.constEnd() || StringIdentifier::precodedTypesWithDebugOperator.contains(field->typeNameSecondPart())) { + output << Util::indent(1 + indentOffset) << "debug << v." << field->lowerFirstName() << "();" << Util::lineBreak(); + } else { + if (field->needContainer()) { + const auto tempListName = QStringLiteral("%1%2").arg(field->lowerFirstName(), "Strings"); + output << Util::indent(1 + indentOffset) << "QStringList " << tempListName << ";" << Util::lineBreak(); + output << Util::indent(1 + indentOffset) << "for (int i = 0; i < v." << field->lowerFirstName() << "().size(); ++i)" << Util::lineBreak(); + output << Util::indent(2 + indentOffset) << tempListName << ".push_back(\"" << isPrecoded->className() << "(...)\"" << ");" << Util::lineBreak(); + output << Util::indent(1 + indentOffset) << "debug << \"QList(\" << " << tempListName << ".join(\", \") << \")\";"; + } else { + output << Util::indent(1 + indentOffset) << "debug << \"" << isPrecoded->className() << "(...)\";"; + } + output << Util::lineBreak(); + } + + if (!structuredType->hasUnion() && structuredType->hasSwitchfield() && field != structuredType->fields().constLast()) + output << Util::indent(1 + indentOffset) << "firstFieldPrinted = true;" << Util::lineBreak(); + + if (structuredType->hasUnion() || !field->switchField().isEmpty()) + output << Util::indent(1) << "}" << Util::lineBreak(); + + output << Util::lineBreak(); + + isFirst = false; + } + + output << Util::indent(1) << "debug << \")\";" << Util::lineBreak(); + output << Util::indent(1) << "return debug;" << Util::lineBreak(); + output << "}" << Util::lineBreak(); +} + +DataTypeFileWriter::GeneratingError DataTypeFileWriter::writeEnumeratedTypes() +{ + QFile file; + const auto fileName = QStringLiteral("%1enumerations.h").arg(m_prefix.toLower()); + QDir dir(m_path); + if (!dir.exists(m_path)) + if (!dir.mkpath(m_path)) + return UnableToWrite; + file.setFileName(dir.absoluteFilePath(fileName)); + file.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream output(&file); + writeLicenseHeader(output); + + if (!m_generatedEnumeratedTypeFilenames.contains( + QStringLiteral("%1enumerations").arg(m_prefix.toLower()))) + m_generatedEnumeratedTypeFilenames.push_back( + QStringLiteral("%1enumerations").arg(m_prefix.toLower())); + output << "#pragma once" + << Util::lineBreak(); + output << "#include <QMetaType>" + << Util::lineBreak(2); + output << "namespace " << m_prefix << " {" + << Util::lineBreak(); + output << "Q_NAMESPACE" + << Util::lineBreak(2); + for (const auto &enumeratedType : m_enumeratedTypes) { + output << "enum class " << enumeratedType->name() << " {" << Util::lineBreak(1); + for (const auto &enumeratedValue : enumeratedType->values()) { + output << Util::indent(1) << enumeratedValue->name() << " = " << enumeratedValue->value(); + if (!enumeratedType->values().endsWith(enumeratedValue)) + output << ","; + output << Util::lineBreak(); + } + output << "};" + << Util::lineBreak(); + output << "Q_ENUM_NS(" << enumeratedType->name() << ")" + << Util::lineBreak(2); + } + + output << Util::lineBreak(); + output << "}" + << Util::lineBreak(); + return GeneratingError::NoError; +} + +DataTypeFileWriter::GeneratingError DataTypeFileWriter::generateFile(const XmlElement *type, + const QString &fileExtension) +{ + QFile file; + const auto fileName = QStringLiteral("%1%2%3").arg(m_prefix.toLower(), + type->name().toLower(), + fileExtension); + QDir dir(m_path); + if (!dir.exists(m_path)) + if (!dir.mkpath(m_path)) + return UnableToWrite; + file.setFileName(dir.absoluteFilePath(fileName)); + file.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream output(&file); + writeLicenseHeader(output); + + auto structuredType = dynamic_cast<const StructuredType *>(type); + if (structuredType) { + if (fileExtension == StringIdentifier::headerIdentifier) { + writeStructuredTypeHeader(structuredType, output); + } else { + if (fileExtension == StringIdentifier::cppIdentifier) { + writeStructuredTypeCpp(structuredType, output); + } + } + } + file.close(); + return NoError; +} + +DataTypeFileWriter::GeneratingError DataTypeFileWriter::generateTypes( + const QList<XmlElement *> &types) +{ + m_generateMapping.append(types); + for (const auto &type : m_generateMapping) { + const auto enumeratedType = dynamic_cast<EnumeratedType *>(type); + if (enumeratedType) + if (!m_enumeratedTypes.contains(enumeratedType)) + m_enumeratedTypes.append(enumeratedType); + } + if (writeEnumeratedTypes() != GeneratingError::NoError) { + return GeneratingError::UnableToWrite; + } + + for (const auto &type : m_generateMapping) { + const auto structuredType = dynamic_cast<StructuredType *>(type); + if (structuredType) { + if (generateFile(type, StringIdentifier::headerIdentifier) != GeneratingError::NoError) + return GeneratingError::UnableToWrite; + if (generateFile(type, StringIdentifier::cppIdentifier) != GeneratingError::NoError) + return GeneratingError::UnableToWrite; + } + } + return NoError; +} + +QList<XmlElement *> DataTypeFileWriter::generateMapping() const +{ + return m_generateMapping; +} + +void DataTypeFileWriter::setGenerateMapping(const QList<XmlElement *> &generateMapping) +{ + m_generateMapping = generateMapping; +} diff --git a/tools/datatypecodegenerator/datatypefilewriter.h b/tools/datatypecodegenerator/datatypefilewriter.h new file mode 100644 index 0000000..aca22a7 --- /dev/null +++ b/tools/datatypecodegenerator/datatypefilewriter.h @@ -0,0 +1,80 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "visitor.h" + +#include <QtCore/qlist.h> +#include <QtCore/qtextstream.h> + +class DataTypeFileWriter : public Visitor +{ +public: + enum GeneratingError { NoError, UnableToWrite }; + DataTypeFileWriter(const QString &path, + const QString &prefix, + const QString &header); + ~DataTypeFileWriter() override = default; + + void visit(EnumeratedType *enumeratedType) override; + void visit(EnumeratedValue *enumeratedValue) override; + void visit(Field *field) override; + void visit(Import *import) override; + void visit(StructuredType *structuredType) override; + void visit(TypeDictionary *typeDictionary) override; + void visit(XmlElement *xmlElement) override; + + GeneratingError generateFile(const XmlElement *type, const QString &fileExtension); + GeneratingError generateTypes(const QList<XmlElement *> &types); + + QList<XmlElement *> generateMapping() const; + void setGenerateMapping(const QList<XmlElement *> &generateMapping); + +private: + QString m_path; + QString m_prefix; + QString m_header; + QList<QString> m_generatedEnumeratedTypeFilenames; + QList<QString> m_generatedStructuredTypeFilenames; + QList<XmlElement *> m_generateMapping; + QList<EnumeratedType *> m_enumeratedTypes; + + void writeLicenseHeader(QTextStream &output); + + void writeStructuredTypeCpp(const StructuredType *structuredType, QTextStream &output); + void writeStructuredTypeCppClassComment(const StructuredType *structuredType, + QTextStream &output); + void writeStructuredTypeCppDataClass(const StructuredType *structuredType, QTextStream &output); + void writeStructuredTypeCppDataClassMember(const StructuredType *structuredType, + QTextStream &output); + void writeStructuredTypeCppConstructors(const StructuredType *structuredType, + QTextStream &output); + void writeStructuredTypeCppOperatorAssignment(const StructuredType *structuredType, + QTextStream &output); + void writeStructuredTypeCppOperatorEquality(const StructuredType *structuredType, + QTextStream &output); + void writeStructuredTypeCppQVariant(const StructuredType *structuredType, QTextStream &output); + void writeStructuredTypeCppDestructor(const StructuredType *structuredType, QTextStream &output); + void writeStructuredTypeCppGetterSetter(const StructuredType *structuredType, + QTextStream &output); + void writeStructuredTypeCppGetter(const StructuredType *structuredType, QTextStream &output); + void writeStructuredTypeCppSetter(const Field *field, + const StructuredType *structuredType, + QTextStream &output); + + void writeStructuredTypeHeader(const StructuredType *structuredType, QTextStream &output); + void writeStructuredTypeHeaderIncludes(const StructuredType *structuredType, + QTextStream &output); + void writeStructuredTypeHeaderUnion(const StructuredType *structuredType, QTextStream &output); + void writeStructuredTypeHeaderGetterSetter(const StructuredType *structuredType, + QTextStream &output); + + void writeStructuredTypeHeaderDebug(const StructuredType *structuredType, + QTextStream &output); + void writeStructuredTypeCppDebug(const StructuredType *structuredType, + QTextStream &output); + + void writeEnumeratedType(const EnumeratedType *enumeratedType, QTextStream &output); + GeneratingError writeEnumeratedTypes(); +}; diff --git a/tools/datatypecodegenerator/dependencydatatypevalidator.cpp b/tools/datatypecodegenerator/dependencydatatypevalidator.cpp new file mode 100644 index 0000000..243fd14 --- /dev/null +++ b/tools/datatypecodegenerator/dependencydatatypevalidator.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "dependencydatatypevalidator.h" +#include "enumeratedtype.h" +#include "field.h" +#include "stringidentifier.h" +#include "structuredtype.h" + +DependencyDataTypeValidator::DependencyDataTypeValidator() + : m_readResolveDependencies(ReadDependencies) +{} + +void DependencyDataTypeValidator::visit(EnumeratedType *enumeratedType) +{ + if (m_readResolveDependencies == DependencyDataTypeValidator::ResolveDependencies) { + if (m_unresolvedDependencyStringList.contains(enumeratedType->name())) { + m_unresolvedDependencyStringList.removeAll(enumeratedType->name()); + m_resolvedDependencyElementList.push_back(enumeratedType); + } + } +} + +void DependencyDataTypeValidator::visit(EnumeratedValue *enumeratedValue) +{ + Q_UNUSED(enumeratedValue); +} + +void DependencyDataTypeValidator::visit(Field *field) +{ + if (m_readResolveDependencies == DependencyDataTypeValidator::ReadDependencies) { + if (!field->typeName().contains("opc:")) { + const auto typeName = field->typeNameSecondPart(); + for (const auto &precoded : StringIdentifier::opcUaPrecodedTypes) { + if (precoded.contains(typeName)) { + return; + } + } + m_unresolvedDependencyStringList.push_back(typeName); + } + } +} + +void DependencyDataTypeValidator::visit(Import *import) +{ + Q_UNUSED(import); +} + +void DependencyDataTypeValidator::visit(StructuredType *structuredType) +{ + if (m_readResolveDependencies == DependencyDataTypeValidator::ResolveDependencies) { + if (m_unresolvedDependencyStringList.contains(structuredType->name())) { + m_unresolvedDependencyStringList.removeAll(structuredType->name()); + m_resolvedDependencyElementList.push_back(structuredType); + for (const auto &field : structuredType->fields()) { + const auto typeName = field->typeNameSecondPart(); + + if (!StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) { + bool isPrecoded = false; + for (const auto &precoded : StringIdentifier::opcUaPrecodedTypes) { + if (precoded.contains(typeName)) { + isPrecoded = true; + break; + } + } + if (!isPrecoded && !m_unresolvedDependencyStringList.contains(typeName)) { + bool isResolved = false; + for (const auto &type : m_resolvedDependencyElementList) { + if (type->name() == typeName) { + isResolved = true; + break; + } + } + if (!isResolved) + m_unresolvedDependencyStringList.push_back(typeName); + } + } + } + } + } +} + +void DependencyDataTypeValidator::visit(TypeDictionary *typeDictionary) +{ + Q_UNUSED(typeDictionary); +} + +void DependencyDataTypeValidator::visit(XmlElement *xmlElement) +{ + Q_UNUSED(xmlElement); +} + +QStringList DependencyDataTypeValidator::unresolvedDependencyStringList() const +{ + return m_unresolvedDependencyStringList; +} + +QList<XmlElement *> DependencyDataTypeValidator::resolvedDependencyElementList() const +{ + return m_resolvedDependencyElementList; +} + +void DependencyDataTypeValidator::setReadResolveDependencies( + const ReadResolveDependencies &readResolveDependencies) +{ + m_readResolveDependencies = readResolveDependencies; +} diff --git a/tools/datatypecodegenerator/dependencydatatypevalidator.h b/tools/datatypecodegenerator/dependencydatatypevalidator.h new file mode 100644 index 0000000..aad3e4e --- /dev/null +++ b/tools/datatypecodegenerator/dependencydatatypevalidator.h @@ -0,0 +1,38 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "visitor.h" + +#include <QtCore/qlist.h> + +class XmlElement; + +class DependencyDataTypeValidator : public Visitor +{ +public: + enum ReadResolveDependencies { ReadDependencies, ResolveDependencies }; + + DependencyDataTypeValidator(); + ~DependencyDataTypeValidator() override = default; + + void visit(EnumeratedType *enumeratedType) override; + void visit(EnumeratedValue *enumeratedValue) override; + void visit(Field *field) override; + void visit(Import *import) override; + void visit(StructuredType *structuredType) override; + void visit(TypeDictionary *typeDictionary) override; + void visit(XmlElement *xmlElement) override; + + QStringList unresolvedDependencyStringList() const; + + QList<XmlElement *> resolvedDependencyElementList() const; + + void setReadResolveDependencies(const ReadResolveDependencies &readResolveDependencies); + +private: + ReadResolveDependencies m_readResolveDependencies; + QList<XmlElement *> m_resolvedDependencyElementList; + QStringList m_unresolvedDependencyStringList; +}; diff --git a/tools/datatypecodegenerator/enumeratedtype.cpp b/tools/datatypecodegenerator/enumeratedtype.cpp new file mode 100644 index 0000000..b31ed43 --- /dev/null +++ b/tools/datatypecodegenerator/enumeratedtype.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "enumeratedtype.h" +#include "enumeratedvalue.h" +#include "visitor.h" + +#include <QtCore/qdebug.h> + +EnumeratedType::EnumeratedType(const QString &name, quint32 lengthInBits) + : XmlElement(name) + , m_lengthInBits(lengthInBits) +{} + +EnumeratedType::~EnumeratedType() +{ + qDeleteAll(m_values); +} + +quint32 EnumeratedType::lengthInBits() const +{ + return m_lengthInBits; +} + +void EnumeratedType::setLengthInBits(quint32 lengthInBits) +{ + m_lengthInBits = lengthInBits; +} + +void EnumeratedType::addValue(EnumeratedValue *enumeratedValue) +{ + m_values.push_back(enumeratedValue); +} + +void EnumeratedType::print() const +{ + XmlElement::print(); + qDebug() << "LengthInBits: " << m_lengthInBits; + for (int i = 0; i < m_values.size(); i++) + m_values.at(i)->print(); +} + +void EnumeratedType::accept(Visitor *visitor) +{ + visitor->visit(this); + for (auto &enumeratedValues : m_values) + enumeratedValues->accept(visitor); +} + +QList<EnumeratedValue *> EnumeratedType::values() const +{ + return m_values; +} + +void EnumeratedType::setValues(const QList<EnumeratedValue *> &values) +{ + m_values = values; +} diff --git a/tools/datatypecodegenerator/enumeratedtype.h b/tools/datatypecodegenerator/enumeratedtype.h new file mode 100644 index 0000000..7e212e6 --- /dev/null +++ b/tools/datatypecodegenerator/enumeratedtype.h @@ -0,0 +1,34 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "xmlelement.h" + +#include <QtCore/qlist.h> + +class EnumeratedValue; +class Visitor; + +class EnumeratedType : public XmlElement + +{ +public: + EnumeratedType(const QString &name, quint32 lengthInBits); + ~EnumeratedType(); + + void addValue(EnumeratedValue *enumeratedValue); + void print() const override; + + quint32 lengthInBits() const; + void setLengthInBits(quint32 lengthInBits); + + QList<EnumeratedValue *> values() const; + void setValues(const QList<EnumeratedValue *> &values); + + virtual void accept(Visitor *visitor) override; + +private: + quint32 m_lengthInBits; + QList<EnumeratedValue *> m_values; +}; diff --git a/tools/datatypecodegenerator/enumeratedvalue.cpp b/tools/datatypecodegenerator/enumeratedvalue.cpp new file mode 100644 index 0000000..f28769c --- /dev/null +++ b/tools/datatypecodegenerator/enumeratedvalue.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "enumeratedvalue.h" +#include "visitor.h" + +#include <QtCore/qdebug.h> + +EnumeratedValue::EnumeratedValue(const QString &name, qint32 value) + : XmlElement(name) + , m_value(value) +{} + +void EnumeratedValue::print() const +{ + XmlElement::print(); + qDebug() << "Value: " << m_value; +} + +void EnumeratedValue::accept(Visitor *visitor) +{ + visitor->visit(this); +} + +qint32 EnumeratedValue::value() const +{ + return m_value; +} + +void EnumeratedValue::setValue(qint32 value) +{ + m_value = value; +} diff --git a/tools/datatypecodegenerator/enumeratedvalue.h b/tools/datatypecodegenerator/enumeratedvalue.h new file mode 100644 index 0000000..fc1d85c --- /dev/null +++ b/tools/datatypecodegenerator/enumeratedvalue.h @@ -0,0 +1,24 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "xmlelement.h" + +class Visitor; + +class EnumeratedValue : public XmlElement +{ +public: + EnumeratedValue(const QString &name, qint32 value); + ~EnumeratedValue() override = default; + + void print() const override; + virtual void accept(Visitor *visitor) override; + + qint32 value() const; + void setValue(qint32 value); + +private: + qint32 m_value; +}; diff --git a/tools/datatypecodegenerator/field.cpp b/tools/datatypecodegenerator/field.cpp new file mode 100644 index 0000000..97499a9 --- /dev/null +++ b/tools/datatypecodegenerator/field.cpp @@ -0,0 +1,142 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "field.h" +#include "visitor.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qstringlist.h> + +Field::Field(const QString &name, const QString &typeName, const QString &lengthField) + : XmlElement(name) + , m_lengthField(lengthField) + , m_typeName(typeName) +{} + +QString Field::typeName() const +{ + return m_typeName; +} + +QString Field::typeNameSecondPart() const +{ + return m_typeName.split(':').value(1, QString()); +} + +void Field::setTypeName(const QString &typeName) +{ + m_typeName = typeName; +} + +QString Field::lengthField() const +{ + return m_lengthField; +} + +void Field::setLengthField(const QString &lengthField) +{ + m_lengthField = lengthField; +} + +bool Field::hasLengthField() const +{ + return m_hasLengthField; +} + +void Field::setHasLengthField(bool hasLengthField) +{ + m_hasLengthField = hasLengthField; +} + +bool Field::needContainer() const +{ + return m_needContainer; +} + +void Field::setNeedContainer(bool needContainer) +{ + m_needContainer = needContainer; +} + +bool Field::isInStructuredTypeBitMask() const +{ + return m_isInStructuredTypeBitMask; +} + +void Field::setIsInStructuredTypeBitMask(bool isInStructuredTypeBitMask) +{ + m_isInStructuredTypeBitMask = isInStructuredTypeBitMask; +} + +int Field::length() const +{ + return m_length; +} + +void Field::setLength(int length) +{ + m_length = length; +} + +QString Field::switchField() const +{ + return m_switchField; +} + +void Field::setSwitchField(const QString &switchField) +{ + m_switchField = switchField; +} + +int Field::switchValue() const +{ + return m_switchValue; +} + +void Field::setSwitchValue(int switchValue) +{ + m_switchValue = switchValue; +} + +bool Field::isUnion() const +{ + return m_isUnion; +} + +void Field::setIsUnion(bool isUnion) +{ + m_isUnion = isUnion; +} + +bool Field::recursive() const +{ + return m_recursive; +} + +void Field::setRecursive(bool recursive) +{ + m_recursive = recursive; +} + +bool Field::isEnum() const +{ + return m_isEnum; +} + +void Field::setIsEnum(bool newIsEnum) +{ + m_isEnum = newIsEnum; +} + +void Field::print() const +{ + XmlElement::print(); + qDebug() << "Type name: " << m_typeName; + if (!m_lengthField.isEmpty()) + qDebug() << "Length field: " << m_lengthField; +} + +void Field::accept(Visitor *visitor) +{ + visitor->visit(this); +} diff --git a/tools/datatypecodegenerator/field.h b/tools/datatypecodegenerator/field.h new file mode 100644 index 0000000..0e36bcf --- /dev/null +++ b/tools/datatypecodegenerator/field.h @@ -0,0 +1,62 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "xmlelement.h" + +class Field : public XmlElement +{ +public: + Field(const QString &name, const QString &typeName, const QString &lengthField = QString()); + ~Field() override = default; + + void print() const override; + virtual void accept(Visitor *visitor) override; + + QString typeName() const; + QString typeNameSecondPart() const; + void setTypeName(const QString &typeName); + + QString lengthField() const; + void setLengthField(const QString &lengthField); + + bool hasLengthField() const; + void setHasLengthField(bool hasLengthField); + + bool needContainer() const; + void setNeedContainer(bool needContainer); + + bool isInStructuredTypeBitMask() const; + void setIsInStructuredTypeBitMask(bool isInStructuredTypeBitMask); + + int length() const; + void setLength(int length); + + QString switchField() const; + void setSwitchField(const QString &switchField); + + int switchValue() const; + void setSwitchValue(int switchValue); + + bool isUnion() const; + void setIsUnion(bool isUnion); + + bool recursive() const; + void setRecursive(bool recursive); + + bool isEnum() const; + void setIsEnum(bool newIsEnum); +private: + bool m_hasLengthField{false}; + bool m_needContainer{false}; + bool m_isInStructuredTypeBitMask{false}; + bool m_isUnion{false}; + int m_length; + int m_switchValue{0}; + bool m_recursive{false}; + bool m_isEnum{false}; + QString m_lengthField; + QString m_switchField; + QString m_typeName; +}; diff --git a/tools/datatypecodegenerator/import.cpp b/tools/datatypecodegenerator/import.cpp new file mode 100644 index 0000000..7702430 --- /dev/null +++ b/tools/datatypecodegenerator/import.cpp @@ -0,0 +1,42 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "import.h" +#include "visitor.h" + +#include <QtCore/qdebug.h> + +Import::Import(const QString &nameSpace, const QString &location) + : m_location(location) + , m_nameSpace(nameSpace) +{} + +QString Import::nameSpace() const +{ + return m_nameSpace; +} + +void Import::setNameSpace(const QString &nameSpace) +{ + m_nameSpace = nameSpace; +} + +QString Import::location() const +{ + return m_location; +} + +void Import::setLocation(const QString &location) +{ + m_location = location; +} + +void Import::print() const +{ + qDebug() << "Namespace:" << m_nameSpace << "\tLocation:" << m_location; +} + +void Import::accept(Visitor *visitor) +{ + visitor->visit(this); +} diff --git a/tools/datatypecodegenerator/import.h b/tools/datatypecodegenerator/import.h new file mode 100644 index 0000000..9343904 --- /dev/null +++ b/tools/datatypecodegenerator/import.h @@ -0,0 +1,28 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "xmlelement.h" + +class Visitor; + +class Import : public XmlElement +{ +public: + Import(const QString &nameSpace, const QString &location); + ~Import() override = default; + + virtual void print() const override; + virtual void accept(Visitor *visitor) override; + + QString nameSpace() const; + void setNameSpace(const QString &nameSpace); + + QString location() const; + void setLocation(const QString &location); + +private: + QString m_location; + QString m_nameSpace; +}; diff --git a/tools/datatypecodegenerator/main.cpp b/tools/datatypecodegenerator/main.cpp new file mode 100644 index 0000000..20ab28a --- /dev/null +++ b/tools/datatypecodegenerator/main.cpp @@ -0,0 +1,181 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "recursivedescentparser.h" + +#include <QtCore/qcommandlineoption.h> +#include <QtCore/qcommandlineparser.h> +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qregularexpression.h> +#include <QtCore/qstring.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qtextstream.h> +#include <QtCore/qxmlstream.h> + +#include <cstdlib> + +bool readBsdFile(RecursiveDescentParser &recursiveDescentParser, + const QString &fileName, + bool dependencyInput) +{ + switch (recursiveDescentParser.parseFile(fileName, dependencyInput)) { + case RecursiveDescentParser::NoError: + return true; + case RecursiveDescentParser::InvalidFileName: + qCritical() << "Error: File does not exist:" << fileName; + return false; + case RecursiveDescentParser::InvalidTypeDictionaryEntry: + qCritical() << "Error: Invalid TypeDictionary entry in" << fileName; + return false; + case RecursiveDescentParser::InvalidStructuredTypeEntry: + qCritical() << "Error: Invalid StructuredType entry in" << fileName; + return false; + case RecursiveDescentParser::InvalidEnumeratedTypeEntry: + qCritical() << "Error: Invalid EnumeratedType entry in" << fileName; + return false; + case RecursiveDescentParser::InvalidImportEntry: + qCritical() << "Error: Invalid Import entry in" << fileName; + return false; + case RecursiveDescentParser::InvalidFieldEntry: + qCritical() << "Error: Invalid Field entry in" << fileName; + return false; + case RecursiveDescentParser::InvalidEnumeratedValueEntry: + qCritical() << "Error: Invalid EnumeratedValue entry in" << fileName; + return false; + case RecursiveDescentParser::CannotFullyGenerateNamespaceZero: + qCritical() << "Error: Full generation of namespace 0 is currently not " + "supported"; + return false; + case RecursiveDescentParser::MissingDependency: + qCritical() << "Error: Missing dependency Type found in" << fileName; + return false; + default: + qCritical() << "Error: Unknown parsing error occurred"; + return false; + } +} + +bool generateBsdFiles(RecursiveDescentParser &recursiveDescentParser, + const QString &outputPath, + const QString &dataPrefix, + const QString &outputFileHeader) +{ + switch (recursiveDescentParser.generateInputFiles(outputPath, + dataPrefix, + outputFileHeader)) { + case RecursiveDescentParser::NoError: + return true; + case RecursiveDescentParser::UnableToWriteFile: + qCritical() << "Error: Unable to write files at specified path."; + return false; + case RecursiveDescentParser::MissingDependency: + qCritical() << "Error: Unresolved dependent type occurred."; + return false; + case RecursiveDescentParser::UnableToResolveDependency: + qCritical() << "Error: Unresolvable mapping occurred."; + default: + qCritical() << "Error: Unknown file generating error occurred."; + return false; + } +} + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + const QString appName = QStringLiteral("qopcuaxmldatatypes2cpp"); + const QString appVersion = QStringLiteral("1.0"); + + auto arguments = a.arguments(); + arguments.replace(0, appName); + + const auto outputFileHeader = QStringLiteral("/*\n" + " * This file was generated by %1 version %2\n" + " * Command line used: %3\n" + " */") + .arg(appName, + appVersion, + arguments.join(QLatin1Char(' '))); + + QCoreApplication::setApplicationName(appName); + QCoreApplication::setApplicationVersion(appVersion); + QCommandLineParser parser; + parser.setApplicationDescription( + "Code generator for custom data models.\n" + "Converts OPC UA .bsd files into enums and C++ data classes and generates a class " + "to decode and encode the values from/to a QOpcUaExtensionObject with binary body or a QByteArray."); + parser.addHelpOption(); + parser.addVersionOption(); + + const QCommandLineOption inputFileOption(QStringList() << "i" + << "input", + "A primary input file. Will generate code for all contained types and " + "check for missing dependencies", + "file"); + parser.addOption(inputFileOption); + const QCommandLineOption inputDependencyFileOption( + QStringList() << "d" + << "dependencyinput", + "A dependency input file. Only types required by primary input files will be generated", + "file"); + parser.addOption(inputDependencyFileOption); + const QCommandLineOption outputDirectoryPathOption(QStringList() << "o" + << "output", + "output directory for the generated C++ files.", + "path"); + parser.addOption(outputDirectoryPathOption); + const QCommandLineOption + outputPrefixOption(QStringList() << "p" + << "prefix", + "prefix for the generated files, default is GeneratedOpcUa", + "prefix", + "GeneratedOpcUa"); + parser.addOption(outputPrefixOption); + + parser.process(a); + + if (!parser.isSet(inputFileOption)) { + qCritical() << "Error: At least one input file must be specified"; + parser.showHelp(1); + return EXIT_FAILURE; + } + + if (!parser.isSet(outputDirectoryPathOption)) { + qCritical() << "Error: The output path must be specified."; + parser.showHelp(1); + return EXIT_FAILURE; + } + + if (parser.values(outputPrefixOption).size() > 1) + qInfo() << "Info: The first output prefix will be used"; + if (!parser.values(outputPrefixOption) + .at(0) + .contains(QRegularExpression("^[A-Za-z]+[A-Za-z0-9]*$"))) { + qCritical() << "Error: The prefix contains illegal characters"; + qInfo() << "Info: The prefix must consist of letters and numbers and start with a letter"; + return EXIT_FAILURE; + } + + const auto dataPrefix = parser.value(outputPrefixOption); + + auto success = true; + RecursiveDescentParser recursiveDescentParser; + const QStringList inputFileNames = parser.values(inputFileOption); + for (const auto &fileName : inputFileNames) + success &= readBsdFile(recursiveDescentParser, fileName, false); + const QStringList dependencyInputFileNames = parser.values(inputDependencyFileOption); + for (const auto &fileName : dependencyInputFileNames) + success &= readBsdFile(recursiveDescentParser, fileName, true); + if (success) { + const auto outputPath = parser.value(outputDirectoryPathOption); + if (generateBsdFiles(recursiveDescentParser, + outputPath, + dataPrefix, + outputFileHeader)) { + qInfo() << "Info: All types were successfully generated"; + return EXIT_SUCCESS; + } + } + return EXIT_FAILURE; +} diff --git a/tools/datatypecodegenerator/mappingfilegenerator.cpp b/tools/datatypecodegenerator/mappingfilegenerator.cpp new file mode 100644 index 0000000..de9e2d7 --- /dev/null +++ b/tools/datatypecodegenerator/mappingfilegenerator.cpp @@ -0,0 +1,636 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "util.h" +#include "enumeratedtype.h" +#include "field.h" +#include "mappingfilegenerator.h" +#include "stringidentifier.h" +#include "structuredtype.h" + +#include <QtCore/qdir.h> +#include <QtCore/qfile.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +MappingFileGenerator::MappingFileGenerator(const QList<XmlElement *> &generateMapping, + const QString &path, + const QString &prefix, + const QString &header) + : m_generateMapping(generateMapping) + , m_path(path) + , m_prefix(prefix) + , m_header(header) +{ + for (const auto &type : m_generateMapping) { + const auto structuredType = dynamic_cast<StructuredType *>(type); + if (structuredType) { + for (const auto &possibleField : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (possibleField->name() == field->lengthField() + && !m_lengthFields.contains(possibleField)) + m_lengthFields.push_back(possibleField); + + if (!m_switchFields.contains(possibleField) + && possibleField->name() == field->switchField()) + m_switchFields.push_back(possibleField); + } + } + } + } +} + +void MappingFileGenerator::addGenerateMapping( + const QList<XmlElement *> &generateMapping) +{ + for (const auto &mapping : generateMapping) { + if (!m_generateMapping.contains(mapping)) + m_generateMapping.push_back(mapping); + } + for (const auto &mapping : m_generateMapping) { + const auto structuredType = dynamic_cast<StructuredType *>(mapping); + if (structuredType) { + for (const auto &possibleField : structuredType->fields()) { + for (const auto &field : structuredType->fields()) { + if (possibleField->name() == field->lengthField() + && !m_lengthFields.contains(possibleField)) + m_lengthFields.push_back(possibleField); + + if (!m_switchFields.contains(possibleField) + && possibleField->name() == field->switchField()) + m_switchFields.push_back(possibleField); + } + } + } + } +} + +MappingFileGenerator::MappingError MappingFileGenerator::generateMapping() +{ + auto error = sortMappings(); + if (error != NoError) + return error; + + if (generateMappingHeader() == NoError) + error = generateMappingCpp(); + else + error = UnanbleToWrite; + return error; +} + +MappingFileGenerator::MappingError MappingFileGenerator::generateMappingHeader() +{ + QFile file; + const auto fileName = QStringLiteral("%1binarydeencoder%2") + .arg(m_prefix.toLower(), StringIdentifier::headerIdentifier); + QDir dir(m_path); + if (!dir.exists(m_path)) + if (!dir.mkpath(m_path)) + return UnanbleToWrite; + file.setFileName(dir.absoluteFilePath(fileName)); + + file.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream output(&file); + + // Print header (if present) + if (!m_header.isEmpty()) + output << m_header << Util::lineBreak(2); + output << "#pragma once" + << Util::lineBreak(2); + QList<QString> includes; + for (auto mapping : m_generateMapping) { + const auto enumeratedType = dynamic_cast<EnumeratedType *>(mapping); + if (enumeratedType) { + if (!includes.contains( + QLatin1String("#include \"%1enumerations.h\"%2").arg(m_prefix.toLower(), Util::lineBreak()))) + includes.push_back( + QLatin1String("#include \"%1enumerations.h\"%2").arg(m_prefix.toLower(), Util::lineBreak())); + } else { + const auto structuredType = dynamic_cast<StructuredType *>(mapping); + if (structuredType) + if (!includes.contains( + QLatin1String("#include \"%1%2.h\"%3") + .arg(m_prefix.toLower(), structuredType->name().toLower(), Util::lineBreak()))) + includes.push_back( + QLatin1String("#include \"%1%2.h\"%3") + .arg(m_prefix.toLower(), structuredType->name().toLower(), Util::lineBreak())); + } + } + + includes.sort(); + for (const auto &include : includes) { + output << include; + } + output << Util::lineBreak(); + output << "#include <QtOpcUa/QOpcUaBinaryDataEncoding>" + << Util::lineBreak(); + output << Util::lineBreak(); + + output << "class " << m_prefix << "BinaryDeEncoder" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << "public:" + << Util::lineBreak(); + output << Util::lineBreak(); + output << Util::indent(1) << m_prefix << "BinaryDeEncoder(QByteArray *buffer);" + << Util::lineBreak(); + output << Util::indent(1) << m_prefix << "BinaryDeEncoder(QOpcUaExtensionObject &object);" + << Util::lineBreak(); + output << Util::lineBreak(); + output << Util::indent(1) << "template <typename T>" + << Util::lineBreak(); + output << Util::indent(1) << "T decode(bool &success);" + << Util::lineBreak(); + output << Util::lineBreak(); + output << Util::indent(1) << "template <typename T>" + << Util::lineBreak(); + output << Util::indent(1) << "QList<T> decodeArray(bool &success);" + << Util::lineBreak(); + output << Util::lineBreak(); + output << Util::indent(1) << "template <typename T>" + << Util::lineBreak(); + output << Util::indent(1) << "bool encode(const T &src);" + << Util::lineBreak(); + output << Util::lineBreak(); + output << Util::indent(1) << "template <typename T>" + << Util::lineBreak(); + output << Util::indent(1) << "bool encodeArray(const QList<T> &src);" + << Util::lineBreak(); + output << Util::lineBreak(); + output << Util::indent(1) << "QOpcUaBinaryDataEncoding &binaryDataEncoding();" + << Util::lineBreak(); + output << Util::lineBreak(); + + output << "private:" + << Util::lineBreak(); + output << Util::indent(1) << "QScopedPointer<QOpcUaBinaryDataEncoding> m_binaryDataEncoding;" + << Util::lineBreak(); + + output << "};" + << "\n\n"; + generateDeEncodingArray(output); + generateDeEncoding(output); + return NoError; +} + +MappingFileGenerator::MappingError MappingFileGenerator::generateMappingCpp() +{ + QFile file; + QDir dir(m_path); + const auto fileName = QStringLiteral("%1binarydeencoder%2") + .arg(m_prefix.toLower(), StringIdentifier::cppIdentifier); + if (!dir.exists(m_path)) + if (!dir.mkpath(m_path)) + return UnanbleToWrite; + file.setFileName(dir.absoluteFilePath(fileName)); + + file.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream output(&file); + + // Print header (if present) + if (!m_header.isEmpty()) + output << m_header << "\n\n"; + + output << "#include \"" << m_prefix.toLower() << "binarydeencoder.h\"" << Util::lineBreak(); + output << Util::lineBreak(); + output << m_prefix << "BinaryDeEncoder::" << m_prefix << "BinaryDeEncoder (QByteArray *buffer)" + << Util::lineBreak(); + output << Util::indent(1) << ": m_binaryDataEncoding(new QOpcUaBinaryDataEncoding(buffer))" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(2); + output << m_prefix << "BinaryDeEncoder::" << m_prefix + << "BinaryDeEncoder " + "(QOpcUaExtensionObject &object) " + << Util::lineBreak(); + output << Util::indent(1) << ": m_binaryDataEncoding(new " + "QOpcUaBinaryDataEncoding(&object.encodedBodyRef()))" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(); + output << "QOpcUaBinaryDataEncoding &" << m_prefix << "BinaryDeEncoder::binaryDataEncoding()" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << Util::indent(1) << "return *m_binaryDataEncoding.data();" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(); + return NoError; +} + +void MappingFileGenerator::generateFieldDecoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level) +{ + output << Util::indent(level) << "temp.set" << field->name() << "("; + generateScalarOrArrayDecoding(output, structuredType, field); + output << ");" << Util::lineBreak(); + output << Util::indent(level) << "if (!success)" << Util::lineBreak(); + output << Util::indent(level + 1) << "return {};" << Util::lineBreak(); +} + +void MappingFileGenerator::generateScalarOrArrayDecoding(QTextStream &output, const StructuredType *structuredType, const Field *field) +{ + Q_UNUSED(structuredType); + const auto builtinType = StringIdentifier::typeNameDataTypeConverter.constFind(field->typeName()); + if (builtinType != StringIdentifier::typeNameDataTypeConverter.constEnd()) { + output << "m_binaryDataEncoding->decode" << (field->lengthField().isEmpty() ? "" : "Array") << "<" << builtinType.value() << ">(success)"; + return; + } + + bool isPrecoded = false; + for (const auto &precodedType : StringIdentifier::opcUaPrecodedTypes) { + if (precodedType.contains(Util::removeNamespace(field->typeName()))) { + output << "m_binaryDataEncoding->decode"; + if (!field->lengthField().isEmpty()) + output << "Array"; + output << "<"; + if (!precodedType.deEncoderName().isEmpty()) + output << precodedType.deEncoderName() << ", QOpcUa::Types::NodeId"; + else + output << precodedType.className(); + output << ">(success)"; + isPrecoded = true; + break; + } + } + if (!isPrecoded) { + output << "decode"; + if (!field->lengthField().isEmpty()) + output << "Array"; + output << "<" << m_prefix << (field->isEnum() ? "::" : "") << Util::removeNamespace(field->typeName()) << ">(success)"; + } +} + +void MappingFileGenerator::generateOptionalFieldDecoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level) +{ + int index = -1; + QSet<QString> usedSwitchFields; + for (const auto ¤t : structuredType->fields()) { + if (!current->switchField().isEmpty() && !usedSwitchFields.contains(current->switchField())) { + index++; + usedSwitchFields.insert(current->switchField()); + } + + if (current == field) + break; + } + + output << Util::indent(level) << "if (decodingMask & " << QStringLiteral("(1 << %1)").arg(index) << ") {" << Util::lineBreak(); + generateFieldDecoding(output, structuredType, field, level + 1); + output << Util::indent(level + 1) << "temp.set" << field->switchField() << "(true);" << Util::lineBreak(); + output << Util::indent(level) << "}" << Util::lineBreak(); +} + +void MappingFileGenerator::generateUnionFieldDecoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level) +{ + output << Util::indent(level) << QStringLiteral("if (switchField == %1) {").arg(field->switchValue()) << Util::lineBreak(); + generateFieldDecoding(output, structuredType, field, level + 1); + output << Util::indent(level) << "}" << Util::lineBreak(); +} + +void MappingFileGenerator::generateFieldEncoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level) +{ + output << Util::indent(level) << "if (!"; + generateScalarOrArrayEncoding(output, structuredType, field); + output << ")" << Util::lineBreak(); + output << Util::indent(level + 1) << "return false;" << Util::lineBreak(); +} + +void MappingFileGenerator::generateScalarOrArrayEncoding(QTextStream &output, const StructuredType *structuredType, const Field *field) +{ + Q_UNUSED(structuredType); + const auto builtinType = StringIdentifier::typeNameDataTypeConverter.constFind(field->typeName()); + if (builtinType != StringIdentifier::typeNameDataTypeConverter.constEnd()) { + output << "m_binaryDataEncoding->encode" << (field->lengthField().isEmpty() ? "" : "Array") << "<" << builtinType.value() << ">(src." << field->lowerFirstName() << "())"; + return; + } + + bool isPrecoded = false; + for (const auto &precodedType : StringIdentifier::opcUaPrecodedTypes) { + if (precodedType.contains(Util::removeNamespace(field->typeName()))) { + output << "m_binaryDataEncoding->encode"; + if (!field->lengthField().isEmpty()) + output << "Array"; + output << "<"; + if (!precodedType.deEncoderName().isEmpty()) + output << precodedType.deEncoderName() << ", QOpcUa::Types::NodeId"; + else + output << precodedType.className(); + output << ">(src." << field->lowerFirstName() << "())"; + isPrecoded = true; + break; + } + } + if (!isPrecoded) { + output << "encode"; + if (!field->lengthField().isEmpty()) + output << "Array"; + output << "<" << m_prefix << (field->isEnum() ? "::" : "") << Util::removeNamespace(field->typeName()) << ">(src." << field->lowerFirstName() << "())"; + } +} + +void MappingFileGenerator::generateOptionalFieldEncoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level) +{ + output << Util::indent(level) << "if (src." << Util::lowerFirstLetter(field->switchField()) << "()) {" << Util::lineBreak(); + generateFieldEncoding(output, structuredType, field, level + 1); + output << Util::indent(level) << "}" << Util::lineBreak(); +} + +void MappingFileGenerator::generateUnionFieldEncoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level) +{ + output << Util::indent(level) << "if (static_cast<quint32>(src." << structuredType->fields().constFirst()->lowerFirstName() << "()) == " << field->switchValue() << ") {" << Util::lineBreak(); + generateFieldEncoding(output, structuredType, field, level + 1); + output << Util::indent(level) << "}" << Util::lineBreak(); +} + +void MappingFileGenerator::generateDeEncodingArray(QTextStream &output) +{ + output << "template<typename T>" + << Util::lineBreak(); + output << "inline QList<T> " << m_prefix << "BinaryDeEncoder::decodeArray(bool &success)" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << Util::indent(1) << "QList<T> temp;" + << Util::lineBreak(2); + output << Util::indent(1) << "qint32 size = m_binaryDataEncoding->decode<qint32>(success);" + << Util::lineBreak(); + output << Util::indent(1) << "if (!success)" + << Util::lineBreak(); + output << Util::indent(2) << "return {};"; + output << Util::lineBreak(2); + output << Util::indent(1) << "for (int i = 0; i < size; ++i) {" + << Util::lineBreak(); + output << Util::indent(2) << "temp.push_back(decode<T>(success));" + << Util::lineBreak(); + output << Util::indent(2) << "if (!success)" + << Util::lineBreak(); + output << Util::indent(3) << "return {};" + << Util::lineBreak(); + output << Util::indent(1) << "}" + << Util::lineBreak(2); + output << Util::indent(1) << "return temp;" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(2); + output << "template<typename T>" + << Util::lineBreak(); + output << "inline bool " << m_prefix << "BinaryDeEncoder::encodeArray(const QList<T> &src)" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << Util::indent(1) << "if (src.size() > (std::numeric_limits<qint32>::max()))" + << Util::lineBreak(); + output << Util::indent(2) << "return false;" + << Util::lineBreak(2); + output << Util::indent(1) << "if (!m_binaryDataEncoding->encode<qint32>(src.size()))" + << Util::lineBreak(); + output << Util::indent(2) << "return false;" + << Util::lineBreak(); + output << Util::indent(1) << "for (const auto &element : src) {" + << Util::lineBreak(); + output << Util::indent(2) << "if (!encode<T>(element))" + << Util::lineBreak(); + output << Util::indent(3) << "return false;" + << Util::lineBreak(); + output << Util::indent(1) << "}" + << Util::lineBreak(); + output << Util::indent(1) << "return true;" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(2); +} + +void MappingFileGenerator::generateDeEncoding(QTextStream &output) +{ + for (const auto &mapping : m_generateMapping) { + const auto enumeratedType = dynamic_cast<EnumeratedType *>(mapping); + if (enumeratedType) { + generateDecodingEnumeratedType(output, enumeratedType); + generateEncodingEnumeratedType(output, enumeratedType); + } else { + const auto structuredType = dynamic_cast<StructuredType *>(mapping); + if (structuredType) { + generateDecodingStructuredType(output, structuredType); + generateEncodingStructuredType(output, structuredType); + } + } + } +} + +void MappingFileGenerator::generateDecodingEnumeratedType(QTextStream &output, + const EnumeratedType *enumeratedType) +{ + output << "template<>" + << Util::lineBreak(); + output << "inline " << m_prefix << "::" << enumeratedType->name() << " " << m_prefix + << "BinaryDeEncoder::decode(bool &success)" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << Util::indent(1) << "return static_cast<" << m_prefix << "::" << enumeratedType->name() << ">(" + "m_binaryDataEncoding->decode<qint32>(success));" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(2); +} + +void MappingFileGenerator::generateEncodingEnumeratedType(QTextStream &output, + const EnumeratedType *enumeratedType) +{ + output << "template<>" + << Util::lineBreak(); + output << "inline bool " << m_prefix << "BinaryDeEncoder" + << "::encode<" << m_prefix << "::" << enumeratedType->name() << ">(const " << m_prefix + << "::" << enumeratedType->name() << " &src)" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + output << Util::indent(1) << "return m_binaryDataEncoding->encode<qint32>(static_cast<qint32>(src));" + << Util::lineBreak(); + output << "}" + << Util::lineBreak(2); +} + +void MappingFileGenerator::generateDecodingStructuredType(QTextStream &output, + const StructuredType *structuredType) +{ + output << "template<>" + << Util::lineBreak(); + output << "inline " << m_prefix << structuredType->name() << " " << m_prefix + << "BinaryDeEncoder::decode<" << m_prefix << structuredType->name() << ">(bool &success)" << Util::lineBreak() + << "{" << Util::lineBreak(); + output << Util::indent(1) << m_prefix << structuredType->name() << " temp;" + << Util::lineBreak(2); + + if (structuredType->containsBitMask()) { + output << Util::indent(1) <<"const auto decodingMask = " + << "m_binaryDataEncoding->decode<quint32>(success);" << Util::lineBreak() + << Util::indent(1) << "if (!success)" << Util::lineBreak() + << Util::indent(2) << "return {};" << Util::lineBreak(2); + } else if (structuredType->hasUnion()) { + output << Util::indent(1) <<"const auto switchField = " + << "m_binaryDataEncoding->decode<quint32>(success);" << Util::lineBreak() + << Util::indent(1) << "if (!success)" << Util::lineBreak() + << Util::indent(2) << "return {};" << Util::lineBreak(2); + } + + for (const auto &field : structuredType->fields()) { + if (field->isInStructuredTypeBitMask()) + continue; + + if (m_lengthFields.contains(field)) + continue; + + // The switch field is not a member of the generated data class + if (structuredType->hasUnion() && field == structuredType->fields().constFirst()) + continue; + + if (!field->switchField().isEmpty() && !field->isUnion()) + generateOptionalFieldDecoding(output, structuredType, field); + else if (structuredType->hasUnion()) + generateUnionFieldDecoding(output, structuredType, field); + else + generateFieldDecoding(output, structuredType, field); + + output << Util::lineBreak(); + } + + output << Util::indent(1) << "return temp;" << Util::lineBreak(); + output << "}" << Util::lineBreak(2); +} + +void MappingFileGenerator::generateEncodingStructuredType(QTextStream &output, + const StructuredType *structuredType) +{ + output << "template<>" + << Util::lineBreak(); + output << "inline bool " << m_prefix << "BinaryDeEncoder" + << "::encode<" << m_prefix << structuredType->name() << ">(const " << m_prefix + << structuredType->name() << " &src)" + << Util::lineBreak(); + output << "{" + << Util::lineBreak(); + + // Create encoding mask for bitmask + if (structuredType->hasSwitchfield() && !structuredType->hasUnion()) { + output << Util::indent(1) << "quint32 encodingMask = 0;" << Util::lineBreak(); + int index = 0; + for (const auto &field : structuredType->fields()) { + if (!m_switchFields.contains(field)) + continue; + output << Util::indent(1) << "encodingMask |= ((src." << field->lowerFirstName() << "() ? 1 : 0) << " << index++ << ");" << Util::lineBreak(); + } + output << Util::lineBreak(); + output << Util::indent(1) << "if (!m_binaryDataEncoding->encode<quint32>(encodingMask))" << Util::lineBreak(); + output << Util::indent(2) << "return false;" << Util::lineBreak(2); + } else if (structuredType->hasUnion()) { + output << Util::indent(1) << "if (!m_binaryDataEncoding->encode<quint32>(static_cast<quint32>(src." << structuredType->fields().constFirst()->lowerFirstName() << "())))" << Util::lineBreak(); + output << Util::indent(2) << "return false;" << Util::lineBreak(2); + } + + for (const auto &field : structuredType->fields()) { + if (m_lengthFields.contains(field)) + continue; + + if (m_switchFields.contains(field)) + continue; + + // Ignore reserved fields + if (field->isInStructuredTypeBitMask()) + continue; + + if (structuredType->hasUnion()) + generateUnionFieldEncoding(output, structuredType, field); + else if (!field->switchField().isEmpty() && !field->isUnion()) + generateOptionalFieldEncoding(output, structuredType, field); + else + generateFieldEncoding(output, structuredType, field); + + output << Util::lineBreak(); + } + + output << Util::indent(1) << "return true;" << Util::lineBreak(); + output << "}" << Util::lineBreak(2); +} + +MappingFileGenerator::MappingError MappingFileGenerator::sortMappings() +{ + QList<XmlElement *> baseType; + QList<XmlElement *> extendedType; + for (const auto &mapping : m_generateMapping) { + const auto structuredType = dynamic_cast<StructuredType *>(mapping); + if (structuredType) { + bool isBaseType = true; + for (const auto &field : structuredType->fields()) { + bool isPreCoded = false; + for (const auto &precodedType : StringIdentifier::opcUaPrecodedTypes) { + if (precodedType.contains(Util::removeNamespace(field->typeName()))) { + isPreCoded = true; + break; + } + } + if (!isPreCoded) + if (!StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) + if (Util::removeNamespace(field->typeName()) != structuredType->name()) + isBaseType = false; + } + if (isBaseType) + baseType.push_back(structuredType); + else + extendedType.push_back(structuredType); + } else { + baseType.push_front(mapping); + } + } + m_generateMapping = baseType; + + while (!extendedType.isEmpty()) { + QList<XmlElement *> tmpExtendedType = extendedType; + for (int i = 0; i < extendedType.size(); i++) { + const auto structuredType = dynamic_cast<StructuredType *>(extendedType.at(i)); + if (structuredType) { + bool independentExtended = true; + for (const auto &field : structuredType->fields()) { + bool isPreCoded = false; + for (const auto &precodedType : StringIdentifier::opcUaPrecodedTypes) { + if (precodedType.contains(Util::removeNamespace(field->typeName()))) { + isPreCoded = true; + break; + } + } + bool basicdependecy = false; + if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) + basicdependecy = true; + else if (!isPreCoded + && Util::removeNamespace(field->typeName()) != structuredType->name()) { + for (const auto &type : m_generateMapping) { + if (type->name() == Util::removeNamespace(field->typeName())) { + basicdependecy = true; + } + } + } else if (!isPreCoded + && Util::removeNamespace(field->typeName()) == structuredType->name()) { + basicdependecy = true; + } else if (isPreCoded) + basicdependecy = true; + independentExtended &= basicdependecy; + } + + if (independentExtended) { + m_generateMapping.append(structuredType); + extendedType.removeAt(extendedType.indexOf(structuredType)); + } + } + } + if (extendedType == tmpExtendedType) { + return MappingFileGenerator::MappingError::UnableToResolve; + } + } + return MappingFileGenerator::MappingError::NoError; +} diff --git a/tools/datatypecodegenerator/mappingfilegenerator.h b/tools/datatypecodegenerator/mappingfilegenerator.h new file mode 100644 index 0000000..923e69b --- /dev/null +++ b/tools/datatypecodegenerator/mappingfilegenerator.h @@ -0,0 +1,64 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include <QtCore/qlist.h> +#include <QtCore/qtextstream.h> + +#include <cmath> +#include <iostream> + +class EnumeratedType; +class Field; +class StructuredType; +class XmlElement; + +class MappingFileGenerator +{ +public: + enum MappingError { NoError, UnanbleToWrite, UnableToResolve }; + + MappingFileGenerator(const QList<XmlElement *> &generateMapping, + const QString &path, + const QString &prefix, + const QString &header); + ~MappingFileGenerator() = default; + + void addGenerateMapping(const QList<XmlElement *> &generateMapping); + + MappingError generateMapping(); + +private: + QList<Field *> m_lengthFields; + QList<Field *> m_switchFields; + QList<XmlElement *> m_generateMapping; + QString m_path; + QString m_prefix; + QString m_header; + + MappingError generateMappingHeader(); + MappingError generateMappingCpp(); + + void generateFieldDecoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level = 1); + void generateScalarOrArrayDecoding(QTextStream &output, const StructuredType *structuredType, const Field *field); + void generateOptionalFieldDecoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level = 1); + void generateUnionFieldDecoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level = 1); + + void generateFieldEncoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level = 1); + void generateScalarOrArrayEncoding(QTextStream &output, const StructuredType *structuredType, const Field *field); + void generateOptionalFieldEncoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level = 1); + void generateUnionFieldEncoding(QTextStream &output, const StructuredType *structuredType, const Field *field, int level = 1); + + void generateDeEncodingArray(QTextStream &output); + + void generateDeEncoding(QTextStream &output); + + void generateDecodingEnumeratedType(QTextStream &output, const EnumeratedType *enumeratedType); + void generateEncodingEnumeratedType(QTextStream &output, const EnumeratedType *enumeratedType); + + void generateDecodingStructuredType(QTextStream &output, const StructuredType *structuredType); + void generateEncodingStructuredType(QTextStream &output, const StructuredType *structuredType); + + MappingFileGenerator::MappingError sortMappings(); +}; diff --git a/tools/datatypecodegenerator/recursivedescentparser.cpp b/tools/datatypecodegenerator/recursivedescentparser.cpp new file mode 100644 index 0000000..92e8adf --- /dev/null +++ b/tools/datatypecodegenerator/recursivedescentparser.cpp @@ -0,0 +1,389 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "util.h" +#include "datatypefilewriter.h" +#include "dependencydatatypevalidator.h" +#include "enumeratedtype.h" +#include "enumeratedvalue.h" +#include "field.h" +#include "import.h" +#include "mappingfilegenerator.h" +#include "recursivedescentparser.h" +#include "stringidentifier.h" +#include "structuredtype.h" +#include "typedictionary.h" + +#include <QtCore/qfile.h> + +RecursiveDescentParser::~RecursiveDescentParser() +{ + qDeleteAll(m_mapTypeDictionary); +} + +void RecursiveDescentParser::printInOrder() const +{ + for (const auto &entry : m_mapTypeDictionary) { + entry->print(); + } +} + +RecursiveDescentParser::ParsingError RecursiveDescentParser::parseEnumeratedType( + QXmlStreamReader &xmlStreamReader, TypeDictionary *typeDictionary) +{ + EnumeratedType *enumeratedType = new EnumeratedType( + xmlStreamReader.attributes().value(StringIdentifier::nameIdentifier).toString(), + xmlStreamReader.attributes().value(StringIdentifier::lengthInBitsIdentifier).toInt()); + typeDictionary->addType(enumeratedType); + xmlStreamReader.readNext(); + ParsingError error = NoError; + while (!xmlStreamReader.atEnd() && error == NoError) { + if (xmlStreamReader.isEndElement()) { + xmlStreamReader.readNext(); + return error; + } + if (xmlStreamReader.isStartElement()) { + if (xmlStreamReader.name() == StringIdentifier::enumeratedValueIdentifier) + error = parseEnumeratedValue(xmlStreamReader, enumeratedType); + else { + if (isKnownElement(xmlStreamReader)) + error = skipKnownElement(xmlStreamReader); + else + error = InvalidEnumeratedTypeEntry; + } + } else + xmlStreamReader.readNext(); + } + return error; +} + +RecursiveDescentParser::ParsingError RecursiveDescentParser::parseEnumeratedValue( + QXmlStreamReader &xmlStreamReader, EnumeratedType *enumeratedType) +{ + const auto name = permittedName(xmlStreamReader.attributes().value(StringIdentifier::nameIdentifier).toString()); + + if (name.isEmpty()) + return ParsingError::InvalidEnumeratedValueEntry; + + EnumeratedValue *enumeratedValue = new EnumeratedValue( + name, xmlStreamReader.attributes().value(StringIdentifier::valueIdentifier).toInt()); + enumeratedType->addValue(enumeratedValue); + ParsingError error = NoError; + xmlStreamReader.readNext(); + while (!xmlStreamReader.atEnd() && error == NoError) { + if (xmlStreamReader.isEndElement()) { + xmlStreamReader.readNext(); + return error; + } else { + if (isKnownElement(xmlStreamReader)) + error = skipKnownElement(xmlStreamReader); + else + error = InvalidEnumeratedValueEntry; + } + } + return error; +} + +RecursiveDescentParser::ParsingError RecursiveDescentParser::parseField( + QXmlStreamReader &xmlStreamReader, StructuredType *structuredType) +{ + const auto name = permittedName(xmlStreamReader.attributes().value(StringIdentifier::nameIdentifier).toString()); + + if (name.isEmpty()) + return ParsingError::InvalidFieldEntry; + + Field *field = new Field( + name, + xmlStreamReader.attributes().value(StringIdentifier::typeNameIdentifier).toString(), + xmlStreamReader.attributes().value(StringIdentifier::lengthFieldIdentifier).toString()); + if (!field->lengthField().isEmpty()) + field->setNeedContainer(true); + + if (field->typeName().endsWith(QStringLiteral(":%1").arg(structuredType->name()))) + field->setRecursive(true); + + if (!xmlStreamReader.attributes().value(StringIdentifier::lengthIdentifier).toString().isEmpty()) { + field->setLength(xmlStreamReader.attributes().value(StringIdentifier::lengthIdentifier).toUInt()); + field->setIsInStructuredTypeBitMask(true); + } + if (!xmlStreamReader.attributes() + .value(StringIdentifier::switchFieldIdentifier) + .toString() + .isEmpty()) { + structuredType->setHasSwitchfield(true); + field->setSwitchField( + xmlStreamReader.attributes().value(StringIdentifier::switchFieldIdentifier).toString()); + } + if (!xmlStreamReader.attributes() + .value(StringIdentifier::switchValueIdentifier) + .toString() + .isEmpty()) { + structuredType->setHasUnion(true); + field->setIsUnion(true); + field->setSwitchValue( + xmlStreamReader.attributes().value(StringIdentifier::switchValueIdentifier).toInt()); + } + if (field->typeName() == StringIdentifier::opcBitIdentifier) + structuredType->setContainsBitMask(true); + if (field->typeName().contains(StringIdentifier::bitIdentifier) + && (field->name().contains(StringIdentifier::specifiedIdentifier) + || field->name().contains(StringIdentifier::reservedIdentifier)) + && structuredType->containsBitMask()) + field->setIsInStructuredTypeBitMask(true); + structuredType->addField(field); + ParsingError error = NoError; + xmlStreamReader.readNext(); + while (!xmlStreamReader.atEnd() && error == NoError) { + if (xmlStreamReader.isEndElement()) { + xmlStreamReader.readNext(); + return error; + } else { + if (isKnownElement(xmlStreamReader)) + error = skipKnownElement(xmlStreamReader); + else + error = InvalidFieldEntry; + } + } + return error; +} + +RecursiveDescentParser::ParsingError RecursiveDescentParser::parseFile( + const QString &fileName, const bool &dependencyTypeDictionary) +{ + ParsingError error = NoError; + QFile xmlFile(fileName); + if (!xmlFile.open(QIODevice::ReadOnly)) + return InvalidFileName; + QXmlStreamReader xmlStreamReader; + xmlStreamReader.setDevice((&xmlFile)); + while (!xmlStreamReader.atEnd() && error == NoError) { + if (xmlStreamReader.isStartElement()) { + if (xmlStreamReader.name() == StringIdentifier::typeDictionaryIdentifier) + error = parseTypeDictionary(xmlStreamReader, dependencyTypeDictionary); + else { + if (isKnownElement(xmlStreamReader)) + error = skipKnownElement(xmlStreamReader); + else + error = InvalidTypeDictionaryEntry; + } + } else + xmlStreamReader.readNext(); + } + xmlFile.close(); + return error; +} + +RecursiveDescentParser::ParsingError RecursiveDescentParser::parseImport( + QXmlStreamReader &xmlStreamReader, TypeDictionary *typeDictionary) +{ + Import *import = new Import( + xmlStreamReader.attributes().value(StringIdentifier::nameSpaceIdentifier).toString(), + xmlStreamReader.attributes().value(StringIdentifier::locationIdentifier).toString()); + typeDictionary->addType(import); + xmlStreamReader.readNext(); + ParsingError error = NoError; + while (!xmlStreamReader.atEnd() && error == NoError) { + if (xmlStreamReader.isEndElement()) { + xmlStreamReader.readNext(); + return error; + } else { + if (isKnownElement(xmlStreamReader)) + error = skipKnownElement(xmlStreamReader); + else + error = InvalidImportEntry; + } + } + return error; +} + +RecursiveDescentParser::ParsingError RecursiveDescentParser::parseStructuredType( + QXmlStreamReader &xmlStreamReader, TypeDictionary *typeDictionary) +{ + StructuredType *structuredType = new StructuredType( + xmlStreamReader.attributes().value(StringIdentifier::nameIdentifier).toString(), + xmlStreamReader.attributes().value(StringIdentifier::baseTypeIdentifier).toString()); + if (StringIdentifier::buildInTypesWithBitMask.contains(structuredType->name())) { + structuredType->setIsBuiltInType(true); + structuredType->setContainsBitMask(true); + } + structuredType->setTargetNamespace(typeDictionary->targetNamespace()); + typeDictionary->addType(structuredType); + xmlStreamReader.readNext(); + ParsingError error = NoError; + while (!xmlStreamReader.atEnd() && error == NoError) { + if (xmlStreamReader.isEndElement()) { + xmlStreamReader.readNext(); + + for (const auto &field : structuredType->fields()) { + if (field->recursive()) { + structuredType->setRecursive(true); + break; + } + } + return error; + } + if (xmlStreamReader.isStartElement()) { + if (xmlStreamReader.name() == StringIdentifier::fieldIdentifier) + error = parseField(xmlStreamReader, structuredType); + else { + if (isKnownElement(xmlStreamReader)) + error = skipKnownElement(xmlStreamReader); + else + error = InvalidStructuredTypeEntry; + } + } else + xmlStreamReader.readNext(); + } + + return error; +} + +RecursiveDescentParser::ParsingError RecursiveDescentParser::parseTypeDictionary( + QXmlStreamReader &xmlStreamReader, const bool &dependencyTypeDictionary) +{ + QMap<QString, QString> namespaces; + for (int i = 0; i < xmlStreamReader.namespaceDeclarations().size(); i++) + namespaces.insert(xmlStreamReader.namespaceDeclarations().at(i).prefix().toString(), + xmlStreamReader.namespaceDeclarations().at(i).namespaceUri().toString()); + m_mapTypeDictionary.insert( + xmlStreamReader.attributes().value(StringIdentifier::targetNamespaceIdentifier).toString(), + new TypeDictionary(dependencyTypeDictionary, + xmlStreamReader.attributes() + .value(StringIdentifier::defaultByteOrderIdentifier) + .toString(), + xmlStreamReader.attributes() + .value(StringIdentifier::targetNamespaceIdentifier) + .toString(), + namespaces)); + QString targetNamespace + = xmlStreamReader.attributes().value(StringIdentifier::targetNamespaceIdentifier).toString(); + ParsingError error = NoError; + if (!dependencyTypeDictionary && targetNamespace == StringIdentifier::namespaceZeroIdentifier) + return CannotFullyGenerateNamespaceZero; + xmlStreamReader.readNext(); + while (!xmlStreamReader.atEnd() && error == NoError) { + if (xmlStreamReader.isEndElement()) { + xmlStreamReader.readNext(); + return error; + } + if (xmlStreamReader.isStartElement()) { + if (xmlStreamReader.name() == StringIdentifier::enumeratedTypeIdentifier) + error = parseEnumeratedType(xmlStreamReader, + m_mapTypeDictionary.value(targetNamespace)); + else { + if (xmlStreamReader.name() == StringIdentifier::structuredTypeIdentifier) + error = parseStructuredType(xmlStreamReader, + m_mapTypeDictionary.value(targetNamespace)); + else { + if (xmlStreamReader.name() == StringIdentifier::importIdentifier) + error = parseImport(xmlStreamReader, + m_mapTypeDictionary.value(targetNamespace)); + else { + if (isKnownElement(xmlStreamReader)) + error = skipKnownElement(xmlStreamReader); + else + error = InvalidTypeDictionaryEntry; + } + } + } + } else + xmlStreamReader.readNext(); + } + return error; +} + +RecursiveDescentParser::ParsingError RecursiveDescentParser::skipKnownElement( + QXmlStreamReader &xmlStreamReader) +{ + ParsingError error = NoError; + xmlStreamReader.readNext(); + while (!xmlStreamReader.atEnd() && error == NoError) { + if (xmlStreamReader.isEndElement()) { + xmlStreamReader.readNext(); + return error; + } + if (xmlStreamReader.isStartElement()) { + if (xmlStreamReader.name() == StringIdentifier::documentationIdentifier + || xmlStreamReader.name() == StringIdentifier::opaqueTypeIdentifier) + skipKnownElement(xmlStreamReader); + else + error = UnknownEntry; + } else + xmlStreamReader.readNext(); + } + return error; +} + +RecursiveDescentParser::ParsingError RecursiveDescentParser::generateInputFiles( + const QString &path, + const QString &prefix, + const QString &header) +{ + DependencyDataTypeValidator dependencyDataTypeValidator; + for (auto it = m_mapTypeDictionary.constBegin(); it != m_mapTypeDictionary.constEnd(); ++it) { + if (!it.value()->dependencyTypeDictionary()) + it.value()->accept(&dependencyDataTypeValidator); + } + + dependencyDataTypeValidator.setReadResolveDependencies( + DependencyDataTypeValidator::ResolveDependencies); + + for (auto it = m_mapTypeDictionary.constBegin(); it != m_mapTypeDictionary.constEnd(); ++it) + it.value()->accept(&dependencyDataTypeValidator); + + for (auto it = m_mapTypeDictionary.constBegin(); it != m_mapTypeDictionary.constEnd(); ++it) + it.value()->accept(&dependencyDataTypeValidator); + + if (dependencyDataTypeValidator.unresolvedDependencyStringList().isEmpty()) { + DataTypeFileWriter filewriter(path, + prefix, + header); + for (auto it = m_mapTypeDictionary.constBegin(); it != m_mapTypeDictionary.constEnd(); ++it) { + if (!it.value()->dependencyTypeDictionary()) + it.value()->accept(&filewriter); + }; + if (filewriter.generateMapping().isEmpty()) { + return UnableToWriteFile; + } + MappingFileGenerator mappingFileGenerator(filewriter.generateMapping(), path, prefix, header); + if (filewriter.generateTypes(dependencyDataTypeValidator.resolvedDependencyElementList()) + != DataTypeFileWriter::GeneratingError::NoError) { + return UnableToWriteFile; + } + + mappingFileGenerator.addGenerateMapping(dependencyDataTypeValidator.resolvedDependencyElementList()); + + if (mappingFileGenerator.generateMapping() != MappingFileGenerator::MappingError::NoError) + return UnableToWriteFile; + } else { + return MissingDependency; + } + + return NoError; +} + +bool RecursiveDescentParser::isKnownElement(const QXmlStreamReader &xmlStreamReader) +{ + return xmlStreamReader.name() == StringIdentifier::documentationIdentifier + || xmlStreamReader.name() == StringIdentifier::opaqueTypeIdentifier; +} + +QString RecursiveDescentParser::permittedName(const QString &name) const +{ + if (name.isEmpty()) + return {}; + + auto result = name; + + if (StringIdentifier::illegalNames.contains(name)) { + result = Util::lowerFirstLetter(name); + result = QStringLiteral("_%1").arg(name); + } + + if (result.contains(QStringLiteral(" "))) + result.replace(QStringLiteral(" "), QStringLiteral("_")); + if (name.contains(QStringLiteral("-"))) + result.replace(QStringLiteral("-"), QStringLiteral("_")); + + return result; +} diff --git a/tools/datatypecodegenerator/recursivedescentparser.h b/tools/datatypecodegenerator/recursivedescentparser.h new file mode 100644 index 0000000..3e540f4 --- /dev/null +++ b/tools/datatypecodegenerator/recursivedescentparser.h @@ -0,0 +1,66 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include <QtCore/qmap.h> +#include <QtCore/qxmlstream.h> + +class EnumeratedType; +class StructuredType; +class TypeDictionary; + +class RecursiveDescentParser +{ + Q_GADGET +public: + enum ParsingError { + NoError, + InvalidFileName, + InvalidTypeDictionaryEntry, + InvalidImportEntry, + InvalidEnumeratedTypeEntry, + InvalidStructuredTypeEntry, + InvalidEnumeratedValueEntry, + InvalidFieldEntry, + UnknownEntry, + MissingDependency, + CannotFullyGenerateNamespaceZero, + UnableToWriteFile, + UnableToResolveDependency + }; + Q_ENUM(ParsingError) + + RecursiveDescentParser() = default; + ~RecursiveDescentParser(); + + void printInOrder() const; + ParsingError parseEnumeratedType(QXmlStreamReader &xmlStreamReader, + TypeDictionary *typeDictionary); + + ParsingError parseEnumeratedValue(QXmlStreamReader &xmlStreamReader, + EnumeratedType *enumeratedType); + ParsingError parseField(QXmlStreamReader &xmlStreamReader, StructuredType *structuredType); + + ParsingError parseFile(const QString &fileName, const bool &dependencyTypeDictionary); + ParsingError parseImport(QXmlStreamReader &xmlStreamReader, TypeDictionary *typeDictionary); + + ParsingError parseStructuredType(QXmlStreamReader &xmlStreamReader, + TypeDictionary *typeDictionary); + ParsingError parseTypeDictionary(QXmlStreamReader &xmlStreamReader, + const bool &dependencyTypeDictionary); + + ParsingError skipKnownElement(QXmlStreamReader &xmlStreamReader); + ParsingError generateInputFiles(const QString &path, + const QString &prefix, + const QString &header); + + ParsingError dependencyCheck(); + bool isKnownElement(const QXmlStreamReader &xmlStreamReader); + + QString permittedName(const QString &name) const; + +private: + QString m_fileName; + QMap<QString, TypeDictionary *> m_mapTypeDictionary; +}; diff --git a/tools/datatypecodegenerator/stringidentifier.cpp b/tools/datatypecodegenerator/stringidentifier.cpp new file mode 100644 index 0000000..1a2b9bb --- /dev/null +++ b/tools/datatypecodegenerator/stringidentifier.cpp @@ -0,0 +1,127 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "stringidentifier.h" + +const QString StringIdentifier::baseTypeIdentifier = "BaseType"; +const QString StringIdentifier::binaryTypeIdentifier = "opc"; +const QString StringIdentifier::bitIdentifier = "Bit"; +const QString StringIdentifier::booleanIdentifier = "Boolean"; +const QString StringIdentifier::byteIdentifier = "Byte"; +const QString StringIdentifier::sbyteIdentifier = "SByte"; +const QString StringIdentifier::byteStringIdentifier = "ByteString"; +const QString StringIdentifier::charArrayIdentifier = "CharArray"; +const QString StringIdentifier::cppIdentifier = ".cpp"; +const QString StringIdentifier::datetimeIdentifier = "DateTime"; +const QString StringIdentifier::defaultByteOrderIdentifier = "DefaultByteOrder"; +const QString StringIdentifier::documentationIdentifier = "Documentation"; +const QString StringIdentifier::doubleIdentifier = "Double"; +const QString StringIdentifier::enumeratedTypeIdentifier = "EnumeratedType"; +const QString StringIdentifier::enumeratedValueIdentifier = "EnumeratedValue"; +const QString StringIdentifier::fieldIdentifier = "Field"; +const QString StringIdentifier::floatIdentifier = "Float"; +const QString StringIdentifier::guidIdentifier = "Guid"; +const QString StringIdentifier::headerIdentifier = ".h"; +const QString StringIdentifier::importIdentifier = "Import"; +const QString StringIdentifier::integerIdentifier = "Int"; +const QString StringIdentifier::lengthIdentifier = "Length"; +const QString StringIdentifier::lengthFieldIdentifier = "LengthField"; +const QString StringIdentifier::lengthInBitsIdentifier = "LengthInBits"; +const QString StringIdentifier::locationIdentifier = "Location"; +const QString StringIdentifier::mainIdentifier = "main.cpp"; +const QString StringIdentifier::makelistIdentifier = "CMakeLists.txt"; +const QString StringIdentifier::nameIdentifier = "Name"; +const QString StringIdentifier::nameSpaceIdentifier = "Namespace"; +const QString StringIdentifier::namespaceZeroIdentifier = "http://opcfoundation.org/UA/"; +const QString StringIdentifier::opaqueTypeIdentifier = "OpaqueType"; +const QString StringIdentifier::projectIdentifier = "cmakeqt6"; +const QString StringIdentifier::qByteArrayIdentifier = "QByteArray"; +const QString StringIdentifier::qStringIdentifier = "QString"; +const QString StringIdentifier::uaStatusCodeIdentifier = "StatusCode"; +const QString StringIdentifier::reservedIdentifier = "Reserved"; +const QString StringIdentifier::specifiedIdentifier = "Specified"; +const QString StringIdentifier::structuredTypeIdentifier = "StructuredType"; +const QString StringIdentifier::switchFieldIdentifier = "SwitchField"; +const QString StringIdentifier::switchValueIdentifier = "SwitchValue"; +const QString StringIdentifier::targetNamespaceIdentifier = "TargetNamespace"; +const QString StringIdentifier::typeDictionaryIdentifier = "TypeDictionary"; +const QString StringIdentifier::typeNameIdentifier = "TypeName"; +const QString StringIdentifier::valueIdentifier = "Value"; +const QString StringIdentifier::xmlElementIdentifier = "XmlElement"; +const QString StringIdentifier::opcBitIdentifier = "opc:Bit"; + +const QList<StringIdentifier::OpcUaPrecodedType> StringIdentifier::opcUaPrecodedTypes{ + OpcUaPrecodedType("ApplicationRecordDataType", "QOpcUaApplicationRecordDataType", + "QOpcUaApplicationRecordDataType"), + OpcUaPrecodedType("Argument", "QOpcUaArgument", "QOpcUaArgument"), + OpcUaPrecodedType("AxisInformation", "QOpcUaAxisInformation", "QOpcUaAxisInformation"), + OpcUaPrecodedType("ComplexNumber", "QOpcUaComplexNumber", "QOpcUaComplexNumber"), + OpcUaPrecodedType("DoubleComplexNumber", "QOpcUaDoubleComplexNumber", "QOpcUaDoubleComplexNumber"), + OpcUaPrecodedType("EUInformation", "QOpcUaEUInformation", "QOpcUaEUInformation"), + OpcUaPrecodedType("ExpandedNodeId", "QOpcUaExpandedNodeId", "QOpcUaExpandedNodeId"), + OpcUaPrecodedType("ExtensionObject", "QOpcUaExtensionObject", "QOpcUaExtensionObject"), + OpcUaPrecodedType("LocalizedText", "QOpcUaLocalizedText", "QOpcUaLocalizedText"), + OpcUaPrecodedType("NodeId", QString(), "QString", "QString"), + OpcUaPrecodedType("QualifiedName", "QOpcUaQualifiedName", "QOpcUaQualifiedName"), + OpcUaPrecodedType("Range", "QOpcUaRange", "QOpcUaRange"), + OpcUaPrecodedType("StatusCode", "qopcuatype.h", "QOpcUa::UaStatusCode"), + OpcUaPrecodedType("XVType", "QOpcUaXValue", "QOpcUaXValue"), + OpcUaPrecodedType("DiagnosticInfo", "QOpcUaDiagnosticInfo", "QOpcUaDiagnosticInfo"), + OpcUaPrecodedType("StructureField", "QOpcUaStructureField", "QOpcUaStructureField"), + OpcUaPrecodedType("StructureDefinition", "QOpcUaStructureDefinition", "QOpcUaStructureDefinition"), + OpcUaPrecodedType("EnumField", "QOpcUaEnumField", "QOpcUaEnumField"), + OpcUaPrecodedType("EnumDefinition", "QOpcUaEnumDefinition", "QOpcUaEnumDefinition") +}; + +const QList<QString> StringIdentifier::buildInTypesWithBitMask = {"DiagnosticInfo", + "LocalizedText", + "Variant", + "DataValue"}; + +const QMap<QString, QString> StringIdentifier::typeNameDataTypeConverter + = {{"opc:Bit", "bool"}, + {"opc:Boolean", "bool"}, + {"opc:Byte", "quint8"}, + {"opc:ByteString", "QByteArray"}, + {"opc:CharArray", "QString"}, + {"opc:DateTime", "QDateTime"}, + {"opc:Double", "double"}, + {"opc:Float", "float"}, + {"opc:Int16", "qint16"}, + {"opc:Int32", "qint32"}, + {"opc:Int64", "qint64"}, + {"opc:SByte", "qint8"}, + {"opc:String", "QString"}, + {"opc:UInt16", "quint16"}, + {"opc:UInt32", "quint32"}, + {"opc:UInt64", "quint64"}, + {"opc:Guid", "QUuid"}, + {"ua:XmlElement", "QString"}}; + +const QList<QString> StringIdentifier::illegalNames + = {"Boolean", "boolean", "Int16", "int16", "Float", "float", + "Datetime", "datetime", "byteString", "ByteString", "XmlElement", "xmlElement", + "byte", "Byte", "SByte", "sByte", "Int32", "int32", + "Int64", "int64", "Double", "double", "String", "string"}; + +const QSet<QString> StringIdentifier::precodedTypesWithDebugOperator = { "LocalizedText", "NodeId", "QualifiedName" }; + +QString StringIdentifier::OpcUaPrecodedType::name() const +{ + return m_name; +} + +QString StringIdentifier::OpcUaPrecodedType::filename() const +{ + return m_filename; +} + +QString StringIdentifier::OpcUaPrecodedType::className() const +{ + return m_className; +} + +QString StringIdentifier::OpcUaPrecodedType::deEncoderName() const +{ + return m_deEncoderName; +} diff --git a/tools/datatypecodegenerator/stringidentifier.h b/tools/datatypecodegenerator/stringidentifier.h new file mode 100644 index 0000000..53dcff4 --- /dev/null +++ b/tools/datatypecodegenerator/stringidentifier.h @@ -0,0 +1,101 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include <QtCore/qlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> +#include <QtCore/qstring.h> + +class StringIdentifier +{ +public: + static const QString baseTypeIdentifier; + static const QString binaryTypeIdentifier; + static const QString bitIdentifier; + static const QString booleanIdentifier; + static const QString byteIdentifier; + static const QString sbyteIdentifier; + static const QString byteStringIdentifier; + static const QString charArrayIdentifier; + static const QString cppIdentifier; + static const QString datetimeIdentifier; + static const QString defaultByteOrderIdentifier; + static const QString documentationIdentifier; + static const QString doubleIdentifier; + static const QString enumeratedTypeIdentifier; + static const QString enumeratedValueIdentifier; + static const QString fieldIdentifier; + static const QString floatIdentifier; + static const QString guidIdentifier; + static const QString headerIdentifier; + static const QString importIdentifier; + static const QString integerIdentifier; + static const QString lengthIdentifier; + static const QString lengthFieldIdentifier; + static const QString lengthInBitsIdentifier; + static const QString locationIdentifier; + static const QString mainIdentifier; + static const QString makelistIdentifier; + static const QString nameIdentifier; + static const QString nameSpaceIdentifier; + static const QString namespaceZeroIdentifier; + static const QString opaqueTypeIdentifier; + static const QString projectIdentifier; + static const QString qByteArrayIdentifier; + static const QString qStringIdentifier; + static const QString uaStatusCodeIdentifier; + static const QString reservedIdentifier; + static const QString specifiedIdentifier; + static const QString structuredTypeIdentifier; + static const QString switchFieldIdentifier; + static const QString switchValueIdentifier; + static const QString targetNamespaceIdentifier; + static const QString typeDictionaryIdentifier; + static const QString typeNameIdentifier; + static const QString valueIdentifier; + static const QString xmlElementIdentifier; + static const QString opcBitIdentifier; + + class OpcUaPrecodedType + { + public: + OpcUaPrecodedType(const QString &typeName, + const QString &fileName, + const QString &className, + const QString &deEncoderName = QString()) + : m_name(typeName) + , m_filename(fileName) + , m_className(className) + , m_deEncoderName(deEncoderName) + {} + + bool contains(QString name) const + { + return m_name == name || m_filename == name || m_className == name; + } + + QString name() const; + + QString filename() const; + + QString className() const; + + QString deEncoderName() const; + + private: + QString m_name; + QString m_filename; + QString m_className; + QString m_deEncoderName; + }; + + static const QList<OpcUaPrecodedType> opcUaPrecodedTypes; + static const QList<QString> buildInTypesWithBitMask; + static const QMap<QString, QString> typeNameDataTypeConverter; + + static const QList<QString> illegalNames; + + static const QSet<QString> precodedTypesWithDebugOperator; +}; diff --git a/tools/datatypecodegenerator/structuredtype.cpp b/tools/datatypecodegenerator/structuredtype.cpp new file mode 100644 index 0000000..948e9bc --- /dev/null +++ b/tools/datatypecodegenerator/structuredtype.cpp @@ -0,0 +1,118 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "field.h" +#include "structuredtype.h" +#include "visitor.h" + +#include <QtCore/qdebug.h> + +StructuredType::StructuredType(const QString &name, const QString &baseType) + : XmlElement(name) + , m_baseType(baseType) +{} + +StructuredType::~StructuredType() +{ + qDeleteAll(m_fields); +} + +QString StructuredType::baseType() const +{ + return m_baseType; +} + +void StructuredType::setBaseType(const QString &baseType) +{ + m_baseType = baseType; +} + +void StructuredType::addField(Field *field) +{ + m_fields.push_back(field); +} + +void StructuredType::print() const +{ + XmlElement::print(); + qDebug() << "BaseType: " << m_baseType; + for (int i = 0; i < m_fields.size(); i++) + m_fields.at(i)->print(); +} + +void StructuredType::accept(Visitor *visitor) +{ + visitor->visit(this); + for (const auto &field : m_fields) + field->accept(visitor); +} + +QList<Field *> StructuredType::fields() const +{ + return m_fields; +} + +void StructuredType::setFields(const QList<Field *> &fields) +{ + m_fields = fields; +} + +bool StructuredType::containsBitMask() const +{ + return m_containsBitMask; +} + +void StructuredType::setContainsBitMask(bool containsBitMask) +{ + m_containsBitMask = containsBitMask; +} + +bool StructuredType::isBuiltInType() const +{ + return m_isBuiltInType; +} + +void StructuredType::setIsBuiltInType(bool isBuiltInType) +{ + m_isBuiltInType = isBuiltInType; +} + +bool StructuredType::hasSwitchfield() const +{ + return m_hasSwitchfield; +} + +void StructuredType::setHasSwitchfield(bool hasSwitchfield) +{ + m_hasSwitchfield = hasSwitchfield; +} + +bool StructuredType::hasUnion() const +{ + return m_hasUnion; +} + +void StructuredType::setHasUnion(bool hasUnion) +{ + m_hasUnion = hasUnion; +} + +QString StructuredType::targetNamespace() const +{ + return m_targetNamespace; +} + +void StructuredType::setTargetNamespace(const QString &targetNamespace) +{ + m_targetNamespace = targetNamespace; +} + +bool StructuredType::recursive() const +{ + return m_recursive; +} + +void StructuredType::setRecursive(bool recursive) +{ + m_recursive = recursive; +} diff --git a/tools/datatypecodegenerator/structuredtype.h b/tools/datatypecodegenerator/structuredtype.h new file mode 100644 index 0000000..13decd5 --- /dev/null +++ b/tools/datatypecodegenerator/structuredtype.h @@ -0,0 +1,56 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "xmlelement.h" + +#include <QtCore/qlist.h> + +class Field; + +class StructuredType : public XmlElement +{ +public: + StructuredType(const QString &name, const QString &baseType); + ~StructuredType(); + + QString baseType() const; + void setBaseType(const QString &baseType); + + void addField(Field *field); + + void print() const override; + void accept(Visitor *visitor) override; + + QList<Field *> fields() const; + void setFields(const QList<Field *> &fields); + + bool containsBitMask() const; + void setContainsBitMask(bool containsBitMask); + + bool isBuiltInType() const; + void setIsBuiltInType(bool isBuiltInType); + + bool hasSwitchfield() const; + void setHasSwitchfield(bool hasSwitchfield); + + bool hasUnion() const; + void setHasUnion(bool hasUnion); + + QString targetNamespace() const; + void setTargetNamespace(const QString &targetNamespace); + + bool recursive() const; + void setRecursive(bool recursive); + +private: + bool m_containsBitMask{false}; + bool m_isBuiltInType{false}; + bool m_hasSwitchfield{false}; + bool m_hasUnion{false}; + bool m_recursive{false}; + QString m_baseType; + QString m_targetNamespace; + QList<Field *> m_fields; +}; diff --git a/tools/datatypecodegenerator/typedictionary.cpp b/tools/datatypecodegenerator/typedictionary.cpp new file mode 100644 index 0000000..7accd34 --- /dev/null +++ b/tools/datatypecodegenerator/typedictionary.cpp @@ -0,0 +1,116 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "typedictionary.h" + +#include "enumeratedtype.h" +#include "import.h" +#include "structuredtype.h" +#include "visitor.h" + +#include <QtCore/qdebug.h> + +TypeDictionary::TypeDictionary(bool dependencyTypeDictionary, + const QString &defaultByOrder, + const QString &targetNamespace, + const QMap<QString, QString> &namespaces) + : m_dependencyTypeDictionary(dependencyTypeDictionary) + , m_defaultByOrder(defaultByOrder) + , m_targetNamespace(targetNamespace) + , m_namespaces(namespaces) +{} + +TypeDictionary::~TypeDictionary() +{ + qDeleteAll(m_types); +} + +QMap<QString, QString> TypeDictionary::namespaces() const +{ + return m_namespaces; +} + +void TypeDictionary::setNamespaces(const QMap<QString, QString> &namespaces) +{ + m_namespaces = namespaces; +} + +QString TypeDictionary::defaultByOrder() const +{ + return m_defaultByOrder; +} + +void TypeDictionary::setDefaultByOrder(const QString &defaultByOrder) +{ + m_defaultByOrder = defaultByOrder; +} + +QString TypeDictionary::targetNamespace() const +{ + return m_targetNamespace; +} + +void TypeDictionary::setTargetNamespace(const QString &targetNamespace) +{ + m_targetNamespace = targetNamespace; +} + +QList<const Import *> TypeDictionary::imports() const +{ + QList<const Import *> imports; + for (auto element : m_types) { + Import *import = dynamic_cast<Import *>(element); + if (import) { + imports.push_back(import); + } + } + return imports; +} + +bool TypeDictionary::dependencyTypeDictionary() const +{ + return m_dependencyTypeDictionary; +} + +QList<XmlElement *> TypeDictionary::types() const +{ + return m_types; +} + +void TypeDictionary::addType(XmlElement *type) +{ + m_types.push_back(type); +} + +void TypeDictionary::print() const +{ + for (auto it = m_namespaces.constBegin(); it != m_namespaces.constEnd(); ++it) { + qDebug() << it.key() << ": " << it.value(); + } + + qDebug() << "defaultByOrder:" << m_defaultByOrder << "\tTargetNamespace:" << m_targetNamespace; + + for (auto element : m_types) { + const auto *enumeratedType = dynamic_cast<EnumeratedType *>(element); + if (enumeratedType) { + enumeratedType->print(); + } else { + const auto *structuredType = dynamic_cast<StructuredType *>(element); + if (structuredType) { + structuredType->print(); + } else { + const auto *import = dynamic_cast<Import *>(element); + if (import) { + import->print(); + } + } + } + } +} + +void TypeDictionary::accept(Visitor *visitor) +{ + visitor->visit(this); + for (const auto &element : m_types) + element->accept(visitor); +} diff --git a/tools/datatypecodegenerator/typedictionary.h b/tools/datatypecodegenerator/typedictionary.h new file mode 100644 index 0000000..abab83e --- /dev/null +++ b/tools/datatypecodegenerator/typedictionary.h @@ -0,0 +1,46 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include <QtCore/qmap.h> + +class Import; +class Visitor; +class XmlElement; + +class TypeDictionary +{ +public: + TypeDictionary(bool dependencyTypeDictionary, + const QString &defaultByOrder, + const QString &targetNamespace, + const QMap<QString, QString> &namespaces); + ~TypeDictionary(); + + void addType(XmlElement *type); + void print() const; + void accept(Visitor *visitor); + + QMap<QString, QString> namespaces() const; + void setNamespaces(const QMap<QString, QString> &namespaces); + + QString defaultByOrder() const; + void setDefaultByOrder(const QString &defaultByOrder); + + QString targetNamespace() const; + void setTargetNamespace(const QString &targetNamespace); + + QList<const Import *> imports() const; + + bool dependencyTypeDictionary() const; + + QList<XmlElement *> types() const; + +private: + bool m_dependencyTypeDictionary; + QString m_defaultByOrder; + QString m_targetNamespace; + QMap<QString, QString> m_namespaces; + QList<XmlElement *> m_types; +}; diff --git a/tools/datatypecodegenerator/util.cpp b/tools/datatypecodegenerator/util.cpp new file mode 100644 index 0000000..7a05e22 --- /dev/null +++ b/tools/datatypecodegenerator/util.cpp @@ -0,0 +1,31 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "util.h" + +#include <QtCore/qstringlist.h> + +QString Util::indent(int level) +{ + return QStringLiteral("%1").arg(" ", level * 4, ' '); +} + +QString Util::lineBreak(int n) +{ + return QStringLiteral("%1").arg("\n", n, '\n'); +} + +QString Util::removeNamespace(const QString &typeName) +{ + return typeName.split(":").value(1, QString()); +} + +QString Util::lowerFirstLetter(const QString &temp) +{ + if (temp.isEmpty()) + return temp; + + auto result = temp; + result.front() = result.at(0).toLower(); + return result; +} diff --git a/tools/datatypecodegenerator/util.h b/tools/datatypecodegenerator/util.h new file mode 100644 index 0000000..5e877b1 --- /dev/null +++ b/tools/datatypecodegenerator/util.h @@ -0,0 +1,15 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include <QtCore/qstring.h> + +namespace Util +{ + QString indent(int level); + QString lineBreak(int n = 1); + + QString removeNamespace(const QString &typeName); + QString lowerFirstLetter(const QString &temp); +} diff --git a/tools/datatypecodegenerator/visitor.h b/tools/datatypecodegenerator/visitor.h new file mode 100644 index 0000000..732ebda --- /dev/null +++ b/tools/datatypecodegenerator/visitor.h @@ -0,0 +1,26 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "xmlelement.h" + +class EnumeratedType; +class EnumeratedValue; +class Import; +class Field; +class StructuredType; +class TypeDictionary; + +class Visitor +{ +public: + virtual ~Visitor() = default; + virtual void visit(XmlElement *xmlElement) = 0; + virtual void visit(EnumeratedType *enumteratedType) = 0; + virtual void visit(EnumeratedValue *enumeratedValue) = 0; + virtual void visit(Import *import) = 0; + virtual void visit(Field *field) = 0; + virtual void visit(StructuredType *structuredType) = 0; + virtual void visit(TypeDictionary *typeDictionary) = 0; +}; diff --git a/tools/datatypecodegenerator/xmlelement.cpp b/tools/datatypecodegenerator/xmlelement.cpp new file mode 100644 index 0000000..d28e036 --- /dev/null +++ b/tools/datatypecodegenerator/xmlelement.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "xmlelement.h" + +#include "util.h" + +#include <QtCore/qdebug.h> + +XmlElement::XmlElement(const QString &name) + : m_name(name) +{} + +void XmlElement::setName(const QString &name) +{ + m_name = name; +} + +QString XmlElement::name() const +{ + return m_name; +} + +QString XmlElement::lowerFirstName() const +{ + return Util::lowerFirstLetter(m_name); +} + +void XmlElement::print() const +{ + qDebug() << "name: " << m_name; +} diff --git a/tools/datatypecodegenerator/xmlelement.h b/tools/datatypecodegenerator/xmlelement.h new file mode 100644 index 0000000..12809f2 --- /dev/null +++ b/tools/datatypecodegenerator/xmlelement.h @@ -0,0 +1,26 @@ +// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include <QtCore/qstring.h> + +class Visitor; + +class XmlElement +{ +public: + XmlElement() = default; + XmlElement(const QString &name); + virtual ~XmlElement() = default; + + void setName(const QString &name); + QString name() const; + QString lowerFirstName() const; + + virtual void print() const; + virtual void accept(Visitor *visitor) = 0; + +private: + QString m_name; +}; |