// Copyright (C) 2020-2021 Jakub Melka // // This file is part of PDF4QT. // // PDF4QT is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // with the written consent of the copyright owner, any later version. // // PDF4QT is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with PDF4QT. If not, see . #include "codegenerator.h" #include #include #include #include #include namespace codegen { GeneratedCodeStorage::GeneratedCodeStorage(QObject* parent) : BaseClass(parent) { } QObjectList GeneratedCodeStorage::getFunctions() const { return m_functions; } void GeneratedCodeStorage::setFunctions(const QObjectList& functions) { m_functions = functions; auto comparator = [](const QObject* left, const QObject* right) { const GeneratedFunction* leftFunction = qobject_cast(left); const GeneratedFunction* rightFunction = qobject_cast(right); return leftFunction->getFunctionName() < rightFunction->getFunctionName(); }; std::sort(m_functions.begin(), m_functions.end(), comparator); } GeneratedFunction* GeneratedCodeStorage::addFunction(const QString& name) { GeneratedFunction* function = new GeneratedFunction(this); function->setFunctionName(name); m_functions.append(function); return function; } GeneratedFunction* GeneratedCodeStorage::addFunction(GeneratedFunction* function) { function->setParent(this); m_functions.append(function); return function; } void GeneratedCodeStorage::removeFunction(GeneratedFunction* function) { function->deleteLater(); m_functions.removeOne(function); } void GeneratedCodeStorage::generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const { stream << Qt::endl << Qt::endl; for (const QObject* object : m_functions) { const GeneratedFunction* generatedFunction = qobject_cast(object); generatedFunction->generateCode(stream, parameters); stream << Qt::endl << Qt::endl; } } QObject* Serializer::load(const QDomElement& element, QObject* parent) { QString className = element.attribute("class"); const QMetaObject* metaObject = QMetaType::fromName(QByteArrayView(className.toLatin1())).metaObject(); if (metaObject) { QObject* deserializedObject = metaObject->newInstance(Q_ARG(QObject*, parent)); const int propertyCount = metaObject->propertyCount(); for (int i = 0; i < propertyCount; ++i) { QMetaProperty property = metaObject->property(i); if (property.isWritable()) { // Find, if property was serialized QDomElement propertyElement; QDomNodeList children = element.childNodes(); for (int iChild = 0; iChild < children.count(); ++iChild) { QDomNode child = children.item(iChild); if (child.isElement()) { QDomElement childElement = child.toElement(); QString attributeName = childElement.attribute("name"); if (attributeName == property.name()) { propertyElement = child.toElement(); break; } } } if (!propertyElement.isNull()) { // Deserialize the element if (property.isEnumType()) { QMetaEnum metaEnum = property.enumerator(); Q_ASSERT(metaEnum.isValid()); property.write(deserializedObject, metaEnum.keyToValue(propertyElement.text().toLatin1().data())); } else if (property.userType() == qMetaTypeId()) { QObjectList objectList; QDomNodeList subChildren = propertyElement.childNodes(); for (int iChild = 0; iChild < subChildren.count(); ++iChild) { QDomNode node = subChildren.item(iChild); if (node.isElement()) { QDomElement nodeElement = node.toElement(); if (QObject* object = Serializer::load(nodeElement, deserializedObject)) { objectList.append(object); } } } property.write(deserializedObject, QVariant::fromValue(qMove(objectList))); } else { QVariant value = propertyElement.text(); property.write(deserializedObject, value); } } } } return deserializedObject; } return nullptr; } void Serializer::store(QObject* object, QDomElement& element) { Q_ASSERT(object); const QMetaObject* metaObject = object->metaObject(); element.setAttribute("class", QString(metaObject->className())); const int propertyCount = metaObject->propertyCount(); if (propertyCount > 0) { for (int i = 0; i < propertyCount; ++i) { QMetaProperty property = metaObject->property(i); if (property.isReadable()) { QDomElement propertyElement = element.ownerDocument().createElement("property"); element.appendChild(propertyElement); propertyElement.setAttribute("name", property.name()); QVariant value = property.read(object); if (property.isEnumType()) { QMetaEnum metaEnum = property.enumerator(); Q_ASSERT(metaEnum.isValid()); propertyElement.appendChild(propertyElement.ownerDocument().createTextNode(metaEnum.valueToKey(value.toInt()))); } else if (value.canConvert()) { QObjectList objectList = value.value(); for (QObject* currentObject : objectList) { QDomElement objectElement = element.ownerDocument().createElement("QObject"); propertyElement.appendChild(objectElement); Serializer::store(currentObject, objectElement); } } else { propertyElement.appendChild(propertyElement.ownerDocument().createTextNode(value.toString())); } } } } } QObject* Serializer::clone(QObject* object, QObject* parent) { QDomDocument document; QDomElement rootElement = document.createElement("root"); document.appendChild(rootElement); Serializer::store(object, rootElement); return Serializer::load(rootElement, parent); } CodeGenerator::CodeGenerator(QObject* parent) : BaseClass(parent) { qRegisterMetaType("codegen::GeneratedCodeStorage"); qRegisterMetaType("codegen::GeneratedFunction"); qRegisterMetaType("codegen::GeneratedBase"); qRegisterMetaType("codegen::GeneratedParameter"); qRegisterMetaType("codegen::GeneratedPDFObject"); qRegisterMetaType("codegen::GeneratedAction"); qRegisterMetaType("QObjectList"); } void CodeGenerator::initialize() { m_storage = new GeneratedCodeStorage(this); } void CodeGenerator::load(const QDomDocument& document) { delete m_storage; m_storage = nullptr; m_storage = qobject_cast(Serializer::load(document.firstChildElement("root"), this)); } void CodeGenerator::store(QDomDocument& document) { if (m_storage) { QDomElement rootElement = document.createElement("root"); document.appendChild(rootElement); Serializer::store(m_storage, rootElement); } } void CodeGenerator::generateCode(QString headerName, QString sourceName) const { QString startMark = "/* START GENERATED CODE */"; QString endMark = "/* END GENERATED CODE */"; QString className = "PDFDocumentBuilder"; const int indent = 4; QFile headerFile(headerName); if (headerFile.exists()) { if (headerFile.open(QFile::ReadOnly | QFile::Text)) { QString utfCode = QString::fromUtf8(headerFile.readAll()); headerFile.close(); int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length(); int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive); QString frontPart = utfCode.left(startIndex); QString backPart = utfCode.mid(endIndex); QString headerGeneratedCode = generateHeader(indent); QString allCode = frontPart + headerGeneratedCode + backPart; headerFile.open(QFile::WriteOnly | QFile::Truncate); headerFile.write(allCode.toUtf8()); headerFile.close(); } } QFile sourceFile(sourceName); if (sourceFile.exists()) { if (sourceFile.open(QFile::ReadOnly | QFile::Text)) { QString utfCode = QString::fromUtf8(sourceFile.readAll()); sourceFile.close(); int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length(); int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive); QString frontPart = utfCode.left(startIndex); QString backPart = utfCode.mid(endIndex); QString sourceGeneratedCode = generateSource(className, indent); QString allCode = frontPart + sourceGeneratedCode + backPart; sourceFile.open(QFile::WriteOnly | QFile::Truncate); sourceFile.write(allCode.toUtf8()); sourceFile.close(); } } } QString CodeGenerator::generateHeader(int indent) const { QByteArray ba; { QTextStream stream(&ba, QIODevice::WriteOnly); stream.setEncoding(QStringConverter::Utf8); stream.setRealNumberPrecision(3); stream.setRealNumberNotation(QTextStream::FixedNotation); CodeGeneratorParameters parameters; parameters.header = true; parameters.indent = indent; m_storage->generateCode(stream, parameters); } return QString::fromUtf8(ba); } QString CodeGenerator::generateSource(QString className, int indent) const { QByteArray ba; { QTextStream stream(&ba, QIODevice::WriteOnly); stream.setEncoding(QStringConverter::Utf8); stream.setRealNumberPrecision(3); stream.setRealNumberNotation(QTextStream::FixedNotation); CodeGeneratorParameters parameters; parameters.header = false; parameters.indent = indent; parameters.className = className; m_storage->generateCode(stream, parameters); } return QString::fromUtf8(ba); } GeneratedFunction::GeneratedFunction(QObject* parent) : BaseClass(parent) { } QString GeneratedFunction::getFunctionTypeString() const { return Serializer::convertEnumToString(m_functionType); } void GeneratedFunction::setFunctionTypeString(const QString& string) { Serializer::convertStringToEnum(string, m_functionType); } GeneratedFunction::FunctionType GeneratedFunction::getFunctionType() const { return m_functionType; } void GeneratedFunction::setFunctionType(const FunctionType& functionType) { m_functionType = functionType; } QString GeneratedFunction::getFunctionName() const { return m_functionName; } void GeneratedFunction::setFunctionName(const QString& functionName) { m_functionName = functionName; } QString GeneratedFunction::getFunctionDescription() const { return m_functionDescription; } void GeneratedFunction::setFunctionDescription(const QString& functionDescription) { m_functionDescription = functionDescription; } GeneratedFunction* GeneratedFunction::clone(QObject* parent) { return qobject_cast(Serializer::clone(this, parent)); } void GeneratedFunction::generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const { QStringList parameterCaptions; QStringList parameterTexts; std::function gatherParameters = [&](const GeneratedBase* object, Pass pass) { if (pass != Pass::Enter) { return; } if (const GeneratedParameter* generatedParameter = qobject_cast(object)) { parameterCaptions << QString("%1 %2").arg(generatedParameter->getParameterName(), generatedParameter->getParameterDescription()); parameterTexts << QString("%1 %2").arg(getCppType(generatedParameter->getParameterDataType()), generatedParameter->getParameterName()); } }; applyFunctor(gatherParameters); if (parameterTexts.isEmpty()) { parameterTexts << ""; } if (parameters.header) { // Generate header source code // Function comments for (const QString& string : getFormattedTextWithLayout("/// ", "/// ", getFunctionDescription(), parameters.indent)) { stream << string << Qt::endl; } // Function parameter comments for (const QString& parameterCaption : parameterCaptions) { for (const QString& string : getFormattedTextWithLayout("/// \\param ", "/// ", parameterCaption, parameters.indent)) { stream << string << Qt::endl; } } // Function declaration QString functionHeader = QString("%1 %2(").arg(getCppType(getReturnType()), getFunctionName()); QString functionHeaderNext(functionHeader.length(), QChar(QChar::Space)); QString functionFooter = QString(");"); for (QString& str : parameterTexts) { str += ","; } parameterTexts.back().replace(",", functionFooter); QStringList functionDeclaration = getFormattedTextBlock(functionHeader, functionHeaderNext, parameterTexts, parameters.indent); for (const QString& functionDeclarationItem : functionDeclaration) { stream << functionDeclarationItem << Qt::endl; } } else { // Generate c++ source code QString functionHeader = QString("%1 %2::%3(").arg(getCppType(getReturnType()), parameters.className, getFunctionName()); QString functionHeaderNext(functionHeader.length(), QChar(QChar::Space)); QString functionFooter = QString(")"); for (QString& str : parameterTexts) { str += ","; } parameterTexts.back().replace(",", functionFooter); QStringList functionDeclaration = getFormattedTextBlock(functionHeader, functionHeaderNext, parameterTexts, 0); for (const QString& functionDeclarationItem : functionDeclaration) { stream << functionDeclarationItem << Qt::endl; } QString indent(parameters.indent, QChar(QChar::Space)); stream << "{" << Qt::endl; stream << indent << "PDFObjectFactory objectBuilder;" << Qt::endl << Qt::endl; generateSourceCode(stream, parameters); stream << "}" << Qt::endl; } } bool GeneratedFunction::hasField(GeneratedBase::FieldType fieldType) const { switch (fieldType) { case FieldType::Name: case FieldType::ItemType: case FieldType::DataType: case FieldType::Description: return true; case FieldType::Value: return false; default: break; } return false; } QVariant GeneratedFunction::readField(GeneratedBase::FieldType fieldType) const { switch (fieldType) { case FieldType::Name: return m_functionName; case FieldType::ItemType: return int(m_functionType); case FieldType::DataType: return int(m_returnType); case FieldType::Description: return m_functionDescription; case FieldType::Value: default: break; } return QVariant(); } void GeneratedFunction::writeField(GeneratedBase::FieldType fieldType, QVariant value) { switch (fieldType) { case FieldType::Name: m_functionName = value.toString(); break; case FieldType::ItemType: m_functionType = static_cast(value.toInt()); break; case FieldType::DataType: m_returnType = static_cast(value.toInt()); break; case FieldType::Description: m_functionDescription = value.toString(); break; case FieldType::Value: default: break; } } void GeneratedFunction::fillComboBox(QComboBox* comboBox, GeneratedBase::FieldType fieldType) { switch (fieldType) { case FieldType::ItemType: Serializer::fillComboBox(comboBox, m_functionType); break; case FieldType::DataType: Serializer::fillComboBox(comboBox, m_returnType); break; default: break; } } bool GeneratedFunction::canHaveSubitems() const { return true; } QStringList GeneratedFunction::getCaptions() const { return QStringList() << QString("Function") << QString("%1 %2(...)").arg(Serializer::convertEnumToString(m_returnType), m_functionName); } GeneratedBase* GeneratedFunction::appendItem() { Q_ASSERT(canHaveSubitems()); GeneratedBase* newItem = new GeneratedAction(this); addItem(newItem); return newItem; } GeneratedFunction::DataType GeneratedFunction::getReturnType() const { return m_returnType; } void GeneratedFunction::setReturnType(DataType returnType) { m_returnType = returnType; } GeneratedAction::GeneratedAction(QObject* parent) : BaseClass(parent), m_actionType(CreateObject) { } bool GeneratedAction::hasField(FieldType fieldType) const { switch (fieldType) { case FieldType::Name: return m_actionType == CreateObject; case FieldType::ItemType: return true; case FieldType::DataType: return hasField(FieldType::Name) && !m_variableName.isEmpty(); case FieldType::Description: return m_actionType == Code; default: break; } return false; } QVariant GeneratedAction::readField(GeneratedBase::FieldType fieldType) const { switch (fieldType) { case FieldType::Name: return m_variableName; case FieldType::ItemType: return int(m_actionType); case FieldType::DataType: return int(m_variableType); case FieldType::Description: return m_code; default: break; } return QVariant(); } void GeneratedAction::writeField(GeneratedBase::FieldType fieldType, QVariant value) { switch (fieldType) { case FieldType::Name: m_variableName = value.toString(); break; case FieldType::ItemType: m_actionType = static_cast(value.toInt()); break; case FieldType::DataType: m_variableType = static_cast(value.toInt()); break; case FieldType::Description: m_code = value.toString(); break; default: break; } } void GeneratedAction::fillComboBox(QComboBox* comboBox, FieldType fieldType) { switch (fieldType) { case FieldType::ItemType: Serializer::fillComboBox(comboBox, m_actionType); break; case FieldType::DataType: Serializer::fillComboBox(comboBox, m_variableType); break; default: break; } } bool GeneratedAction::canHaveSubitems() const { switch (m_actionType) { case Parameters: case CreateObject: return true; default: break; } return false; } QStringList GeneratedAction::getCaptions() const { return QStringList() << QString("Action %1").arg(Serializer::convertEnumToString(m_actionType)) << (!m_variableName.isEmpty() ? QString("%1 %2").arg(Serializer::convertEnumToString(m_variableType), m_variableName) : QString()); } GeneratedBase* GeneratedAction::appendItem() { Q_ASSERT(canHaveSubitems()); GeneratedBase* newItem = nullptr; switch (m_actionType) { case Parameters: newItem = new GeneratedParameter(this); break; default: newItem = new GeneratedPDFObject(this); break; } addItem(newItem); return newItem; } GeneratedAction::ActionType GeneratedAction::getActionType() const { return m_actionType; } void GeneratedAction::setActionType(ActionType actionType) { m_actionType = actionType; if (!canHaveSubitems()) { clearItems(); } } QString GeneratedAction::getVariableName() const { return m_variableName; } void GeneratedAction::setVariableName(const QString& variableName) { m_variableName = variableName; } GeneratedAction::DataType GeneratedAction::getVariableType() const { return m_variableType; } void GeneratedAction::setVariableType(DataType variableType) { m_variableType = variableType; } QString GeneratedAction::getCode() const { return m_code; } void GeneratedAction::setCode(const QString& code) { m_code = code; } void GeneratedAction::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const { if (pass == Pass::Enter && getActionType() == Code) { QString indent(parameters.indent, QChar(QChar::Space)); QStringList lines = getCode().split(QChar('\n'), Qt::KeepEmptyParts, Qt::CaseInsensitive); for (QString string : lines) { string = string.trimmed(); if (!string.isEmpty()) { stream << indent << string << Qt::endl; } else { stream << Qt::endl; } } } if (pass == Pass::Leave && getActionType() == CreateObject) { QString indent(parameters.indent, QChar(QChar::Space)); switch (getVariableType()) { case _PDFObject: { stream << indent << getCppType(getVariableType()) << " " << getVariableName() << " = objectBuilder.takeObject();"; break; } case _PDFObjectReference: { stream << indent << getCppType(getVariableType()) << " " << getVariableName() << " = addObject(objectBuilder.takeObject());"; break; } default: Q_ASSERT(false); break; } } if (pass == Pass::Leave && getActionType() != Parameters && !parameters.isLastItem) { stream << Qt::endl; } } void GeneratedBase::generateSourceCode(QTextStream& stream, CodeGeneratorParameters& parameters) const { generateSourceCodeImpl(stream, parameters, Pass::Enter); for (const QObject* object : m_items) { const GeneratedBase* generatedBase = qobject_cast(object); CodeGeneratorParameters parametersTemporary = parameters; parametersTemporary.isFirstItem = object == m_items.front(); parametersTemporary.isLastItem = object == m_items.back(); generatedBase->generateSourceCode(stream, parametersTemporary); } generateSourceCodeImpl(stream, parameters, Pass::Leave); } void GeneratedBase::applyFunctor(std::function& functor) const { functor(this, Pass::Enter); for (const QObject* object : m_items) { const GeneratedBase* generatedBase = qobject_cast(object); generatedBase->applyFunctor(functor); } functor(this, Pass::Leave); } bool GeneratedBase::canPerformOperation(Operation operation) const { const bool isFunction = qobject_cast(this) != nullptr; switch (operation) { case Operation::Delete: return !isFunction; case Operation::MoveUp: { if (const GeneratedBase* parentBase = getParent()) { QObjectList items = parentBase->getItems(); return items.indexOf(const_cast(this)) > 0; } break; } case Operation::MoveDown: { if (const GeneratedBase* parentBase = getParent()) { QObjectList items = parentBase->getItems(); return items.indexOf(const_cast(this)) < items.size() - 1; } break; } case Operation::NewSibling: return !isFunction; case Operation::NewChild: return canHaveSubitems(); default: break; } return false; } void GeneratedBase::performOperation(GeneratedBase::Operation operation) { switch (operation) { case Operation::Delete: { getParent()->removeItem(this); break; } case Operation::MoveUp: { GeneratedBase* parentItem = getParent(); QObjectList items = parentItem->getItems(); const int index = items.indexOf(this); items.removeAll(const_cast(this)); items.insert(index - 1, this); parentItem->setItems(qMove(items)); break; } case Operation::MoveDown: { GeneratedBase* parentItem = getParent(); QObjectList items = parentItem->getItems(); const int index = items.indexOf(this); items.removeAll(const_cast(this)); items.insert(index + 1, this); parentItem->setItems(qMove(items)); break; } case Operation::NewSibling: { GeneratedBase* parentItem = getParent(); if (GeneratedBase* createdItem = parentItem->appendItem()) { QObjectList items = parentItem->getItems(); items.removeAll(createdItem); items.insert(items.indexOf(const_cast(this)) + 1, createdItem); parentItem->setItems(qMove(items)); } break; } case Operation::NewChild: { appendItem(); break; } default: break; } } QObjectList GeneratedBase::getItems() const { return m_items; } void GeneratedBase::setItems(const QObjectList& items) { m_items = items; } void GeneratedBase::addItem(QObject* object) { object->setParent(this); m_items.append(object); } void GeneratedBase::removeItem(QObject* object) { object->deleteLater(); m_items.removeAll(object); } void GeneratedBase::clearItems() { qDeleteAll(m_items); m_items.clear(); } void GeneratedBase::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const { Q_UNUSED(stream); Q_UNUSED(parameters); Q_UNUSED(pass); } QString GeneratedBase::getCppType(DataType type) const { return Serializer::convertEnumToString(type).mid(1); } QStringList GeneratedBase::getFormattedTextWithLayout(QString firstPrefix, QString prefix, QString text, int indent) const { QFont font = QApplication::font(); QFontMetrics fontMetrics(font); int usedLength = indent + qMax(firstPrefix.length(), prefix.length()); int length = 80 - usedLength; QString testText(length, QChar('A')); int width = fontMetrics.horizontalAdvance(testText); QTextLayout layout(text, font); layout.setCacheEnabled(false); QTextOption textOption = layout.textOption(); textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); layout.setTextOption(textOption); layout.beginLayout(); while (true) { QTextLine textLine = layout.createLine(); if (!textLine.isValid()) { break; } textLine.setLineWidth(width); } layout.endLayout(); QStringList texts; const int lineCount = layout.lineCount(); for (int i = 0; i < lineCount; ++i) { QTextLine line = layout.lineAt(i); texts << text.mid(line.textStart(), line.textLength()); } return getFormattedTextBlock(firstPrefix, prefix, texts, indent); } QStringList GeneratedBase::getFormattedTextBlock(QString firstPrefix, QString prefix, QStringList texts, int indent) const { QString indentText(indent, QChar(QChar::Space)); firstPrefix.prepend(indentText); prefix.prepend(indentText); QStringList result; QString currentPrefix = firstPrefix; for (const QString& text : texts) { result << QString("%1%2").arg(currentPrefix, text); currentPrefix = prefix; } return result; } GeneratedPDFObject::GeneratedPDFObject(QObject* parent) : BaseClass(parent) { } bool GeneratedPDFObject::hasField(GeneratedBase::FieldType fieldType) const { switch (fieldType) { case FieldType::Name: return m_objectType == DictionaryItemSimple || m_objectType == DictionaryItemComplex; case FieldType::ItemType: return true; case FieldType::Value: return m_objectType == Object || m_objectType == ArraySimple || m_objectType == DictionaryItemSimple; default: break; } return false; } QVariant GeneratedPDFObject::readField(GeneratedBase::FieldType fieldType) const { switch (fieldType) { case FieldType::Name: return m_dictionaryItemName; case FieldType::ItemType: return int(m_objectType); case FieldType::Value: return m_value; default: break; } return QVariant(); } void GeneratedPDFObject::writeField(GeneratedBase::FieldType fieldType, QVariant value) { switch (fieldType) { case FieldType::Name: m_dictionaryItemName = value.toString(); break; case FieldType::ItemType: m_objectType = static_cast(value.toInt()); break; case FieldType::Value: m_value = value.toString(); break; default: break; } } void GeneratedPDFObject::fillComboBox(QComboBox* comboBox, GeneratedBase::FieldType fieldType) { switch (fieldType) { case FieldType::ItemType: Serializer::fillComboBox(comboBox, m_objectType); break; default: break; } } bool GeneratedPDFObject::canHaveSubitems() const { switch (m_objectType) { case ArrayComplex: case Dictionary: case DictionaryItemComplex: return true; default: break; } return false; } QStringList GeneratedPDFObject::getCaptions() const { QString name; switch (m_objectType) { case Object: name = tr("Object"); break; case ArraySimple: name = tr("Array (simple)"); break; case ArrayComplex: name = tr("Array (complex)"); break; case Dictionary: name = tr("Dictionary"); break; case DictionaryItemSimple: name = tr("Item (simple), name = '%1'").arg(m_dictionaryItemName); break; case DictionaryItemComplex: name = tr("Item (complex), name = '%1'").arg(m_dictionaryItemName); break; default: Q_ASSERT(false); break; } return QStringList() << name << m_value; } GeneratedBase* GeneratedPDFObject::appendItem() { Q_ASSERT(canHaveSubitems()); GeneratedBase* newItem = new GeneratedPDFObject(this); addItem(newItem); return newItem; } QString GeneratedPDFObject::getValue() const { return m_value; } void GeneratedPDFObject::setValue(const QString& value) { m_value = value; } GeneratedPDFObject::ObjectType GeneratedPDFObject::getObjectType() const { return m_objectType; } void GeneratedPDFObject::setObjectType(ObjectType objectType) { m_objectType = objectType; if (!canHaveSubitems()) { clearItems(); } } QString GeneratedPDFObject::getDictionaryItemName() const { return m_dictionaryItemName; } void GeneratedPDFObject::setDictionaryItemName(const QString& dictionaryItemName) { m_dictionaryItemName = dictionaryItemName; } void GeneratedPDFObject::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const { QString indent(parameters.indent, QChar(QChar::Space)); QString writeTo("objectBuilder << "); switch (getObjectType()) { case codegen::GeneratedPDFObject::Object: { if (pass == Pass::Enter) { stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl; } break; } case codegen::GeneratedPDFObject::ArraySimple: { if (pass == Pass::Enter) { stream << indent << "objectBuilder.beginArray();" << Qt::endl; for (const QString& arrayPart : getValue().trimmed().split(";")) { stream << indent << writeTo << arrayPart << ";" << Qt::endl; } stream << indent << "objectBuilder.endArray();" << Qt::endl; } break; } case codegen::GeneratedPDFObject::ArrayComplex: { if (pass == Pass::Enter) { stream << indent << "objectBuilder.beginArray();" << Qt::endl; } if (pass == Pass::Leave) { stream << indent << "objectBuilder.endArray();" << Qt::endl; } break; } case codegen::GeneratedPDFObject::Dictionary: { if (pass == Pass::Enter) { stream << indent << "objectBuilder.beginDictionary();" << Qt::endl; } if (pass == Pass::Leave) { stream << indent << "objectBuilder.endDictionary();" << Qt::endl; } break; } case codegen::GeneratedPDFObject::DictionaryItemSimple: { if (pass == Pass::Enter) { stream << indent << QString("objectBuilder.beginDictionaryItem(\"%1\");").arg(getDictionaryItemName()) << Qt::endl; stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl; stream << indent << "objectBuilder.endDictionaryItem();" << Qt::endl; } break; } case codegen::GeneratedPDFObject::DictionaryItemComplex: { if (pass == Pass::Enter) { stream << indent << QString("objectBuilder.beginDictionaryItem(\"%1\");").arg(getDictionaryItemName()) << Qt::endl; } if (pass == Pass::Leave) { stream << indent << "objectBuilder.endDictionaryItem();" << Qt::endl; } break; } default: Q_ASSERT(false); break; } } GeneratedParameter::GeneratedParameter(QObject* parent) : BaseClass(parent) { } bool GeneratedParameter::hasField(GeneratedBase::FieldType fieldType) const { switch (fieldType) { case FieldType::Name: case FieldType::DataType: case FieldType::Description: return true; default: break; } return false; } QVariant GeneratedParameter::readField(GeneratedBase::FieldType fieldType) const { switch (fieldType) { case FieldType::Name: return m_parameterName; case FieldType::DataType: return m_parameterDataType; case FieldType::Description: return m_parameterDescription; default: break; } return QVariant(); } void GeneratedParameter::writeField(GeneratedBase::FieldType fieldType, QVariant value) { switch (fieldType) { case FieldType::Name: m_parameterName = value.toString(); break; case FieldType::DataType: m_parameterDataType = static_cast(value.toInt()); break; case FieldType::Description: m_parameterDescription = value.toString(); break; default: break; } } void GeneratedParameter::fillComboBox(QComboBox* comboBox, GeneratedBase::FieldType fieldType) { switch (fieldType) { case FieldType::DataType: Serializer::fillComboBox(comboBox, m_parameterDataType); break; default: break; } } bool GeneratedParameter::canHaveSubitems() const { return false; } QStringList GeneratedParameter::getCaptions() const { return QStringList() << QString("%1 %2").arg(Serializer::convertEnumToString(m_parameterDataType)).arg(m_parameterName) << m_parameterDescription; } GeneratedBase* GeneratedParameter::appendItem() { return nullptr; } QString GeneratedParameter::getParameterName() const { return m_parameterName; } void GeneratedParameter::setParameterName(const QString& parameterName) { m_parameterName = parameterName; } GeneratedParameter::DataType GeneratedParameter::getParameterDataType() const { return m_parameterDataType; } void GeneratedParameter::setParameterDataType(const DataType& parameterDataType) { m_parameterDataType = parameterDataType; } QString GeneratedParameter::getParameterDescription() const { return m_parameterDescription; } void GeneratedParameter::setParameterDescription(const QString& parameterDescription) { m_parameterDescription = parameterDescription; } void XFACodeGenerator::generateCode(const QDomDocument& document, QString headerName, QString sourceName) { QString startMark = "/* START GENERATED CODE */"; QString endMark = "/* END GENERATED CODE */"; loadClasses(document); QFile headerFile(headerName); if (headerFile.exists()) { if (headerFile.open(QFile::ReadOnly | QFile::Text)) { QString utfCode = QString::fromUtf8(headerFile.readAll()); headerFile.close(); int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length(); int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive); QString frontPart = utfCode.left(startIndex); QString backPart = utfCode.mid(endIndex); QString headerGeneratedCode = generateHeader(); QString allCode = frontPart + headerGeneratedCode + backPart; headerFile.open(QFile::WriteOnly | QFile::Truncate); headerFile.write(allCode.toUtf8()); headerFile.close(); } } QFile sourceFile(sourceName); if (sourceFile.exists()) { if (sourceFile.open(QFile::ReadOnly | QFile::Text)) { QString utfCode = QString::fromUtf8(sourceFile.readAll()); sourceFile.close(); int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length(); int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive); QString frontPart = utfCode.left(startIndex); QString backPart = utfCode.mid(endIndex); QString sourceGeneratedCode = generateSource(); QString allCode = frontPart + sourceGeneratedCode + backPart; sourceFile.open(QFile::WriteOnly | QFile::Truncate); sourceFile.write(allCode.toUtf8()); sourceFile.close(); } } } void XFACodeGenerator::loadClasses(const QDomDocument& document) { QDomElement xfaElement = document.firstChildElement("xfa"); if (xfaElement.isNull()) { return; } QDomElement element = xfaElement.firstChildElement("template"); if (element.isNull()) { return; } QDomNodeList childNodes = element.elementsByTagName("class"); const int size = childNodes.size(); m_classes.reserve(size); for (int i = 0; i < size; ++i) { QDomNode child = childNodes.item(i); if (!child.isElement()) { continue; } Class myClass; QDomElement childElement = child.toElement(); // Class name myClass.className = childElement.attribute("name"); QDomElement valueElement = childElement.firstChildElement("value"); if (!valueElement.isNull()) { QString valueType = valueElement.attribute("type"); myClass.valueType = createType("node_value", "nodeValue", valueType); } // Attributes QDomNodeList attributes = childElement.elementsByTagName("property"); const int attributeCount = attributes.size(); for (int ai = 0; ai < attributeCount; ++ai) { QDomElement attributeElement = attributes.item(ai).toElement(); QString name = attributeElement.attribute("name"); QString type = attributeElement.attribute("type"); QString defaultValue = attributeElement.attribute("default"); QString id = QString("%1_%2").arg(name, type); Attribute attribute; attribute.attributeName = name; attribute.defaultValue = defaultValue; attribute.type = createType(id, name, type); myClass.attributes.emplace_back(std::move(attribute)); } // Subnodes QDomNodeList subnodes = childElement.elementsByTagName("item"); const int subnodeCount = subnodes.size(); for (int ai = 0; ai < subnodeCount; ++ai) { QDomElement subnodeElement = subnodes.item(ai).toElement(); QString className = subnodeElement.attribute("class"); QString min = subnodeElement.attribute("min"); QString max = subnodeElement.attribute("max"); Subnode subnode; subnode.subnodeName = std::move(className); subnode.min = min.toInt(); subnode.max = (max == "n") ? std::numeric_limits::max() : max.toInt(); myClass.subnodes.emplace_back(std::move(subnode)); } m_classes.emplace_back(std::move(myClass)); } std::map prerequisites; for (const auto& myClass : m_classes) { QStringList& sl = prerequisites[myClass.className]; for (const auto& subnode : myClass.subnodes) { if (subnode.subnodeName != myClass.className) { sl << subnode.subnodeName; } } } std::vector sortedClasses; while (!m_classes.empty()) { auto it = m_classes.begin(); for (; it != m_classes.end(); ++it) { if (prerequisites[it->className].isEmpty()) { break; } } if (it == m_classes.end()) { break; } Class myClass = *it; it = m_classes.erase(it); for (auto& prerequisite : prerequisites) { prerequisite.second.removeAll(myClass.className); } sortedClasses.emplace_back(std::move(myClass)); } sortedClasses.insert(sortedClasses.end(), m_classes.begin(), m_classes.end()); m_classes = std::move(sortedClasses); } const XFACodeGenerator::Type* XFACodeGenerator::createType(QString id, QString name, QString type) { QString simpleType; QString adjustedId = id; if (type == "cdata" || type == "pcdata") { simpleType = "QString"; adjustedId = "QString"; } else if (type == "0 | 1") { simpleType = "bool"; adjustedId = "bool"; } else if (type.contains("measurement")) { simpleType = "XFA_Measurement"; adjustedId = "XFA_Measurement"; } else if (type.contains("integer")) { simpleType = "PDFInteger"; adjustedId = "PDFInteger"; } else if (type.contains("angle")) { simpleType = "PDFReal"; adjustedId = "PDFReal"; } QString enumValuesString = type; auto it = m_types.find(adjustedId); if (it == m_types.end()) { Type typeObject; typeObject.id = adjustedId; typeObject.typeName = simpleType; if (typeObject.typeName.isEmpty()) { QString typeName = name.toUpper(); QString finalTypeName = typeName; int i = 1; while (m_usedTypes.count(finalTypeName)) { finalTypeName = typeName + QString::number(i++); } m_usedTypes.insert(finalTypeName); QString enumValues = enumValuesString.remove(QChar::Space); typeObject.enumValues = enumValues.split("|"); typeObject.typeName = finalTypeName; } it = m_types.insert(std::make_pair(typeObject.id, typeObject)).first; } return &it->second; } QString XFACodeGenerator::generateSource() const { QByteArray ba; { QTextStream stream(&ba, QIODevice::WriteOnly); stream.setEncoding(QStringConverter::Utf8); stream.setRealNumberPrecision(3); stream.setRealNumberNotation(QTextStream::FixedNotation); stream << Qt::endl << Qt::endl; stream << "namespace xfa" << Qt::endl; stream << "{" << Qt::endl << Qt::endl; // Forward declarations for (const Class& myClass : m_classes) { stream << QString("class XFA_%1;").arg(myClass.className) << Qt::endl; } stream << Qt::endl; stream << "class XFA_AbstractVisitor" << Qt::endl; stream << "{" << Qt::endl; stream << "public:" << Qt::endl; stream << " XFA_AbstractVisitor() = default;" << Qt::endl; stream << " virtual ~XFA_AbstractVisitor() = default;" << Qt::endl << Qt::endl; for (const Class& myClass : m_classes) { stream << QString(" virtual void visit(const XFA_%1* node) { Q_UNUSED(node); }").arg(myClass.className) << Qt::endl; } stream << "};" << Qt::endl; stream << Qt::endl; stream << "class XFA_BaseNode : public XFA_AbstractNode" << Qt::endl; stream << "{" << Qt::endl; stream << "public:" << Qt::endl; stream << " using XFA_AbstractNode::parseAttribute;" << Qt::endl; stream << Qt::endl; for (const auto& typeItem : m_types) { const Type& type = typeItem.second; if (type.enumValues.isEmpty()) { continue; } stream << QString(" enum class %1").arg(type.typeName) << Qt::endl; stream << " {" << Qt::endl; for (const QString& enumValue : type.enumValues) { stream << " " << getEnumValueName(enumValue) << "," << Qt::endl; } stream << " };" << Qt::endl << Qt::endl; } for (const auto& typeItem : m_types) { const Type& type = typeItem.second; if (type.enumValues.isEmpty()) { continue; } stream << QString(" static void parseAttribute(const QDomElement& element, QString attributeFieldName, XFA_Attribute<%1>& attribute, QString defaultValue)").arg(type.typeName) << Qt::endl; stream << QString(" {") << Qt::endl; stream << QString(" constexpr std::array enumValues = {") << Qt::endl; for (const QString& enumValue : type.enumValues) { QString adjustedEnumValue = enumValue; adjustedEnumValue.replace("\\", "\\\\"); stream << QString(" std::make_pair(%1::%2, \"%3\"),").arg(type.typeName, getEnumValueName(enumValue), adjustedEnumValue) << Qt::endl; } stream << QString(" };") << Qt::endl; stream << QString(" parseEnumAttribute(element, attributeFieldName, attribute, defaultValue, enumValues);") << Qt::endl; stream << QString(" }") << Qt::endl << Qt::endl; } stream << "};" << Qt::endl << Qt::endl; for (const Class& myClass : m_classes) { stream << QString("class XFA_%1 : public XFA_BaseNode").arg(myClass.className) << Qt::endl; stream << "{" << Qt::endl; stream << "public:" << Qt::endl; QStringList attributeGetters; QStringList attributeDeclarations; for (const Attribute& attribute : myClass.attributes) { QString attributeFieldName = QString("m_%1").arg(attribute.attributeName); QString attributeGetterName = attribute.attributeName; attributeGetterName[0] = attributeGetterName.front().toUpper(); QString attributeDeclaration = QString(" XFA_Attribute<%1> %2;").arg(attribute.type->typeName, attributeFieldName); QString attributeGetter = QString(" %1 get%2() const { return %3.getValueOrDefault(); }").arg(attribute.type->typeName, attributeGetterName, attributeFieldName); attributeDeclarations << attributeDeclaration; attributeGetters << attributeGetter; } stream << Qt::endl; QStringList subnodeGetters; QStringList subnodeDeclarations; for (const Subnode& subnode : myClass.subnodes) { if (subnode.max == 1) { QString subnodeFieldName = QString("m_%1").arg(subnode.subnodeName); QString subnodeTypeName = QString("XFA_%1").arg(subnode.subnodeName); QString subnodeGetterName = subnode.subnodeName; subnodeGetterName[0] = subnodeGetterName.front().toUpper(); QString subnodeDeclaration = QString(" XFA_Node<%1> %2;").arg(subnodeTypeName, subnodeFieldName); QString subnodeGetter = QString(" const %1* get%2() const { return %3.getValue(); }").arg(subnodeTypeName, subnodeGetterName, subnodeFieldName); subnodeDeclarations << subnodeDeclaration; subnodeGetters << subnodeGetter; } else { QString subnodeFieldName = QString("m_%1").arg(subnode.subnodeName); QString subnodeTypeName = QString("std::vector>").arg(subnode.subnodeName); QString subnodeGetterName = subnode.subnodeName; subnodeGetterName[0] = subnodeGetterName.front().toUpper(); QString subnodeDeclaration = QString(" %1 %2;").arg(subnodeTypeName, subnodeFieldName); QString subnodeGetter = QString(" const %1& get%2() const { return %3; }").arg(subnodeTypeName, subnodeGetterName, subnodeFieldName); subnodeDeclarations << subnodeDeclaration; subnodeGetters << subnodeGetter; } } for (const QString& getter : attributeGetters) { stream << getter << Qt::endl; } stream << Qt::endl; for (const QString& getter : subnodeGetters) { stream << getter << Qt::endl; } stream << Qt::endl; if (myClass.valueType) { stream << QString(" const %1* getNodeValue() const { return m_nodeValue.getValue(); }").arg(myClass.valueType->typeName) << Qt::endl << Qt::endl; } stream << QString(" virtual void accept(XFA_AbstractVisitor* visitor) const override { visitor->visit(this); }") << Qt::endl << Qt::endl; stream << QString(" static std::optional parse(const QDomElement& element);").arg(myClass.className) << Qt::endl; stream << Qt::endl; stream << "private:" << Qt::endl; stream << " /* properties */" << Qt::endl; for (const QString& getter : attributeDeclarations) { stream << getter << Qt::endl; } stream << Qt::endl; stream << " /* subnodes */" << Qt::endl; for (const QString& getter : subnodeDeclarations) { stream << getter << Qt::endl; } if (myClass.valueType) { stream << Qt::endl; stream << QString(" XFA_Value<%1> m_nodeValue;").arg(myClass.valueType->typeName) << Qt::endl; } stream << "};" << Qt::endl << Qt::endl; // Class loader stream << QString("std::optional XFA_%1::parse(const QDomElement& element)").arg(myClass.className) << Qt::endl; stream << "{" << Qt::endl; stream << " if (element.isNull())" << Qt::endl << " {" << Qt::endl << " return std::nullopt;" << Qt::endl << " }" << Qt::endl << Qt::endl; stream << QString(" XFA_%1 myClass;").arg(myClass.className) << Qt::endl << Qt::endl; // Load attributes stream << " // load attributes" << Qt::endl; for (const Attribute& attribute : myClass.attributes) { QString attributeFieldName = QString("m_%1").arg(attribute.attributeName); QString adjustedDefaultValue = attribute.defaultValue; adjustedDefaultValue.replace("\\", "\\\\"); stream << QString(" parseAttribute(element, \"%1\", myClass.%2, \"%3\");").arg(attribute.attributeName, attributeFieldName, adjustedDefaultValue) << Qt::endl; } stream << Qt::endl; // Load subitems stream << " // load items" << Qt::endl; for (const Subnode& subnode : myClass.subnodes) { QString subnodeFieldName = QString("m_%1").arg(subnode.subnodeName); stream << QString(" parseItem(element, \"%1\", myClass.%2);").arg(subnode.subnodeName, subnodeFieldName) << Qt::endl; } // Node value if (myClass.valueType) { stream << Qt::endl; stream << " // load node value" << Qt::endl; stream << QString(" parseValue(element, myClass.m_nodeValue);") << Qt::endl << Qt::endl; } stream << " myClass.setOrderFromElement(element);" << Qt::endl; stream << " return myClass;" << Qt::endl; stream << "}" << Qt::endl; stream << Qt::endl << Qt::endl; } stream << "} // namespace xfa" << Qt::endl; stream << Qt::endl << Qt::endl; } return QString::fromUtf8(ba); } QString XFACodeGenerator::getEnumValueName(QString enumName) const { if (!enumName.isEmpty()) { enumName[0] = enumName.front().toUpper(); if (enumName.front().isDigit()) { enumName.push_front("_"); } enumName.replace("-", "_"); enumName.replace('\\', "Backslash"); enumName.replace('/', "Slash"); enumName.replace('.', "_"); } return enumName; } QString XFACodeGenerator::generateHeader() const { QByteArray ba; { QTextStream stream(&ba, QIODevice::WriteOnly); stream.setEncoding(QStringConverter::Utf8); stream.setRealNumberPrecision(3); stream.setRealNumberNotation(QTextStream::FixedNotation); stream << Qt::endl << Qt::endl; stream << "namespace xfa" << Qt::endl; stream << "{" << Qt::endl; stream << "} // namespace xfa" << Qt::endl; stream << Qt::endl << Qt::endl; } return QString::fromUtf8(ba); } }