Просмотр исходного кода

fix: 限制梯形图注释为单行文本

main
suyu 1 месяц назад
Родитель
Сommit
898aaf5e6a
9 измененных файлов: 133 добавлений и 10 удалений
  1. +18
    -3
      app/src/domain/control_logic_model.cpp
  2. +2
    -0
      app/src/domain/project_limits.h
  3. +13
    -2
      app/src/domain/project_model.cpp
  4. +14
    -2
      app/src/services/logic_editor_service.cpp
  5. +2
    -2
      app/src/ui/logic_editor_widget.cpp
  6. +2
    -1
      app/src/ui/main_window.cpp
  7. +20
    -0
      app/tests/domain_tests.cpp
  8. +10
    -0
      app/tests/logic_editor_service_tests.cpp
  9. +52
    -0
      app/tests/project_management_tests.cpp

+ 18
- 3
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) bool validateConfig(const ContactNodeConfig &config, std::string *error)
{ {
if (!config.address.isValid() || config.address.area() != RegisterArea::M) 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 字节"); setError(error, "梯形图网络 ID 不能超过 128 个 UTF-8 字节");
return false; 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; return false;
} }
std::vector<std::string> node_ids; std::vector<std::string> node_ids;


+ 2
- 0
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 kMaximumTextBytes = 4096U;
constexpr std::size_t kMaximumPropertyKeyBytes = 128U; constexpr std::size_t kMaximumPropertyKeyBytes = 128U;
constexpr std::size_t kMaximumPropertyValueBytes = 4096U; constexpr std::size_t kMaximumPropertyValueBytes = 4096U;
constexpr std::size_t kMaximumRegisterCommentBytes = 64U;
constexpr std::size_t kMaximumRungCommentBytes = 128U;


constexpr int kMaximumHmiPageWidth = 8192; constexpr int kMaximumHmiPageWidth = 8192;
constexpr int kMaximumHmiPageHeight = 8192; constexpr int kMaximumHmiPageHeight = 8192;


+ 13
- 2
app/src/domain/project_model.cpp Просмотреть файл

@@ -64,6 +64,12 @@ bool isBlank(const std::string &value)
[](unsigned char character) { return std::isspace(character) != 0; }); [](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 } // namespace


bool RegisterComment::validate(std::string *error) const bool RegisterComment::validate(std::string *error) const
@@ -78,9 +84,14 @@ bool RegisterComment::validate(std::string *error) const
setError(error, "软元件注释内容不能为空"); setError(error, "软元件注释内容不能为空");
return false; 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 false;
} }
return true; return true;


+ 14
- 2
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; }); [](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) std::string makeUniqueLogicId(const Project &project)
{ {
int suffix = 1; int suffix = 1;
@@ -865,11 +871,17 @@ LogicEditorResult LogicEditorService::updateRungComment(
{ {
return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); return failure(LogicEditorError::RungNotFound, "未找到梯形图网络");
} }
if (comment.size() > ProjectLimits::kMaximumTextBytes)
if (containsLineBreak(comment))
{
return failure(
LogicEditorError::InvalidOperation,
"梯形图网络注释只能使用单行文本");
}
if (comment.size() > ProjectLimits::kMaximumRungCommentBytes)
{ {
return failure( return failure(
LogicEditorError::InvalidOperation, LogicEditorError::InvalidOperation,
"梯形图网络注释不能超过 4096 个 UTF-8 字节");
"梯形图网络注释不能超过 128 个 UTF-8 字节");
} }
if (rung->comment == comment) if (rung->comment == comment)
{ {


+ 2
- 2
app/src/ui/logic_editor_widget.cpp Просмотреть файл

@@ -79,7 +79,7 @@ void drawRegisterComment(
comment, Qt::ElideRight, static_cast<int>(kCellWidth - 8.0)); comment, Qt::ElideRight, static_cast<int>(kCellWidth - 8.0));
painter->drawText( painter->drawText(
QRectF(-kCellWidth / 2.0, top, kCellWidth, 18), QRectF(-kCellWidth / 2.0, top, kCellWidth, 18),
Qt::AlignCenter,
Qt::AlignCenter | Qt::TextSingleLine,
visible_comment); visible_comment);
} }


@@ -810,7 +810,7 @@ public:
comment_, Qt::ElideRight, static_cast<int>(width_ - 16.0)); comment_, Qt::ElideRight, static_cast<int>(width_ - 16.0));
painter->drawText( painter->drawText(
QRectF(kSceneMargin + 8, 24, width_ - 16, 22), QRectF(kSceneMargin + 8, 24, width_ - 16, 22),
Qt::AlignLeft | Qt::AlignVCenter,
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine,
visible_comment); visible_comment);
} }
painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1)); painter->setPen(QPen(QColor(QStringLiteral("#d4dce1")), 1));


