综合平台编程器项目的远程存储
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1990 lines
61 KiB

  1. #include "json_project_storage.h"
  2. #include "domain/hmi_control_registry.h"
  3. #include "domain/project_limits.h"
  4. #include <QFile>
  5. #include <QJsonArray>
  6. #include <QJsonDocument>
  7. #include <QJsonObject>
  8. #include <QJsonParseError>
  9. #include <QSaveFile>
  10. #include <cmath>
  11. #include <limits>
  12. #include <string>
  13. #include <utility>
  14. namespace {
  15. // 当前读写实现支持的工程文件格式版本
  16. constexpr const char *kCurrentFormatVersion = "3.0";
  17. // 保存解析过程中遇到的第一个错误,避免后续失败覆盖根因
  18. struct ParseState
  19. {
  20. ProjectStorageError error = ProjectStorageError::None;
  21. std::string message;
  22. // 记录首次解析错误并返回 false,便于解析函数直接向上传播失败
  23. bool fail(ProjectStorageError new_error, const std::string &new_message)
  24. {
  25. if (error == ProjectStorageError::None)
  26. {
  27. error = new_error;
  28. message = new_message;
  29. }
  30. return false;
  31. }
  32. };
  33. // 将领域层使用的 UTF-8 字符串转换为 Qt 字符串
  34. QString fromUtf8(const std::string &value)
  35. {
  36. return QString::fromUtf8(value.data(), static_cast<int>(value.size()));
  37. }
  38. // 将 Qt 字符串转换为领域层使用的 UTF-8 字符串
  39. std::string toUtf8(const QString &value)
  40. {
  41. const QByteArray bytes = value.toUtf8();
  42. return std::string(bytes.constData(), static_cast<std::size_t>(bytes.size()));
  43. }
  44. // 拼接带上下文的字段路径,用于生成可定位的错误信息
  45. std::string fieldPath(const std::string &context, const char *field)
  46. {
  47. return context + '.' + field;
  48. }
  49. // 读取必填 JSON 字段,字段不存在时记录 MissingField 错误
  50. bool readValue(
  51. const QJsonObject &object,
  52. const char *field,
  53. const std::string &context,
  54. QJsonValue *value,
  55. ParseState *state)
  56. {
  57. const QString key = QString::fromLatin1(field);
  58. if (!object.contains(key))
  59. {
  60. return state->fail(
  61. ProjectStorageError::MissingField,
  62. "缺少必填字段:" + fieldPath(context, field));
  63. }
  64. *value = object.value(key);
  65. return true;
  66. }
  67. // 读取必填字符串字段并转换为 UTF-8 标准字符串
  68. bool readString(
  69. const QJsonObject &object,
  70. const char *field,
  71. const std::string &context,
  72. std::string *value,
  73. ParseState *state,
  74. std::size_t maximum_bytes = ProjectLimits::kMaximumTextBytes)
  75. {
  76. QJsonValue json_value;
  77. if (!readValue(object, field, context, &json_value, state))
  78. {
  79. return false;
  80. }
  81. if (!json_value.isString())
  82. {
  83. return state->fail(
  84. ProjectStorageError::InvalidField,
  85. fieldPath(context, field) + " 必须是字符串");
  86. }
  87. *value = toUtf8(json_value.toString());
  88. if (value->size() > maximum_bytes)
  89. {
  90. return state->fail(
  91. ProjectStorageError::InvalidField,
  92. fieldPath(context, field) + " 超出允许的 UTF-8 字节长度");
  93. }
  94. return true;
  95. }
  96. // 读取必填布尔字段并校验 JSON 类型
  97. bool readBool(
  98. const QJsonObject &object,
  99. const char *field,
  100. const std::string &context,
  101. bool *value,
  102. ParseState *state)
  103. {
  104. QJsonValue json_value;
  105. if (!readValue(object, field, context, &json_value, state))
  106. {
  107. return false;
  108. }
  109. if (!json_value.isBool())
  110. {
  111. return state->fail(
  112. ProjectStorageError::InvalidField,
  113. fieldPath(context, field) + " 必须是布尔值");
  114. }
  115. *value = json_value.toBool();
  116. return true;
  117. }
  118. // 读取指定闭区间内的整数,拒绝小数、非有限值和越界值
  119. bool readInt(
  120. const QJsonObject &object,
  121. const char *field,
  122. const std::string &context,
  123. int minimum,
  124. int maximum,
  125. int *value,
  126. ParseState *state)
  127. {
  128. QJsonValue json_value;
  129. if (!readValue(object, field, context, &json_value, state))
  130. {
  131. return false;
  132. }
  133. if (!json_value.isDouble())
  134. {
  135. return state->fail(
  136. ProjectStorageError::InvalidField,
  137. fieldPath(context, field) + " 必须是整数");
  138. }
  139. // Qt JSON 统一用 double 表示数值,需要额外确认它能无损转换为 int
  140. const double number = json_value.toDouble();
  141. if (!std::isfinite(number) || std::floor(number) != number
  142. || number < minimum || number > maximum)
  143. {
  144. return state->fail(
  145. ProjectStorageError::InvalidField,
  146. fieldPath(context, field) + " 超出支持的整数范围");
  147. }
  148. *value = static_cast<int>(number);
  149. return true;
  150. }
  151. // 读取必填对象字段并校验 JSON 类型
  152. bool readObject(
  153. const QJsonObject &object,
  154. const char *field,
  155. const std::string &context,
  156. QJsonObject *value,
  157. ParseState *state)
  158. {
  159. QJsonValue json_value;
  160. if (!readValue(object, field, context, &json_value, state))
  161. {
  162. return false;
  163. }
  164. if (!json_value.isObject())
  165. {
  166. return state->fail(
  167. ProjectStorageError::InvalidField,
  168. fieldPath(context, field) + " 必须是对象");
  169. }
  170. *value = json_value.toObject();
  171. return true;
  172. }
  173. // 读取必填数组字段并校验 JSON 类型
  174. bool readArray(
  175. const QJsonObject &object,
  176. const char *field,
  177. const std::string &context,
  178. QJsonArray *value,
  179. ParseState *state,
  180. int maximum_count = std::numeric_limits<int>::max())
  181. {
  182. QJsonValue json_value;
  183. if (!readValue(object, field, context, &json_value, state))
  184. {
  185. return false;
  186. }
  187. if (!json_value.isArray())
  188. {
  189. return state->fail(
  190. ProjectStorageError::InvalidField,
  191. fieldPath(context, field) + " 必须是数组");
  192. }
  193. *value = json_value.toArray();
  194. if (value->size() > maximum_count)
  195. {
  196. return state->fail(
  197. ProjectStorageError::InvalidField,
  198. fieldPath(context, field) + " 的元素数量为 "
  199. + std::to_string(value->size()) + ",当前上限为 "
  200. + std::to_string(maximum_count));
  201. }
  202. return true;
  203. }
  204. // 将寄存器地址序列化为包含区域和原始索引的 JSON 对象
  205. QJsonObject serializeAddress(const RegisterAddress &address)
  206. {
  207. QJsonObject object;
  208. object.insert(QStringLiteral("area"),
  209. address.area() == RegisterArea::M ? QStringLiteral("M")
  210. : QStringLiteral("D"));
  211. object.insert(QStringLiteral("index"), address.index());
  212. return object;
  213. }
  214. // 解析寄存器地址并校验区域名称及项目允许的索引范围
  215. bool parseAddress(
  216. const QJsonObject &object,
  217. const std::string &context,
  218. RegisterAddress *address,
  219. ParseState *state)
  220. {
  221. std::string area_text;
  222. int index = 0;
  223. if (!readString(object, "area", context, &area_text, state)
  224. || !readInt(
  225. object,
  226. "index",
  227. context,
  228. RegisterAddress::kMinimumIndex,
  229. RegisterAddress::kMaximumIndex,
  230. &index,
  231. state))
  232. {
  233. return false;
  234. }
  235. RegisterArea area = RegisterArea::M;
  236. if (area_text == "M")
  237. {
  238. area = RegisterArea::M;
  239. }
  240. else if (area_text == "D")
  241. {
  242. area = RegisterArea::D;
  243. }
  244. else
  245. {
  246. return state->fail(
  247. ProjectStorageError::InvalidField,
  248. context + ".area 必须是 M 或 D");
  249. }
  250. *address = RegisterAddress{area, index};
  251. return true;
  252. }
  253. QString wordOperandKindName(WordOperandKind kind)
  254. {
  255. return kind == WordOperandKind::Constant
  256. ? QStringLiteral("constant") : QStringLiteral("register");
  257. }
  258. QJsonObject serializeWordOperand(const WordOperand &operand)
  259. {
  260. QJsonObject object;
  261. object.insert(QStringLiteral("kind"), wordOperandKindName(operand.kind));
  262. if (operand.kind == WordOperandKind::Constant)
  263. {
  264. object.insert(QStringLiteral("value"), operand.constant);
  265. }
  266. else
  267. {
  268. object.insert(QStringLiteral("address"), serializeAddress(operand.address));
  269. }
  270. return object;
  271. }
  272. bool parseWordOperand(
  273. const QJsonObject &object,
  274. const std::string &context,
  275. WordOperand *operand,
  276. ParseState *state)
  277. {
  278. std::string kind;
  279. if (!readString(object, "kind", context, &kind, state))
  280. {
  281. return false;
  282. }
  283. if (kind == "constant")
  284. {
  285. int value = 0;
  286. if (!readInt(
  287. object,
  288. "value",
  289. context,
  290. std::numeric_limits<std::int16_t>::min(),
  291. std::numeric_limits<std::int16_t>::max(),
  292. &value,
  293. state))
  294. {
  295. return false;
  296. }
  297. *operand = WordOperand{
  298. WordOperandKind::Constant,
  299. RegisterAddress{RegisterArea::D, 0},
  300. static_cast<std::int16_t>(value)};
  301. return true;
  302. }
  303. if (kind != "register")
  304. {
  305. return state->fail(
  306. ProjectStorageError::InvalidField,
  307. context + ".kind 必须是 constant 或 register");
  308. }
  309. QJsonObject address_object;
  310. RegisterAddress address{RegisterArea::D, 0};
  311. if (!readObject(object, "address", context, &address_object, state)
  312. || !parseAddress(address_object, context + ".address", &address, state))
  313. {
  314. return false;
  315. }
  316. if (address.area() != RegisterArea::D)
  317. {
  318. return state->fail(
  319. ProjectStorageError::InvalidField,
  320. context + ".address 必须使用 D 区地址");
  321. }
  322. *operand = WordOperand{
  323. WordOperandKind::Register,
  324. address,
  325. 0};
  326. return true;
  327. }
  328. // 将 HMI 控件类型枚举转换为工程文件中的稳定字符串
  329. QString hmiControlTypeName(HmiControlType type)
  330. {
  331. const HmiControlDescriptor *descriptor = findHmiControlDescriptor(type);
  332. return descriptor == nullptr
  333. ? QString{}
  334. : QString::fromLatin1(descriptor->storageName);
  335. }
  336. // 将工程文件中的控件类型字符串转换为 HMI 控件类型枚举
  337. bool parseHmiControlType(
  338. const std::string &value, HmiControlType *type, ParseState *state)
  339. {
  340. const HmiControlDescriptor *descriptor = findHmiControlDescriptor(value);
  341. if (descriptor == nullptr)
  342. {
  343. return state->fail(
  344. ProjectStorageError::InvalidField,
  345. "不支持的 HMI 控件类型:" + value);
  346. }
  347. *type = descriptor->type;
  348. return true;
  349. }
  350. QString hmiButtonOperationName(HmiButtonOperation operation)
  351. {
  352. switch (operation)
  353. {
  354. case HmiButtonOperation::SetOn:
  355. {
  356. return QStringLiteral("setOn");
  357. }
  358. case HmiButtonOperation::SetOff:
  359. {
  360. return QStringLiteral("setOff");
  361. }
  362. case HmiButtonOperation::Toggle:
  363. {
  364. return QStringLiteral("toggle");
  365. }
  366. case HmiButtonOperation::MomentaryOn:
  367. default:
  368. {
  369. return QStringLiteral("momentaryOn");
  370. }
  371. }
  372. }
  373. bool parseHmiButtonOperation(
  374. const std::string &value, HmiButtonOperation *operation, ParseState *state)
  375. {
  376. if (value == "setOn")
  377. {
  378. *operation = HmiButtonOperation::SetOn;
  379. }
  380. else if (value == "setOff")
  381. {
  382. *operation = HmiButtonOperation::SetOff;
  383. }
  384. else if (value == "toggle")
  385. {
  386. *operation = HmiButtonOperation::Toggle;
  387. }
  388. else if (value == "momentaryOn")
  389. {
  390. *operation = HmiButtonOperation::MomentaryOn;
  391. }
  392. else
  393. {
  394. return state->fail(
  395. ProjectStorageError::InvalidField,
  396. "不支持的 HMI 按钮操作:" + value);
  397. }
  398. return true;
  399. }
  400. QString alarmConditionName(AlarmCondition condition)
  401. {
  402. switch (condition)
  403. {
  404. case AlarmCondition::MOn:
  405. {
  406. return QStringLiteral("mOn");
  407. }
  408. case AlarmCondition::MOff:
  409. {
  410. return QStringLiteral("mOff");
  411. }
  412. case AlarmCondition::DHigh:
  413. {
  414. return QStringLiteral("dHigh");
  415. }
  416. case AlarmCondition::DLow:
  417. {
  418. return QStringLiteral("dLow");
  419. }
  420. default:
  421. {
  422. return {};
  423. }
  424. }
  425. }
  426. bool parseAlarmCondition(
  427. const std::string &value, AlarmCondition *condition, ParseState *state)
  428. {
  429. if (value == "mOn")
  430. {
  431. *condition = AlarmCondition::MOn;
  432. }
  433. else if (value == "mOff")
  434. {
  435. *condition = AlarmCondition::MOff;
  436. }
  437. else if (value == "dHigh")
  438. {
  439. *condition = AlarmCondition::DHigh;
  440. }
  441. else if (value == "dLow")
  442. {
  443. *condition = AlarmCondition::DLow;
  444. }
  445. else
  446. {
  447. return state->fail(
  448. ProjectStorageError::InvalidField,
  449. "不支持的报警触发条件:" + value);
  450. }
  451. return true;
  452. }
  453. QJsonObject serializeAlarmDefinition(const AlarmDefinition &definition)
  454. {
  455. QJsonObject object;
  456. object.insert(QStringLiteral("id"), fromUtf8(definition.id));
  457. object.insert(QStringLiteral("address"), serializeAddress(definition.address));
  458. object.insert(
  459. QStringLiteral("condition"), alarmConditionName(definition.condition));
  460. object.insert(QStringLiteral("threshold"), definition.threshold);
  461. object.insert(QStringLiteral("message"), fromUtf8(definition.message));
  462. return object;
  463. }
  464. bool parseAlarmDefinition(
  465. const QJsonObject &object,
  466. const std::string &context,
  467. AlarmDefinition *definition,
  468. ParseState *state)
  469. {
  470. QJsonObject address;
  471. std::string condition;
  472. int threshold = 0;
  473. if (!readString(
  474. object, "id", context, &definition->id, state,
  475. ProjectLimits::kMaximumIdBytes)
  476. || !readObject(object, "address", context, &address, state)
  477. || !readString(object, "condition", context, &condition, state)
  478. || !readInt(
  479. object,
  480. "threshold",
  481. context,
  482. std::numeric_limits<std::int16_t>::min(),
  483. std::numeric_limits<std::int16_t>::max(),
  484. &threshold,
  485. state)
  486. || !readString(object, "message", context, &definition->message, state))
  487. {
  488. return false;
  489. }
  490. if (!parseAddress(address, context + ".address", &definition->address, state)
  491. || !parseAlarmCondition(condition, &definition->condition, state))
  492. {
  493. return false;
  494. }
  495. definition->threshold = static_cast<std::int16_t>(threshold);
  496. return true;
  497. }
  498. QJsonObject serializeRegisterComment(const RegisterComment &comment)
  499. {
  500. QJsonObject object;
  501. object.insert(QStringLiteral("address"), serializeAddress(comment.address));
  502. object.insert(QStringLiteral("text"), fromUtf8(comment.text));
  503. return object;
  504. }
  505. bool parseRegisterComment(
  506. const QJsonObject &object,
  507. const std::string &context,
  508. RegisterComment *comment,
  509. ParseState *state)
  510. {
  511. QJsonObject address;
  512. if (!readObject(object, "address", context, &address, state)
  513. || !readString(object, "text", context, &comment->text, state))
  514. {
  515. return false;
  516. }
  517. return parseAddress(address, context + ".address", &comment->address, state);
  518. }
  519. // 将 HMI 控件矩形区域序列化为 JSON 对象
  520. QJsonObject serializeBounds(const HmiRect &bounds)
  521. {
  522. QJsonObject object;
  523. object.insert(QStringLiteral("x"), bounds.x);
  524. object.insert(QStringLiteral("y"), bounds.y);
  525. object.insert(QStringLiteral("width"), bounds.width);
  526. object.insert(QStringLiteral("height"), bounds.height);
  527. return object;
  528. }
  529. // 解析控件矩形区域,允许任意坐标但要求宽高为正数
  530. bool parseBounds(
  531. const QJsonObject &object,
  532. const std::string &context,
  533. HmiRect *bounds,
  534. ParseState *state)
  535. {
  536. return readInt(
  537. object,
  538. "x",
  539. context,
  540. 0,
  541. ProjectLimits::kMaximumHmiPageWidth,
  542. &bounds->x,
  543. state)
  544. && readInt(
  545. object,
  546. "y",
  547. context,
  548. 0,
  549. ProjectLimits::kMaximumHmiPageHeight,
  550. &bounds->y,
  551. state)
  552. && readInt(
  553. object,
  554. "width",
  555. context,
  556. 1,
  557. ProjectLimits::kMaximumHmiControlWidth,
  558. &bounds->width,
  559. state)
  560. && readInt(
  561. object,
  562. "height",
  563. context,
  564. 1,
  565. ProjectLimits::kMaximumHmiControlHeight,
  566. &bounds->height,
  567. state);
  568. }
  569. // 将 HMI 控件的字符串扩展属性序列化为 JSON 对象
  570. QJsonObject serializeProperties(const std::map<std::string, std::string> &properties)
  571. {
  572. QJsonObject object;
  573. for (const auto &property : properties)
  574. {
  575. object.insert(fromUtf8(property.first), fromUtf8(property.second));
  576. }
  577. return object;
  578. }
  579. // 解析 HMI 控件扩展属性,并确保所有属性值都是字符串
  580. bool parseProperties(
  581. const QJsonObject &object,
  582. std::map<std::string, std::string> *properties,
  583. ParseState *state)
  584. {
  585. if (object.size() > static_cast<int>(ProjectLimits::kMaximumHmiProperties))
  586. {
  587. return state->fail(
  588. ProjectStorageError::InvalidField,
  589. "单个 HMI 控件最多保存 64 对扩展属性");
  590. }
  591. for (auto current = object.constBegin(); current != object.constEnd(); ++current)
  592. {
  593. if (!current.value().isString())
  594. {
  595. return state->fail(
  596. ProjectStorageError::InvalidField,
  597. "HMI 控件属性值必须是字符串");
  598. }
  599. const std::string key = toUtf8(current.key());
  600. const std::string value = toUtf8(current.value().toString());
  601. if (key.empty() || key.size() > ProjectLimits::kMaximumPropertyKeyBytes
  602. || value.size() > ProjectLimits::kMaximumPropertyValueBytes)
  603. {
  604. return state->fail(
  605. ProjectStorageError::InvalidField,
  606. "HMI 控件属性名称最多 128 字节,属性值最多 1024 字节");
  607. }
  608. properties->emplace(key, value);
  609. }
  610. return true;
  611. }
  612. QJsonValue serializeOptionalNumber(const std::optional<double> &value)
  613. {
  614. return value.has_value() ? QJsonValue(*value) : QJsonValue(QJsonValue::Null);
  615. }
  616. QJsonObject serializeStatusTextConfig(const HmiStatusTextConfig &config)
  617. {
  618. QJsonObject object;
  619. if (const auto *bit = std::get_if<HmiStatusBitTextConfig>(&config))
  620. {
  621. object.insert(QStringLiteral("source"), QStringLiteral("m"));
  622. object.insert(QStringLiteral("offText"), fromUtf8(bit->offText));
  623. object.insert(QStringLiteral("onText"), fromUtf8(bit->onText));
  624. return object;
  625. }
  626. object.insert(QStringLiteral("source"), QStringLiteral("d"));
  627. QJsonArray ranges;
  628. for (const HmiStatusValueRange &range :
  629. std::get<HmiStatusWordTextConfig>(config).ranges)
  630. {
  631. QJsonObject range_object;
  632. range_object.insert(
  633. QStringLiteral("lower"), serializeOptionalNumber(range.lowerBound));
  634. range_object.insert(
  635. QStringLiteral("upper"), serializeOptionalNumber(range.upperBound));
  636. range_object.insert(QStringLiteral("text"), fromUtf8(range.text));
  637. ranges.append(range_object);
  638. }
  639. object.insert(QStringLiteral("ranges"), ranges);
  640. return object;
  641. }
  642. bool readNullableFiniteNumber(
  643. const QJsonObject &object,
  644. const char *field,
  645. const std::string &context,
  646. std::optional<double> *value,
  647. ParseState *state)
  648. {
  649. QJsonValue json_value;
  650. if (!readValue(object, field, context, &json_value, state))
  651. {
  652. return false;
  653. }
  654. if (json_value.isNull())
  655. {
  656. value->reset();
  657. return true;
  658. }
  659. if (!json_value.isDouble() || !std::isfinite(json_value.toDouble()))
  660. {
  661. return state->fail(
  662. ProjectStorageError::InvalidField,
  663. fieldPath(context, field) + " 必须是有限数值或 null");
  664. }
  665. *value = json_value.toDouble();
  666. return true;
  667. }
  668. bool parseStatusTextConfig(
  669. const QJsonObject &object,
  670. const std::string &context,
  671. HmiStatusTextConfig *config,
  672. ParseState *state)
  673. {
  674. std::string source;
  675. if (!readString(object, "source", context, &source, state))
  676. {
  677. return false;
  678. }
  679. if (source == "m")
  680. {
  681. HmiStatusBitTextConfig bit;
  682. if (!readString(object, "offText", context, &bit.offText, state)
  683. || !readString(object, "onText", context, &bit.onText, state))
  684. {
  685. return false;
  686. }
  687. *config = std::move(bit);
  688. return true;
  689. }
  690. if (source != "d")
  691. {
  692. return state->fail(
  693. ProjectStorageError::InvalidField,
  694. context + ".source 必须是 m 或 d");
  695. }
  696. QJsonArray range_array;
  697. if (!readArray(object, "ranges", context, &range_array, state)
  698. || range_array.isEmpty()
  699. || range_array.size()
  700. > static_cast<int>(ProjectLimits::kMaximumStatusTextRanges))
  701. {
  702. return state->fail(
  703. ProjectStorageError::InvalidField,
  704. context + ".ranges 必须包含 1~16 个区间");
  705. }
  706. HmiStatusWordTextConfig word;
  707. word.ranges.reserve(static_cast<std::size_t>(range_array.size()));
  708. for (int index = 0; index < range_array.size(); ++index)
  709. {
  710. if (!range_array.at(index).isObject())
  711. {
  712. return state->fail(
  713. ProjectStorageError::InvalidField,
  714. context + ".ranges 的元素必须是对象");
  715. }
  716. const QJsonObject range_object = range_array.at(index).toObject();
  717. const std::string range_context = context + ".ranges["
  718. + std::to_string(index) + ']';
  719. HmiStatusValueRange range;
  720. if (!readNullableFiniteNumber(
  721. range_object, "lower", range_context,
  722. &range.lowerBound, state)
  723. || !readNullableFiniteNumber(
  724. range_object, "upper", range_context,
  725. &range.upperBound, state)
  726. || !readString(
  727. range_object, "text", range_context, &range.text, state))
  728. {
  729. return false;
  730. }
  731. word.ranges.push_back(std::move(range));
  732. }
  733. *config = std::move(word);
  734. return true;
  735. }
  736. // 将单个 HMI 控件及其可选寄存器绑定序列化为 JSON 对象
  737. QJsonObject serializeHmiControl(const HmiControl &control)
  738. {
  739. QJsonObject object;
  740. object.insert(QStringLiteral("id"), fromUtf8(control.id));
  741. object.insert(QStringLiteral("type"), hmiControlTypeName(control.type));
  742. object.insert(QStringLiteral("bounds"), serializeBounds(control.bounds));
  743. object.insert(QStringLiteral("text"), fromUtf8(control.text));
  744. // 使用 JSON null 明确表示控件没有寄存器绑定
  745. if (control.binding.has_value())
  746. {
  747. object.insert(QStringLiteral("binding"), serializeAddress(*control.binding));
  748. }
  749. else
  750. {
  751. object.insert(QStringLiteral("binding"), QJsonValue::Null);
  752. }
  753. object.insert(QStringLiteral("properties"), serializeProperties(control.properties));
  754. const bool status_word = control.type == HmiControlType::StatusText
  755. && control.statusText.has_value()
  756. && std::holds_alternative<HmiStatusWordTextConfig>(*control.statusText);
  757. if (control.type == HmiControlType::NumericDisplay
  758. || control.type == HmiControlType::NumericInput
  759. || status_word)
  760. {
  761. object.insert(
  762. QStringLiteral("dataType"),
  763. QString::fromLatin1(registerDataTypeName(control.dataType)));
  764. }
  765. if (control.type == HmiControlType::Button)
  766. {
  767. object.insert(
  768. QStringLiteral("buttonOperation"),
  769. hmiButtonOperationName(control.buttonOperation));
  770. }
  771. if (control.type == HmiControlType::PageJump)
  772. {
  773. object.insert(
  774. QStringLiteral("targetPageId"),
  775. fromUtf8(control.pageJump.has_value()
  776. ? control.pageJump->targetPageId
  777. : std::string{}));
  778. }
  779. if (control.type == HmiControlType::StatusText)
  780. {
  781. object.insert(
  782. QStringLiteral("statusText"),
  783. control.statusText.has_value()
  784. ? serializeStatusTextConfig(*control.statusText)
  785. : QJsonObject{});
  786. }
  787. return object;
  788. }
  789. // 解析单个 HMI 控件,并逐层校验类型、区域、绑定和扩展属性
  790. bool parseHmiControl(
  791. const QJsonObject &object,
  792. const std::string &context,
  793. HmiControl *control,
  794. ParseState *state)
  795. {
  796. std::string type_text;
  797. QJsonObject bounds;
  798. QJsonObject properties;
  799. QJsonValue binding;
  800. if (!readString(
  801. object, "id", context, &control->id, state,
  802. ProjectLimits::kMaximumIdBytes)
  803. || !readString(object, "type", context, &type_text, state)
  804. || !readObject(object, "bounds", context, &bounds, state)
  805. || !readString(object, "text", context, &control->text, state)
  806. || !readValue(object, "binding", context, &binding, state)
  807. || !readObject(object, "properties", context, &properties, state))
  808. {
  809. return false;
  810. }
  811. if (!parseHmiControlType(type_text, &control->type, state)
  812. || !parseBounds(bounds, context + ".bounds", &control->bounds, state)
  813. || !parseProperties(properties, &control->properties, state))
  814. {
  815. return false;
  816. }
  817. if (control->type == HmiControlType::StatusText)
  818. {
  819. QJsonObject status_object;
  820. HmiStatusTextConfig status_config = HmiStatusBitTextConfig{};
  821. if (!readObject(object, "statusText", context, &status_object, state)
  822. || !parseStatusTextConfig(
  823. status_object, context + ".statusText", &status_config, state))
  824. {
  825. return false;
  826. }
  827. control->statusText = std::move(status_config);
  828. }
  829. const bool status_word = control->type == HmiControlType::StatusText
  830. && control->statusText.has_value()
  831. && std::holds_alternative<HmiStatusWordTextConfig>(*control->statusText);
  832. if (control->type == HmiControlType::NumericDisplay
  833. || control->type == HmiControlType::NumericInput
  834. || status_word)
  835. {
  836. std::string data_type_text;
  837. if (!readString(object, "dataType", context, &data_type_text, state)
  838. || !parseRegisterDataType(data_type_text, &control->dataType))
  839. {
  840. return state->fail(
  841. ProjectStorageError::InvalidField,
  842. context + ".dataType 必须是 int16、int32、float32 或 float64");
  843. }
  844. }
  845. if (control->type == HmiControlType::Button)
  846. {
  847. std::string operation_text;
  848. if (!readString(
  849. object,
  850. "buttonOperation",
  851. context,
  852. &operation_text,
  853. state)
  854. || !parseHmiButtonOperation(
  855. operation_text, &control->buttonOperation, state))
  856. {
  857. return false;
  858. }
  859. }
  860. if (control->type == HmiControlType::PageJump)
  861. {
  862. std::string target_page_id;
  863. if (!readString(
  864. object, "targetPageId", context, &target_page_id, state,
  865. ProjectLimits::kMaximumIdBytes))
  866. {
  867. return false;
  868. }
  869. control->pageJump = HmiPageJumpConfig{std::move(target_page_id)};
  870. }
  871. // binding 允许为 null,其余非空值必须是合法的寄存器地址对象
  872. if (binding.isNull())
  873. {
  874. control->binding.reset();
  875. return true;
  876. }
  877. if (!binding.isObject())
  878. {
  879. return state->fail(
  880. ProjectStorageError::InvalidField,
  881. context + ".binding 必须是对象或 null");
  882. }
  883. RegisterAddress address{RegisterArea::M, 0};
  884. if (!parseAddress(binding.toObject(), context + ".binding", &address, state))
  885. {
  886. return false;
  887. }
  888. control->binding = address;
  889. return true;
  890. }
  891. // 将 HMI 页面及其全部控件序列化为 JSON 对象
  892. QJsonObject serializeHmiPage(const HmiPage &page)
  893. {
  894. QJsonArray controls;
  895. for (const HmiControl &control : page.controls)
  896. {
  897. controls.append(serializeHmiControl(control));
  898. }
  899. QJsonObject object;
  900. object.insert(QStringLiteral("id"), fromUtf8(page.id));
  901. object.insert(QStringLiteral("name"), fromUtf8(page.name));
  902. object.insert(QStringLiteral("width"), page.width);
  903. object.insert(QStringLiteral("height"), page.height);
  904. object.insert(QStringLiteral("controls"), controls);
  905. return object;
  906. }
  907. // 解析 HMI 页面基础信息,并按数组顺序构造页面控件
  908. bool parseHmiPage(
  909. const QJsonObject &object,
  910. const std::string &context,
  911. const ProjectLimitSettings &limits,
  912. HmiPage *page,
  913. ParseState *state)
  914. {
  915. QJsonArray controls;
  916. if (!readString(
  917. object, "id", context, &page->id, state,
  918. ProjectLimits::kMaximumIdBytes)
  919. || !readString(object, "name", context, &page->name, state)
  920. || !readInt(object, "width", context,
  921. ProjectLimits::kMinimumHmiPageWidth,
  922. ProjectLimits::kMaximumHmiPageWidth,
  923. &page->width, state)
  924. || !readInt(object, "height", context,
  925. ProjectLimits::kMinimumHmiPageHeight,
  926. ProjectLimits::kMaximumHmiPageHeight,
  927. &page->height, state)
  928. || !readArray(
  929. object, "controls", context, &controls, state,
  930. static_cast<int>(limits.maximumHmiControlsPerPage)))
  931. {
  932. return false;
  933. }
  934. // 预留准确容量,避免逐项加入控件时重复扩容
  935. page->controls.reserve(static_cast<std::size_t>(controls.size()));
  936. for (int index = 0; index < controls.size(); ++index)
  937. {
  938. if (!controls.at(index).isObject())
  939. {
  940. return state->fail(
  941. ProjectStorageError::InvalidField,
  942. context + ".controls 的元素必须是对象");
  943. }
  944. HmiControl control;
  945. if (!parseHmiControl(
  946. controls.at(index).toObject(),
  947. context + ".controls[" + std::to_string(index) + ']',
  948. &control,
  949. state))
  950. {
  951. return false;
  952. }
  953. page->controls.push_back(std::move(control));
  954. }
  955. return true;
  956. }
  957. // 将触点模式枚举转换为工程文件中的稳定字符串
  958. QString contactModeName(ContactMode mode)
  959. {
  960. return mode == ContactMode::NormallyOpen
  961. ? QStringLiteral("normallyOpen")
  962. : QStringLiteral("normallyClosed");
  963. }
  964. // 将工程文件中的触点模式字符串转换为枚举
  965. bool parseContactMode(
  966. const std::string &value, ContactMode *mode, ParseState *state)
  967. {
  968. if (value == "normallyOpen")
  969. {
  970. *mode = ContactMode::NormallyOpen;
  971. }
  972. else if (value == "normallyClosed")
  973. {
  974. *mode = ContactMode::NormallyClosed;
  975. }
  976. else
  977. {
  978. return state->fail(
  979. ProjectStorageError::InvalidField,
  980. "不支持的触点模式:" + value);
  981. }
  982. return true;
  983. }
  984. QString edgeModeName(EdgeMode mode)
  985. {
  986. return mode == EdgeMode::Rising
  987. ? QStringLiteral("rising")
  988. : QStringLiteral("falling");
  989. }
  990. bool parseEdgeMode(
  991. const std::string &value, EdgeMode *mode, ParseState *state)
  992. {
  993. if (value == "rising")
  994. {
  995. *mode = EdgeMode::Rising;
  996. }
  997. else if (value == "falling")
  998. {
  999. *mode = EdgeMode::Falling;
  1000. }
  1001. else
  1002. {
  1003. return state->fail(
  1004. ProjectStorageError::InvalidField,
  1005. "不支持的边沿模式:" + value);
  1006. }
  1007. return true;
  1008. }
  1009. // 将线圈模式枚举转换为工程文件中的稳定字符串
  1010. QString coilModeName(CoilMode mode)
  1011. {
  1012. switch (mode)
  1013. {
  1014. case CoilMode::Normal:
  1015. {
  1016. return QStringLiteral("normal");
  1017. }
  1018. case CoilMode::Set:
  1019. {
  1020. return QStringLiteral("set");
  1021. }
  1022. case CoilMode::Reset:
  1023. {
  1024. return QStringLiteral("reset");
  1025. }
  1026. default:
  1027. {
  1028. return {};
  1029. }
  1030. }
  1031. }
  1032. // 将工程文件中的线圈模式字符串转换为枚举
  1033. bool parseCoilMode(const std::string &value, CoilMode *mode, ParseState *state)
  1034. {
  1035. if (value == "normal")
  1036. {
  1037. *mode = CoilMode::Normal;
  1038. }
  1039. else if (value == "set")
  1040. {
  1041. *mode = CoilMode::Set;
  1042. }
  1043. else if (value == "reset")
  1044. {
  1045. *mode = CoilMode::Reset;
  1046. }
  1047. else
  1048. {
  1049. return state->fail(
  1050. ProjectStorageError::InvalidField,
  1051. "不支持的线圈模式:" + value);
  1052. }
  1053. return true;
  1054. }
  1055. // 将比较运算符枚举转换为工程文件中的稳定字符串
  1056. QString comparisonName(ComparisonOperator comparison)
  1057. {
  1058. switch (comparison)
  1059. {
  1060. case ComparisonOperator::Equal:
  1061. {
  1062. return QStringLiteral("equal");
  1063. }
  1064. case ComparisonOperator::NotEqual:
  1065. {
  1066. return QStringLiteral("notEqual");
  1067. }
  1068. case ComparisonOperator::LessThan:
  1069. {
  1070. return QStringLiteral("lessThan");
  1071. }
  1072. case ComparisonOperator::LessThanOrEqual:
  1073. {
  1074. return QStringLiteral("lessThanOrEqual");
  1075. }
  1076. case ComparisonOperator::GreaterThan:
  1077. {
  1078. return QStringLiteral("greaterThan");
  1079. }
  1080. case ComparisonOperator::GreaterThanOrEqual:
  1081. {
  1082. return QStringLiteral("greaterThanOrEqual");
  1083. }
  1084. default:
  1085. {
  1086. return {};
  1087. }
  1088. }
  1089. }
  1090. // 将工程文件中的比较运算符字符串转换为枚举
  1091. bool parseComparison(
  1092. const std::string &value,
  1093. ComparisonOperator *comparison,
  1094. ParseState *state)
  1095. {
  1096. if (value == "equal")
  1097. {
  1098. *comparison = ComparisonOperator::Equal;
  1099. }
  1100. else if (value == "notEqual")
  1101. {
  1102. *comparison = ComparisonOperator::NotEqual;
  1103. }
  1104. else if (value == "lessThan")
  1105. {
  1106. *comparison = ComparisonOperator::LessThan;
  1107. }
  1108. else if (value == "lessThanOrEqual")
  1109. {
  1110. *comparison = ComparisonOperator::LessThanOrEqual;
  1111. }
  1112. else if (value == "greaterThan")
  1113. {
  1114. *comparison = ComparisonOperator::GreaterThan;
  1115. }
  1116. else if (value == "greaterThanOrEqual")
  1117. {
  1118. *comparison = ComparisonOperator::GreaterThanOrEqual;
  1119. }
  1120. else
  1121. {
  1122. return state->fail(
  1123. ProjectStorageError::InvalidField,
  1124. "不支持的比较运算符:" + value);
  1125. }
  1126. return true;
  1127. }
  1128. // 将触点节点配置序列化,并写入用于反序列化分派的类型标记
  1129. QJsonObject serializeNodeConfig(const ContactNodeConfig &config)
  1130. {
  1131. QJsonObject object;
  1132. object.insert(QStringLiteral("type"), QStringLiteral("contact"));
  1133. object.insert(QStringLiteral("address"), serializeAddress(config.address));
  1134. object.insert(QStringLiteral("mode"), contactModeName(config.mode));
  1135. return object;
  1136. }
  1137. QJsonObject serializeNodeConfig(const EdgeContactNodeConfig &config)
  1138. {
  1139. QJsonObject object;
  1140. object.insert(QStringLiteral("type"), QStringLiteral("edgeContact"));
  1141. object.insert(QStringLiteral("address"), serializeAddress(config.address));
  1142. object.insert(QStringLiteral("mode"), edgeModeName(config.mode));
  1143. return object;
  1144. }
  1145. // 将线圈节点配置序列化,并写入用于反序列化分派的类型标记
  1146. QJsonObject serializeNodeConfig(const CoilNodeConfig &config)
  1147. {
  1148. QJsonObject object;
  1149. object.insert(QStringLiteral("type"), QStringLiteral("coil"));
  1150. object.insert(QStringLiteral("address"), serializeAddress(config.address));
  1151. object.insert(QStringLiteral("mode"), coilModeName(config.mode));
  1152. return object;
  1153. }
  1154. // 将数值比较节点配置序列化,并保留有符号 16 位比较常量
  1155. QJsonObject serializeNodeConfig(const CompareNodeConfig &config)
  1156. {
  1157. QJsonObject object;
  1158. object.insert(QStringLiteral("type"), QStringLiteral("compare"));
  1159. object.insert(QStringLiteral("address"), serializeAddress(config.address));
  1160. object.insert(QStringLiteral("comparison"), comparisonName(config.comparison));
  1161. object.insert(QStringLiteral("value"), config.value);
  1162. return object;
  1163. }
  1164. QJsonObject serializeNodeConfig(const MoveNodeConfig &config)
  1165. {
  1166. QJsonObject object;
  1167. object.insert(QStringLiteral("type"), QStringLiteral("move"));
  1168. object.insert(QStringLiteral("source"), serializeWordOperand(config.source));
  1169. object.insert(QStringLiteral("destination"), serializeAddress(config.destination));
  1170. return object;
  1171. }
  1172. QString arithmeticOperationName(ArithmeticOperation operation)
  1173. {
  1174. return operation == ArithmeticOperation::Add
  1175. ? QStringLiteral("add") : QStringLiteral("subtract");
  1176. }
  1177. QJsonObject serializeNodeConfig(const ArithmeticNodeConfig &config)
  1178. {
  1179. QJsonObject object;
  1180. object.insert(QStringLiteral("type"), QStringLiteral("arithmetic"));
  1181. object.insert(QStringLiteral("operation"), arithmeticOperationName(config.operation));
  1182. object.insert(QStringLiteral("left"), serializeWordOperand(config.left));
  1183. object.insert(QStringLiteral("right"), serializeWordOperand(config.right));
  1184. object.insert(QStringLiteral("destination"), serializeAddress(config.destination));
  1185. return object;
  1186. }
  1187. // 将逻辑节点序列化,并根据 variant 中的实际配置类型选择对应重载
  1188. QJsonObject serializeLogicNode(const LogicNode &node)
  1189. {
  1190. QJsonObject object;
  1191. object.insert(QStringLiteral("id"), fromUtf8(node.id));
  1192. object.insert(QStringLiteral("configured"), node.configured);
  1193. // std::visit 将不同节点配置统一转换为 config JSON 对象
  1194. object.insert(
  1195. QStringLiteral("config"),
  1196. std::visit(
  1197. [](const auto &config)
  1198. {
  1199. return serializeNodeConfig(config);
  1200. },
  1201. node.config));
  1202. return object;
  1203. }
  1204. // 根据 type 字段解析具体节点配置,并写入 LogicNodeConfig 变体
  1205. bool parseNodeConfig(
  1206. const QJsonObject &object,
  1207. const std::string &context,
  1208. LogicNodeConfig *config,
  1209. ParseState *state)
  1210. {
  1211. std::string type;
  1212. if (!readString(object, "type", context, &type, state))
  1213. {
  1214. return false;
  1215. }
  1216. if (type == "move")
  1217. {
  1218. QJsonObject source_object;
  1219. QJsonObject destination_object;
  1220. WordOperand source;
  1221. RegisterAddress destination{RegisterArea::D, 0};
  1222. if (!readObject(object, "source", context, &source_object, state)
  1223. || !parseWordOperand(source_object, context + ".source", &source, state)
  1224. || !readObject(object, "destination", context, &destination_object, state)
  1225. || !parseAddress(
  1226. destination_object,
  1227. context + ".destination",
  1228. &destination,
  1229. state))
  1230. {
  1231. return false;
  1232. }
  1233. *config = MoveNodeConfig{source, destination};
  1234. return true;
  1235. }
  1236. if (type == "arithmetic")
  1237. {
  1238. std::string operation_text;
  1239. QJsonObject left_object;
  1240. QJsonObject right_object;
  1241. QJsonObject destination_object;
  1242. WordOperand left;
  1243. WordOperand right;
  1244. RegisterAddress destination{RegisterArea::D, 0};
  1245. ArithmeticOperation operation = ArithmeticOperation::Add;
  1246. if (!readString(object, "operation", context, &operation_text, state))
  1247. {
  1248. return false;
  1249. }
  1250. if (operation_text == "add")
  1251. {
  1252. operation = ArithmeticOperation::Add;
  1253. }
  1254. else if (operation_text == "subtract")
  1255. {
  1256. operation = ArithmeticOperation::Subtract;
  1257. }
  1258. else
  1259. {
  1260. return state->fail(
  1261. ProjectStorageError::InvalidField,
  1262. "不支持的算术运算:" + operation_text);
  1263. }
  1264. if (!readObject(object, "left", context, &left_object, state)
  1265. || !parseWordOperand(left_object, context + ".left", &left, state)
  1266. || !readObject(object, "right", context, &right_object, state)
  1267. || !parseWordOperand(right_object, context + ".right", &right, state)
  1268. || !readObject(object, "destination", context, &destination_object, state)
  1269. || !parseAddress(
  1270. destination_object,
  1271. context + ".destination",
  1272. &destination,
  1273. state))
  1274. {
  1275. return false;
  1276. }
  1277. *config = ArithmeticNodeConfig{operation, left, right, destination};
  1278. return true;
  1279. }
  1280. if (type != "contact" && type != "edgeContact"
  1281. && type != "coil" && type != "compare")
  1282. {
  1283. return state->fail(
  1284. ProjectStorageError::InvalidField,
  1285. "不支持的逻辑节点类型:" + type);
  1286. }
  1287. QJsonObject address_object;
  1288. if (!readObject(object, "address", context, &address_object, state))
  1289. {
  1290. return false;
  1291. }
  1292. RegisterAddress address{RegisterArea::M, 0};
  1293. if (!parseAddress(address_object, context + ".address", &address, state))
  1294. {
  1295. return false;
  1296. }
  1297. // 每种节点只读取自身需要的字段,避免无关配置进入领域模型
  1298. if (type == "contact")
  1299. {
  1300. std::string mode_text;
  1301. ContactMode mode = ContactMode::NormallyOpen;
  1302. if (!readString(object, "mode", context, &mode_text, state)
  1303. || !parseContactMode(mode_text, &mode, state))
  1304. {
  1305. return false;
  1306. }
  1307. *config = ContactNodeConfig{address, mode};
  1308. return true;
  1309. }
  1310. if (type == "edgeContact")
  1311. {
  1312. std::string mode_text;
  1313. EdgeMode mode = EdgeMode::Rising;
  1314. if (!readString(object, "mode", context, &mode_text, state)
  1315. || !parseEdgeMode(mode_text, &mode, state))
  1316. {
  1317. return false;
  1318. }
  1319. *config = EdgeContactNodeConfig{address, mode};
  1320. return true;
  1321. }
  1322. if (type == "coil")
  1323. {
  1324. std::string mode_text;
  1325. CoilMode mode = CoilMode::Normal;
  1326. if (!readString(object, "mode", context, &mode_text, state)
  1327. || !parseCoilMode(mode_text, &mode, state))
  1328. {
  1329. return false;
  1330. }
  1331. *config = CoilNodeConfig{address, mode};
  1332. return true;
  1333. }
  1334. if (type == "compare")
  1335. {
  1336. std::string comparison_text;
  1337. int value = 0;
  1338. ComparisonOperator comparison = ComparisonOperator::Equal;
  1339. if (!readString(object, "comparison", context, &comparison_text, state)
  1340. || !readInt(
  1341. object,
  1342. "value",
  1343. context,
  1344. std::numeric_limits<std::int16_t>::min(),
  1345. std::numeric_limits<std::int16_t>::max(),
  1346. &value,
  1347. state)
  1348. || !parseComparison(comparison_text, &comparison, state))
  1349. {
  1350. return false;
  1351. }
  1352. // 先按 int 校验范围,再安全收窄为领域模型要求的 int16_t
  1353. *config = CompareNodeConfig{
  1354. address, comparison, static_cast<std::int16_t>(value)};
  1355. return true;
  1356. }
  1357. return state->fail(ProjectStorageError::InvalidField, "逻辑节点类型解析失败");
  1358. }
  1359. // 解析逻辑节点标识及其多态配置
  1360. bool parseLogicNode(
  1361. const QJsonObject &object,
  1362. const std::string &context,
  1363. LogicNode *node,
  1364. ParseState *state)
  1365. {
  1366. QJsonObject config;
  1367. if (!readString(
  1368. object, "id", context, &node->id, state,
  1369. ProjectLimits::kMaximumIdBytes)
  1370. || !readBool(object, "configured", context, &node->configured, state)
  1371. || !readObject(object, "config", context, &config, state)
  1372. || !parseNodeConfig(config, context + ".config", &node->config, state))
  1373. {
  1374. return false;
  1375. }
  1376. return true;
  1377. }
  1378. QJsonObject serializeLadderRung(const LadderRung &rung)
  1379. {
  1380. QJsonObject object;
  1381. object.insert(QStringLiteral("id"), fromUtf8(rung.id));
  1382. object.insert(QStringLiteral("name"), fromUtf8(rung.name));
  1383. object.insert(QStringLiteral("comment"), fromUtf8(rung.comment));
  1384. QJsonArray cells;
  1385. for (const LadderCell &cell : rung.cells)
  1386. {
  1387. QJsonObject cell_object;
  1388. cell_object.insert(QStringLiteral("id"), fromUtf8(cell.id));
  1389. cell_object.insert(
  1390. QStringLiteral("kind"),
  1391. cell.kind == LadderCellKind::Node
  1392. ? QStringLiteral("node")
  1393. : cell.kind == LadderCellKind::Wire
  1394. ? QStringLiteral("wire") : QStringLiteral("gap"));
  1395. if (cell.node.has_value())
  1396. {
  1397. cell_object.insert(QStringLiteral("node"), serializeLogicNode(*cell.node));
  1398. }
  1399. cells.append(cell_object);
  1400. }
  1401. object.insert(QStringLiteral("cells"), cells);
  1402. object.insert(
  1403. QStringLiteral("output"),
  1404. rung.output.has_value() ? QJsonValue(serializeLogicNode(*rung.output))
  1405. : QJsonValue(QJsonValue::Null));
  1406. return object;
  1407. }
  1408. bool parseLadderRung(
  1409. const QJsonObject &object,
  1410. const std::string &context,
  1411. LadderRung *rung,
  1412. ParseState *state)
  1413. {
  1414. QJsonArray cells;
  1415. QJsonValue output;
  1416. if (!readString(
  1417. object, "id", context, &rung->id, state,
  1418. ProjectLimits::kMaximumIdBytes)
  1419. || !readString(object, "name", context, &rung->name, state)
  1420. || !readString(object, "comment", context, &rung->comment, state)
  1421. || !readArray(object, "cells", context, &cells, state,
  1422. ProjectLimits::kMaximumConditionColumns)
  1423. || !readValue(object, "output", context, &output, state))
  1424. {
  1425. return false;
  1426. }
  1427. if (cells.size() != ProjectLimits::kMaximumConditionColumns)
  1428. {
  1429. return state->fail(ProjectStorageError::InvalidField,
  1430. context + ".cells 必须严格包含 10 个网格");
  1431. }
  1432. for (int index = 0; index < cells.size(); ++index)
  1433. {
  1434. if (!cells.at(index).isObject())
  1435. {
  1436. return state->fail(ProjectStorageError::InvalidField,
  1437. context + ".cells 的元素必须是对象");
  1438. }
  1439. const QJsonObject cell_object = cells.at(index).toObject();
  1440. const std::string cell_context = context + ".cells["
  1441. + std::to_string(index) + ']';
  1442. LadderCell cell;
  1443. std::string kind;
  1444. if (!readString(cell_object, "id", cell_context, &cell.id, state,
  1445. ProjectLimits::kMaximumIdBytes)
  1446. || !readString(cell_object, "kind", cell_context, &kind, state))
  1447. {
  1448. return false;
  1449. }
  1450. if (kind == "gap")
  1451. {
  1452. cell.kind = LadderCellKind::Gap;
  1453. }
  1454. else if (kind == "wire")
  1455. {
  1456. cell.kind = LadderCellKind::Wire;
  1457. }
  1458. else if (kind == "node")
  1459. {
  1460. cell.kind = LadderCellKind::Node;
  1461. QJsonObject node_object;
  1462. if (!readObject(cell_object, "node", cell_context, &node_object, state))
  1463. {
  1464. return false;
  1465. }
  1466. LogicNode node;
  1467. if (!parseLogicNode(node_object, cell_context + ".node", &node, state))
  1468. {
  1469. return false;
  1470. }
  1471. cell.node = std::move(node);
  1472. }
  1473. else
  1474. {
  1475. return state->fail(ProjectStorageError::InvalidField,
  1476. cell_context + ".kind 必须是 gap、wire 或 node");
  1477. }
  1478. rung->cells.push_back(std::move(cell));
  1479. }
  1480. if (output.isNull())
  1481. {
  1482. rung->output.reset();
  1483. return true;
  1484. }
  1485. if (!output.isObject())
  1486. {
  1487. return state->fail(
  1488. ProjectStorageError::InvalidField,
  1489. context + ".output 必须是对象或 null");
  1490. }
  1491. LogicNode node;
  1492. if (!parseLogicNode(output.toObject(), context + ".output", &node, state))
  1493. {
  1494. return false;
  1495. }
  1496. rung->output = std::move(node);
  1497. return true;
  1498. }
  1499. QJsonObject serializeControlLogic(const ControlLogic &logic)
  1500. {
  1501. QJsonArray rungs;
  1502. for (const LadderRung &rung : logic.rungs)
  1503. {
  1504. rungs.append(serializeLadderRung(rung));
  1505. }
  1506. QJsonArray connections;
  1507. for (const VerticalConnection &connection : logic.verticalConnections)
  1508. {
  1509. QJsonObject item;
  1510. item.insert(QStringLiteral("id"), fromUtf8(connection.id));
  1511. item.insert(QStringLiteral("upperRungId"), fromUtf8(connection.upperRungId));
  1512. item.insert(QStringLiteral("lowerRungId"), fromUtf8(connection.lowerRungId));
  1513. item.insert(QStringLiteral("columnBoundary"), connection.columnBoundary);
  1514. connections.append(item);
  1515. }
  1516. QJsonObject object;
  1517. object.insert(QStringLiteral("id"), fromUtf8(logic.id));
  1518. object.insert(QStringLiteral("name"), fromUtf8(logic.name));
  1519. object.insert(QStringLiteral("enabled"), logic.enabled);
  1520. object.insert(QStringLiteral("rungs"), rungs);
  1521. object.insert(QStringLiteral("verticalConnections"), connections);
  1522. return object;
  1523. }
  1524. bool parseControlLogic(
  1525. const QJsonObject &object,
  1526. const std::string &context,
  1527. const ProjectLimitSettings &limits,
  1528. ControlLogic *logic,
  1529. ParseState *state)
  1530. {
  1531. QJsonArray rungs;
  1532. QJsonArray connections;
  1533. if (!readString(
  1534. object, "id", context, &logic->id, state,
  1535. ProjectLimits::kMaximumIdBytes)
  1536. || !readString(object, "name", context, &logic->name, state)
  1537. || !readBool(object, "enabled", context, &logic->enabled, state)
  1538. || !readArray(
  1539. object, "rungs", context, &rungs, state,
  1540. static_cast<int>(limits.maximumRungsPerLogic))
  1541. || !readArray(
  1542. object, "verticalConnections", context, &connections, state,
  1543. static_cast<int>(ProjectLimits::kMaximumVerticalConnectionsPerLogic)))
  1544. {
  1545. return false;
  1546. }
  1547. logic->rungs.reserve(static_cast<std::size_t>(rungs.size()));
  1548. for (int index = 0; index < rungs.size(); ++index)
  1549. {
  1550. if (!rungs.at(index).isObject())
  1551. {
  1552. return state->fail(
  1553. ProjectStorageError::InvalidField,
  1554. context + ".rungs 的元素必须是对象");
  1555. }
  1556. LadderRung rung;
  1557. if (!parseLadderRung(
  1558. rungs.at(index).toObject(),
  1559. context + ".rungs[" + std::to_string(index) + ']',
  1560. &rung,
  1561. state))
  1562. {
  1563. return false;
  1564. }
  1565. logic->rungs.push_back(std::move(rung));
  1566. }
  1567. for (int index = 0; index < connections.size(); ++index)
  1568. {
  1569. if (!connections.at(index).isObject())
  1570. {
  1571. return state->fail(ProjectStorageError::InvalidField,
  1572. context + ".verticalConnections 的元素必须是对象");
  1573. }
  1574. const QJsonObject item = connections.at(index).toObject();
  1575. VerticalConnection connection;
  1576. int boundary = 0;
  1577. const std::string item_context = context + ".verticalConnections["
  1578. + std::to_string(index) + ']';
  1579. if (!readString(item, "id", item_context, &connection.id, state,
  1580. ProjectLimits::kMaximumIdBytes)
  1581. || !readString(item, "upperRungId", item_context,
  1582. &connection.upperRungId, state,
  1583. ProjectLimits::kMaximumIdBytes)
  1584. || !readString(item, "lowerRungId", item_context,
  1585. &connection.lowerRungId, state,
  1586. ProjectLimits::kMaximumIdBytes)
  1587. || !readInt(item, "columnBoundary", item_context, 0,
  1588. ProjectLimits::kMaximumConditionColumns,
  1589. &boundary, state))
  1590. {
  1591. return false;
  1592. }
  1593. connection.columnBoundary = boundary;
  1594. logic->verticalConnections.push_back(std::move(connection));
  1595. }
  1596. return true;
  1597. }
  1598. // 将完整领域工程聚合为工程文件的顶层 JSON 对象
  1599. QJsonObject serializeProject(const Project &project)
  1600. {
  1601. QJsonArray pages;
  1602. for (const HmiPage &page : project.hmiPages)
  1603. {
  1604. pages.append(serializeHmiPage(page));
  1605. }
  1606. QJsonArray logics;
  1607. for (const ControlLogic &logic : project.controlLogics)
  1608. {
  1609. logics.append(serializeControlLogic(logic));
  1610. }
  1611. QJsonArray alarms;
  1612. for (const AlarmDefinition &definition : project.alarmDefinitions)
  1613. {
  1614. alarms.append(serializeAlarmDefinition(definition));
  1615. }
  1616. QJsonArray register_comments;
  1617. for (const RegisterComment &comment : project.registerComments)
  1618. {
  1619. register_comments.append(serializeRegisterComment(comment));
  1620. }
  1621. QJsonObject object;
  1622. object.insert(QStringLiteral("formatVersion"), fromUtf8(project.metadata.formatVersion));
  1623. object.insert(QStringLiteral("id"), fromUtf8(project.metadata.id));
  1624. object.insert(QStringLiteral("name"), fromUtf8(project.metadata.name));
  1625. object.insert(QStringLiteral("hmiPages"), pages);
  1626. object.insert(QStringLiteral("initialHmiPageId"),
  1627. fromUtf8(project.initialHmiPageId));
  1628. object.insert(QStringLiteral("alarmDefinitions"), alarms);
  1629. object.insert(QStringLiteral("registerComments"), register_comments);
  1630. object.insert(QStringLiteral("controlLogics"), logics);
  1631. return object;
  1632. }
  1633. // 从顶层 JSON 对象解析完整工程,并在解析子对象前检查格式版本
  1634. bool parseProject(
  1635. const QJsonObject &object,
  1636. const ProjectLimitSettings &limits,
  1637. Project *project,
  1638. ParseState *state)
  1639. {
  1640. QJsonArray pages;
  1641. QJsonArray alarms;
  1642. QJsonArray register_comments;
  1643. QJsonArray logics;
  1644. if (!readString(
  1645. object,
  1646. "formatVersion",
  1647. "project",
  1648. &project->metadata.formatVersion,
  1649. state,
  1650. ProjectLimits::kMaximumIdBytes))
  1651. {
  1652. return false;
  1653. }
  1654. // 不尝试猜测其他版本的结构,避免按当前格式错误解释数据
  1655. if (project->metadata.formatVersion != kCurrentFormatVersion)
  1656. {
  1657. return state->fail(
  1658. ProjectStorageError::UnsupportedVersion,
  1659. "不支持的工程格式版本:" + project->metadata.formatVersion);
  1660. }
  1661. if (!readString(
  1662. object, "id", "project", &project->metadata.id, state,
  1663. ProjectLimits::kMaximumIdBytes)
  1664. || !readString(object, "name", "project", &project->metadata.name, state)
  1665. || !readArray(
  1666. object, "hmiPages", "project", &pages, state,
  1667. static_cast<int>(limits.maximumHmiPages))
  1668. || !readString(
  1669. object, "initialHmiPageId", "project",
  1670. &project->initialHmiPageId, state,
  1671. ProjectLimits::kMaximumIdBytes)
  1672. || !readArray(
  1673. object, "alarmDefinitions", "project", &alarms, state,
  1674. static_cast<int>(limits.maximumAlarmDefinitions))
  1675. || !readArray(
  1676. object, "registerComments", "project", &register_comments, state,
  1677. static_cast<int>(ProjectLimits::kMaximumRegisterComments))
  1678. || !readArray(
  1679. object, "controlLogics", "project", &logics, state,
  1680. static_cast<int>(limits.maximumControlLogics)))
  1681. {
  1682. return false;
  1683. }
  1684. // 逐层解析时持续传递字段路径,任何失败都保留最初的具体位置
  1685. project->hmiPages.reserve(static_cast<std::size_t>(pages.size()));
  1686. for (int index = 0; index < pages.size(); ++index)
  1687. {
  1688. if (!pages.at(index).isObject())
  1689. {
  1690. return state->fail(
  1691. ProjectStorageError::InvalidField,
  1692. "project.hmiPages 的元素必须是对象");
  1693. }
  1694. HmiPage page;
  1695. if (!parseHmiPage(
  1696. pages.at(index).toObject(),
  1697. "project.hmiPages[" + std::to_string(index) + ']',
  1698. limits,
  1699. &page,
  1700. state))
  1701. {
  1702. return false;
  1703. }
  1704. project->hmiPages.push_back(std::move(page));
  1705. }
  1706. project->alarmDefinitions.reserve(static_cast<std::size_t>(alarms.size()));
  1707. for (int index = 0; index < alarms.size(); ++index)
  1708. {
  1709. if (!alarms.at(index).isObject())
  1710. {
  1711. return state->fail(
  1712. ProjectStorageError::InvalidField,
  1713. "project.alarmDefinitions 的元素必须是对象");
  1714. }
  1715. AlarmDefinition definition;
  1716. if (!parseAlarmDefinition(
  1717. alarms.at(index).toObject(),
  1718. "project.alarmDefinitions[" + std::to_string(index) + ']',
  1719. &definition,
  1720. state))
  1721. {
  1722. return false;
  1723. }
  1724. project->alarmDefinitions.push_back(std::move(definition));
  1725. }
  1726. project->registerComments.reserve(static_cast<std::size_t>(register_comments.size()));
  1727. for (int index = 0; index < register_comments.size(); ++index)
  1728. {
  1729. if (!register_comments.at(index).isObject())
  1730. {
  1731. return state->fail(
  1732. ProjectStorageError::InvalidField,
  1733. "project.registerComments 的元素必须是对象");
  1734. }
  1735. RegisterComment comment;
  1736. if (!parseRegisterComment(
  1737. register_comments.at(index).toObject(),
  1738. "project.registerComments[" + std::to_string(index) + ']',
  1739. &comment,
  1740. state))
  1741. {
  1742. return false;
  1743. }
  1744. project->registerComments.push_back(std::move(comment));
  1745. }
  1746. project->controlLogics.reserve(static_cast<std::size_t>(logics.size()));
  1747. for (int index = 0; index < logics.size(); ++index)
  1748. {
  1749. if (!logics.at(index).isObject())
  1750. {
  1751. return state->fail(
  1752. ProjectStorageError::InvalidField,
  1753. "project.controlLogics 的元素必须是对象");
  1754. }
  1755. ControlLogic logic;
  1756. if (!parseControlLogic(
  1757. logics.at(index).toObject(),
  1758. "project.controlLogics[" + std::to_string(index) + ']',
  1759. limits,
  1760. &logic,
  1761. state))
  1762. {
  1763. return false;
  1764. }
  1765. project->controlLogics.push_back(std::move(logic));
  1766. }
  1767. return true;
  1768. }
  1769. // 将 Qt 文件错误转换为领域层统一的工程保存失败结果
  1770. ProjectSaveResult saveFailure(
  1771. ProjectStorageError error, const QString &message)
  1772. {
  1773. return {false, error, toUtf8(message)};
  1774. }
  1775. } // namespace
  1776. JsonProjectStorage::JsonProjectStorage(
  1777. const ProjectLimitSettings &project_limits)
  1778. : project_limits_(project_limits)
  1779. {
  1780. }
  1781. // 校验工程后将其序列化,并通过 QSaveFile 原子写入目标文件
  1782. ProjectSaveResult JsonProjectStorage::save(
  1783. const Project &project, const std::string &file_path)
  1784. {
  1785. // 写文件前先执行领域校验,防止持久化内部关系不合法的工程
  1786. std::string validation_error;
  1787. if (!project.validate(project_limits_, &validation_error))
  1788. {
  1789. return {false, ProjectStorageError::InvalidProject, validation_error};
  1790. }
  1791. if (project.metadata.formatVersion != kCurrentFormatVersion)
  1792. {
  1793. return {false,
  1794. ProjectStorageError::UnsupportedVersion,
  1795. "不支持的工程格式版本:" + project.metadata.formatVersion};
  1796. }
  1797. // QSaveFile 先写临时文件,仅在 commit 成功后替换目标文件
  1798. QSaveFile file(fromUtf8(file_path));
  1799. if (!file.open(QIODevice::WriteOnly))
  1800. {
  1801. return saveFailure(ProjectStorageError::FileOpenFailed, file.errorString());
  1802. }
  1803. const QByteArray data = QJsonDocument(serializeProject(project)).toJson(
  1804. QJsonDocument::Indented);
  1805. if (static_cast<std::size_t>(data.size())
  1806. > ProjectLimits::kMaximumProjectFileBytes)
  1807. {
  1808. file.cancelWriting();
  1809. return {false,
  1810. ProjectStorageError::InvalidProject,
  1811. "工程 JSON 文件不能超过 16 MiB"};
  1812. }
  1813. // 短写入也视为失败,并取消临时文件提交
  1814. if (file.write(data) != data.size())
  1815. {
  1816. file.cancelWriting();
  1817. return saveFailure(ProjectStorageError::FileWriteFailed, file.errorString());
  1818. }
  1819. if (!file.commit())
  1820. {
  1821. return saveFailure(ProjectStorageError::FileWriteFailed, file.errorString());
  1822. }
  1823. return {true, ProjectStorageError::None, {}};
  1824. }
  1825. // 读取并解析工程文件,全部校验通过后才返回新的领域工程
  1826. ProjectLoadResult JsonProjectStorage::load(const std::string &file_path)
  1827. {
  1828. QFile file(fromUtf8(file_path));
  1829. if (!file.open(QIODevice::ReadOnly))
  1830. {
  1831. return {false,
  1832. {},
  1833. ProjectStorageError::FileOpenFailed,
  1834. toUtf8(file.errorString())};
  1835. }
  1836. if (file.size() < 0
  1837. || static_cast<quint64>(file.size())
  1838. > static_cast<quint64>(ProjectLimits::kMaximumProjectFileBytes))
  1839. {
  1840. return {false,
  1841. {},
  1842. ProjectStorageError::InvalidJson,
  1843. "工程 JSON 文件不能超过 16 MiB"};
  1844. }
  1845. // 最多只读到上限加 1 字节,避免文件属性检查后文件变大导致无限制分配
  1846. const QByteArray data = file.read(
  1847. static_cast<qint64>(ProjectLimits::kMaximumProjectFileBytes) + 1);
  1848. if (file.error() != QFileDevice::NoError)
  1849. {
  1850. return {false,
  1851. {},
  1852. ProjectStorageError::FileReadFailed,
  1853. toUtf8(file.errorString())};
  1854. }
  1855. if (static_cast<std::size_t>(data.size())
  1856. > ProjectLimits::kMaximumProjectFileBytes)
  1857. {
  1858. return {false,
  1859. {},
  1860. ProjectStorageError::InvalidJson,
  1861. "工程 JSON 文件不能超过 16 MiB"};
  1862. }
  1863. // 顶层必须是 JSON 对象,数组或标量不能表示完整工程
  1864. QJsonParseError parse_error;
  1865. const QJsonDocument document = QJsonDocument::fromJson(data, &parse_error);
  1866. if (parse_error.error != QJsonParseError::NoError || !document.isObject())
  1867. {
  1868. const QString message = parse_error.error == QJsonParseError::NoError
  1869. ? QStringLiteral("工程 JSON 根节点必须是对象")
  1870. : parse_error.errorString();
  1871. return {false,
  1872. {},
  1873. ProjectStorageError::InvalidJson,
  1874. toUtf8(message)};
  1875. }
  1876. // 先在局部对象中完成结构解析,失败时不会暴露半成品工程
  1877. Project project;
  1878. ParseState state;
  1879. if (!parseProject(document.object(), project_limits_, &project, &state))
  1880. {
  1881. return {false, {}, state.error, state.message};
  1882. }
  1883. // JSON 字段合法不代表业务关系合法,还需执行领域层整体校验
  1884. std::string validation_error;
  1885. if (!project.validate(project_limits_, &validation_error))
  1886. {
  1887. return {false,
  1888. {},
  1889. ProjectStorageError::InvalidProject,
  1890. validation_error};
  1891. }
  1892. return {true, std::move(project), ProjectStorageError::None, {}};
  1893. }