|
- #include "json_project_storage.h"
-
- #include "domain/hmi_control_registry.h"
- #include "domain/project_limits.h"
-
- #include <QFile>
- #include <QJsonArray>
- #include <QJsonDocument>
- #include <QJsonObject>
- #include <QJsonParseError>
- #include <QSaveFile>
-
- #include <cmath>
- #include <limits>
- #include <string>
- #include <utility>
-
- namespace {
-
- // 当前读写实现支持的工程文件格式版本
- constexpr const char *kCurrentFormatVersion = "3.0";
-
- // 保存解析过程中遇到的第一个错误,避免后续失败覆盖根因
- struct ParseState
- {
- ProjectStorageError error = ProjectStorageError::None;
- std::string message;
-
- // 记录首次解析错误并返回 false,便于解析函数直接向上传播失败
- bool fail(ProjectStorageError new_error, const std::string &new_message)
- {
- if (error == ProjectStorageError::None)
- {
- error = new_error;
- message = new_message;
- }
- return false;
- }
- };
-
- // 将领域层使用的 UTF-8 字符串转换为 Qt 字符串
- QString fromUtf8(const std::string &value)
- {
- return QString::fromUtf8(value.data(), static_cast<int>(value.size()));
- }
-
- // 将 Qt 字符串转换为领域层使用的 UTF-8 字符串
- std::string toUtf8(const QString &value)
- {
- const QByteArray bytes = value.toUtf8();
- return std::string(bytes.constData(), static_cast<std::size_t>(bytes.size()));
- }
-
- // 拼接带上下文的字段路径,用于生成可定位的错误信息
- std::string fieldPath(const std::string &context, const char *field)
- {
- return context + '.' + field;
- }
-
- // 读取必填 JSON 字段,字段不存在时记录 MissingField 错误
- bool readValue(
- const QJsonObject &object,
- const char *field,
- const std::string &context,
- QJsonValue *value,
- ParseState *state)
- {
- const QString key = QString::fromLatin1(field);
- if (!object.contains(key))
- {
- return state->fail(
- ProjectStorageError::MissingField,
- "缺少必填字段:" + fieldPath(context, field));
- }
- *value = object.value(key);
- return true;
- }
-
- // 读取必填字符串字段并转换为 UTF-8 标准字符串
- bool readString(
- const QJsonObject &object,
- const char *field,
- const std::string &context,
- std::string *value,
- ParseState *state,
- std::size_t maximum_bytes = ProjectLimits::kMaximumTextBytes)
- {
- QJsonValue json_value;
- if (!readValue(object, field, context, &json_value, state))
- {
- return false;
- }
- if (!json_value.isString())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- fieldPath(context, field) + " 必须是字符串");
- }
- *value = toUtf8(json_value.toString());
- if (value->size() > maximum_bytes)
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- fieldPath(context, field) + " 超出允许的 UTF-8 字节长度");
- }
- return true;
- }
-
- // 读取必填布尔字段并校验 JSON 类型
- bool readBool(
- const QJsonObject &object,
- const char *field,
- const std::string &context,
- bool *value,
- ParseState *state)
- {
- QJsonValue json_value;
- if (!readValue(object, field, context, &json_value, state))
- {
- return false;
- }
- if (!json_value.isBool())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- fieldPath(context, field) + " 必须是布尔值");
- }
- *value = json_value.toBool();
- return true;
- }
-
- // 读取指定闭区间内的整数,拒绝小数、非有限值和越界值
- bool readInt(
- const QJsonObject &object,
- const char *field,
- const std::string &context,
- int minimum,
- int maximum,
- int *value,
- ParseState *state)
- {
- QJsonValue json_value;
- if (!readValue(object, field, context, &json_value, state))
- {
- return false;
- }
- if (!json_value.isDouble())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- fieldPath(context, field) + " 必须是整数");
- }
-
- // Qt JSON 统一用 double 表示数值,需要额外确认它能无损转换为 int
- const double number = json_value.toDouble();
- if (!std::isfinite(number) || std::floor(number) != number
- || number < minimum || number > maximum)
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- fieldPath(context, field) + " 超出支持的整数范围");
- }
- *value = static_cast<int>(number);
- return true;
- }
-
- // 读取必填对象字段并校验 JSON 类型
- bool readObject(
- const QJsonObject &object,
- const char *field,
- const std::string &context,
- QJsonObject *value,
- ParseState *state)
- {
- QJsonValue json_value;
- if (!readValue(object, field, context, &json_value, state))
- {
- return false;
- }
- if (!json_value.isObject())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- fieldPath(context, field) + " 必须是对象");
- }
- *value = json_value.toObject();
- return true;
- }
-
- // 读取必填数组字段并校验 JSON 类型
- bool readArray(
- const QJsonObject &object,
- const char *field,
- const std::string &context,
- QJsonArray *value,
- ParseState *state,
- int maximum_count = std::numeric_limits<int>::max())
- {
- QJsonValue json_value;
- if (!readValue(object, field, context, &json_value, state))
- {
- return false;
- }
- if (!json_value.isArray())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- fieldPath(context, field) + " 必须是数组");
- }
- *value = json_value.toArray();
- if (value->size() > maximum_count)
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- fieldPath(context, field) + " 的元素数量为 "
- + std::to_string(value->size()) + ",当前上限为 "
- + std::to_string(maximum_count));
- }
- return true;
- }
-
- // 将寄存器地址序列化为包含区域和原始索引的 JSON 对象
- QJsonObject serializeAddress(const RegisterAddress &address)
- {
- QJsonObject object;
- object.insert(QStringLiteral("area"),
- address.area() == RegisterArea::M ? QStringLiteral("M")
- : QStringLiteral("D"));
- object.insert(QStringLiteral("index"), address.index());
- return object;
- }
-
- // 解析寄存器地址并校验区域名称及项目允许的索引范围
- bool parseAddress(
- const QJsonObject &object,
- const std::string &context,
- RegisterAddress *address,
- ParseState *state)
- {
- std::string area_text;
- int index = 0;
- if (!readString(object, "area", context, &area_text, state)
- || !readInt(
- object,
- "index",
- context,
- RegisterAddress::kMinimumIndex,
- RegisterAddress::kMaximumIndex,
- &index,
- state))
- {
- return false;
- }
-
- RegisterArea area = RegisterArea::M;
- if (area_text == "M")
- {
- area = RegisterArea::M;
- }
- else if (area_text == "D")
- {
- area = RegisterArea::D;
- }
- else
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".area 必须是 M 或 D");
- }
-
- *address = RegisterAddress{area, index};
- return true;
- }
-
- QString wordOperandKindName(WordOperandKind kind)
- {
- return kind == WordOperandKind::Constant
- ? QStringLiteral("constant") : QStringLiteral("register");
- }
-
- QJsonObject serializeWordOperand(const WordOperand &operand)
- {
- QJsonObject object;
- object.insert(QStringLiteral("kind"), wordOperandKindName(operand.kind));
- if (operand.kind == WordOperandKind::Constant)
- {
- object.insert(QStringLiteral("value"), operand.constant);
- }
- else
- {
- object.insert(QStringLiteral("address"), serializeAddress(operand.address));
- }
- return object;
- }
-
- bool parseWordOperand(
- const QJsonObject &object,
- const std::string &context,
- WordOperand *operand,
- ParseState *state)
- {
- std::string kind;
- if (!readString(object, "kind", context, &kind, state))
- {
- return false;
- }
- if (kind == "constant")
- {
- int value = 0;
- if (!readInt(
- object,
- "value",
- context,
- std::numeric_limits<std::int16_t>::min(),
- std::numeric_limits<std::int16_t>::max(),
- &value,
- state))
- {
- return false;
- }
- *operand = WordOperand{
- WordOperandKind::Constant,
- RegisterAddress{RegisterArea::D, 0},
- static_cast<std::int16_t>(value)};
- return true;
- }
- if (kind != "register")
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".kind 必须是 constant 或 register");
- }
- QJsonObject address_object;
- RegisterAddress address{RegisterArea::D, 0};
- if (!readObject(object, "address", context, &address_object, state)
- || !parseAddress(address_object, context + ".address", &address, state))
- {
- return false;
- }
- if (address.area() != RegisterArea::D)
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".address 必须使用 D 区地址");
- }
- *operand = WordOperand{
- WordOperandKind::Register,
- address,
- 0};
- return true;
- }
-
- // 将 HMI 控件类型枚举转换为工程文件中的稳定字符串
- QString hmiControlTypeName(HmiControlType type)
- {
- const HmiControlDescriptor *descriptor = findHmiControlDescriptor(type);
- return descriptor == nullptr
- ? QString{}
- : QString::fromLatin1(descriptor->storageName);
- }
-
- // 将工程文件中的控件类型字符串转换为 HMI 控件类型枚举
- bool parseHmiControlType(
- const std::string &value, HmiControlType *type, ParseState *state)
- {
- const HmiControlDescriptor *descriptor = findHmiControlDescriptor(value);
- if (descriptor == nullptr)
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "不支持的 HMI 控件类型:" + value);
- }
- *type = descriptor->type;
- return true;
- }
-
- QString hmiButtonOperationName(HmiButtonOperation operation)
- {
- switch (operation)
- {
- case HmiButtonOperation::SetOn:
- {
- return QStringLiteral("setOn");
- }
- case HmiButtonOperation::SetOff:
- {
- return QStringLiteral("setOff");
- }
- case HmiButtonOperation::Toggle:
- {
- return QStringLiteral("toggle");
- }
- case HmiButtonOperation::MomentaryOn:
- default:
- {
- return QStringLiteral("momentaryOn");
- }
- }
- }
-
- bool parseHmiButtonOperation(
- const std::string &value, HmiButtonOperation *operation, ParseState *state)
- {
- if (value == "setOn")
- {
- *operation = HmiButtonOperation::SetOn;
- }
- else if (value == "setOff")
- {
- *operation = HmiButtonOperation::SetOff;
- }
- else if (value == "toggle")
- {
- *operation = HmiButtonOperation::Toggle;
- }
- else if (value == "momentaryOn")
- {
- *operation = HmiButtonOperation::MomentaryOn;
- }
- else
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "不支持的 HMI 按钮操作:" + value);
- }
- return true;
- }
-
- QString alarmConditionName(AlarmCondition condition)
- {
- switch (condition)
- {
- case AlarmCondition::MOn:
- {
- return QStringLiteral("mOn");
- }
- case AlarmCondition::MOff:
- {
- return QStringLiteral("mOff");
- }
- case AlarmCondition::DHigh:
- {
- return QStringLiteral("dHigh");
- }
- case AlarmCondition::DLow:
- {
- return QStringLiteral("dLow");
- }
- default:
- {
- return {};
- }
- }
- }
-
- bool parseAlarmCondition(
- const std::string &value, AlarmCondition *condition, ParseState *state)
- {
- if (value == "mOn")
- {
- *condition = AlarmCondition::MOn;
- }
- else if (value == "mOff")
- {
- *condition = AlarmCondition::MOff;
- }
- else if (value == "dHigh")
- {
- *condition = AlarmCondition::DHigh;
- }
- else if (value == "dLow")
- {
- *condition = AlarmCondition::DLow;
- }
- else
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "不支持的报警触发条件:" + value);
- }
- return true;
- }
-
- QJsonObject serializeAlarmDefinition(const AlarmDefinition &definition)
- {
- QJsonObject object;
- object.insert(QStringLiteral("id"), fromUtf8(definition.id));
- object.insert(QStringLiteral("address"), serializeAddress(definition.address));
- object.insert(
- QStringLiteral("condition"), alarmConditionName(definition.condition));
- object.insert(QStringLiteral("threshold"), definition.threshold);
- object.insert(QStringLiteral("message"), fromUtf8(definition.message));
- return object;
- }
-
- bool parseAlarmDefinition(
- const QJsonObject &object,
- const std::string &context,
- AlarmDefinition *definition,
- ParseState *state)
- {
- QJsonObject address;
- std::string condition;
- int threshold = 0;
- if (!readString(
- object, "id", context, &definition->id, state,
- ProjectLimits::kMaximumIdBytes)
- || !readObject(object, "address", context, &address, state)
- || !readString(object, "condition", context, &condition, state)
- || !readInt(
- object,
- "threshold",
- context,
- std::numeric_limits<std::int16_t>::min(),
- std::numeric_limits<std::int16_t>::max(),
- &threshold,
- state)
- || !readString(object, "message", context, &definition->message, state))
- {
- return false;
- }
- if (!parseAddress(address, context + ".address", &definition->address, state)
- || !parseAlarmCondition(condition, &definition->condition, state))
- {
- return false;
- }
- definition->threshold = static_cast<std::int16_t>(threshold);
- return true;
- }
-
- QJsonObject serializeRegisterComment(const RegisterComment &comment)
- {
- QJsonObject object;
- object.insert(QStringLiteral("address"), serializeAddress(comment.address));
- object.insert(QStringLiteral("text"), fromUtf8(comment.text));
- return object;
- }
-
- bool parseRegisterComment(
- const QJsonObject &object,
- const std::string &context,
- RegisterComment *comment,
- ParseState *state)
- {
- QJsonObject address;
- if (!readObject(object, "address", context, &address, state)
- || !readString(object, "text", context, &comment->text, state))
- {
- return false;
- }
- return parseAddress(address, context + ".address", &comment->address, state);
- }
-
- // 将 HMI 控件矩形区域序列化为 JSON 对象
- QJsonObject serializeBounds(const HmiRect &bounds)
- {
- QJsonObject object;
- object.insert(QStringLiteral("x"), bounds.x);
- object.insert(QStringLiteral("y"), bounds.y);
- object.insert(QStringLiteral("width"), bounds.width);
- object.insert(QStringLiteral("height"), bounds.height);
- return object;
- }
-
- // 解析控件矩形区域,允许任意坐标但要求宽高为正数
- bool parseBounds(
- const QJsonObject &object,
- const std::string &context,
- HmiRect *bounds,
- ParseState *state)
- {
- return readInt(
- object,
- "x",
- context,
- 0,
- ProjectLimits::kMaximumHmiPageWidth,
- &bounds->x,
- state)
- && readInt(
- object,
- "y",
- context,
- 0,
- ProjectLimits::kMaximumHmiPageHeight,
- &bounds->y,
- state)
- && readInt(
- object,
- "width",
- context,
- 1,
- ProjectLimits::kMaximumHmiControlWidth,
- &bounds->width,
- state)
- && readInt(
- object,
- "height",
- context,
- 1,
- ProjectLimits::kMaximumHmiControlHeight,
- &bounds->height,
- state);
- }
-
- // 将 HMI 控件的字符串扩展属性序列化为 JSON 对象
- QJsonObject serializeProperties(const std::map<std::string, std::string> &properties)
- {
- QJsonObject object;
- for (const auto &property : properties)
- {
- object.insert(fromUtf8(property.first), fromUtf8(property.second));
- }
- return object;
- }
-
- // 解析 HMI 控件扩展属性,并确保所有属性值都是字符串
- bool parseProperties(
- const QJsonObject &object,
- std::map<std::string, std::string> *properties,
- ParseState *state)
- {
- if (object.size() > static_cast<int>(ProjectLimits::kMaximumHmiProperties))
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "单个 HMI 控件最多保存 64 对扩展属性");
- }
- for (auto current = object.constBegin(); current != object.constEnd(); ++current)
- {
- if (!current.value().isString())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "HMI 控件属性值必须是字符串");
- }
- const std::string key = toUtf8(current.key());
- const std::string value = toUtf8(current.value().toString());
- if (key.empty() || key.size() > ProjectLimits::kMaximumPropertyKeyBytes
- || value.size() > ProjectLimits::kMaximumPropertyValueBytes)
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "HMI 控件属性名称最多 128 字节,属性值最多 1024 字节");
- }
- properties->emplace(key, value);
- }
- return true;
- }
-
- QJsonValue serializeOptionalNumber(const std::optional<double> &value)
- {
- return value.has_value() ? QJsonValue(*value) : QJsonValue(QJsonValue::Null);
- }
-
- QJsonObject serializeStatusTextConfig(const HmiStatusTextConfig &config)
- {
- QJsonObject object;
- if (const auto *bit = std::get_if<HmiStatusBitTextConfig>(&config))
- {
- object.insert(QStringLiteral("source"), QStringLiteral("m"));
- object.insert(QStringLiteral("offText"), fromUtf8(bit->offText));
- object.insert(QStringLiteral("onText"), fromUtf8(bit->onText));
- return object;
- }
- object.insert(QStringLiteral("source"), QStringLiteral("d"));
- QJsonArray ranges;
- for (const HmiStatusValueRange &range :
- std::get<HmiStatusWordTextConfig>(config).ranges)
- {
- QJsonObject range_object;
- range_object.insert(
- QStringLiteral("lower"), serializeOptionalNumber(range.lowerBound));
- range_object.insert(
- QStringLiteral("upper"), serializeOptionalNumber(range.upperBound));
- range_object.insert(QStringLiteral("text"), fromUtf8(range.text));
- ranges.append(range_object);
- }
- object.insert(QStringLiteral("ranges"), ranges);
- return object;
- }
-
- bool readNullableFiniteNumber(
- const QJsonObject &object,
- const char *field,
- const std::string &context,
- std::optional<double> *value,
- ParseState *state)
- {
- QJsonValue json_value;
- if (!readValue(object, field, context, &json_value, state))
- {
- return false;
- }
- if (json_value.isNull())
- {
- value->reset();
- return true;
- }
- if (!json_value.isDouble() || !std::isfinite(json_value.toDouble()))
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- fieldPath(context, field) + " 必须是有限数值或 null");
- }
- *value = json_value.toDouble();
- return true;
- }
-
- bool parseStatusTextConfig(
- const QJsonObject &object,
- const std::string &context,
- HmiStatusTextConfig *config,
- ParseState *state)
- {
- std::string source;
- if (!readString(object, "source", context, &source, state))
- {
- return false;
- }
- if (source == "m")
- {
- HmiStatusBitTextConfig bit;
- if (!readString(object, "offText", context, &bit.offText, state)
- || !readString(object, "onText", context, &bit.onText, state))
- {
- return false;
- }
- *config = std::move(bit);
- return true;
- }
- if (source != "d")
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".source 必须是 m 或 d");
- }
- QJsonArray range_array;
- if (!readArray(object, "ranges", context, &range_array, state)
- || range_array.isEmpty()
- || range_array.size()
- > static_cast<int>(ProjectLimits::kMaximumStatusTextRanges))
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".ranges 必须包含 1~16 个区间");
- }
- HmiStatusWordTextConfig word;
- word.ranges.reserve(static_cast<std::size_t>(range_array.size()));
- for (int index = 0; index < range_array.size(); ++index)
- {
- if (!range_array.at(index).isObject())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".ranges 的元素必须是对象");
- }
- const QJsonObject range_object = range_array.at(index).toObject();
- const std::string range_context = context + ".ranges["
- + std::to_string(index) + ']';
- HmiStatusValueRange range;
- if (!readNullableFiniteNumber(
- range_object, "lower", range_context,
- &range.lowerBound, state)
- || !readNullableFiniteNumber(
- range_object, "upper", range_context,
- &range.upperBound, state)
- || !readString(
- range_object, "text", range_context, &range.text, state))
- {
- return false;
- }
- word.ranges.push_back(std::move(range));
- }
- *config = std::move(word);
- return true;
- }
-
- // 将单个 HMI 控件及其可选寄存器绑定序列化为 JSON 对象
- QJsonObject serializeHmiControl(const HmiControl &control)
- {
- QJsonObject object;
- object.insert(QStringLiteral("id"), fromUtf8(control.id));
- object.insert(QStringLiteral("type"), hmiControlTypeName(control.type));
- object.insert(QStringLiteral("bounds"), serializeBounds(control.bounds));
- object.insert(QStringLiteral("text"), fromUtf8(control.text));
- // 使用 JSON null 明确表示控件没有寄存器绑定
- if (control.binding.has_value())
- {
- object.insert(QStringLiteral("binding"), serializeAddress(*control.binding));
- }
- else
- {
- object.insert(QStringLiteral("binding"), QJsonValue::Null);
- }
- object.insert(QStringLiteral("properties"), serializeProperties(control.properties));
- const bool status_word = control.type == HmiControlType::StatusText
- && control.statusText.has_value()
- && std::holds_alternative<HmiStatusWordTextConfig>(*control.statusText);
- if (control.type == HmiControlType::NumericDisplay
- || control.type == HmiControlType::NumericInput
- || status_word)
- {
- object.insert(
- QStringLiteral("dataType"),
- QString::fromLatin1(registerDataTypeName(control.dataType)));
- }
- if (control.type == HmiControlType::Button)
- {
- object.insert(
- QStringLiteral("buttonOperation"),
- hmiButtonOperationName(control.buttonOperation));
- }
- if (control.type == HmiControlType::PageJump)
- {
- object.insert(
- QStringLiteral("targetPageId"),
- fromUtf8(control.pageJump.has_value()
- ? control.pageJump->targetPageId
- : std::string{}));
- }
- if (control.type == HmiControlType::StatusText)
- {
- object.insert(
- QStringLiteral("statusText"),
- control.statusText.has_value()
- ? serializeStatusTextConfig(*control.statusText)
- : QJsonObject{});
- }
- return object;
- }
-
- // 解析单个 HMI 控件,并逐层校验类型、区域、绑定和扩展属性
- bool parseHmiControl(
- const QJsonObject &object,
- const std::string &context,
- HmiControl *control,
- ParseState *state)
- {
- std::string type_text;
- QJsonObject bounds;
- QJsonObject properties;
- QJsonValue binding;
- if (!readString(
- object, "id", context, &control->id, state,
- ProjectLimits::kMaximumIdBytes)
- || !readString(object, "type", context, &type_text, state)
- || !readObject(object, "bounds", context, &bounds, state)
- || !readString(object, "text", context, &control->text, state)
- || !readValue(object, "binding", context, &binding, state)
- || !readObject(object, "properties", context, &properties, state))
- {
- return false;
- }
- if (!parseHmiControlType(type_text, &control->type, state)
- || !parseBounds(bounds, context + ".bounds", &control->bounds, state)
- || !parseProperties(properties, &control->properties, state))
- {
- return false;
- }
- if (control->type == HmiControlType::StatusText)
- {
- QJsonObject status_object;
- HmiStatusTextConfig status_config = HmiStatusBitTextConfig{};
- if (!readObject(object, "statusText", context, &status_object, state)
- || !parseStatusTextConfig(
- status_object, context + ".statusText", &status_config, state))
- {
- return false;
- }
- control->statusText = std::move(status_config);
- }
- const bool status_word = control->type == HmiControlType::StatusText
- && control->statusText.has_value()
- && std::holds_alternative<HmiStatusWordTextConfig>(*control->statusText);
- if (control->type == HmiControlType::NumericDisplay
- || control->type == HmiControlType::NumericInput
- || status_word)
- {
- std::string data_type_text;
- if (!readString(object, "dataType", context, &data_type_text, state)
- || !parseRegisterDataType(data_type_text, &control->dataType))
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".dataType 必须是 int16、int32、float32 或 float64");
- }
- }
- if (control->type == HmiControlType::Button)
- {
- std::string operation_text;
- if (!readString(
- object,
- "buttonOperation",
- context,
- &operation_text,
- state)
- || !parseHmiButtonOperation(
- operation_text, &control->buttonOperation, state))
- {
- return false;
- }
- }
- if (control->type == HmiControlType::PageJump)
- {
- std::string target_page_id;
- if (!readString(
- object, "targetPageId", context, &target_page_id, state,
- ProjectLimits::kMaximumIdBytes))
- {
- return false;
- }
- control->pageJump = HmiPageJumpConfig{std::move(target_page_id)};
- }
- // binding 允许为 null,其余非空值必须是合法的寄存器地址对象
- if (binding.isNull())
- {
- control->binding.reset();
- return true;
- }
- if (!binding.isObject())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".binding 必须是对象或 null");
- }
-
- RegisterAddress address{RegisterArea::M, 0};
- if (!parseAddress(binding.toObject(), context + ".binding", &address, state))
- {
- return false;
- }
- control->binding = address;
- return true;
- }
-
- // 将 HMI 页面及其全部控件序列化为 JSON 对象
- QJsonObject serializeHmiPage(const HmiPage &page)
- {
- QJsonArray controls;
- for (const HmiControl &control : page.controls)
- {
- controls.append(serializeHmiControl(control));
- }
-
- QJsonObject object;
- object.insert(QStringLiteral("id"), fromUtf8(page.id));
- object.insert(QStringLiteral("name"), fromUtf8(page.name));
- object.insert(QStringLiteral("width"), page.width);
- object.insert(QStringLiteral("height"), page.height);
- object.insert(QStringLiteral("controls"), controls);
- return object;
- }
-
- // 解析 HMI 页面基础信息,并按数组顺序构造页面控件
- bool parseHmiPage(
- const QJsonObject &object,
- const std::string &context,
- const ProjectLimitSettings &limits,
- HmiPage *page,
- ParseState *state)
- {
- QJsonArray controls;
- if (!readString(
- object, "id", context, &page->id, state,
- ProjectLimits::kMaximumIdBytes)
- || !readString(object, "name", context, &page->name, state)
- || !readInt(object, "width", context,
- ProjectLimits::kMinimumHmiPageWidth,
- ProjectLimits::kMaximumHmiPageWidth,
- &page->width, state)
- || !readInt(object, "height", context,
- ProjectLimits::kMinimumHmiPageHeight,
- ProjectLimits::kMaximumHmiPageHeight,
- &page->height, state)
- || !readArray(
- object, "controls", context, &controls, state,
- static_cast<int>(limits.maximumHmiControlsPerPage)))
- {
- return false;
- }
-
- // 预留准确容量,避免逐项加入控件时重复扩容
- page->controls.reserve(static_cast<std::size_t>(controls.size()));
- for (int index = 0; index < controls.size(); ++index)
- {
- if (!controls.at(index).isObject())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".controls 的元素必须是对象");
- }
- HmiControl control;
- if (!parseHmiControl(
- controls.at(index).toObject(),
- context + ".controls[" + std::to_string(index) + ']',
- &control,
- state))
- {
- return false;
- }
- page->controls.push_back(std::move(control));
- }
- return true;
- }
-
- // 将触点模式枚举转换为工程文件中的稳定字符串
- QString contactModeName(ContactMode mode)
- {
- return mode == ContactMode::NormallyOpen
- ? QStringLiteral("normallyOpen")
- : QStringLiteral("normallyClosed");
- }
-
- // 将工程文件中的触点模式字符串转换为枚举
- bool parseContactMode(
- const std::string &value, ContactMode *mode, ParseState *state)
- {
- if (value == "normallyOpen")
- {
- *mode = ContactMode::NormallyOpen;
- }
- else if (value == "normallyClosed")
- {
- *mode = ContactMode::NormallyClosed;
- }
- else
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "不支持的触点模式:" + value);
- }
- return true;
- }
-
- QString edgeModeName(EdgeMode mode)
- {
- return mode == EdgeMode::Rising
- ? QStringLiteral("rising")
- : QStringLiteral("falling");
- }
-
- bool parseEdgeMode(
- const std::string &value, EdgeMode *mode, ParseState *state)
- {
- if (value == "rising")
- {
- *mode = EdgeMode::Rising;
- }
- else if (value == "falling")
- {
- *mode = EdgeMode::Falling;
- }
- else
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "不支持的边沿模式:" + value);
- }
- return true;
- }
-
- // 将线圈模式枚举转换为工程文件中的稳定字符串
- QString coilModeName(CoilMode mode)
- {
- switch (mode)
- {
- case CoilMode::Normal:
- {
- return QStringLiteral("normal");
- }
- case CoilMode::Set:
- {
- return QStringLiteral("set");
- }
- case CoilMode::Reset:
- {
- return QStringLiteral("reset");
- }
- default:
- {
- return {};
- }
- }
- }
-
- // 将工程文件中的线圈模式字符串转换为枚举
- bool parseCoilMode(const std::string &value, CoilMode *mode, ParseState *state)
- {
- if (value == "normal")
- {
- *mode = CoilMode::Normal;
- }
- else if (value == "set")
- {
- *mode = CoilMode::Set;
- }
- else if (value == "reset")
- {
- *mode = CoilMode::Reset;
- }
- else
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "不支持的线圈模式:" + value);
- }
- return true;
- }
-
- // 将比较运算符枚举转换为工程文件中的稳定字符串
- QString comparisonName(ComparisonOperator comparison)
- {
- switch (comparison)
- {
- case ComparisonOperator::Equal:
- {
- return QStringLiteral("equal");
- }
- case ComparisonOperator::NotEqual:
- {
- return QStringLiteral("notEqual");
- }
- case ComparisonOperator::LessThan:
- {
- return QStringLiteral("lessThan");
- }
- case ComparisonOperator::LessThanOrEqual:
- {
- return QStringLiteral("lessThanOrEqual");
- }
- case ComparisonOperator::GreaterThan:
- {
- return QStringLiteral("greaterThan");
- }
- case ComparisonOperator::GreaterThanOrEqual:
- {
- return QStringLiteral("greaterThanOrEqual");
- }
- default:
- {
- return {};
- }
- }
- }
-
- // 将工程文件中的比较运算符字符串转换为枚举
- bool parseComparison(
- const std::string &value,
- ComparisonOperator *comparison,
- ParseState *state)
- {
- if (value == "equal")
- {
- *comparison = ComparisonOperator::Equal;
- }
- else if (value == "notEqual")
- {
- *comparison = ComparisonOperator::NotEqual;
- }
- else if (value == "lessThan")
- {
- *comparison = ComparisonOperator::LessThan;
- }
- else if (value == "lessThanOrEqual")
- {
- *comparison = ComparisonOperator::LessThanOrEqual;
- }
- else if (value == "greaterThan")
- {
- *comparison = ComparisonOperator::GreaterThan;
- }
- else if (value == "greaterThanOrEqual")
- {
- *comparison = ComparisonOperator::GreaterThanOrEqual;
- }
- else
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "不支持的比较运算符:" + value);
- }
- return true;
- }
-
- // 将触点节点配置序列化,并写入用于反序列化分派的类型标记
- QJsonObject serializeNodeConfig(const ContactNodeConfig &config)
- {
- QJsonObject object;
- object.insert(QStringLiteral("type"), QStringLiteral("contact"));
- object.insert(QStringLiteral("address"), serializeAddress(config.address));
- object.insert(QStringLiteral("mode"), contactModeName(config.mode));
- return object;
- }
-
- QJsonObject serializeNodeConfig(const EdgeContactNodeConfig &config)
- {
- QJsonObject object;
- object.insert(QStringLiteral("type"), QStringLiteral("edgeContact"));
- object.insert(QStringLiteral("address"), serializeAddress(config.address));
- object.insert(QStringLiteral("mode"), edgeModeName(config.mode));
- return object;
- }
-
- // 将线圈节点配置序列化,并写入用于反序列化分派的类型标记
- QJsonObject serializeNodeConfig(const CoilNodeConfig &config)
- {
- QJsonObject object;
- object.insert(QStringLiteral("type"), QStringLiteral("coil"));
- object.insert(QStringLiteral("address"), serializeAddress(config.address));
- object.insert(QStringLiteral("mode"), coilModeName(config.mode));
- return object;
- }
-
- // 将数值比较节点配置序列化,并保留有符号 16 位比较常量
- QJsonObject serializeNodeConfig(const CompareNodeConfig &config)
- {
- QJsonObject object;
- object.insert(QStringLiteral("type"), QStringLiteral("compare"));
- object.insert(QStringLiteral("address"), serializeAddress(config.address));
- object.insert(QStringLiteral("comparison"), comparisonName(config.comparison));
- object.insert(QStringLiteral("value"), config.value);
- return object;
- }
-
- QJsonObject serializeNodeConfig(const MoveNodeConfig &config)
- {
- QJsonObject object;
- object.insert(QStringLiteral("type"), QStringLiteral("move"));
- object.insert(QStringLiteral("source"), serializeWordOperand(config.source));
- object.insert(QStringLiteral("destination"), serializeAddress(config.destination));
- return object;
- }
-
- QString arithmeticOperationName(ArithmeticOperation operation)
- {
- return operation == ArithmeticOperation::Add
- ? QStringLiteral("add") : QStringLiteral("subtract");
- }
-
- QJsonObject serializeNodeConfig(const ArithmeticNodeConfig &config)
- {
- QJsonObject object;
- object.insert(QStringLiteral("type"), QStringLiteral("arithmetic"));
- object.insert(QStringLiteral("operation"), arithmeticOperationName(config.operation));
- object.insert(QStringLiteral("left"), serializeWordOperand(config.left));
- object.insert(QStringLiteral("right"), serializeWordOperand(config.right));
- object.insert(QStringLiteral("destination"), serializeAddress(config.destination));
- return object;
- }
-
- // 将逻辑节点序列化,并根据 variant 中的实际配置类型选择对应重载
- QJsonObject serializeLogicNode(const LogicNode &node)
- {
- QJsonObject object;
- object.insert(QStringLiteral("id"), fromUtf8(node.id));
- object.insert(QStringLiteral("configured"), node.configured);
- // std::visit 将不同节点配置统一转换为 config JSON 对象
- object.insert(
- QStringLiteral("config"),
- std::visit(
- [](const auto &config)
- {
- return serializeNodeConfig(config);
- },
- node.config));
- return object;
- }
-
- // 根据 type 字段解析具体节点配置,并写入 LogicNodeConfig 变体
- bool parseNodeConfig(
- const QJsonObject &object,
- const std::string &context,
- LogicNodeConfig *config,
- ParseState *state)
- {
- std::string type;
- if (!readString(object, "type", context, &type, state))
- {
- return false;
- }
-
- if (type == "move")
- {
- QJsonObject source_object;
- QJsonObject destination_object;
- WordOperand source;
- RegisterAddress destination{RegisterArea::D, 0};
- if (!readObject(object, "source", context, &source_object, state)
- || !parseWordOperand(source_object, context + ".source", &source, state)
- || !readObject(object, "destination", context, &destination_object, state)
- || !parseAddress(
- destination_object,
- context + ".destination",
- &destination,
- state))
- {
- return false;
- }
- *config = MoveNodeConfig{source, destination};
- return true;
- }
- if (type == "arithmetic")
- {
- std::string operation_text;
- QJsonObject left_object;
- QJsonObject right_object;
- QJsonObject destination_object;
- WordOperand left;
- WordOperand right;
- RegisterAddress destination{RegisterArea::D, 0};
- ArithmeticOperation operation = ArithmeticOperation::Add;
- if (!readString(object, "operation", context, &operation_text, state))
- {
- return false;
- }
- if (operation_text == "add")
- {
- operation = ArithmeticOperation::Add;
- }
- else if (operation_text == "subtract")
- {
- operation = ArithmeticOperation::Subtract;
- }
- else
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "不支持的算术运算:" + operation_text);
- }
- if (!readObject(object, "left", context, &left_object, state)
- || !parseWordOperand(left_object, context + ".left", &left, state)
- || !readObject(object, "right", context, &right_object, state)
- || !parseWordOperand(right_object, context + ".right", &right, state)
- || !readObject(object, "destination", context, &destination_object, state)
- || !parseAddress(
- destination_object,
- context + ".destination",
- &destination,
- state))
- {
- return false;
- }
- *config = ArithmeticNodeConfig{operation, left, right, destination};
- return true;
- }
-
- if (type != "contact" && type != "edgeContact"
- && type != "coil" && type != "compare")
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "不支持的逻辑节点类型:" + type);
- }
-
- QJsonObject address_object;
- if (!readObject(object, "address", context, &address_object, state))
- {
- return false;
- }
- RegisterAddress address{RegisterArea::M, 0};
- if (!parseAddress(address_object, context + ".address", &address, state))
- {
- return false;
- }
-
- // 每种节点只读取自身需要的字段,避免无关配置进入领域模型
- if (type == "contact")
- {
- std::string mode_text;
- ContactMode mode = ContactMode::NormallyOpen;
- if (!readString(object, "mode", context, &mode_text, state)
- || !parseContactMode(mode_text, &mode, state))
- {
- return false;
- }
- *config = ContactNodeConfig{address, mode};
- return true;
- }
- if (type == "edgeContact")
- {
- std::string mode_text;
- EdgeMode mode = EdgeMode::Rising;
- if (!readString(object, "mode", context, &mode_text, state)
- || !parseEdgeMode(mode_text, &mode, state))
- {
- return false;
- }
- *config = EdgeContactNodeConfig{address, mode};
- return true;
- }
- if (type == "coil")
- {
- std::string mode_text;
- CoilMode mode = CoilMode::Normal;
- if (!readString(object, "mode", context, &mode_text, state)
- || !parseCoilMode(mode_text, &mode, state))
- {
- return false;
- }
- *config = CoilNodeConfig{address, mode};
- return true;
- }
- if (type == "compare")
- {
- std::string comparison_text;
- int value = 0;
- ComparisonOperator comparison = ComparisonOperator::Equal;
- if (!readString(object, "comparison", context, &comparison_text, state)
- || !readInt(
- object,
- "value",
- context,
- std::numeric_limits<std::int16_t>::min(),
- std::numeric_limits<std::int16_t>::max(),
- &value,
- state)
- || !parseComparison(comparison_text, &comparison, state))
- {
- return false;
- }
- // 先按 int 校验范围,再安全收窄为领域模型要求的 int16_t
- *config = CompareNodeConfig{
- address, comparison, static_cast<std::int16_t>(value)};
- return true;
- }
- return state->fail(ProjectStorageError::InvalidField, "逻辑节点类型解析失败");
- }
-
- // 解析逻辑节点标识及其多态配置
- bool parseLogicNode(
- const QJsonObject &object,
- const std::string &context,
- LogicNode *node,
- ParseState *state)
- {
- QJsonObject config;
- if (!readString(
- object, "id", context, &node->id, state,
- ProjectLimits::kMaximumIdBytes)
- || !readBool(object, "configured", context, &node->configured, state)
- || !readObject(object, "config", context, &config, state)
- || !parseNodeConfig(config, context + ".config", &node->config, state))
- {
- return false;
- }
- return true;
- }
-
- QJsonObject serializeLadderRung(const LadderRung &rung)
- {
- QJsonObject object;
- object.insert(QStringLiteral("id"), fromUtf8(rung.id));
- object.insert(QStringLiteral("name"), fromUtf8(rung.name));
- object.insert(QStringLiteral("comment"), fromUtf8(rung.comment));
- QJsonArray cells;
- for (const LadderCell &cell : rung.cells)
- {
- QJsonObject cell_object;
- cell_object.insert(QStringLiteral("id"), fromUtf8(cell.id));
- cell_object.insert(
- QStringLiteral("kind"),
- cell.kind == LadderCellKind::Node
- ? QStringLiteral("node")
- : cell.kind == LadderCellKind::Wire
- ? QStringLiteral("wire") : QStringLiteral("gap"));
- if (cell.node.has_value())
- {
- cell_object.insert(QStringLiteral("node"), serializeLogicNode(*cell.node));
- }
- cells.append(cell_object);
- }
- object.insert(QStringLiteral("cells"), cells);
- object.insert(
- QStringLiteral("output"),
- rung.output.has_value() ? QJsonValue(serializeLogicNode(*rung.output))
- : QJsonValue(QJsonValue::Null));
- return object;
- }
-
- bool parseLadderRung(
- const QJsonObject &object,
- const std::string &context,
- LadderRung *rung,
- ParseState *state)
- {
- QJsonArray cells;
- QJsonValue output;
- if (!readString(
- object, "id", context, &rung->id, state,
- ProjectLimits::kMaximumIdBytes)
- || !readString(object, "name", context, &rung->name, state)
- || !readString(object, "comment", context, &rung->comment, state)
- || !readArray(object, "cells", context, &cells, state,
- ProjectLimits::kMaximumConditionColumns)
- || !readValue(object, "output", context, &output, state))
- {
- return false;
- }
- if (cells.size() != ProjectLimits::kMaximumConditionColumns)
- {
- return state->fail(ProjectStorageError::InvalidField,
- context + ".cells 必须严格包含 10 个网格");
- }
- for (int index = 0; index < cells.size(); ++index)
- {
- if (!cells.at(index).isObject())
- {
- return state->fail(ProjectStorageError::InvalidField,
- context + ".cells 的元素必须是对象");
- }
- const QJsonObject cell_object = cells.at(index).toObject();
- const std::string cell_context = context + ".cells["
- + std::to_string(index) + ']';
- LadderCell cell;
- std::string kind;
- if (!readString(cell_object, "id", cell_context, &cell.id, state,
- ProjectLimits::kMaximumIdBytes)
- || !readString(cell_object, "kind", cell_context, &kind, state))
- {
- return false;
- }
- if (kind == "gap")
- {
- cell.kind = LadderCellKind::Gap;
- }
- else if (kind == "wire")
- {
- cell.kind = LadderCellKind::Wire;
- }
- else if (kind == "node")
- {
- cell.kind = LadderCellKind::Node;
- QJsonObject node_object;
- if (!readObject(cell_object, "node", cell_context, &node_object, state))
- {
- return false;
- }
- LogicNode node;
- if (!parseLogicNode(node_object, cell_context + ".node", &node, state))
- {
- return false;
- }
- cell.node = std::move(node);
- }
- else
- {
- return state->fail(ProjectStorageError::InvalidField,
- cell_context + ".kind 必须是 gap、wire 或 node");
- }
- rung->cells.push_back(std::move(cell));
- }
- if (output.isNull())
- {
- rung->output.reset();
- return true;
- }
- if (!output.isObject())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".output 必须是对象或 null");
- }
- LogicNode node;
- if (!parseLogicNode(output.toObject(), context + ".output", &node, state))
- {
- return false;
- }
- rung->output = std::move(node);
- return true;
- }
-
- QJsonObject serializeControlLogic(const ControlLogic &logic)
- {
- QJsonArray rungs;
- for (const LadderRung &rung : logic.rungs)
- {
- rungs.append(serializeLadderRung(rung));
- }
- QJsonArray connections;
- for (const VerticalConnection &connection : logic.verticalConnections)
- {
- QJsonObject item;
- item.insert(QStringLiteral("id"), fromUtf8(connection.id));
- item.insert(QStringLiteral("upperRungId"), fromUtf8(connection.upperRungId));
- item.insert(QStringLiteral("lowerRungId"), fromUtf8(connection.lowerRungId));
- item.insert(QStringLiteral("columnBoundary"), connection.columnBoundary);
- connections.append(item);
- }
- QJsonObject object;
- object.insert(QStringLiteral("id"), fromUtf8(logic.id));
- object.insert(QStringLiteral("name"), fromUtf8(logic.name));
- object.insert(QStringLiteral("enabled"), logic.enabled);
- object.insert(QStringLiteral("rungs"), rungs);
- object.insert(QStringLiteral("verticalConnections"), connections);
- return object;
- }
-
- bool parseControlLogic(
- const QJsonObject &object,
- const std::string &context,
- const ProjectLimitSettings &limits,
- ControlLogic *logic,
- ParseState *state)
- {
- QJsonArray rungs;
- QJsonArray connections;
- if (!readString(
- object, "id", context, &logic->id, state,
- ProjectLimits::kMaximumIdBytes)
- || !readString(object, "name", context, &logic->name, state)
- || !readBool(object, "enabled", context, &logic->enabled, state)
- || !readArray(
- object, "rungs", context, &rungs, state,
- static_cast<int>(limits.maximumRungsPerLogic))
- || !readArray(
- object, "verticalConnections", context, &connections, state,
- static_cast<int>(ProjectLimits::kMaximumVerticalConnectionsPerLogic)))
- {
- return false;
- }
- logic->rungs.reserve(static_cast<std::size_t>(rungs.size()));
- for (int index = 0; index < rungs.size(); ++index)
- {
- if (!rungs.at(index).isObject())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- context + ".rungs 的元素必须是对象");
- }
- LadderRung rung;
- if (!parseLadderRung(
- rungs.at(index).toObject(),
- context + ".rungs[" + std::to_string(index) + ']',
- &rung,
- state))
- {
- return false;
- }
- logic->rungs.push_back(std::move(rung));
- }
- for (int index = 0; index < connections.size(); ++index)
- {
- if (!connections.at(index).isObject())
- {
- return state->fail(ProjectStorageError::InvalidField,
- context + ".verticalConnections 的元素必须是对象");
- }
- const QJsonObject item = connections.at(index).toObject();
- VerticalConnection connection;
- int boundary = 0;
- const std::string item_context = context + ".verticalConnections["
- + std::to_string(index) + ']';
- if (!readString(item, "id", item_context, &connection.id, state,
- ProjectLimits::kMaximumIdBytes)
- || !readString(item, "upperRungId", item_context,
- &connection.upperRungId, state,
- ProjectLimits::kMaximumIdBytes)
- || !readString(item, "lowerRungId", item_context,
- &connection.lowerRungId, state,
- ProjectLimits::kMaximumIdBytes)
- || !readInt(item, "columnBoundary", item_context, 0,
- ProjectLimits::kMaximumConditionColumns,
- &boundary, state))
- {
- return false;
- }
- connection.columnBoundary = boundary;
- logic->verticalConnections.push_back(std::move(connection));
- }
- return true;
- }
-
- // 将完整领域工程聚合为工程文件的顶层 JSON 对象
- QJsonObject serializeProject(const Project &project)
- {
- QJsonArray pages;
- for (const HmiPage &page : project.hmiPages)
- {
- pages.append(serializeHmiPage(page));
- }
-
- QJsonArray logics;
- for (const ControlLogic &logic : project.controlLogics)
- {
- logics.append(serializeControlLogic(logic));
- }
-
- QJsonArray alarms;
- for (const AlarmDefinition &definition : project.alarmDefinitions)
- {
- alarms.append(serializeAlarmDefinition(definition));
- }
-
- QJsonArray register_comments;
- for (const RegisterComment &comment : project.registerComments)
- {
- register_comments.append(serializeRegisterComment(comment));
- }
-
- QJsonObject object;
- object.insert(QStringLiteral("formatVersion"), fromUtf8(project.metadata.formatVersion));
- object.insert(QStringLiteral("id"), fromUtf8(project.metadata.id));
- object.insert(QStringLiteral("name"), fromUtf8(project.metadata.name));
- object.insert(QStringLiteral("hmiPages"), pages);
- object.insert(QStringLiteral("initialHmiPageId"),
- fromUtf8(project.initialHmiPageId));
- object.insert(QStringLiteral("alarmDefinitions"), alarms);
- object.insert(QStringLiteral("registerComments"), register_comments);
- object.insert(QStringLiteral("controlLogics"), logics);
- return object;
- }
-
- // 从顶层 JSON 对象解析完整工程,并在解析子对象前检查格式版本
- bool parseProject(
- const QJsonObject &object,
- const ProjectLimitSettings &limits,
- Project *project,
- ParseState *state)
- {
- QJsonArray pages;
- QJsonArray alarms;
- QJsonArray register_comments;
- QJsonArray logics;
- if (!readString(
- object,
- "formatVersion",
- "project",
- &project->metadata.formatVersion,
- state,
- ProjectLimits::kMaximumIdBytes))
- {
- return false;
- }
- // 不尝试猜测其他版本的结构,避免按当前格式错误解释数据
- if (project->metadata.formatVersion != kCurrentFormatVersion)
- {
- return state->fail(
- ProjectStorageError::UnsupportedVersion,
- "不支持的工程格式版本:" + project->metadata.formatVersion);
- }
-
- if (!readString(
- object, "id", "project", &project->metadata.id, state,
- ProjectLimits::kMaximumIdBytes)
- || !readString(object, "name", "project", &project->metadata.name, state)
- || !readArray(
- object, "hmiPages", "project", &pages, state,
- static_cast<int>(limits.maximumHmiPages))
- || !readString(
- object, "initialHmiPageId", "project",
- &project->initialHmiPageId, state,
- ProjectLimits::kMaximumIdBytes)
- || !readArray(
- object, "alarmDefinitions", "project", &alarms, state,
- static_cast<int>(limits.maximumAlarmDefinitions))
- || !readArray(
- object, "registerComments", "project", ®ister_comments, state,
- static_cast<int>(ProjectLimits::kMaximumRegisterComments))
- || !readArray(
- object, "controlLogics", "project", &logics, state,
- static_cast<int>(limits.maximumControlLogics)))
- {
- return false;
- }
-
- // 逐层解析时持续传递字段路径,任何失败都保留最初的具体位置
- project->hmiPages.reserve(static_cast<std::size_t>(pages.size()));
- for (int index = 0; index < pages.size(); ++index)
- {
- if (!pages.at(index).isObject())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "project.hmiPages 的元素必须是对象");
- }
- HmiPage page;
- if (!parseHmiPage(
- pages.at(index).toObject(),
- "project.hmiPages[" + std::to_string(index) + ']',
- limits,
- &page,
- state))
- {
- return false;
- }
- project->hmiPages.push_back(std::move(page));
- }
-
- project->alarmDefinitions.reserve(static_cast<std::size_t>(alarms.size()));
- for (int index = 0; index < alarms.size(); ++index)
- {
- if (!alarms.at(index).isObject())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "project.alarmDefinitions 的元素必须是对象");
- }
- AlarmDefinition definition;
- if (!parseAlarmDefinition(
- alarms.at(index).toObject(),
- "project.alarmDefinitions[" + std::to_string(index) + ']',
- &definition,
- state))
- {
- return false;
- }
- project->alarmDefinitions.push_back(std::move(definition));
- }
-
- project->registerComments.reserve(static_cast<std::size_t>(register_comments.size()));
- for (int index = 0; index < register_comments.size(); ++index)
- {
- if (!register_comments.at(index).isObject())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "project.registerComments 的元素必须是对象");
- }
- RegisterComment comment;
- if (!parseRegisterComment(
- register_comments.at(index).toObject(),
- "project.registerComments[" + std::to_string(index) + ']',
- &comment,
- state))
- {
- return false;
- }
- project->registerComments.push_back(std::move(comment));
- }
-
- project->controlLogics.reserve(static_cast<std::size_t>(logics.size()));
- for (int index = 0; index < logics.size(); ++index)
- {
- if (!logics.at(index).isObject())
- {
- return state->fail(
- ProjectStorageError::InvalidField,
- "project.controlLogics 的元素必须是对象");
- }
- ControlLogic logic;
- if (!parseControlLogic(
- logics.at(index).toObject(),
- "project.controlLogics[" + std::to_string(index) + ']',
- limits,
- &logic,
- state))
- {
- return false;
- }
- project->controlLogics.push_back(std::move(logic));
- }
- return true;
- }
-
- // 将 Qt 文件错误转换为领域层统一的工程保存失败结果
- ProjectSaveResult saveFailure(
- ProjectStorageError error, const QString &message)
- {
- return {false, error, toUtf8(message)};
- }
-
- } // namespace
-
- JsonProjectStorage::JsonProjectStorage(
- const ProjectLimitSettings &project_limits)
- : project_limits_(project_limits)
- {
- }
-
- // 校验工程后将其序列化,并通过 QSaveFile 原子写入目标文件
- ProjectSaveResult JsonProjectStorage::save(
- const Project &project, const std::string &file_path)
- {
- // 写文件前先执行领域校验,防止持久化内部关系不合法的工程
- std::string validation_error;
- if (!project.validate(project_limits_, &validation_error))
- {
- return {false, ProjectStorageError::InvalidProject, validation_error};
- }
- if (project.metadata.formatVersion != kCurrentFormatVersion)
- {
- return {false,
- ProjectStorageError::UnsupportedVersion,
- "不支持的工程格式版本:" + project.metadata.formatVersion};
- }
-
- // QSaveFile 先写临时文件,仅在 commit 成功后替换目标文件
- QSaveFile file(fromUtf8(file_path));
- if (!file.open(QIODevice::WriteOnly))
- {
- return saveFailure(ProjectStorageError::FileOpenFailed, file.errorString());
- }
-
- const QByteArray data = QJsonDocument(serializeProject(project)).toJson(
- QJsonDocument::Indented);
- if (static_cast<std::size_t>(data.size())
- > ProjectLimits::kMaximumProjectFileBytes)
- {
- file.cancelWriting();
- return {false,
- ProjectStorageError::InvalidProject,
- "工程 JSON 文件不能超过 16 MiB"};
- }
- // 短写入也视为失败,并取消临时文件提交
- if (file.write(data) != data.size())
- {
- file.cancelWriting();
- return saveFailure(ProjectStorageError::FileWriteFailed, file.errorString());
- }
- if (!file.commit())
- {
- return saveFailure(ProjectStorageError::FileWriteFailed, file.errorString());
- }
- return {true, ProjectStorageError::None, {}};
- }
-
- // 读取并解析工程文件,全部校验通过后才返回新的领域工程
- ProjectLoadResult JsonProjectStorage::load(const std::string &file_path)
- {
- QFile file(fromUtf8(file_path));
- if (!file.open(QIODevice::ReadOnly))
- {
- return {false,
- {},
- ProjectStorageError::FileOpenFailed,
- toUtf8(file.errorString())};
- }
- if (file.size() < 0
- || static_cast<quint64>(file.size())
- > static_cast<quint64>(ProjectLimits::kMaximumProjectFileBytes))
- {
- return {false,
- {},
- ProjectStorageError::InvalidJson,
- "工程 JSON 文件不能超过 16 MiB"};
- }
-
- // 最多只读到上限加 1 字节,避免文件属性检查后文件变大导致无限制分配
- const QByteArray data = file.read(
- static_cast<qint64>(ProjectLimits::kMaximumProjectFileBytes) + 1);
- if (file.error() != QFileDevice::NoError)
- {
- return {false,
- {},
- ProjectStorageError::FileReadFailed,
- toUtf8(file.errorString())};
- }
- if (static_cast<std::size_t>(data.size())
- > ProjectLimits::kMaximumProjectFileBytes)
- {
- return {false,
- {},
- ProjectStorageError::InvalidJson,
- "工程 JSON 文件不能超过 16 MiB"};
- }
-
- // 顶层必须是 JSON 对象,数组或标量不能表示完整工程
- QJsonParseError parse_error;
- const QJsonDocument document = QJsonDocument::fromJson(data, &parse_error);
- if (parse_error.error != QJsonParseError::NoError || !document.isObject())
- {
- const QString message = parse_error.error == QJsonParseError::NoError
- ? QStringLiteral("工程 JSON 根节点必须是对象")
- : parse_error.errorString();
- return {false,
- {},
- ProjectStorageError::InvalidJson,
- toUtf8(message)};
- }
-
- // 先在局部对象中完成结构解析,失败时不会暴露半成品工程
- Project project;
- ParseState state;
- if (!parseProject(document.object(), project_limits_, &project, &state))
- {
- return {false, {}, state.error, state.message};
- }
- // JSON 字段合法不代表业务关系合法,还需执行领域层整体校验
- std::string validation_error;
- if (!project.validate(project_limits_, &validation_error))
- {
- return {false,
- {},
- ProjectStorageError::InvalidProject,
- validation_error};
- }
- return {true, std::move(project), ProjectStorageError::None, {}};
- }
|