综合平台编程器项目的远程存储
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.
 
 
 
 

1908 lines
57 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 = "1.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::DHigh:
  409. {
  410. return QStringLiteral("dHigh");
  411. }
  412. case AlarmCondition::DLow:
  413. {
  414. return QStringLiteral("dLow");
  415. }
  416. default:
  417. {
  418. return {};
  419. }
  420. }
  421. }
  422. bool parseAlarmCondition(
  423. const std::string &value, AlarmCondition *condition, ParseState *state)
  424. {
  425. if (value == "mOn")
  426. {
  427. *condition = AlarmCondition::MOn;
  428. }
  429. else if (value == "dHigh")
  430. {
  431. *condition = AlarmCondition::DHigh;
  432. }
  433. else if (value == "dLow")
  434. {
  435. *condition = AlarmCondition::DLow;
  436. }
  437. else
  438. {
  439. return state->fail(
  440. ProjectStorageError::InvalidField,
  441. "不支持的报警触发条件:" + value);
  442. }
  443. return true;
  444. }
  445. QJsonObject serializeAlarmDefinition(const AlarmDefinition &definition)
  446. {
  447. QJsonObject object;
  448. object.insert(QStringLiteral("id"), fromUtf8(definition.id));
  449. object.insert(QStringLiteral("address"), serializeAddress(definition.address));
  450. object.insert(
  451. QStringLiteral("condition"), alarmConditionName(definition.condition));
  452. object.insert(QStringLiteral("threshold"), definition.threshold);
  453. object.insert(QStringLiteral("message"), fromUtf8(definition.message));
  454. return object;
  455. }
  456. bool parseAlarmDefinition(
  457. const QJsonObject &object,
  458. const std::string &context,
  459. AlarmDefinition *definition,
  460. ParseState *state)
  461. {
  462. QJsonObject address;
  463. std::string condition;
  464. int threshold = 0;
  465. if (!readString(
  466. object, "id", context, &definition->id, state,
  467. ProjectLimits::kMaximumIdBytes)
  468. || !readObject(object, "address", context, &address, state)
  469. || !readString(object, "condition", context, &condition, state)
  470. || !readInt(
  471. object,
  472. "threshold",
  473. context,
  474. std::numeric_limits<std::int16_t>::min(),
  475. std::numeric_limits<std::int16_t>::max(),
  476. &threshold,
  477. state)
  478. || !readString(object, "message", context, &definition->message, state))
  479. {
  480. return false;
  481. }
  482. if (!parseAddress(address, context + ".address", &definition->address, state)
  483. || !parseAlarmCondition(condition, &definition->condition, state))
  484. {
  485. return false;
  486. }
  487. definition->threshold = static_cast<std::int16_t>(threshold);
  488. return true;
  489. }
  490. QJsonObject serializeRegisterComment(const RegisterComment &comment)
  491. {
  492. QJsonObject object;
  493. object.insert(QStringLiteral("address"), serializeAddress(comment.address));
  494. object.insert(QStringLiteral("text"), fromUtf8(comment.text));
  495. return object;
  496. }
  497. bool parseRegisterComment(
  498. const QJsonObject &object,
  499. const std::string &context,
  500. RegisterComment *comment,
  501. ParseState *state)
  502. {
  503. QJsonObject address;
  504. if (!readObject(object, "address", context, &address, state)
  505. || !readString(object, "text", context, &comment->text, state))
  506. {
  507. return false;
  508. }
  509. return parseAddress(address, context + ".address", &comment->address, state);
  510. }
  511. // 将 HMI 控件矩形区域序列化为 JSON 对象
  512. QJsonObject serializeBounds(const HmiRect &bounds)
  513. {
  514. QJsonObject object;
  515. object.insert(QStringLiteral("x"), bounds.x);
  516. object.insert(QStringLiteral("y"), bounds.y);
  517. object.insert(QStringLiteral("width"), bounds.width);
  518. object.insert(QStringLiteral("height"), bounds.height);
  519. return object;
  520. }
  521. // 解析控件矩形区域,允许任意坐标但要求宽高为正数
  522. bool parseBounds(
  523. const QJsonObject &object,
  524. const std::string &context,
  525. HmiRect *bounds,
  526. ParseState *state)
  527. {
  528. return readInt(
  529. object,
  530. "x",
  531. context,
  532. 0,
  533. ProjectLimits::kMaximumHmiPageWidth,
  534. &bounds->x,
  535. state)
  536. && readInt(
  537. object,
  538. "y",
  539. context,
  540. 0,
  541. ProjectLimits::kMaximumHmiPageHeight,
  542. &bounds->y,
  543. state)
  544. && readInt(
  545. object,
  546. "width",
  547. context,
  548. 1,
  549. ProjectLimits::kMaximumHmiControlWidth,
  550. &bounds->width,
  551. state)
  552. && readInt(
  553. object,
  554. "height",
  555. context,
  556. 1,
  557. ProjectLimits::kMaximumHmiControlHeight,
  558. &bounds->height,
  559. state);
  560. }
  561. // 将 HMI 控件的字符串扩展属性序列化为 JSON 对象
  562. QJsonObject serializeProperties(const std::map<std::string, std::string> &properties)
  563. {
  564. QJsonObject object;
  565. for (const auto &property : properties)
  566. {
  567. object.insert(fromUtf8(property.first), fromUtf8(property.second));
  568. }
  569. return object;
  570. }
  571. // 解析 HMI 控件扩展属性,并确保所有属性值都是字符串
  572. bool parseProperties(
  573. const QJsonObject &object,
  574. std::map<std::string, std::string> *properties,
  575. ParseState *state)
  576. {
  577. if (object.size() > static_cast<int>(ProjectLimits::kMaximumHmiProperties))
  578. {
  579. return state->fail(
  580. ProjectStorageError::InvalidField,
  581. "单个 HMI 控件最多保存 64 对扩展属性");
  582. }
  583. for (auto current = object.constBegin(); current != object.constEnd(); ++current)
  584. {
  585. if (!current.value().isString())
  586. {
  587. return state->fail(
  588. ProjectStorageError::InvalidField,
  589. "HMI 控件属性值必须是字符串");
  590. }
  591. const std::string key = toUtf8(current.key());
  592. const std::string value = toUtf8(current.value().toString());
  593. if (key.empty() || key.size() > ProjectLimits::kMaximumPropertyKeyBytes
  594. || value.size() > ProjectLimits::kMaximumPropertyValueBytes)
  595. {
  596. return state->fail(
  597. ProjectStorageError::InvalidField,
  598. "HMI 控件属性名称最多 128 字节,属性值最多 1024 字节");
  599. }
  600. properties->emplace(key, value);
  601. }
  602. return true;
  603. }
  604. // 将单个 HMI 控件及其可选寄存器绑定序列化为 JSON 对象
  605. QJsonObject serializeHmiControl(const HmiControl &control)
  606. {
  607. QJsonObject object;
  608. object.insert(QStringLiteral("id"), fromUtf8(control.id));
  609. object.insert(QStringLiteral("type"), hmiControlTypeName(control.type));
  610. object.insert(QStringLiteral("bounds"), serializeBounds(control.bounds));
  611. object.insert(QStringLiteral("text"), fromUtf8(control.text));
  612. // 使用 JSON null 明确表示控件没有寄存器绑定
  613. if (control.binding.has_value())
  614. {
  615. object.insert(QStringLiteral("binding"), serializeAddress(*control.binding));
  616. }
  617. else
  618. {
  619. object.insert(QStringLiteral("binding"), QJsonValue::Null);
  620. }
  621. object.insert(QStringLiteral("properties"), serializeProperties(control.properties));
  622. if (control.type == HmiControlType::NumericDisplay
  623. || control.type == HmiControlType::NumericInput)
  624. {
  625. object.insert(
  626. QStringLiteral("dataType"),
  627. QString::fromLatin1(registerDataTypeName(control.dataType)));
  628. }
  629. if (control.type == HmiControlType::Button)
  630. {
  631. object.insert(
  632. QStringLiteral("buttonOperation"),
  633. hmiButtonOperationName(control.buttonOperation));
  634. }
  635. if (control.type == HmiControlType::PageJump)
  636. {
  637. object.insert(
  638. QStringLiteral("targetPageId"),
  639. fromUtf8(control.pageJump.has_value()
  640. ? control.pageJump->targetPageId
  641. : std::string{}));
  642. }
  643. return object;
  644. }
  645. // 解析单个 HMI 控件,并逐层校验类型、区域、绑定和扩展属性
  646. bool parseHmiControl(
  647. const QJsonObject &object,
  648. const std::string &context,
  649. HmiControl *control,
  650. ParseState *state)
  651. {
  652. std::string type_text;
  653. QJsonObject bounds;
  654. QJsonObject properties;
  655. QJsonValue binding;
  656. if (!readString(
  657. object, "id", context, &control->id, state,
  658. ProjectLimits::kMaximumIdBytes)
  659. || !readString(object, "type", context, &type_text, state)
  660. || !readObject(object, "bounds", context, &bounds, state)
  661. || !readString(object, "text", context, &control->text, state)
  662. || !readValue(object, "binding", context, &binding, state)
  663. || !readObject(object, "properties", context, &properties, state))
  664. {
  665. return false;
  666. }
  667. if (!parseHmiControlType(type_text, &control->type, state)
  668. || !parseBounds(bounds, context + ".bounds", &control->bounds, state)
  669. || !parseProperties(properties, &control->properties, state))
  670. {
  671. return false;
  672. }
  673. if (control->type == HmiControlType::NumericDisplay
  674. || control->type == HmiControlType::NumericInput)
  675. {
  676. std::string data_type_text;
  677. if (!readString(object, "dataType", context, &data_type_text, state)
  678. || !parseRegisterDataType(data_type_text, &control->dataType))
  679. {
  680. return state->fail(
  681. ProjectStorageError::InvalidField,
  682. context + ".dataType 必须是 int16 或 float32");
  683. }
  684. }
  685. if (control->type == HmiControlType::Button)
  686. {
  687. std::string operation_text;
  688. if (!readString(
  689. object,
  690. "buttonOperation",
  691. context,
  692. &operation_text,
  693. state)
  694. || !parseHmiButtonOperation(
  695. operation_text, &control->buttonOperation, state))
  696. {
  697. return false;
  698. }
  699. }
  700. if (control->type == HmiControlType::PageJump)
  701. {
  702. std::string target_page_id;
  703. if (!readString(
  704. object, "targetPageId", context, &target_page_id, state,
  705. ProjectLimits::kMaximumIdBytes))
  706. {
  707. return false;
  708. }
  709. control->pageJump = HmiPageJumpConfig{std::move(target_page_id)};
  710. }
  711. // binding 允许为 null,其余非空值必须是合法的寄存器地址对象
  712. if (binding.isNull())
  713. {
  714. control->binding.reset();
  715. return true;
  716. }
  717. if (!binding.isObject())
  718. {
  719. return state->fail(
  720. ProjectStorageError::InvalidField,
  721. context + ".binding 必须是对象或 null");
  722. }
  723. RegisterAddress address{RegisterArea::M, 0};
  724. if (!parseAddress(binding.toObject(), context + ".binding", &address, state))
  725. {
  726. return false;
  727. }
  728. control->binding = address;
  729. return true;
  730. }
  731. // 将 HMI 页面及其全部控件序列化为 JSON 对象
  732. QJsonObject serializeHmiPage(const HmiPage &page)
  733. {
  734. QJsonArray controls;
  735. for (const HmiControl &control : page.controls)
  736. {
  737. controls.append(serializeHmiControl(control));
  738. }
  739. QJsonObject object;
  740. object.insert(QStringLiteral("id"), fromUtf8(page.id));
  741. object.insert(QStringLiteral("name"), fromUtf8(page.name));
  742. object.insert(QStringLiteral("width"), page.width);
  743. object.insert(QStringLiteral("height"), page.height);
  744. object.insert(QStringLiteral("controls"), controls);
  745. return object;
  746. }
  747. // 解析 HMI 页面基础信息,并按数组顺序构造页面控件
  748. bool parseHmiPage(
  749. const QJsonObject &object,
  750. const std::string &context,
  751. const ProjectLimitSettings &limits,
  752. HmiPage *page,
  753. ParseState *state)
  754. {
  755. QJsonArray controls;
  756. if (!readString(
  757. object, "id", context, &page->id, state,
  758. ProjectLimits::kMaximumIdBytes)
  759. || !readString(object, "name", context, &page->name, state)
  760. || !readInt(object, "width", context,
  761. ProjectLimits::kMinimumHmiPageWidth,
  762. ProjectLimits::kMaximumHmiPageWidth,
  763. &page->width, state)
  764. || !readInt(object, "height", context,
  765. ProjectLimits::kMinimumHmiPageHeight,
  766. ProjectLimits::kMaximumHmiPageHeight,
  767. &page->height, state)
  768. || !readArray(
  769. object, "controls", context, &controls, state,
  770. static_cast<int>(limits.maximumHmiControlsPerPage)))
  771. {
  772. return false;
  773. }
  774. // 预留准确容量,避免逐项加入控件时重复扩容
  775. page->controls.reserve(static_cast<std::size_t>(controls.size()));
  776. for (int index = 0; index < controls.size(); ++index)
  777. {
  778. if (!controls.at(index).isObject())
  779. {
  780. return state->fail(
  781. ProjectStorageError::InvalidField,
  782. context + ".controls 的元素必须是对象");
  783. }
  784. HmiControl control;
  785. if (!parseHmiControl(
  786. controls.at(index).toObject(),
  787. context + ".controls[" + std::to_string(index) + ']',
  788. &control,
  789. state))
  790. {
  791. return false;
  792. }
  793. page->controls.push_back(std::move(control));
  794. }
  795. return true;
  796. }
  797. // 将触点模式枚举转换为工程文件中的稳定字符串
  798. QString contactModeName(ContactMode mode)
  799. {
  800. return mode == ContactMode::NormallyOpen
  801. ? QStringLiteral("normallyOpen")
  802. : QStringLiteral("normallyClosed");
  803. }
  804. // 将工程文件中的触点模式字符串转换为枚举
  805. bool parseContactMode(
  806. const std::string &value, ContactMode *mode, ParseState *state)
  807. {
  808. if (value == "normallyOpen")
  809. {
  810. *mode = ContactMode::NormallyOpen;
  811. }
  812. else if (value == "normallyClosed")
  813. {
  814. *mode = ContactMode::NormallyClosed;
  815. }
  816. else
  817. {
  818. return state->fail(
  819. ProjectStorageError::InvalidField,
  820. "不支持的触点模式:" + value);
  821. }
  822. return true;
  823. }
  824. QString edgeModeName(EdgeMode mode)
  825. {
  826. return mode == EdgeMode::Rising
  827. ? QStringLiteral("rising")
  828. : QStringLiteral("falling");
  829. }
  830. bool parseEdgeMode(
  831. const std::string &value, EdgeMode *mode, ParseState *state)
  832. {
  833. if (value == "rising")
  834. {
  835. *mode = EdgeMode::Rising;
  836. }
  837. else if (value == "falling")
  838. {
  839. *mode = EdgeMode::Falling;
  840. }
  841. else
  842. {
  843. return state->fail(
  844. ProjectStorageError::InvalidField,
  845. "不支持的边沿模式:" + value);
  846. }
  847. return true;
  848. }
  849. // 将线圈模式枚举转换为工程文件中的稳定字符串
  850. QString coilModeName(CoilMode mode)
  851. {
  852. switch (mode)
  853. {
  854. case CoilMode::Normal:
  855. {
  856. return QStringLiteral("normal");
  857. }
  858. case CoilMode::Set:
  859. {
  860. return QStringLiteral("set");
  861. }
  862. case CoilMode::Reset:
  863. {
  864. return QStringLiteral("reset");
  865. }
  866. default:
  867. {
  868. return {};
  869. }
  870. }
  871. }
  872. // 将工程文件中的线圈模式字符串转换为枚举
  873. bool parseCoilMode(const std::string &value, CoilMode *mode, ParseState *state)
  874. {
  875. if (value == "normal")
  876. {
  877. *mode = CoilMode::Normal;
  878. }
  879. else if (value == "set")
  880. {
  881. *mode = CoilMode::Set;
  882. }
  883. else if (value == "reset")
  884. {
  885. *mode = CoilMode::Reset;
  886. }
  887. else
  888. {
  889. return state->fail(
  890. ProjectStorageError::InvalidField,
  891. "不支持的线圈模式:" + value);
  892. }
  893. return true;
  894. }
  895. // 将比较运算符枚举转换为工程文件中的稳定字符串
  896. QString comparisonName(ComparisonOperator comparison)
  897. {
  898. switch (comparison)
  899. {
  900. case ComparisonOperator::Equal:
  901. {
  902. return QStringLiteral("equal");
  903. }
  904. case ComparisonOperator::NotEqual:
  905. {
  906. return QStringLiteral("notEqual");
  907. }
  908. case ComparisonOperator::LessThan:
  909. {
  910. return QStringLiteral("lessThan");
  911. }
  912. case ComparisonOperator::LessThanOrEqual:
  913. {
  914. return QStringLiteral("lessThanOrEqual");
  915. }
  916. case ComparisonOperator::GreaterThan:
  917. {
  918. return QStringLiteral("greaterThan");
  919. }
  920. case ComparisonOperator::GreaterThanOrEqual:
  921. {
  922. return QStringLiteral("greaterThanOrEqual");
  923. }
  924. default:
  925. {
  926. return {};
  927. }
  928. }
  929. }
  930. // 将工程文件中的比较运算符字符串转换为枚举
  931. bool parseComparison(
  932. const std::string &value,
  933. ComparisonOperator *comparison,
  934. ParseState *state)
  935. {
  936. if (value == "equal")
  937. {
  938. *comparison = ComparisonOperator::Equal;
  939. }
  940. else if (value == "notEqual")
  941. {
  942. *comparison = ComparisonOperator::NotEqual;
  943. }
  944. else if (value == "lessThan")
  945. {
  946. *comparison = ComparisonOperator::LessThan;
  947. }
  948. else if (value == "lessThanOrEqual")
  949. {
  950. *comparison = ComparisonOperator::LessThanOrEqual;
  951. }
  952. else if (value == "greaterThan")
  953. {
  954. *comparison = ComparisonOperator::GreaterThan;
  955. }
  956. else if (value == "greaterThanOrEqual")
  957. {
  958. *comparison = ComparisonOperator::GreaterThanOrEqual;
  959. }
  960. else
  961. {
  962. return state->fail(
  963. ProjectStorageError::InvalidField,
  964. "不支持的比较运算符:" + value);
  965. }
  966. return true;
  967. }
  968. // 将触点节点配置序列化,并写入用于反序列化分派的类型标记
  969. QJsonObject serializeNodeConfig(const ContactNodeConfig &config)
  970. {
  971. QJsonObject object;
  972. object.insert(QStringLiteral("type"), QStringLiteral("contact"));
  973. object.insert(QStringLiteral("address"), serializeAddress(config.address));
  974. object.insert(QStringLiteral("mode"), contactModeName(config.mode));
  975. return object;
  976. }
  977. QJsonObject serializeNodeConfig(const EdgeContactNodeConfig &config)
  978. {
  979. QJsonObject object;
  980. object.insert(QStringLiteral("type"), QStringLiteral("edgeContact"));
  981. object.insert(QStringLiteral("address"), serializeAddress(config.address));
  982. object.insert(QStringLiteral("mode"), edgeModeName(config.mode));
  983. return object;
  984. }
  985. // 将线圈节点配置序列化,并写入用于反序列化分派的类型标记
  986. QJsonObject serializeNodeConfig(const CoilNodeConfig &config)
  987. {
  988. QJsonObject object;
  989. object.insert(QStringLiteral("type"), QStringLiteral("coil"));
  990. object.insert(QStringLiteral("address"), serializeAddress(config.address));
  991. object.insert(QStringLiteral("mode"), coilModeName(config.mode));
  992. return object;
  993. }
  994. // 将数值比较节点配置序列化,并保留有符号 16 位比较常量
  995. QJsonObject serializeNodeConfig(const CompareNodeConfig &config)
  996. {
  997. QJsonObject object;
  998. object.insert(QStringLiteral("type"), QStringLiteral("compare"));
  999. object.insert(QStringLiteral("address"), serializeAddress(config.address));
  1000. object.insert(QStringLiteral("comparison"), comparisonName(config.comparison));
  1001. object.insert(QStringLiteral("value"), config.value);
  1002. return object;
  1003. }
  1004. QJsonObject serializeNodeConfig(const MoveNodeConfig &config)
  1005. {
  1006. QJsonObject object;
  1007. object.insert(QStringLiteral("type"), QStringLiteral("move"));
  1008. object.insert(QStringLiteral("source"), serializeWordOperand(config.source));
  1009. object.insert(QStringLiteral("destination"), serializeAddress(config.destination));
  1010. return object;
  1011. }
  1012. QString arithmeticOperationName(ArithmeticOperation operation)
  1013. {
  1014. return operation == ArithmeticOperation::Add
  1015. ? QStringLiteral("add") : QStringLiteral("subtract");
  1016. }
  1017. QJsonObject serializeNodeConfig(const ArithmeticNodeConfig &config)
  1018. {
  1019. QJsonObject object;
  1020. object.insert(QStringLiteral("type"), QStringLiteral("arithmetic"));
  1021. object.insert(QStringLiteral("operation"), arithmeticOperationName(config.operation));
  1022. object.insert(QStringLiteral("left"), serializeWordOperand(config.left));
  1023. object.insert(QStringLiteral("right"), serializeWordOperand(config.right));
  1024. object.insert(QStringLiteral("destination"), serializeAddress(config.destination));
  1025. return object;
  1026. }
  1027. // 将逻辑节点序列化,并根据 variant 中的实际配置类型选择对应重载
  1028. QJsonObject serializeLogicNode(const LogicNode &node)
  1029. {
  1030. QJsonObject object;
  1031. object.insert(QStringLiteral("id"), fromUtf8(node.id));
  1032. object.insert(QStringLiteral("configured"), node.configured);
  1033. // std::visit 将不同节点配置统一转换为 config JSON 对象
  1034. object.insert(
  1035. QStringLiteral("config"),
  1036. std::visit(
  1037. [](const auto &config)
  1038. {
  1039. return serializeNodeConfig(config);
  1040. },
  1041. node.config));
  1042. return object;
  1043. }
  1044. // 根据 type 字段解析具体节点配置,并写入 LogicNodeConfig 变体
  1045. bool parseNodeConfig(
  1046. const QJsonObject &object,
  1047. const std::string &context,
  1048. LogicNodeConfig *config,
  1049. ParseState *state)
  1050. {
  1051. std::string type;
  1052. if (!readString(object, "type", context, &type, state))
  1053. {
  1054. return false;
  1055. }
  1056. if (type == "move")
  1057. {
  1058. QJsonObject source_object;
  1059. QJsonObject destination_object;
  1060. WordOperand source;
  1061. RegisterAddress destination{RegisterArea::D, 0};
  1062. if (!readObject(object, "source", context, &source_object, state)
  1063. || !parseWordOperand(source_object, context + ".source", &source, state)
  1064. || !readObject(object, "destination", context, &destination_object, state)
  1065. || !parseAddress(
  1066. destination_object,
  1067. context + ".destination",
  1068. &destination,
  1069. state))
  1070. {
  1071. return false;
  1072. }
  1073. *config = MoveNodeConfig{source, destination};
  1074. return true;
  1075. }
  1076. if (type == "arithmetic")
  1077. {
  1078. std::string operation_text;
  1079. QJsonObject left_object;
  1080. QJsonObject right_object;
  1081. QJsonObject destination_object;
  1082. WordOperand left;
  1083. WordOperand right;
  1084. RegisterAddress destination{RegisterArea::D, 0};
  1085. ArithmeticOperation operation = ArithmeticOperation::Add;
  1086. if (!readString(object, "operation", context, &operation_text, state))
  1087. {
  1088. return false;
  1089. }
  1090. if (operation_text == "add")
  1091. {
  1092. operation = ArithmeticOperation::Add;
  1093. }
  1094. else if (operation_text == "subtract")
  1095. {
  1096. operation = ArithmeticOperation::Subtract;
  1097. }
  1098. else
  1099. {
  1100. return state->fail(
  1101. ProjectStorageError::InvalidField,
  1102. "不支持的算术运算:" + operation_text);
  1103. }
  1104. if (!readObject(object, "left", context, &left_object, state)
  1105. || !parseWordOperand(left_object, context + ".left", &left, state)
  1106. || !readObject(object, "right", context, &right_object, state)
  1107. || !parseWordOperand(right_object, context + ".right", &right, state)
  1108. || !readObject(object, "destination", context, &destination_object, state)
  1109. || !parseAddress(
  1110. destination_object,
  1111. context + ".destination",
  1112. &destination,
  1113. state))
  1114. {
  1115. return false;
  1116. }
  1117. *config = ArithmeticNodeConfig{operation, left, right, destination};
  1118. return true;
  1119. }
  1120. if (type != "contact" && type != "edgeContact"
  1121. && type != "coil" && type != "compare")
  1122. {
  1123. return state->fail(
  1124. ProjectStorageError::InvalidField,
  1125. "不支持的逻辑节点类型:" + type);
  1126. }
  1127. QJsonObject address_object;
  1128. if (!readObject(object, "address", context, &address_object, state))
  1129. {
  1130. return false;
  1131. }
  1132. RegisterAddress address{RegisterArea::M, 0};
  1133. if (!parseAddress(address_object, context + ".address", &address, state))
  1134. {
  1135. return false;
  1136. }
  1137. // 每种节点只读取自身需要的字段,避免无关配置进入领域模型
  1138. if (type == "contact")
  1139. {
  1140. std::string mode_text;
  1141. ContactMode mode = ContactMode::NormallyOpen;
  1142. if (!readString(object, "mode", context, &mode_text, state)
  1143. || !parseContactMode(mode_text, &mode, state))
  1144. {
  1145. return false;
  1146. }
  1147. *config = ContactNodeConfig{address, mode};
  1148. return true;
  1149. }
  1150. if (type == "edgeContact")
  1151. {
  1152. std::string mode_text;
  1153. EdgeMode mode = EdgeMode::Rising;
  1154. if (!readString(object, "mode", context, &mode_text, state)
  1155. || !parseEdgeMode(mode_text, &mode, state))
  1156. {
  1157. return false;
  1158. }
  1159. *config = EdgeContactNodeConfig{address, mode};
  1160. return true;
  1161. }
  1162. if (type == "coil")
  1163. {
  1164. std::string mode_text;
  1165. CoilMode mode = CoilMode::Normal;
  1166. if (!readString(object, "mode", context, &mode_text, state)
  1167. || !parseCoilMode(mode_text, &mode, state))
  1168. {
  1169. return false;
  1170. }
  1171. *config = CoilNodeConfig{address, mode};
  1172. return true;
  1173. }
  1174. if (type == "compare")
  1175. {
  1176. std::string comparison_text;
  1177. int value = 0;
  1178. ComparisonOperator comparison = ComparisonOperator::Equal;
  1179. if (!readString(object, "comparison", context, &comparison_text, state)
  1180. || !readInt(
  1181. object,
  1182. "value",
  1183. context,
  1184. std::numeric_limits<std::int16_t>::min(),
  1185. std::numeric_limits<std::int16_t>::max(),
  1186. &value,
  1187. state)
  1188. || !parseComparison(comparison_text, &comparison, state))
  1189. {
  1190. return false;
  1191. }
  1192. // 先按 int 校验范围,再安全收窄为领域模型要求的 int16_t
  1193. *config = CompareNodeConfig{
  1194. address, comparison, static_cast<std::int16_t>(value)};
  1195. return true;
  1196. }
  1197. return state->fail(ProjectStorageError::InvalidField, "逻辑节点类型解析失败");
  1198. }
  1199. // 解析逻辑节点标识及其多态配置
  1200. bool parseLogicNode(
  1201. const QJsonObject &object,
  1202. const std::string &context,
  1203. LogicNode *node,
  1204. ParseState *state)
  1205. {
  1206. QJsonObject config;
  1207. if (!readString(
  1208. object, "id", context, &node->id, state,
  1209. ProjectLimits::kMaximumIdBytes)
  1210. || !readBool(object, "configured", context, &node->configured, state)
  1211. || !readObject(object, "config", context, &config, state)
  1212. || !parseNodeConfig(config, context + ".config", &node->config, state))
  1213. {
  1214. return false;
  1215. }
  1216. return true;
  1217. }
  1218. QString expressionKindText(ConditionExpressionKind kind)
  1219. {
  1220. switch (kind)
  1221. {
  1222. case ConditionExpressionKind::Node:
  1223. return QStringLiteral("node");
  1224. case ConditionExpressionKind::Wire:
  1225. return QStringLiteral("wire");
  1226. case ConditionExpressionKind::Gap:
  1227. return QStringLiteral("gap");
  1228. case ConditionExpressionKind::Series:
  1229. return QStringLiteral("series");
  1230. case ConditionExpressionKind::Parallel:
  1231. return QStringLiteral("parallel");
  1232. }
  1233. return {};
  1234. }
  1235. QJsonObject serializeConditionExpression(const ConditionExpression &expression)
  1236. {
  1237. QJsonObject object;
  1238. object.insert(QStringLiteral("id"), fromUtf8(expression.id));
  1239. object.insert(QStringLiteral("kind"), expressionKindText(expression.kind));
  1240. if (expression.kind == ConditionExpressionKind::Node)
  1241. {
  1242. object.insert(QStringLiteral("node"), serializeLogicNode(*expression.node));
  1243. }
  1244. else if (expression.kind == ConditionExpressionKind::Wire)
  1245. {
  1246. object.insert(QStringLiteral("columnSpan"), expression.wire->columnSpan);
  1247. }
  1248. else if (expression.kind == ConditionExpressionKind::Gap)
  1249. {
  1250. object.insert(QStringLiteral("columnSpan"), expression.gap->columnSpan);
  1251. }
  1252. else
  1253. {
  1254. QJsonArray children;
  1255. for (const ConditionExpression &child : expression.children)
  1256. {
  1257. children.append(serializeConditionExpression(child));
  1258. }
  1259. object.insert(QStringLiteral("children"), children);
  1260. }
  1261. return object;
  1262. }
  1263. bool parseConditionExpression(
  1264. const QJsonObject &object,
  1265. const std::string &context,
  1266. ConditionExpression *expression,
  1267. ParseState *state,
  1268. std::size_t depth,
  1269. std::size_t *node_count)
  1270. {
  1271. if (depth > ProjectLimits::kMaximumExpressionDepth)
  1272. {
  1273. return state->fail(
  1274. ProjectStorageError::InvalidField,
  1275. context + " 的嵌套深度超过 12 层");
  1276. }
  1277. ++*node_count;
  1278. if (*node_count > ProjectLimits::kMaximumExpressionNodesPerRung)
  1279. {
  1280. return state->fail(
  1281. ProjectStorageError::InvalidField,
  1282. context + " 的表达式节点总数超过 1024 个");
  1283. }
  1284. std::string kind;
  1285. if (!readString(
  1286. object, "id", context, &expression->id, state,
  1287. ProjectLimits::kMaximumIdBytes)
  1288. || !readString(object, "kind", context, &kind, state))
  1289. {
  1290. return false;
  1291. }
  1292. if (kind == "node")
  1293. {
  1294. QJsonObject node;
  1295. if (!readObject(object, "node", context, &node, state))
  1296. {
  1297. return false;
  1298. }
  1299. LogicNode parsed_node;
  1300. if (!parseLogicNode(node, context + ".node", &parsed_node, state))
  1301. {
  1302. return false;
  1303. }
  1304. expression->kind = ConditionExpressionKind::Node;
  1305. expression->node = std::move(parsed_node);
  1306. return true;
  1307. }
  1308. if (kind == "wire")
  1309. {
  1310. int column_span = 0;
  1311. if (!readInt(
  1312. object,
  1313. "columnSpan",
  1314. context,
  1315. WireSegment::kMinimumColumnSpan,
  1316. WireSegment::kMaximumColumnSpan,
  1317. &column_span,
  1318. state))
  1319. {
  1320. return false;
  1321. }
  1322. expression->kind = ConditionExpressionKind::Wire;
  1323. expression->wire = WireSegment{column_span};
  1324. return true;
  1325. }
  1326. if (kind == "gap")
  1327. {
  1328. int column_span = 0;
  1329. if (!readInt(
  1330. object,
  1331. "columnSpan",
  1332. context,
  1333. GapSegment::kMinimumColumnSpan,
  1334. GapSegment::kMaximumColumnSpan,
  1335. &column_span,
  1336. state))
  1337. {
  1338. return false;
  1339. }
  1340. expression->kind = ConditionExpressionKind::Gap;
  1341. expression->gap = GapSegment{column_span};
  1342. return true;
  1343. }
  1344. if (kind != "series" && kind != "parallel")
  1345. {
  1346. return state->fail(
  1347. ProjectStorageError::InvalidField,
  1348. context + ".kind 必须是 node、wire、gap、series 或 parallel");
  1349. }
  1350. QJsonArray children;
  1351. if (!readArray(
  1352. object, "children", context, &children, state,
  1353. static_cast<int>(ProjectLimits::kMaximumExpressionChildren)))
  1354. {
  1355. return false;
  1356. }
  1357. expression->kind = kind == "series"
  1358. ? ConditionExpressionKind::Series : ConditionExpressionKind::Parallel;
  1359. expression->children.reserve(static_cast<std::size_t>(children.size()));
  1360. for (int index = 0; index < children.size(); ++index)
  1361. {
  1362. if (!children.at(index).isObject())
  1363. {
  1364. return state->fail(
  1365. ProjectStorageError::InvalidField,
  1366. context + ".children 的元素必须是对象");
  1367. }
  1368. ConditionExpression child;
  1369. if (!parseConditionExpression(
  1370. children.at(index).toObject(),
  1371. context + ".children[" + std::to_string(index) + ']',
  1372. &child,
  1373. state,
  1374. depth + 1U,
  1375. node_count))
  1376. {
  1377. return false;
  1378. }
  1379. expression->children.push_back(std::move(child));
  1380. }
  1381. return true;
  1382. }
  1383. QJsonObject serializeLadderRung(const LadderRung &rung)
  1384. {
  1385. QJsonObject object;
  1386. object.insert(QStringLiteral("id"), fromUtf8(rung.id));
  1387. object.insert(QStringLiteral("name"), fromUtf8(rung.name));
  1388. object.insert(QStringLiteral("comment"), fromUtf8(rung.comment));
  1389. object.insert(
  1390. QStringLiteral("condition"),
  1391. rung.condition.has_value()
  1392. ? QJsonValue(serializeConditionExpression(*rung.condition))
  1393. : QJsonValue(QJsonValue::Null));
  1394. object.insert(
  1395. QStringLiteral("output"),
  1396. rung.output.has_value() ? QJsonValue(serializeLogicNode(*rung.output))
  1397. : QJsonValue(QJsonValue::Null));
  1398. return object;
  1399. }
  1400. bool parseLadderRung(
  1401. const QJsonObject &object,
  1402. const std::string &context,
  1403. LadderRung *rung,
  1404. ParseState *state)
  1405. {
  1406. QJsonValue condition;
  1407. QJsonValue output;
  1408. if (!readString(
  1409. object, "id", context, &rung->id, state,
  1410. ProjectLimits::kMaximumIdBytes)
  1411. || !readString(object, "name", context, &rung->name, state)
  1412. || !readString(object, "comment", context, &rung->comment, state)
  1413. || !readValue(object, "output", context, &output, state))
  1414. {
  1415. return false;
  1416. }
  1417. if (!readValue(object, "condition", context, &condition, state))
  1418. {
  1419. return false;
  1420. }
  1421. if (condition.isNull())
  1422. {
  1423. rung->condition.reset();
  1424. }
  1425. else if (!condition.isObject())
  1426. {
  1427. return state->fail(
  1428. ProjectStorageError::InvalidField,
  1429. context + ".condition 必须是对象或 null");
  1430. }
  1431. else
  1432. {
  1433. ConditionExpression parsed_condition;
  1434. std::size_t expression_node_count = 0U;
  1435. if (!parseConditionExpression(
  1436. condition.toObject(), context + ".condition", &parsed_condition,
  1437. state, 1U, &expression_node_count))
  1438. {
  1439. return false;
  1440. }
  1441. rung->condition = std::move(parsed_condition);
  1442. }
  1443. if (output.isNull())
  1444. {
  1445. rung->output.reset();
  1446. return true;
  1447. }
  1448. if (!output.isObject())
  1449. {
  1450. return state->fail(
  1451. ProjectStorageError::InvalidField,
  1452. context + ".output 必须是对象或 null");
  1453. }
  1454. LogicNode node;
  1455. if (!parseLogicNode(output.toObject(), context + ".output", &node, state))
  1456. {
  1457. return false;
  1458. }
  1459. rung->output = std::move(node);
  1460. return true;
  1461. }
  1462. QJsonObject serializeControlLogic(const ControlLogic &logic)
  1463. {
  1464. QJsonArray rungs;
  1465. for (const LadderRung &rung : logic.rungs)
  1466. {
  1467. rungs.append(serializeLadderRung(rung));
  1468. }
  1469. QJsonObject object;
  1470. object.insert(QStringLiteral("id"), fromUtf8(logic.id));
  1471. object.insert(QStringLiteral("name"), fromUtf8(logic.name));
  1472. object.insert(QStringLiteral("enabled"), logic.enabled);
  1473. object.insert(QStringLiteral("rungs"), rungs);
  1474. return object;
  1475. }
  1476. bool parseControlLogic(
  1477. const QJsonObject &object,
  1478. const std::string &context,
  1479. const ProjectLimitSettings &limits,
  1480. ControlLogic *logic,
  1481. ParseState *state)
  1482. {
  1483. QJsonArray rungs;
  1484. if (!readString(
  1485. object, "id", context, &logic->id, state,
  1486. ProjectLimits::kMaximumIdBytes)
  1487. || !readString(object, "name", context, &logic->name, state)
  1488. || !readBool(object, "enabled", context, &logic->enabled, state)
  1489. || !readArray(
  1490. object, "rungs", context, &rungs, state,
  1491. static_cast<int>(limits.maximumRungsPerLogic)))
  1492. {
  1493. return false;
  1494. }
  1495. logic->rungs.reserve(static_cast<std::size_t>(rungs.size()));
  1496. for (int index = 0; index < rungs.size(); ++index)
  1497. {
  1498. if (!rungs.at(index).isObject())
  1499. {
  1500. return state->fail(
  1501. ProjectStorageError::InvalidField,
  1502. context + ".rungs 的元素必须是对象");
  1503. }
  1504. LadderRung rung;
  1505. if (!parseLadderRung(
  1506. rungs.at(index).toObject(),
  1507. context + ".rungs[" + std::to_string(index) + ']',
  1508. &rung,
  1509. state))
  1510. {
  1511. return false;
  1512. }
  1513. logic->rungs.push_back(std::move(rung));
  1514. }
  1515. return true;
  1516. }
  1517. // 将完整领域工程聚合为工程文件的顶层 JSON 对象
  1518. QJsonObject serializeProject(const Project &project)
  1519. {
  1520. QJsonArray pages;
  1521. for (const HmiPage &page : project.hmiPages)
  1522. {
  1523. pages.append(serializeHmiPage(page));
  1524. }
  1525. QJsonArray logics;
  1526. for (const ControlLogic &logic : project.controlLogics)
  1527. {
  1528. logics.append(serializeControlLogic(logic));
  1529. }
  1530. QJsonArray alarms;
  1531. for (const AlarmDefinition &definition : project.alarmDefinitions)
  1532. {
  1533. alarms.append(serializeAlarmDefinition(definition));
  1534. }
  1535. QJsonArray register_comments;
  1536. for (const RegisterComment &comment : project.registerComments)
  1537. {
  1538. register_comments.append(serializeRegisterComment(comment));
  1539. }
  1540. QJsonObject object;
  1541. object.insert(QStringLiteral("formatVersion"), fromUtf8(project.metadata.formatVersion));
  1542. object.insert(QStringLiteral("id"), fromUtf8(project.metadata.id));
  1543. object.insert(QStringLiteral("name"), fromUtf8(project.metadata.name));
  1544. object.insert(QStringLiteral("hmiPages"), pages);
  1545. object.insert(QStringLiteral("initialHmiPageId"),
  1546. fromUtf8(project.initialHmiPageId));
  1547. object.insert(QStringLiteral("alarmDefinitions"), alarms);
  1548. object.insert(QStringLiteral("registerComments"), register_comments);
  1549. object.insert(QStringLiteral("controlLogics"), logics);
  1550. return object;
  1551. }
  1552. // 从顶层 JSON 对象解析完整工程,并在解析子对象前检查格式版本
  1553. bool parseProject(
  1554. const QJsonObject &object,
  1555. const ProjectLimitSettings &limits,
  1556. Project *project,
  1557. ParseState *state)
  1558. {
  1559. QJsonArray pages;
  1560. QJsonArray alarms;
  1561. QJsonArray register_comments;
  1562. QJsonArray logics;
  1563. if (!readString(
  1564. object,
  1565. "formatVersion",
  1566. "project",
  1567. &project->metadata.formatVersion,
  1568. state,
  1569. ProjectLimits::kMaximumIdBytes))
  1570. {
  1571. return false;
  1572. }
  1573. // 不尝试猜测其他版本的结构,避免按当前格式错误解释数据
  1574. if (project->metadata.formatVersion != kCurrentFormatVersion)
  1575. {
  1576. return state->fail(
  1577. ProjectStorageError::UnsupportedVersion,
  1578. "不支持的工程格式版本:" + project->metadata.formatVersion);
  1579. }
  1580. if (!readString(
  1581. object, "id", "project", &project->metadata.id, state,
  1582. ProjectLimits::kMaximumIdBytes)
  1583. || !readString(object, "name", "project", &project->metadata.name, state)
  1584. || !readArray(
  1585. object, "hmiPages", "project", &pages, state,
  1586. static_cast<int>(limits.maximumHmiPages))
  1587. || !readString(
  1588. object, "initialHmiPageId", "project",
  1589. &project->initialHmiPageId, state,
  1590. ProjectLimits::kMaximumIdBytes)
  1591. || !readArray(
  1592. object, "alarmDefinitions", "project", &alarms, state,
  1593. static_cast<int>(limits.maximumAlarmDefinitions))
  1594. || !readArray(
  1595. object, "registerComments", "project", &register_comments, state,
  1596. static_cast<int>(ProjectLimits::kMaximumRegisterComments))
  1597. || !readArray(
  1598. object, "controlLogics", "project", &logics, state,
  1599. static_cast<int>(limits.maximumControlLogics)))
  1600. {
  1601. return false;
  1602. }
  1603. // 逐层解析时持续传递字段路径,任何失败都保留最初的具体位置
  1604. project->hmiPages.reserve(static_cast<std::size_t>(pages.size()));
  1605. for (int index = 0; index < pages.size(); ++index)
  1606. {
  1607. if (!pages.at(index).isObject())
  1608. {
  1609. return state->fail(
  1610. ProjectStorageError::InvalidField,
  1611. "project.hmiPages 的元素必须是对象");
  1612. }
  1613. HmiPage page;
  1614. if (!parseHmiPage(
  1615. pages.at(index).toObject(),
  1616. "project.hmiPages[" + std::to_string(index) + ']',
  1617. limits,
  1618. &page,
  1619. state))
  1620. {
  1621. return false;
  1622. }
  1623. project->hmiPages.push_back(std::move(page));
  1624. }
  1625. project->alarmDefinitions.reserve(static_cast<std::size_t>(alarms.size()));
  1626. for (int index = 0; index < alarms.size(); ++index)
  1627. {
  1628. if (!alarms.at(index).isObject())
  1629. {
  1630. return state->fail(
  1631. ProjectStorageError::InvalidField,
  1632. "project.alarmDefinitions 的元素必须是对象");
  1633. }
  1634. AlarmDefinition definition;
  1635. if (!parseAlarmDefinition(
  1636. alarms.at(index).toObject(),
  1637. "project.alarmDefinitions[" + std::to_string(index) + ']',
  1638. &definition,
  1639. state))
  1640. {
  1641. return false;
  1642. }
  1643. project->alarmDefinitions.push_back(std::move(definition));
  1644. }
  1645. project->registerComments.reserve(static_cast<std::size_t>(register_comments.size()));
  1646. for (int index = 0; index < register_comments.size(); ++index)
  1647. {
  1648. if (!register_comments.at(index).isObject())
  1649. {
  1650. return state->fail(
  1651. ProjectStorageError::InvalidField,
  1652. "project.registerComments 的元素必须是对象");
  1653. }
  1654. RegisterComment comment;
  1655. if (!parseRegisterComment(
  1656. register_comments.at(index).toObject(),
  1657. "project.registerComments[" + std::to_string(index) + ']',
  1658. &comment,
  1659. state))
  1660. {
  1661. return false;
  1662. }
  1663. project->registerComments.push_back(std::move(comment));
  1664. }
  1665. project->controlLogics.reserve(static_cast<std::size_t>(logics.size()));
  1666. for (int index = 0; index < logics.size(); ++index)
  1667. {
  1668. if (!logics.at(index).isObject())
  1669. {
  1670. return state->fail(
  1671. ProjectStorageError::InvalidField,
  1672. "project.controlLogics 的元素必须是对象");
  1673. }
  1674. ControlLogic logic;
  1675. if (!parseControlLogic(
  1676. logics.at(index).toObject(),
  1677. "project.controlLogics[" + std::to_string(index) + ']',
  1678. limits,
  1679. &logic,
  1680. state))
  1681. {
  1682. return false;
  1683. }
  1684. project->controlLogics.push_back(std::move(logic));
  1685. }
  1686. return true;
  1687. }
  1688. // 将 Qt 文件错误转换为领域层统一的工程保存失败结果
  1689. ProjectSaveResult saveFailure(
  1690. ProjectStorageError error, const QString &message)
  1691. {
  1692. return {false, error, toUtf8(message)};
  1693. }
  1694. } // namespace
  1695. JsonProjectStorage::JsonProjectStorage(
  1696. const ProjectLimitSettings &project_limits)
  1697. : project_limits_(project_limits)
  1698. {
  1699. }
  1700. // 校验工程后将其序列化,并通过 QSaveFile 原子写入目标文件
  1701. ProjectSaveResult JsonProjectStorage::save(
  1702. const Project &project, const std::string &file_path)
  1703. {
  1704. // 写文件前先执行领域校验,防止持久化内部关系不合法的工程
  1705. std::string validation_error;
  1706. if (!project.validate(project_limits_, &validation_error))
  1707. {
  1708. return {false, ProjectStorageError::InvalidProject, validation_error};
  1709. }
  1710. if (project.metadata.formatVersion != kCurrentFormatVersion)
  1711. {
  1712. return {false,
  1713. ProjectStorageError::UnsupportedVersion,
  1714. "不支持的工程格式版本:" + project.metadata.formatVersion};
  1715. }
  1716. // QSaveFile 先写临时文件,仅在 commit 成功后替换目标文件
  1717. QSaveFile file(fromUtf8(file_path));
  1718. if (!file.open(QIODevice::WriteOnly))
  1719. {
  1720. return saveFailure(ProjectStorageError::FileOpenFailed, file.errorString());
  1721. }
  1722. const QByteArray data = QJsonDocument(serializeProject(project)).toJson(
  1723. QJsonDocument::Indented);
  1724. if (static_cast<std::size_t>(data.size())
  1725. > ProjectLimits::kMaximumProjectFileBytes)
  1726. {
  1727. file.cancelWriting();
  1728. return {false,
  1729. ProjectStorageError::InvalidProject,
  1730. "工程 JSON 文件不能超过 16 MiB"};
  1731. }
  1732. // 短写入也视为失败,并取消临时文件提交
  1733. if (file.write(data) != data.size())
  1734. {
  1735. file.cancelWriting();
  1736. return saveFailure(ProjectStorageError::FileWriteFailed, file.errorString());
  1737. }
  1738. if (!file.commit())
  1739. {
  1740. return saveFailure(ProjectStorageError::FileWriteFailed, file.errorString());
  1741. }
  1742. return {true, ProjectStorageError::None, {}};
  1743. }
  1744. // 读取并解析工程文件,全部校验通过后才返回新的领域工程
  1745. ProjectLoadResult JsonProjectStorage::load(const std::string &file_path)
  1746. {
  1747. QFile file(fromUtf8(file_path));
  1748. if (!file.open(QIODevice::ReadOnly))
  1749. {
  1750. return {false,
  1751. {},
  1752. ProjectStorageError::FileOpenFailed,
  1753. toUtf8(file.errorString())};
  1754. }
  1755. if (file.size() < 0
  1756. || static_cast<quint64>(file.size())
  1757. > static_cast<quint64>(ProjectLimits::kMaximumProjectFileBytes))
  1758. {
  1759. return {false,
  1760. {},
  1761. ProjectStorageError::InvalidJson,
  1762. "工程 JSON 文件不能超过 16 MiB"};
  1763. }
  1764. // 最多只读到上限加 1 字节,避免文件属性检查后文件变大导致无限制分配
  1765. const QByteArray data = file.read(
  1766. static_cast<qint64>(ProjectLimits::kMaximumProjectFileBytes) + 1);
  1767. if (file.error() != QFileDevice::NoError)
  1768. {
  1769. return {false,
  1770. {},
  1771. ProjectStorageError::FileReadFailed,
  1772. toUtf8(file.errorString())};
  1773. }
  1774. if (static_cast<std::size_t>(data.size())
  1775. > ProjectLimits::kMaximumProjectFileBytes)
  1776. {
  1777. return {false,
  1778. {},
  1779. ProjectStorageError::InvalidJson,
  1780. "工程 JSON 文件不能超过 16 MiB"};
  1781. }
  1782. // 顶层必须是 JSON 对象,数组或标量不能表示完整工程
  1783. QJsonParseError parse_error;
  1784. const QJsonDocument document = QJsonDocument::fromJson(data, &parse_error);
  1785. if (parse_error.error != QJsonParseError::NoError || !document.isObject())
  1786. {
  1787. const QString message = parse_error.error == QJsonParseError::NoError
  1788. ? QStringLiteral("工程 JSON 根节点必须是对象")
  1789. : parse_error.errorString();
  1790. return {false,
  1791. {},
  1792. ProjectStorageError::InvalidJson,
  1793. toUtf8(message)};
  1794. }
  1795. // 先在局部对象中完成结构解析,失败时不会暴露半成品工程
  1796. Project project;
  1797. ParseState state;
  1798. if (!parseProject(document.object(), project_limits_, &project, &state))
  1799. {
  1800. return {false, {}, state.error, state.message};
  1801. }
  1802. // JSON 字段合法不代表业务关系合法,还需执行领域层整体校验
  1803. std::string validation_error;
  1804. if (!project.validate(project_limits_, &validation_error))
  1805. {
  1806. return {false,
  1807. {},
  1808. ProjectStorageError::InvalidProject,
  1809. validation_error};
  1810. }
  1811. return {true, std::move(project), ProjectStorageError::None, {}};
  1812. }