From 898aaf5e6a87fda2c08ce814d8f012721ef678f9 Mon Sep 17 00:00:00 2001 From: suyu <1643689728@qq.com> Date: Thu, 20 Aug 2026 14:37:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=99=90=E5=88=B6=E6=A2=AF=E5=BD=A2?= =?UTF-8?q?=E5=9B=BE=E6=B3=A8=E9=87=8A=E4=B8=BA=E5=8D=95=E8=A1=8C=E6=96=87?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/domain/control_logic_model.cpp | 21 +++++++-- app/src/domain/project_limits.h | 2 + app/src/domain/project_model.cpp | 15 ++++++- app/src/services/logic_editor_service.cpp | 16 ++++++- app/src/ui/logic_editor_widget.cpp | 4 +- app/src/ui/main_window.cpp | 3 +- app/tests/domain_tests.cpp | 20 +++++++++ app/tests/logic_editor_service_tests.cpp | 10 +++++ app/tests/project_management_tests.cpp | 52 +++++++++++++++++++++++ 9 files changed, 133 insertions(+), 10 deletions(-) diff --git a/app/src/domain/control_logic_model.cpp b/app/src/domain/control_logic_model.cpp index 854a3d2..2d393af 100644 --- a/app/src/domain/control_logic_model.cpp +++ b/app/src/domain/control_logic_model.cpp @@ -17,6 +17,12 @@ void setError(std::string *error, const std::string &message) } } +bool containsLineBreak(const std::string &value) +{ + return value.find('\r') != std::string::npos + || value.find('\n') != std::string::npos; +} + bool validateConfig(const ContactNodeConfig &config, std::string *error) { if (!config.address.isValid() || config.address.area() != RegisterArea::M) @@ -831,10 +837,19 @@ bool LadderRung::validateStructure(std::string *error) const setError(error, "梯形图网络 ID 不能超过 128 个 UTF-8 字节"); return false; } - if (name.size() > ProjectLimits::kMaximumTextBytes - || comment.size() > ProjectLimits::kMaximumTextBytes) + if (name.size() > ProjectLimits::kMaximumTextBytes) + { + setError(error, "梯形图网络名称不能超过 4096 个 UTF-8 字节"); + return false; + } + if (containsLineBreak(comment)) + { + setError(error, "梯形图网络注释只能使用单行文本"); + return false; + } + if (comment.size() > ProjectLimits::kMaximumRungCommentBytes) { - setError(error, "梯形图网络名称和注释不能超过 4096 个 UTF-8 字节"); + setError(error, "梯形图网络注释不能超过 128 个 UTF-8 字节"); return false; } std::vector node_ids; diff --git a/app/src/domain/project_limits.h b/app/src/domain/project_limits.h index 61b7c1c..cd9dd54 100644 --- a/app/src/domain/project_limits.h +++ b/app/src/domain/project_limits.h @@ -27,6 +27,8 @@ constexpr std::size_t kMaximumIdBytes = 128U; constexpr std::size_t kMaximumTextBytes = 4096U; constexpr std::size_t kMaximumPropertyKeyBytes = 128U; constexpr std::size_t kMaximumPropertyValueBytes = 4096U; +constexpr std::size_t kMaximumRegisterCommentBytes = 64U; +constexpr std::size_t kMaximumRungCommentBytes = 128U; constexpr int kMaximumHmiPageWidth = 8192; constexpr int kMaximumHmiPageHeight = 8192; diff --git a/app/src/domain/project_model.cpp b/app/src/domain/project_model.cpp index 3361fab..0ec0a6a 100644 --- a/app/src/domain/project_model.cpp +++ b/app/src/domain/project_model.cpp @@ -64,6 +64,12 @@ bool isBlank(const std::string &value) [](unsigned char character) { return std::isspace(character) != 0; }); } +bool containsLineBreak(const std::string &value) +{ + return value.find('\r') != std::string::npos + || value.find('\n') != std::string::npos; +} + } // namespace bool RegisterComment::validate(std::string *error) const @@ -78,9 +84,14 @@ bool RegisterComment::validate(std::string *error) const setError(error, "软元件注释内容不能为空"); return false; } - if (text.size() > ProjectLimits::kMaximumTextBytes) + if (containsLineBreak(text)) + { + setError(error, "软元件注释只能使用单行文本"); + return false; + } + if (text.size() > ProjectLimits::kMaximumRegisterCommentBytes) { - setError(error, "软元件注释不能超过 4096 个 UTF-8 字节"); + setError(error, "软元件注释不能超过 64 个 UTF-8 字节"); return false; } return true; diff --git a/app/src/services/logic_editor_service.cpp b/app/src/services/logic_editor_service.cpp index 800643e..a1a278e 100644 --- a/app/src/services/logic_editor_service.cpp +++ b/app/src/services/logic_editor_service.cpp @@ -21,6 +21,12 @@ bool isBlank(const std::string &value) [](unsigned char character) { return std::isspace(character) != 0; }); } +bool containsLineBreak(const std::string &value) +{ + return value.find('\r') != std::string::npos + || value.find('\n') != std::string::npos; +} + std::string makeUniqueLogicId(const Project &project) { int suffix = 1; @@ -865,11 +871,17 @@ LogicEditorResult LogicEditorService::updateRungComment( { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } - if (comment.size() > ProjectLimits::kMaximumTextBytes) + if (containsLineBreak(comment)) + { + return failure( + LogicEditorError::InvalidOperation, + "梯形图网络注释只能使用单行文本"); + } + if (comment.size() > ProjectLimits::kMaximumRungCommentBytes) { return failure( LogicEditorError::InvalidOperation, - "梯形图网络注释不能超过 4096 个 UTF-8 字节"); + "梯形图网络注释不能超过 128 个 UTF-8 字节"); } if (rung->comment == comment) { diff --git a/app/src/ui/logic_editor_widget.cpp b/app/src/ui/logic_editor_widget.cpp index 7cc17f4..58add82 100644 --- a/app/src/ui/logic_editor_widget.cpp +++ b/app/src/ui/logic_editor_widget.cpp @@ -79,7 +79,7 @@ void drawRegisterComment( comment, Qt::ElideRight, static_cast(kCellWidth - 8.0)); painter->drawText( QRectF(-kCellWidth / 2.0, top, kCellWidth, 18), - Qt::AlignCenter, + Qt::AlignCenter | Qt::TextSingleLine, visible_comment); } @@ -810,7 +810,7 @@ public: comment_, Qt::ElideRight, static_cast(width_ - 16.0)); painter->drawText( QRectF(kSceneMargin + 8, 24, width_ - 16, 22), - Qt::AlignLeft | Qt::AlignVCenter, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, visible_comment); } painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1)); diff --git a/app/src/ui/main_window.cpp b/app/src/ui/main_window.cpp index 822f517..8ba350c 100644 --- a/app/src/ui/main_window.cpp +++ b/app/src/ui/main_window.cpp @@ -1339,10 +1339,11 @@ void MainWindow::editSelectedRungComment() return; } bool accepted = false; - const QString comment = QInputDialog::getMultiLineText( + const QString comment = QInputDialog::getText( this, tr("网络注释"), tr("说明"), + QLineEdit::Normal, fromUtf8(rung->comment), &accepted); if (!accepted) diff --git a/app/tests/domain_tests.cpp b/app/tests/domain_tests.cpp index 46c4177..9a3c9b0 100644 --- a/app/tests/domain_tests.cpp +++ b/app/tests/domain_tests.cpp @@ -497,9 +497,29 @@ void testTimerAndCommentBoundaries() RegisterComment comment{RegisterAddress{RegisterArea::M, 0}, "启动按钮"}; require(comment.validate(), "a nonblank register comment must be valid"); + comment.text.assign(ProjectLimits::kMaximumRegisterCommentBytes, 'a'); + require(comment.validate(), "a register comment at the byte limit must be valid"); + comment.text.push_back('a'); + require(!comment.validate(), "a register comment above the byte limit must fail"); + comment.text = "启动\n按钮"; + require(!comment.validate(), "a multiline register comment must be rejected"); + comment.text = "启动\r按钮"; + require(!comment.validate(), "a register comment containing CR must be rejected"); comment.text = " \t"; require(!comment.validate(), "a blank register comment must be rejected"); + LadderRung comment_rung; + comment_rung.id = "comment-rung"; + comment_rung.name = "Comment rung"; + comment_rung.comment.assign(ProjectLimits::kMaximumRungCommentBytes, 'a'); + require(comment_rung.validate(), "a rung comment at the byte limit must be valid"); + comment_rung.comment.push_back('a'); + require(!comment_rung.validate(), "a rung comment above the byte limit must fail"); + comment_rung.comment = "第一行\n第二行"; + require(!comment_rung.validate(), "a multiline rung comment must be rejected"); + comment_rung.comment = "第一行\r第二行"; + require(!comment_rung.validate(), "a rung comment containing CR must be rejected"); + Project project = makeValidProject(); project.registerComments = { {RegisterAddress{RegisterArea::M, 0}, "启动按钮"}, diff --git a/app/tests/logic_editor_service_tests.cpp b/app/tests/logic_editor_service_tests.cpp index 5379b55..11f665b 100644 --- a/app/tests/logic_editor_service_tests.cpp +++ b/app/tests/logic_editor_service_tests.cpp @@ -1,4 +1,5 @@ #include "domain/project_storage.h" +#include "domain/project_limits.h" #include "services/logic_editor_service.h" #include "services/project_service.h" @@ -712,6 +713,15 @@ void testEdgeTimerNodesAndRungComments() "the editor must apply TON preset properties"); require(service.updateRungComment(logic_id, rung_id, "延时启动网络").succeeded, "the editor must update a network comment by stable rung id"); + require(!service.updateRungComment( + logic_id, rung_id, "第一行\n第二行").succeeded, + "the editor must reject multiline network comments"); + require(!service.updateRungComment( + logic_id, + rung_id, + std::string(ProjectLimits::kMaximumRungCommentBytes + 1U, 'a')) + .succeeded, + "the editor must reject oversized network comments"); const LadderRung *rung = service.findRung(logic_id, rung_id); require(rung != nullptr && rung->comment == "延时启动网络" diff --git a/app/tests/project_management_tests.cpp b/app/tests/project_management_tests.cpp index 55db3fc..f7d0b4a 100644 --- a/app/tests/project_management_tests.cpp +++ b/app/tests/project_management_tests.cpp @@ -435,6 +435,10 @@ void testExampleProjectRoundTrip() "missing-register-comments.json"); const QString missing_rung_comment_path = directory.filePath( "missing-rung-comment.json"); + const QString multiline_register_comment_path = directory.filePath( + "multiline-register-comment.json"); + const QString multiline_rung_comment_path = directory.filePath( + "multiline-rung-comment.json"); require(service.saveAs(first_path.toStdString()).succeeded, "example project save must succeed"); const QByteArray saved_json = readBytes(first_path); @@ -671,6 +675,41 @@ void testExampleProjectRoundTrip() && missing_result.storageError == ProjectStorageError::MissingField, "the 1.0 schema must require rung comments without migration defaults"); + QJsonObject multiline_register_comment = QJsonDocument::fromJson( + saved_json).object(); + QJsonArray comments = multiline_register_comment.value( + QStringLiteral("registerComments")).toArray(); + QJsonObject first_comment = comments.at(0).toObject(); + first_comment.insert(QStringLiteral("text"), QStringLiteral("第一行\n第二行")); + comments.replace(0, first_comment); + multiline_register_comment.insert(QStringLiteral("registerComments"), comments); + writeText( + multiline_register_comment_path, + QJsonDocument(multiline_register_comment).toJson(QJsonDocument::Compact)); + missing_result = service.load(multiline_register_comment_path.toStdString()); + require(!missing_result.succeeded + && missing_result.storageError == ProjectStorageError::InvalidProject, + "JSON loading must reject multiline register comments"); + + QJsonObject multiline_rung_comment = QJsonDocument::fromJson(saved_json).object(); + QJsonArray multiline_logics = multiline_rung_comment.value( + QStringLiteral("controlLogics")).toArray(); + first_logic = multiline_logics.at(0).toObject(); + first_rungs = first_logic.value(QStringLiteral("rungs")).toArray(); + first_rung = first_rungs.at(0).toObject(); + first_rung.insert(QStringLiteral("comment"), QStringLiteral("第一行\n第二行")); + first_rungs.replace(0, first_rung); + first_logic.insert(QStringLiteral("rungs"), first_rungs); + multiline_logics.replace(0, first_logic); + multiline_rung_comment.insert(QStringLiteral("controlLogics"), multiline_logics); + writeText( + multiline_rung_comment_path, + QJsonDocument(multiline_rung_comment).toJson(QJsonDocument::Compact)); + missing_result = service.load(multiline_rung_comment_path.toStdString()); + require(!missing_result.succeeded + && missing_result.storageError == ProjectStorageError::InvalidProject, + "JSON loading must reject multiline rung comments"); + QJsonObject missing_target = QJsonDocument::fromJson(saved_json).object(); QJsonArray pages = missing_target.value(QStringLiteral("hmiPages")).toArray(); QJsonObject main_page = pages.at(0).toObject(); @@ -887,6 +926,19 @@ void testRegisterCommentService() "setting an existing address must update instead of duplicating it"); require(!service.setComment(RegisterAddress{RegisterArea::M, 4}, " \t").succeeded, "blank register comments must be rejected by the service"); + require(service.setComment( + RegisterAddress{RegisterArea::M, 4}, + std::string(ProjectLimits::kMaximumRegisterCommentBytes, 'a')) + .succeeded, + "a register comment at the byte limit must be accepted"); + require(!service.setComment( + RegisterAddress{RegisterArea::M, 5}, + std::string(ProjectLimits::kMaximumRegisterCommentBytes + 1U, 'a')) + .succeeded, + "an oversized register comment must be rejected by the service"); + require(!service.setComment( + RegisterAddress{RegisterArea::M, 5}, "第一行\n第二行").succeeded, + "a multiline register comment must be rejected by the service"); require(service.removeComment(RegisterAddress{RegisterArea::M, 2}).succeeded && service.findComment(RegisterAddress{RegisterArea::M, 2}) == nullptr, "register comments must be removable");