+ 2
- 1
app/src/ui/main_window.cpp Просмотреть файл

@@ -1339,10 +1339,11 @@ void MainWindow::editSelectedRungComment()
return; return;
} }
bool accepted = false; bool accepted = false;
const QString comment = QInputDialog::getMultiLineText(
const QString comment = QInputDialog::getText(
this, this,
tr("网络注释"), tr("网络注释"),
tr("说明"), tr("说明"),
QLineEdit::Normal,
fromUtf8(rung->comment), fromUtf8(rung->comment),
&accepted); &accepted);
if (!accepted) if (!accepted)


+ 20
- 0
app/tests/domain_tests.cpp Просмотреть файл

@@ -497,9 +497,29 @@ void testTimerAndCommentBoundaries()


RegisterComment comment{RegisterAddress{RegisterArea::M, 0}, "启动按钮"}; RegisterComment comment{RegisterAddress{RegisterArea::M, 0}, "启动按钮"};
require(comment.validate(), "a nonblank register comment must be valid"); 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"; comment.text = " \t";
require(!comment.validate(), "a blank register comment must be rejected"); 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 project = makeValidProject();
project.registerComments = { project.registerComments = {
{RegisterAddress{RegisterArea::M, 0}, "启动按钮"}, {RegisterAddress{RegisterArea::M, 0}, "启动按钮"},


+ 10
- 0
app/tests/logic_editor_service_tests.cpp Просмотреть файл

@@ -1,4 +1,5 @@
#include "domain/project_storage.h" #include "domain/project_storage.h"
#include "domain/project_limits.h"
#include "services/logic_editor_service.h" #include "services/logic_editor_service.h"
#include "services/project_service.h" #include "services/project_service.h"


@@ -712,6 +713,15 @@ void testEdgeTimerNodesAndRungComments()
"the editor must apply TON preset properties"); "the editor must apply TON preset properties");
require(service.updateRungComment(logic_id, rung_id, "延时启动网络").succeeded, require(service.updateRungComment(logic_id, rung_id, "延时启动网络").succeeded,
"the editor must update a network comment by stable rung id"); "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); const LadderRung *rung = service.findRung(logic_id, rung_id);
require(rung != nullptr && rung->comment == "延时启动网络" require(rung != nullptr && rung->comment == "延时启动网络"


+ 52
- 0
app/tests/project_management_tests.cpp Просмотреть файл

@@ -435,6 +435,10 @@ void testExampleProjectRoundTrip()
"missing-register-comments.json"); "missing-register-comments.json");
const QString missing_rung_comment_path = directory.filePath( const QString missing_rung_comment_path = directory.filePath(
"missing-rung-comment.json"); "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, require(service.saveAs(first_path.toStdString()).succeeded,
"example project save must succeed"); "example project save must succeed");
const QByteArray saved_json = readBytes(first_path); const QByteArray saved_json = readBytes(first_path);
@@ -671,6 +675,41 @@ void testExampleProjectRoundTrip()
&& missing_result.storageError == ProjectStorageError::MissingField, && missing_result.storageError == ProjectStorageError::MissingField,
"the 1.0 schema must require rung comments without migration defaults"); "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(); QJsonObject missing_target = QJsonDocument::fromJson(saved_json).object();
QJsonArray pages = missing_target.value(QStringLiteral("hmiPages")).toArray(); QJsonArray pages = missing_target.value(QStringLiteral("hmiPages")).toArray();
QJsonObject main_page = pages.at(0).toObject(); QJsonObject main_page = pages.at(0).toObject();
@@ -887,6 +926,19 @@ void testRegisterCommentService()
"setting an existing address must update instead of duplicating it"); "setting an existing address must update instead of duplicating it");
require(!service.setComment(RegisterAddress{RegisterArea::M, 4}, " \t").succeeded, require(!service.setComment(RegisterAddress{RegisterArea::M, 4}, " \t").succeeded,
"blank register comments must be rejected by the service"); "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 require(service.removeComment(RegisterAddress{RegisterArea::M, 2}).succeeded
&& service.findComment(RegisterAddress{RegisterArea::M, 2}) == nullptr, && service.findComment(RegisterAddress{RegisterArea::M, 2}) == nullptr,
"register comments must be removable"); "register comments must be removable");


Загрузка…
Отмена
Сохранить